From 8bb6c0ddf0faed9f77611f3c5e48571bf03a24c5 Mon Sep 17 00:00:00 2001 From: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Wed, 24 Jul 2024 22:59:56 +0300 Subject: [PATCH 001/109] address_space: Fix windows placeholder mapping --- src/core/address_space.cpp | 8 +++----- src/core/address_space.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 1bc803a1f..42c16c609 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -59,10 +59,10 @@ struct AddressSpace::Impl { static constexpr size_t MaxReductions = 10; size_t reduction = 0; + size_t virtual_size = SystemManagedSize + SystemReservedSize + UserSize; for (u32 i = 0; i < MaxReductions; i++) { - req.LowestStartingAddress = reinterpret_cast(SYSTEM_MANAGED_MIN + reduction); virtual_base = static_cast(VirtualAlloc2( - process, NULL, SystemManagedSize + SystemReservedSize + UserSize - reduction, + process, NULL, virtual_size - reduction, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1)); if (virtual_base) { break; @@ -92,9 +92,7 @@ struct AddressSpace::Impl { const uintptr_t system_managed_addr = reinterpret_cast(system_managed_base); const uintptr_t system_reserved_addr = reinterpret_cast(system_reserved_base); const uintptr_t user_addr = reinterpret_cast(user_base); - placeholders.insert({system_managed_addr, system_managed_addr + system_managed_size}); - placeholders.insert({system_reserved_addr, system_reserved_addr + system_reserved_size}); - placeholders.insert({user_addr, user_addr + user_size}); + placeholders.insert({system_managed_addr, virtual_size - reduction}); // Allocate backing file that represents the total physical memory. backing_handle = diff --git a/src/core/address_space.h b/src/core/address_space.h index c181b3625..e25159023 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -22,7 +22,7 @@ constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL; constexpr VAddr SYSTEM_MANAGED_MIN = 0x00000400000ULL; constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL; -constexpr VAddr SYSTEM_RESERVED_MIN = 0x800000000ULL; +constexpr VAddr SYSTEM_RESERVED_MIN = 0x07FFFFC000ULL; #ifdef __APPLE__ // Can only comfortably reserve the first 0x7C0000000 of system reserved space. constexpr VAddr SYSTEM_RESERVED_MAX = 0xFBFFFFFFFULL; From 8fa6a8c036a89fae77a580a0338a45394d200566 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 25 Jul 2024 11:59:39 +0300 Subject: [PATCH 002/109] clang fix --- src/core/address_space.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 42c16c609..c3e0d77aa 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -61,9 +61,9 @@ struct AddressSpace::Impl { size_t reduction = 0; size_t virtual_size = SystemManagedSize + SystemReservedSize + UserSize; for (u32 i = 0; i < MaxReductions; i++) { - virtual_base = static_cast(VirtualAlloc2( - process, NULL, virtual_size - reduction, - MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1)); + virtual_base = static_cast(VirtualAlloc2(process, NULL, virtual_size - reduction, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, ¶m, 1)); if (virtual_base) { break; } From a2cd1669b65c0b5ed3d5d2d8d0f7b813d989b8e3 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:01:12 +0300 Subject: [PATCH 003/109] memory: Cleanups and refactors (#324) * memory: Various fixes * Added (Partial) sceKernelBatchMap/sceKernelBatchMap2 * memory: Rename and implement batch unmap * memory: Remove uneeded assert * memory: Commonize free search routine * memory: Contains check is inclusive * memory: Address some alignment issues * clang format --------- Co-authored-by: raziel1000 --- src/core/libraries/kernel/libkernel.cpp | 3 + .../libraries/kernel/memory_management.cpp | 48 ++++++ src/core/libraries/kernel/memory_management.h | 17 +- .../libraries/kernel/thread_management.cpp | 24 ++- src/core/libraries/playgo/playgo.cpp | 4 +- src/core/linker.cpp | 6 +- src/core/memory.cpp | 162 +++++++++--------- src/core/memory.h | 16 +- src/emulator.cpp | 4 +- .../backend/spirv/spirv_emit_context.cpp | 4 + 10 files changed, 198 insertions(+), 90 deletions(-) diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index f44d928bb..a7f619f1a 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -405,6 +405,9 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("VOx8NGmHXTs", "libkernel", 1, "libkernel", 1, 1, sceKernelGetCpumode); LIB_FUNCTION("Xjoosiw+XPI", "libkernel", 1, "libkernel", 1, 1, sceKernelUuidCreate); + LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap); + LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2); + // equeue LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue); LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteEqueue); diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index cdee3f465..e0509fb41 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -3,6 +3,7 @@ #include #include "common/alignment.h" +#include "common/assert.h" #include "common/logging/log.h" #include "common/singleton.h" #include "core/libraries/error_codes.h" @@ -225,4 +226,51 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut directMemoryEndOut); } +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, + int* numEntriesOut) { + return sceKernelBatchMap2(entries, numEntries, numEntriesOut, 0x10); // 0x10 : Fixed / 0x410 +} + +int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); + +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, + int* numEntriesOut, int flags) { + int processed = 0; + int result = 0; + for (int i = 0; i < numEntries; i++) { + if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) { + result = ORBIS_KERNEL_ERROR_EINVAL; + break; // break and assign a value to numEntriesOut. + } + + if (entries[i].operation == 0) { // MAP_DIRECT + result = sceKernelMapNamedDirectMemory(&entries[i].start, entries[i].length, + entries[i].protection, flags, + static_cast(entries[i].offset), 0, ""); + LOG_INFO( + Kernel_Vmm, + "BatchMap: entry = {}, operation = {}, len = {:#x}, offset = {:#x}, type = {}, " + "result = {}", + i, entries[i].operation, entries[i].length, entries[i].offset, (u8)entries[i].type, + result); + + if (result == 0) + processed++; + } else if (entries[i].operation == 1) { + result = sceKernelMunmap(entries[i].start, entries[i].length); + LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", + i, entries[i].operation, entries[i].length, result); + + if (result == 0) + processed++; + } else { + UNREACHABLE_MSG("called: Unimplemented Operation = {}", entries[i].operation); + } + } + if (numEntriesOut != NULL) { // can be zero. do not return an error code. + *numEntriesOut = processed; + } + return result; +} + } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 2a17f6ed8..25434ecbe 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -6,7 +6,7 @@ #include "common/bit_field.h" #include "common/types.h" -constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5376_MB; // ~ 6GB +constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 6_GB; // ~ 6GB namespace Libraries::Kernel { @@ -53,6 +53,16 @@ struct OrbisVirtualQueryInfo { std::array name; }; +struct OrbisKernelBatchMapEntry { + void* start; + off_t offset; + size_t length; + char protection; + char type; + short reserved; + int operation; +}; + u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, int memoryType, s64* physAddrOut); @@ -85,4 +95,9 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut void** directMemoryStartOut, void** directMemoryEndOut); +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, + int* numEntriesOut); +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, + int* numEntriesOut, int flags); + } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index e536412fd..7c075e871 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -785,7 +785,22 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(ScePthreadMutex* mutex) { int PS4_SYSV_ABI posix_pthread_cond_wait(ScePthreadCond* cond, ScePthreadMutex* mutex) { int result = scePthreadCondWait(cond, mutex); if (result < 0) { - UNREACHABLE(); + int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP + ? result + -SCE_KERNEL_ERROR_UNKNOWN + : POSIX_EOTHER; + return rt; + } + return result; +} + +int PS4_SYSV_ABI posix_pthread_cond_timedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, + u64 usec) { + int result = scePthreadCondTimedwait(cond, mutex, usec); + if (result < 0) { + int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP + ? result + -SCE_KERNEL_ERROR_UNKNOWN + : POSIX_EOTHER; + return rt; } return result; } @@ -1350,6 +1365,11 @@ int PS4_SYSV_ABI scePthreadOnce(int* once_control, void (*init_routine)(void)) { UNREACHABLE(); } +[[noreturn]] void PS4_SYSV_ABI posix_pthread_exit(void* value_ptr) { + pthread_exit(value_ptr); + UNREACHABLE(); +} + int PS4_SYSV_ABI scePthreadGetthreadid() { return (int)(size_t)g_pthread_self; } @@ -1401,6 +1421,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("4qGrR6eoP9Y", "libkernel", 1, "libkernel", 1, 1, scePthreadDetach); LIB_FUNCTION("3PtV6p3QNX4", "libkernel", 1, "libkernel", 1, 1, scePthreadEqual); LIB_FUNCTION("3kg7rT0NQIs", "libkernel", 1, "libkernel", 1, 1, scePthreadExit); + LIB_FUNCTION("FJrT5LuUBAU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_exit); LIB_FUNCTION("7Xl257M4VNI", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_equal); LIB_FUNCTION("h9CcP3J0oVM", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_join); LIB_FUNCTION("EI-5-jlq2dE", "libkernel", 1, "libkernel", 1, 1, scePthreadGetthreadid); @@ -1462,6 +1483,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy); LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", 1, 1, posix_pthread_cond_wait); LIB_FUNCTION("Op8TBGY5KHg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_wait); + LIB_FUNCTION("27bAgiJmOh0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_timedwait); LIB_FUNCTION("mkx2fVhNMsg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_broadcast); LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init); LIB_FUNCTION("mDmgMOGVUqg", "libScePosix", 1, "libkernel", 1, 1, diff --git a/src/core/libraries/playgo/playgo.cpp b/src/core/libraries/playgo/playgo.cpp index 1a335a2a3..e029413e5 100644 --- a/src/core/libraries/playgo/playgo.cpp +++ b/src/core/libraries/playgo/playgo.cpp @@ -8,8 +8,6 @@ #include "playgo.h" namespace Libraries::PlayGo { -// this lib is used to play as the game is being installed. -// can be skipped by just returning and assigning the correct values. s32 PS4_SYSV_ABI sceDbgPlayGoRequestNextChunk() { LOG_ERROR(Lib_PlayGo, "(STUBBED)called"); @@ -141,4 +139,4 @@ void RegisterlibScePlayGo(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("MPe0EeBGM-E", "libScePlayGo", 1, "libScePlayGo", 1, 0, scePlayGoTerminate); }; -} // namespace Libraries::PlayGo \ No newline at end of file +} // namespace Libraries::PlayGo diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 2e47d17d6..e4cbe5739 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -320,11 +320,15 @@ void Linker::InitTlsForThread(bool is_primary) { static constexpr size_t TlsAllocAlign = 0x20; const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize; + // If sceKernelMapNamedFlexibleMemory is being called from libkernel and addr = 0 + // it automatically places mappings in system reserved area instead of managed. + static constexpr VAddr KernelAllocBase = 0x880000000ULL; + // The kernel module has a few different paths for TLS allocation. // For SDK < 1.7 it allocates both main and secondary thread blocks using libc mspace/malloc. // In games compiled with newer SDK, the main thread gets mapped from flexible memory, // with addr = 0, so system managed area. Here we will only implement the latter. - void* addr_out{}; + void* addr_out{reinterpret_cast(KernelAllocBase)}; if (is_primary) { const size_t tls_aligned = Common::AlignUp(total_tls_size, 16_KB); const int ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory( diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9326ccaad..f2607bffd 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -4,7 +4,6 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/debug.h" -#include "common/scope_exit.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" @@ -55,7 +54,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr; // Add the allocated region to the list and commit its pages. - auto& area = AddDmemAllocation(free_addr, size); + auto& area = CarveDmemArea(free_addr, size); area.memory_type = memory_type; area.is_free = false; return free_addr; @@ -100,29 +99,32 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem alignment = alignment > 0 ? alignment : 16_KB; VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr; + // Fixed mapping means the virtual address must exactly match the provided one. + if (True(flags & MemoryMapFlags::Fixed)) { + const auto& vma = FindVMA(mapped_addr)->second; + // If the VMA is mapped, unmap the region first. + if (vma.IsMapped()) { + ASSERT_MSG(vma.base == mapped_addr && vma.size == size, + "Region must match when reserving a mapped region"); + UnmapMemory(mapped_addr, size); + } + const size_t remaining_size = vma.base + vma.size - mapped_addr; + ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size); + } + // Find the first free area starting with provided virtual address. if (False(flags & MemoryMapFlags::Fixed)) { - auto it = FindVMA(mapped_addr); - // If the VMA is free and contains the requested mapping we are done. - if (it->second.type == VMAType::Free && it->second.Contains(virtual_addr, size)) { - mapped_addr = virtual_addr; - } else { - // Search for the first free VMA that fits our mapping. - while (it->second.type != VMAType::Free || it->second.size < size) { - it++; - } - ASSERT(it != vma_map.end()); - const auto& vma = it->second; - mapped_addr = alignment > 0 ? Common::AlignUp(vma.base, alignment) : vma.base; - } + mapped_addr = SearchFree(mapped_addr, size, alignment); } // Add virtual memory area - auto& new_vma = AddMapping(mapped_addr, size); + const auto new_vma_handle = CarveVMA(mapped_addr, size); + auto& new_vma = new_vma_handle->second; new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); new_vma.prot = MemoryProt::NoAccess; new_vma.name = ""; new_vma.type = VMAType::Reserved; + MergeAdjacent(vma_map, new_vma_handle); *out_addr = std::bit_cast(mapped_addr); return ORBIS_OK; @@ -132,6 +134,9 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M MemoryMapFlags flags, VMAType type, std::string_view name, bool is_exec, PAddr phys_addr, u64 alignment) { std::scoped_lock lk{mutex}; + + // Certain games perform flexible mappings on loop to determine + // the available flexible memory size. Questionable but we need to handle this. if (type == VMAType::Flexible && flexible_usage + size > total_flexible_size) { return SCE_KERNEL_ERROR_ENOMEM; } @@ -140,91 +145,63 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M // 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; - SCOPE_EXIT { - auto& new_vma = AddMapping(mapped_addr, size); - new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); - new_vma.prot = prot; - new_vma.name = name; - new_vma.type = type; - - if (type == VMAType::Direct) { - new_vma.phys_base = phys_addr; - MapVulkanMemory(mapped_addr, size); - } - if (type == VMAType::Flexible) { - flexible_usage += size; - } - }; // Fixed mapping means the virtual address must exactly match the provided one. - if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) { + if (True(flags & MemoryMapFlags::Fixed)) { // This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen. const auto& vma = FindVMA(mapped_addr)->second; const size_t remaining_size = vma.base + vma.size - mapped_addr; - ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size); + ASSERT_MSG(!vma.IsMapped() && remaining_size >= size); } // Find the first free area starting with provided virtual address. if (False(flags & MemoryMapFlags::Fixed)) { - auto it = FindVMA(mapped_addr); - // If the VMA is free and contains the requested mapping we are done. - if (it->second.type == VMAType::Free && it->second.Contains(virtual_addr, size)) { - mapped_addr = virtual_addr; - } else { - // Search for the first free VMA that fits our mapping. - while (it->second.type != VMAType::Free || it->second.size < size) { - it++; - } - ASSERT(it != vma_map.end()); - const auto& vma = it->second; - mapped_addr = alignment > 0 ? Common::AlignUp(vma.base, alignment) : vma.base; - } + mapped_addr = SearchFree(mapped_addr, size, alignment); } // Perform the mapping. *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec); TRACK_ALLOC(*out_addr, size, "VMEM"); + + auto& new_vma = CarveVMA(mapped_addr, size)->second; + new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); + new_vma.prot = prot; + new_vma.name = name; + new_vma.type = type; + + if (type == VMAType::Direct) { + new_vma.phys_base = phys_addr; + MapVulkanMemory(mapped_addr, size); + } + if (type == VMAType::Flexible) { + flexible_usage += size; + } + return ORBIS_OK; } int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, uintptr_t fd, size_t offset) { - if (virtual_addr == 0) { - virtual_addr = impl.SystemManagedVirtualBase(); - } else { - LOG_INFO(Kernel_Vmm, "Virtual addr {:#x} with size {:#x}", virtual_addr, size); - } - - VAddr mapped_addr = 0; + VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; const size_t size_aligned = Common::AlignUp(size, 16_KB); // Find first free area to map the file. if (False(flags & MemoryMapFlags::Fixed)) { - auto it = FindVMA(virtual_addr); - while (it->second.type != VMAType::Free || it->second.size < size_aligned) { - it++; - } - ASSERT(it != vma_map.end()); - - mapped_addr = it->second.base; + mapped_addr = SearchFree(mapped_addr, size_aligned); } if (True(flags & MemoryMapFlags::Fixed)) { const auto& vma = FindVMA(virtual_addr)->second; const size_t remaining_size = vma.base + vma.size - virtual_addr; - ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) && - remaining_size >= size); - - mapped_addr = virtual_addr; + ASSERT_MSG(!vma.IsMapped() && remaining_size >= size); } // Map the file. impl.MapFile(mapped_addr, size, offset, std::bit_cast(prot), fd); // Add virtual memory area - auto& new_vma = AddMapping(mapped_addr, size_aligned); + auto& new_vma = CarveVMA(mapped_addr, size_aligned)->second; new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); new_vma.prot = prot; new_vma.name = "File"; @@ -238,10 +215,9 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { std::scoped_lock lk{mutex}; - // TODO: Partial unmaps are technically supported by the guest. - const auto it = vma_map.find(virtual_addr); - ASSERT_MSG(it != vma_map.end() && it->first == virtual_addr, - "Attempting to unmap partially mapped range"); + const auto it = FindVMA(virtual_addr); + ASSERT_MSG(it->second.Contains(virtual_addr, size), + "Existing mapping does not contain requested unmap range"); const auto type = it->second.type; const bool has_backing = type == VMAType::Direct || type == VMAType::File; @@ -253,11 +229,13 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { } // Mark region as free and attempt to coalesce it with neighbours. - auto& vma = it->second; + const auto new_it = CarveVMA(virtual_addr, size); + auto& vma = new_it->second; vma.type = VMAType::Free; vma.prot = MemoryProt::NoAccess; vma.phys_base = 0; - MergeAdjacent(vma_map, it); + vma.disallow_merge = false; + MergeAdjacent(vma_map, new_it); // Unmap the memory region. impl.Unmap(virtual_addr, size, has_backing); @@ -288,10 +266,10 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, std::scoped_lock lk{mutex}; auto it = FindVMA(addr); - if (it->second.type == VMAType::Free && flags == 1) { + if (!it->second.IsMapped() && flags == 1) { it++; } - if (it->second.type == VMAType::Free) { + if (!it->second.IsMapped()) { LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region"); return ORBIS_KERNEL_ERROR_EACCES; } @@ -360,14 +338,38 @@ std::pair MemoryManager::GetVulkanBuffer(VAddr addr) { return std::make_pair(*it->second.buffer, addr - it->first); } -VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) { +VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { + auto it = FindVMA(virtual_addr); + // If the VMA is free and contains the requested mapping we are done. + if (it->second.IsFree() && it->second.Contains(virtual_addr, size)) { + return virtual_addr; + } + // Search for the first free VMA that fits our mapping. + const auto is_suitable = [&] { + if (!it->second.IsFree()) { + return false; + } + const auto& vma = it->second; + virtual_addr = Common::AlignUp(vma.base, alignment); + // Sometimes the alignment itself might be larger than the VMA. + if (virtual_addr > vma.base + vma.size) { + return false; + } + const size_t remaining_size = vma.base + vma.size - virtual_addr; + return remaining_size >= size; + }; + while (!is_suitable()) { + it++; + } + return virtual_addr; +} + +MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size) { auto vma_handle = FindVMA(virtual_addr); ASSERT_MSG(vma_handle != vma_map.end(), "Virtual address not in vm_map"); const VirtualMemoryArea& vma = vma_handle->second; - ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) && - vma.base <= virtual_addr, - "Adding a mapping to already mapped region"); + ASSERT_MSG(vma.base <= virtual_addr, "Adding a mapping to already mapped region"); const VAddr start_in_vma = virtual_addr - vma.base; const VAddr end_in_vma = start_in_vma + size; @@ -382,10 +384,10 @@ VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) { vma_handle = Split(vma_handle, start_in_vma); } - return vma_handle->second; + return vma_handle; } -DirectMemoryArea& MemoryManager::AddDmemAllocation(PAddr addr, size_t size) { +DirectMemoryArea& MemoryManager::CarveDmemArea(PAddr addr, size_t size) { auto dmem_handle = FindDmemArea(addr); ASSERT_MSG(dmem_handle != dmem_map.end(), "Physical address not in dmem_map"); diff --git a/src/core/memory.h b/src/core/memory.h index 93aef2d8c..ff4af5cd2 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -89,7 +89,15 @@ struct VirtualMemoryArea { uintptr_t fd = 0; bool Contains(VAddr addr, size_t size) const { - return addr >= base && (addr + size) < (base + this->size); + return addr >= base && (addr + size) <= (base + this->size); + } + + bool IsFree() const noexcept { + return type == VMAType::Free; + } + + bool IsMapped() const noexcept { + return type != VMAType::Free && type != VMAType::Reserved; } bool CanMergeWith(const VirtualMemoryArea& next) const { @@ -198,9 +206,11 @@ private: return iter; } - VirtualMemoryArea& AddMapping(VAddr virtual_addr, size_t size); + VAddr SearchFree(VAddr virtual_addr, size_t size, u32 alignment = 0); - DirectMemoryArea& AddDmemAllocation(PAddr addr, size_t size); + VMAHandle CarveVMA(VAddr virtual_addr, size_t size); + + DirectMemoryArea& CarveDmemArea(PAddr addr, size_t size); VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); diff --git a/src/emulator.cpp b/src/emulator.cpp index 5e584eee9..47ac57acf 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -150,10 +150,12 @@ void Emulator::Run(const std::filesystem::path& file) { } void Emulator::LoadSystemModules(const std::filesystem::path& file) { - constexpr std::array ModulesToLoad{ + constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", nullptr}, {"libSceFiber.sprx", nullptr}, {"libSceUlt.sprx", nullptr}, + {"libSceJson.sprx", nullptr}, + {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 16c10f53c..9ce87add2 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -388,6 +388,10 @@ spv::ImageFormat GetFormat(const AmdGpu::Image& image) { image.GetNumberFmt() == AmdGpu::NumberFormat::Unorm) { return spv::ImageFormat::Rgba8; } + if (image.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8 && + image.GetNumberFmt() == AmdGpu::NumberFormat::Uint) { + return spv::ImageFormat::Rgba8ui; + } UNREACHABLE(); } From b2ba84aa111fe6ed5409d8d0cde5614dcd6f6f3b Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Fri, 26 Jul 2024 00:25:29 -0300 Subject: [PATCH 004/109] BUFFER_STORE_DWORDX2 --- src/shader_recompiler/frontend/translate/translate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 9e67e82e5..15052b2af 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -612,6 +612,9 @@ void Translate(IR::Block* block, u32 block_base, std::span inst_l case Opcode::BUFFER_STORE_DWORD: translator.BUFFER_STORE_FORMAT(1, false, inst); break; + case Opcode::BUFFER_STORE_DWORDX2: + translator.BUFFER_STORE_FORMAT(2, false, inst); + break; case Opcode::BUFFER_STORE_DWORDX3: translator.BUFFER_STORE_FORMAT(3, false, inst); break; From 02d4af27df1ba9714d16e94fb217f3f23f49f210 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 24 Jul 2024 00:02:16 -0600 Subject: [PATCH 005/109] save_data: fix/accuracy for saveDataMem functions --- .../libraries/app_content/app_content.cpp | 7 ++++-- src/core/libraries/save_data/savedata.cpp | 24 ++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 7e9cf7a21..3c9b4e1e4 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -198,13 +198,16 @@ int PS4_SYSV_ABI sceAppContentTemporaryDataMount() { int PS4_SYSV_ABI sceAppContentTemporaryDataMount2(OrbisAppContentTemporaryDataOption option, OrbisAppContentMountPoint* mountPoint) { - if (std::string_view(mountPoint->data).empty()) // causing issues with save_data. + if (mountPoint == nullptr) return ORBIS_APP_CONTENT_ERROR_PARAMETER; auto* param_sfo = Common::Singleton::Instance(); std::string id(param_sfo->GetString("CONTENT_ID"), 7, 9); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id; auto* mnt = Common::Singleton::Instance(); - mnt->Mount(mount_dir, mountPoint->data); + if (std::string(mountPoint->data).empty()) // killzone + mnt->Mount(mount_dir, "/temp0"); + else + mnt->Mount(mount_dir, mountPoint->data); LOG_INFO(Lib_AppContent, "sceAppContentTemporaryDataMount2: option = {}, mountPoint = {}", option, mountPoint->data); return ORBIS_OK; diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index db6d0964d..a8519ab79 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -186,21 +186,23 @@ int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { if (cond->dirName == nullptr) { // look for all dirs if no dir is provided. for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (std::filesystem::is_directory(entry.path())) { + if (std::filesystem::is_directory(entry.path()) && + entry.path().filename().string() != "sdmemory") { + // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. i++; result->dirNamesNum = 0; // why is it 1024? is it max? // copy dir name to be used by sceSaveDataMount in read mode. strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32); result->hitNum = i + 1; - result->dirNamesNum = i + 1; // to confirm - result->setNum = i + 1; // to confirm + result->dirNamesNum = i + 1; + result->setNum = i + 1; } } } else { // Need a game to test. strncpy(result->dirNames[0].data, cond->dirName->data, 32); result->hitNum = 1; - result->dirNamesNum = 1; // to confirm - result->setNum = 1; // to confirm + result->dirNamesNum = 1; + result->setNum = 1; } } else { result->hitNum = 0; @@ -321,7 +323,7 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, const int64_t offset) { const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "save_mem1.sav"; + std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { @@ -336,7 +338,7 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(getParam->userId) / game_serial; + std::to_string(getParam->userId) / game_serial / "sdmemory"; if (getParam == nullptr) return ORBIS_SAVE_DATA_ERROR_PARAMETER; if (getParam->data != nullptr) { @@ -604,7 +606,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, const size_t bufSize, const int64_t offset) { LOG_INFO(Lib_SaveData, "called"); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "save_mem1.sav"; + std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); file.Seek(offset); @@ -616,7 +618,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { LOG_INFO(Lib_SaveData, "called: dataNum = {}, slotId= {}", setParam->dataNum, setParam->slotId); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setParam->userId) / game_serial; + std::to_string(setParam->userId) / game_serial / "sdmemory"; if (setParam->data != nullptr) { Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Write); if (!file.IsOpen()) @@ -644,7 +646,7 @@ int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, LOG_INFO(Lib_SaveData, "called:userId = {}, memorySize = {}", userId, memorySize); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial; + std::to_string(userId) / game_serial / "sdmemory"; if (std::filesystem::exists(mount_dir)) { return ORBIS_SAVE_DATA_ERROR_EXISTS; @@ -663,7 +665,7 @@ int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2 LOG_INFO(Lib_SaveData, "called"); // if (setupParam->option == 1) { // check this later. const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setupParam->userId) / game_serial; + std::to_string(setupParam->userId) / game_serial / "sdmemory"; if (std::filesystem::exists(mount_dir) && std::filesystem::exists(mount_dir / "save_mem2.sav")) { Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); From f29293c9fb9c1e98c06a1b063178b73205877167 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 24 Jul 2024 00:03:26 -0600 Subject: [PATCH 006/109] thread_management: some pthread functions --- .../libraries/kernel/thread_management.cpp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 7c075e871..bbd926d09 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1340,6 +1340,10 @@ int PS4_SYSV_ABI posix_sem_post(sem_t* sem) { return sem_post(sem); } +int PS4_SYSV_ABI posix_sem_destroy(sem_t* sem) { + return sem_destroy(sem); +} + int PS4_SYSV_ABI posix_sem_getvalue(sem_t* sem, int* sval) { return sem_getvalue(sem, sval); } @@ -1403,6 +1407,26 @@ int PS4_SYSV_ABI posix_pthread_condattr_setclock(ScePthreadCondattr* attr, clock return SCE_OK; } +int PS4_SYSV_ABI posix_pthread_getschedparam(ScePthread thread, int* policy, + SceKernelSchedParam* param) { + return scePthreadGetschedparam(thread, policy, param); +} + +int PS4_SYSV_ABI posix_pthread_setschedparam(ScePthread thread, int policy, + const SceKernelSchedParam* param) { + return scePthreadSetschedparam(thread, policy, param); +} + +int PS4_SYSV_ABI posix_pthread_attr_getschedpolicy(const ScePthreadAttr* attr, int* policy) { + return scePthreadAttrGetschedpolicy(attr, policy); +} + +int PS4_SYSV_ABI scePthreadRename(ScePthread thread, const char* name) { + thread->name = name; + LOG_INFO(Kernel_Pthread, "scePthreadRename: name = {}", thread->name); + return SCE_OK; +} + void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("lZzFeSxPl08", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setcancelstate); LIB_FUNCTION("0TyVk4MSLt0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_init); @@ -1427,6 +1451,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("EI-5-jlq2dE", "libkernel", 1, "libkernel", 1, 1, scePthreadGetthreadid); LIB_FUNCTION("1tKyG7RlMJo", "libkernel", 1, "libkernel", 1, 1, scePthreadGetprio); LIB_FUNCTION("W0Hpm2X0uPE", "libkernel", 1, "libkernel", 1, 1, scePthreadSetprio); + LIB_FUNCTION("GBUY7ywdULE", "libkernel", 1, "libkernel", 1, 1, scePthreadRename); LIB_FUNCTION("aI+OeCz8xrQ", "libkernel", 1, "libkernel", 1, 1, scePthreadSelf); LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_self); @@ -1498,6 +1523,8 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("EjllaAqAPZo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_condattr_setclock); LIB_FUNCTION("Z4QosVuAsA0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_once); + LIB_FUNCTION("RtLRV-pBTTY", "libScePosix", 1, "libkernel", 1, 1, + posix_pthread_attr_getschedpolicy); // openorbis weird functions LIB_FUNCTION("7H0iTOciTLo", "libkernel", 1, "libkernel", 1, 1, posix_pthread_mutex_lock); @@ -1512,9 +1539,12 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("+U1R4WtXvoc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_detach); LIB_FUNCTION("CBNtXOoef-E", "libScePosix", 1, "libkernel", 1, 1, posix_sched_get_priority_max); LIB_FUNCTION("m0iS6jNsXds", "libScePosix", 1, "libkernel", 1, 1, posix_sched_get_priority_min); + LIB_FUNCTION("FIs3-UQT9sg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_getschedparam); + LIB_FUNCTION("Xs9hdiD7sAA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setschedparam); LIB_FUNCTION("pDuPEf3m4fI", "libScePosix", 1, "libkernel", 1, 1, posix_sem_init); LIB_FUNCTION("YCV5dGGBcCo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_wait); LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post); + LIB_FUNCTION("cDW233RAwWo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_destroy); LIB_FUNCTION("Bq+LRV-N6Hk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_getvalue); // libs RwlockSymbolsRegister(sym); From f35518d527d9eebe876ecf7bd8baca4032959418 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 24 Jul 2024 00:12:53 -0600 Subject: [PATCH 007/109] sdl window: Added game title (serial, title and app_ver) --- src/emulator.cpp | 14 +++++++++----- src/sdl_window.cpp | 7 ++++--- src/sdl_window.h | 5 ++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 47ac57acf..7985e9e26 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -51,9 +51,6 @@ Emulator::Emulator() { memory = Core::Memory::Instance(); controller = Common::Singleton::Instance(); linker = Common::Singleton::Instance(); - window = std::make_unique(WindowWidth, WindowHeight, controller); - - g_window = window.get(); } Emulator::~Emulator() { @@ -68,6 +65,8 @@ void Emulator::Run(const std::filesystem::path& file) { // Loading param.sfo file if exists std::string id; + std::string title; + std::string app_version; std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys"; if (std::filesystem::is_directory(sce_sys_folder)) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { @@ -75,10 +74,10 @@ void Emulator::Run(const std::filesystem::path& file) { auto* param_sfo = Common::Singleton::Instance(); param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - std::string title(param_sfo->GetString("TITLE")); + title = param_sfo->GetString("TITLE"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); - std::string app_version = param_sfo->GetString("APP_VER"); + app_version = param_sfo->GetString("APP_VER"); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); } else if (entry.path().filename() == "pic0.png" || entry.path().filename() == "pic1.png") { @@ -92,6 +91,11 @@ void Emulator::Run(const std::filesystem::path& file) { } } } + std::string game_title = id + " - " + title + " <" + app_version + ">"; + window = + std::make_unique(WindowWidth, WindowHeight, controller, game_title); + + g_window = window.get(); const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir) / id; if (!std::filesystem::exists(mount_data_dir)) { diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index c4fcbcfae..c0e9afddd 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -18,14 +18,15 @@ namespace Frontend { -WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_) - : width{width_}, height{height_}, controller{controller_} { +WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, + std::string game_title_) + : width{width_}, height{height_}, controller{controller_}, game_title{game_title_} { if (SDL_Init(SDL_INIT_VIDEO) < 0) { UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); } SDL_InitSubSystem(SDL_INIT_AUDIO); - const std::string title = "shadPS4 v" + std::string(Common::VERSION); + const std::string title = "shadPS4 v" + std::string(Common::VERSION) + " | " + game_title; SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); diff --git a/src/sdl_window.h b/src/sdl_window.h index 6e14fbd0e..688a44073 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -3,6 +3,7 @@ #pragma once +#include #include "common/types.h" struct SDL_Window; @@ -40,7 +41,8 @@ struct WindowSystemInfo { class WindowSDL { public: - explicit WindowSDL(s32 width, s32 height, Input::GameController* controller); + explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, + std::string game_title); ~WindowSDL(); s32 getWidth() const { @@ -68,6 +70,7 @@ private: private: s32 width; s32 height; + std::string game_title; Input::GameController* controller; WindowSystemInfo window_info{}; SDL_Window* window{}; From a475b38e5f2b3fe6961a75644e699a225385d313 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 24 Jul 2024 01:53:07 -0600 Subject: [PATCH 008/109] - fixed sceAppContentTemporaryDataMount2 --- src/common/config.cpp | 2 +- src/core/libraries/app_content/app_content.cpp | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index a577b143a..218575ffe 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -220,7 +220,7 @@ void load(const std::filesystem::path& path) { auto general = generalResult.unwrap(); isNeo = toml::find_or(general, "isPS4Pro", false); - isFullscreen = toml::find_or(general, "Fullscreen", true); + isFullscreen = toml::find_or(general, "Fullscreen", false); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); isShowSplash = toml::find_or(general, "showSplash", true); diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 3c9b4e1e4..882f99e49 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -200,14 +200,7 @@ int PS4_SYSV_ABI sceAppContentTemporaryDataMount2(OrbisAppContentTemporaryDataOp OrbisAppContentMountPoint* mountPoint) { if (mountPoint == nullptr) return ORBIS_APP_CONTENT_ERROR_PARAMETER; - auto* param_sfo = Common::Singleton::Instance(); - std::string id(param_sfo->GetString("CONTENT_ID"), 7, 9); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id; - auto* mnt = Common::Singleton::Instance(); - if (std::string(mountPoint->data).empty()) // killzone - mnt->Mount(mount_dir, "/temp0"); - else - mnt->Mount(mount_dir, mountPoint->data); + strncpy(mountPoint->data, "/temp0", 16); LOG_INFO(Lib_AppContent, "sceAppContentTemporaryDataMount2: option = {}, mountPoint = {}", option, mountPoint->data); return ORBIS_OK; From b4916ef2cab28ad77e4157fb5eb3d5a70a271f7b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 24 Jul 2024 19:02:22 +0300 Subject: [PATCH 009/109] some fixup to playgo , makes Worms go further --- CMakeLists.txt | 2 ++ src/core/file_format/playgo_chunk.cpp | 16 ++++++++++++++ src/core/file_format/playgo_chunk.h | 31 +++++++++++++++++++++++++++ src/core/libraries/playgo/playgo.cpp | 18 +++++++++++----- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/core/file_format/playgo_chunk.cpp create mode 100644 src/core/file_format/playgo_chunk.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ec7cd54b..880d1cf58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/pkg_type.h src/core/file_format/psf.cpp src/core/file_format/psf.h + src/core/file_format/playgo_chunk.cpp + src/core/file_format/playgo_chunk.h src/core/file_format/trp.cpp src/core/file_format/trp.h src/core/file_format/splash.h diff --git a/src/core/file_format/playgo_chunk.cpp b/src/core/file_format/playgo_chunk.cpp new file mode 100644 index 000000000..43d8a4ded --- /dev/null +++ b/src/core/file_format/playgo_chunk.cpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/io_file.h" + +#include "playgo_chunk.h" + +bool PlaygoChunk::Open(const std::filesystem::path& filepath) { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } + file.Read(playgoHeader); + + return true; +} \ No newline at end of file diff --git a/src/core/file_format/playgo_chunk.h b/src/core/file_format/playgo_chunk.h new file mode 100644 index 000000000..d17d24bf9 --- /dev/null +++ b/src/core/file_format/playgo_chunk.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "common/types.h" + +struct PlaygoHeader { + u32 magic; + + u16 version_major; + u16 version_minor; + u16 image_count; + u16 chunk_count; + u16 mchunk_count; + u16 scenario_count; + // TODO fill the rest +}; +class PlaygoChunk { +public: + PlaygoChunk() = default; + ~PlaygoChunk() = default; + + bool Open(const std::filesystem::path& filepath); + PlaygoHeader GetPlaygoHeader() { + return playgoHeader; + } + +private: + PlaygoHeader playgoHeader; +}; \ No newline at end of file diff --git a/src/core/libraries/playgo/playgo.cpp b/src/core/libraries/playgo/playgo.cpp index e029413e5..a3af8b4c9 100644 --- a/src/core/libraries/playgo/playgo.cpp +++ b/src/core/libraries/playgo/playgo.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "common/logging/log.h" #include "common/singleton.h" #include "core/libraries/error_codes.h" @@ -50,9 +51,16 @@ s32 PS4_SYSV_ABI scePlayGoGetLocus(OrbisPlayGoHandle handle, const OrbisPlayGoCh uint32_t numberOfEntries, OrbisPlayGoLocus* outLoci) { LOG_ERROR(Lib_PlayGo, "(STUBBED)called handle = {}, chunkIds = {}, numberOfEntries = {}", handle, *chunkIds, numberOfEntries); - // assign all now so that scePlayGoGetLocus is not called again for every single entry - std::fill(outLoci, outLoci + numberOfEntries, - OrbisPlayGoLocusValue::ORBIS_PLAYGO_LOCUS_LOCAL_FAST); + + auto* playgo = Common::Singleton::Instance(); + + for (uint32_t i = 0; i < numberOfEntries; i++) { + if (chunkIds[i] <= playgo->GetPlaygoHeader().mchunk_count) { + outLoci[i] = OrbisPlayGoLocusValue::ORBIS_PLAYGO_LOCUS_LOCAL_FAST; + } else { + return ORBIS_PLAYGO_ERROR_BAD_CHUNK_ID; + } + } return ORBIS_OK; } @@ -68,7 +76,7 @@ s32 PS4_SYSV_ABI scePlayGoGetProgress(OrbisPlayGoHandle handle, const OrbisPlayG s32 PS4_SYSV_ABI scePlayGoGetToDoList(OrbisPlayGoHandle handle, OrbisPlayGoToDo* outTodoList, u32 numberOfEntries, u32* outEntries) { LOG_ERROR(Lib_PlayGo, "(STUBBED)called"); - if (handle != shadMagic) + if (handle != 1) return ORBIS_PLAYGO_ERROR_BAD_HANDLE; if (outTodoList == nullptr) return ORBIS_PLAYGO_ERROR_BAD_POINTER; @@ -86,7 +94,7 @@ s32 PS4_SYSV_ABI scePlayGoInitialize(OrbisPlayGoInitParams* param) { } s32 PS4_SYSV_ABI scePlayGoOpen(OrbisPlayGoHandle* outHandle, const void* param) { - *outHandle = shadMagic; + *outHandle = 1; LOG_INFO(Lib_PlayGo, "(STUBBED)called"); return ORBIS_OK; } From b62836d29f3a824440ddb172190dd84288700388 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 24 Jul 2024 19:02:38 +0300 Subject: [PATCH 010/109] forgot a file --- src/emulator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/emulator.cpp b/src/emulator.cpp index 7985e9e26..e8c53725d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -79,6 +80,9 @@ void Emulator::Run(const std::filesystem::path& file) { u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); app_version = param_sfo->GetString("APP_VER"); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); + } else if (entry.path().filename() == "playgo-chunk.dat") { + auto* playgo = Common::Singleton::Instance(); + playgo->Open(sce_sys_folder.string() + "/playgo-chunk.dat"); } else if (entry.path().filename() == "pic0.png" || entry.path().filename() == "pic1.png") { auto* splash = Common::Singleton::Instance(); From fa76a723adeef84b4ca51ede771b600f2ad7a1cf Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Thu, 25 Jul 2024 01:13:14 -0600 Subject: [PATCH 011/109] Applied feedback from @raphaelthegreat --- src/core/libraries/kernel/memory_management.cpp | 5 +++-- src/core/libraries/kernel/memory_management.h | 8 ++++++++ src/core/libraries/kernel/thread_management.cpp | 5 +++++ src/emulator.cpp | 3 ++- src/sdl_window.cpp | 6 +++--- src/sdl_window.h | 3 +-- 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index e0509fb41..7ba85de21 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -228,7 +228,8 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, int* numEntriesOut) { - return sceKernelBatchMap2(entries, numEntries, numEntriesOut, 0x10); // 0x10 : Fixed / 0x410 + return sceKernelBatchMap2(entries, numEntries, numEntriesOut, + MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410? } int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); @@ -243,7 +244,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn break; // break and assign a value to numEntriesOut. } - if (entries[i].operation == 0) { // MAP_DIRECT + if (entries[i].operation == MemoryOpTypes::SCE_KERNEL_MAP_OP_MAP_DIRECT) { result = sceKernelMapNamedDirectMemory(&entries[i].start, entries[i].length, entries[i].protection, flags, static_cast(entries[i].offset), 0, ""); diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 25434ecbe..df94e677a 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -31,6 +31,14 @@ enum MemoryProtection : u32 { SCE_KERNEL_PROT_GPU_RW = 0x30 // Permit reads/writes from the GPU }; +enum MemoryOpTypes : u32 { + SCE_KERNEL_MAP_OP_MAP_DIRECT = 0, + SCE_KERNEL_MAP_OP_UNMAP = 1, + SCE_KERNEL_MAP_OP_PROTECT = 2, + SCE_KERNEL_MAP_OP_MAP_FLEXIBLE = 3, + SCE_KERNEL_MAP_OP_TYPE_PROTECT = 4 +}; + struct OrbisQueryInfo { uintptr_t start; uintptr_t end; diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index bbd926d09..b18bb0439 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1336,6 +1336,10 @@ int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) { return sem_wait(sem); } +int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) { + return sem_timedwait(sem, t); +} + int PS4_SYSV_ABI posix_sem_post(sem_t* sem) { return sem_post(sem); } @@ -1543,6 +1547,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Xs9hdiD7sAA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setschedparam); LIB_FUNCTION("pDuPEf3m4fI", "libScePosix", 1, "libkernel", 1, 1, posix_sem_init); LIB_FUNCTION("YCV5dGGBcCo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_wait); + LIB_FUNCTION("w5IHyvahg-o", "libScePosix", 1, "libkernel", 1, 1, posix_sem_timedwait); LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post); LIB_FUNCTION("cDW233RAwWo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_destroy); LIB_FUNCTION("Bq+LRV-N6Hk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_getvalue); diff --git a/src/emulator.cpp b/src/emulator.cpp index e8c53725d..e617a9752 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -95,7 +95,8 @@ void Emulator::Run(const std::filesystem::path& file) { } } } - std::string game_title = id + " - " + title + " <" + app_version + ">"; + std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); + window = std::make_unique(WindowWidth, WindowHeight, controller, game_title); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index c0e9afddd..6d3e6848a 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -19,14 +19,14 @@ namespace Frontend { WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, - std::string game_title_) - : width{width_}, height{height_}, controller{controller_}, game_title{game_title_} { + std::string_view game_title) + : width{width_}, height{height_}, controller{controller_}{ if (SDL_Init(SDL_INIT_VIDEO) < 0) { UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); } SDL_InitSubSystem(SDL_INIT_AUDIO); - const std::string title = "shadPS4 v" + std::string(Common::VERSION) + " | " + game_title; + const std::string title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); diff --git a/src/sdl_window.h b/src/sdl_window.h index 688a44073..89b2a8771 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -42,7 +42,7 @@ struct WindowSystemInfo { class WindowSDL { public: explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, - std::string game_title); + std::string_view game_title); ~WindowSDL(); s32 getWidth() const { @@ -70,7 +70,6 @@ private: private: s32 width; s32 height; - std::string game_title; Input::GameController* controller; WindowSystemInfo window_info{}; SDL_Window* window{}; From a11ac5a687501da0dadb6fbb0689d424bc74b4ab Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Thu, 25 Jul 2024 01:15:44 -0600 Subject: [PATCH 012/109] ... --- src/sdl_window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 6d3e6848a..4570b64ef 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -20,7 +20,7 @@ namespace Frontend { WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, std::string_view game_title) - : width{width_}, height{height_}, controller{controller_}{ + : width{width_}, height{height_}, controller{controller_} { if (SDL_Init(SDL_INIT_VIDEO) < 0) { UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); } From 64d305faeb8440bf0006039d423aff82c5822918 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 26 Jul 2024 08:08:47 +0300 Subject: [PATCH 013/109] cleanup memory_management --- src/core/libraries/kernel/memory_management.cpp | 4 ++-- src/core/libraries/kernel/memory_management.h | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 7ba85de21..988b69d0c 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -244,7 +244,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn break; // break and assign a value to numEntriesOut. } - if (entries[i].operation == MemoryOpTypes::SCE_KERNEL_MAP_OP_MAP_DIRECT) { + if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_DIRECT) { result = sceKernelMapNamedDirectMemory(&entries[i].start, entries[i].length, entries[i].protection, flags, static_cast(entries[i].offset), 0, ""); @@ -257,7 +257,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn if (result == 0) processed++; - } else if (entries[i].operation == 1) { + } else if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_UNMAP) { result = sceKernelMunmap(entries[i].start, entries[i].length); LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", i, entries[i].operation, entries[i].length, result); diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index df94e677a..cc89dfa7d 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -32,11 +32,11 @@ enum MemoryProtection : u32 { }; enum MemoryOpTypes : u32 { - SCE_KERNEL_MAP_OP_MAP_DIRECT = 0, - SCE_KERNEL_MAP_OP_UNMAP = 1, - SCE_KERNEL_MAP_OP_PROTECT = 2, - SCE_KERNEL_MAP_OP_MAP_FLEXIBLE = 3, - SCE_KERNEL_MAP_OP_TYPE_PROTECT = 4 + ORBIS_KERNEL_MAP_OP_MAP_DIRECT = 0, + ORBIS_KERNEL_MAP_OP_UNMAP = 1, + ORBIS_KERNEL_MAP_OP_PROTECT = 2, + ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE = 3, + ORBIS_KERNEL_MAP_OP_TYPE_PROTECT = 4 }; struct OrbisQueryInfo { From a4912b8245ed8a55862feb61dd04b209e87dc009 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 26 Jul 2024 08:16:32 +0300 Subject: [PATCH 014/109] commented sem_timedwait for linux untill @squidbus fix it --- src/core/libraries/kernel/thread_management.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index b18bb0439..3e9e1994c 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1337,7 +1337,12 @@ int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) { } int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) { +#ifndef __APPLE__ return sem_timedwait(sem, t); +#else + LOG_ERROR(Kernel_Pthread, "Apple doesn't support sem_timedwait yet"); + return 0; // unsupported for apple yet +#endif } int PS4_SYSV_ABI posix_sem_post(sem_t* sem) { From 2841eba538f84c85139606c8ac4174bb07ffc64f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 26 Jul 2024 08:50:39 +0300 Subject: [PATCH 015/109] added /dev/urandom --- src/core/libraries/kernel/file_system.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 8734b9649..4a42b0d6f 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -53,6 +53,9 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { if (std::string_view{path} == "/dev/stdout") { return 2002; } + if (std::string_view{path} == "/dev/urandom") { + return 2003; + } u32 handle = h->CreateHandle(); auto* file = h->GetFile(handle); if (directory) { @@ -113,6 +116,9 @@ int PS4_SYSV_ABI sceKernelClose(int d) { if (d < 3) { // d probably hold an error code return ORBIS_KERNEL_ERROR_EPERM; } + if (d == 2003) { // dev/urandom case + return SCE_OK; + } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { @@ -223,6 +229,13 @@ s64 PS4_SYSV_ABI posix_lseek(int d, s64 offset, int whence) { } s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) { + if (d == 2003) // dev urandom case + { + auto rbuf = static_cast(buf); + for (size_t i = 0; i < nbytes; i++) + rbuf[i] = std::rand() & 0xFF; + return nbytes; + } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { @@ -460,6 +473,7 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { } void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) { + std::srand(std::time(nullptr)); LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen); LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open); LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose); From 600a13c38fb963ddb26653db87dd3e27497c8ab8 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Fri, 26 Jul 2024 08:07:22 -0600 Subject: [PATCH 016/109] fs: added /download0 mount fs: get rid of double slashes --- src/common/path_util.cpp | 1 + src/common/path_util.h | 2 ++ src/core/file_sys/fs.cpp | 22 +++++++++++++--------- src/emulator.cpp | 7 +++++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 429fe2a5c..f0f56b85f 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -72,6 +72,7 @@ static auto UserPaths = [] { create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR); create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); + create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index 57a9a73fa..67688f897 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -18,6 +18,7 @@ enum class PathType { TempDataDir, // Where game temp data is stored. GameDataDir, // Where game data is stored. SysModuleDir, // Where system modules are stored. + DownloadDir, // Where downloads/temp files are stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -31,6 +32,7 @@ constexpr auto SAVEDATA_DIR = "savedata"; constexpr auto GAMEDATA_DIR = "data"; constexpr auto TEMPDATA_DIR = "temp"; constexpr auto SYSMODULES_DIR = "sys_modules"; +constexpr auto DOWNLOAD_DIR = "download"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 2f57c9f34..3177770b0 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -26,23 +26,27 @@ void MntPoints::UnmountAll() { } std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) { - const MntPair* mount = GetMount(guest_directory); + // Evil games like Turok2 pass double slashes e.g /app0//game.kpf + auto corrected_path = guest_directory; + size_t pos = corrected_path.find("//"); + while (pos != std::string::npos) { + corrected_path.replace(pos, 2, "/"); + pos = corrected_path.find("//", pos + 1); + } + + const MntPair* mount = GetMount(corrected_path); if (!mount) { - return guest_directory; + return ""; } // Nothing to do if getting the mount itself. - if (guest_directory == mount->mount) { + if (corrected_path == mount->mount) { return mount->host_path; } // Remove device (e.g /app0) from path to retrieve relative path. - u32 pos = mount->mount.size() + 1; - // Evil games like Turok2 pass double slashes e.g /app0//game.kpf - if (guest_directory[pos] == '/') { - pos++; - } - const auto rel_path = std::string_view(guest_directory).substr(pos); + pos = mount->mount.size() + 1; + const auto rel_path = std::string_view(corrected_path).substr(pos); const auto host_path = mount->host_path / rel_path; if (!NeedsCaseInsensiveSearch) { return host_path; diff --git a/src/emulator.cpp b/src/emulator.cpp index e617a9752..0b542e68e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -113,6 +113,13 @@ void Emulator::Run(const std::filesystem::path& file) { } mnt->Mount(mount_temp_dir, "/temp0"); // called in app_content ==> stat/mkdir + const auto& mount_download_dir = + Common::FS::GetUserPath(Common::FS::PathType::DownloadDir) / id; + if (!std::filesystem::exists(mount_download_dir)) { + std::filesystem::create_directory(mount_download_dir); + } + mnt->Mount(mount_download_dir, "/download0"); + // Initialize kernel and library facilities. Libraries::Kernel::init_pthreads(); Libraries::InitHLELibs(&linker->GetHLESymbols()); From d84b4adc831037137ad7da211be3ef17a83e9819 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Sat, 27 Jul 2024 17:18:18 +0300 Subject: [PATCH 017/109] semaphore: Yet another race condition fix (#327) --- src/core/libraries/kernel/threads/semaphore.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index bfa6a68db..370dba445 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -41,7 +41,6 @@ public: AddWaiter(waiter); // Perform the wait. - std::exchange(lk, std::unique_lock{waiter.mutex}); return waiter.Wait(lk, timeout); } @@ -59,10 +58,9 @@ public: it++; continue; } - std::scoped_lock lk2{waiter.mutex}; + it = wait_list.erase(it); token_count -= waiter.need_count; waiter.cv.notify_one(); - it = wait_list.erase(it); } return true; @@ -84,7 +82,6 @@ public: public: struct WaitingThread : public ListBaseHook { - std::mutex mutex; std::string name; std::condition_variable cv; u32 priority; From 680192a0c452d2f0d79582494c4e0a6e47e6e365 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sat, 27 Jul 2024 11:23:59 -0300 Subject: [PATCH 018/109] 64 bits OP, impl V_ADDC_U32 & V_MAD_U64_U32 (#310) * impl V_ADDC_U32 & V_MAD_U64_U32 * shader recompiler: add 64 bits version to get register / GetSrc * fix V_ADDC_U32 carry * shader recompiler: removed automatic conversion to force_flt in GetSRc * shader recompiler: auto cast between u32 and u64 during ssa pass * shader recompiler: fix SetVectorReg64 & standardize switches-case * shader translate: fix overflow detection in V_ADD_I32 use vcc lo instead of vcc thread bit * shader recompiler: more 64-bit work - removed bit_size parameter from Get[Scalar/Vector]Register - add BitwiseOr64 - add SetDst64 as a replacement for SetScalarReg64 & SetVectorReg64 - add GetSrc64 for 64-bit value * shader recompiler: add V_MAD_U64_U32 vcc output - add V_MAD_U64_U32 vcc output - ILessThan for 64-bits * shader recompiler: removed unnecessary changes & missing consts * shader_recompiler: Add s64 type in constant propagation --- .../backend/spirv/emit_spirv_instructions.h | 8 +- .../backend/spirv/emit_spirv_integer.cpp | 23 +- src/shader_recompiler/frontend/opcodes.h | 8 +- .../frontend/translate/translate.cpp | 208 +++++++++++++++++- .../frontend/translate/translate.h | 8 +- .../frontend/translate/vector_alu.cpp | 44 +++- src/shader_recompiler/ir/ir_emitter.cpp | 67 +++++- src/shader_recompiler/ir/ir_emitter.h | 6 +- src/shader_recompiler/ir/opcodes.inc | 8 +- .../ir/passes/constant_propogation_pass.cpp | 12 +- .../ir/passes/ssa_rewrite_pass.cpp | 8 +- src/shader_recompiler/ir/value.h | 1 + 12 files changed, 361 insertions(+), 40 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index e2b411e47..80dd66b16 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -258,6 +258,7 @@ Id EmitISub64(EmitContext& ctx, Id a, Id b); Id EmitSMulExt(EmitContext& ctx, Id a, Id b); Id EmitUMulExt(EmitContext& ctx, Id a, Id b); Id EmitIMul32(EmitContext& ctx, Id a, Id b); +Id EmitIMul64(EmitContext& ctx, Id a, Id b); Id EmitSDiv32(EmitContext& ctx, Id a, Id b); Id EmitUDiv32(EmitContext& ctx, Id a, Id b); Id EmitINeg32(EmitContext& ctx, Id value); @@ -271,6 +272,7 @@ Id EmitShiftRightArithmetic32(EmitContext& ctx, Id base, Id shift); Id EmitShiftRightArithmetic64(EmitContext& ctx, Id base, Id shift); Id EmitBitwiseAnd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitBitwiseOr32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); +Id EmitBitwiseOr64(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitBitwiseXor32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitBitFieldInsert(EmitContext& ctx, Id base, Id insert, Id offset, Id count); Id EmitBitFieldSExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count); @@ -286,8 +288,10 @@ Id EmitSMax32(EmitContext& ctx, Id a, Id b); Id EmitUMax32(EmitContext& ctx, Id a, Id b); Id EmitSClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max); Id EmitUClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max); -Id EmitSLessThan(EmitContext& ctx, Id lhs, Id rhs); -Id EmitULessThan(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSLessThan32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSLessThan64(EmitContext& ctx, Id lhs, Id rhs); +Id EmitULessThan32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitULessThan64(EmitContext& ctx, Id lhs, Id rhs); Id EmitIEqual(EmitContext& ctx, Id lhs, Id rhs); Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs); Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index d5a0f2767..019ceb01b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -84,6 +84,10 @@ Id EmitIMul32(EmitContext& ctx, Id a, Id b) { return ctx.OpIMul(ctx.U32[1], a, b); } +Id EmitIMul64(EmitContext& ctx, Id a, Id b) { + return ctx.OpIMul(ctx.U64, a, b); +} + Id EmitSDiv32(EmitContext& ctx, Id a, Id b) { return ctx.OpSDiv(ctx.U32[1], a, b); } @@ -142,6 +146,13 @@ Id EmitBitwiseOr32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) { return result; } +Id EmitBitwiseOr64(EmitContext& ctx, IR::Inst* inst, Id a, Id b) { + const Id result{ctx.OpBitwiseOr(ctx.U64, a, b)}; + SetZeroFlag(ctx, inst, result); + SetSignFlag(ctx, inst, result); + return result; +} + Id EmitBitwiseXor32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) { const Id result{ctx.OpBitwiseXor(ctx.U32[1], a, b)}; SetZeroFlag(ctx, inst, result); @@ -231,11 +242,19 @@ Id EmitUClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max) { return result; } -Id EmitSLessThan(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSLessThan32(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpSLessThan(ctx.U1[1], lhs, rhs); } -Id EmitULessThan(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSLessThan64(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpSLessThan(ctx.U1[1], lhs, rhs); +} + +Id EmitULessThan32(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpULessThan(ctx.U1[1], lhs, rhs); +} + +Id EmitULessThan64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpULessThan(ctx.U1[1], lhs, rhs); } diff --git a/src/shader_recompiler/frontend/opcodes.h b/src/shader_recompiler/frontend/opcodes.h index d38140d8f..cdc1e4746 100644 --- a/src/shader_recompiler/frontend/opcodes.h +++ b/src/shader_recompiler/frontend/opcodes.h @@ -2392,10 +2392,10 @@ enum class OperandField : u32 { ConstFloatPos_4_0, ConstFloatNeg_4_0, VccZ = 251, - ExecZ, - Scc, - LdsDirect, - LiteralConst, + ExecZ = 252, + Scc = 253, + LdsDirect = 254, + LiteralConst = 255, VectorGPR, Undefined = 0xFFFFFFFF, diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 15052b2af..c4c6e5052 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -76,21 +76,21 @@ void Translator::EmitPrologue() { } } +template <> IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { - // Input modifiers work on float values. - force_flt |= operand.input_modifier.abs | operand.input_modifier.neg; - IR::U32F32 value{}; + + const bool is_float = operand.type == ScalarType::Float32 || force_flt; switch (operand.field) { case OperandField::ScalarGPR: - if (operand.type == ScalarType::Float32 || force_flt) { + if (is_float) { value = ir.GetScalarReg(IR::ScalarReg(operand.code)); } else { value = ir.GetScalarReg(IR::ScalarReg(operand.code)); } break; case OperandField::VectorGPR: - if (operand.type == ScalarType::Float32 || force_flt) { + if (is_float) { value = ir.GetVectorReg(IR::VectorReg(operand.code)); } else { value = ir.GetVectorReg(IR::VectorReg(operand.code)); @@ -164,15 +164,160 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { UNREACHABLE(); } - if (operand.input_modifier.abs) { - value = ir.FPAbs(value); - } - if (operand.input_modifier.neg) { - value = ir.FPNeg(value); + if (is_float) { + if (operand.input_modifier.abs) { + value = ir.FPAbs(value); + } + if (operand.input_modifier.neg) { + value = ir.FPNeg(value); + } } return value; } +template <> +IR::U32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { + return GetSrc(operand, force_flt); +} + +template <> +IR::F32 Translator::GetSrc(const InstOperand& operand, bool) { + return GetSrc(operand, true); +} + +template <> +IR::U64F64 Translator::GetSrc64(const InstOperand& operand, bool force_flt) { + IR::Value value_hi{}; + IR::Value value_lo{}; + + bool immediate = false; + const bool is_float = operand.type == ScalarType::Float64 || force_flt; + switch (operand.field) { + case OperandField::ScalarGPR: + if (is_float) { + value_lo = ir.GetScalarReg(IR::ScalarReg(operand.code)); + value_hi = ir.GetScalarReg(IR::ScalarReg(operand.code + 1)); + } else if (operand.type == ScalarType::Uint64 || operand.type == ScalarType::Sint64) { + value_lo = ir.GetScalarReg(IR::ScalarReg(operand.code)); + value_hi = ir.GetScalarReg(IR::ScalarReg(operand.code + 1)); + } else { + UNREACHABLE(); + } + break; + case OperandField::VectorGPR: + if (is_float) { + value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); + value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); + } else if (operand.type == ScalarType::Uint64 || operand.type == ScalarType::Sint64) { + value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); + value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); + } else { + UNREACHABLE(); + } + break; + case OperandField::ConstZero: + immediate = true; + if (force_flt) { + value_lo = ir.Imm64(0.0); + } else { + value_lo = ir.Imm64(u64(0U)); + } + break; + case OperandField::SignedConstIntPos: + ASSERT(!force_flt); + immediate = true; + value_lo = ir.Imm64(s64(operand.code) - SignedConstIntPosMin + 1); + break; + case OperandField::SignedConstIntNeg: + ASSERT(!force_flt); + immediate = true; + value_lo = ir.Imm64(-s64(operand.code) + SignedConstIntNegMin - 1); + break; + case OperandField::LiteralConst: + immediate = true; + if (force_flt) { + UNREACHABLE(); // There is a literal double? + } else { + value_lo = ir.Imm64(u64(operand.code)); + } + break; + case OperandField::ConstFloatPos_1_0: + immediate = true; + if (force_flt) { + value_lo = ir.Imm64(1.0); + } else { + value_lo = ir.Imm64(std::bit_cast(f64(1.0))); + } + break; + case OperandField::ConstFloatPos_0_5: + immediate = true; + value_lo = ir.Imm64(0.5); + break; + case OperandField::ConstFloatPos_2_0: + immediate = true; + value_lo = ir.Imm64(2.0); + break; + case OperandField::ConstFloatPos_4_0: + immediate = true; + value_lo = ir.Imm64(4.0); + break; + case OperandField::ConstFloatNeg_0_5: + immediate = true; + value_lo = ir.Imm64(-0.5); + break; + case OperandField::ConstFloatNeg_1_0: + immediate = true; + value_lo = ir.Imm64(-1.0); + break; + case OperandField::ConstFloatNeg_2_0: + immediate = true; + value_lo = ir.Imm64(-2.0); + break; + case OperandField::ConstFloatNeg_4_0: + immediate = true; + value_lo = ir.Imm64(-4.0); + break; + case OperandField::VccLo: { + value_lo = ir.GetVccLo(); + value_hi = ir.GetVccHi(); + } break; + case OperandField::VccHi: + UNREACHABLE(); + default: + UNREACHABLE(); + } + + IR::Value value; + + if (immediate) { + value = value_lo; + } else if (is_float) { + throw NotImplementedException("required OpPackDouble2x32 implementation"); + } else { + IR::Value packed = ir.CompositeConstruct(value_lo, value_hi); + value = ir.PackUint2x32(packed); + } + + if (is_float) { + if (operand.input_modifier.abs) { + value = ir.FPAbs(IR::F32F64(value)); + } + if (operand.input_modifier.neg) { + value = ir.FPNeg(IR::F32F64(value)); + } + } + return IR::U64F64(value); +} + +template <> +IR::U64 Translator::GetSrc64(const InstOperand& operand, bool force_flt) { + return GetSrc64(operand, force_flt); +} +template <> +IR::F64 Translator::GetSrc64(const InstOperand& operand, bool) { + return GetSrc64(operand, true); +} + void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { IR::U32F32 result = value; if (operand.output_modifier.multiplier != 0.f) { @@ -197,6 +342,43 @@ void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { } } +void Translator::SetDst64(const InstOperand& operand, const IR::U64F64& value_raw) { + IR::U64F64 value_untyped = value_raw; + + const bool is_float = value_raw.Type() == IR::Type::F64 || value_raw.Type() == IR::Type::F32; + if (is_float) { + if (operand.output_modifier.multiplier != 0.f) { + value_untyped = + ir.FPMul(value_untyped, ir.Imm64(f64(operand.output_modifier.multiplier))); + } + if (operand.output_modifier.clamp) { + value_untyped = ir.FPSaturate(value_raw); + } + } + const IR::U64 value = + is_float ? ir.BitCast(IR::F64{value_untyped}) : IR::U64{value_untyped}; + + const IR::Value unpacked{ir.UnpackUint2x32(value)}; + const IR::U32 lo{ir.CompositeExtract(unpacked, 0U)}; + const IR::U32 hi{ir.CompositeExtract(unpacked, 1U)}; + switch (operand.field) { + case OperandField::ScalarGPR: + ir.SetScalarReg(IR::ScalarReg(operand.code + 1), hi); + return ir.SetScalarReg(IR::ScalarReg(operand.code), lo); + case OperandField::VectorGPR: + ir.SetVectorReg(IR::VectorReg(operand.code + 1), hi); + return ir.SetVectorReg(IR::VectorReg(operand.code), lo); + case OperandField::VccLo: + UNREACHABLE(); + case OperandField::VccHi: + UNREACHABLE(); + case OperandField::M0: + break; + default: + UNREACHABLE(); + } +} + void Translator::EmitFetch(const GcnInst& inst) { // Read the pointer to the fetch shader assembly. const u32 sgpr_base = inst.src[0].code; @@ -320,6 +502,9 @@ void Translate(IR::Block* block, u32 block_base, std::span inst_l case Opcode::V_ADD_I32: translator.V_ADD_I32(inst); break; + case Opcode::V_ADDC_U32: + translator.V_ADDC_U32(inst); + break; case Opcode::V_CVT_F32_I32: translator.V_CVT_F32_I32(inst); break; @@ -470,6 +655,9 @@ void Translate(IR::Block* block, u32 block_base, std::span inst_l case Opcode::IMAGE_LOAD: translator.IMAGE_LOAD(false, inst); break; + case Opcode::V_MAD_U64_U32: + translator.V_MAD_U64_U32(inst); + break; case Opcode::V_CMP_GE_I32: translator.V_CMP_U32(ConditionOp::GE, true, false, inst); break; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 2aa6f7124..3203ad730 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -100,6 +100,7 @@ public: void V_AND_B32(const GcnInst& inst); void V_LSHLREV_B32(const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); + void V_ADDC_U32(const GcnInst& inst); void V_CVT_F32_I32(const GcnInst& inst); void V_CVT_F32_U32(const GcnInst& inst); void V_MAD_F32(const GcnInst& inst); @@ -129,6 +130,7 @@ public: void V_CVT_U32_F32(const GcnInst& inst); void V_SUBREV_F32(const GcnInst& inst); void V_SUBREV_I32(const GcnInst& inst); + void V_MAD_U64_U32(const GcnInst& inst); void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); void V_LSHRREV_B32(const GcnInst& inst); void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); @@ -186,8 +188,12 @@ public: void EXP(const GcnInst& inst); private: - IR::U32F32 GetSrc(const InstOperand& operand, bool flt_zero = false); + template + [[nodiscard]] T GetSrc(const InstOperand& operand, bool flt_zero = false); + template + [[nodiscard]] T GetSrc64(const InstOperand& operand, bool flt_zero = false); void SetDst(const InstOperand& operand, const IR::U32F32& value); + void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); private: IR::IREmitter ir; diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index ca648f882..1b2024f89 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -67,7 +67,8 @@ void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, is_xor ? ir.BitwiseXor(src0, src1) : ir.BitwiseOr(src0, src1)); + ir.SetVectorReg(dst_reg, + is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); } void Translator::V_AND_B32(const GcnInst& inst) { @@ -92,6 +93,30 @@ void Translator::V_ADD_I32(const GcnInst& inst) { // TODO: Carry } +void Translator::V_ADDC_U32(const GcnInst& inst) { + + const auto src0 = GetSrc(inst.src[0]); + const auto src1 = GetSrc(inst.src[1]); + + IR::U32 scarry; + if (inst.src_count == 3) { // VOP3 + IR::U1 thread_bit{ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[2].code))}; + scarry = IR::U32{ir.Select(thread_bit, ir.Imm32(1), ir.Imm32(0))}; + } else { // VOP2 + scarry = ir.GetVccLo(); + } + + const IR::U32 result = ir.IAdd(ir.IAdd(src0, src1), scarry); + + const IR::VectorReg dst_reg{inst.dst[0].code}; + ir.SetVectorReg(dst_reg, result); + + const IR::U1 less_src0 = ir.ILessThan(result, src0, false); + const IR::U1 less_src1 = ir.ILessThan(result, src1, false); + const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); + ir.SetVcc(did_overflow); +} + void Translator::V_CVT_F32_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::VectorReg dst_reg{inst.dst[0].code}; @@ -294,6 +319,23 @@ void Translator::V_SUBREV_I32(const GcnInst& inst) { // TODO: Carry-out } +void Translator::V_MAD_U64_U32(const GcnInst& inst) { + + const auto src0 = GetSrc(inst.src[0]); + const auto src1 = GetSrc(inst.src[1]); + const auto src2 = GetSrc64(inst.src[2]); + + const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + const IR::U64 sum_result = ir.IAdd(mul_result, src2); + + SetDst64(inst.dst[0], sum_result); + + const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); + const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); + const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); + ir.SetVcc(did_overflow); +} + void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index cd4fdaa29..6ea3123dd 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -964,8 +964,18 @@ IR::Value IREmitter::IMulExt(const U32& a, const U32& b, bool is_signed) { return Inst(is_signed ? Opcode::SMulExt : Opcode::UMulExt, a, b); } -U32 IREmitter::IMul(const U32& a, const U32& b) { - return Inst(Opcode::IMul32, a, b); +U32U64 IREmitter::IMul(const U32U64& a, const U32U64& b) { + if (a.Type() != b.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type()); + } + switch (a.Type()) { + case Type::U32: + return Inst(Opcode::IMul32, a, b); + case Type::U64: + return Inst(Opcode::IMul64, a, b); + default: + ThrowInvalidType(a.Type()); + } } U32 IREmitter::IDiv(const U32& a, const U32& b, bool is_signed) { @@ -1024,8 +1034,18 @@ U32 IREmitter::BitwiseAnd(const U32& a, const U32& b) { return Inst(Opcode::BitwiseAnd32, a, b); } -U32 IREmitter::BitwiseOr(const U32& a, const U32& b) { - return Inst(Opcode::BitwiseOr32, a, b); +U32U64 IREmitter::BitwiseOr(const U32U64& a, const U32U64& b) { + if (a.Type() != b.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type()); + } + switch (a.Type()) { + case Type::U32: + return Inst(Opcode::BitwiseOr32, a, b); + case Type::U64: + return Inst(Opcode::BitwiseOr64, a, b); + default: + ThrowInvalidType(a.Type()); + } } U32 IREmitter::BitwiseXor(const U32& a, const U32& b) { @@ -1095,8 +1115,18 @@ U32 IREmitter::UClamp(const U32& value, const U32& min, const U32& max) { return Inst(Opcode::UClamp32, value, min, max); } -U1 IREmitter::ILessThan(const U32& lhs, const U32& rhs, bool is_signed) { - return Inst(is_signed ? Opcode::SLessThan : Opcode::ULessThan, lhs, rhs); +U1 IREmitter::ILessThan(const U32U64& lhs, const U32U64& rhs, bool is_signed) { + if (lhs.Type() != rhs.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); + } + switch (lhs.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SLessThan32 : Opcode::ULessThan32, lhs, rhs); + case Type::U64: + return Inst(is_signed ? Opcode::SLessThan64 : Opcode::ULessThan64, lhs, rhs); + default: + ThrowInvalidType(lhs.Type()); + } } U1 IREmitter::IEqual(const U32U64& lhs, const U32U64& rhs) { @@ -1155,8 +1185,9 @@ U32U64 IREmitter::ConvertFToS(size_t bitsize, const F32F64& value) { ThrowInvalidType(value.Type()); } default: - UNREACHABLE_MSG("Invalid destination bitsize {}", bitsize); + break; } + throw NotImplementedException("Invalid destination bitsize {}", bitsize); } U32U64 IREmitter::ConvertFToU(size_t bitsize, const F32F64& value) { @@ -1183,13 +1214,17 @@ F32F64 IREmitter::ConvertSToF(size_t dest_bitsize, size_t src_bitsize, const Val switch (src_bitsize) { case 32: return Inst(Opcode::ConvertF32S32, value); + default: + break; } - break; case 64: switch (src_bitsize) { case 32: return Inst(Opcode::ConvertF64S32, value); + default: + break; } + default: break; } UNREACHABLE_MSG("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize); @@ -1203,13 +1238,17 @@ F32F64 IREmitter::ConvertUToF(size_t dest_bitsize, size_t src_bitsize, const Val return Inst(Opcode::ConvertF32U16, value); case 32: return Inst(Opcode::ConvertF32U32, value); + default: + break; } - break; case 64: switch (src_bitsize) { case 32: return Inst(Opcode::ConvertF64U32, value); + default: + break; } + default: break; } UNREACHABLE_MSG("Invalid bit size combination dst={} src={}", dest_bitsize, src_bitsize); @@ -1227,7 +1266,11 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) { switch (value.Type()) { case Type::U32: return Inst(Opcode::ConvertU16U32, value); + default: + break; } + default: + break; } throw NotImplementedException("Conversion from {} to {} bits", value.Type(), result_bitsize); } @@ -1238,13 +1281,17 @@ F16F32F64 IREmitter::FPConvert(size_t result_bitsize, const F16F32F64& value) { switch (value.Type()) { case Type::F32: return Inst(Opcode::ConvertF16F32, value); + default: + break; } - break; case 32: switch (value.Type()) { case Type::F16: return Inst(Opcode::ConvertF32F16, value); + default: + break; } + default: break; } throw NotImplementedException("Conversion from {} to {} bits", value.Type(), result_bitsize); diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index e7512430a..7ee4e8240 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -159,7 +159,7 @@ public: [[nodiscard]] Value IAddCary(const U32& a, const U32& b); [[nodiscard]] U32U64 ISub(const U32U64& a, const U32U64& b); [[nodiscard]] Value IMulExt(const U32& a, const U32& b, bool is_signed = false); - [[nodiscard]] U32 IMul(const U32& a, const U32& b); + [[nodiscard]] U32U64 IMul(const U32U64& a, const U32U64& b); [[nodiscard]] U32 IDiv(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32U64 INeg(const U32U64& value); [[nodiscard]] U32 IAbs(const U32& value); @@ -167,7 +167,7 @@ public: [[nodiscard]] U32U64 ShiftRightLogical(const U32U64& base, const U32& shift); [[nodiscard]] U32U64 ShiftRightArithmetic(const U32U64& base, const U32& shift); [[nodiscard]] U32 BitwiseAnd(const U32& a, const U32& b); - [[nodiscard]] U32 BitwiseOr(const U32& a, const U32& b); + [[nodiscard]] U32U64 BitwiseOr(const U32U64& a, const U32U64& b); [[nodiscard]] U32 BitwiseXor(const U32& a, const U32& b); [[nodiscard]] U32 BitFieldInsert(const U32& base, const U32& insert, const U32& offset, const U32& count); @@ -188,7 +188,7 @@ public: [[nodiscard]] U32 SClamp(const U32& value, const U32& min, const U32& max); [[nodiscard]] U32 UClamp(const U32& value, const U32& min, const U32& max); - [[nodiscard]] U1 ILessThan(const U32& lhs, const U32& rhs, bool is_signed); + [[nodiscard]] U1 ILessThan(const U32U64& lhs, const U32U64& rhs, bool is_signed); [[nodiscard]] U1 IEqual(const U32U64& lhs, const U32U64& rhs); [[nodiscard]] U1 ILessThanEqual(const U32& lhs, const U32& rhs, bool is_signed); [[nodiscard]] U1 IGreaterThan(const U32& lhs, const U32& rhs, bool is_signed); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 9aefc8b39..628b8d4fa 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -227,6 +227,7 @@ OPCODE(IAddCary32, U32x2, U32, OPCODE(ISub32, U32, U32, U32, ) OPCODE(ISub64, U64, U64, U64, ) OPCODE(IMul32, U32, U32, U32, ) +OPCODE(IMul64, U64, U64, U64, ) OPCODE(SMulExt, U32x2, U32, U32, ) OPCODE(UMulExt, U32x2, U32, U32, ) OPCODE(SDiv32, U32, U32, U32, ) @@ -242,6 +243,7 @@ OPCODE(ShiftRightArithmetic32, U32, U32, OPCODE(ShiftRightArithmetic64, U64, U64, U32, ) OPCODE(BitwiseAnd32, U32, U32, U32, ) OPCODE(BitwiseOr32, U32, U32, U32, ) +OPCODE(BitwiseOr64, U64, U64, U64, ) OPCODE(BitwiseXor32, U32, U32, U32, ) OPCODE(BitFieldInsert, U32, U32, U32, U32, U32, ) OPCODE(BitFieldSExtract, U32, U32, U32, U32, ) @@ -258,8 +260,10 @@ OPCODE(SMax32, U32, U32, OPCODE(UMax32, U32, U32, U32, ) OPCODE(SClamp32, U32, U32, U32, U32, ) OPCODE(UClamp32, U32, U32, U32, U32, ) -OPCODE(SLessThan, U1, U32, U32, ) -OPCODE(ULessThan, U1, U32, U32, ) +OPCODE(SLessThan32, U1, U32, U32, ) +OPCODE(SLessThan64, U1, U64, U64, ) +OPCODE(ULessThan32, U1, U32, U32, ) +OPCODE(ULessThan64, U1, U64, U64, ) OPCODE(IEqual, U1, U32, U32, ) OPCODE(SLessThanEqual, U1, U32, U32, ) OPCODE(ULessThanEqual, U1, U32, U32, ) diff --git a/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp index 7cd896fbd..13c0246ea 100644 --- a/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp @@ -21,6 +21,8 @@ template return value.F32(); } else if constexpr (std::is_same_v) { return value.U64(); + } else if constexpr (std::is_same_v) { + return static_cast(value.U64()); } } @@ -281,12 +283,18 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { return FoldLogicalOr(inst); case IR::Opcode::LogicalNot: return FoldLogicalNot(inst); - case IR::Opcode::SLessThan: + case IR::Opcode::SLessThan32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a < b; }); return; - case IR::Opcode::ULessThan: + case IR::Opcode::SLessThan64: + FoldWhenAllImmediates(inst, [](s64 a, s64 b) { return a < b; }); + return; + case IR::Opcode::ULessThan32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a < b; }); return; + case IR::Opcode::ULessThan64: + FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a < b; }); + return; case IR::Opcode::SLessThanEqual: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a <= b; }); return; diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 6a43ad6be..805914924 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -348,13 +348,15 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::GetThreadBitScalarReg: case IR::Opcode::GetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; - inst.ReplaceUsesWith( - pass.ReadVariable(reg, block, opcode == IR::Opcode::GetThreadBitScalarReg)); + const bool thread_bit = opcode == IR::Opcode::GetThreadBitScalarReg; + const IR::Value value = pass.ReadVariable(reg, block, thread_bit); + inst.ReplaceUsesWith(value); break; } case IR::Opcode::GetVectorRegister: { const IR::VectorReg reg{inst.Arg(0).VectorReg()}; - inst.ReplaceUsesWith(pass.ReadVariable(reg, block)); + const IR::Value value = pass.ReadVariable(reg, block); + inst.ReplaceUsesWith(value); break; } case IR::Opcode::GetGotoVariable: diff --git a/src/shader_recompiler/ir/value.h b/src/shader_recompiler/ir/value.h index a43c17f5b..db939eaa5 100644 --- a/src/shader_recompiler/ir/value.h +++ b/src/shader_recompiler/ir/value.h @@ -220,6 +220,7 @@ using F16 = TypedValue; using F32 = TypedValue; using F64 = TypedValue; using U32F32 = TypedValue; +using U64F64 = TypedValue; using U32U64 = TypedValue; using U16U32U64 = TypedValue; using F32F64 = TypedValue; From 361412031cffe2a3725759d41406dca7ff519a72 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sat, 27 Jul 2024 14:16:21 -0300 Subject: [PATCH 019/109] fix tls patch on windows (#328) * fix tls patch on windows based on TlsGetValue in kernel32.dll * fix tls patch on windows for expansion slots --- src/core/cpu_patches.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 2a9cf5e29..42318822b 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -285,20 +285,24 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe const auto slot = GetTcbKey(); #if defined(_WIN32) - // The following logic is based on the wine implementation of TlsGetValue - // https://github.com/wine-mirror/wine/blob/a27b9551/dlls/kernelbase/thread.c#L719 + // The following logic is based on the Kernel32.dll asm of TlsGetValue static constexpr u32 TlsSlotsOffset = 0x1480; static constexpr u32 TlsExpansionSlotsOffset = 0x1780; static constexpr u32 TlsMinimumAvailable = 64; - const u32 teb_offset = slot < TlsMinimumAvailable ? TlsSlotsOffset : TlsExpansionSlotsOffset; - const u32 tls_index = slot < TlsMinimumAvailable ? slot : slot - TlsMinimumAvailable; - // Load the pointer to the table of TLS slots. c.putSeg(gs); - c.mov(dst, ptr[reinterpret_cast(teb_offset)]); - // Load the pointer to our buffer. - c.mov(dst, qword[dst + tls_index * sizeof(LPVOID)]); + if (slot < TlsMinimumAvailable) { + // Load the pointer to TLS slots. + c.mov(dst, ptr[reinterpret_cast(TlsSlotsOffset + slot * sizeof(LPVOID))]); + } else { + const u32 tls_index = slot - TlsMinimumAvailable; + + // Load the pointer to the table of TLS expansion slots. + c.mov(dst, ptr[reinterpret_cast(TlsExpansionSlotsOffset)]); + // Load the pointer to our buffer. + c.mov(dst, qword[dst + tls_index * sizeof(LPVOID)]); + } #elif defined(__APPLE__) // The following logic is based on the Darwin implementation of _os_tsd_get_direct, used by // pthread_getspecific https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L89-L96 From 0d6edaa0a0282571b07d5e518e72231a2d863927 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:54:09 +0300 Subject: [PATCH 020/109] Move presentation to separate thread/improve sync (#303) * video_out: Move presentation to separate thread * liverpool: Better sync for CPU flips * driver: Make flip blocking * videoout: Proper flip rate and vblank management * config: Add vblank divider option * clang format * videoout: added `sceVideoOutWaitVblank` * clang format * vk_scheduler: Silly merge conflict * externals: Add renderdoc API * clang format * reuse * rdoc: manual capture trigger * clang fmt --------- Co-authored-by: psucien <168137814+psucien@users.noreply.github.com> --- .reuse/dep5 | 4 + CMakeLists.txt | 5 +- externals/CMakeLists.txt | 7 + externals/renderdoc/renderdoc_app.h | 741 ++++++++++++++++++ src/common/config.cpp | 18 +- src/common/config.h | 2 + src/common/path_util.cpp | 1 + src/common/path_util.h | 2 + src/core/libraries/gnmdriver/gnmdriver.cpp | 4 +- src/core/libraries/kernel/event_queue.cpp | 4 +- src/core/libraries/libs.cpp | 2 +- src/core/libraries/videoout/driver.cpp | 103 ++- src/core/libraries/videoout/driver.h | 38 +- src/core/libraries/videoout/video_out.cpp | 22 +- src/core/libraries/videoout/video_out.h | 6 +- src/emulator.cpp | 27 +- src/sdl_window.cpp | 8 +- src/video_core/amdgpu/liverpool.cpp | 35 +- src/video_core/amdgpu/liverpool.h | 22 + src/video_core/amdgpu/pm4_cmds.h | 14 +- src/video_core/renderdoc.cpp | 120 +++ src/video_core/renderdoc.h | 25 + .../renderer_vulkan/renderer_vulkan.cpp | 98 +-- .../renderer_vulkan/renderer_vulkan.h | 27 +- .../renderer_vulkan/vk_master_semaphore.cpp | 44 -- .../renderer_vulkan/vk_master_semaphore.h | 4 - .../renderer_vulkan/vk_rasterizer.cpp | 7 + .../renderer_vulkan/vk_rasterizer.h | 2 + .../renderer_vulkan/vk_scheduler.cpp | 54 +- src/video_core/renderer_vulkan/vk_scheduler.h | 33 +- .../renderer_vulkan/vk_swapchain.cpp | 2 +- src/video_core/texture_cache/tile_manager.cpp | 2 +- 32 files changed, 1259 insertions(+), 224 deletions(-) create mode 100644 externals/renderdoc/renderdoc_app.h create mode 100644 src/video_core/renderdoc.cpp create mode 100644 src/video_core/renderdoc.h diff --git a/.reuse/dep5 b/.reuse/dep5 index 1dad50148..c467a1647 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -42,3 +42,7 @@ Files: CMakeSettings.json src/shadps4.rc Copyright: shadPS4 Emulator Project License: GPL-2.0-or-later + +Files: externals/renderdoc/* +Copyright: 2019-2024 Baldur Karlsson +License: MIT diff --git a/CMakeLists.txt b/CMakeLists.txt index 880d1cf58..08cc41036 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) find_package(zlib-ng 2.2.0 MODULE) find_package(Zydis 4.1.0 CONFIG) +find_package(RenderDoc MODULE) if (APPLE) find_package(date 3.0.1 CONFIG) @@ -484,6 +485,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/types.h + src/video_core/renderdoc.cpp + src/video_core/renderdoc.h ) set(INPUT src/input/controller.cpp @@ -559,7 +562,7 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) if (APPLE) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 7fca7b546..9ebdd8783 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -74,6 +74,13 @@ if (NOT TARGET GPUOpen::VulkanMemoryAllocator) add_subdirectory(vma) endif() +# RenderDoc +if (NOT TARGET RenderDoc::API) + add_library(renderdoc INTERFACE) + target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) + add_library(RenderDoc::API ALIAS renderdoc) +endif() + # glslang if (NOT TARGET glslang::glslang) set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "") diff --git a/externals/renderdoc/renderdoc_app.h b/externals/renderdoc/renderdoc_app.h new file mode 100644 index 000000000..c01e05932 --- /dev/null +++ b/externals/renderdoc/renderdoc_app.h @@ -0,0 +1,741 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) +#include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define RENDERDOC_CC __cdecl +#elif defined(__linux__) || defined(__FreeBSD__) +#define RENDERDOC_CC +#elif defined(__APPLE__) +#define RENDERDOC_CC +#else +#error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { \ + 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { \ + 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption +{ + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from actions. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for actions. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton +{ + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits +{ + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | + eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = ~0U, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, + uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, + const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, + const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +// Requests that the replay UI show itself (if hidden or not the current top window). This can be +// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle +// showing the UI after making a capture. +// +// This will return 1 if the request was successfully passed on, though it's not guaranteed that +// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current +// target control connection to make such a request, or if there was another error +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom +// title to the capture produced which will be displayed in the UI. +// +// If multiple captures are ongoing, this title will be applied to the first capture to end after +// this call. The second capture to end will have no title, unless this function is called again. +// +// Calling this function has no effect if no capture is currently running, and if it is called +// multiple times only the last title will be used. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version +{ + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 + eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 + eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 + eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening +// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option. +// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected +// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a +// capture made with StartFrameCapture() or EndFrameCapture() + +typedef struct RENDERDOC_API_1_6_0 +{ + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union + { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union + { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; + + // new function in 1.5.0 + pRENDERDOC_ShowReplayUI ShowReplayUI; + + // new function in 1.6.0 + pRENDERDOC_SetCaptureTitle SetCaptureTitle; +} RENDERDOC_API_1_6_0; + +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/common/config.cpp b/src/common/config.cpp index 218575ffe..57f40b212 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -22,8 +22,10 @@ static bool isShowSplash = false; static bool isNullGpu = false; static bool shouldDumpShaders = false; static bool shouldDumpPM4 = false; +static u32 vblankDivider = 1; static bool vkValidation = false; static bool vkValidationSync = false; +static bool rdocEnable = false; // Gui std::string settings_install_dir = ""; u32 main_window_geometry_x = 400; @@ -94,6 +96,14 @@ bool dumpPM4() { return shouldDumpPM4; } +bool isRdocEnabled() { + return rdocEnable; +} + +u32 vblankDiv() { + return vblankDivider; +} + bool vkValidationEnabled() { return vkValidation; } @@ -233,10 +243,10 @@ void load(const std::filesystem::path& path) { screenWidth = toml::find_or(gpu, "screenWidth", screenWidth); screenHeight = toml::find_or(gpu, "screenHeight", screenHeight); - gpuId = toml::find_or(gpu, "gpuId", 0); isNullGpu = toml::find_or(gpu, "nullGpu", false); shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); shouldDumpPM4 = toml::find_or(gpu, "dumpPM4", false); + vblankDivider = toml::find_or(gpu, "vblankDivider", 1); } } if (data.contains("Vulkan")) { @@ -244,8 +254,10 @@ void load(const std::filesystem::path& path) { if (vkResult.is_ok()) { auto vk = vkResult.unwrap(); + gpuId = toml::find_or(vk, "gpuId", 0); vkValidation = toml::find_or(vk, "validation", true); vkValidationSync = toml::find_or(vk, "validation_sync", true); + rdocEnable = toml::find_or(vk, "rdocEnable", false); } } if (data.contains("Debug")) { @@ -312,14 +324,16 @@ void save(const std::filesystem::path& path) { data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; data["General"]["showSplash"] = isShowSplash; - data["GPU"]["gpuId"] = gpuId; data["GPU"]["screenWidth"] = screenWidth; data["GPU"]["screenHeight"] = screenHeight; data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["dumpPM4"] = shouldDumpPM4; + data["GPU"]["vblankDivider"] = vblankDivider; + data["Vulkan"]["gpuId"] = gpuId; data["Vulkan"]["validation"] = vkValidation; data["Vulkan"]["validation_sync"] = vkValidationSync; + data["Vulkan"]["rdocEnable"] = rdocEnable; data["Debug"]["DebugDump"] = isDebugDump; data["LLE"]["libc"] = isLibc; data["GUI"]["theme"] = mw_themes; diff --git a/src/common/config.h b/src/common/config.h index 0a3b4905e..637ac7468 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,6 +26,8 @@ bool showSplash(); bool nullGpu(); bool dumpShaders(); bool dumpPM4(); +bool isRdocEnabled(); +u32 vblankDiv(); bool vkValidationEnabled(); bool vkValidationSyncEnabled(); diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index f0f56b85f..c1e8a5c0a 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -73,6 +73,7 @@ static auto UserPaths = [] { create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR); + create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR); return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index 67688f897..263edd46e 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -19,6 +19,7 @@ enum class PathType { GameDataDir, // Where game data is stored. SysModuleDir, // Where system modules are stored. DownloadDir, // Where downloads/temp files are stored. + CapturesDir, // Where rdoc captures are stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -33,6 +34,7 @@ constexpr auto GAMEDATA_DIR = "data"; constexpr auto TEMPDATA_DIR = "temp"; constexpr auto SYSMODULES_DIR = "sys_modules"; constexpr auto DOWNLOAD_DIR = "download"; +constexpr auto CAPTURES_DIR = "captures"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 866a96989..dba69d6eb 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -20,13 +20,12 @@ extern Frontend::WindowSDL* g_window; std::unique_ptr renderer; +std::unique_ptr liverpool; namespace Libraries::GnmDriver { using namespace AmdGpu; -static std::unique_ptr liverpool; - enum GnmEventIdents : u64 { Compute0RelMem = 0x00, Compute1RelMem = 0x01, @@ -2131,6 +2130,7 @@ int PS4_SYSV_ABI sceGnmSubmitDone() { if (!liverpool->IsGpuIdle()) { submission_lock = true; } + liverpool->SubmitDone(); send_init_packet = true; ++frames_submitted; return ORBIS_OK; diff --git a/src/core/libraries/kernel/event_queue.cpp b/src/core/libraries/kernel/event_queue.cpp index 6bd88459b..3555fddc9 100644 --- a/src/core/libraries/kernel/event_queue.cpp +++ b/src/core/libraries/kernel/event_queue.cpp @@ -78,9 +78,7 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { std::scoped_lock lock{m_mutex}; for (auto& event : m_events) { - ASSERT_MSG(event.event.filter == filter, - "Event to trigger doesn't match to queue events"); - if (event.event.ident == ident) { + if ((event.event.ident == ident) && (event.event.filter == filter)) { event.Trigger(trigger_data); has_found = true; } diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index f9325297c..47073b2c6 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -43,8 +43,8 @@ namespace Libraries { void InitHLELibs(Core::Loader::SymbolsResolver* sym) { LOG_INFO(Lib_Kernel, "Initializing HLE libraries"); Libraries::Kernel::LibKernel_Register(sym); - Libraries::VideoOut::RegisterLib(sym); Libraries::GnmDriver::RegisterlibSceGnmDriver(sym); + Libraries::VideoOut::RegisterLib(sym); if (!Config::isLleLibc()) { Libraries::LibC::libcSymbolsRegister(sym); } diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index e74fb10f2..97b1816e5 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -3,14 +3,16 @@ #include #include "common/assert.h" +#include "common/config.h" +#include "common/debug.h" +#include "common/thread.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time_management.h" #include "core/libraries/videoout/driver.h" -#include "core/platform.h" - #include "video_core/renderer_vulkan/renderer_vulkan.h" extern std::unique_ptr renderer; +extern std::unique_ptr liverpool; namespace Libraries::VideoOut { @@ -41,20 +43,18 @@ VideoOutDriver::VideoOutDriver(u32 width, u32 height) { main_port.resolution.fullHeight = height; main_port.resolution.paneWidth = width; main_port.resolution.paneHeight = height; + present_thread = std::jthread([&](std::stop_token token) { PresentThread(token); }); } VideoOutDriver::~VideoOutDriver() = default; int VideoOutDriver::Open(const ServiceThreadParams* params) { - std::scoped_lock lock{mutex}; - if (main_port.is_open) { return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY; } - - int handle = 1; main_port.is_open = true; - return handle; + liverpool->SetVoPort(&main_port); + return 1; } void VideoOutDriver::Close(s32 handle) { @@ -158,31 +158,22 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { return ORBIS_OK; } -void VideoOutDriver::Flip(std::chrono::microseconds timeout) { - Request req; - { - std::unique_lock lock{mutex}; - submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); }); - if (requests.empty()) { - renderer->ShowSplash(); - return; - } - - // Retrieve the request. - req = requests.front(); - requests.pop(); +std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { + if (!req) { + return std::chrono::microseconds{0}; } + const auto start = std::chrono::high_resolution_clock::now(); + // Whatever the game is rendering show splash if it is active if (!renderer->ShowSplash(req.frame)) { // Present the frame. renderer->Present(req.frame); } - std::scoped_lock lock{mutex}; - // Update flip status. - auto& flip_status = req.port->flip_status; + auto* port = req.port; + auto& flip_status = port->flip_status; flip_status.count++; flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); flip_status.tsc = Libraries::Kernel::sceKernelReadTsc(); @@ -192,7 +183,7 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) { flip_status.flipPendingNum = static_cast(requests.size()); // Trigger flip events for the port. - for (auto& event : req.port->flip_events) { + for (auto& event : port->flip_events) { if (event != nullptr) { event->TriggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::SceKernelEvent::Filter::VideoOut, reinterpret_cast(req.flip_arg)); @@ -201,21 +192,23 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) { // Reset flip label if (req.index != -1) { - req.port->buffer_labels[req.index] = 0; + port->buffer_labels[req.index] = 0; + port->SignalVoLabel(); } + + const auto end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start); } bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { - std::scoped_lock lock{mutex}; - Vulkan::Frame* frame; if (index == -1) { frame = renderer->PrepareBlankFrame(); } else { const auto& buffer = port->buffer_slots[index]; const auto& group = port->groups[buffer.group_index]; - frame = renderer->PrepareFrame(group, buffer.address_left); + frame = renderer->PrepareFrame(group, buffer.address_left, is_eop); } if (index != -1 && requests.size() >= port->NumRegisteredBuffers()) { @@ -223,6 +216,7 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, return false; } + std::scoped_lock lock{mutex}; requests.push({ .frame = frame, .port = port, @@ -234,24 +228,53 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, port->flip_status.flipPendingNum = static_cast(requests.size()); port->flip_status.gcQueueNum = 0; - submit_cond.notify_one(); return true; } -void VideoOutDriver::Vblank() { - std::scoped_lock lock{mutex}; +void VideoOutDriver::PresentThread(std::stop_token token) { + static constexpr std::chrono::milliseconds VblankPeriod{16}; + Common::SetCurrentThreadName("PresentThread"); - auto& vblank_status = main_port.vblank_status; - vblank_status.count++; - vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); - vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); + const auto receive_request = [this] -> Request { + std::scoped_lock lk{mutex}; + if (!requests.empty()) { + const auto request = requests.front(); + requests.pop(); + return request; + } + return {}; + }; - // Trigger flip events for the port. - for (auto& event : main_port.vblank_events) { - if (event != nullptr) { - event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK, - Kernel::SceKernelEvent::Filter::VideoOut, nullptr); + auto vblank_period = VblankPeriod / Config::vblankDiv(); + auto delay = std::chrono::microseconds{0}; + while (!token.stop_requested()) { + // Sleep for most of the vblank duration. + std::this_thread::sleep_for(vblank_period - delay); + + // Check if it's time to take a request. + auto& vblank_status = main_port.vblank_status; + if (vblank_status.count % (main_port.flip_rate + 1) == 0) { + const auto request = receive_request(); + delay = Flip(request); + FRAME_END; + } + + { + // Needs lock here as can be concurrently read by `sceVideoOutGetVblankStatus` + std::unique_lock lock{main_port.vo_mutex}; + vblank_status.count++; + vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); + vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); + main_port.vblank_cv.notify_all(); + } + + // Trigger flip events for the port. + for (auto& event : main_port.vblank_events) { + if (event != nullptr) { + event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK, + Kernel::SceKernelEvent::Filter::VideoOut, nullptr); + } } } } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index d98e62eea..104056ded 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -3,10 +3,13 @@ #pragma once +#include "common/debug.h" +#include "common/polyfill_thread.h" +#include "core/libraries/videoout/video_out.h" + #include #include #include -#include "core/libraries/videoout/video_out.h" namespace Vulkan { struct Frame; @@ -25,6 +28,9 @@ struct VideoOutPort { SceVideoOutVblankStatus vblank_status; std::vector flip_events; std::vector vblank_events; + std::mutex vo_mutex; + std::condition_variable vo_cv; + std::condition_variable vblank_cv; int flip_rate = 0; s32 FindFreeGroup() const { @@ -35,6 +41,22 @@ struct VideoOutPort { return index; } + bool IsVoLabel(const u64* address) const { + const u64* start = &buffer_labels[0]; + const u64* end = &buffer_labels[MaxDisplayBuffers - 1]; + return address >= start && address <= end; + } + + void WaitVoLabel(auto&& pred) { + std::unique_lock lk{vo_mutex}; + vo_cv.wait(lk, pred); + } + + void SignalVoLabel() { + std::scoped_lock lk{vo_mutex}; + vo_cv.notify_one(); + } + [[nodiscard]] int NumRegisteredBuffers() const { return std::count_if(buffer_slots.cbegin(), buffer_slots.cend(), [](auto& buffer) { return buffer.group_index != -1; }); @@ -63,11 +85,8 @@ public: const BufferAttribute* attribute); int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex); - void Flip(std::chrono::microseconds timeout); bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); - void Vblank(); - private: struct Request { Vulkan::Frame* frame; @@ -76,14 +95,19 @@ private: s64 flip_arg; u64 submit_tsc; bool eop; + + operator bool() const noexcept { + return frame != nullptr; + } }; + std::chrono::microseconds Flip(const Request& req); + void PresentThread(std::stop_token token); + std::mutex mutex; VideoOutPort main_port{}; - std::condition_variable_any submit_cond; - std::condition_variable done_cond; + std::jthread present_thread; std::queue requests; - bool is_neo{}; }; } // namespace Libraries::VideoOut diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 8fbd69c4d..15e14662a 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -183,6 +183,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus* return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } + std::unique_lock lock{port->vo_mutex}; *status = port->vblank_status; return ORBIS_OK; } @@ -229,14 +230,6 @@ s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) { return driver->UnregisterBuffers(port, attributeIndex); } -void Flip(std::chrono::microseconds micros) { - return driver->Flip(micros); -} - -void Vblank() { - return driver->Vblank(); -} - void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr) { auto* port = driver->GetPort(handle); ASSERT(port); @@ -266,6 +259,18 @@ s32 PS4_SYSV_ABI sceVideoOutGetDeviceCapabilityInfo( return ORBIS_OK; } +s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle) { + auto* port = driver->GetPort(handle); + if (!port) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + std::unique_lock lock{port->vo_mutex}; + const auto prev_counter = port->vblank_status.count; + port->vblank_cv.wait(lock, [&]() { return prev_counter != port->vblank_status.count; }); + return ORBIS_OK; +} + void RegisterLib(Core::Loader::SymbolsResolver* sym) { driver = std::make_unique(Config::getScreenWidth(), Config::getScreenHeight()); @@ -294,6 +299,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideoOutGetVblankStatus); LIB_FUNCTION("kGVLc3htQE8", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetDeviceCapabilityInfo); + LIB_FUNCTION("j6RaAUlaLv0", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutWaitVblank); // openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1 LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index 52426eccb..b4423efdf 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -92,11 +92,12 @@ void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, Pixe u32 tilingMode, u32 aspectRatio, u32 width, u32 height, u32 pitchInPixel); s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); -s32 PS4_SYSV_ABI sceVideoOutAddVBlankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); +s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const BufferAttribute* attribute); s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate); s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle); +s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle); s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg); s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status); s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status); @@ -104,9 +105,6 @@ s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 i const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); -void Flip(std::chrono::microseconds micros); -void Vblank(); - // Internal system functions void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr); s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk); diff --git a/src/emulator.cpp b/src/emulator.cpp index 0b542e68e..5b162e056 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include @@ -10,21 +9,30 @@ #include #include #include -#include #include "common/config.h" #include "common/debug.h" #include "common/logging/backend.h" +#include "common/logging/log.h" #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" #include "common/singleton.h" #include "common/version.h" +#include "core/file_format/psf.h" +#include "core/file_format/splash.h" #include "core/file_sys/fs.h" +#include "core/libraries/disc_map/disc_map.h" #include "core/libraries/kernel/thread_management.h" +#include "core/libraries/libc/libc.h" +#include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" +#include "core/libraries/rtc/rtc.h" #include "core/linker.h" #include "core/memory.h" #include "emulator.h" +#include "video_core/renderdoc.h" + +#include Frontend::WindowSDL* g_window = nullptr; @@ -52,6 +60,9 @@ Emulator::Emulator() { memory = Core::Memory::Instance(); controller = Common::Singleton::Instance(); linker = Common::Singleton::Instance(); + + // Load renderdoc module. + VideoCore::LoadRenderDoc(); } Emulator::~Emulator() { @@ -120,6 +131,12 @@ void Emulator::Run(const std::filesystem::path& file) { } mnt->Mount(mount_download_dir, "/download0"); + const auto& mount_captures_dir = Common::FS::GetUserPath(Common::FS::PathType::CapturesDir); + if (!std::filesystem::exists(mount_captures_dir)) { + std::filesystem::create_directory(mount_captures_dir); + } + VideoCore::SetOutputDir(mount_captures_dir.generic_string(), id); + // Initialize kernel and library facilities. Libraries::Kernel::init_pthreads(); Libraries::InitHLELibs(&linker->GetHLESymbols()); @@ -152,14 +169,8 @@ void Emulator::Run(const std::filesystem::path& file) { std::jthread mainthread = std::jthread([this](std::stop_token stop_token) { linker->Execute(); }); - // Begin main window loop until the application exits - static constexpr std::chrono::milliseconds FlipPeriod{16}; - while (window->isOpen()) { window->waitEvent(); - Libraries::VideoOut::Flip(FlipPeriod); - Libraries::VideoOut::Vblank(); - FRAME_END; } std::exit(0); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 4570b64ef..2da246107 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -11,6 +11,7 @@ #include "core/libraries/pad/pad.h" #include "input/controller.h" #include "sdl_window.h" +#include "video_core/renderdoc.h" #ifdef __APPLE__ #include @@ -72,7 +73,7 @@ void WindowSDL::waitEvent() { // Called on main thread SDL_Event event; - if (!SDL_PollEvent(&event)) { + if (!SDL_WaitEvent(&event)) { return; } @@ -180,6 +181,11 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { ax = Input::GetAxis(-0x80, 0x80, axisvalue); break; case SDLK_S: + if (event->key.mod == SDL_KMOD_LCTRL) { + // Trigger rdoc capture + VideoCore::TriggerCapture(); + break; + } axis = Input::Axis::LeftY; if (event->type == SDL_EVENT_KEY_DOWN) { axisvalue += 127; diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index ab7ad2415..df7eec826 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -5,8 +5,10 @@ #include "common/debug.h" #include "common/polyfill_thread.h" #include "common/thread.h" +#include "core/libraries/videoout/driver.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pm4_cmds.h" +#include "video_core/renderdoc.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" namespace AmdGpu { @@ -32,12 +34,15 @@ void Liverpool::Process(std::stop_token stoken) { while (!stoken.stop_requested()) { { std::unique_lock lk{submit_mutex}; - Common::CondvarWait(submit_cv, lk, stoken, [this] { return num_submits != 0; }); + Common::CondvarWait(submit_cv, lk, stoken, + [this] { return num_submits != 0 || submit_done; }); } if (stoken.stop_requested()) { break; } + VideoCore::StartCapture(); + int qid = -1; while (num_submits) { @@ -48,11 +53,9 @@ void Liverpool::Process(std::stop_token stoken) { Task::Handle task{}; { std::scoped_lock lock{queue.m_access}; - if (queue.submits.empty()) { continue; } - task = queue.submits.front(); } task.resume(); @@ -64,9 +67,20 @@ void Liverpool::Process(std::stop_token stoken) { queue.submits.pop(); --num_submits; + std::scoped_lock lock2{submit_mutex}; + submit_cv.notify_all(); } } + if (submit_done) { + VideoCore::EndCapture(); + + if (rasterizer) { + rasterizer->Flush(); + } + submit_done = false; + } + Platform::IrqC::Instance()->Signal(Platform::InterruptId::GpuIdle); } } @@ -365,8 +379,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); ASSERT(write_data->dst_sel.Value() == 2 || write_data->dst_sel.Value() == 5); const u32 data_size = (header->type3.count.Value() - 2) * 4; + u64* address = write_data->Address(); if (!write_data->wr_one_addr.Value()) { - std::memcpy(write_data->Address(), write_data->data, data_size); + std::memcpy(address, write_data->data, data_size); } else { UNREACHABLE(); } @@ -379,6 +394,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); + // Optimization: VO label waits are special because the emulator + // will write to the label when presentation is finished. So if + // there are no other submits to yield to we can sleep the thread + // instead and allow other tasks to run. + const u64* wait_addr = wait_reg_mem->Address(); + if (vo_port->IsVoLabel(wait_addr) && num_submits == 1) { + vo_port->WaitVoLabel([&] { return wait_reg_mem->Test(); }); + } while (!wait_reg_mem->Test()) { TracyFiberLeave; co_yield {}; @@ -511,7 +534,7 @@ void Liverpool::SubmitGfx(std::span dcb, std::span ccb) { auto task = ProcessGraphics(dcb, ccb); { - std::unique_lock lock{queue.m_access}; + std::scoped_lock lock{queue.m_access}; queue.submits.emplace(task.handle); } @@ -526,7 +549,7 @@ void Liverpool::SubmitAsc(u32 vqid, std::span acb) { const auto& task = ProcessCompute(acb, vqid); { - std::unique_lock lock{queue.m_access}; + std::scoped_lock lock{queue.m_access}; queue.submits.emplace(task.handle); } diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index b87c80ed9..8553bc92d 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,10 @@ namespace Vulkan { class Rasterizer; } +namespace Libraries::VideoOut { +struct VideoOutPort; +} + namespace AmdGpu { #define GFX6_3D_REG_INDEX(field_name) (offsetof(AmdGpu::Liverpool::Regs, field_name) / sizeof(u32)) @@ -991,10 +996,25 @@ public: void SubmitGfx(std::span dcb, std::span ccb); void SubmitAsc(u32 vqid, std::span acb); + void SubmitDone() noexcept { + std::scoped_lock lk{submit_mutex}; + submit_done = true; + submit_cv.notify_one(); + } + + void WaitGpuIdle() noexcept { + std::unique_lock lk{submit_mutex}; + submit_cv.wait(lk, [this] { return num_submits == 0; }); + } + bool IsGpuIdle() const { return num_submits == 0; } + void SetVoPort(Libraries::VideoOut::VideoOutPort* port) { + vo_port = port; + } + void BindRasterizer(Vulkan::Rasterizer* rasterizer_) { rasterizer = rasterizer_; } @@ -1059,8 +1079,10 @@ private: } cblock{}; Vulkan::Rasterizer* rasterizer{}; + Libraries::VideoOut::VideoOutPort* vo_port{}; std::jthread process_thread{}; std::atomic num_submits{}; + std::atomic submit_done{}; std::mutex submit_mutex; std::condition_variable_any submit_cv; }; diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index eded2de37..9b44da024 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -404,8 +404,9 @@ struct PM4CmdWaitRegMem { u32 mask; u32 poll_interval; - u32* Address() const { - return reinterpret_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); + template + T Address() const { + return reinterpret_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); } bool Test() const { @@ -464,8 +465,8 @@ struct PM4CmdWriteData { } template - T* Address() const { - return reinterpret_cast(addr64); + T Address() const { + return reinterpret_cast(addr64); } }; @@ -494,8 +495,9 @@ struct PM4CmdEventWriteEos { BitField<16, 16, u32> size; ///< Number of DWs to read from the GDS }; - u32* Address() const { - return reinterpret_cast(address_lo | u64(address_hi) << 32); + template + T Address() const { + return reinterpret_cast(address_lo | u64(address_hi) << 32); } u32 DataDWord() const { diff --git a/src/video_core/renderdoc.cpp b/src/video_core/renderdoc.cpp new file mode 100644 index 000000000..7f88e1264 --- /dev/null +++ b/src/video_core/renderdoc.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/config.h" +#include "video_core/renderdoc.h" + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +namespace VideoCore { + +enum class CaptureState { + Idle, + Triggered, + InProgress, +}; +static CaptureState capture_state{CaptureState::Idle}; + +RENDERDOC_API_1_6_0* rdoc_api{}; + +void LoadRenderDoc() { +#ifdef WIN32 + + // Check if we are running by RDoc GUI + HMODULE mod = GetModuleHandleA("renderdoc.dll"); + if (!mod && Config::isRdocEnabled()) { + // If enabled in config, try to load RDoc runtime in offline mode + HKEY h_reg_key; + LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, + KEY_READ, &h_reg_key); + if (result != ERROR_SUCCESS) { + return; + } + std::array key_str{}; + DWORD str_sz_out{key_str.size()}; + result = RegQueryValueExW(h_reg_key, L"", 0, NULL, (LPBYTE)key_str.data(), &str_sz_out); + if (result != ERROR_SUCCESS) { + return; + } + + std::filesystem::path path{key_str.cbegin(), key_str.cend()}; + path = path.parent_path().append("renderdoc.dll"); + const auto path_to_lib = path.generic_string(); + mod = LoadLibraryA(path_to_lib.c_str()); + } + + if (mod) { + const auto RENDERDOC_GetAPI = + reinterpret_cast(GetProcAddress(mod, "RENDERDOC_GetAPI")); + const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); + ASSERT(ret == 1); + } +#else +#ifdef ANDROID + static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; +#else + static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; +#endif + if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) { + const auto RENDERDOC_GetAPI = + reinterpret_cast(dlsym(mod, "RENDERDOC_GetAPI")); + const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); + ASSERT(ret == 1); + } +#endif + if (rdoc_api) { + // Disable default capture keys as they suppose to trigger present-to-present capturing + // and it is not what we want + rdoc_api->SetCaptureKeys(nullptr, 0); + + // Also remove rdoc crash handler + rdoc_api->UnloadCrashHandler(); + } +} + +void StartCapture() { + if (!rdoc_api) { + return; + } + + if (capture_state == CaptureState::Triggered) { + rdoc_api->StartFrameCapture(nullptr, nullptr); + capture_state = CaptureState::InProgress; + } +} + +void EndCapture() { + if (!rdoc_api) { + return; + } + + if (capture_state == CaptureState::InProgress) { + rdoc_api->EndFrameCapture(nullptr, nullptr); + capture_state = CaptureState::Idle; + } +} + +void TriggerCapture() { + if (capture_state == CaptureState::Idle) { + capture_state = CaptureState::Triggered; + } +} + +void SetOutputDir(const std::string& path, const std::string& prefix) { + if (!rdoc_api) { + return; + } + rdoc_api->SetCaptureFilePathTemplate((path + '\\' + prefix).c_str()); +} + +} // namespace VideoCore diff --git a/src/video_core/renderdoc.h b/src/video_core/renderdoc.h new file mode 100644 index 000000000..febf6fbc1 --- /dev/null +++ b/src/video_core/renderdoc.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace VideoCore { + +/// Loads renderdoc dynamic library module. +void LoadRenderDoc(); + +/// Begins a capture if a renderdoc instance is attached. +void StartCapture(); + +/// Ends current renderdoc capture. +void EndCapture(); + +/// Triggers capturing process. +void TriggerCapture(); + +/// Sets output directory for captures +void SetOutputDir(const std::string& path, const std::string& prefix); + +} // namespace VideoCore diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 098f14d9b..6810bf34e 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -63,44 +63,30 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format for }; } -RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool) - : window{window_}, instance{window, Config::getGpuId(), Config::vkValidationEnabled()}, - scheduler{instance}, swapchain{instance, window}, texture_cache{instance, scheduler} { - rasterizer = std::make_unique(instance, scheduler, texture_cache, liverpool); +RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_) + : window{window_}, liverpool{liverpool_}, + instance{window, Config::getGpuId(), Config::vkValidationEnabled()}, draw_scheduler{instance}, + present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, + texture_cache{instance, draw_scheduler} { + rasterizer = std::make_unique(instance, draw_scheduler, texture_cache, liverpool); const u32 num_images = swapchain.GetImageCount(); const vk::Device device = instance.GetDevice(); - const vk::CommandPoolCreateInfo pool_info = { - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer | - vk::CommandPoolCreateFlagBits::eTransient, - .queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(), - }; - command_pool = device.createCommandPoolUnique(pool_info); - - const vk::CommandBufferAllocateInfo alloc_info = { - .commandPool = *command_pool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = num_images, - }; - - const auto cmdbuffers = device.allocateCommandBuffers(alloc_info); + // Create presentation frames. present_frames.resize(num_images); for (u32 i = 0; i < num_images; i++) { Frame& frame = present_frames[i]; - frame.cmdbuf = cmdbuffers[i]; - frame.render_ready = device.createSemaphore({}); frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); free_queue.push(&frame); } } RendererVulkan::~RendererVulkan() { - scheduler.Finish(); + draw_scheduler.Finish(); const vk::Device device = instance.GetDevice(); for (auto& frame : present_frames) { vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation); device.destroyImageView(frame.image_view); - device.destroySemaphore(frame.render_ready); device.destroyFence(frame.present_done); } } @@ -184,7 +170,7 @@ bool RendererVulkan::ShowSplash(Frame* frame /*= nullptr*/) { info.pitch = splash->GetImageInfo().width; info.guest_address = VAddr(splash->GetImageData().data()); info.guest_size_bytes = splash->GetImageData().size(); - splash_img.emplace(instance, scheduler, info); + splash_img.emplace(instance, present_scheduler, info); texture_cache.RefreshImage(*splash_img); } frame = PrepareFrameInternal(*splash_img); @@ -193,12 +179,18 @@ bool RendererVulkan::ShowSplash(Frame* frame /*= nullptr*/) { return true; } -Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image) { +Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop) { // Request a free presentation frame. Frame* frame = GetRenderFrame(); - // Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image. - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + // EOP flips are triggered from GPU thread so use the drawing scheduler to record + // commands. Otherwise we are dealing with a CPU flip which could have arrived + // from any guest thread. Use a separate scheduler for that. + auto& scheduler = is_eop ? draw_scheduler : flip_scheduler; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead, cmdbuf); const std::array pre_barrier{ vk::ImageMemoryBarrier{ @@ -218,12 +210,11 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image) { }, }, }; - - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier); + // Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image. cmdbuf.blitImage( image.image, image.layout, frame->image, vk::ImageLayout::eTransferDstOptimal, MakeImageBlit(image.info.size.width, image.info.size.height, frame->width, frame->height), @@ -245,13 +236,15 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image) { .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); - // Flush pending vulkan operations. - scheduler.Flush(frame->render_ready); + // Flush frame creation commands. + frame->ready_semaphore = scheduler.GetMasterSemaphore()->Handle(); + frame->ready_tick = scheduler.CurrentTick(); + SubmitInfo info{}; + scheduler.Flush(info); return frame; } @@ -260,11 +253,8 @@ void RendererVulkan::Present(Frame* frame) { const vk::Image swapchain_image = swapchain.Image(); - const vk::CommandBufferBeginInfo begin_info = { - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, - }; - const vk::CommandBuffer cmdbuf = frame->cmdbuf; - cmdbuf.begin(begin_info); + auto& scheduler = present_scheduler; + const auto cmdbuf = scheduler.CommandBuffer(); { auto* profiler_ctx = instance.GetProfilerContext(); TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", @@ -339,35 +329,17 @@ void RendererVulkan::Present(Frame* frame) { TracyVkCollect(profiler_ctx, cmdbuf); } } - cmdbuf.end(); - static constexpr std::array wait_stage_masks = { - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eAllGraphics, - }; - - const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore(); - const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore(); - const std::array wait_semaphores = {image_acquired, frame->render_ready}; - - vk::SubmitInfo submit_info = { - .waitSemaphoreCount = static_cast(wait_semaphores.size()), - .pWaitSemaphores = wait_semaphores.data(), - .pWaitDstStageMask = wait_stage_masks.data(), - .commandBufferCount = 1u, - .pCommandBuffers = &cmdbuf, - .signalSemaphoreCount = 1, - .pSignalSemaphores = &present_ready, - }; - - std::scoped_lock submit_lock{scheduler.submit_mutex}; - try { - instance.GetGraphicsQueue().submit(submit_info, frame->present_done); - } catch (vk::DeviceLostError& err) { - LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what()); - UNREACHABLE(); - } + // Flush vulkan commands. + SubmitInfo info{}; + info.AddWait(swapchain.GetImageAcquiredSemaphore()); + info.AddWait(frame->ready_semaphore, frame->ready_tick); + info.AddSignal(swapchain.GetPresentReadySemaphore()); + info.AddSignal(frame->present_done); + scheduler.Flush(info); + // Present to swapchain. + std::scoped_lock submit_lock{Scheduler::submit_mutex}; swapchain.Present(); // Free the frame for reuse diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 701d3d14b..3fe9267fe 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -26,9 +26,15 @@ struct Frame { VmaAllocation allocation; vk::Image image; vk::ImageView image_view; - vk::Semaphore render_ready; vk::Fence present_done; - vk::CommandBuffer cmdbuf; + vk::Semaphore ready_semaphore; + u64 ready_tick; +}; + +enum SchedulerType { + Draw, + Present, + CpuFlip, }; class Rasterizer; @@ -39,16 +45,16 @@ public: ~RendererVulkan(); Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, - VAddr cpu_address) { + VAddr cpu_address, bool is_eop) { const auto info = VideoCore::ImageInfo{attribute, cpu_address}; const auto image_id = texture_cache.FindImage(info, cpu_address); auto& image = texture_cache.GetImage(image_id); - return PrepareFrameInternal(image); + return PrepareFrameInternal(image, is_eop); } Frame* PrepareBlankFrame() { auto& image = texture_cache.GetImage(VideoCore::NULL_IMAGE_ID); - return PrepareFrameInternal(image); + return PrepareFrameInternal(image, true); } VideoCore::Image& RegisterVideoOutSurface( @@ -60,9 +66,9 @@ public: } bool IsVideoOutSurface(const AmdGpu::Liverpool::ColorBuffer& color_buffer) { - return std::find_if(vo_buffers_addr.cbegin(), vo_buffers_addr.cend(), [&](VAddr vo_buffer) { + return std::ranges::find_if(vo_buffers_addr, [&](VAddr vo_buffer) { return vo_buffer == color_buffer.Address(); - }) != vo_buffers_addr.cend(); + }) != vo_buffers_addr.end(); } bool ShowSplash(Frame* frame = nullptr); @@ -70,13 +76,16 @@ public: void RecreateFrame(Frame* frame, u32 width, u32 height); private: - Frame* PrepareFrameInternal(VideoCore::Image& image); + Frame* PrepareFrameInternal(VideoCore::Image& image, bool is_eop = true); Frame* GetRenderFrame(); private: Frontend::WindowSDL& window; + AmdGpu::Liverpool* liverpool; Instance instance; - Scheduler scheduler; + Scheduler draw_scheduler; + Scheduler present_scheduler; + Scheduler flip_scheduler; Swapchain swapchain; std::unique_ptr rasterizer; VideoCore::TextureCache texture_cache; diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 037510d41..753f2bbdf 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -2,8 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include "common/assert.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" @@ -60,46 +58,4 @@ void MasterSemaphore::Wait(u64 tick) { Refresh(); } -void MasterSemaphore::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, - u64 signal_value) { - cmdbuf.end(); - - const u32 num_signal_semaphores = signal ? 2U : 1U; - const std::array signal_values{signal_value, u64(0)}; - const std::array signal_semaphores{Handle(), signal}; - - const u32 num_wait_semaphores = wait ? 2U : 1U; - const std::array wait_values{signal_value - 1, u64(1)}; - const std::array wait_semaphores{Handle(), wait}; - - static constexpr std::array wait_stage_masks = { - vk::PipelineStageFlagBits::eAllCommands, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - }; - - const vk::TimelineSemaphoreSubmitInfo timeline_si = { - .waitSemaphoreValueCount = num_wait_semaphores, - .pWaitSemaphoreValues = wait_values.data(), - .signalSemaphoreValueCount = num_signal_semaphores, - .pSignalSemaphoreValues = signal_values.data(), - }; - - const vk::SubmitInfo submit_info = { - .pNext = &timeline_si, - .waitSemaphoreCount = num_wait_semaphores, - .pWaitSemaphores = wait_semaphores.data(), - .pWaitDstStageMask = wait_stage_masks.data(), - .commandBufferCount = 1u, - .pCommandBuffers = &cmdbuf, - .signalSemaphoreCount = num_signal_semaphores, - .pSignalSemaphores = signal_semaphores.data(), - }; - - try { - instance.GetGraphicsQueue().submit(submit_info); - } catch (vk::DeviceLostError& err) { - UNREACHABLE_MSG("Device lost during submit: {}", err.what()); - } -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h index 963676b1a..ebc7a60a1 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.h +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h @@ -46,10 +46,6 @@ public: /// Waits for a tick to be hit on the GPU void Wait(u64 tick); - /// Submits the provided command buffer for execution - void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, - u64 signal_value); - protected: const Instance& instance; vk::UniqueSemaphore semaphore; ///< Timeline semaphore. diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index fe52d0741..67a88c473 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -96,6 +96,13 @@ void Rasterizer::DispatchDirect() { cmdbuf.dispatch(cs_program.dim_x, cs_program.dim_y, cs_program.dim_z); } +u64 Rasterizer::Flush() { + const u64 current_tick = scheduler.CurrentTick(); + SubmitInfo info{}; + scheduler.Flush(info); + return current_tick; +} + void Rasterizer::BeginRendering() { const auto& regs = liverpool->regs; RenderState state; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index aead5955d..64dc87ef6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -36,6 +36,8 @@ public: void ScopeMarkerBegin(const std::string& str); void ScopeMarkerEnd(); + u64 Flush(); + private: u32 SetupIndexBuffer(bool& is_indexed, u32 index_offset); void MapMemory(VAddr addr, size_t size); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 39dc2847d..e7b12d49a 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -2,12 +2,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/assert.h" #include "common/debug.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" namespace Vulkan { +std::mutex Scheduler::submit_mutex; + Scheduler::Scheduler(const Instance& instance) : instance{instance}, master_semaphore{instance}, command_pool{instance, &master_semaphore} { profiler_scope = reinterpret_cast(std::malloc(sizeof(tracy::VkCtxScope))); @@ -50,22 +53,24 @@ void Scheduler::EndRendering() { current_cmdbuf.endRendering(); } -void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) { - // When flushing, we only send data to the worker thread; no waiting is necessary. - SubmitExecution(signal, wait); +void Scheduler::Flush(SubmitInfo& info) { + // When flushing, we only send data to the driver; no waiting is necessary. + SubmitExecution(info); } -void Scheduler::Finish(vk::Semaphore signal, vk::Semaphore wait) { +void Scheduler::Finish() { // When finishing, we need to wait for the submission to have executed on the device. const u64 presubmit_tick = CurrentTick(); - SubmitExecution(signal, wait); + SubmitInfo info{}; + SubmitExecution(info); Wait(presubmit_tick); } void Scheduler::Wait(u64 tick) { if (tick >= master_semaphore.CurrentTick()) { // Make sure we are not waiting for the current tick without signalling - Flush(); + SubmitInfo info{}; + Flush(info); } master_semaphore.Wait(tick); } @@ -86,7 +91,7 @@ void Scheduler::AllocateWorkerCommandBuffers() { } } -void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) { +void Scheduler::SubmitExecution(SubmitInfo& info) { std::scoped_lock lk{submit_mutex}; const u64 signal_value = master_semaphore.NextTick(); @@ -97,7 +102,40 @@ void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wa } EndRendering(); - master_semaphore.SubmitWork(current_cmdbuf, wait_semaphore, signal_semaphore, signal_value); + current_cmdbuf.end(); + + const vk::Semaphore timeline = master_semaphore.Handle(); + info.AddSignal(timeline, signal_value); + + static constexpr std::array wait_stage_masks = { + vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + }; + + const vk::TimelineSemaphoreSubmitInfo timeline_si = { + .waitSemaphoreValueCount = static_cast(info.wait_ticks.size()), + .pWaitSemaphoreValues = info.wait_ticks.data(), + .signalSemaphoreValueCount = static_cast(info.signal_ticks.size()), + .pSignalSemaphoreValues = info.signal_ticks.data(), + }; + + const vk::SubmitInfo submit_info = { + .pNext = &timeline_si, + .waitSemaphoreCount = static_cast(info.wait_semas.size()), + .pWaitSemaphores = info.wait_semas.data(), + .pWaitDstStageMask = wait_stage_masks.data(), + .commandBufferCount = 1U, + .pCommandBuffers = ¤t_cmdbuf, + .signalSemaphoreCount = static_cast(info.signal_semas.size()), + .pSignalSemaphores = info.signal_semas.data(), + }; + + try { + instance.GetGraphicsQueue().submit(submit_info, info.fence); + } catch (vk::DeviceLostError& err) { + UNREACHABLE_MSG("Device lost during submit: {}", err.what()); + } + master_semaphore.Refresh(); AllocateWorkerCommandBuffers(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index b45042743..1e640b083 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -26,16 +26,39 @@ struct RenderState { } }; +struct SubmitInfo { + boost::container::static_vector wait_semas; + boost::container::static_vector wait_ticks; + boost::container::static_vector signal_semas; + boost::container::static_vector signal_ticks; + vk::Fence fence; + + void AddWait(vk::Semaphore semaphore, u64 tick = 1) { + wait_semas.emplace_back(semaphore); + wait_ticks.emplace_back(tick); + } + + void AddSignal(vk::Semaphore semaphore, u64 tick = 1) { + signal_semas.emplace_back(semaphore); + signal_ticks.emplace_back(tick); + } + + void AddSignal(vk::Fence fence) { + this->fence = fence; + } +}; + class Scheduler { public: explicit Scheduler(const Instance& instance); ~Scheduler(); - /// Sends the current execution context to the GPU. - void Flush(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); + /// Sends the current execution context to the GPU + /// and increments the scheduler timeline semaphore. + void Flush(SubmitInfo& info); /// Sends the current execution context to the GPU and waits for it to complete. - void Finish(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); + void Finish(); /// Waits for the given tick to trigger on the GPU. void Wait(u64 tick); @@ -76,12 +99,12 @@ public: pending_ops.emplace(func, CurrentTick()); } - std::mutex submit_mutex; + static std::mutex submit_mutex; private: void AllocateWorkerCommandBuffers(); - void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore); + void SubmitExecution(SubmitInfo& info); private: const Instance& instance; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 7fffdeb2c..20c99e302 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -55,7 +55,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { .pQueueFamilyIndices = queue_family_indices.data(), .preTransform = transform, .compositeAlpha = composite_alpha, - .presentMode = vk::PresentModeKHR::eFifo, + .presentMode = vk::PresentModeKHR::eMailbox, .clipped = true, .oldSwapchain = nullptr, }; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index e097ba3ed..ace2e4d53 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -231,7 +231,7 @@ static constexpr vk::BufferUsageFlags StagingFlags = vk::BufferUsageFlagBits::eT TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) : instance{instance}, scheduler{scheduler}, - staging{instance, scheduler, StagingFlags, 128_MB, Vulkan::BufferType::Upload} { + staging{instance, scheduler, StagingFlags, 256_MB, Vulkan::BufferType::Upload} { static const std::array detiler_shaders{ HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, From 30198d5ffcd1495a81ad0407dbc5c67058de5ef7 Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:20:42 +0200 Subject: [PATCH 021/109] Surface management rework (2/3) (#329) * texture_cache: interface refactoring * a bit of fixes and improvements * texture_cache: macro tile extents for bpp 128 * texture_cache: detiler: prefer host memory for large buffers upload --- src/video_core/amdgpu/liverpool.h | 6 +- src/video_core/host_shaders/detile_m32x1.comp | 26 ++- src/video_core/host_shaders/detile_m32x2.comp | 32 ++- src/video_core/host_shaders/detile_m32x4.comp | 38 ++-- src/video_core/host_shaders/detile_m8x1.comp | 26 ++- src/video_core/host_shaders/detile_m8x2.comp | 26 +-- .../renderer_vulkan/liverpool_to_vk.cpp | 4 + .../renderer_vulkan/vk_compute_pipeline.cpp | 4 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 4 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 2 +- .../renderer_vulkan/vk_platform.cpp | 1 + .../renderer_vulkan/vk_rasterizer.cpp | 11 +- src/video_core/texture_cache/image.cpp | 20 +- src/video_core/texture_cache/image.h | 1 - src/video_core/texture_cache/image_info.cpp | 156 +++++++------ src/video_core/texture_cache/image_info.h | 23 +- src/video_core/texture_cache/image_view.cpp | 38 +++- src/video_core/texture_cache/image_view.h | 9 +- .../texture_cache/texture_cache.cpp | 210 +++++++++--------- src/video_core/texture_cache/texture_cache.h | 16 +- src/video_core/texture_cache/tile_manager.cpp | 138 +++++++++--- src/video_core/texture_cache/tile_manager.h | 9 +- 22 files changed, 478 insertions(+), 322 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 8553bc92d..db2ee91c3 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -377,9 +377,13 @@ struct Liverpool { return 1u << z_info.num_samples; // spec doesn't say it is a log2 } + u32 NumBits() const { + return z_info.format == ZFormat::Z32Float ? 32 : 16; + } + size_t GetDepthSliceSize() const { ASSERT(z_info.format != ZFormat::Invalid); - const auto bpe = z_info.format == ZFormat::Z32Float ? 4 : 2; + const auto bpe = NumBits() >> 3; // in bytes return (depth_slice.tile_max + 1) * 64 * bpe * NumSamples(); } }; diff --git a/src/video_core/host_shaders/detile_m32x1.comp b/src/video_core/host_shaders/detile_m32x1.comp index f3e84c753..fecea109b 100644 --- a/src/video_core/host_shaders/detile_m32x1.comp +++ b/src/video_core/host_shaders/detile_m32x1.comp @@ -8,10 +8,14 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(std430, binding = 0) buffer input_buf { uint in_data[]; }; -layout(r32ui, binding = 1) uniform writeonly uimage2D output_img; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; layout(push_constant) uniform image_info { + uint num_levels; uint pitch; + uint sizes[14]; } info; // Inverse morton LUT, small enough to fit into K$ @@ -31,20 +35,22 @@ uint rmort[16] = { #define TEXELS_PER_ELEMENT (1) void main() { + uint tile_base = gl_GlobalInvocationID.x - gl_LocalInvocationID.x; // WG*16 + uint p0 = in_data[gl_GlobalInvocationID.x]; uint bit_ofs = 8 * (gl_LocalInvocationID.x % 4); uint packed_pos = rmort[gl_LocalInvocationID.x >> 2] >> bit_ofs; uint col = bitfieldExtract(packed_pos, 4, 4); uint row = bitfieldExtract(packed_pos, 0, 4); - uint p0 = in_data[gl_GlobalInvocationID.x]; + uint mip = 0; + for (int m = 0; m < info.num_levels; ++m) { + mip += (gl_GlobalInvocationID.x * 4) >= info.sizes[m] ? 1 : 0; + } - uint tiles_per_pitch = info.pitch >> 3; // log2(MICRO_TILE_DIM) + uint tiles_per_pitch = max((info.pitch >> mip) / MICRO_TILE_DIM, 1); uint target_tile_x = gl_WorkGroupID.x % tiles_per_pitch; uint target_tile_y = gl_WorkGroupID.x / tiles_per_pitch; - - uint dw_ofs_x = target_tile_x * MICRO_TILE_DIM + TEXELS_PER_ELEMENT * col; - uint dw_ofs_y = target_tile_y * MICRO_TILE_DIM + row; - - ivec2 img_pos = ivec2(dw_ofs_x, dw_ofs_y); - imageStore(output_img, img_pos, uvec4(p0, 0, 0, 0)); -} \ No newline at end of file + uint dw_ofs_x = target_tile_x * MICRO_TILE_DIM + col; + uint dw_ofs_y = (target_tile_y * tiles_per_pitch * 64) + row * tiles_per_pitch * MICRO_TILE_DIM; + out_data[dw_ofs_x + dw_ofs_y] = p0; +} diff --git a/src/video_core/host_shaders/detile_m32x2.comp b/src/video_core/host_shaders/detile_m32x2.comp index 2853f8b78..c2caa62c2 100644 --- a/src/video_core/host_shaders/detile_m32x2.comp +++ b/src/video_core/host_shaders/detile_m32x2.comp @@ -8,10 +8,14 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(std430, binding = 0) buffer input_buf { uint in_data[]; }; -layout(rg32ui, binding = 1) uniform writeonly uimage2D output_img; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; layout(push_constant) uniform image_info { + uint num_levels; uint pitch; + uint sizes[14]; } info; // Inverse morton LUT, small enough to fit into K$ @@ -30,19 +34,25 @@ uint rmort[16] = { #define MICRO_TILE_DIM (8) void main() { + uint block_ofs = 2 * gl_GlobalInvocationID.x; + uint p0 = in_data[block_ofs + 0]; + uint p1 = in_data[block_ofs + 1]; + uint bit_ofs = 8 * (gl_LocalInvocationID.x % 4); uint packed_pos = rmort[gl_LocalInvocationID.x >> 2] >> bit_ofs; uint col = bitfieldExtract(packed_pos, 4, 4); uint row = bitfieldExtract(packed_pos, 0, 4); - uint block_ofs = 2 * gl_GlobalInvocationID.x; - uint p0 = in_data[block_ofs + 0]; - uint p1 = in_data[block_ofs + 1]; + uint mip = 0; + for (int m = 0; m < info.num_levels; ++m) { + mip += (gl_GlobalInvocationID.x * 8) >= info.sizes[m] ? 1 : 0; + } - uint tiles_per_pitch = (info.pitch >> 3) >> 2; // log2(MICRO_TILE_DIM) / 4 - ivec2 img_pos = MICRO_TILE_DIM * ivec2( - gl_WorkGroupID.x % tiles_per_pitch, - gl_WorkGroupID.x / tiles_per_pitch - ); - imageStore(output_img, img_pos + ivec2(col, row), uvec4(p0, p1, 0, 0)); -} \ No newline at end of file + uint tiles_per_pitch = max((info.pitch >> mip) / MICRO_TILE_DIM, 1) * 2; + uint target_tile_x = 2 * gl_WorkGroupID.x % tiles_per_pitch; + uint target_tile_y = 2 * gl_WorkGroupID.x / tiles_per_pitch; + uint dw_ofs_x = target_tile_x * MICRO_TILE_DIM + col * 2; + uint dw_ofs_y = (target_tile_y * tiles_per_pitch * 64) + row * tiles_per_pitch * MICRO_TILE_DIM; + out_data[dw_ofs_x + dw_ofs_y] = p0; + out_data[dw_ofs_x + dw_ofs_y + 1] = p1; +} diff --git a/src/video_core/host_shaders/detile_m32x4.comp b/src/video_core/host_shaders/detile_m32x4.comp index 64f34e6fa..113538706 100644 --- a/src/video_core/host_shaders/detile_m32x4.comp +++ b/src/video_core/host_shaders/detile_m32x4.comp @@ -8,10 +8,14 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(std430, binding = 0) buffer input_buf { uint in_data[]; }; -layout(rgba32ui, binding = 1) uniform writeonly uimage2D output_img; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; layout(push_constant) uniform image_info { + uint num_levels; uint pitch; + uint sizes[14]; } info; // Inverse morton LUT, small enough to fit into K$ @@ -30,21 +34,29 @@ uint rmort[16] = { #define MICRO_TILE_DIM (8) void main() { - uint bit_ofs = 8 * (gl_LocalInvocationID.x % 4); - uint packed_pos = rmort[gl_LocalInvocationID.x >> 2] >> bit_ofs; - uint col = bitfieldExtract(packed_pos, 4, 4); - uint row = bitfieldExtract(packed_pos, 0, 4); - uint block_ofs = 4 * gl_GlobalInvocationID.x; uint p0 = in_data[block_ofs + 0]; uint p1 = in_data[block_ofs + 1]; uint p2 = in_data[block_ofs + 2]; uint p3 = in_data[block_ofs + 3]; - uint tiles_per_pitch = (info.pitch >> 3) >> 2; // log2(MICRO_TILE_DIM) / 4 - ivec2 img_pos = MICRO_TILE_DIM * ivec2( - gl_WorkGroupID.x % tiles_per_pitch, - gl_WorkGroupID.x / tiles_per_pitch - ); - imageStore(output_img, img_pos + ivec2(col, row), uvec4(p0, p1, p2, p3)); -} \ No newline at end of file + uint bit_ofs = 8 * (gl_LocalInvocationID.x % 4); + uint packed_pos = rmort[gl_LocalInvocationID.x >> 2] >> bit_ofs; + uint col = bitfieldExtract(packed_pos, 4, 4); + uint row = bitfieldExtract(packed_pos, 0, 4); + + uint mip = 0; + for (int m = 0; m < info.num_levels; ++m) { + mip += (gl_GlobalInvocationID.x * 16) >= info.sizes[m] ? 1 : 0; + } + + uint tiles_per_pitch = max(((info.pitch >> mip) / MICRO_TILE_DIM), 1u) * 4; + uint target_tile_x = 4 * gl_WorkGroupID.x % tiles_per_pitch; + uint target_tile_y = 4 * gl_WorkGroupID.x / tiles_per_pitch; + uint dw_ofs_x = (target_tile_x * MICRO_TILE_DIM) + 4 * col; + uint dw_ofs_y = ((target_tile_y * tiles_per_pitch) * 64u) + ((row * tiles_per_pitch) * MICRO_TILE_DIM); + out_data[dw_ofs_x + dw_ofs_y] = p0; + out_data[dw_ofs_x + dw_ofs_y + 1] = p1; + out_data[dw_ofs_x + dw_ofs_y + 2] = p2; + out_data[dw_ofs_x + dw_ofs_y + 3] = p3; +} diff --git a/src/video_core/host_shaders/detile_m8x1.comp b/src/video_core/host_shaders/detile_m8x1.comp index b4d920e68..5ec48fae2 100644 --- a/src/video_core/host_shaders/detile_m8x1.comp +++ b/src/video_core/host_shaders/detile_m8x1.comp @@ -11,10 +11,14 @@ layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in; layout(std430, binding = 0) buffer input_buf { uint in_data[]; }; -layout(r8ui, binding = 1) uniform writeonly uimage2D output_img; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; layout(push_constant) uniform image_info { + uint num_levels; uint pitch; + uint sizes[14]; } info; #define MICRO_TILE_DIM 8 @@ -32,17 +36,15 @@ void main() { uint row = (gl_LocalInvocationID.x % TEXELS_PER_ELEMENT) + TEXELS_PER_ELEMENT * (gl_LocalInvocationID.x >> 3); - uint tiles_per_pitch = info.pitch >> 3; // log2(MICRO_TILE_DIM) + uint mip = 0; + for (int m = 0; m < info.num_levels; ++m) { + mip += (gl_GlobalInvocationID.x * 4) >= info.sizes[m] ? 1 : 0; + } + + uint tiles_per_pitch = max((info.pitch >> mip) / 8, 1); uint target_tile_x = gl_WorkGroupID.x % tiles_per_pitch; uint target_tile_y = gl_WorkGroupID.x / tiles_per_pitch; - uint dw_ofs_x = target_tile_x * MICRO_TILE_DIM + TEXELS_PER_ELEMENT * col; - uint dw_ofs_y = target_tile_y * MICRO_TILE_DIM + row; - - ivec2 img_pos = ivec2(dw_ofs_x, dw_ofs_y); - - #pragma unroll - for (int ofs = 0; ofs < TEXELS_PER_ELEMENT; ++ofs) { - imageStore(output_img, img_pos + ivec2(ofs, 0), uvec4(dst_tx & 0xff)); - dst_tx >>= 8; - } + uint dw_ofs_x = target_tile_x * 2 + col; // 2 = uints + uint dw_ofs_y = (target_tile_y * MICRO_TILE_DIM + row) * tiles_per_pitch * 2; // 2 = uints + out_data[dw_ofs_x + dw_ofs_y] = dst_tx; } \ No newline at end of file diff --git a/src/video_core/host_shaders/detile_m8x2.comp b/src/video_core/host_shaders/detile_m8x2.comp index 1cebc12b3..d27bc6e2d 100644 --- a/src/video_core/host_shaders/detile_m8x2.comp +++ b/src/video_core/host_shaders/detile_m8x2.comp @@ -10,10 +10,14 @@ layout (local_size_x = 32, local_size_y = 1, local_size_z = 1) in; layout(std430, binding = 0) buffer input_buf { uint in_data[]; }; -layout(rg8ui, binding = 1) uniform writeonly uimage2D output_img; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; layout(push_constant) uniform image_info { + uint num_levels; uint pitch; + uint sizes[14]; } info; #define MICRO_TILE_DIM 8 @@ -44,18 +48,14 @@ void main() { uint col = bitfieldExtract(packed_pos, 4, 4); uint row = bitfieldExtract(packed_pos, 0, 4); - uint tiles_per_pitch = info.pitch >> 3; // log2(MICRO_TILE_DIM) + uint mip = 0u; + for (int m = 0; m < info.num_levels; ++m) { + mip += (gl_GlobalInvocationID.x * 4) >= info.sizes[m] ? 1 : 0; + } + uint tiles_per_pitch = max(((info.pitch >> mip) / 8u), 1u); uint target_tile_x = gl_WorkGroupID.x % tiles_per_pitch; uint target_tile_y = gl_WorkGroupID.x / tiles_per_pitch; - uint dw_ofs_x = target_tile_x * MICRO_TILE_DIM + col; - uint dw_ofs_y = target_tile_y * MICRO_TILE_DIM + row; - - ivec2 img_pos = ivec2(dw_ofs_x, dw_ofs_y); - - #pragma unroll - for (int ofs = 0; ofs < TEXELS_PER_ELEMENT; ++ofs) { - uint p0 = (p[ofs] >> 8) & 0xff; - uint p1 = p[ofs] & 0xff; - imageStore(output_img, img_pos + ivec2(ofs, 0), uvec4(p1, p0, 0, 0)); - } + uint dw_ofs_x = target_tile_x * 8 + col; + uint dw_ofs_y = (target_tile_y * tiles_per_pitch * 64) + row * tiles_per_pitch * 8; + out_data[(dw_ofs_x + dw_ofs_y) / 2] = src_tx; } diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 90d974040..fc7943e62 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -392,6 +392,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::Float) { return vk::Format::eR16G16Sfloat; } + if (data_format == AmdGpu::DataFormat::Format16_16 && + num_format == AmdGpu::NumberFormat::Unorm) { + return vk::Format::eR16G16Unorm; + } if (data_format == AmdGpu::DataFormat::Format10_11_11 && num_format == AmdGpu::NumberFormat::Float) { return vk::Format::eB10G11R11UfloatPack32; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 954adf448..51bb7f831 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -128,7 +128,9 @@ bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& s for (const auto& image_desc : info.images) { const auto tsharp = info.ReadUd(image_desc.sgpr_base, image_desc.dword_offset); - const auto& image_view = texture_cache.FindTexture(tsharp, image_desc.is_storage); + VideoCore::ImageInfo image_info{tsharp}; + VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; + const auto& image_view = texture_cache.FindTexture(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f119bc770..eb5522688 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -366,7 +366,9 @@ void GraphicsPipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& for (const auto& image_desc : stage.images) { const auto& tsharp = tsharps.emplace_back( stage.ReadUd(image_desc.sgpr_base, image_desc.dword_offset)); - const auto& image_view = texture_cache.FindTexture(tsharp, image_desc.is_storage); + VideoCore::ImageInfo image_info{tsharp}; + VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; + const auto& image_view = texture_cache.FindTexture(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7f0b74ab6..67994485a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -191,7 +191,7 @@ void PipelineCache::RefreshGraphicsKey() { LiverpoolToVK::SurfaceFormat(col_buf.info.format, col_buf.NumFormat()); const auto is_vo_surface = renderer->IsVideoOutSurface(col_buf); key.color_formats[remapped_cb] = LiverpoolToVK::AdjustColorBufferFormat( - base_format, col_buf.info.comp_swap.Value(), is_vo_surface); + base_format, col_buf.info.comp_swap.Value(), false /*is_vo_surface*/); key.blend_controls[remapped_cb] = regs.blend_control[cb]; key.blend_controls[remapped_cb].enable.Assign(key.blend_controls[remapped_cb].enable && !col_buf.info.blend_bypass); diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 1499d8771..0915514b8 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -32,6 +32,7 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( switch (static_cast(callback_data->messageIdNumber)) { case 0x609a13b: // Vertex attribute at location not consumed by shader case 0xc81ad50e: + case 0x92d66fc1: // `pMultisampleState is NULL` for depth only passes (confirmed VL error) return VK_FALSE; default: break; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 67a88c473..c64f6089b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -120,7 +120,9 @@ void Rasterizer::BeginRendering() { } const auto& hint = liverpool->last_cb_extent[col_buf_id]; - const auto& image_view = texture_cache.FindRenderTarget(col_buf, hint); + VideoCore::ImageInfo image_info{col_buf, hint}; + VideoCore::ImageViewInfo view_info{col_buf, false /*!!image.info.usage.vo_buffer*/}; + const auto& image_view = texture_cache.FindRenderTarget(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); state.width = std::min(state.width, image.info.size.width); state.height = std::min(state.height, image.info.size.height); @@ -143,9 +145,10 @@ void Rasterizer::BeginRendering() { const bool is_clear = regs.depth_render_control.depth_clear_enable || texture_cache.IsMetaCleared(htile_address); const auto& hint = liverpool->last_db_extent; - const auto& image_view = texture_cache.FindDepthTarget( - regs.depth_buffer, regs.depth_view.NumSlices(), htile_address, hint, - regs.depth_control.depth_write_enable); + VideoCore::ImageInfo image_info{regs.depth_buffer, regs.depth_view.NumSlices(), + htile_address, hint}; + VideoCore::ImageViewInfo view_info{regs.depth_buffer, regs.depth_view, regs.depth_control}; + const auto& image_view = texture_cache.FindDepthTarget(image_info, view_info); const auto& image = texture_cache.GetImage(image_view.image_id); state.width = std::min(state.width, image.info.size.width); state.height = std::min(state.height, image.info.size.height); diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index b4b3f48a7..f7aef8471 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -117,18 +117,15 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, image{instance->GetDevice(), instance->GetAllocator()}, cpu_addr{info.guest_address}, cpu_addr_end{cpu_addr + info.guest_size_bytes} { ASSERT(info.pixel_format != vk::Format::eUndefined); + // Here we force `eExtendedUsage` as don't know all image usage cases beforehand. In normal case + // the texture cache should re-create the resource with the usage requested vk::ImageCreateFlags flags{vk::ImageCreateFlagBits::eMutableFormat | vk::ImageCreateFlagBits::eExtendedUsage}; - if (info.type == vk::ImageType::e2D && info.resources.layers >= 6 && - info.size.width == info.size.height) { + if (info.props.is_cube) { flags |= vk::ImageCreateFlagBits::eCubeCompatible; - } - if (info.type == vk::ImageType::e3D) { + } else if (info.props.is_volume) { flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; } - if (info.IsBlockCoded()) { - flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible; - } usage = ImageUsageFlags(info); @@ -157,15 +154,6 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, }; image.Create(image_ci); - - // Create a special view for detiler - if (info.is_tiled) { - ImageViewInfo view_info; - view_info.format = DemoteImageFormatForDetiling(info.pixel_format); - view_for_detiler.emplace(*instance, view_info, *this, ImageId{}); - } - - Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eNone); } void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 97ceaa098..b18f1002b 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -105,7 +105,6 @@ struct Image { VAddr cpu_addr_end = 0; std::vector image_view_infos; std::vector image_view_ids; - std::optional view_for_detiler; // Resource state tracking vk::ImageUsageFlags usage; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 41ad09381..e01a61ae7 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -47,33 +47,33 @@ static vk::ImageType ConvertImageType(AmdGpu::ImageType type) noexcept { // clang-format off // The table of macro tiles parameters for given tiling index (row) and bpp (column) static constexpr std::array macro_tile_extents{ - std::pair{256u, 128u}, std::pair{256u, 128u}, std::pair{256u, 128u}, std::pair{256u, 128u}, // 00 - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, // 01 - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 02 - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 03 - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 04 - std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 05 - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, // 06 - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 07 - std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 08 - std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 09 - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 0A - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 0B - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 0C - std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 0D - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 0E - std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 0F - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 10 - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 11 - std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, // 12 - std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 13 - std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 14 - std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 15 - std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 16 - std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 17 - std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 18 - std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 19 - std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 1A + std::pair{256u, 128u}, std::pair{256u, 128u}, std::pair{256u, 128u}, std::pair{256u, 128u}, std::pair{256u, 128u}, // 00 + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, // 01 + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 02 + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 03 + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 04 + std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 05 + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, std::pair{128u, 128u}, // 06 + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 07 + std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 08 + std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 09 + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 0A + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 0B + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 0C + std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 0D + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 0E + std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 0F + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 10 + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, // 11 + std::pair{256u, 256u}, std::pair{256u, 128u}, std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{128u, 64u}, // 12 + std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, std::pair{0u, 0u}, // 13 + std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 14 + std::pair{128u, 64u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 15 + std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 16 + std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 17 + std::pair{128u, 128u}, std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{128u, 64u}, // 18 + std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 19 + std::pair{128u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, std::pair{64u, 64u}, // 1A }; // clang-format on @@ -82,62 +82,65 @@ static constexpr auto hw_pipe_interleave = 256u; static constexpr std::pair GetMacroTileExtents(u32 tiling_idx, u32 bpp, u32 num_samples) { ASSERT(num_samples == 1); - const auto row = tiling_idx * 4; - const auto column = std::bit_width(bpp) - 4; // bpps are 8, 16, 32, 64 + const auto row = tiling_idx * 5; + const auto column = std::bit_width(bpp) - 4; // bpps are 8, 16, 32, 64, 128 return macro_tile_extents[row + column]; } -static constexpr size_t ImageSizeLinearAligned(u32 pitch, u32 height, u32 bpp, u32 num_samples) { +static constexpr std::pair ImageSizeLinearAligned(u32 pitch, u32 height, u32 bpp, + u32 num_samples) { const auto pitch_align = std::max(8u, 64u / ((bpp + 7) / 8)); auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); const auto height_aligned = height; - size_t log_sz = 1; - const auto slice_align = std::max(64u, hw_pipe_interleave / (bpp + 7) / 8); + size_t log_sz = pitch_aligned * height_aligned * num_samples; + const auto slice_align = std::max(64u, 256u / ((bpp + 7) / 8)); while (log_sz % slice_align) { - log_sz = pitch_aligned * height_aligned * num_samples; pitch_aligned += pitch_align; + log_sz = pitch_aligned * height_aligned * num_samples; } - return (log_sz * bpp + 7) / 8; + return {pitch_aligned, (log_sz * bpp + 7) / 8}; } -static constexpr size_t ImageSizeMicroTiled(u32 pitch, u32 height, u32 bpp, u32 num_samples) { +static constexpr std::pair ImageSizeMicroTiled(u32 pitch, u32 height, u32 bpp, + u32 num_samples) { const auto& [pitch_align, height_align] = micro_tile_extent; auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); const auto height_aligned = (height + height_align - 1) & ~(height_align - 1); - size_t log_sz = 1; + size_t log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; while (log_sz % 256) { - log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; pitch_aligned += 8; + log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; } - return log_sz; + return {pitch_aligned, log_sz}; } -static constexpr size_t ImageSizeMacroTiled(u32 pitch, u32 height, u32 bpp, u32 num_samples, - u32 tiling_idx) { +static constexpr std::pair ImageSizeMacroTiled(u32 pitch, u32 height, u32 bpp, + u32 num_samples, u32 tiling_idx) { const auto& [pitch_align, height_align] = GetMacroTileExtents(tiling_idx, bpp, num_samples); ASSERT(pitch_align != 0 && height_align != 0); const auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); const auto height_aligned = (height + height_align - 1) & ~(height_align - 1); - return (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; + const auto log_sz = pitch_aligned * height_aligned * num_samples; + return {pitch_aligned, (log_sz * bpp + 7) / 8}; } ImageInfo::ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group, VAddr cpu_address) noexcept { const auto& attrib = group.attrib; - is_tiled = attrib.tiling_mode == TilingMode::Tile; - tiling_mode = - is_tiled ? AmdGpu::TilingMode::Display_MacroTiled : AmdGpu::TilingMode::Display_Linear; + props.is_tiled = attrib.tiling_mode == TilingMode::Tile; + tiling_mode = props.is_tiled ? AmdGpu::TilingMode::Display_MacroTiled + : AmdGpu::TilingMode::Display_Linear; pixel_format = ConvertPixelFormat(attrib.pixel_format); type = vk::ImageType::e2D; size.width = attrib.width; size.height = attrib.height; pitch = attrib.tiling_mode == TilingMode::Linear ? size.width : (size.width + 127) & (~127); usage.vo_buffer = true; - const bool is_32bpp = attrib.pixel_format != VideoOutFormat::A16R16G16B16Float; - ASSERT(is_32bpp); + num_bits = attrib.pixel_format != VideoOutFormat::A16R16G16B16Float ? 32 : 64; + ASSERT(num_bits == 32); guest_address = cpu_address; - if (!is_tiled) { + if (!props.is_tiled) { guest_size_bytes = pitch * size.height * 4; } else { if (Config::isNeoMode()) { @@ -146,15 +149,16 @@ ImageInfo::ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group, guest_size_bytes = pitch * ((size.height + 63) & (~63)) * 4; } } - mips_layout.emplace_back(0, guest_size_bytes); + mips_layout.emplace_back(guest_size_bytes, pitch, 0); } ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, const AmdGpu::Liverpool::CbDbExtent& hint /*= {}*/) noexcept { - is_tiled = buffer.IsTiled(); + props.is_tiled = buffer.IsTiled(); tiling_mode = buffer.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(buffer.info.format, buffer.NumFormat()); num_samples = 1 << buffer.attrib.num_fragments_log2; + num_bits = NumBits(buffer.info.format); type = vk::ImageType::e2D; size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); @@ -168,15 +172,16 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, guest_address = buffer.Address(); const auto color_slice_sz = buffer.GetColorSliceSize(); guest_size_bytes = color_slice_sz * buffer.NumSlices(); - mips_layout.emplace_back(0, color_slice_sz); + mips_layout.emplace_back(color_slice_sz, pitch, 0); } ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, VAddr htile_address, const AmdGpu::Liverpool::CbDbExtent& hint) noexcept { - is_tiled = false; + props.is_tiled = false; pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format); type = vk::ImageType::e2D; num_samples = 1 << buffer.z_info.num_samples; // spec doesn't say it is a log2 + num_bits = buffer.NumBits(); size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); size.depth = 1; @@ -188,37 +193,38 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice guest_address = buffer.Address(); const auto depth_slice_sz = buffer.GetDepthSliceSize(); guest_size_bytes = depth_slice_sz * num_slices; - mips_layout.emplace_back(0, depth_slice_sz); + mips_layout.emplace_back(depth_slice_sz, pitch, 0); } ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { - is_tiled = image.IsTiled(); tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); type = ConvertImageType(image.GetType()); - is_cube = image.GetType() == AmdGpu::ImageType::Cube; - is_volume = image.GetType() == AmdGpu::ImageType::Color3D; + props.is_tiled = image.IsTiled(); + props.is_cube = image.GetType() == AmdGpu::ImageType::Cube; + props.is_volume = image.GetType() == AmdGpu::ImageType::Color3D; + props.is_pow2 = image.pow2pad; + props.is_block = IsBlockCoded(); size.width = image.width + 1; size.height = image.height + 1; - size.depth = is_volume ? image.depth + 1 : 1; + size.depth = props.is_volume ? image.depth + 1 : 1; pitch = image.Pitch(); resources.levels = image.NumLevels(); resources.layers = image.NumLayers(); + num_bits = NumBits(image.GetDataFmt()); usage.texture = true; guest_address = image.Address(); mips_layout.reserve(resources.levels); - const auto num_bits = NumBits(image.GetDataFmt()); - const auto is_block = IsBlockCoded(); - const auto is_pow2 = image.pow2pad; + MipInfo mip_info{}; guest_size_bytes = 0; for (auto mip = 0u; mip < resources.levels; ++mip) { auto bpp = num_bits; auto mip_w = pitch >> mip; auto mip_h = size.height >> mip; - if (is_block) { + if (props.is_block) { mip_w = (mip_w + 3) / 4; mip_h = (mip_h + 3) / 4; bpp *= 16; @@ -227,40 +233,48 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { mip_h = std::max(mip_h, 1u); auto mip_d = std::max(size.depth >> mip, 1u); - if (is_pow2) { + if (props.is_pow2) { mip_w = std::bit_ceil(mip_w); mip_h = std::bit_ceil(mip_h); mip_d = std::bit_ceil(mip_d); } - size_t mip_size = 0; switch (tiling_mode) { case AmdGpu::TilingMode::Display_Linear: { - ASSERT(!is_cube); - mip_size = ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); + ASSERT(!props.is_cube); + std::tie(mip_info.pitch, mip_info.size) = + ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); + mip_info.height = mip_h; break; } case AmdGpu::TilingMode::Texture_MicroTiled: { - mip_size = ImageSizeMicroTiled(mip_w, mip_h, bpp, num_samples); + std::tie(mip_info.pitch, mip_info.size) = + ImageSizeMicroTiled(mip_w, mip_h, bpp, num_samples); + mip_info.height = std::max(mip_h, 8u); + if (props.is_block) { + mip_info.pitch = std::max(mip_info.pitch * 4, 32u); + mip_info.height = std::max(mip_info.height * 4, 32u); + } break; } case AmdGpu::TilingMode::Display_MacroTiled: case AmdGpu::TilingMode::Texture_MacroTiled: case AmdGpu::TilingMode::Depth_MacroTiled: { - ASSERT(!is_cube && !is_block); + ASSERT(!props.is_cube && !props.is_block); ASSERT(num_samples == 1); - ASSERT(num_bits <= 64); - mip_size = ImageSizeMacroTiled(mip_w, mip_h, bpp, num_samples, image.tiling_index); + std::tie(mip_info.pitch, mip_info.size) = + ImageSizeMacroTiled(mip_w, mip_h, bpp, num_samples, image.tiling_index); break; } default: { UNREACHABLE(); } } - mip_size *= mip_d; + mip_info.size *= mip_d; - mips_layout.emplace_back(guest_size_bytes, mip_size); - guest_size_bytes += mip_size; + mip_info.offset = guest_size_bytes; + mips_layout.emplace_back(mip_info); + guest_size_bytes += mip_info.size; } guest_size_bytes *= resources.layers; } diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index b98410b90..9dad0dd67 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -9,6 +9,8 @@ #include "video_core/amdgpu/liverpool.h" #include "video_core/texture_cache/types.h" +#include + namespace VideoCore { struct ImageInfo { @@ -42,18 +44,29 @@ struct ImageInfo { u32 vo_buffer : 1; } usage{}; // Usage data tracked during image lifetime - bool is_cube = false; - bool is_volume = false; - bool is_tiled = false; - bool is_read_only = false; + struct { + u32 is_cube : 1; + u32 is_volume : 1; + u32 is_tiled : 1; + u32 is_pow2 : 1; + u32 is_block : 1; + } props{}; // Surface properties with impact on various calculation factors + vk::Format pixel_format = vk::Format::eUndefined; vk::ImageType type = vk::ImageType::e1D; SubresourceExtent resources; Extent3D size{1, 1, 1}; + u32 num_bits{}; u32 num_samples = 1; u32 pitch = 0; AmdGpu::TilingMode tiling_mode{AmdGpu::TilingMode::Display_Linear}; - std::vector> mips_layout; + struct MipInfo { + u32 size; + u32 pitch; + u32 height; + u32 offset; + }; + boost::container::small_vector mips_layout; VAddr guest_address{0}; u32 guest_size_bytes{0}; }; diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index 8f9722531..ff85a8aa3 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/logging/log.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/texture_cache/image.h" @@ -50,15 +51,18 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexce : is_storage{is_storage} { type = ConvertImageViewType(image.GetType()); format = Vulkan::LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); - range.base.level = static_cast(image.base_level); - range.base.layer = static_cast(image.base_array); - range.extent.levels = image.NumLevels(); - range.extent.layers = image.NumLayers(); - if (!is_storage) { - mapping.r = ConvertComponentSwizzle(image.dst_sel_x); - mapping.g = ConvertComponentSwizzle(image.dst_sel_y); - mapping.b = ConvertComponentSwizzle(image.dst_sel_z); - mapping.a = ConvertComponentSwizzle(image.dst_sel_w); + range.base.level = image.base_level; + range.base.layer = image.base_array; + range.extent.levels = image.last_level + 1; + range.extent.layers = image.last_array + 1; + mapping.r = ConvertComponentSwizzle(image.dst_sel_x); + mapping.g = ConvertComponentSwizzle(image.dst_sel_y); + mapping.b = ConvertComponentSwizzle(image.dst_sel_z); + mapping.a = ConvertComponentSwizzle(image.dst_sel_w); + // Check for unfortunate case of storage images being swizzled + if (is_storage && (mapping != vk::ComponentMapping{})) { + LOG_ERROR(Render_Vulkan, "Storage image requires swizzling"); + mapping = vk::ComponentMapping{}; } } @@ -70,6 +74,16 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, base_format, col_buffer.info.comp_swap.Value(), is_vo_surface); } +ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, + AmdGpu::Liverpool::DepthView view, + AmdGpu::Liverpool::DepthControl ctl) { + format = Vulkan::LiverpoolToVK::DepthFormat(depth_buffer.z_info.format, + depth_buffer.stencil_info.format); + is_storage = ctl.depth_write_enable; + range.base.layer = view.slice_start; + range.extent.layers = view.NumSlices(); +} + ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info_, Image& image, ImageId image_id_, std::optional usage_override /*= {}*/) : info{info_}, image_id{image_id_} { @@ -93,10 +107,10 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .components = instance.GetSupportedComponentSwizzle(format, info.mapping), .subresourceRange{ .aspectMask = aspect, - .baseMipLevel = 0U, - .levelCount = 1, + .baseMipLevel = info.range.base.level, + .levelCount = info.range.extent.levels - info.range.base.level, .baseArrayLayer = info_.range.base.layer, - .layerCount = image.info.IsBlockCoded() ? 1 : VK_REMAINING_ARRAY_LAYERS, + .layerCount = info.range.extent.layers - info.range.base.layer, }, }; image_view = instance.GetDevice().createImageViewUnique(image_view_ci); diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h index b43f65dee..590ac9be6 100644 --- a/src/video_core/texture_cache/image_view.h +++ b/src/video_core/texture_cache/image_view.h @@ -18,10 +18,11 @@ class Scheduler; namespace VideoCore { struct ImageViewInfo { - explicit ImageViewInfo() = default; - explicit ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept; - explicit ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, - bool is_vo_surface) noexcept; + ImageViewInfo() = default; + ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept; + ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, bool is_vo_surface) noexcept; + ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, + AmdGpu::Liverpool::DepthView view, AmdGpu::Liverpool::DepthControl ctl); vk::ImageViewType type = vk::ImageViewType::e2D; vk::Format format = vk::Format::eR8G8B8A8Unorm; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 55bb99cca..9131e6f1f 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -152,8 +152,6 @@ ImageId TextureCache::FindImage(const ImageInfo& info, bool refresh_on_create) { image_id = image_ids[0]; } - RegisterMeta(info, image_id); - Image& image = slot_images[image_id]; if (True(image.flags & ImageFlagBits::CpuModified) && refresh_on_create) { RefreshImage(image); @@ -184,13 +182,12 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo return slot_image_views[view_id]; } -ImageView& TextureCache::FindTexture(const AmdGpu::Image& desc, bool is_storage) { - const ImageInfo info{desc}; +ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { const ImageId image_id = FindImage(info); Image& image = slot_images[image_id]; auto& usage = image.info.usage; - if (is_storage) { + if (view_info.is_storage) { image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eShaderWrite); usage.storage = true; } else { @@ -201,14 +198,36 @@ ImageView& TextureCache::FindTexture(const AmdGpu::Image& desc, bool is_storage) usage.texture = true; } - const ImageViewInfo view_info{desc, is_storage}; - return RegisterImageView(image_id, view_info); + // These changes are temporary and should be removed once texture cache will handle subresources + // merging + auto view_info_tmp = view_info; + if (view_info_tmp.range.base.level > image.info.resources.levels - 1 || + view_info_tmp.range.base.layer > image.info.resources.layers - 1 || + view_info_tmp.range.extent.levels > image.info.resources.levels || + view_info_tmp.range.extent.layers > image.info.resources.layers) { + + LOG_ERROR(Render_Vulkan, + "Subresource range ({}~{},{}~{}) exceeds base image extents ({},{})", + view_info_tmp.range.base.level, view_info_tmp.range.extent.levels, + view_info_tmp.range.base.layer, view_info_tmp.range.extent.layers, + image.info.resources.levels, image.info.resources.layers); + + view_info_tmp.range.base.level = + std::min(view_info_tmp.range.base.level, image.info.resources.levels - 1); + view_info_tmp.range.base.layer = + std::min(view_info_tmp.range.base.layer, image.info.resources.layers - 1); + view_info_tmp.range.extent.levels = + std::min(view_info_tmp.range.extent.levels, image.info.resources.levels); + view_info_tmp.range.extent.layers = + std::min(view_info_tmp.range.extent.layers, image.info.resources.layers); + } + + return RegisterImageView(image_id, view_info_tmp); } -ImageView& TextureCache::FindRenderTarget(const AmdGpu::Liverpool::ColorBuffer& buffer, - const AmdGpu::Liverpool::CbDbExtent& hint) { - const ImageInfo info{buffer, hint}; - const ImageId image_id = FindImage(info); +ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, + const ImageViewInfo& view_info) { + const ImageId image_id = FindImage(image_info); Image& image = slot_images[image_id]; image.flags &= ~ImageFlagBits::CpuModified; @@ -216,30 +235,56 @@ ImageView& TextureCache::FindRenderTarget(const AmdGpu::Liverpool::ColorBuffer& vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eColorAttachmentRead); + // Register meta data for this color buffer + if (!(image.flags & ImageFlagBits::MetaRegistered)) { + if (image_info.meta_info.cmask_addr) { + surface_metas.emplace( + image_info.meta_info.cmask_addr, + MetaDataInfo{.type = MetaDataInfo::Type::CMask, .is_cleared = true}); + image.info.meta_info.cmask_addr = image_info.meta_info.cmask_addr; + image.flags |= ImageFlagBits::MetaRegistered; + } + + if (image_info.meta_info.fmask_addr) { + surface_metas.emplace( + image_info.meta_info.fmask_addr, + MetaDataInfo{.type = MetaDataInfo::Type::FMask, .is_cleared = true}); + image.info.meta_info.fmask_addr = image_info.meta_info.fmask_addr; + image.flags |= ImageFlagBits::MetaRegistered; + } + } + + // Update tracked image usage image.info.usage.render_target = true; - ImageViewInfo view_info{buffer, !!image.info.usage.vo_buffer}; return RegisterImageView(image_id, view_info); } -ImageView& TextureCache::FindDepthTarget(const AmdGpu::Liverpool::DepthBuffer& buffer, - u32 num_slices, VAddr htile_address, - const AmdGpu::Liverpool::CbDbExtent& hint, - bool write_enabled) { - const ImageInfo info{buffer, num_slices, htile_address, hint}; - const ImageId image_id = FindImage(info, false); +ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, + const ImageViewInfo& view_info) { + const ImageId image_id = FindImage(image_info, false); Image& image = slot_images[image_id]; image.flags &= ~ImageFlagBits::CpuModified; - const auto new_layout = write_enabled ? vk::ImageLayout::eDepthStencilAttachmentOptimal - : vk::ImageLayout::eDepthStencilReadOnlyOptimal; + const auto new_layout = view_info.is_storage ? vk::ImageLayout::eDepthStencilAttachmentOptimal + : vk::ImageLayout::eDepthStencilReadOnlyOptimal; image.Transit(new_layout, vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead); + // Register meta data for this depth buffer + if (!(image.flags & ImageFlagBits::MetaRegistered)) { + if (image_info.meta_info.htile_addr) { + surface_metas.emplace( + image_info.meta_info.htile_addr, + MetaDataInfo{.type = MetaDataInfo::Type::HTile, .is_cleared = true}); + image.info.meta_info.htile_addr = image_info.meta_info.htile_addr; + image.flags |= ImageFlagBits::MetaRegistered; + } + } + + // Update tracked image usage image.info.usage.depth_target = true; - ImageViewInfo view_info; - view_info.format = info.pixel_format; return RegisterImageView(image_id, view_info); } @@ -247,64 +292,56 @@ void TextureCache::RefreshImage(Image& image) { // Mark image as validated. image.flags &= ~ImageFlagBits::CpuModified; - { - if (!tile_manager.TryDetile(image)) { - // Upload data to the staging buffer. - const auto offset = staging.Copy(image.cpu_addr, image.info.guest_size_bytes, 4); - // Copy to the image. - image.Upload(staging.Handle(), offset); - } + scheduler.EndRendering(); - image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); - return; + const auto cmdbuf = scheduler.CommandBuffer(); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + + vk::Buffer buffer{staging.Handle()}; + u32 offset{0}; + + auto upload_buffer = tile_manager.TryDetile(image); + if (upload_buffer) { + buffer = *upload_buffer; + } else { + // Upload data to the staging buffer. + const auto [data, offset_, _] = staging.Map(image.info.guest_size_bytes, 16); + std::memcpy(data, (void*)image.info.guest_address, image.info.guest_size_bytes); + staging.Commit(image.info.guest_size_bytes); + offset = offset_; } - ASSERT(image.info.resources.levels == image.info.mips_layout.size()); - const u8* image_data = reinterpret_cast(image.cpu_addr); - for (u32 m = 0; m < image.info.resources.levels; m++) { + const auto& num_layers = image.info.resources.layers; + const auto& num_mips = image.info.resources.levels; + ASSERT(num_mips == image.info.mips_layout.size()); + + boost::container::small_vector image_copy{}; + for (u32 m = 0; m < num_mips; m++) { const u32 width = std::max(image.info.size.width >> m, 1u); const u32 height = std::max(image.info.size.height >> m, 1u); - const u32 depth = image.info.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; - const u32 map_size = image.info.mips_layout[m].second * image.info.resources.layers; + const u32 depth = + image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; + const auto& [_, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; - // Upload data to the staging buffer. - const auto [data, offset, _] = staging.Map(map_size, 16); - if (image.info.is_tiled) { - ConvertTileToLinear(data, image_data, width, height, Config::isNeoMode()); - } else { - std::memcpy(data, - image_data + image.info.mips_layout[m].first * image.info.resources.layers, - map_size); - } - staging.Commit(map_size); - - // Copy to the image. - const vk::BufferImageCopy image_copy = { - .bufferOffset = offset, - .bufferRowLength = 0, - .bufferImageHeight = 0, + image_copy.push_back({ + .bufferOffset = offset + mip_ofs * num_layers, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), .imageSubresource{ .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = m, .baseArrayLayer = 0, - .layerCount = u32(image.info.resources.layers), + .layerCount = num_layers, }, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, depth}, - }; - - scheduler.EndRendering(); - - const auto cmdbuf = scheduler.CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); - - cmdbuf.copyBufferToImage(staging.Handle(), image.image, - vk::ImageLayout::eTransferDstOptimal, image_copy); - - image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + }); } + + cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); + + image.Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); } vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) { @@ -320,47 +357,8 @@ void TextureCache::RegisterImage(ImageId image_id) { image.flags |= ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.info.guest_size_bytes, [this, image_id](u64 page) { page_table[page].push_back(image_id); }); -} -void TextureCache::RegisterMeta(const ImageInfo& info, ImageId image_id) { - Image& image = slot_images[image_id]; - - if (image.flags & ImageFlagBits::MetaRegistered) { - return; - } - - bool registered = true; - // Current resource tracking implementation allows us to detect usage of meta only in the last - // moment, so we likely will miss its first clear. To avoid this and make first frame, where - // the meta is encountered, looks correct we set its state to "cleared" at registrations time. - if (info.usage.render_target) { - if (info.meta_info.cmask_addr) { - surface_metas.emplace( - info.meta_info.cmask_addr, - MetaDataInfo{.type = MetaDataInfo::Type::CMask, .is_cleared = true}); - image.info.meta_info.cmask_addr = info.meta_info.cmask_addr; - } - - if (info.meta_info.fmask_addr) { - surface_metas.emplace( - info.meta_info.fmask_addr, - MetaDataInfo{.type = MetaDataInfo::Type::FMask, .is_cleared = true}); - image.info.meta_info.fmask_addr = info.meta_info.fmask_addr; - } - } else if (info.usage.depth_target) { - if (info.meta_info.htile_addr) { - surface_metas.emplace( - info.meta_info.htile_addr, - MetaDataInfo{.type = MetaDataInfo::Type::HTile, .is_cleared = true}); - image.info.meta_info.htile_addr = info.meta_info.htile_addr; - } - } else { - registered = false; - } - - if (registered) { - image.flags |= ImageFlagBits::MetaRegistered; - } + image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eNone); } void TextureCache::UnregisterImage(ImageId image_id) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 8a6189835..aef33bcf1 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -51,17 +51,16 @@ public: [[nodiscard]] ImageId FindImage(const ImageInfo& info, bool refresh_on_create = true); /// Retrieves an image view with the properties of the specified image descriptor. - [[nodiscard]] ImageView& FindTexture(const AmdGpu::Image& image, bool is_storage); + [[nodiscard]] ImageView& FindTexture(const ImageInfo& image_info, + const ImageViewInfo& view_info); /// Retrieves the render target with specified properties - [[nodiscard]] ImageView& FindRenderTarget(const AmdGpu::Liverpool::ColorBuffer& buffer, - const AmdGpu::Liverpool::CbDbExtent& hint); + [[nodiscard]] ImageView& FindRenderTarget(const ImageInfo& image_info, + const ImageViewInfo& view_info); /// Retrieves the depth target with specified properties - [[nodiscard]] ImageView& FindDepthTarget(const AmdGpu::Liverpool::DepthBuffer& buffer, - u32 num_slices, VAddr htile_address, - const AmdGpu::Liverpool::CbDbExtent& hint, - bool write_enabled); + [[nodiscard]] ImageView& FindDepthTarget(const ImageInfo& image_info, + const ImageViewInfo& view_info); /// Reuploads image contents. void RefreshImage(Image& image); @@ -158,9 +157,6 @@ private: /// Register image in the page table void RegisterImage(ImageId image); - /// Register metadata surfaces attached to the image - void RegisterMeta(const ImageInfo& info, ImageId image); - /// Unregister image from the page table void UnregisterImage(ImageId image); diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index ace2e4d53..4864b9dbe 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -16,6 +16,7 @@ #include #include +#include namespace VideoCore { @@ -176,6 +177,7 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { return vk::Format::eR8Uint; case vk::Format::eR8G8Unorm: case vk::Format::eR16Sfloat: + case vk::Format::eR16Unorm: return vk::Format::eR8G8Uint; case vk::Format::eR8G8B8A8Srgb: case vk::Format::eB8G8R8A8Srgb: @@ -183,10 +185,13 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eR8G8B8A8Unorm: case vk::Format::eR32Sfloat: case vk::Format::eR32Uint: + case vk::Format::eR16G16Sfloat: return vk::Format::eR32Uint; case vk::Format::eBc1RgbaUnormBlock: case vk::Format::eBc4UnormBlock: case vk::Format::eR32G32Sfloat: + case vk::Format::eR32G32Uint: + case vk::Format::eR16G16B16A16Unorm: return vk::Format::eR32G32Uint; case vk::Format::eBc2SrgbBlock: case vk::Format::eBc2UnormBlock: @@ -225,14 +230,14 @@ const DetilerContext* TileManager::GetDetiler(const Image& image) const { return nullptr; } -static constexpr vk::BufferUsageFlags StagingFlags = vk::BufferUsageFlagBits::eTransferDst | - vk::BufferUsageFlagBits::eUniformBuffer | - vk::BufferUsageFlagBits::eStorageBuffer; +struct DetilerParams { + u32 num_levels; + u32 pitch0; + u32 sizes[14]; +}; TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) - : instance{instance}, scheduler{scheduler}, - staging{instance, scheduler, StagingFlags, 256_MB, Vulkan::BufferType::Upload} { - + : instance{instance}, scheduler{scheduler} { static const std::array detiler_shaders{ HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, @@ -264,7 +269,7 @@ TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& sc }, { .binding = 1, - .descriptorType = vk::DescriptorType::eStorageImage, + .descriptorType = vk::DescriptorType::eStorageBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eCompute, }, @@ -281,7 +286,7 @@ TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& sc const vk::PushConstantRange push_constants = { .stageFlags = vk::ShaderStageFlagBits::eCompute, .offset = 0, - .size = sizeof(u32), + .size = sizeof(DetilerParams), }; const vk::DescriptorSetLayout set_layout = *desc_layout; @@ -312,35 +317,88 @@ TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& sc TileManager::~TileManager() = default; -bool TileManager::TryDetile(Image& image) { - if (!image.info.is_tiled) { - return false; +TileManager::ScratchBuffer TileManager::AllocBuffer(u32 size, bool is_storage /*= false*/) { + const auto usage = vk::BufferUsageFlagBits::eStorageBuffer | + (is_storage ? vk::BufferUsageFlagBits::eTransferSrc + : vk::BufferUsageFlagBits::eTransferDst); + const vk::BufferCreateInfo buffer_ci{ + .size = size, + .usage = usage, + }; + + const bool is_large_buffer = size > 128_MB; + VmaAllocationCreateInfo alloc_info{ + .flags = !is_storage ? VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT + : static_cast(0), + .usage = is_large_buffer ? VMA_MEMORY_USAGE_AUTO_PREFER_HOST + : VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + .requiredFlags = !is_storage ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + : static_cast(0), + }; + + VkBuffer buffer; + VmaAllocation allocation; + const auto buffer_ci_unsafe = static_cast(buffer_ci); + const auto result = vmaCreateBuffer(instance.GetAllocator(), &buffer_ci_unsafe, &alloc_info, + &buffer, &allocation, nullptr); + ASSERT(result == VK_SUCCESS); + return {buffer, allocation}; +} + +void TileManager::Upload(ScratchBuffer buffer, const void* data, size_t size) { + VmaAllocationInfo alloc_info{}; + vmaGetAllocationInfo(instance.GetAllocator(), buffer.second, &alloc_info); + ASSERT(size <= alloc_info.size); + void* ptr{}; + const auto result = vmaMapMemory(instance.GetAllocator(), buffer.second, &ptr); + ASSERT(result == VK_SUCCESS); + std::memcpy(ptr, data, size); + vmaUnmapMemory(instance.GetAllocator(), buffer.second); +} + +void TileManager::FreeBuffer(ScratchBuffer buffer) { + vmaDestroyBuffer(instance.GetAllocator(), buffer.first, buffer.second); +} + +std::optional TileManager::TryDetile(Image& image) { + if (!image.info.props.is_tiled) { + return std::nullopt; } const auto* detiler = GetDetiler(image); if (!detiler) { LOG_ERROR(Render_Vulkan, "Unsupported tiled image: {} ({})", vk::to_string(image.info.pixel_format), NameOf(image.info.tiling_mode)); - return false; + return std::nullopt; } - const auto offset = - staging.Copy(image.cpu_addr, image.info.guest_size_bytes, instance.StorageMinAlignment()); - image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eShaderWrite); + // Prepare input buffer + auto in_buffer = AllocBuffer(image.info.guest_size_bytes); + Upload(in_buffer, reinterpret_cast(image.info.guest_address), + image.info.guest_size_bytes); + + // Prepare output buffer + auto out_buffer = AllocBuffer(image.info.guest_size_bytes, true); + + scheduler.DeferOperation([=, this]() { + FreeBuffer(in_buffer); + FreeBuffer(out_buffer); + }); auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, *detiler->pl); const vk::DescriptorBufferInfo input_buffer_info{ - .buffer = staging.Handle(), - .offset = offset, + .buffer = in_buffer.first, + .offset = 0, .range = image.info.guest_size_bytes, }; - ASSERT(image.view_for_detiler.has_value()); - const vk::DescriptorImageInfo output_image_info{ - .imageView = *image.view_for_detiler->image_view, - .imageLayout = image.layout, + const vk::DescriptorBufferInfo output_buffer_info{ + .buffer = out_buffer.first, + .offset = 0, + .range = image.info.guest_size_bytes, }; std::vector set_writes{ @@ -357,20 +415,44 @@ bool TileManager::TryDetile(Image& image) { .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eStorageImage, - .pImageInfo = &output_image_info, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &output_buffer_info, }, }; cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *detiler->pl_layout, 0, set_writes); - cmdbuf.pushConstants(*detiler->pl_layout, vk::ShaderStageFlagBits::eCompute, 0u, - sizeof(image.info.pitch), &image.info.pitch); + DetilerParams params; + params.pitch0 = image.info.pitch >> (image.info.props.is_block ? 2u : 0u); + params.num_levels = image.info.resources.levels; - cmdbuf.dispatch((image.info.size.width * image.info.size.height) / 64, 1, - 1); // round to 64 + ASSERT(image.info.resources.levels <= 14); + std::memset(¶ms.sizes, 0, sizeof(params.sizes)); + for (int m = 0; m < image.info.resources.levels; ++m) { + params.sizes[m] = image.info.mips_layout[m].size * image.info.resources.layers + + (m > 0 ? params.sizes[m - 1] : 0); + } - return true; + auto pitch = image.info.pitch; + cmdbuf.pushConstants(*detiler->pl_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(params), + ¶ms); + + ASSERT((image.info.guest_size_bytes % 64) == 0); + const auto bpp = image.info.num_bits * (image.info.props.is_block ? 16u : 1u); + const auto num_tiles = image.info.guest_size_bytes / (64 * (bpp / 8)); + cmdbuf.dispatch(num_tiles, 1, 1); + + const vk::BufferMemoryBarrier post_barrier{ + .srcAccessMask = vk::AccessFlagBits::eShaderWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .buffer = out_buffer.first, + .size = image.info.guest_size_bytes, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + {}, post_barrier, {}); + + return {out_buffer.first}; } } // namespace VideoCore diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index 98a337860..9102da089 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -34,10 +34,16 @@ struct DetilerContext { class TileManager { public: + using ScratchBuffer = std::pair; + TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); ~TileManager(); - bool TryDetile(Image& image); + std::optional TryDetile(Image& image); + + ScratchBuffer AllocBuffer(u32 size, bool is_storage = false); + void Upload(ScratchBuffer buffer, const void* data, size_t size); + void FreeBuffer(ScratchBuffer buffer); private: const DetilerContext* GetDetiler(const Image& image) const; @@ -45,7 +51,6 @@ private: private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - Vulkan::StreamBuffer staging; std::array detilers; }; From fb145342ce8f4b62aa475539a539352ff022b906 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 28 Jul 2024 12:42:54 -0300 Subject: [PATCH 022/109] log improvement ThrowInvalidType (#330) * log improvement ThrowInvalidType * log improvement ThrowInvalidType --- src/shader_recompiler/ir/ir_emitter.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 6ea3123dd..8b605df81 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -2,14 +2,19 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "shader_recompiler/exception.h" #include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/value.h" namespace Shader::IR { namespace { -[[noreturn]] void ThrowInvalidType(Type type) { - UNREACHABLE_MSG("Invalid type {}", u32(type)); +[[noreturn]] void ThrowInvalidType(Type type, + std::source_location loc = std::source_location::current()) { + const std::string functionName = loc.function_name(); + const int lineNumber = loc.line(); + UNREACHABLE_MSG("Invalid type = {}, functionName = {}, line = {}", u32(type), functionName, + lineNumber); } Value MakeLodClampPair(IREmitter& ir, const F32& bias_lod, const F32& lod_clamp) { From aa7c8ca2b6b0788550b700ebced6505dd92d4ddb Mon Sep 17 00:00:00 2001 From: Dzmitry Dubrova Date: Sun, 28 Jul 2024 18:43:44 +0300 Subject: [PATCH 023/109] Implement some pthread calls (#332) --- .../libraries/kernel/thread_management.cpp | 52 +++++++++++++++++++ src/core/libraries/kernel/thread_management.h | 3 ++ 2 files changed, 55 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 3e9e1994c..0992009aa 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -394,6 +394,18 @@ int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpuma return result; } +int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask) { + LOG_INFO(Kernel_Pthread, "called"); + + if (thread == nullptr) { + return SCE_KERNEL_ERROR_ESRCH; + } + + auto result = scePthreadAttrGetaffinity(&thread->attr, mask); + + return result; +} + ScePthreadMutex* createMutex(ScePthreadMutex* addr) { if (addr == nullptr || *addr != nullptr) { return addr; @@ -1243,6 +1255,40 @@ int PS4_SYSV_ABI posix_pthread_attr_destroy(ScePthreadAttr* attr) { return result; } +int PS4_SYSV_ABI posix_pthread_attr_setschedparam(ScePthreadAttr* attr, + const SceKernelSchedParam* param) { + int result = scePthreadAttrSetschedparam(attr, param); + if (result < 0) { + int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP + ? result + -SCE_KERNEL_ERROR_UNKNOWN + : POSIX_EOTHER; + return rt; + } + return result; +} + +int PS4_SYSV_ABI posix_pthread_attr_setinheritsched(ScePthreadAttr* attr, int inheritSched) { + int result = scePthreadAttrSetinheritsched(attr, inheritSched); + if (result < 0) { + int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP + ? result + -SCE_KERNEL_ERROR_UNKNOWN + : POSIX_EOTHER; + return rt; + } + return result; +} + +int PS4_SYSV_ABI posix_pthread_setprio(ScePthread thread, int prio) { + int result = scePthreadSetprio(thread, prio); + if (result < 0) { + int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP + ? result + -SCE_KERNEL_ERROR_UNKNOWN + : POSIX_EOTHER; + return rt; + } + return result; +} + int PS4_SYSV_ABI posix_pthread_attr_setdetachstate(ScePthreadAttr* attr, int detachstate) { // LOG_INFO(Kernel_Pthread, "posix pthread_mutexattr_init redirect to scePthreadMutexattrInit"); int result = scePthreadAttrSetdetachstate(attr, detachstate); @@ -1476,6 +1522,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", 1, 1, posix_pthread_create); LIB_FUNCTION("OxhIB8LB-PQ", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create); LIB_FUNCTION("bt3CTBKmGyI", "libkernel", 1, "libkernel", 1, 1, scePthreadSetaffinity); + LIB_FUNCTION("rcrVFJsQWRY", "libkernel", 1, "libkernel", 1, 1, scePthreadGetaffinity); LIB_FUNCTION("6UgtwV+0zb4", "libkernel", 1, "libkernel", 1, 1, scePthreadCreate); LIB_FUNCTION("T72hz6ffq08", "libkernel", 1, "libkernel", 1, 1, scePthreadYield); LIB_FUNCTION("B5GmVDKwpn0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_yield); @@ -1543,6 +1590,11 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("E+tyo3lp5Lw", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_attr_setdetachstate); LIB_FUNCTION("zHchY8ft5pk", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_attr_destroy); + LIB_FUNCTION("euKRgm0Vn2M", "libScePosix", 1, "libkernel", 1, 1, + posix_pthread_attr_setschedparam); + LIB_FUNCTION("7ZlAakEf0Qg", "libScePosix", 1, "libkernel", 1, 1, + posix_pthread_attr_setinheritsched); + LIB_FUNCTION("a2P9wYGeZvc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setprio); LIB_FUNCTION("Jmi+9w9u0E4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create_name_np); LIB_FUNCTION("OxhIB8LB-PQ", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create); LIB_FUNCTION("+U1R4WtXvoc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_detach); diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index 8303c9ef1..1b33ac949 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -169,9 +169,12 @@ ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask); int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask); +int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask); int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr, PthreadEntryFunc start_routine, void* arg, const char* name); +int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); + /*** * Mutex calls */ From 3e6af54ea34f706c0c80a2ba2efaee72bb71c7ed Mon Sep 17 00:00:00 2001 From: Vasyl Baran Date: Sun, 28 Jul 2024 22:21:18 +0300 Subject: [PATCH 024/109] Fixup for detiler artifacts on macOS --- src/video_core/texture_cache/tile_manager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 4864b9dbe..4f199f81f 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -326,7 +326,12 @@ TileManager::ScratchBuffer TileManager::AllocBuffer(u32 size, bool is_storage /* .usage = usage, }; +#ifdef __APPLE__ + // Fix for detiler artifacts on macOS + const bool is_large_buffer = true; +#else const bool is_large_buffer = size > 128_MB; +#endif VmaAllocationCreateInfo alloc_info{ .flags = !is_storage ? VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT From 43d60a8ac9e5b0f44c50c8fa263ac24957b8eeaf Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:31:15 -0700 Subject: [PATCH 025/109] Add sem_timedwait polyfill for macOS. --- CMakeLists.txt | 6 ++- .../libraries/kernel/thread_management.cpp | 37 ++++++++++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08cc41036..9cb5f0ce3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,12 +85,16 @@ if (APPLE) find_package(date 3.0.1 CONFIG) endif() +# Note: Windows always has these functions through winpthreads include(CheckSymbolExists) check_symbol_exists(pthread_mutex_timedlock "pthread.h" HAVE_PTHREAD_MUTEX_TIMEDLOCK) -# Windows always has the function through winpthreads if(HAVE_PTHREAD_MUTEX_TIMEDLOCK OR WIN32) add_compile_options(-DHAVE_PTHREAD_MUTEX_TIMEDLOCK) endif() +check_symbol_exists(sem_timedwait "semaphore.h" HAVE_SEM_TIMEDWAIT) +if(HAVE_SEM_TIMEDWAIT OR WIN32) + add_compile_options(-DHAVE_SEM_TIMEDWAIT) +endif() add_subdirectory(externals) include_directories(src) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 0992009aa..d5e2adead 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1382,13 +1382,38 @@ int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) { return sem_wait(sem); } -int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) { -#ifndef __APPLE__ - return sem_timedwait(sem, t); -#else - LOG_ERROR(Kernel_Pthread, "Apple doesn't support sem_timedwait yet"); - return 0; // unsupported for apple yet +#ifndef HAVE_SEM_TIMEDWAIT +int sem_timedwait(sem_t* sem, const struct timespec* abstime) { + int rc; + while ((rc = sem_trywait(sem)) == EAGAIN) { + struct timespec curr_time; + clock_gettime(CLOCK_REALTIME, &curr_time); + + s64 remaining_ns = 0; + remaining_ns += + (static_cast(abstime->tv_sec) - static_cast(curr_time.tv_sec)) * 1000000000L; + remaining_ns += static_cast(abstime->tv_nsec) - static_cast(curr_time.tv_nsec); + + if (remaining_ns <= 0) { + return ETIMEDOUT; + } + + struct timespec sleep_time; + sleep_time.tv_sec = 0; + if (remaining_ns < 5000000L) { + sleep_time.tv_nsec = remaining_ns; + } else { + sleep_time.tv_nsec = 5000000; + } + + nanosleep(&sleep_time, nullptr); + } + return rc; +} #endif + +int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) { + return sem_timedwait(sem, t); } int PS4_SYSV_ABI posix_sem_post(sem_t* sem) { From c1d01709bed276b174a90a679100ecbc8576c2bc Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 29 Jul 2024 19:08:06 +0300 Subject: [PATCH 026/109] kernel: Implement sceKernelSetVirtualRangeName (#338) * Fix in searchFree should fix #337 * clang format fix * sceKernelSetVirtualRangeName implementation * improved vaddr conversion * updated VirtualQuery to include name too * unmap also removed name thanks @red_prig * fixed copy... --- src/core/libraries/kernel/libkernel.cpp | 1 + src/core/libraries/kernel/memory_management.cpp | 15 +++++++++++++++ src/core/libraries/kernel/memory_management.h | 2 ++ src/core/memory.cpp | 11 ++++++++++- src/core/memory.h | 2 ++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index a7f619f1a..5f2e5a509 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -407,6 +407,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap); LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2); + LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName); // equeue LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue); diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 988b69d0c..d396e1d74 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -274,4 +274,19 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn return result; } +s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name) { + static constexpr size_t MaxNameSize = 32; + if (std::strlen(name) > MaxNameSize) { + LOG_ERROR(Kernel_Vmm, "name exceeds 32 bytes!"); + return ORBIS_KERNEL_ERROR_ENAMETOOLONG; + } + + if (name == nullptr) { + LOG_ERROR(Kernel_Vmm, "name is invalid!"); + return ORBIS_KERNEL_ERROR_EFAULT; + } + auto* memory = Core::Memory::Instance(); + memory->NameVirtualRange(std::bit_cast(addr), len, name); + return ORBIS_OK; +} } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index cc89dfa7d..25a4a9f09 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -108,4 +108,6 @@ s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEnt s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, int* numEntriesOut, int flags); +s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name); + } // namespace Libraries::Kernel diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f2607bffd..27e05f41a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -188,7 +188,7 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem // Find first free area to map the file. if (False(flags & MemoryMapFlags::Fixed)) { - mapped_addr = SearchFree(mapped_addr, size_aligned); + mapped_addr = SearchFree(mapped_addr, size_aligned, 1); } if (True(flags & MemoryMapFlags::Fixed)) { @@ -235,6 +235,7 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { vma.prot = MemoryProt::NoAccess; vma.phys_base = 0; vma.disallow_merge = false; + vma.name = ""; MergeAdjacent(vma_map, new_it); // Unmap the memory region. @@ -280,6 +281,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, info->is_flexible.Assign(vma.type == VMAType::Flexible); info->is_direct.Assign(vma.type == VMAType::Direct); info->is_commited.Assign(vma.type != VMAType::Free); + vma.name.copy(info->name.data(), std::min(info->name.size(), vma.name.size())); if (vma.type == VMAType::Direct) { const auto dmem_it = FindDmemArea(vma.phys_base); ASSERT(dmem_it != dmem_map.end()); @@ -338,6 +340,13 @@ std::pair MemoryManager::GetVulkanBuffer(VAddr addr) { return std::make_pair(*it->second.buffer, addr - it->first); } +void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) { + auto it = FindVMA(virtual_addr); + + ASSERT_MSG(it->second.Contains(virtual_addr, size), + "Range provided is not fully containted in vma"); + it->second.name = name; +} VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { auto it = FindVMA(virtual_addr); // If the VMA is free and contains the requested mapping we are done. diff --git a/src/core/memory.h b/src/core/memory.h index ff4af5cd2..2b3d07a78 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -177,6 +177,8 @@ public: int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); + void NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name); + private: VMAHandle FindVMA(VAddr target) { return std::prev(vma_map.upper_bound(target)); From 867f38fe13f484e439f978e5a3d0f4742324e0f0 Mon Sep 17 00:00:00 2001 From: Borchev <4501931+Borchev@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:37:05 -0700 Subject: [PATCH 027/109] Add pthread_attr_getstacksize thunk --- src/core/libraries/kernel/thread_management.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index d5e2adead..4615b246a 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1428,6 +1428,10 @@ int PS4_SYSV_ABI posix_sem_getvalue(sem_t* sem, int* sval) { return sem_getvalue(sem, sval); } +int PS4_SYSV_ABI posix_pthread_attr_getstacksize(const pthread_attr_t* attr, size_t* size) { + return pthread_attr_getstacksize(attr, size); +} + int PS4_SYSV_ABI scePthreadGetschedparam(ScePthread thread, int* policy, SceKernelSchedParam* param) { return pthread_getschedparam(thread->pth, policy, param); @@ -1633,6 +1637,8 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post); LIB_FUNCTION("cDW233RAwWo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_destroy); LIB_FUNCTION("Bq+LRV-N6Hk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_getvalue); + LIB_FUNCTION("0qOtCR-ZHck", "libScePosix", 1, "libkernel", 1, 1, + posix_pthread_attr_getstacksize); // libs RwlockSymbolsRegister(sym); SemaphoreSymbolsRegister(sym); From 361271826e27fd15aa724b1806c5f58003760977 Mon Sep 17 00:00:00 2001 From: Borchev <4501931+Borchev@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:40:42 -0700 Subject: [PATCH 028/109] Fix SearchFree function bug (#339) --- src/core/memory.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 27e05f41a..aa552d511 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -348,7 +348,15 @@ void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::strin it->second.name = name; } VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { + // If the requested address is below the mapped range, start search from the lowest address + auto min_search_address = impl.SystemManagedVirtualBase(); + if (virtual_addr < min_search_address) { + virtual_addr = min_search_address; + } + auto it = FindVMA(virtual_addr); + ASSERT_MSG(it != vma_map.end(), "Specified mapping address was not found!"); + // If the VMA is free and contains the requested mapping we are done. if (it->second.IsFree() && it->second.Contains(virtual_addr, size)) { return virtual_addr; From b3525d7f79a3d9f917b050ce296152e1cd90b3ae Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:41:31 +0200 Subject: [PATCH 029/109] Don't download unnecessary DLLs (#341) --- .github/workflows/windows-qt.yml | 3 ++- CMakeLists.txt | 21 ++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 9610280bc..099ece0bc 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -28,8 +28,9 @@ jobs: - name: Setup Qt uses: jurplel/install-qt-action@v4 with: - arch: win64_msvc2019_64 version: 6.7.2 + arch: win64_msvc2019_64 + archives: qtsvg qtbase - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cb5f0ce3..60dad68aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,11 @@ project(shadPS4) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) -# This function should be passed a list of all files in a target. It will automatically generate -# file groups following the directory hierarchy, so that the layout of the files in IDEs matches the -# one in the filesystem. +# This function should be passed a list of all files in a target. It will automatically generate file groups +# following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem. function(create_target_directory_groups target_name) - # Place any files that aren't in the source list in a separate group so that they don't get in - # the way. + + # Place any files that aren't in the source list in a separate group so that they don't get in the way. source_group("Other Files" REGULAR_EXPRESSION ".") get_target_property(target_sources "${target_name}" SOURCES) @@ -39,14 +38,6 @@ endfunction() # Setup a custom clang-format target (if clang-format can be found) that will run # against all the src files. This should be used before making a pull request. -# ======================================================================= - -set(CLANG_FORMAT_POSTFIX "-17") -find_program(CLANG_FORMAT - NAMES clang-format${CLANG_FORMAT_POSTFIX} - clang-format - PATHS ${PROJECT_BINARY_DIR}/externals) - if (CLANG_FORMAT) set(SRCS ${PROJECT_SOURCE_DIR}/src) set(CCOMMENT "Running clang format against all the .h and .cpp files in src/") @@ -503,7 +494,7 @@ set(EMULATOR src/emulator.cpp src/sdl_window.cpp ) -# the above is shared in sdl and qt version (TODO share them all) +# The above is shared in SDL and Qt version (TODO share them all) if(ENABLE_QT_GUI) qt_add_resources(RESOURCE_FILES src/shadps4.qrc) @@ -632,6 +623,6 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES -# WIN32_EXECUTABLE ON +# WIN32_EXECUTABLE ON MACOSX_BUNDLE ON) endif() From a7c9bfa5c59d3e75981feeacfba9c92ca951a057 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Wed, 31 Jul 2024 00:32:40 +0300 Subject: [PATCH 030/109] shader_recompiler: Small instruction parsing refactor/bugfixes (#340) * translator: Implemtn f32 to f16 convert * shader_recompiler: Add bit instructions * shader_recompiler: More data share instructions * shader_recompiler: Remove exec contexts, fix S_MOV_B64 * shader_recompiler: Split instruction parsing into categories * shader_recompiler: Better BFS search * shader_recompiler: Constant propagation pass for cmp_class_f32 * shader_recompiler: Partial readfirstlane implementation * shader_recompiler: Stub readlane/writelane only for non-compute * hack: Fix swizzle on RDR * Will properly fix this when merging this * clang format * address_space: Bump user area size to full * shader_recompiler: V_INTERP_MOV_F32 * Should work the same as spirv will emit flat decoration on demand * kernel: Add MAP_OP_MAP_FLEXIBLE * image_view: Attempt to apply storage swizzle on format * vk_scheduler: Barrier attachments on renderpass end * clang format * liverpool: cs state backup * shader_recompiler: More instructions and formats * vector_alu: Proper V_MBCNT_U32_B32 * shader_recompiler: Port some dark souls things * file_system: Implement sceKernelRename * more formats * clang format * resource_tracking_pass: Back to assert * translate: Tracedata * kernel: Remove tracy lock * Solves random crashes in Dark Souls * code: Review comments --- src/common/config.cpp | 2 +- src/common/logging/backend.cpp | 2 +- src/core/address_space.h | 5 +- src/core/file_sys/fs.cpp | 2 +- src/core/libraries/gnmdriver/gnmdriver.cpp | 2 +- src/core/libraries/kernel/file_system.cpp | 23 + src/core/libraries/kernel/libkernel.cpp | 4 + .../libraries/kernel/memory_management.cpp | 10 + .../libraries/kernel/thread_management.cpp | 12 - src/core/libraries/kernel/thread_management.h | 2 - src/core/libraries/network/net.cpp | 2 +- src/core/libraries/network/netctl.cpp | 2 +- src/core/libraries/np_manager/np_manager.cpp | 4 +- .../backend/spirv/emit_spirv.cpp | 9 +- .../spirv/emit_spirv_bitwise_conversion.cpp | 4 +- .../spirv/emit_spirv_context_get_set.cpp | 6 +- .../backend/spirv/emit_spirv_convert.cpp | 4 + .../spirv/emit_spirv_floating_point.cpp | 4 + .../backend/spirv/emit_spirv_image.cpp | 4 +- .../backend/spirv/emit_spirv_instructions.h | 6 +- .../backend/spirv/emit_spirv_integer.cpp | 4 + .../backend/spirv/emit_spirv_warp.cpp | 4 + .../backend/spirv/spirv_emit_context.cpp | 43 +- .../backend/spirv/spirv_emit_context.h | 4 +- src/shader_recompiler/frontend/format.cpp | 12 +- src/shader_recompiler/frontend/instruction.h | 4 +- .../frontend/structured_control_flow.cpp | 14 +- .../frontend/structured_control_flow.h | 5 +- .../frontend/translate/data_share.cpp | 76 +- .../frontend/translate/export.cpp | 2 +- .../frontend/translate/scalar_alu.cpp | 177 +++- .../frontend/translate/scalar_memory.cpp | 23 + .../frontend/translate/translate.cpp | 772 ++---------------- .../frontend/translate/translate.h | 40 +- .../frontend/translate/vector_alu.cpp | 397 ++++++++- .../translate/vector_interpolation.cpp | 20 + .../frontend/translate/vector_memory.cpp | 91 ++- .../ir/breadth_first_search.h | 22 +- src/shader_recompiler/ir/ir_emitter.cpp | 19 +- src/shader_recompiler/ir/ir_emitter.h | 3 + src/shader_recompiler/ir/opcodes.inc | 4 + .../ir/passes/constant_propogation_pass.cpp | 15 + .../ir/passes/resource_tracking_pass.cpp | 85 +- .../ir/passes/shader_info_collection_pass.cpp | 8 + src/shader_recompiler/ir/reg.h | 18 + src/shader_recompiler/profile.h | 1 + src/shader_recompiler/recompiler.cpp | 9 +- src/shader_recompiler/recompiler.h | 5 +- src/shader_recompiler/runtime_info.h | 8 + src/video_core/amdgpu/liverpool.cpp | 5 +- src/video_core/amdgpu/liverpool.h | 2 + src/video_core/amdgpu/pixel_format.cpp | 71 ++ src/video_core/amdgpu/pixel_format.h | 11 + src/video_core/amdgpu/resource.h | 46 +- .../renderer_vulkan/liverpool_to_vk.cpp | 24 + .../renderer_vulkan/vk_compute_pipeline.cpp | 2 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 2 +- .../renderer_vulkan/vk_instance.cpp | 10 +- src/video_core/renderer_vulkan/vk_instance.h | 6 + .../renderer_vulkan/vk_pipeline_cache.cpp | 7 +- .../renderer_vulkan/vk_rasterizer.cpp | 4 +- .../renderer_vulkan/vk_scheduler.cpp | 25 + src/video_core/renderer_vulkan/vk_scheduler.h | 2 + src/video_core/texture_cache/image_view.cpp | 25 +- src/video_core/texture_cache/image_view.h | 2 + .../texture_cache/texture_cache.cpp | 15 +- 66 files changed, 1349 insertions(+), 904 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 57f40b212..7e677f84a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -15,7 +15,7 @@ static u32 screenWidth = 1280; static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static std::string logFilter; -static std::string logType = "sync"; +static std::string logType = "async"; static bool isDebugDump = false; static bool isLibc = true; static bool isShowSplash = false; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 0b03c86b8..a21af8bba 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -207,8 +207,8 @@ public: message_queue.EmplaceWait(entry); } else { ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); + std::fflush(stdout); } - std::fflush(stdout); } private: diff --git a/src/core/address_space.h b/src/core/address_space.h index e25159023..29f74f568 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -34,10 +34,7 @@ constexpr VAddr USER_MAX = 0xFBFFFFFFFFULL; static constexpr size_t SystemManagedSize = SYSTEM_MANAGED_MAX - SYSTEM_MANAGED_MIN + 1; static constexpr size_t SystemReservedSize = SYSTEM_RESERVED_MAX - SYSTEM_RESERVED_MIN + 1; -// User area size is normally larger than this. However games are unlikely to map to high -// regions of that area, so by default we allocate a smaller virtual address space (about 1/4th). -// to save space on page tables. -static constexpr size_t UserSize = 1ULL << 39; +static constexpr size_t UserSize = 1ULL << 40; /** * Represents the user virtual address space backed by a dmem memory block diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 3177770b0..2bcff191c 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -70,7 +70,7 @@ std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) // exist in filesystem but in different case. auto guest_path = current_path; while (!path_parts.empty()) { - const auto& part = path_parts.back(); + const auto part = path_parts.back(); const auto add_match = [&](const auto& host_part) { current_path /= host_part; guest_path /= part; diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index dba69d6eb..650252f95 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -957,7 +957,7 @@ int PS4_SYSV_ABI sceGnmGetGpuBlockStatus() { } int PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); + LOG_DEBUG(Lib_GnmDriver, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 4a42b0d6f..f83863479 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -472,6 +472,28 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { return file->f.WriteRaw(buf, nbytes); } +s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { + auto* mnt = Common::Singleton::Instance(); + const auto src_path = mnt->GetHostPath(from); + if (!std::filesystem::exists(src_path)) { + return ORBIS_KERNEL_ERROR_ENOENT; + } + const auto dst_path = mnt->GetHostPath(to); + const bool src_is_dir = std::filesystem::is_directory(src_path); + const bool dst_is_dir = std::filesystem::is_directory(dst_path); + if (src_is_dir && !dst_is_dir) { + return ORBIS_KERNEL_ERROR_ENOTDIR; + } + if (!src_is_dir && dst_is_dir) { + return ORBIS_KERNEL_ERROR_EISDIR; + } + if (dst_is_dir && !std::filesystem::is_empty(dst_path)) { + return ORBIS_KERNEL_ERROR_ENOTEMPTY; + } + std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + return ORBIS_OK; +} + void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) { std::srand(std::time(nullptr)); LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen); @@ -493,6 +515,7 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat); LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat); LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate); + LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename); LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat); LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread); diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 5f2e5a509..e2625819b 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -7,6 +7,7 @@ #include #include "common/assert.h" +#include "common/debug.h" #include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/singleton.h" @@ -84,6 +85,9 @@ static PS4_SYSV_ABI void stack_chk_fail() { int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len); + if (len == 0) { + return ORBIS_OK; + } auto* memory = Core::Memory::Instance(); memory->UnmapMemory(std::bit_cast(addr), len); return SCE_OK; diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index d396e1d74..94762c4a0 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -262,6 +262,16 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}", i, entries[i].operation, entries[i].length, result); + if (result == 0) + processed++; + } else if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE) { + result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length, + entries[i].protection, flags, ""); + LOG_INFO(Kernel_Vmm, + "BatchMap: entry = {}, operation = {}, len = {:#x}, type = {}, " + "result = {}", + i, entries[i].operation, entries[i].length, (u8)entries[i].type, result); + if (result == 0) processed++; } else { diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 4615b246a..c5237d0ad 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -439,11 +439,7 @@ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMut int result = pthread_mutex_init(&(*mutex)->pth_mutex, &(*attr)->pth_mutex_attr); - static auto mutex_loc = MUTEX_LOCATION("mutex"); - (*mutex)->tracy_lock = std::make_unique(&mutex_loc); - if (name != nullptr) { - (*mutex)->tracy_lock->CustomName(name, std::strlen(name)); LOG_INFO(Kernel_Pthread, "name={}, result={}", name, result); } @@ -555,15 +551,11 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) { return SCE_KERNEL_ERROR_EINVAL; } - (*mutex)->tracy_lock->BeforeLock(); - int result = pthread_mutex_lock(&(*mutex)->pth_mutex); if (result != 0) { LOG_TRACE(Kernel_Pthread, "Locked name={}, result={}", (*mutex)->name, result); } - (*mutex)->tracy_lock->AfterLock(); - switch (result) { case 0: return SCE_OK; @@ -589,8 +581,6 @@ int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) { LOG_TRACE(Kernel_Pthread, "Unlocking name={}, result={}", (*mutex)->name, result); } - (*mutex)->tracy_lock->AfterUnlock(); - switch (result) { case 0: return SCE_OK; @@ -1195,8 +1185,6 @@ int PS4_SYSV_ABI scePthreadMutexTrylock(ScePthreadMutex* mutex) { LOG_TRACE(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result); } - (*mutex)->tracy_lock->AfterTryLock(result == 0); - switch (result) { case 0: return ORBIS_OK; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index 1b33ac949..c5935275f 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -9,7 +9,6 @@ #include #include #include -#include "common/debug.h" #include "common/types.h" namespace Core::Loader { @@ -74,7 +73,6 @@ struct PthreadMutexInternal { u8 reserved[256]; std::string name; pthread_mutex_t pth_mutex; - std::unique_ptr tracy_lock; }; struct PthreadMutexattrInternal { diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 1569a51ce..958f9264e 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -559,7 +559,7 @@ int PS4_SYSV_ABI sceNetEpollDestroy() { } int PS4_SYSV_ABI sceNetEpollWait() { - LOG_ERROR(Lib_Net, "(STUBBED) called"); + LOG_TRACE(Lib_Net, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index ab1cb8ae6..a1c8e81c0 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -79,7 +79,7 @@ int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() { } int PS4_SYSV_ABI sceNetCtlCheckCallback() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); + LOG_TRACE(Lib_NetCtl, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index ee4b3d6b6..33308abc1 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -870,7 +870,7 @@ int PS4_SYSV_ABI sceNpAsmTerminate() { } int PS4_SYSV_ABI sceNpCheckCallback() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); + LOG_TRACE(Lib_NpManager, "(STUBBED) called"); return ORBIS_OK; } @@ -3510,4 +3510,4 @@ void RegisterlibSceNpManager(Core::Loader::SymbolsResolver* sym) { sceNpUnregisterStateCallbackForToolkit); }; -} // namespace Libraries::NpManager \ No newline at end of file +} // namespace Libraries::NpManager diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 561014a33..c70427635 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -183,6 +183,7 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { ctx.AddCapability(spv::Capability::Float16); ctx.AddCapability(spv::Capability::Int16); } + ctx.AddCapability(spv::Capability::Int64); if (info.has_storage_images) { ctx.AddCapability(spv::Capability::StorageImageExtendedFormats); } @@ -204,8 +205,8 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { } else { ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft); } + ctx.AddCapability(spv::Capability::GroupNonUniform); if (info.uses_group_quad) { - ctx.AddCapability(spv::Capability::GroupNonUniform); ctx.AddCapability(spv::Capability::GroupNonUniformQuad); } if (info.has_discard) { @@ -217,9 +218,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (info.has_image_query) { ctx.AddCapability(spv::Capability::ImageQuery); } - // if (program.info.stores_frag_depth) { - // ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing); - // } + if (info.stores.Get(IR::Attribute::Depth)) { + ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing); + } break; default: throw NotImplementedException("Stage {}", u32(program.info.stage)); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp index da29f3927..03a0a00f0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp @@ -6,8 +6,8 @@ namespace Shader::Backend::SPIRV { -void EmitBitCastU16F16(EmitContext&) { - UNREACHABLE_MSG("SPIR-V Instruction"); +Id EmitBitCastU16F16(EmitContext& ctx, Id value) { + return ctx.OpBitcast(ctx.U16, value); } Id EmitBitCastU32F32(EmitContext& ctx, Id value) { 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 87ffa150f..02480303f 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 @@ -120,6 +120,7 @@ void EmitGetGotoVariable(EmitContext&) { } Id EmitReadConst(EmitContext& ctx) { + return ctx.u32_zero_value; UNREACHABLE_MSG("Unreachable instruction"); } @@ -149,6 +150,9 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp) { // Attribute is disabled or varying component is not written return ctx.ConstF32(comp == 3 ? 1.0f : 0.0f); } + if (param.is_default) { + return ctx.OpCompositeExtract(param.component_type, param.id, comp); + } if (param.num_components > 1) { const Id pointer{ @@ -208,7 +212,7 @@ Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp) { void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 element) { const Id pointer{OutputAttrPointer(ctx, attr, element)}; - ctx.OpStore(pointer, value); + ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value)); } Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp index ede592e0d..945fa6877 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp @@ -259,4 +259,8 @@ Id EmitConvertU16U32(EmitContext& ctx, Id value) { return ctx.OpUConvert(ctx.U16, value); } +Id EmitConvertU32U16(EmitContext& ctx, Id value) { + return ctx.OpUConvert(ctx.U32[1], value); +} + } // namespace Shader::Backend::SPIRV 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 911983a41..e822eabef 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -385,4 +385,8 @@ Id EmitFPIsInf64(EmitContext& ctx, Id value) { return ctx.OpIsInf(ctx.U1[1], value); } +void EmitFPCmpClass32(EmitContext&) { + UNREACHABLE(); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 17def57ab..030d39485 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -70,7 +70,6 @@ Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id o const u32 comp = inst->Flags().gather_comp.Value(); ImageOperands operands; operands.Add(spv::ImageOperandsMask::Offset, offset); - operands.Add(spv::ImageOperandsMask::Lod, ctx.ConstF32(0.f)); return ctx.OpImageGather(ctx.F32[4], sampled_image, coords, ctx.ConstU32(comp), operands.mask, operands.operands); } @@ -106,8 +105,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod const auto type = ctx.info.images[handle & 0xFFFF].type; const Id zero = ctx.u32_zero_value; const auto mips{[&] { return skip_mips ? zero : ctx.OpImageQueryLevels(ctx.U32[1], image); }}; - const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa && - type != AmdGpu::ImageType::Buffer}; + const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa}; const auto query{[&](Id type) { return uses_lod ? ctx.OpImageQuerySizeLod(type, image, lod) : ctx.OpImageQuerySize(type, image); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 80dd66b16..51899eb4d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -42,6 +42,7 @@ void EmitSetVcc(EmitContext& ctx); void EmitSetSccLo(EmitContext& ctx); void EmitSetVccLo(EmitContext& ctx); void EmitSetVccHi(EmitContext& ctx); +void EmitFPCmpClass32(EmitContext& ctx); void EmitPrologue(EmitContext& ctx); void EmitEpilogue(EmitContext& ctx); void EmitDiscard(EmitContext& ctx); @@ -148,7 +149,7 @@ Id EmitSelectU64(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF16(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value); -void EmitBitCastU16F16(EmitContext& ctx); +Id EmitBitCastU16F16(EmitContext& ctx, Id value); Id EmitBitCastU32F32(EmitContext& ctx, Id value); void EmitBitCastU64F64(EmitContext& ctx); Id EmitBitCastF16U16(EmitContext& ctx, Id value); @@ -282,6 +283,7 @@ Id EmitBitCount32(EmitContext& ctx, Id value); Id EmitBitwiseNot32(EmitContext& ctx, Id value); Id EmitFindSMsb32(EmitContext& ctx, Id value); Id EmitFindUMsb32(EmitContext& ctx, Id value); +Id EmitFindILsb32(EmitContext& ctx, Id value); Id EmitSMin32(EmitContext& ctx, Id a, Id b); Id EmitUMin32(EmitContext& ctx, Id a, Id b); Id EmitSMax32(EmitContext& ctx, Id a, Id b); @@ -353,6 +355,7 @@ Id EmitConvertF64U16(EmitContext& ctx, Id value); Id EmitConvertF64U32(EmitContext& ctx, Id value); Id EmitConvertF64U64(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value); +Id EmitConvertU32U16(EmitContext& ctx, Id value); Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, Id offset); @@ -387,6 +390,7 @@ Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitLaneId(EmitContext& ctx); +Id EmitWarpId(EmitContext& ctx); Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index); } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 019ceb01b..f20c4fac2 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -198,6 +198,10 @@ Id EmitFindUMsb32(EmitContext& ctx, Id value) { return ctx.OpFindUMsb(ctx.U32[1], value); } +Id EmitFindILsb32(EmitContext& ctx, Id value) { + return ctx.OpFindILsb(ctx.U32[1], value); +} + Id EmitSMin32(EmitContext& ctx, Id a, Id b) { return ctx.OpSMin(ctx.U32[1], a, b); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index a17515887..bd4ac0668 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -10,6 +10,10 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.ConstU32(static_cast(spv::Scope::Subgroup)); } +Id EmitWarpId(EmitContext& ctx) { + return ctx.OpLoad(ctx.U32[1], ctx.subgroup_id); +} + Id EmitLaneId(EmitContext& ctx) { return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 9ce87add2..f7b30052b 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -49,7 +49,7 @@ EmitContext::EmitContext(const Profile& profile_, IR::Program& program, u32& bin DefineInterfaces(program); DefineBuffers(info); DefineImagesAndSamplers(info); - DefineSharedMemory(info); + DefineSharedMemory(); } EmitContext::~EmitContext() = default; @@ -86,6 +86,7 @@ void EmitContext::DefineArithmeticTypes() { F32[1] = Name(TypeFloat(32), "f32_id"); S32[1] = Name(TypeSInt(32), "i32_id"); U32[1] = Name(TypeUInt(32), "u32_id"); + U64 = Name(TypeUInt(64), "u64_id"); for (u32 i = 2; i <= 4; i++) { if (info.uses_fp16) { @@ -126,6 +127,7 @@ Id GetAttributeType(EmitContext& ctx, AmdGpu::NumberFormat fmt) { case AmdGpu::NumberFormat::Float: case AmdGpu::NumberFormat::Unorm: case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: return ctx.F32[4]; case AmdGpu::NumberFormat::Sint: return ctx.S32[4]; @@ -146,6 +148,7 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f case AmdGpu::NumberFormat::Float: case AmdGpu::NumberFormat::Unorm: case AmdGpu::NumberFormat::Snorm: + case AmdGpu::NumberFormat::SnormNz: return {id, input_f32, F32[1], 4}; case AmdGpu::NumberFormat::Uint: return {id, input_u32, U32[1], 4}; @@ -204,7 +207,9 @@ void EmitContext::DefineInputs(const Info& info) { : 1; // Note that we pass index rather than Id input_params[input.binding] = { - rate_idx, input_u32, U32[1], input.num_components, input.instance_data_buf, + rate_idx, input_u32, + U32[1], input.num_components, + false, input.instance_data_buf, }; } else { Id id{DefineInput(type, input.binding)}; @@ -220,19 +225,18 @@ void EmitContext::DefineInputs(const Info& info) { break; } case Stage::Fragment: - if (info.uses_group_quad) { - subgroup_local_invocation_id = DefineVariable( - U32[1], spv::BuiltIn::SubgroupLocalInvocationId, spv::StorageClass::Input); - Decorate(subgroup_local_invocation_id, spv::Decoration::Flat); - } + subgroup_id = DefineVariable(U32[1], spv::BuiltIn::SubgroupId, spv::StorageClass::Input); + subgroup_local_invocation_id = DefineVariable( + U32[1], spv::BuiltIn::SubgroupLocalInvocationId, spv::StorageClass::Input); + Decorate(subgroup_local_invocation_id, spv::Decoration::Flat); frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input); frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output); front_facing = DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); for (const auto& input : info.ps_inputs) { const u32 semantic = input.param_index; if (input.is_default) { - input_params[semantic] = {MakeDefaultValue(*this, input.default_value), input_f32, - F32[1]}; + input_params[semantic] = {MakeDefaultValue(*this, input.default_value), F32[1], + F32[1], 4, true}; continue; } const IR::Attribute param{IR::Attribute::Param0 + input.param_index}; @@ -392,7 +396,16 @@ spv::ImageFormat GetFormat(const AmdGpu::Image& image) { image.GetNumberFmt() == AmdGpu::NumberFormat::Uint) { return spv::ImageFormat::Rgba8ui; } - UNREACHABLE(); + if (image.GetDataFmt() == AmdGpu::DataFormat::Format10_11_11 && + image.GetNumberFmt() == AmdGpu::NumberFormat::Float) { + return spv::ImageFormat::R11fG11fB10f; + } + if (image.GetDataFmt() == AmdGpu::DataFormat::Format32_32_32_32 && + image.GetNumberFmt() == AmdGpu::NumberFormat::Float) { + return spv::ImageFormat::Rgba32f; + } + UNREACHABLE_MSG("Unknown storage format data_format={}, num_format={}", image.GetDataFmt(), + image.GetNumberFmt()); } Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { @@ -412,8 +425,6 @@ Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, false, false, false, sampled, format); case AmdGpu::ImageType::Cube: return ctx.TypeImage(sampled_type, spv::Dim::Cube, false, false, false, sampled, format); - case AmdGpu::ImageType::Buffer: - throw NotImplementedException("Image buffer"); default: break; } @@ -471,10 +482,14 @@ void EmitContext::DefineImagesAndSamplers(const Info& info) { } } -void EmitContext::DefineSharedMemory(const Info& info) { - if (info.shared_memory_size == 0) { +void EmitContext::DefineSharedMemory() { + static constexpr size_t DefaultSharedMemSize = 16_KB; + if (!info.uses_shared) { return; } + if (info.shared_memory_size == 0) { + info.shared_memory_size = DefaultSharedMemSize; + } const auto make{[&](Id element_type, u32 element_size) { const u32 num_elements{Common::DivCeil(info.shared_memory_size, element_size)}; const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index fc6783442..34c13d3f9 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -180,6 +180,7 @@ public: Id workgroup_id{}; Id local_invocation_id{}; + Id subgroup_id{}; Id subgroup_local_invocation_id{}; Id image_u32{}; @@ -219,6 +220,7 @@ public: Id pointer_type; Id component_type; u32 num_components; + bool is_default{}; s32 buffer_handle{-1}; }; std::array input_params{}; @@ -231,7 +233,7 @@ private: void DefineOutputs(const Info& info); void DefineBuffers(const Info& info); void DefineImagesAndSamplers(const Info& info); - void DefineSharedMemory(const Info& info); + void DefineSharedMemory(); SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id); }; diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 634566fa8..8df3ac364 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -1479,7 +1479,7 @@ constexpr std::array InstructionFormatVOP3 = {{ {InstClass::VectorFpGraph32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, ScalarType::Float32}, // 337 = V_MIN3_F32 - {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, + {InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, ScalarType::Float32}, // 338 = V_MIN3_I32 {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Sint32, @@ -1488,7 +1488,7 @@ constexpr std::array InstructionFormatVOP3 = {{ {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 340 = V_MAX3_F32 - {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, + {InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, ScalarType::Float32}, // 341 = V_MAX3_I32 {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Sint32, @@ -1497,7 +1497,7 @@ constexpr std::array InstructionFormatVOP3 = {{ {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 343 = V_MED3_F32 - {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, + {InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32, ScalarType::Float32}, // 344 = V_MED3_I32 {InstClass::VectorIntArith32, InstCategory::VectorALU, 3, 1, ScalarType::Sint32, @@ -2779,11 +2779,9 @@ constexpr std::array InstructionFormatDS = {{ // 60 = DS_READ_U16 {InstClass::DsIdxRd, InstCategory::DataShare, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 61 = DS_CONSUME - {InstClass::DsAppendCon, InstCategory::DataShare, 3, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::DsAppendCon, InstCategory::DataShare, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 62 = DS_APPEND - {InstClass::DsAppendCon, InstCategory::DataShare, 3, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::DsAppendCon, InstCategory::DataShare, 3, 1, ScalarType::Uint32, ScalarType::Uint32}, // 63 = DS_ORDERED_COUNT {InstClass::GdsOrdCnt, InstCategory::DataShare, 3, 1, ScalarType::Undefined, ScalarType::Undefined}, diff --git a/src/shader_recompiler/frontend/instruction.h b/src/shader_recompiler/frontend/instruction.h index d1d10efb6..f83f43db5 100644 --- a/src/shader_recompiler/frontend/instruction.h +++ b/src/shader_recompiler/frontend/instruction.h @@ -76,11 +76,11 @@ struct SMRD { }; struct InstControlSOPK { - BitField<0, 16, u32> simm; + s16 simm; }; struct InstControlSOPP { - BitField<0, 16, u32> simm; + s16 simm; }; struct InstControlVOP3 { diff --git a/src/shader_recompiler/frontend/structured_control_flow.cpp b/src/shader_recompiler/frontend/structured_control_flow.cpp index 346f00aa4..c8d738580 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/structured_control_flow.cpp @@ -600,13 +600,13 @@ public: TranslatePass(ObjectPool& inst_pool_, ObjectPool& block_pool_, ObjectPool& stmt_pool_, Statement& root_stmt, IR::AbstractSyntaxList& syntax_list_, std::span inst_list_, - Info& info_) + Info& info_, const Profile& profile_) : stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, - syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_} { + syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_}, profile{profile_} { Visit(root_stmt, nullptr, nullptr); IR::Block& first_block{*syntax_list.front().data.block}; - Translator{&first_block, info}.EmitPrologue(); + Translator{&first_block, info, profile}.EmitPrologue(); } private: @@ -635,7 +635,7 @@ private: const u32 start = stmt.block->begin_index; const u32 size = stmt.block->end_index - start + 1; Translate(current_block, stmt.block->begin, inst_list.subspan(start, size), - info); + info, profile); } break; } @@ -815,16 +815,18 @@ private: const Block dummy_flow_block{.is_dummy = true}; std::span inst_list; Info& info; + const Profile& profile; }; } // Anonymous namespace IR::AbstractSyntaxList BuildASL(ObjectPool& inst_pool, ObjectPool& block_pool, - CFG& cfg, Info& info) { + CFG& cfg, Info& info, const Profile& profile) { ObjectPool stmt_pool{64}; GotoPass goto_pass{cfg, stmt_pool}; Statement& root{goto_pass.RootStatement()}; IR::AbstractSyntaxList syntax_list; - TranslatePass{inst_pool, block_pool, stmt_pool, root, syntax_list, cfg.inst_list, info}; + TranslatePass{inst_pool, block_pool, stmt_pool, root, + syntax_list, cfg.inst_list, info, profile}; ASSERT_MSG(!info.translation_failed, "Shader translation has failed"); return syntax_list; } diff --git a/src/shader_recompiler/frontend/structured_control_flow.h b/src/shader_recompiler/frontend/structured_control_flow.h index 09814349c..da4ef1ff0 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.h +++ b/src/shader_recompiler/frontend/structured_control_flow.h @@ -11,12 +11,13 @@ namespace Shader { struct Info; -} +struct Profile; +} // namespace Shader namespace Shader::Gcn { [[nodiscard]] IR::AbstractSyntaxList BuildASL(ObjectPool& inst_pool, ObjectPool& block_pool, CFG& cfg, - Info& info); + Info& info, const Profile& profile); } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index c5d9f0ecd..148371660 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -5,6 +5,31 @@ namespace Shader::Gcn { +void Translator::EmitDataShare(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::DS_SWIZZLE_B32: + return DS_SWIZZLE_B32(inst); + case Opcode::DS_READ_B32: + return DS_READ(32, false, false, inst); + case Opcode::DS_READ_B64: + return DS_READ(64, false, false, inst); + case Opcode::DS_READ2_B32: + return DS_READ(32, false, true, inst); + case Opcode::DS_READ2_B64: + return DS_READ(64, false, true, inst); + case Opcode::DS_WRITE_B32: + return DS_WRITE(32, false, false, inst); + case Opcode::DS_WRITE_B64: + return DS_WRITE(64, false, false, inst); + case Opcode::DS_WRITE2_B32: + return DS_WRITE(32, false, true, inst); + case Opcode::DS_WRITE2_B64: + return DS_WRITE(64, false, true, inst); + default: + LogMissingOpcode(inst); + } +} + void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { const u8 offset0 = inst.control.ds.offset0; const u8 offset1 = inst.control.ds.offset1; @@ -20,14 +45,25 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst) { const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; + IR::VectorReg dst_reg{inst.dst[0].code}; if (is_pair) { - // Pair loads are either 32 or 64-bit. We assume 32-bit for now. - ASSERT(bit_size == 32); + // Pair loads are either 32 or 64-bit const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - ir.SetVectorReg(dst_reg, IR::U32{ir.LoadShared(32, is_signed, addr0)}); + const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data0}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); + } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1))); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.LoadShared(32, is_signed, addr1)}); + const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data1}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); + } } else if (bit_size == 64) { const IR::Value data = ir.LoadShared(bit_size, is_signed, addr); ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); @@ -43,11 +79,22 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnI const IR::VectorReg data0{inst.src[1].code}; const IR::VectorReg data1{inst.src[2].code}; if (is_pair) { - ASSERT(bit_size == 32); const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), + addr0); + } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1))); - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), + addr1); + } } else if (bit_size == 64) { const IR::Value data = ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); @@ -62,7 +109,18 @@ void Translator::S_BARRIER() { } void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { - UNREACHABLE(); + ASSERT(info.stage != Stage::Compute); + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::V_READLANE_B32(const GcnInst& inst) { + ASSERT(info.stage != Stage::Compute); + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::V_WRITELANE_B32(const GcnInst& inst) { + ASSERT(info.stage != Stage::Compute); + SetDst(inst.dst[0], GetSrc(inst.src[0])); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index 518405373..889de21b7 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -6,7 +6,7 @@ namespace Shader::Gcn { -void Translator::EXP(const GcnInst& inst) { +void Translator::EmitExport(const GcnInst& inst) { if (ir.block->has_multiple_predecessors && info.stage == Stage::Fragment) { LOG_WARNING(Render_Recompiler, "An ambiguous export appeared in translation"); ir.Discard(ir.LogicalNot(ir.GetExec())); diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index a20e91ca1..795b148d6 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -5,8 +5,102 @@ namespace Shader::Gcn { +void Translator::EmitScalarAlu(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::S_MOVK_I32: + return S_MOVK(inst); + case Opcode::S_MOV_B32: + return S_MOV(inst); + case Opcode::S_MUL_I32: + return S_MUL_I32(inst); + case Opcode::S_AND_SAVEEXEC_B64: + return S_AND_SAVEEXEC_B64(inst); + case Opcode::S_MOV_B64: + return S_MOV_B64(inst); + case Opcode::S_CMP_LT_U32: + return S_CMP(ConditionOp::LT, false, inst); + case Opcode::S_CMP_LE_U32: + return S_CMP(ConditionOp::LE, false, inst); + case Opcode::S_CMP_LG_U32: + return S_CMP(ConditionOp::LG, false, inst); + case Opcode::S_CMP_LT_I32: + return S_CMP(ConditionOp::LT, true, inst); + case Opcode::S_CMP_LG_I32: + return S_CMP(ConditionOp::LG, true, inst); + case Opcode::S_CMP_GT_I32: + return S_CMP(ConditionOp::GT, true, inst); + case Opcode::S_CMP_GE_I32: + return S_CMP(ConditionOp::GE, true, inst); + case Opcode::S_CMP_EQ_I32: + return S_CMP(ConditionOp::EQ, true, inst); + case Opcode::S_CMP_EQ_U32: + return S_CMP(ConditionOp::EQ, false, inst); + case Opcode::S_CMP_GE_U32: + return S_CMP(ConditionOp::GE, false, inst); + case Opcode::S_CMP_GT_U32: + return S_CMP(ConditionOp::GT, false, inst); + case Opcode::S_OR_B64: + return S_OR_B64(NegateMode::None, false, inst); + case Opcode::S_NOR_B64: + return S_OR_B64(NegateMode::Result, false, inst); + case Opcode::S_XOR_B64: + return S_OR_B64(NegateMode::None, true, inst); + case Opcode::S_ORN2_B64: + return S_OR_B64(NegateMode::Src1, false, inst); + case Opcode::S_AND_B64: + return S_AND_B64(NegateMode::None, inst); + case Opcode::S_NAND_B64: + return S_AND_B64(NegateMode::Result, inst); + case Opcode::S_ANDN2_B64: + return S_AND_B64(NegateMode::Src1, inst); + case Opcode::S_NOT_B64: + return S_NOT_B64(inst); + case Opcode::S_ADD_I32: + return S_ADD_I32(inst); + case Opcode::S_AND_B32: + return S_AND_B32(inst); + case Opcode::S_ASHR_I32: + return S_ASHR_I32(inst); + case Opcode::S_OR_B32: + return S_OR_B32(inst); + case Opcode::S_LSHL_B32: + return S_LSHL_B32(inst); + case Opcode::S_LSHR_B32: + return S_LSHR_B32(inst); + case Opcode::S_CSELECT_B32: + return S_CSELECT_B32(inst); + case Opcode::S_CSELECT_B64: + return S_CSELECT_B64(inst); + case Opcode::S_BFE_U32: + return S_BFE_U32(inst); + case Opcode::S_BFM_B32: + return S_BFM_B32(inst); + case Opcode::S_BREV_B32: + return S_BREV_B32(inst); + case Opcode::S_ADD_U32: + return S_ADD_U32(inst); + case Opcode::S_ADDC_U32: + return S_ADDC_U32(inst); + case Opcode::S_ADDK_I32: + return S_ADDK_I32(inst); + case Opcode::S_MULK_I32: + return S_MULK_I32(inst); + case Opcode::S_SUB_U32: + case Opcode::S_SUB_I32: + return S_SUB_U32(inst); + case Opcode::S_MIN_U32: + return S_MIN_U32(inst); + case Opcode::S_MAX_U32: + return S_MAX_U32(inst); + case Opcode::S_WQM_B64: + break; + default: + LogMissingOpcode(inst); + } +} + void Translator::S_MOVK(const GcnInst& inst) { - const auto simm16 = inst.control.sopk.simm.Value(); + const auto simm16 = inst.control.sopk.simm; if (simm16 & (1 << 15)) { // TODO: need to verify the case of imm sign extension UNREACHABLE(); @@ -14,6 +108,16 @@ void Translator::S_MOVK(const GcnInst& inst) { SetDst(inst.dst[0], ir.Imm32(simm16)); } +void Translator::S_ADDK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +void Translator::S_MULK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + void Translator::S_MOV(const GcnInst& inst) { SetDst(inst.dst[0], GetSrc(inst.src[0])); } @@ -62,15 +166,10 @@ void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { } }(); - // Mark destination SPGR as an EXEC context. This means we will use 1-bit - // IR instruction whenever it's loaded. switch (inst.dst[0].field) { - case OperandField::ScalarGPR: { - const u32 reg = inst.dst[0].code; - exec_contexts[reg] = true; - ir.SetThreadBitScalarReg(IR::ScalarReg(reg), exec); + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); break; - } case OperandField::VccLo: ir.SetVcc(exec); break; @@ -79,27 +178,37 @@ void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { } // Update EXEC. - ir.SetExec(ir.LogicalAnd(exec, src)); + const IR::U1 result = ir.LogicalAnd(exec, src); + ir.SetExec(result); + ir.SetScc(result); } void Translator::S_MOV_B64(const GcnInst& inst) { - // TODO: Using VCC as EXEC context. - if (inst.src[0].field == OperandField::VccLo || inst.dst[0].field == OperandField::VccLo) { - return; - } - if (inst.dst[0].field == OperandField::ScalarGPR && inst.src[0].field == OperandField::ExecLo) { - // Exec context push - exec_contexts[inst.dst[0].code] = true; - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), ir.GetExec()); - } else if (inst.dst[0].field == OperandField::ExecLo && - inst.src[0].field == OperandField::ScalarGPR) { - // Exec context pop - exec_contexts[inst.src[0].code] = false; - ir.SetExec(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); - } else if (inst.dst[0].field == OperandField::ExecLo && - inst.src[0].field == OperandField::ConstZero) { - ir.SetExec(ir.Imm1(false)); - } else { + const IR::U1 src = [&] { + switch (inst.src[0].field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + case OperandField::ConstZero: + return ir.Imm1(false); + default: + UNREACHABLE(); + } + }(); + switch (inst.dst[0].field) { + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); + break; + case OperandField::ExecLo: + ir.SetExec(src); + break; + case OperandField::VccLo: + ir.SetVcc(src); + break; + default: UNREACHABLE(); } } @@ -338,4 +447,20 @@ void Translator::S_ADDC_U32(const GcnInst& inst) { SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), ir.GetSccLo())); } +void Translator::S_MAX_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.UMax(src0, src1); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); +} + +void Translator::S_MIN_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.UMin(src0, src1); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 3c80764c4..29f2acc27 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -7,6 +7,29 @@ namespace Shader::Gcn { static constexpr u32 SQ_SRC_LITERAL = 0xFF; +void Translator::EmitScalarMemory(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::S_LOAD_DWORDX4: + return S_LOAD_DWORD(4, inst); + case Opcode::S_LOAD_DWORDX8: + return S_LOAD_DWORD(8, inst); + case Opcode::S_LOAD_DWORDX16: + return S_LOAD_DWORD(16, inst); + case Opcode::S_BUFFER_LOAD_DWORD: + return S_BUFFER_LOAD_DWORD(1, inst); + case Opcode::S_BUFFER_LOAD_DWORDX2: + return S_BUFFER_LOAD_DWORD(2, inst); + case Opcode::S_BUFFER_LOAD_DWORDX4: + return S_BUFFER_LOAD_DWORD(4, inst); + case Opcode::S_BUFFER_LOAD_DWORDX8: + return S_BUFFER_LOAD_DWORD(8, inst); + case Opcode::S_BUFFER_LOAD_DWORDX16: + return S_BUFFER_LOAD_DWORD(16, inst); + default: + LogMissingOpcode(inst); + } +} + void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { const auto& smrd = inst.control.smrd; const u32 dword_offset = [&] -> u32 { diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index c4c6e5052..e8c2a31c9 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -16,13 +16,10 @@ namespace Shader::Gcn { -std::array Translator::exec_contexts{}; - -Translator::Translator(IR::Block* block_, Info& info_) - : ir{*block_, block_->begin()}, info{info_} {} +Translator::Translator(IR::Block* block_, Info& info_, const Profile& profile_) + : ir{*block_, block_->begin()}, info{info_}, profile{profile_} {} void Translator::EmitPrologue() { - exec_contexts.fill(false); ir.Prologue(); ir.SetExec(ir.Imm1(true)); @@ -97,7 +94,7 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { } break; case OperandField::ConstZero: - if (force_flt) { + if (is_float) { value = ir.Imm32(0.f); } else { value = ir.Imm32(0U); @@ -112,14 +109,14 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { value = ir.Imm32(-s32(operand.code) + SignedConstIntNegMin - 1); break; case OperandField::LiteralConst: - if (force_flt) { + if (is_float) { value = ir.Imm32(std::bit_cast(operand.code)); } else { value = ir.Imm32(operand.code); } break; case OperandField::ConstFloatPos_1_0: - if (force_flt) { + if (is_float) { value = ir.Imm32(1.f); } else { value = ir.Imm32(std::bit_cast(1.f)); @@ -138,7 +135,11 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { value = ir.Imm32(-0.5f); break; case OperandField::ConstFloatNeg_1_0: - value = ir.Imm32(-1.0f); + if (is_float) { + value = ir.Imm32(-1.0f); + } else { + value = ir.Imm32(std::bit_cast(-1.0f)); + } break; case OperandField::ConstFloatNeg_2_0: value = ir.Imm32(-2.0f); @@ -160,6 +161,8 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { value = ir.GetVccHi(); } break; + case OperandField::M0: + return m0_value; default: UNREACHABLE(); } @@ -336,6 +339,7 @@ void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { case OperandField::VccHi: return ir.SetVccHi(result); case OperandField::M0: + m0_value = result; break; default: UNREACHABLE(); @@ -458,712 +462,84 @@ void Translator::EmitFetch(const GcnInst& inst) { } } -void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info) { +void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::S_BARRIER: + return S_BARRIER(); + case Opcode::S_TTRACEDATA: + LOG_WARNING(Render_Vulkan, "S_TTRACEDATA instruction!"); + return; + case Opcode::S_GETPC_B64: + return S_GETPC_B64(pc, inst); + case Opcode::S_WAITCNT: + case Opcode::S_NOP: + case Opcode::S_ENDPGM: + case Opcode::S_CBRANCH_EXECZ: + case Opcode::S_CBRANCH_SCC0: + case Opcode::S_CBRANCH_SCC1: + case Opcode::S_CBRANCH_VCCNZ: + case Opcode::S_CBRANCH_VCCZ: + case Opcode::S_BRANCH: + return; + default: + UNREACHABLE(); + } +} + +void Translator::LogMissingOpcode(const GcnInst& inst) { + const u32 opcode = u32(inst.opcode); + LOG_ERROR(Render_Recompiler, "Unknown opcode {} ({}, category = {})", + magic_enum::enum_name(inst.opcode), u32(inst.opcode), + magic_enum::enum_name(inst.category)); + info.translation_failed = true; +} + +void Translate(IR::Block* block, u32 pc, std::span inst_list, Info& info, + const Profile& profile) { if (inst_list.empty()) { return; } - Translator translator{block, info}; + Translator translator{block, info, profile}; for (const auto& inst : inst_list) { - block_base += inst.length; - switch (inst.opcode) { - case Opcode::S_MOVK_I32: - translator.S_MOVK(inst); - break; - case Opcode::S_MOV_B32: - translator.S_MOV(inst); - break; - case Opcode::S_MUL_I32: - translator.S_MUL_I32(inst); - break; - case Opcode::V_MAD_F32: - translator.V_MAD_F32(inst); - break; - case Opcode::V_MOV_B32: - translator.V_MOV(inst); - break; - case Opcode::V_MAC_F32: - translator.V_MAC_F32(inst); - break; - case Opcode::V_MUL_F32: - translator.V_MUL_F32(inst); - break; - case Opcode::V_AND_B32: - translator.V_AND_B32(inst); - break; - case Opcode::V_OR_B32: - translator.V_OR_B32(false, inst); - break; - case Opcode::V_XOR_B32: - translator.V_OR_B32(true, inst); - break; - case Opcode::V_LSHLREV_B32: - translator.V_LSHLREV_B32(inst); - break; - case Opcode::V_ADD_I32: - translator.V_ADD_I32(inst); - break; - case Opcode::V_ADDC_U32: - translator.V_ADDC_U32(inst); - break; - case Opcode::V_CVT_F32_I32: - translator.V_CVT_F32_I32(inst); - break; - case Opcode::V_CVT_F32_U32: - translator.V_CVT_F32_U32(inst); - break; - case Opcode::V_RCP_F32: - translator.V_RCP_F32(inst); - break; - case Opcode::S_SWAPPC_B64: + pc += inst.length; + + // Special case for emitting fetch shader. + if (inst.opcode == Opcode::S_SWAPPC_B64) { ASSERT(info.stage == Stage::Vertex); translator.EmitFetch(inst); - break; - case Opcode::S_WAITCNT: - break; - case Opcode::S_LOAD_DWORDX4: - translator.S_LOAD_DWORD(4, inst); - break; - case Opcode::S_LOAD_DWORDX8: - translator.S_LOAD_DWORD(8, inst); - break; - case Opcode::S_LOAD_DWORDX16: - translator.S_LOAD_DWORD(16, inst); - break; - case Opcode::S_BUFFER_LOAD_DWORD: - translator.S_BUFFER_LOAD_DWORD(1, inst); - break; - case Opcode::S_BUFFER_LOAD_DWORDX2: - translator.S_BUFFER_LOAD_DWORD(2, inst); - break; - case Opcode::S_BUFFER_LOAD_DWORDX4: - translator.S_BUFFER_LOAD_DWORD(4, inst); - break; - case Opcode::S_BUFFER_LOAD_DWORDX8: - translator.S_BUFFER_LOAD_DWORD(8, inst); - break; - case Opcode::S_BUFFER_LOAD_DWORDX16: - translator.S_BUFFER_LOAD_DWORD(16, inst); - break; - case Opcode::EXP: - translator.EXP(inst); - break; - case Opcode::V_INTERP_P2_F32: - translator.V_INTERP_P2_F32(inst); - break; - case Opcode::V_CVT_PKRTZ_F16_F32: - translator.V_CVT_PKRTZ_F16_F32(inst); - break; - case Opcode::V_CVT_F32_F16: - translator.V_CVT_F32_F16(inst); - break; - case Opcode::V_CVT_F32_UBYTE0: - translator.V_CVT_F32_UBYTE(0, inst); - break; - case Opcode::V_CVT_F32_UBYTE1: - translator.V_CVT_F32_UBYTE(1, inst); - break; - case Opcode::V_CVT_F32_UBYTE2: - translator.V_CVT_F32_UBYTE(2, inst); - break; - case Opcode::V_CVT_F32_UBYTE3: - translator.V_CVT_F32_UBYTE(3, inst); - break; - case Opcode::V_BFREV_B32: - translator.V_BFREV_B32(inst); - break; - case Opcode::V_LDEXP_F32: - translator.V_LDEXP_F32(inst); - break; - case Opcode::V_FRACT_F32: - translator.V_FRACT_F32(inst); - break; - case Opcode::V_ADD_F32: - translator.V_ADD_F32(inst); - break; - case Opcode::V_CVT_OFF_F32_I4: - translator.V_CVT_OFF_F32_I4(inst); - break; - case Opcode::V_MED3_F32: - translator.V_MED3_F32(inst); - break; - case Opcode::V_FLOOR_F32: - translator.V_FLOOR_F32(inst); - break; - case Opcode::V_SUB_F32: - translator.V_SUB_F32(inst); - break; - case Opcode::V_FMA_F32: - case Opcode::V_MADAK_F32: // Yes these can share the opcode - translator.V_FMA_F32(inst); - break; - case Opcode::IMAGE_SAMPLE_LZ_O: - case Opcode::IMAGE_SAMPLE_O: - case Opcode::IMAGE_SAMPLE_C: - case Opcode::IMAGE_SAMPLE_C_LZ: - case Opcode::IMAGE_SAMPLE_LZ: - case Opcode::IMAGE_SAMPLE: - case Opcode::IMAGE_SAMPLE_L: - case Opcode::IMAGE_SAMPLE_C_O: - case Opcode::IMAGE_SAMPLE_B: - case Opcode::IMAGE_SAMPLE_C_LZ_O: - translator.IMAGE_SAMPLE(inst); - break; - case Opcode::IMAGE_ATOMIC_ADD: - translator.IMAGE_ATOMIC(AtomicOp::Add, inst); - break; - case Opcode::IMAGE_ATOMIC_AND: - translator.IMAGE_ATOMIC(AtomicOp::And, inst); - break; - case Opcode::IMAGE_ATOMIC_OR: - translator.IMAGE_ATOMIC(AtomicOp::Or, inst); - break; - case Opcode::IMAGE_ATOMIC_XOR: - translator.IMAGE_ATOMIC(AtomicOp::Xor, inst); - break; - case Opcode::IMAGE_ATOMIC_UMAX: - translator.IMAGE_ATOMIC(AtomicOp::Umax, inst); - break; - case Opcode::IMAGE_ATOMIC_SMAX: - translator.IMAGE_ATOMIC(AtomicOp::Smax, inst); - break; - case Opcode::IMAGE_ATOMIC_UMIN: - translator.IMAGE_ATOMIC(AtomicOp::Umin, inst); - break; - case Opcode::IMAGE_ATOMIC_SMIN: - translator.IMAGE_ATOMIC(AtomicOp::Smin, inst); - break; - case Opcode::IMAGE_ATOMIC_INC: - translator.IMAGE_ATOMIC(AtomicOp::Inc, inst); - break; - case Opcode::IMAGE_ATOMIC_DEC: - translator.IMAGE_ATOMIC(AtomicOp::Dec, inst); - break; - case Opcode::IMAGE_GET_LOD: - translator.IMAGE_GET_LOD(inst); - break; - case Opcode::IMAGE_GATHER4_C: - case Opcode::IMAGE_GATHER4_LZ: - case Opcode::IMAGE_GATHER4_LZ_O: - translator.IMAGE_GATHER(inst); - break; - case Opcode::IMAGE_STORE: - translator.IMAGE_STORE(inst); - break; - case Opcode::IMAGE_LOAD_MIP: - translator.IMAGE_LOAD(true, inst); - break; - case Opcode::IMAGE_LOAD: - translator.IMAGE_LOAD(false, inst); - break; - case Opcode::V_MAD_U64_U32: - translator.V_MAD_U64_U32(inst); - break; - case Opcode::V_CMP_GE_I32: - translator.V_CMP_U32(ConditionOp::GE, true, false, inst); - break; - case Opcode::V_CMP_EQ_I32: - translator.V_CMP_U32(ConditionOp::EQ, true, false, inst); - break; - case Opcode::V_CMP_LE_I32: - translator.V_CMP_U32(ConditionOp::LE, true, false, inst); - break; - case Opcode::V_CMP_NE_I32: - translator.V_CMP_U32(ConditionOp::LG, true, false, inst); - break; - case Opcode::V_CMP_NE_U32: - translator.V_CMP_U32(ConditionOp::LG, false, false, inst); - break; - case Opcode::V_CMP_EQ_U32: - translator.V_CMP_U32(ConditionOp::EQ, false, false, inst); - break; - case Opcode::V_CMP_F_U32: - translator.V_CMP_U32(ConditionOp::F, false, false, inst); - break; - case Opcode::V_CMP_LT_U32: - translator.V_CMP_U32(ConditionOp::LT, false, false, inst); - break; - case Opcode::V_CMP_GT_U32: - translator.V_CMP_U32(ConditionOp::GT, false, false, inst); - break; - case Opcode::V_CMP_GE_U32: - translator.V_CMP_U32(ConditionOp::GE, false, false, inst); - break; - case Opcode::V_CMP_TRU_U32: - translator.V_CMP_U32(ConditionOp::TRU, false, false, inst); - break; - case Opcode::V_CMP_NEQ_F32: - translator.V_CMP_F32(ConditionOp::LG, false, inst); - break; - case Opcode::V_CMP_F_F32: - translator.V_CMP_F32(ConditionOp::F, false, inst); - break; - case Opcode::V_CMP_LT_F32: - translator.V_CMP_F32(ConditionOp::LT, false, inst); - break; - case Opcode::V_CMP_EQ_F32: - translator.V_CMP_F32(ConditionOp::EQ, false, inst); - break; - case Opcode::V_CMP_LE_F32: - translator.V_CMP_F32(ConditionOp::LE, false, inst); - break; - case Opcode::V_CMP_GT_F32: - translator.V_CMP_F32(ConditionOp::GT, false, inst); - break; - case Opcode::V_CMP_LG_F32: - translator.V_CMP_F32(ConditionOp::LG, false, inst); - break; - case Opcode::V_CMP_GE_F32: - translator.V_CMP_F32(ConditionOp::GE, false, inst); - break; - case Opcode::V_CMP_NLE_F32: - translator.V_CMP_F32(ConditionOp::GT, false, inst); - break; - case Opcode::V_CMP_NLT_F32: - translator.V_CMP_F32(ConditionOp::GE, false, inst); - break; - case Opcode::V_CMP_NGT_F32: - translator.V_CMP_F32(ConditionOp::LE, false, inst); - break; - case Opcode::V_CMP_NGE_F32: - translator.V_CMP_F32(ConditionOp::LT, false, inst); - break; - case Opcode::S_CMP_LT_U32: - translator.S_CMP(ConditionOp::LT, false, inst); - break; - case Opcode::S_CMP_LE_U32: - translator.S_CMP(ConditionOp::LE, false, inst); - break; - case Opcode::S_CMP_LG_U32: - translator.S_CMP(ConditionOp::LG, false, inst); - break; - case Opcode::S_CMP_LT_I32: - translator.S_CMP(ConditionOp::LT, true, inst); - break; - case Opcode::S_CMP_LG_I32: - translator.S_CMP(ConditionOp::LG, true, inst); - break; - case Opcode::S_CMP_GT_I32: - translator.S_CMP(ConditionOp::GT, true, inst); - break; - case Opcode::S_CMP_GE_I32: - translator.S_CMP(ConditionOp::GE, true, inst); - break; - case Opcode::S_CMP_EQ_I32: - translator.S_CMP(ConditionOp::EQ, true, inst); - break; - case Opcode::S_CMP_EQ_U32: - translator.S_CMP(ConditionOp::EQ, false, inst); - break; - case Opcode::S_LSHL_B32: - translator.S_LSHL_B32(inst); - break; - case Opcode::V_CNDMASK_B32: - translator.V_CNDMASK_B32(inst); - break; - case Opcode::TBUFFER_LOAD_FORMAT_X: - translator.BUFFER_LOAD_FORMAT(1, true, true, inst); - break; - case Opcode::TBUFFER_LOAD_FORMAT_XY: - translator.BUFFER_LOAD_FORMAT(2, true, true, inst); - break; - case Opcode::TBUFFER_LOAD_FORMAT_XYZ: - translator.BUFFER_LOAD_FORMAT(3, true, true, inst); - break; - case Opcode::TBUFFER_LOAD_FORMAT_XYZW: - translator.BUFFER_LOAD_FORMAT(4, true, true, inst); - break; - case Opcode::BUFFER_LOAD_FORMAT_X: - translator.BUFFER_LOAD_FORMAT(1, false, true, inst); - break; - case Opcode::BUFFER_LOAD_FORMAT_XY: - translator.BUFFER_LOAD_FORMAT(2, false, true, inst); - break; - case Opcode::BUFFER_LOAD_FORMAT_XYZ: - translator.BUFFER_LOAD_FORMAT(3, false, true, inst); - break; - case Opcode::BUFFER_LOAD_FORMAT_XYZW: - translator.BUFFER_LOAD_FORMAT(4, false, true, inst); - break; - case Opcode::BUFFER_LOAD_DWORD: - translator.BUFFER_LOAD_FORMAT(1, false, false, inst); - break; - case Opcode::BUFFER_LOAD_DWORDX2: - translator.BUFFER_LOAD_FORMAT(2, false, false, inst); - break; - case Opcode::BUFFER_LOAD_DWORDX3: - translator.BUFFER_LOAD_FORMAT(3, false, false, inst); - break; - case Opcode::BUFFER_LOAD_DWORDX4: - translator.BUFFER_LOAD_FORMAT(4, false, false, inst); - break; - case Opcode::BUFFER_STORE_FORMAT_X: - case Opcode::BUFFER_STORE_DWORD: - translator.BUFFER_STORE_FORMAT(1, false, inst); - break; - case Opcode::BUFFER_STORE_DWORDX2: - translator.BUFFER_STORE_FORMAT(2, false, inst); - break; - case Opcode::BUFFER_STORE_DWORDX3: - translator.BUFFER_STORE_FORMAT(3, false, inst); - break; - case Opcode::BUFFER_STORE_FORMAT_XYZW: - case Opcode::BUFFER_STORE_DWORDX4: - translator.BUFFER_STORE_FORMAT(4, false, inst); - break; - case Opcode::V_MAX_F32: - translator.V_MAX_F32(inst); - break; - case Opcode::V_MAX_I32: - translator.V_MAX_U32(true, inst); - break; - case Opcode::V_MAX_U32: - translator.V_MAX_U32(false, inst); - break; - case Opcode::V_NOT_B32: - translator.V_NOT_B32(inst); - break; - case Opcode::V_RSQ_F32: - translator.V_RSQ_F32(inst); - break; - case Opcode::S_ANDN2_B64: - translator.S_AND_B64(NegateMode::Src1, inst); - break; - case Opcode::S_ORN2_B64: - translator.S_OR_B64(NegateMode::Src1, false, inst); - break; - case Opcode::V_SIN_F32: - translator.V_SIN_F32(inst); - break; - case Opcode::V_COS_F32: - translator.V_COS_F32(inst); - break; - case Opcode::V_LOG_F32: - translator.V_LOG_F32(inst); - break; - case Opcode::V_EXP_F32: - translator.V_EXP_F32(inst); - break; - case Opcode::V_SQRT_F32: - translator.V_SQRT_F32(inst); - break; - case Opcode::V_MIN_F32: - translator.V_MIN_F32(inst); - break; - case Opcode::V_MIN_I32: - translator.V_MIN_I32(inst); - break; - case Opcode::V_MIN3_F32: - translator.V_MIN3_F32(inst); - break; - case Opcode::V_MIN_LEGACY_F32: - translator.V_MIN_F32(inst, true); - break; - case Opcode::V_MADMK_F32: - translator.V_MADMK_F32(inst); - break; - case Opcode::V_CUBEMA_F32: - translator.V_CUBEMA_F32(inst); - break; - case Opcode::V_CUBESC_F32: - translator.V_CUBESC_F32(inst); - break; - case Opcode::V_CUBETC_F32: - translator.V_CUBETC_F32(inst); - break; - case Opcode::V_CUBEID_F32: - translator.V_CUBEID_F32(inst); - break; - case Opcode::V_CVT_U32_F32: - translator.V_CVT_U32_F32(inst); - break; - case Opcode::V_CVT_I32_F32: - translator.V_CVT_I32_F32(inst); - break; - case Opcode::V_CVT_FLR_I32_F32: - translator.V_CVT_FLR_I32_F32(inst); - break; - case Opcode::V_SUBREV_F32: - translator.V_SUBREV_F32(inst); - break; - case Opcode::S_AND_SAVEEXEC_B64: - translator.S_AND_SAVEEXEC_B64(inst); - break; - case Opcode::S_MOV_B64: - translator.S_MOV_B64(inst); - break; - case Opcode::V_SUBREV_I32: - translator.V_SUBREV_I32(inst); - break; + continue; + } - case Opcode::V_CMPX_F_F32: - translator.V_CMP_F32(ConditionOp::F, true, inst); + // Emit instructions for each category. + switch (inst.category) { + case InstCategory::DataShare: + translator.EmitDataShare(inst); break; - case Opcode::V_CMPX_LT_F32: - translator.V_CMP_F32(ConditionOp::LT, true, inst); + case InstCategory::VectorInterpolation: + translator.EmitVectorInterpolation(inst); break; - case Opcode::V_CMPX_EQ_F32: - translator.V_CMP_F32(ConditionOp::EQ, true, inst); + case InstCategory::ScalarMemory: + translator.EmitScalarMemory(inst); break; - case Opcode::V_CMPX_LE_F32: - translator.V_CMP_F32(ConditionOp::LE, true, inst); + case InstCategory::VectorMemory: + translator.EmitVectorMemory(inst); break; - case Opcode::V_CMPX_GT_F32: - translator.V_CMP_F32(ConditionOp::GT, true, inst); + case InstCategory::Export: + translator.EmitExport(inst); break; - case Opcode::V_CMPX_LG_F32: - translator.V_CMP_F32(ConditionOp::LG, true, inst); + case InstCategory::FlowControl: + translator.EmitFlowControl(pc, inst); break; - case Opcode::V_CMPX_GE_F32: - translator.V_CMP_F32(ConditionOp::GE, true, inst); + case InstCategory::ScalarALU: + translator.EmitScalarAlu(inst); break; - case Opcode::V_CMPX_NGE_F32: - translator.V_CMP_F32(ConditionOp::LT, true, inst); + case InstCategory::VectorALU: + translator.EmitVectorAlu(inst); break; - case Opcode::V_CMPX_NLG_F32: - translator.V_CMP_F32(ConditionOp::EQ, true, inst); - break; - case Opcode::V_CMPX_NGT_F32: - translator.V_CMP_F32(ConditionOp::LE, true, inst); - break; - case Opcode::V_CMPX_NLE_F32: - translator.V_CMP_F32(ConditionOp::GT, true, inst); - break; - case Opcode::V_CMPX_NEQ_F32: - translator.V_CMP_F32(ConditionOp::LG, true, inst); - break; - case Opcode::V_CMPX_NLT_F32: - translator.V_CMP_F32(ConditionOp::GE, true, inst); - break; - case Opcode::V_CMPX_TRU_F32: - translator.V_CMP_F32(ConditionOp::TRU, true, inst); - break; - case Opcode::V_CMP_LE_U32: - translator.V_CMP_U32(ConditionOp::LE, false, false, inst); - break; - case Opcode::V_CMP_GT_I32: - translator.V_CMP_U32(ConditionOp::GT, true, false, inst); - break; - case Opcode::V_CMP_LT_I32: - translator.V_CMP_U32(ConditionOp::LT, true, false, inst); - break; - case Opcode::V_CMPX_LT_I32: - translator.V_CMP_U32(ConditionOp::LT, true, true, inst); - break; - case Opcode::V_CMPX_F_U32: - translator.V_CMP_U32(ConditionOp::F, false, true, inst); - break; - case Opcode::V_CMPX_LT_U32: - translator.V_CMP_U32(ConditionOp::LT, false, true, inst); - break; - case Opcode::V_CMPX_EQ_U32: - translator.V_CMP_U32(ConditionOp::EQ, false, true, inst); - break; - case Opcode::V_CMPX_LE_U32: - translator.V_CMP_U32(ConditionOp::LE, false, true, inst); - break; - case Opcode::V_CMPX_GT_U32: - translator.V_CMP_U32(ConditionOp::GT, false, true, inst); - break; - case Opcode::V_CMPX_NE_U32: - translator.V_CMP_U32(ConditionOp::LG, false, true, inst); - break; - case Opcode::V_CMPX_GE_U32: - translator.V_CMP_U32(ConditionOp::GE, false, true, inst); - break; - case Opcode::V_CMPX_TRU_U32: - translator.V_CMP_U32(ConditionOp::TRU, false, true, inst); - break; - case Opcode::S_OR_B64: - translator.S_OR_B64(NegateMode::None, false, inst); - break; - case Opcode::S_NOR_B64: - translator.S_OR_B64(NegateMode::Result, false, inst); - break; - case Opcode::S_XOR_B64: - translator.S_OR_B64(NegateMode::None, true, inst); - break; - case Opcode::S_AND_B64: - translator.S_AND_B64(NegateMode::None, inst); - break; - case Opcode::S_NOT_B64: - translator.S_NOT_B64(inst); - break; - case Opcode::S_NAND_B64: - translator.S_AND_B64(NegateMode::Result, inst); - break; - case Opcode::V_LSHRREV_B32: - translator.V_LSHRREV_B32(inst); - break; - case Opcode::S_ADD_I32: - translator.S_ADD_I32(inst); - break; - case Opcode::V_MUL_HI_U32: - translator.V_MUL_HI_U32(false, inst); - break; - case Opcode::V_MUL_LO_I32: - translator.V_MUL_LO_U32(inst); - break; - case Opcode::V_SAD_U32: - translator.V_SAD_U32(inst); - break; - case Opcode::V_BFE_U32: - translator.V_BFE_U32(false, inst); - break; - case Opcode::V_BFE_I32: - translator.V_BFE_U32(true, inst); - break; - case Opcode::V_MAD_I32_I24: - translator.V_MAD_I32_I24(inst); - break; - case Opcode::V_MUL_I32_I24: - case Opcode::V_MUL_U32_U24: - translator.V_MUL_I32_I24(inst); - break; - case Opcode::V_SUB_I32: - translator.V_SUB_I32(inst); - break; - case Opcode::V_LSHR_B32: - translator.V_LSHR_B32(inst); - break; - case Opcode::V_ASHRREV_I32: - translator.V_ASHRREV_I32(inst); - break; - case Opcode::V_MAD_U32_U24: - translator.V_MAD_U32_U24(inst); - break; - case Opcode::S_AND_B32: - translator.S_AND_B32(inst); - break; - case Opcode::S_ASHR_I32: - translator.S_ASHR_I32(inst); - break; - case Opcode::S_OR_B32: - translator.S_OR_B32(inst); - break; - case Opcode::S_LSHR_B32: - translator.S_LSHR_B32(inst); - break; - case Opcode::S_CSELECT_B32: - translator.S_CSELECT_B32(inst); - break; - case Opcode::S_CSELECT_B64: - translator.S_CSELECT_B64(inst); - break; - case Opcode::S_BFE_U32: - translator.S_BFE_U32(inst); - break; - case Opcode::V_RNDNE_F32: - translator.V_RNDNE_F32(inst); - break; - case Opcode::V_BCNT_U32_B32: - translator.V_BCNT_U32_B32(inst); - break; - case Opcode::V_MAX3_F32: - translator.V_MAX3_F32(inst); - break; - case Opcode::DS_SWIZZLE_B32: - translator.DS_SWIZZLE_B32(inst); - break; - case Opcode::V_MUL_LO_U32: - translator.V_MUL_LO_U32(inst); - break; - case Opcode::S_BFM_B32: - translator.S_BFM_B32(inst); - break; - case Opcode::V_MIN_U32: - translator.V_MIN_U32(inst); - break; - case Opcode::V_CMP_NE_U64: - translator.V_CMP_NE_U64(inst); - break; - case Opcode::V_CMP_CLASS_F32: - translator.V_CMP_CLASS_F32(inst); - break; - case Opcode::V_TRUNC_F32: - translator.V_TRUNC_F32(inst); - break; - case Opcode::V_CEIL_F32: - translator.V_CEIL_F32(inst); - break; - case Opcode::V_BFI_B32: - translator.V_BFI_B32(inst); - break; - case Opcode::S_BREV_B32: - translator.S_BREV_B32(inst); - break; - case Opcode::S_ADD_U32: - translator.S_ADD_U32(inst); - break; - case Opcode::S_ADDC_U32: - translator.S_ADDC_U32(inst); - break; - case Opcode::S_SUB_U32: - case Opcode::S_SUB_I32: - translator.S_SUB_U32(inst); - break; - // TODO: Separate implementation for legacy variants. - case Opcode::V_MUL_LEGACY_F32: - translator.V_MUL_F32(inst); - break; - case Opcode::V_MAC_LEGACY_F32: - translator.V_MAC_F32(inst); - break; - case Opcode::V_MAD_LEGACY_F32: - translator.V_MAD_F32(inst); - break; - case Opcode::V_MAX_LEGACY_F32: - translator.V_MAX_F32(inst, true); - break; - case Opcode::V_RSQ_LEGACY_F32: - case Opcode::V_RSQ_CLAMP_F32: - translator.V_RSQ_F32(inst); - break; - case Opcode::V_RCP_IFLAG_F32: - translator.V_RCP_F32(inst); - break; - case Opcode::IMAGE_GET_RESINFO: - translator.IMAGE_GET_RESINFO(inst); - break; - case Opcode::S_BARRIER: - translator.S_BARRIER(); - break; - case Opcode::S_TTRACEDATA: - LOG_WARNING(Render_Vulkan, "S_TTRACEDATA instruction!"); - break; - case Opcode::DS_READ_B32: - translator.DS_READ(32, false, false, inst); - break; - case Opcode::DS_READ2_B32: - translator.DS_READ(32, false, true, inst); - break; - case Opcode::DS_WRITE_B32: - translator.DS_WRITE(32, false, false, inst); - break; - case Opcode::DS_WRITE2_B32: - translator.DS_WRITE(32, false, true, inst); - break; - case Opcode::V_READFIRSTLANE_B32: - translator.V_READFIRSTLANE_B32(inst); - break; - case Opcode::S_GETPC_B64: - translator.S_GETPC_B64(block_base, inst); - break; - case Opcode::S_NOP: - case Opcode::S_CBRANCH_EXECZ: - case Opcode::S_CBRANCH_SCC0: - case Opcode::S_CBRANCH_SCC1: - case Opcode::S_CBRANCH_VCCNZ: - case Opcode::S_CBRANCH_VCCZ: - case Opcode::S_BRANCH: - case Opcode::S_WQM_B64: - case Opcode::V_INTERP_P1_F32: - case Opcode::S_ENDPGM: + case InstCategory::DebugProfile: break; default: - const u32 opcode = u32(inst.opcode); - LOG_ERROR(Render_Recompiler, "Unknown opcode {} ({})", - magic_enum::enum_name(inst.opcode), opcode); - info.translation_failed = true; + UNREACHABLE(); } } } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 3203ad730..8d1b76833 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -11,7 +11,8 @@ namespace Shader { struct Info; -} +struct Profile; +} // namespace Shader namespace Shader::Gcn { @@ -24,6 +25,7 @@ enum class ConditionOp : u32 { LT, LE, TRU, + U, }; enum class AtomicOp : u32 { @@ -53,10 +55,19 @@ enum class NegateMode : u32 { class Translator { public: - explicit Translator(IR::Block* block_, Info& info); + explicit Translator(IR::Block* block_, Info& info, const Profile& profile); + // Instruction categories void EmitPrologue(); void EmitFetch(const GcnInst& inst); + void EmitDataShare(const GcnInst& inst); + void EmitVectorInterpolation(const GcnInst& inst); + void EmitScalarMemory(const GcnInst& inst); + void EmitVectorMemory(const GcnInst& inst); + void EmitExport(const GcnInst& inst); + void EmitFlowControl(u32 pc, const GcnInst& inst); + void EmitScalarAlu(const GcnInst& inst); + void EmitVectorAlu(const GcnInst& inst); // Scalar ALU void S_MOVK(const GcnInst& inst); @@ -83,6 +94,10 @@ public: void S_SUB_U32(const GcnInst& inst); void S_GETPC_B64(u32 pc, const GcnInst& inst); void S_ADDC_U32(const GcnInst& inst); + void S_MULK_I32(const GcnInst& inst); + void S_ADDK_I32(const GcnInst& inst); + void S_MAX_U32(const GcnInst& inst); + void S_MIN_U32(const GcnInst& inst); // Scalar Memory void S_LOAD_DWORD(int num_dwords, const GcnInst& inst); @@ -94,11 +109,13 @@ public: void V_MAC_F32(const GcnInst& inst); void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); void V_CVT_F32_F16(const GcnInst& inst); + void V_CVT_F16_F32(const GcnInst& inst); void V_MUL_F32(const GcnInst& inst); void V_CNDMASK_B32(const GcnInst& inst); void V_OR_B32(bool is_xor, const GcnInst& inst); void V_AND_B32(const GcnInst& inst); void V_LSHLREV_B32(const GcnInst& inst); + void V_LSHL_B32(const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); void V_ADDC_U32(const GcnInst& inst); void V_CVT_F32_I32(const GcnInst& inst); @@ -122,6 +139,7 @@ public: void V_SQRT_F32(const GcnInst& inst); void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); void V_MIN3_F32(const GcnInst& inst); + void V_MIN3_I32(const GcnInst& inst); void V_MADMK_F32(const GcnInst& inst); void V_CUBEMA_F32(const GcnInst& inst); void V_CUBESC_F32(const GcnInst& inst); @@ -146,6 +164,7 @@ public: void V_BCNT_U32_B32(const GcnInst& inst); void V_COS_F32(const GcnInst& inst); void V_MAX3_F32(const GcnInst& inst); + void V_MAX3_U32(const GcnInst& inst); void V_CVT_I32_F32(const GcnInst& inst); void V_MIN_I32(const GcnInst& inst); void V_MUL_LO_U32(const GcnInst& inst); @@ -160,6 +179,8 @@ public: void V_LDEXP_F32(const GcnInst& inst); void V_CVT_FLR_I32_F32(const GcnInst& inst); void V_CMP_CLASS_F32(const GcnInst& inst); + void V_FFBL_B32(const GcnInst& inst); + void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); // Vector Memory void BUFFER_LOAD_FORMAT(u32 num_dwords, bool is_typed, bool is_format, const GcnInst& inst); @@ -167,12 +188,15 @@ public: // Vector interpolation void V_INTERP_P2_F32(const GcnInst& inst); + void V_INTERP_MOV_F32(const GcnInst& inst); // Data share void DS_SWIZZLE_B32(const GcnInst& inst); void DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst); void DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst); void V_READFIRSTLANE_B32(const GcnInst& inst); + void V_READLANE_B32(const GcnInst& inst); + void V_WRITELANE_B32(const GcnInst& inst); void S_BARRIER(); // MIMG @@ -184,9 +208,6 @@ public: void IMAGE_GET_LOD(const GcnInst& inst); void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); - // Export - void EXP(const GcnInst& inst); - private: template [[nodiscard]] T GetSrc(const InstOperand& operand, bool flt_zero = false); @@ -195,12 +216,17 @@ private: void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); + void LogMissingOpcode(const GcnInst& inst); + private: IR::IREmitter ir; Info& info; - static std::array exec_contexts; + const Profile& profile; + IR::U32 m0_value; + bool opcode_missing = false; }; -void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info); +void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info, + const Profile& profile); } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 1b2024f89..669ef7ca1 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -2,9 +2,311 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "shader_recompiler/frontend/translate/translate.h" +#include "shader_recompiler/profile.h" namespace Shader::Gcn { +void Translator::EmitVectorAlu(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::V_LSHLREV_B32: + return V_LSHLREV_B32(inst); + case Opcode::V_LSHL_B32: + return V_LSHL_B32(inst); + case Opcode::V_BFREV_B32: + return V_BFREV_B32(inst); + case Opcode::V_BFE_U32: + return V_BFE_U32(false, inst); + case Opcode::V_BFE_I32: + return V_BFE_U32(true, inst); + case Opcode::V_BFI_B32: + return V_BFI_B32(inst); + case Opcode::V_LSHR_B32: + return V_LSHR_B32(inst); + case Opcode::V_ASHRREV_I32: + return V_ASHRREV_I32(inst); + case Opcode::V_LSHRREV_B32: + return V_LSHRREV_B32(inst); + case Opcode::V_NOT_B32: + return V_NOT_B32(inst); + case Opcode::V_AND_B32: + return V_AND_B32(inst); + case Opcode::V_OR_B32: + return V_OR_B32(false, inst); + case Opcode::V_XOR_B32: + return V_OR_B32(true, inst); + case Opcode::V_FFBL_B32: + return V_FFBL_B32(inst); + + case Opcode::V_MOV_B32: + return V_MOV(inst); + case Opcode::V_ADD_I32: + return V_ADD_I32(inst); + case Opcode::V_ADDC_U32: + return V_ADDC_U32(inst); + case Opcode::V_CVT_F32_I32: + return V_CVT_F32_I32(inst); + case Opcode::V_CVT_F32_U32: + return V_CVT_F32_U32(inst); + case Opcode::V_CVT_PKRTZ_F16_F32: + return V_CVT_PKRTZ_F16_F32(inst); + case Opcode::V_CVT_F32_F16: + return V_CVT_F32_F16(inst); + case Opcode::V_CVT_F16_F32: + return V_CVT_F16_F32(inst); + case Opcode::V_CVT_F32_UBYTE0: + return V_CVT_F32_UBYTE(0, inst); + case Opcode::V_CVT_F32_UBYTE1: + return V_CVT_F32_UBYTE(1, inst); + case Opcode::V_CVT_F32_UBYTE2: + return V_CVT_F32_UBYTE(2, inst); + case Opcode::V_CVT_F32_UBYTE3: + return V_CVT_F32_UBYTE(3, inst); + case Opcode::V_CVT_OFF_F32_I4: + return V_CVT_OFF_F32_I4(inst); + case Opcode::V_MAD_U64_U32: + return V_MAD_U64_U32(inst); + case Opcode::V_CMP_GE_I32: + return V_CMP_U32(ConditionOp::GE, true, false, inst); + case Opcode::V_CMP_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, false, inst); + case Opcode::V_CMP_LE_I32: + return V_CMP_U32(ConditionOp::LE, true, false, inst); + case Opcode::V_CMP_NE_I32: + return V_CMP_U32(ConditionOp::LG, true, false, inst); + case Opcode::V_CMP_NE_U32: + return V_CMP_U32(ConditionOp::LG, false, false, inst); + case Opcode::V_CMP_EQ_U32: + return V_CMP_U32(ConditionOp::EQ, false, false, inst); + case Opcode::V_CMP_F_U32: + return V_CMP_U32(ConditionOp::F, false, false, inst); + case Opcode::V_CMP_LT_U32: + return V_CMP_U32(ConditionOp::LT, false, false, inst); + case Opcode::V_CMP_GT_U32: + return V_CMP_U32(ConditionOp::GT, false, false, inst); + case Opcode::V_CMP_GE_U32: + return V_CMP_U32(ConditionOp::GE, false, false, inst); + case Opcode::V_CMP_TRU_U32: + return V_CMP_U32(ConditionOp::TRU, false, false, inst); + case Opcode::V_CMP_NEQ_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_CMP_F_F32: + return V_CMP_F32(ConditionOp::F, false, inst); + case Opcode::V_CMP_LT_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_EQ_F32: + return V_CMP_F32(ConditionOp::EQ, false, inst); + case Opcode::V_CMP_LE_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); + case Opcode::V_CMP_GT_F32: + return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_LG_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_CMP_GE_F32: + return V_CMP_F32(ConditionOp::GE, false, inst); + case Opcode::V_CMP_NLE_F32: + return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_NLT_F32: + return V_CMP_F32(ConditionOp::GE, false, inst); + case Opcode::V_CMP_NGT_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); + case Opcode::V_CMP_NGE_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_U_F32: + return V_CMP_F32(ConditionOp::U, false, inst); + case Opcode::V_CNDMASK_B32: + return V_CNDMASK_B32(inst); + case Opcode::V_MAX_I32: + return V_MAX_U32(true, inst); + case Opcode::V_MAX_U32: + return V_MAX_U32(false, inst); + case Opcode::V_MIN_I32: + return V_MIN_I32(inst); + case Opcode::V_CUBEMA_F32: + return V_CUBEMA_F32(inst); + case Opcode::V_CUBESC_F32: + return V_CUBESC_F32(inst); + case Opcode::V_CUBETC_F32: + return V_CUBETC_F32(inst); + case Opcode::V_CUBEID_F32: + return V_CUBEID_F32(inst); + case Opcode::V_CVT_U32_F32: + return V_CVT_U32_F32(inst); + case Opcode::V_CVT_I32_F32: + return V_CVT_I32_F32(inst); + case Opcode::V_CVT_FLR_I32_F32: + return V_CVT_FLR_I32_F32(inst); + case Opcode::V_SUBREV_I32: + return V_SUBREV_I32(inst); + case Opcode::V_MUL_HI_U32: + return V_MUL_HI_U32(false, inst); + case Opcode::V_MUL_LO_I32: + return V_MUL_LO_U32(inst); + case Opcode::V_SAD_U32: + return V_SAD_U32(inst); + case Opcode::V_SUB_I32: + return V_SUB_I32(inst); + case Opcode::V_MAD_I32_I24: + return V_MAD_I32_I24(inst); + case Opcode::V_MUL_I32_I24: + case Opcode::V_MUL_U32_U24: + return V_MUL_I32_I24(inst); + case Opcode::V_MAD_U32_U24: + return V_MAD_U32_U24(inst); + case Opcode::V_BCNT_U32_B32: + return V_BCNT_U32_B32(inst); + case Opcode::V_MUL_LO_U32: + return V_MUL_LO_U32(inst); + case Opcode::V_MIN_U32: + return V_MIN_U32(inst); + case Opcode::V_CMP_NE_U64: + return V_CMP_NE_U64(inst); + case Opcode::V_READFIRSTLANE_B32: + return V_READFIRSTLANE_B32(inst); + case Opcode::V_READLANE_B32: + return V_READLANE_B32(inst); + case Opcode::V_WRITELANE_B32: + return V_WRITELANE_B32(inst); + + case Opcode::V_MAD_F32: + return V_MAD_F32(inst); + case Opcode::V_MAC_F32: + return V_MAC_F32(inst); + case Opcode::V_MUL_F32: + return V_MUL_F32(inst); + case Opcode::V_RCP_F32: + return V_RCP_F32(inst); + case Opcode::V_LDEXP_F32: + return V_LDEXP_F32(inst); + case Opcode::V_FRACT_F32: + return V_FRACT_F32(inst); + case Opcode::V_ADD_F32: + return V_ADD_F32(inst); + case Opcode::V_MED3_F32: + return V_MED3_F32(inst); + case Opcode::V_FLOOR_F32: + return V_FLOOR_F32(inst); + case Opcode::V_SUB_F32: + return V_SUB_F32(inst); + case Opcode::V_FMA_F32: + case Opcode::V_MADAK_F32: + return V_FMA_F32(inst); + case Opcode::V_MAX_F32: + return V_MAX_F32(inst); + case Opcode::V_RSQ_F32: + return V_RSQ_F32(inst); + case Opcode::V_SIN_F32: + return V_SIN_F32(inst); + case Opcode::V_COS_F32: + return V_COS_F32(inst); + case Opcode::V_LOG_F32: + return V_LOG_F32(inst); + case Opcode::V_EXP_F32: + return V_EXP_F32(inst); + case Opcode::V_SQRT_F32: + return V_SQRT_F32(inst); + case Opcode::V_MIN_F32: + return V_MIN_F32(inst, false); + case Opcode::V_MIN3_F32: + return V_MIN3_F32(inst); + case Opcode::V_MIN3_I32: + return V_MIN3_I32(inst); + case Opcode::V_MIN_LEGACY_F32: + return V_MIN_F32(inst, true); + case Opcode::V_MADMK_F32: + return V_MADMK_F32(inst); + case Opcode::V_SUBREV_F32: + return V_SUBREV_F32(inst); + case Opcode::V_RNDNE_F32: + return V_RNDNE_F32(inst); + case Opcode::V_MAX3_F32: + return V_MAX3_F32(inst); + case Opcode::V_MAX3_U32: + return V_MAX3_U32(inst); + case Opcode::V_TRUNC_F32: + return V_TRUNC_F32(inst); + case Opcode::V_CEIL_F32: + return V_CEIL_F32(inst); + case Opcode::V_MUL_LEGACY_F32: + return V_MUL_F32(inst); + case Opcode::V_MAC_LEGACY_F32: + return V_MAC_F32(inst); + case Opcode::V_MAD_LEGACY_F32: + return V_MAD_F32(inst); + case Opcode::V_MAX_LEGACY_F32: + return V_MAX_F32(inst, true); + case Opcode::V_RSQ_LEGACY_F32: + case Opcode::V_RSQ_CLAMP_F32: + return V_RSQ_F32(inst); + case Opcode::V_RCP_IFLAG_F32: + return V_RCP_F32(inst); + + case Opcode::V_CMPX_F_F32: + return V_CMP_F32(ConditionOp::F, true, inst); + case Opcode::V_CMPX_LT_F32: + return V_CMP_F32(ConditionOp::LT, true, inst); + case Opcode::V_CMPX_EQ_F32: + return V_CMP_F32(ConditionOp::EQ, true, inst); + case Opcode::V_CMPX_LE_F32: + return V_CMP_F32(ConditionOp::LE, true, inst); + case Opcode::V_CMPX_GT_F32: + return V_CMP_F32(ConditionOp::GT, true, inst); + case Opcode::V_CMPX_LG_F32: + return V_CMP_F32(ConditionOp::LG, true, inst); + case Opcode::V_CMPX_GE_F32: + return V_CMP_F32(ConditionOp::GE, true, inst); + case Opcode::V_CMPX_NGE_F32: + return V_CMP_F32(ConditionOp::LT, true, inst); + case Opcode::V_CMPX_NLG_F32: + return V_CMP_F32(ConditionOp::EQ, true, inst); + case Opcode::V_CMPX_NGT_F32: + return V_CMP_F32(ConditionOp::LE, true, inst); + case Opcode::V_CMPX_NLE_F32: + return V_CMP_F32(ConditionOp::GT, true, inst); + case Opcode::V_CMPX_NEQ_F32: + return V_CMP_F32(ConditionOp::LG, true, inst); + case Opcode::V_CMPX_NLT_F32: + return V_CMP_F32(ConditionOp::GE, true, inst); + case Opcode::V_CMPX_TRU_F32: + return V_CMP_F32(ConditionOp::TRU, true, inst); + case Opcode::V_CMP_CLASS_F32: + return V_CMP_CLASS_F32(inst); + + case Opcode::V_CMP_LE_U32: + return V_CMP_U32(ConditionOp::LE, false, false, inst); + case Opcode::V_CMP_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, false, inst); + case Opcode::V_CMP_LT_I32: + return V_CMP_U32(ConditionOp::LT, true, false, inst); + case Opcode::V_CMPX_LT_I32: + return V_CMP_U32(ConditionOp::LT, true, true, inst); + case Opcode::V_CMPX_F_U32: + return V_CMP_U32(ConditionOp::F, false, true, inst); + case Opcode::V_CMPX_LT_U32: + return V_CMP_U32(ConditionOp::LT, false, true, inst); + case Opcode::V_CMPX_EQ_U32: + return V_CMP_U32(ConditionOp::EQ, false, true, inst); + case Opcode::V_CMPX_LE_U32: + return V_CMP_U32(ConditionOp::LE, false, true, inst); + case Opcode::V_CMPX_GT_U32: + return V_CMP_U32(ConditionOp::GT, false, true, inst); + case Opcode::V_CMPX_NE_U32: + return V_CMP_U32(ConditionOp::LG, false, true, inst); + case Opcode::V_CMPX_GE_U32: + return V_CMP_U32(ConditionOp::GE, false, true, inst); + case Opcode::V_CMPX_TRU_U32: + return V_CMP_U32(ConditionOp::TRU, false, true, inst); + case Opcode::V_CMPX_LG_I32: + return V_CMP_U32(ConditionOp::LG, true, true, inst); + + case Opcode::V_MBCNT_LO_U32_B32: + return V_MBCNT_U32_B32(true, inst); + case Opcode::V_MBCNT_HI_U32_B32: + return V_MBCNT_U32_B32(false, inst); + default: + LogMissingOpcode(inst); + } +} + void Translator::V_MOV(const GcnInst& inst) { SetDst(inst.dst[0], GetSrc(inst.src[0])); } @@ -32,6 +334,12 @@ void Translator::V_CVT_F32_F16(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); } +void Translator::V_CVT_F16_F32(const GcnInst& inst) { + const IR::F32 src0 = GetSrc(inst.src[0], true); + const IR::F16 src0fp16 = ir.FPConvert(16, src0); + SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +} + void Translator::V_MUL_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0], true), GetSrc(inst.src[1], true))); } @@ -85,6 +393,12 @@ void Translator::V_LSHLREV_B32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } +void Translator::V_LSHL_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + void Translator::V_ADD_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; @@ -208,6 +522,8 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { return ir.FPLessThanEqual(src0, src1); case ConditionOp::GE: return ir.FPGreaterThanEqual(src0, src1); + case ConditionOp::U: + return ir.LogicalNot(ir.LogicalAnd(ir.FPIsNan(src0), ir.FPIsNan(src1))); default: UNREACHABLE(); } @@ -278,6 +594,13 @@ void Translator::V_MIN3_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); } +void Translator::V_MIN3_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); +} + void Translator::V_MADMK_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0], true)}; const IR::F32 src1{GetSrc(inst.src[1], true)}; @@ -320,12 +643,13 @@ void Translator::V_SUBREV_I32(const GcnInst& inst) { } void Translator::V_MAD_U64_U32(const GcnInst& inst) { - const auto src0 = GetSrc(inst.src[0]); const auto src1 = GetSrc(inst.src[1]); const auto src2 = GetSrc64(inst.src[2]); - const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + const IR::U64 mul_result = + ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); const IR::U64 sum_result = ir.IAdd(mul_result, src2); SetDst64(inst.dst[0], sum_result); @@ -463,6 +787,13 @@ void Translator::V_MAX3_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); } +void Translator::V_MAX3_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.UMax(src0, ir.UMax(src1, src2))); +} + void Translator::V_CVT_I32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0], true)}; SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); @@ -561,38 +892,58 @@ void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { } void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { - constexpr u32 SIGNALING_NAN = 1 << 0; - constexpr u32 QUIET_NAN = 1 << 1; - constexpr u32 NEGATIVE_INFINITY = 1 << 2; - constexpr u32 NEGATIVE_NORMAL = 1 << 3; - constexpr u32 NEGATIVE_DENORM = 1 << 4; - constexpr u32 NEGATIVE_ZERO = 1 << 5; - constexpr u32 POSITIVE_ZERO = 1 << 6; - constexpr u32 POSITIVE_DENORM = 1 << 7; - constexpr u32 POSITIVE_NORMAL = 1 << 8; - constexpr u32 POSITIVE_INFINITY = 1 << 9; - const IR::F32F64 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; + IR::U1 value; if (src1.IsImmediate()) { - const u32 class_mask = src1.U32(); - IR::U1 value; - if ((class_mask & (SIGNALING_NAN | QUIET_NAN)) == (SIGNALING_NAN | QUIET_NAN)) { + const auto class_mask = static_cast(src1.U32()); + if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { value = ir.FPIsNan(src0); - } else if ((class_mask & (POSITIVE_INFINITY | NEGATIVE_INFINITY)) == - (POSITIVE_INFINITY | NEGATIVE_INFINITY)) { + } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { value = ir.FPIsInf(src0); } else { UNREACHABLE(); } - if (inst.dst[1].field == OperandField::VccLo) { - return ir.SetVcc(value); - } else { - UNREACHABLE(); - } } else { + // We don't know the type yet, delay its resolution. + value = ir.FPCmpClass32(src0, src1); + } + + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(value); + default: UNREACHABLE(); } } +void Translator::V_FFBL_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FindILsb(src0)); +} + +void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 lane_id = ir.LaneId(); + + const auto [warp_half, mask_shift] = [&]() -> std::pair { + if (profile.subgroup_size == 32) { + const IR::U32 warp_half = ir.BitwiseAnd(ir.WarpId(), ir.Imm32(1)); + return std::make_pair(warp_half, lane_id); + } + const IR::U32 warp_half = ir.ShiftRightLogical(lane_id, ir.Imm32(5)); + const IR::U32 mask_shift = ir.BitwiseAnd(lane_id, ir.Imm32(0x1F)); + return std::make_pair(warp_half, mask_shift); + }(); + + const IR::U32 thread_mask = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), mask_shift), ir.Imm32(1)); + const IR::U1 is_odd_warp = ir.INotEqual(warp_half, ir.Imm32(0)); + const IR::U32 mask = IR::U32{ir.Select(is_odd_warp, is_low ? ir.Imm32(~0U) : thread_mask, + is_low ? thread_mask : ir.Imm32(0))}; + const IR::U32 masked_value = ir.BitwiseAnd(src0, mask); + const IR::U32 result = ir.IAdd(src1, ir.BitCount(masked_value)); + SetDst(inst.dst[0], result); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index 55a2d624e..4ff846cf8 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -12,4 +12,24 @@ void Translator::V_INTERP_P2_F32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); } +void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { + const IR::VectorReg dst_reg{inst.dst[0].code}; + auto& attr = info.ps_inputs.at(inst.control.vintrp.attr); + const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); +} + +void Translator::EmitVectorInterpolation(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::V_INTERP_P1_F32: + return; + case Opcode::V_INTERP_P2_F32: + return V_INTERP_P2_F32(inst); + case Opcode::V_INTERP_MOV_F32: + return V_INTERP_MOV_F32(inst); + default: + LogMissingOpcode(inst); + } +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index f4383c61d..c667968a4 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -5,9 +5,96 @@ namespace Shader::Gcn { +void Translator::EmitVectorMemory(const GcnInst& inst) { + switch (inst.opcode) { + case Opcode::IMAGE_SAMPLE_LZ_O: + case Opcode::IMAGE_SAMPLE_O: + case Opcode::IMAGE_SAMPLE_C: + case Opcode::IMAGE_SAMPLE_C_LZ: + case Opcode::IMAGE_SAMPLE_LZ: + case Opcode::IMAGE_SAMPLE: + case Opcode::IMAGE_SAMPLE_L: + case Opcode::IMAGE_SAMPLE_C_O: + case Opcode::IMAGE_SAMPLE_B: + case Opcode::IMAGE_SAMPLE_C_LZ_O: + return IMAGE_SAMPLE(inst); + case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_LZ: + case Opcode::IMAGE_GATHER4_LZ_O: + return IMAGE_GATHER(inst); + case Opcode::IMAGE_ATOMIC_ADD: + return IMAGE_ATOMIC(AtomicOp::Add, inst); + case Opcode::IMAGE_ATOMIC_AND: + return IMAGE_ATOMIC(AtomicOp::And, inst); + case Opcode::IMAGE_ATOMIC_OR: + return IMAGE_ATOMIC(AtomicOp::Or, inst); + case Opcode::IMAGE_ATOMIC_XOR: + return IMAGE_ATOMIC(AtomicOp::Xor, inst); + case Opcode::IMAGE_ATOMIC_UMAX: + return IMAGE_ATOMIC(AtomicOp::Umax, inst); + case Opcode::IMAGE_ATOMIC_SMAX: + return IMAGE_ATOMIC(AtomicOp::Smax, inst); + case Opcode::IMAGE_ATOMIC_UMIN: + return IMAGE_ATOMIC(AtomicOp::Umin, inst); + case Opcode::IMAGE_ATOMIC_SMIN: + return IMAGE_ATOMIC(AtomicOp::Smin, inst); + case Opcode::IMAGE_ATOMIC_INC: + return IMAGE_ATOMIC(AtomicOp::Inc, inst); + case Opcode::IMAGE_ATOMIC_DEC: + return IMAGE_ATOMIC(AtomicOp::Dec, inst); + case Opcode::IMAGE_GET_LOD: + return IMAGE_GET_LOD(inst); + case Opcode::IMAGE_STORE: + return IMAGE_STORE(inst); + case Opcode::IMAGE_LOAD_MIP: + return IMAGE_LOAD(true, inst); + case Opcode::IMAGE_LOAD: + return IMAGE_LOAD(false, inst); + case Opcode::IMAGE_GET_RESINFO: + return IMAGE_GET_RESINFO(inst); + + case Opcode::TBUFFER_LOAD_FORMAT_X: + return BUFFER_LOAD_FORMAT(1, true, true, inst); + case Opcode::TBUFFER_LOAD_FORMAT_XY: + return BUFFER_LOAD_FORMAT(2, true, true, inst); + case Opcode::TBUFFER_LOAD_FORMAT_XYZ: + return BUFFER_LOAD_FORMAT(3, true, true, inst); + case Opcode::TBUFFER_LOAD_FORMAT_XYZW: + return BUFFER_LOAD_FORMAT(4, true, true, inst); + case Opcode::BUFFER_LOAD_FORMAT_X: + return BUFFER_LOAD_FORMAT(1, false, true, inst); + case Opcode::BUFFER_LOAD_FORMAT_XY: + return BUFFER_LOAD_FORMAT(2, false, true, inst); + case Opcode::BUFFER_LOAD_FORMAT_XYZ: + return BUFFER_LOAD_FORMAT(3, false, true, inst); + case Opcode::BUFFER_LOAD_FORMAT_XYZW: + return BUFFER_LOAD_FORMAT(4, false, true, inst); + case Opcode::BUFFER_LOAD_DWORD: + return BUFFER_LOAD_FORMAT(1, false, false, inst); + case Opcode::BUFFER_LOAD_DWORDX2: + return BUFFER_LOAD_FORMAT(2, false, false, inst); + case Opcode::BUFFER_LOAD_DWORDX3: + return BUFFER_LOAD_FORMAT(3, false, false, inst); + case Opcode::BUFFER_LOAD_DWORDX4: + return BUFFER_LOAD_FORMAT(4, false, false, inst); + case Opcode::BUFFER_STORE_FORMAT_X: + case Opcode::BUFFER_STORE_DWORD: + return BUFFER_STORE_FORMAT(1, false, inst); + case Opcode::BUFFER_STORE_DWORDX2: + return BUFFER_STORE_FORMAT(2, false, inst); + case Opcode::BUFFER_STORE_DWORDX3: + return BUFFER_STORE_FORMAT(3, false, inst); + case Opcode::BUFFER_STORE_FORMAT_XYZW: + case Opcode::BUFFER_STORE_DWORDX4: + return BUFFER_STORE_FORMAT(4, false, inst); + default: + LogMissingOpcode(inst); + } +} + void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; const auto flags = ImageResFlags(inst.control.mimg.dmask); const bool has_mips = flags.test(ImageResComponent::MipCount); const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); @@ -157,7 +244,7 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) { info.has_bias.Assign(flags.test(MimgModifier::LodBias)); info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.explicit_lod.Assign(explicit_lod); + // info.explicit_lod.Assign(explicit_lod); info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); // Issue IR instruction, leaving unknown fields blank to patch later. diff --git a/src/shader_recompiler/ir/breadth_first_search.h b/src/shader_recompiler/ir/breadth_first_search.h index 21a34a903..0156303f0 100644 --- a/src/shader_recompiler/ir/breadth_first_search.h +++ b/src/shader_recompiler/ir/breadth_first_search.h @@ -12,16 +12,16 @@ namespace Shader::IR { template -auto BreadthFirstSearch(const Value& value, Pred&& pred) - -> std::invoke_result_t { - if (value.IsImmediate()) { - // Nothing to do with immediates - return std::nullopt; +auto BreadthFirstSearch(const Inst* inst, Pred&& pred) -> std::invoke_result_t { + // Most often case the instruction is the desired already. + if (const std::optional result = pred(inst)) { + return result; } + // Breadth-first search visiting the right most arguments first boost::container::small_vector visited; std::queue queue; - queue.push(value.InstRecursive()); + queue.push(inst); while (!queue.empty()) { // Pop one instruction from the queue @@ -49,4 +49,14 @@ auto BreadthFirstSearch(const Value& value, Pred&& pred) return std::nullopt; } +template +auto BreadthFirstSearch(const Value& value, Pred&& pred) + -> std::invoke_result_t { + if (value.IsImmediate()) { + // Nothing to do with immediates + return std::nullopt; + } + return BreadthFirstSearch(value.InstRecursive(), pred); +} + } // namespace Shader::IR diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 8b605df81..03404aca0 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -278,7 +278,7 @@ Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { case 32: return Inst(Opcode::LoadSharedU32, offset); case 64: - return Inst(Opcode::LoadSharedU64, offset); + return Inst(Opcode::LoadSharedU64, offset); case 128: return Inst(Opcode::LoadSharedU128, offset); default: @@ -373,6 +373,10 @@ U32 IREmitter::LaneId() { return Inst(Opcode::LaneId); } +U32 IREmitter::WarpId() { + return Inst(Opcode::WarpId); +} + U32 IREmitter::QuadShuffle(const U32& value, const U32& index) { return Inst(Opcode::QuadShuffle, value, index); } @@ -876,6 +880,10 @@ U1 IREmitter::FPIsInf(const F32F64& value) { } } +U1 IREmitter::FPCmpClass32(const F32& value, const U32& op) { + return Inst(Opcode::FPCmpClass32, value, op); +} + U1 IREmitter::FPOrdered(const F32F64& lhs, const F32F64& rhs) { if (lhs.Type() != rhs.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); @@ -1088,6 +1096,10 @@ U32 IREmitter::FindUMsb(const U32& value) { return Inst(Opcode::FindUMsb32, value); } +U32 IREmitter::FindILsb(const U32& value) { + return Inst(Opcode::FindILsb32, value); +} + U32 IREmitter::SMin(const U32& a, const U32& b) { return Inst(Opcode::SMin32, a, b); } @@ -1274,6 +1286,11 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) { default: break; } + case 32: + switch (value.Type()) { + case Type::U16: + return Inst(Opcode::ConvertU32U16, value); + } default: break; } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 7ee4e8240..a65e46136 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -95,6 +95,7 @@ public: BufferInstInfo info); [[nodiscard]] U32 LaneId(); + [[nodiscard]] U32 WarpId(); [[nodiscard]] U32 QuadShuffle(const U32& value, const U32& index); [[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2); @@ -150,6 +151,7 @@ public: [[nodiscard]] U1 FPGreaterThan(const F32F64& lhs, const F32F64& rhs, bool ordered = true); [[nodiscard]] U1 FPIsNan(const F32F64& value); [[nodiscard]] U1 FPIsInf(const F32F64& value); + [[nodiscard]] U1 FPCmpClass32(const F32& value, const U32& op); [[nodiscard]] U1 FPOrdered(const F32F64& lhs, const F32F64& rhs); [[nodiscard]] U1 FPUnordered(const F32F64& lhs, const F32F64& rhs); [[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs, bool is_legacy = false); @@ -179,6 +181,7 @@ public: [[nodiscard]] U32 FindSMsb(const U32& value); [[nodiscard]] U32 FindUMsb(const U32& value); + [[nodiscard]] U32 FindILsb(const U32& value); [[nodiscard]] U32 SMin(const U32& a, const U32& b); [[nodiscard]] U32 UMin(const U32& a, const U32& b); [[nodiscard]] U32 IMin(const U32& a, const U32& b, bool is_signed); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 628b8d4fa..aa2fd3f82 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -219,6 +219,7 @@ OPCODE(FPIsNan32, U1, F32, OPCODE(FPIsNan64, U1, F64, ) OPCODE(FPIsInf32, U1, F32, ) OPCODE(FPIsInf64, U1, F64, ) +OPCODE(FPCmpClass32, U1, F32, U32 ) // Integer operations OPCODE(IAdd32, U32, U32, U32, ) @@ -254,6 +255,7 @@ OPCODE(BitwiseNot32, U32, U32, OPCODE(FindSMsb32, U32, U32, ) OPCODE(FindUMsb32, U32, U32, ) +OPCODE(FindILsb32, U32, U32, ) OPCODE(SMin32, U32, U32, U32, ) OPCODE(UMin32, U32, U32, U32, ) OPCODE(SMax32, U32, U32, U32, ) @@ -293,6 +295,7 @@ OPCODE(ConvertF64S32, F64, U32, OPCODE(ConvertF64U32, F64, U32, ) OPCODE(ConvertF32U16, F32, U16, ) OPCODE(ConvertU16U32, U16, U32, ) +OPCODE(ConvertU32U16, U32, U16, ) // Image operations OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, Opaque, Opaque, ) @@ -323,4 +326,5 @@ OPCODE(ImageAtomicExchange32, U32, Opaq // Warp operations OPCODE(LaneId, U32, ) +OPCODE(WarpId, U32, ) OPCODE(QuadShuffle, U32, U32, U32 ) diff --git a/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp index 13c0246ea..94218b32f 100644 --- a/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propogation_pass.cpp @@ -238,6 +238,18 @@ void FoldBooleanConvert(IR::Inst& inst) { } } +void FoldCmpClass(IR::Inst& inst) { + ASSERT_MSG(inst.Arg(1).IsImmediate(), "Unable to resolve compare operation"); + const auto class_mask = static_cast(inst.Arg(1).U32()); + if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { + inst.ReplaceOpcode(IR::Opcode::FPIsNan32); + } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { + inst.ReplaceOpcode(IR::Opcode::FPIsInf32); + } else { + UNREACHABLE(); + } +} + void ConstantPropagation(IR::Block& block, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::IAdd32: @@ -251,6 +263,9 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::IMul32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a * b; }); return; + case IR::Opcode::FPCmpClass32: + FoldCmpClass(inst); + return; case IR::Opcode::ShiftRightArithmetic32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return static_cast(a >> b); }); return; diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 6526ece62..eaca8ce8c 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/breadth_first_search.h" @@ -273,9 +272,18 @@ std::pair TryDisableAnisoLod0(const IR::Inst* inst) { } SharpLocation TrackSharp(const IR::Inst* inst) { - while (inst->GetOpcode() == IR::Opcode::Phi) { - inst = inst->Arg(0).InstRecursive(); - } + // Search until we find a potential sharp source. + const auto pred0 = [](const IR::Inst* inst) -> std::optional { + if (inst->GetOpcode() == IR::Opcode::GetUserData || + inst->GetOpcode() == IR::Opcode::ReadConst) { + return inst; + } + return std::nullopt; + }; + const auto result = IR::BreadthFirstSearch(inst, pred0); + ASSERT_MSG(result, "Unable to track sharp source"); + inst = result.value(); + // If its from user data not much else to do. if (inst->GetOpcode() == IR::Opcode::GetUserData) { return SharpLocation{ .sgpr_base = u32(IR::ScalarReg::Max), @@ -289,14 +297,14 @@ SharpLocation TrackSharp(const IR::Inst* inst) { const IR::Inst* spgpr_base = inst->Arg(0).InstRecursive(); // Retrieve SGPR pair that holds sbase - const auto pred = [](const IR::Inst* inst) -> std::optional { + const auto pred1 = [](const IR::Inst* inst) -> std::optional { if (inst->GetOpcode() == IR::Opcode::GetUserData) { return inst->Arg(0).ScalarReg(); } return std::nullopt; }; - const auto base0 = IR::BreadthFirstSearch(spgpr_base->Arg(0), pred); - const auto base1 = IR::BreadthFirstSearch(spgpr_base->Arg(1), pred); + const auto base0 = IR::BreadthFirstSearch(spgpr_base->Arg(0), pred1); + const auto base1 = IR::BreadthFirstSearch(spgpr_base->Arg(1), pred1); ASSERT_MSG(base0 && base1, "Nested resource loads not supported"); // Return retrieved location. @@ -456,36 +464,26 @@ IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& } void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { - std::deque insts{&inst}; - const auto& pred = [](auto opcode) -> bool { - return (opcode == IR::Opcode::CompositeConstructU32x2 || // IMAGE_SAMPLE (image+sampler) - opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) - opcode == IR::Opcode::GetUserData); + const auto pred = [](const IR::Inst* inst) -> std::optional { + const auto opcode = inst->GetOpcode(); + if (opcode == IR::Opcode::CompositeConstructU32x2 || // IMAGE_SAMPLE (image+sampler) + opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) + opcode == IR::Opcode::GetUserData) { + return inst; + } + return std::nullopt; }; - - IR::Inst* producer{}; - while (!insts.empty() && (producer = insts.front(), !pred(producer->GetOpcode()))) { - for (auto arg_idx = 0u; arg_idx < producer->NumArgs(); ++arg_idx) { - const auto arg = producer->Arg(arg_idx); - if (arg.TryInstRecursive()) { - insts.push_back(arg.InstRecursive()); - } - } - insts.pop_front(); - } - ASSERT(pred(producer->GetOpcode())); - auto [tsharp_handle, ssharp_handle] = [&] -> std::pair { - if (producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2) { - return std::make_pair(producer->Arg(0).InstRecursive(), - producer->Arg(1).InstRecursive()); - } - return std::make_pair(producer, nullptr); - }(); + const auto result = IR::BreadthFirstSearch(&inst, pred); + ASSERT_MSG(result, "Unable to find image sharp source"); + const IR::Inst* producer = result.value(); + const bool has_sampler = producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2; + const auto tsharp_handle = has_sampler ? producer->Arg(0).InstRecursive() : producer; // Read image sharp. const auto tsharp = TrackSharp(tsharp_handle); const auto image = info.ReadUd(tsharp.sgpr_base, tsharp.dword_offset); const auto inst_info = inst.Flags(); + ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); u32 image_binding = descriptors.Add(ImageResource{ .sgpr_base = tsharp.sgpr_base, .dword_offset = tsharp.dword_offset, @@ -496,17 +494,32 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip }); // Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions - if (ssharp_handle) { + const u32 sampler_binding = [&] { + if (!has_sampler) { + return 0U; + } + const IR::Value& handle = producer->Arg(1); + // Inline sampler resource. + if (handle.IsImmediate()) { + LOG_WARNING(Render_Vulkan, "Inline sampler detected"); + return descriptors.Add(SamplerResource{ + .sgpr_base = std::numeric_limits::max(), + .dword_offset = 0, + .inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()}, + }); + } + // Normal sampler resource. + const auto ssharp_handle = handle.InstRecursive(); const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); const auto ssharp = TrackSharp(ssharp_ud); - const u32 sampler_binding = descriptors.Add(SamplerResource{ + return descriptors.Add(SamplerResource{ .sgpr_base = ssharp.sgpr_base, .dword_offset = ssharp.dword_offset, .associated_image = image_binding, .disable_aniso = disable_aniso, }); - image_binding |= (sampler_binding << 16); - } + }(); + image_binding |= (sampler_binding << 16); // Patch image handle IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; @@ -607,7 +620,7 @@ void ResourceTrackingPass(IR::Program& program) { // Iterate resource instructions and patch them after finding the sharp. auto& info = program.info; Descriptors descriptors{info.buffers, info.images, info.samplers}; - for (IR::Block* const block : program.post_order_blocks) { + for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (IsBufferInstruction(inst)) { PatchBufferInstruction(*block, inst, info, descriptors); 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 b51ce94ee..7100b3844 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -20,11 +20,19 @@ void Visit(Info& info, IR::Inst& inst) { case IR::Opcode::LoadSharedU8: case IR::Opcode::WriteSharedU8: info.uses_shared_u8 = true; + info.uses_shared = true; break; case IR::Opcode::LoadSharedS16: case IR::Opcode::LoadSharedU16: case IR::Opcode::WriteSharedU16: info.uses_shared_u16 = true; + info.uses_shared = true; + break; + case IR::Opcode::LoadSharedU32: + case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU32: + case IR::Opcode::WriteSharedU64: + info.uses_shared = true; break; case IR::Opcode::ConvertF32F16: case IR::Opcode::BitCastF16U16: diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index d9e9b0307..e3d04260b 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/bit_field.h" +#include "common/enum.h" #include "common/types.h" #include "video_core/amdgpu/pixel_format.h" @@ -24,6 +25,23 @@ enum class FpDenormMode : u32 { InOutAllow = 3, }; +enum class FloatClassFunc : u32 { + SignalingNan = 1 << 0, + QuietNan = 1 << 1, + NegativeInfinity = 1 << 2, + NegativeNormal = 1 << 3, + NegativeDenorm = 1 << 4, + NegativeZero = 1 << 5, + PositiveZero = 1 << 6, + PositiveDenorm = 1 << 7, + PositiveNormal = 1 << 8, + PositiveInfinity = 1 << 9, + + NaN = SignalingNan | QuietNan, + Infinity = PositiveInfinity | NegativeInfinity, +}; +DECLARE_ENUM_FLAG_OPERATORS(FloatClassFunc) + union Mode { BitField<0, 4, FpRoundMode> fp_round; BitField<4, 2, FpDenormMode> fp_denorm_single; diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 54b347300..badd54554 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -9,6 +9,7 @@ namespace Shader { struct Profile { u32 supported_spirv{0x00010000}; + u32 subgroup_size{}; bool unified_descriptor_binding{}; bool support_descriptor_aliasing{}; bool support_int8{}; diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index f2834abf1..d747c016b 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -28,7 +28,8 @@ IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) { } IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, - std::span token, const Info&& info) { + std::span token, const Info&& info, + const Profile& profile) { // Ensure first instruction is expected. constexpr u32 token_mov_vcchi = 0xBEEB03FF; ASSERT_MSG(token[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); @@ -49,7 +50,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& inst_pool, ObjectPool& inst_pool, ObjectPool& block_pool, - std::span code, const Info&& info); + std::span code, const Info&& info, + const Profile& profile); } // namespace Shader diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 8824e344b..277c38b72 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -97,8 +97,11 @@ using ImageResourceList = boost::container::static_vector; struct SamplerResource { u32 sgpr_base; u32 dword_offset; + AmdGpu::Sampler inline_sampler{}; u32 associated_image : 4; u32 disable_aniso : 1; + + constexpr AmdGpu::Sampler GetSsharp(const Info& info) const noexcept; }; using SamplerResourceList = boost::container::static_vector; @@ -175,6 +178,7 @@ struct Info { bool has_image_gather{}; bool has_image_query{}; bool uses_group_quad{}; + bool uses_shared{}; bool uses_shared_u8{}; bool uses_shared_u16{}; bool uses_fp16{}; @@ -196,6 +200,10 @@ constexpr AmdGpu::Buffer BufferResource::GetVsharp(const Info& info) const noexc return inline_cbuf ? inline_cbuf : info.ReadUd(sgpr_base, dword_offset); } +constexpr AmdGpu::Sampler SamplerResource::GetSsharp(const Info& info) const noexcept { + return inline_sampler ? inline_sampler : info.ReadUd(sgpr_base, dword_offset); +} + } // namespace Shader template <> diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index df7eec826..af1963eec 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -403,9 +403,11 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanWaitVoLabel([&] { return wait_reg_mem->Test(); }); } while (!wait_reg_mem->Test()) { + mapped_queues[GfxQueueId].cs_state = regs.cs_program; TracyFiberLeave; co_yield {}; TracyFiberEnter(dcb_task_name); + regs.cs_program = mapped_queues[GfxQueueId].cs_state; } break; } @@ -506,9 +508,11 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { const auto* wait_reg_mem = reinterpret_cast(header); ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); while (!wait_reg_mem->Test()) { + mapped_queues[vqid].cs_state = regs.cs_program; TracyFiberLeave; co_yield {}; TracyFiberEnter(acb_task_name); + regs.cs_program = mapped_queues[vqid].cs_state; } break; } @@ -529,7 +533,6 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { } void Liverpool::SubmitGfx(std::span dcb, std::span ccb) { - static constexpr u32 GfxQueueId = 0u; auto& queue = mapped_queues[GfxQueueId]; auto task = ProcessGraphics(dcb, ccb); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index db2ee91c3..b0285809c 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -36,6 +36,7 @@ namespace AmdGpu { [[maybe_unused]] std::array CONCAT2(pad, __LINE__) struct Liverpool { + static constexpr u32 GfxQueueId = 0u; static constexpr u32 NumGfxRings = 1u; // actually 2, but HP is reserved by system software static constexpr u32 NumComputePipes = 7u; // actually 8, but #7 is reserved by system software static constexpr u32 NumQueuesPerPipe = 8u; @@ -1061,6 +1062,7 @@ private: struct GpuQueue { std::mutex m_access{}; std::queue submits{}; + ComputeProgram cs_state{}; }; std::array mapped_queues{}; diff --git a/src/video_core/amdgpu/pixel_format.cpp b/src/video_core/amdgpu/pixel_format.cpp index 6618e72ad..6744891a6 100644 --- a/src/video_core/amdgpu/pixel_format.cpp +++ b/src/video_core/amdgpu/pixel_format.cpp @@ -7,6 +7,77 @@ namespace AmdGpu { +std::string_view NameOf(DataFormat fmt) { + switch (fmt) { + case DataFormat::FormatInvalid: + return "FormatInvalid"; + case DataFormat::Format8: + return "Format8"; + case DataFormat::Format16: + return "Format16"; + case DataFormat::Format8_8: + return "Format8_8"; + case DataFormat::Format32: + return "Format32"; + case DataFormat::Format16_16: + return "Format16_16"; + case DataFormat::Format10_11_11: + return "Format10_11_11"; + case DataFormat::Format11_11_10: + return "Format11_11_10"; + case DataFormat::Format10_10_10_2: + return "Format10_10_10_2"; + case DataFormat::Format2_10_10_10: + return "Format2_10_10_10"; + case DataFormat::Format8_8_8_8: + return "Format8_8_8_8"; + case DataFormat::Format32_32: + return "Format32_32"; + case DataFormat::Format16_16_16_16: + return "Format16_16_16_16"; + case DataFormat::Format32_32_32: + return "Format32_32_32"; + case DataFormat::Format32_32_32_32: + return "Format32_32_32_32"; + case DataFormat::Format5_6_5: + return "Format5_6_5"; + case DataFormat::Format1_5_5_5: + return "Format1_5_5_5"; + case DataFormat::Format5_5_5_1: + return "Format5_5_5_1"; + case DataFormat::Format4_4_4_4: + return "Format4_4_4_4"; + case DataFormat::Format8_24: + return "Format8_24"; + case DataFormat::Format24_8: + return "Format24_8"; + case DataFormat::FormatX24_8_32: + return "FormatX24_8_32"; + case DataFormat::FormatGB_GR: + return "FormatGB_GR"; + case DataFormat::FormatBG_RG: + return "FormatBG_RG"; + case DataFormat::Format5_9_9_9: + return "Format5_9_9_9"; + case DataFormat::FormatBc1: + return "FormatBc1"; + case DataFormat::FormatBc2: + return "FormatBc2"; + case DataFormat::FormatBc3: + return "FormatBc3"; + case DataFormat::FormatBc4: + return "FormatBc4"; + case DataFormat::FormatBc5: + return "FormatBc5"; + case DataFormat::FormatBc6: + return "FormatBc6"; + case DataFormat::FormatBc7: + return "FormatBc7"; + default: + UNREACHABLE(); + } +} + std::string_view NameOf(NumberFormat fmt) { switch (fmt) { case NumberFormat::Unorm: diff --git a/src/video_core/amdgpu/pixel_format.h b/src/video_core/amdgpu/pixel_format.h index 2a38c5a02..1004ed7d2 100644 --- a/src/video_core/amdgpu/pixel_format.h +++ b/src/video_core/amdgpu/pixel_format.h @@ -61,6 +61,7 @@ enum class NumberFormat : u32 { Ubscaled = 13, }; +[[nodiscard]] std::string_view NameOf(DataFormat fmt); [[nodiscard]] std::string_view NameOf(NumberFormat fmt); int NumComponents(DataFormat format); @@ -70,6 +71,16 @@ s32 ComponentOffset(DataFormat format, u32 comp); } // namespace AmdGpu +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + auto format(AmdGpu::DataFormat fmt, format_context& ctx) const { + return fmt::format_to(ctx.out(), "{}", AmdGpu::NameOf(fmt)); + } +}; + template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) { diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 6ab3306b4..01271792b 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -75,7 +75,7 @@ struct Buffer { static_assert(sizeof(Buffer) == 16); // 128bits enum class ImageType : u64 { - Buffer = 0, + Invalid = 0, Color1D = 8, Color2D = 9, Color3D = 10, @@ -88,8 +88,8 @@ enum class ImageType : u64 { constexpr std::string_view NameOf(ImageType type) { switch (type) { - case ImageType::Buffer: - return "Buffer"; + case ImageType::Invalid: + return "Invalid"; case ImageType::Color1D: return "Color1D"; case ImageType::Color2D: @@ -179,6 +179,40 @@ struct Image { return base_address << 8; } + u32 DstSelect() const { + return dst_sel_x | (dst_sel_y << 3) | (dst_sel_z << 6) | (dst_sel_w << 9); + } + + static char SelectComp(u32 sel) { + switch (sel) { + case 0: + return '0'; + case 1: + return '1'; + case 4: + return 'R'; + case 5: + return 'G'; + case 6: + return 'B'; + case 7: + return 'A'; + default: + UNREACHABLE(); + } + } + + std::string DstSelectName() const { + std::string result = "["; + u32 dst_sel = DstSelect(); + for (u32 i = 0; i < 4; i++) { + result += SelectComp(dst_sel & 7); + dst_sel >>= 3; + } + result += ']'; + return result; + } + u32 Pitch() const { return pitch + 1; } @@ -290,6 +324,7 @@ enum class BorderColor : u64 { // Table 8.12 Sampler Resource Definition struct Sampler { union { + u64 raw0; BitField<0, 3, ClampMode> clamp_x; BitField<3, 3, ClampMode> clamp_y; BitField<6, 3, ClampMode> clamp_z; @@ -309,6 +344,7 @@ struct Sampler { BitField<60, 4, u64> perf_z; }; union { + u64 raw1; BitField<0, 14, u64> lod_bias; BitField<14, 6, u64> lod_bias_sec; BitField<20, 2, Filter> xy_mag_filter; @@ -323,6 +359,10 @@ struct Sampler { BitField<62, 2, BorderColor> border_color_type; }; + operator bool() const noexcept { + return raw0 != 0 || raw1 != 0; + } + float LodBias() const noexcept { return static_cast(static_cast((lod_bias.Value() ^ 0x2000u) - 0x2000u)) / 256.0f; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index fc7943e62..e7c39de05 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -297,6 +297,7 @@ std::span GetAllFormats() { vk::Format::eBc3UnormBlock, vk::Format::eBc4UnormBlock, vk::Format::eBc5UnormBlock, + vk::Format::eBc5SnormBlock, vk::Format::eBc7SrgbBlock, vk::Format::eBc7UnormBlock, vk::Format::eD16Unorm, @@ -308,6 +309,7 @@ std::span GetAllFormats() { vk::Format::eR8G8B8A8Srgb, vk::Format::eR8G8B8A8Uint, vk::Format::eR8G8B8A8Unorm, + vk::Format::eR8G8B8A8Snorm, vk::Format::eR8G8B8A8Uscaled, vk::Format::eR8G8Snorm, vk::Format::eR8G8Uint, @@ -335,6 +337,10 @@ std::span GetAllFormats() { vk::Format::eR32Sfloat, vk::Format::eR32Sint, vk::Format::eR32Uint, + vk::Format::eBc6HUfloatBlock, + vk::Format::eR16G16Unorm, + vk::Format::eR16G16B16A16Sscaled, + vk::Format::eR16G16Sscaled, }; return formats; } @@ -384,10 +390,17 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu if (data_format == AmdGpu::DataFormat::FormatBc5 && num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eBc5UnormBlock; } + if (data_format == AmdGpu::DataFormat::FormatBc5 && num_format == AmdGpu::NumberFormat::Snorm) { + return vk::Format::eBc5SnormBlock; + } if (data_format == AmdGpu::DataFormat::Format16_16_16_16 && num_format == AmdGpu::NumberFormat::Sint) { return vk::Format::eR16G16B16A16Sint; } + if (data_format == AmdGpu::DataFormat::Format16_16_16_16 && + num_format == AmdGpu::NumberFormat::Sscaled) { + return vk::Format::eR16G16B16A16Sscaled; + } if (data_format == AmdGpu::DataFormat::Format16_16 && num_format == AmdGpu::NumberFormat::Float) { return vk::Format::eR16G16Sfloat; @@ -496,6 +509,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::Sint) { return vk::Format::eR16G16Sint; } + if (data_format == AmdGpu::DataFormat::Format16_16 && + num_format == AmdGpu::NumberFormat::Sscaled) { + return vk::Format::eR16G16Sscaled; + } if (data_format == AmdGpu::DataFormat::Format8_8_8_8 && num_format == AmdGpu::NumberFormat::Uscaled) { return vk::Format::eR8G8B8A8Uscaled; @@ -518,6 +535,13 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::SnormNz) { return vk::Format::eR16G16B16A16Snorm; } + if (data_format == AmdGpu::DataFormat::Format8_8_8_8 && + num_format == AmdGpu::NumberFormat::Snorm) { + return vk::Format::eR8G8B8A8Snorm; + } + if (data_format == AmdGpu::DataFormat::FormatBc6 && num_format == AmdGpu::NumberFormat::Unorm) { + return vk::Format::eBc6HUfloatBlock; + } UNREACHABLE_MSG("Unknown data_format={} and num_format={}", u32(data_format), u32(num_format)); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 51bb7f831..34f1e9cc4 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -148,7 +148,7 @@ bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& s } } for (const auto& sampler : info.samplers) { - const auto ssharp = info.ReadUd(sampler.sgpr_base, sampler.dword_offset); + const auto ssharp = sampler.GetSsharp(info); const auto vk_sampler = texture_cache.GetSampler(ssharp); image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index eb5522688..7b00a9111 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -386,7 +386,7 @@ void GraphicsPipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& } } for (const auto& sampler : stage.samplers) { - auto ssharp = stage.ReadUd(sampler.sgpr_base, sampler.dword_offset); + auto ssharp = sampler.GetSsharp(stage); if (sampler.disable_aniso) { const auto& tsharp = tsharps[sampler.associated_image]; if (tsharp.base_level == 0 && tsharp.last_level == 0) { diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 09a9180e1..735303a3e 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -164,10 +164,11 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR, vk::PhysicalDeviceDepthClipControlFeaturesEXT>(); - const vk::StructureChain properties_chain = - physical_device.getProperties2(); + const vk::StructureChain properties_chain = physical_device.getProperties2< + vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, + vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties>(); + subgroup_size = properties_chain.get().subgroupSize; + LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); features = feature_chain.get().features; if (available_extensions.empty()) { @@ -261,6 +262,7 @@ bool Instance::CreateDevice() { .shaderStorageImageExtendedFormats = features.shaderStorageImageExtendedFormats, .shaderStorageImageMultisample = features.shaderStorageImageMultisample, .shaderClipDistance = features.shaderClipDistance, + .shaderInt64 = features.shaderInt64, .shaderInt16 = features.shaderInt16, }, }, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 32965ddb1..a8c0dcf45 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -188,6 +188,11 @@ public: return properties.limits.nonCoherentAtomSize; } + /// Returns the subgroup size of the selected physical device. + u32 SubgroupSize() const { + return subgroup_size; + } + /// Returns the maximum supported elements in a texel buffer u32 MaxTexelBufferElements() const { return properties.limits.maxTexelBufferElements; @@ -249,6 +254,7 @@ private: bool workgroup_memory_explicit_layout{}; bool color_write_en{}; u64 min_imported_host_pointer_alignment{}; + u32 subgroup_size{}; bool tooling_info{}; bool debug_utils_supported{}; bool has_nsight_graphics{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 67994485a..8d27d252c 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -109,6 +109,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, pipeline_cache = instance.GetDevice().createPipelineCacheUnique({}); profile = Shader::Profile{ .supported_spirv = 0x00010600U, + .subgroup_size = instance.SubgroupSize(), .support_explicit_workgroup_layout = true, }; } @@ -268,7 +269,8 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline() { Shader::Info info = MakeShaderInfo(stage, pgm->user_data, regs); info.pgm_base = pgm->Address(); info.pgm_hash = hash; - programs[i] = Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info)); + programs[i] = + Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info), profile); // Compile IR to SPIR-V auto spv_code = Shader::Backend::SPIRV::EmitSPIRV(profile, programs[i], binding); @@ -308,7 +310,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline() { Shader::Info info = MakeShaderInfo(Shader::Stage::Compute, cs_pgm.user_data, liverpool->regs); info.pgm_base = cs_pgm.Address(); - auto program = Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info)); + auto program = + Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info), profile); // Compile IR to SPIR-V u32 binding{}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c64f6089b..ff5e97d5b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -23,7 +23,7 @@ Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, : instance{instance_}, scheduler{scheduler_}, texture_cache{texture_cache_}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool}, - vertex_index_buffer{instance, scheduler, VertexIndexFlags, 1_GB, BufferType::Upload} { + vertex_index_buffer{instance, scheduler, VertexIndexFlags, 2_GB, BufferType::Upload} { if (!Config::nullGpu()) { liverpool->BindRasterizer(this); } @@ -128,6 +128,7 @@ void Rasterizer::BeginRendering() { state.height = std::min(state.height, image.info.size.height); const bool is_clear = texture_cache.IsMetaCleared(col_buf.CmaskAddress()); + state.color_images[state.num_color_attachments] = image.image; state.color_attachments[state.num_color_attachments++] = { .imageView = *image_view.image_view, .imageLayout = vk::ImageLayout::eGeneral, @@ -152,6 +153,7 @@ void Rasterizer::BeginRendering() { const auto& image = texture_cache.GetImage(image_view.image_id); state.width = std::min(state.width, image.info.size.width); state.height = std::min(state.height, image.info.size.height); + state.depth_image = image.image; state.depth_attachment = { .imageView = *image_view.image_view, .imageLayout = image.layout, diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index e7b12d49a..fb64285f1 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -50,7 +50,32 @@ void Scheduler::EndRendering() { return; } is_rendering = false; + boost::container::static_vector barriers; + for (size_t i = 0; i < render_state.num_color_attachments; ++i) { + barriers.push_back(vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = render_state.color_images[i], + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); + } current_cmdbuf.endRendering(); + if (!barriers.empty()) { + current_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eFragmentShader, + vk::DependencyFlagBits::eByRegion, {}, {}, barriers); + } } void Scheduler::Flush(SubmitInfo& info) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 1e640b083..48c3af7a4 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -15,7 +15,9 @@ class Instance; struct RenderState { std::array color_attachments{}; + std::array color_images{}; vk::RenderingAttachmentInfo depth_attachment{}; + vk::Image depth_image{}; u32 num_color_attachments{}; u32 num_depth_attachments{}; u32 width = std::numeric_limits::max(); diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index ff85a8aa3..04bedaffc 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -47,6 +47,20 @@ vk::ComponentSwizzle ConvertComponentSwizzle(u32 dst_sel) { } } +bool IsIdentityMapping(u32 dst_sel, u32 num_components) { + return (num_components == 1 && dst_sel == 0b100) || + (num_components == 2 && dst_sel == 0b101'100) || + (num_components == 3 && dst_sel == 0b110'101'100) || + (num_components == 4 && dst_sel == 0b111'110'101'100); +} + +vk::Format TrySwizzleFormat(vk::Format format, u32 dst_sel) { + if (format == vk::Format::eR8G8B8A8Unorm && dst_sel == 0b111100101110) { + return vk::Format::eB8G8R8A8Unorm; + } + return format; +} + ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept : is_storage{is_storage} { type = ConvertImageViewType(image.GetType()); @@ -60,9 +74,16 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexce mapping.b = ConvertComponentSwizzle(image.dst_sel_z); mapping.a = ConvertComponentSwizzle(image.dst_sel_w); // Check for unfortunate case of storage images being swizzled - if (is_storage && (mapping != vk::ComponentMapping{})) { - LOG_ERROR(Render_Vulkan, "Storage image requires swizzling"); + const u32 num_comps = AmdGpu::NumComponents(image.GetDataFmt()); + const u32 dst_sel = image.DstSelect(); + if (is_storage && !IsIdentityMapping(dst_sel, num_comps)) { mapping = vk::ComponentMapping{}; + if (auto new_format = TrySwizzleFormat(format, dst_sel); new_format != format) { + format = new_format; + return; + } + LOG_ERROR(Render_Vulkan, "Storage image (num_comps = {}) requires swizzling {}", num_comps, + image.DstSelectName()); } } diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h index 590ac9be6..fbc62db36 100644 --- a/src/video_core/texture_cache/image_view.h +++ b/src/video_core/texture_cache/image_view.h @@ -35,6 +35,8 @@ struct ImageViewInfo { struct Image; +constexpr Common::SlotId NULL_IMAGE_VIEW_ID{0}; + struct ImageView { explicit ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info, Image& image, ImageId image_id, std::optional usage_override = {}); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 9131e6f1f..7b8a55547 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -142,14 +142,14 @@ ImageId TextureCache::FindImage(const ImageInfo& info, bool refresh_on_create) { image_ids.push_back(image_id); }); - ASSERT_MSG(image_ids.size() <= 1, "Overlapping images not allowed!"); + // ASSERT_MSG(image_ids.size() <= 1, "Overlapping images not allowed!"); ImageId image_id{}; if (image_ids.empty()) { image_id = slot_images.insert(instance, scheduler, info); RegisterImage(image_id); } else { - image_id = image_ids[0]; + image_id = image_ids[image_ids.size() > 1 ? 1 : 0]; } Image& image = slot_images[image_id]; @@ -183,12 +183,17 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo } ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { + if (info.guest_address == 0) [[unlikely]] { + return slot_image_views[NULL_IMAGE_VIEW_ID]; + } + const ImageId image_id = FindImage(info); Image& image = slot_images[image_id]; auto& usage = image.info.usage; if (view_info.is_storage) { - image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eShaderWrite); + image.Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); usage.storage = true; } else { const auto new_layout = image.info.IsDepthStencil() @@ -206,7 +211,7 @@ ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info_tmp.range.extent.levels > image.info.resources.levels || view_info_tmp.range.extent.layers > image.info.resources.layers) { - LOG_ERROR(Render_Vulkan, + LOG_DEBUG(Render_Vulkan, "Subresource range ({}~{},{}~{}) exceeds base image extents ({},{})", view_info_tmp.range.base.level, view_info_tmp.range.extent.levels, view_info_tmp.range.base.layer, view_info_tmp.range.extent.layers, @@ -341,7 +346,7 @@ void TextureCache::RefreshImage(Image& image) { cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead); } vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) { From d7acb93d6f3fb4f91620067a43ba9a2e86c8b54f Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Tue, 30 Jul 2024 22:55:58 -0600 Subject: [PATCH 031/109] - Implemented sceSaveDataSetParam and sceSaveDataGetParam - Fixed sceSaveDataDirNameSearch --- src/core/libraries/save_data/savedata.cpp | 158 +++++++++++++++++----- src/core/libraries/save_data/savedata.h | 11 +- 2 files changed, 131 insertions(+), 38 deletions(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index a8519ab79..64237994e 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -15,7 +15,7 @@ #include "error_codes.h" namespace Libraries::SaveData { - +bool is_rw_mode = false; static constexpr std::string_view g_mount_point = "/savedata0"; // temp mount point (todo) std::string game_serial; @@ -180,25 +180,27 @@ int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* OrbisSaveDataDirNameSearchResult* result) { if (cond == nullptr) return ORBIS_SAVE_DATA_ERROR_PARAMETER; - LOG_ERROR(Lib_SaveData, "TODO sceSaveDataDirNameSearch: Add params"); + LOG_INFO(Lib_SaveData, "called"); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(cond->userId) / game_serial; if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { - if (cond->dirName == nullptr) { // look for all dirs if no dir is provided. + if (cond->dirName == nullptr || std::string_view(cond->dirName->data) + .empty()) { // look for all dirs if no dir is provided. for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) { if (std::filesystem::is_directory(entry.path()) && entry.path().filename().string() != "sdmemory") { // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. - i++; - result->dirNamesNum = 0; // why is it 1024? is it max? // copy dir name to be used by sceSaveDataMount in read mode. strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32); - result->hitNum = i + 1; - result->dirNamesNum = i + 1; - result->setNum = i + 1; + i++; + result->hitNum = i; + result->dirNamesNum = i; + result->setNum = i; } } } else { // Need a game to test. + LOG_ERROR(Lib_SaveData, "Check Me. sceSaveDataDirNameSearch: dirName = {}", + cond->dirName->data); strncpy(result->dirNames[0].data, cond->dirName->data, 32); result->hitNum = 1; result->dirNamesNum = 1; @@ -305,8 +307,51 @@ int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoi return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetParam() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); +int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataParamType paramType, void* paramBuf, + const size_t paramBufSize, size_t* gotSize) { + + if (mountPoint == nullptr) + return ORBIS_SAVE_DATA_ERROR_PARAMETER; + + auto* mnt = Common::Singleton::Instance(); + const auto mount_dir = mnt->GetHostPath(mountPoint->data); + Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); + OrbisSaveDataParam params; + file.Read(params); + + LOG_INFO(Lib_SaveData, "called"); + + switch (paramType) { + case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { + memcpy(paramBuf, ¶ms, sizeof(OrbisSaveDataParam)); + *gotSize = sizeof(OrbisSaveDataParam); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { + std::memcpy(paramBuf, ¶ms.title, ORBIS_SAVE_DATA_TITLE_MAXSIZE); + *gotSize = ORBIS_SAVE_DATA_TITLE_MAXSIZE; + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { + std::memcpy(paramBuf, ¶ms.subTitle, ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE); + *gotSize = ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE; + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { + std::memcpy(paramBuf, ¶ms.detail, ORBIS_SAVE_DATA_DETAIL_MAXSIZE); + *gotSize = ORBIS_SAVE_DATA_DETAIL_MAXSIZE; + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { + std::memcpy(paramBuf, ¶ms.userParam, sizeof(u32)); + *gotSize = sizeof(u32); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { + std::memcpy(paramBuf, ¶ms.mtime, sizeof(time_t)); + *gotSize = sizeof(time_t); + } break; + default: { + UNREACHABLE_MSG("Unknown Param = {}", paramType); + } break; + } + return ORBIS_OK; } @@ -445,6 +490,7 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: { + is_rw_mode = (mount_mode == ORBIS_SAVE_DATA_MOUNT_MODE_RDWR) ? true : false; if (!std::filesystem::exists(mount_dir)) { return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; } @@ -462,10 +508,6 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { if (std::filesystem::exists(mount_dir)) { - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->required_blocks = 0; - mount_result->mount_status = 0; return ORBIS_SAVE_DATA_ERROR_EXISTS; } if (std::filesystem::create_directories(mount_dir)) { @@ -485,7 +527,7 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, mount_result->mount_status = 1; } break; default: - UNREACHABLE(); + UNREACHABLE_MSG("Unknown mount mode = {}", mount_mode); } mount_result->required_blocks = 0; @@ -585,15 +627,46 @@ int PS4_SYSV_ABI sceSaveDataSetEventInfo() { int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataParamType paramType, const void* paramBuf, size_t paramBufSize) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called = {}, mountPoint->data = {}", mount_dir.string(), - mountPoint->data); + if (paramBuf == nullptr) + return ORBIS_SAVE_DATA_ERROR_PARAMETER; - if (paramBuf != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Write); - file.WriteRaw(paramBuf, paramBufSize); + auto* mnt = Common::Singleton::Instance(); + const auto mount_dir = mnt->GetHostPath(mountPoint->data) / "param.txt"; + OrbisSaveDataParam params; + if (std::filesystem::exists(mount_dir)) { + Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); + file.ReadRaw(¶ms, sizeof(OrbisSaveDataParam)); } + + LOG_INFO(Lib_SaveData, "called"); + + switch (paramType) { + case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { + memcpy(¶ms, paramBuf, sizeof(OrbisSaveDataParam)); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { + strncpy(params.title, static_cast(paramBuf), paramBufSize); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { + strncpy(params.subTitle, static_cast(paramBuf), paramBufSize); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { + strncpy(params.detail, static_cast(paramBuf), paramBufSize); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { + params.userParam = *(static_cast(paramBuf)); + } break; + case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { + params.mtime = *(static_cast(paramBuf)); + } break; + default: { + UNREACHABLE_MSG("Unknown Param = {}", paramType); + } + } + + Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); + file.WriteRaw(¶ms, sizeof(OrbisSaveDataParam)); + return ORBIS_OK; } @@ -610,7 +683,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); file.Seek(offset); - file.WriteRaw((void*)buf, bufSize); + file.WriteRaw(buf, bufSize); return ORBIS_OK; } @@ -624,7 +697,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* se if (!file.IsOpen()) return -1; file.Seek(setParam->data->offset); - file.WriteRaw((void*)setParam->data->buf, setParam->data->bufSize); + file.WriteRaw(setParam->data->buf, setParam->data->bufSize); } if (setParam->param != nullptr) { @@ -634,7 +707,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* se if (setParam->icon != nullptr) { Common::FS::IOFile file(mount_dir / "save_icon.png", Common::FS::FileAccessMode::Write); - file.WriteRaw((void*)setParam->icon->buf, setParam->icon->bufSize); + file.WriteRaw(setParam->icon->buf, setParam->icon->bufSize); } return ORBIS_OK; @@ -719,16 +792,17 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { } s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { + LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data)); if (std::string(mountPoint->data).empty()) { return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; } const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(1) / game_serial / mountPoint->data; auto* mnt = Common::Singleton::Instance(); - + const auto& guest_path = mnt->GetHostPath(mountPoint->data); + if (guest_path.empty()) + return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; mnt->Unmount(mount_dir, mountPoint->data); - LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data)); - return ORBIS_OK; } @@ -738,23 +812,33 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() { } int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { - LOG_ERROR(Lib_SaveData, "called = {}", std::string(mountPoint->data)); + LOG_INFO(Lib_SaveData, "called mount = {}, is_rw_mode = {}", std::string(mountPoint->data), + is_rw_mode); auto* mnt = Common::Singleton::Instance(); const auto mount_dir = mnt->GetHostPath(mountPoint->data); if (!std::filesystem::exists(mount_dir)) { return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; } + // leave disabled for now. and just unmount. - std::filesystem::create_directories(mount_dir.parent_path() / "backup"); + /* if (is_rw_mode) { // backup is done only when mount mode is ReadWrite. + auto backup_path = mount_dir; + std::string save_data_dir = (mount_dir.string() + "_backup"); + backup_path.replace_filename(save_data_dir); - for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) { - const auto& path = entry.path(); - const auto target_path = mount_dir.parent_path() / "backup"; - if (std::filesystem::is_regular_file(path)) { - std::filesystem::copy(path, target_path, - std::filesystem::copy_options::overwrite_existing); + std::filesystem::create_directories(backup_path); + + for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) { + const auto& path = entry.path(); + if (std::filesystem::is_regular_file(path)) { + std::filesystem::copy(path, save_data_dir, + std::filesystem::copy_options::overwrite_existing); + } } - } + }*/ + const auto& guest_path = mnt->GetHostPath(mountPoint->data); + if (guest_path.empty()) + return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; mnt->Unmount(mount_dir, mountPoint->data); return ORBIS_OK; diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index f342d0ddb..9b3cf900f 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -242,6 +242,13 @@ struct OrbisSaveDataMemorySync { u8 reserved[28]; }; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4; +constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5; + int PS4_SYSV_ABI sceSaveDataAbort(); int PS4_SYSV_ABI sceSaveDataBackup(); int PS4_SYSV_ABI sceSaveDataBindPsnAccount(); @@ -291,7 +298,9 @@ int PS4_SYSV_ABI sceSaveDataGetFormat(); int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount(); int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, OrbisSaveDataMountInfo* info); -int PS4_SYSV_ABI sceSaveDataGetParam(); +int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataParamType paramType, void* paramBuf, + const size_t paramBufSize, size_t* gotSize); int PS4_SYSV_ABI sceSaveDataGetProgress(); int PS4_SYSV_ABI sceSaveDataGetSaveDataCount(); int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, From d017bab21ec15e60bb011ac6c2fe369a6db22d9e Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 31 Jul 2024 00:21:23 -0600 Subject: [PATCH 032/109] Kernel: added sceKernelConvertLocaltimeToUtc --- src/core/libraries/kernel/time_management.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index bc1617d36..c4854937b 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -214,6 +214,22 @@ int PS4_SYSV_ABI posix_clock_getres(u32 clock_id, OrbisKernelTimespec* res) { return SCE_KERNEL_ERROR_EINVAL; } +int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, + OrbisKernelTimezone* timezone, int* dst_seconds) { + LOG_INFO(Kernel, "called"); + if (timezone) { + sceKernelGettimezone(timezone); + param_1 -= (timezone->tz_minuteswest + timezone->tz_dsttime) * 60; + if (seconds) + *seconds = param_1; + if (dst_seconds) + *dst_seconds = timezone->tz_dsttime * 60; + } else { + return SCE_KERNEL_ERROR_EINVAL; + } + return SCE_OK; +} + void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) { clock = std::make_unique(); initial_ptc = clock->GetUptime(); @@ -239,6 +255,7 @@ void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) { 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", "libScePosix", 1, "libkernel", 1, 1, posix_clock_getres); + LIB_FUNCTION("0NTHN1NKONI", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertLocaltimeToUtc); } } // namespace Libraries::Kernel From 51c89a9958fc357d1aa4b27ded6f0c03df53f5bf Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 31 Jul 2024 00:32:50 -0600 Subject: [PATCH 033/109] added data_format=10 and num_format=5 --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index e7c39de05..ea6ab4a5f 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -542,6 +542,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu if (data_format == AmdGpu::DataFormat::FormatBc6 && num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eBc6HUfloatBlock; } + if (data_format == AmdGpu::DataFormat::Format8_8_8_8 && + num_format == AmdGpu::NumberFormat::Sint) { + return vk::Format::eR8G8B8A8Sint; + } UNREACHABLE_MSG("Unknown data_format={} and num_format={}", u32(data_format), u32(num_format)); } From ec1335911b85664256ad72cbb686648730ee0b2a Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 31 Jul 2024 01:24:44 -0600 Subject: [PATCH 034/109] added data_format=1 and num_format=9 --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index ea6ab4a5f..bdb0ed95e 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -546,6 +546,9 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::Sint) { return vk::Format::eR8G8B8A8Sint; } + if (data_format == AmdGpu::DataFormat::Format8 && num_format == AmdGpu::NumberFormat::Srgb) { + return vk::Format::eR8Srgb; + } UNREACHABLE_MSG("Unknown data_format={} and num_format={}", u32(data_format), u32(num_format)); } From 413402600b20c2faad278fce1dd563e9a226ca54 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Wed, 31 Jul 2024 02:07:02 -0600 Subject: [PATCH 035/109] missing eR8Srgb --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index bdb0ed95e..e45391230 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -317,6 +317,7 @@ std::span GetAllFormats() { vk::Format::eR8Sint, vk::Format::eR8Uint, vk::Format::eR8Unorm, + vk::Format::eR8Srgb, vk::Format::eR16G16B16A16Sfloat, vk::Format::eR16G16B16A16Sint, vk::Format::eR16G16B16A16Snorm, From a7f2f09a446b124e1fd9b3079b0ea6588c8270dc Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:25:55 +0200 Subject: [PATCH 036/109] Reorganization of includes (#348) * Reorganization of includes * fix --- src/audio_core/sdl_audio.cpp | 4 ++-- src/emulator.cpp | 14 ++++---------- src/emulator.h | 2 +- src/input/controller.cpp | 1 + src/main.cpp | 2 +- src/qt_gui/elf_viewer.cpp | 1 + src/qt_gui/elf_viewer.h | 2 +- src/qt_gui/main.cpp | 5 ++--- src/qt_gui/main_window.h | 2 +- 9 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 0d494707d..141d338e8 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include "common/assert.h" +#include "core/libraries/error_codes.h" #include "sdl_audio.h" namespace Audio { diff --git a/src/emulator.cpp b/src/emulator.cpp index 5b162e056..374e0e84d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,14 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include -#include -#include -#include -#include +#include + #include "common/config.h" #include "common/debug.h" #include "common/logging/backend.h" @@ -18,6 +12,7 @@ #include "common/polyfill_thread.h" #include "common/singleton.h" #include "common/version.h" +#include "core/file_format/playgo_chunk.h" #include "core/file_format/psf.h" #include "core/file_format/splash.h" #include "core/file_sys/fs.h" @@ -27,13 +22,12 @@ #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/rtc/rtc.h" +#include "core/libraries/videoout/video_out.h" #include "core/linker.h" #include "core/memory.h" #include "emulator.h" #include "video_core/renderdoc.h" -#include - Frontend::WindowSDL* g_window = nullptr; namespace Core { diff --git a/src/emulator.h b/src/emulator.h index 323170e38..01bce7e7c 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -6,7 +6,7 @@ #include #include -#include +#include "common/singleton.h" #include "core/linker.h" #include "input/controller.h" #include "sdl_window.h" diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 7bfecadc8..247e08ce8 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -4,6 +4,7 @@ #include "core/libraries/kernel/time_management.h" #include "core/libraries/pad/pad.h" #include "input/controller.h" + namespace Input { GameController::GameController() { diff --git a/src/main.cpp b/src/main.cpp index c7210ac57..9df14f138 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include +#include "emulator.h" int main(int argc, char* argv[]) { if (argc == 1) { diff --git a/src/qt_gui/elf_viewer.cpp b/src/qt_gui/elf_viewer.cpp index 1674e1ab8..47a25914c 100644 --- a/src/qt_gui/elf_viewer.cpp +++ b/src/qt_gui/elf_viewer.cpp @@ -3,6 +3,7 @@ #include #include "elf_viewer.h" + ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { dir_list_std = Config::getElfViewer(); for (const auto& str : dir_list_std) { diff --git a/src/qt_gui/elf_viewer.h b/src/qt_gui/elf_viewer.h index 15aeb55fd..fbf14d30d 100644 --- a/src/qt_gui/elf_viewer.h +++ b/src/qt_gui/elf_viewer.h @@ -14,8 +14,8 @@ #include #include #include +#include "core/loader/elf.h" #include "game_list_frame.h" -#include "src/core/loader/elf.h" class ElfViewer : public QTableWidget { Q_OBJECT diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index ea3f27f82..15a06c867 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -2,15 +2,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/config.h" #include "core/file_sys/fs.h" +#include "emulator.h" #include "qt_gui/game_install_dialog.h" #include "qt_gui/main_window.h" -#include -#include - // Custom message handler to ignore Qt logs void customMessageHandler(QtMsgType, const QMessageLogContext&, const QString&) {} diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 27d14b937..a70137382 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -9,13 +9,13 @@ #include #include #include -#include #include #include "common/config.h" #include "common/path_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "elf_viewer.h" +#include "emulator.h" #include "game_grid_frame.h" #include "game_info.h" #include "game_list_frame.h" From 0fe766db6cf9b3c294a621a6cbe743cc99c47ad0 Mon Sep 17 00:00:00 2001 From: Dzmitry Dubrova Date: Wed, 31 Jul 2024 14:01:22 +0300 Subject: [PATCH 037/109] core: Implement sceRandomGetRandomNumber (#350) --- CMakeLists.txt | 5 +++++ src/common/logging/types.h | 1 + src/core/libraries/error_codes.h | 3 +++ src/core/libraries/libs.cpp | 2 ++ src/core/libraries/random/random.cpp | 27 +++++++++++++++++++++++++++ src/core/libraries/random/random.h | 17 +++++++++++++++++ 6 files changed, 55 insertions(+) create mode 100644 src/core/libraries/random/random.cpp create mode 100644 src/core/libraries/random/random.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 60dad68aa..49376076e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,6 +222,10 @@ set(PLAYGO_LIB src/core/libraries/playgo/playgo.cpp src/core/libraries/playgo/playgo_types.h ) +set(RANDOM_LIB src/core/libraries/random/random.cpp + src/core/libraries/random/random.h +) + set(USBD_LIB src/core/libraries/usbd/usbd.cpp src/core/libraries/usbd/usbd.h ) @@ -334,6 +338,7 @@ set(CORE src/core/aerolib/stubs.cpp ${NP_LIBS} ${PNG_LIB} ${PLAYGO_LIB} + ${RANDOM_LIB} ${USBD_LIB} ${MISC_LIBS} ${DIALOGS_LIB} diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 33e52fd4c..dccb838a5 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -73,6 +73,7 @@ enum class Class : u8 { Lib_DiscMap, ///< The LibSceDiscMap implementation. Lib_Png, ///< The LibScePng implementation. Lib_PlayGo, ///< The LibScePlayGo implementation. + Lib_Random, ///< The libSceRandom implementation. Lib_Usbd, ///< The LibSceUsbd implementation. Lib_Ajm, ///< The LibSceAjm implementation. Lib_ErrorDialog, ///< The LibSceErrorDialog implementation. diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 63016213d..5eabaaf67 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -233,6 +233,9 @@ constexpr int SCE_KERNEL_ERROR_ESDKVERSION = 0x80020063; constexpr int SCE_KERNEL_ERROR_ESTART = 0x80020064; constexpr int SCE_KERNEL_ERROR_ESTOP = 0x80020065; +// libSceRandom error codes +constexpr int SCE_RANDOM_ERROR_INVALID = 0x817C0016; + // videoOut constexpr int SCE_VIDEO_OUT_ERROR_INVALID_VALUE = 0x80290001; // invalid argument constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS = 0x80290002; // invalid addresses diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 47073b2c6..20efd3c0e 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -20,6 +20,7 @@ #include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/pad/pad.h" #include "core/libraries/playgo/playgo.h" +#include "core/libraries/random/random.h" #include "core/libraries/rtc/rtc.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" @@ -71,6 +72,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::AppContent::RegisterlibSceAppContent(sym); Libraries::PngDec::RegisterlibScePngDec(sym); Libraries::PlayGo::RegisterlibScePlayGo(sym); + Libraries::Random::RegisterlibSceRandom(sym); Libraries::Usbd::RegisterlibSceUsbd(sym); Libraries::Pad::RegisterlibScePad(sym); Libraries::Ajm::RegisterlibSceAjm(sym); diff --git a/src/core/libraries/random/random.cpp b/src/core/libraries/random/random.cpp new file mode 100644 index 000000000..8147c5183 --- /dev/null +++ b/src/core/libraries/random/random.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "random.h" + +namespace Libraries::Random { + +s32 PS4_SYSV_ABI sceRandomGetRandomNumber(u8* buf, size_t size) { + LOG_TRACE(Lib_Random, "called"); + if (size > SCE_RANDOM_MAX_SIZE) { + return SCE_RANDOM_ERROR_INVALID; + } + + for (auto i = 0; i < size; ++i) { + buf[i] = std::rand() & 0xFF; + } + return ORBIS_OK; +} + +void RegisterlibSceRandom(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("PI7jIZj4pcE", "libSceRandom", 1, "libSceRandom", 1, 1, sceRandomGetRandomNumber); +}; + +} // namespace Libraries::Random diff --git a/src/core/libraries/random/random.h b/src/core/libraries/random/random.h new file mode 100644 index 000000000..b5f87f877 --- /dev/null +++ b/src/core/libraries/random/random.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Random { +constexpr int32_t SCE_RANDOM_MAX_SIZE = 64; + +s32 PS4_SYSV_ABI sceRandomGetRandomNumber(u8* buf, size_t size); + +void RegisterlibSceRandom(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Random \ No newline at end of file From 205c0b961b09845cfff5cfc652cf1b402b361185 Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:43:30 +0200 Subject: [PATCH 038/109] Adding macOS to readme + minor changes --- README.md | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cf7d9f9b7..7089b3c01 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later # shadPS4 -shadPS4 is an early PS4 emulator for Windows and Linux written in C++ +shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++ If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md). @@ -42,13 +42,13 @@ To verify that a game works, you can look at [**shadPS4 Game Compatibility**](ht To discuss shadPS4 development or suggest ideas, join the [**Discord server**](https://discord.gg/MyZRaBngxA). -Check us on [**X (twitter)**](https://x.com/shadps4) or on our [**website**](https://shadps4.net/). +To get the latest news, go to our [**X (twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/). # Status In development, small games are working like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4), [**Dysmantle**](https://youtu.be/b9xzhLBdESE) and others... -# Why? +# Why The project started as a fun project. Due to limited free time, it will probably take a while before shadPS4 is able to run anything decent, but we're trying to make small, regular commits. @@ -64,20 +64,37 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad ## Build status -|Windows|Build status| -|--------|------------| +
+Windows + +| Windows | Build status | +|--------|--------| |Windows SDL Build|[![Windows-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml) |Windows Qt Build|[![Windows-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml) +
-|Linux|Build status| -|--------|------------| +
+Linux + +| Linux | Build status | +|--------|--------| |Linux SDL Build|[![Linux-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml) |Linux Qt Build|[![Linux-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml) +
+ +
+macOS + +| macOS | Build status | +|--------|--------| +|macOS SDL Build|[![macOS-sdl](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml) +|macOS Qt Build|[![macOS-qt](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml/badge.svg)](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml) +
# Keyboard Mapping | Controller button | Keyboard | -| ------------- | ------------- | +|-------------|-------------| LEFT AXIS UP | W | LEFT AXIS DOWN | S | LEFT AXIS LEFT | A | @@ -123,7 +140,7 @@ Open a PR and we'll check it :) # Contributors - + # Sister Projects From 9968d03b90939a74e24e44219b82a38d33ce27d7 Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:14:22 +0200 Subject: [PATCH 039/109] Better logo for shadPS4 --- .github/shadps4.png | Bin 201838 -> 133377 bytes src/images/shadps4.ico | Bin 270398 -> 74814 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/shadps4.png b/.github/shadps4.png index 86fa592d57c986ac4ba3b0aeecaf78562bc7e149..7ad301d4c66869ba0dd40378ca461ab5bc5f67d8 100644 GIT binary patch literal 133377 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�_ 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,x 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 literal 201838 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 z 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 diff --git a/src/images/shadps4.ico b/src/images/shadps4.ico index 4f71b2341e2945e944cedbba16deaa088c5c0e71..bb50f9995ac82ebe259df8199ea9a805b35df411 100644 GIT binary patch literal 74814 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 JGm5~z0|545Addh5 literal 270398 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

5Db|gK(q}&g7iS< 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 Z(GVC7fzc2c4S~@R7!85Z5EyzP004?SZsPy| From 62e501f31fe8ab920abb437591514656a88f2882 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 1 Aug 2024 00:56:10 +0300 Subject: [PATCH 040/109] print git info in windows title --- .gitignore | 1 + CMakeLists.txt | 11 ++ .../GetGitRevisionDescription.cmake | 158 ++++++++++++++++++ .../GetGitRevisionDescription.cmake.in | 42 +++++ src/common/scm_rev.cpp.in | 17 ++ src/common/scm_rev.h | 12 ++ src/emulator.cpp | 6 +- src/sdl_window.cpp | 5 +- src/sdl_window.h | 2 +- 9 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 externals/cmake-modules/GetGitRevisionDescription.cmake create mode 100644 externals/cmake-modules/GetGitRevisionDescription.cmake.in create mode 100644 src/common/scm_rev.cpp.in create mode 100644 src/common/scm_rev.h diff --git a/.gitignore b/.gitignore index fcf7634f4..2a3145085 100644 --- a/.gitignore +++ b/.gitignore @@ -408,3 +408,4 @@ FodyWeavers.xsd /emulator/eboot.bin /out/* /third-party/out/* +/src/common/scm_rev.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 49376076e..2901cb2f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,15 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +# generate git revision information +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules/") +include(GetGitRevisionDescription) +get_git_head_revision(GIT_REF_SPEC GIT_REV) +git_describe(GIT_DESC --always --long --dirty) +git_branch_name(GIT_BRANCH) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp" @ONLY) + find_package(Boost 1.84.0 CONFIG) find_package(cryptopp 8.9.0 MODULE) find_package(fmt 10.2.1 CONFIG) @@ -288,6 +297,8 @@ set(COMMON src/common/logging/backend.cpp src/common/version.h src/common/ntapi.h src/common/ntapi.cpp + src/common/scm_rev.cpp + src/common/scm_rev.h ) set(CORE src/core/aerolib/stubs.cpp diff --git a/externals/cmake-modules/GetGitRevisionDescription.cmake b/externals/cmake-modules/GetGitRevisionDescription.cmake new file mode 100644 index 000000000..087f5deea --- /dev/null +++ b/externals/cmake-modules/GetGitRevisionDescription.cmake @@ -0,0 +1,158 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_branch_name _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + #get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + #if(NOT hash) + # set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + # return() + #endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() diff --git a/externals/cmake-modules/GetGitRevisionDescription.cmake.in b/externals/cmake-modules/GetGitRevisionDescription.cmake.in new file mode 100644 index 000000000..0d7eb3c26 --- /dev/null +++ b/externals/cmake-modules/GetGitRevisionDescription.cmake.in @@ -0,0 +1,42 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + if(EXISTS "@GIT_DATA@/head-ref") + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) + else() + set(HEAD_HASH "Unknown") + endif() +endif() diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in new file mode 100644 index 000000000..7f6fba9ed --- /dev/null +++ b/src/common/scm_rev.cpp.in @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scm_rev.h" + +#define GIT_REV "@GIT_REV@" +#define GIT_BRANCH "@GIT_BRANCH@" +#define GIT_DESC "@GIT_DESC@" + +namespace Common { + +const char g_scm_rev[] = GIT_REV; +const char g_scm_branch[] = GIT_BRANCH; +const char g_scm_desc[] = GIT_DESC; + +} // namespace + diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h new file mode 100644 index 000000000..877a01272 --- /dev/null +++ b/src/common/scm_rev.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common { + +extern const char g_scm_rev[]; +extern const char g_scm_branch[]; +extern const char g_scm_desc[]; + +} // namespace Common diff --git a/src/emulator.cpp b/src/emulator.cpp index 374e0e84d..41b3b5766 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -27,6 +27,7 @@ #include "core/memory.h" #include "emulator.h" #include "video_core/renderdoc.h" +#include "src/common/scm_rev.h" Frontend::WindowSDL* g_window = nullptr; @@ -100,10 +101,11 @@ void Emulator::Run(const std::filesystem::path& file) { } } } + std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); - + const std::string window_title = fmt::format("shadPS4 v{} {}| {}", Common::VERSION, Common::g_scm_desc,game_title); window = - std::make_unique(WindowWidth, WindowHeight, controller, game_title); + std::make_unique(WindowWidth, WindowHeight, controller, window_title); g_window = window.get(); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 2da246107..98c883ee1 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -20,16 +20,15 @@ namespace Frontend { WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, - std::string_view game_title) + std::string_view window_title) : width{width_}, height{height_}, controller{controller_} { if (SDL_Init(SDL_INIT_VIDEO) < 0) { UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); } SDL_InitSubSystem(SDL_INIT_AUDIO); - const std::string title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, std::string(window_title).c_str()); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); diff --git a/src/sdl_window.h b/src/sdl_window.h index 89b2a8771..02d011285 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -42,7 +42,7 @@ struct WindowSystemInfo { class WindowSDL { public: explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, - std::string_view game_title); + std::string_view window_title); ~WindowSDL(); s32 getWidth() const { From bd48e24c321637b083c43929225fe1f135b9cd40 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 1 Aug 2024 01:11:58 +0300 Subject: [PATCH 041/109] print git info to log as well --- .reuse/dep5 | 1 + src/common/version.h | 1 + src/emulator.cpp | 15 ++++++++++++--- src/sdl_window.cpp | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index c467a1647..5f081569b 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -16,6 +16,7 @@ Files: CMakeSettings.json documents/Screenshots/We are DOOMED.png externals/stb_image.h externals/tracy/* + externals/cmake-modules/* scripts/ps4_names.txt src/images/controller_icon.png src/images/exit_icon.png diff --git a/src/common/version.h b/src/common/version.h index 8492c7d97..92fd18fb2 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -9,5 +9,6 @@ namespace Common { constexpr char VERSION[] = "0.1.1 WIP"; +constexpr bool isRelease = false; } // namespace Common diff --git a/src/emulator.cpp b/src/emulator.cpp index 41b3b5766..a34ee359b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -26,8 +26,8 @@ #include "core/linker.h" #include "core/memory.h" #include "emulator.h" -#include "video_core/renderdoc.h" #include "src/common/scm_rev.h" +#include "video_core/renderdoc.h" Frontend::WindowSDL* g_window = nullptr; @@ -50,6 +50,9 @@ Emulator::Emulator() { Common::Log::Initialize(); Common::Log::Start(); LOG_INFO(Loader, "Starting shadps4 emulator v{} ", Common::VERSION); + LOG_INFO(Loader, "Revision {}", Common::g_scm_rev); + LOG_INFO(Loader, "Branch {}", Common::g_scm_branch); + LOG_INFO(Loader, "Description {}", Common::g_scm_desc); // Defer until after logging is initialized. memory = Core::Memory::Instance(); @@ -101,9 +104,15 @@ void Emulator::Run(const std::filesystem::path& file) { } } } - + std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); - const std::string window_title = fmt::format("shadPS4 v{} {}| {}", Common::VERSION, Common::g_scm_desc,game_title); + std::string window_title = ""; + if (Common::isRelease) { + window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); + } else { + window_title = + fmt::format("shadPS4 v{} {} | {}", Common::VERSION, Common::g_scm_desc, game_title); + } window = std::make_unique(WindowWidth, WindowHeight, controller, window_title); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 98c883ee1..0d25cd3ff 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -28,7 +28,8 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_InitSubSystem(SDL_INIT_AUDIO); SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, std::string(window_title).c_str()); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, + std::string(window_title).c_str()); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); From 56b362bb24a17f35352c6b120b9e94e21e7e3f43 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:36:33 -0700 Subject: [PATCH 042/109] Add macOS icon. --- CMakeLists.txt | 7 ++++++- src/images/shadPS4.icns | Bin 0 -> 768841 bytes 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/images/shadPS4.icns diff --git a/CMakeLists.txt b/CMakeLists.txt index 2901cb2f2..90ba4d83a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -553,6 +553,7 @@ if (ENABLE_QT_GUI) ${SHADER_RECOMPILER} ${VIDEO_CORE} ${EMULATOR} + src/images/shadPS4.icns ) else() add_executable(shadps4 @@ -640,5 +641,9 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES # WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON) + MACOSX_BUNDLE ON + MACOSX_BUNDLE_ICON_FILE shadPS4.icns) + + set_source_files_properties(src/images/shadPS4.icns PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) endif() diff --git a/src/images/shadPS4.icns b/src/images/shadPS4.icns new file mode 100644 index 0000000000000000000000000000000000000000..0e26368dbb2dda958383230e592e36f62f289b27 GIT binary patch literal 768841 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 @z*b9`r`R&GrvE7GDFJF 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

W%gP#EkWO*njPcYP|y4b^p;W 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

HpeW@zDM6{n+?p$_-UQ2g0}9xwWnKOOc|s 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

Y+ku}&k072rwNk` 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 z z$LKw8ets#xb~7mcq;*4s*%E7pUt%SZ=J$i5g?G-^Z=3h)!}BR$wp8Cba{YLxI8(V6 zlW%wGN&DsgrKLr4dv7eayV88^=AQXKVjCLFj_giQ%PHdc`z-O~yvzKn9#1I#w`J*; z&U`8DNRBtAEVINeO{y2JJ!iiB^XEnL@BZ$(pURQ3c+csy6Jk8+7b>Hkho?UlVE?!< z-^*Nh_OnSkEbl~E%%XjMAI-nUH|O`$9l_6z&if&IOW=Ly%@~`U_+MY8WA)6W!ll~3 zI5}`U5xn7{cyHZ|KOIZ&mU280-JDe|%qaSLujT!(tKajmm<64AUH3EW`Rdo++hUs< zx(?{g6x>}R5oK5QvghK476)gRkO$IC+r;l|ZM@H2T$x_Kc-qFfN8|V2zsIqn;QqbO z{)@$#K;1${MFp0Y1_6Ol&R`9Lg5QcC>i4J@>j%3Un=vpjFnGH9xvXkOG;{z`THwff+2q$iT3G2`;O!fEmFCsa(^R^qGNy zy&W`7AHn%HEHfwMy6C@mwfSM|bK(rm9xxnyBRj{cutnC%CHPiIr&OIn=uMYZ3ZkJ? zxqMgN^737*6ZBv4DgP>mi6_*wn>0FfcnuZTajHI$Xl!P#VBXVr{9DiQy7%{L%8#A4 z39j32U$n&f{XXmW^FH4#-dB8%wQj=yjqFG6zP)}k;=2P&K&ViaT|2+P%lEAUHP7$x zC_8EMX{6d+j4^*d`Oqi+U&Y0Gs(!M6xh%|Q{=4=&yE=aPy=(s#xh{fb8P-QmnI354IP-#@0@VQP6!*Fa zy+C}rlbKAL#TWh#V|i!$2>$G^RUFq_ZhqtWAS*0Ye>b*!v->>9#JCB+r!z&~bT}NC zut5BVm%+aW{Ra9Gwz+p~_A?}W)&ckePfoOMq$ zG%#Z-EoQKq!kqhiA=AwZ_v2VMRW5oH^KLn#l$pa9mH^?%p61{3j_LmTB9;w%Y7#tI zzbqAd<|mP*z;^Km=NIO!8_Zpw-4|`Ra)`fsi-FAIqRBZg6R-K)*{Qd7W~srX)TKKs zzE3?~@Z?9OjNLcWrfm%?Vi_*ZB{ZBPG>m1+^a(R`uEvHN z$v*hXz$|yQjA2zWOVJmelCM@Te6n6D*0+COf+P9{H_kYXR&DwECA|$16-r`DoXt8FSw}9}PY0b(7)sJL4wX{_ZQUN^jKP&j<^CnkICsGpS~WX#D^5i0pfS*gh-#X<~>Z42TB`2B*tQ!7(b9HELXa&U$e<>!|)FfQlj5topZk_%PI zE6vHd%*)3wAS@Q{7p#|^T3HrN5zz*g2G$0)ki5j) z)OZ20#Ny)AlH|ma#GL$e-J;aA)S}e9cvE@C)__dw@ewN=4Ji+|ws8I65(-pt`BGy|brp!ldaN gw(i-x@4(RuH=ltTL!fpJBPhfe7#LKbG>rNL0K}#BivR!s literal 0 HcmV?d00001 From be829389ea6aac463e309f4b32c3ac2e7967ba3f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:40:25 -0700 Subject: [PATCH 043/109] Add new icons file to reuse config. --- .reuse/dep5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.reuse/dep5 b/.reuse/dep5 index 5f081569b..cfa8fd090 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -37,6 +37,7 @@ Files: CMakeSettings.json src/images/refresh_icon.png src/images/settings_icon.png src/images/stop_icon.png + src/images/shadPS4.icns src/images/shadps4.ico src/images/themes_icon.png src/shadps4.qrc From 6d0a7631456486bf4fe5a13f2085111e7fdf6f5a Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Fri, 2 Aug 2024 12:57:16 +0000 Subject: [PATCH 044/109] reuse: fix license for externals (#358) --- .reuse/dep5 | 15 ++++++++++++--- LICENSES/BSL-1.0.txt | 7 +++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 LICENSES/BSL-1.0.txt diff --git a/.reuse/dep5 b/.reuse/dep5 index cfa8fd090..a80001f84 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -14,9 +14,6 @@ Files: CMakeSettings.json documents/Screenshots/Sonic Mania.png documents/Screenshots/Undertale.png documents/Screenshots/We are DOOMED.png - externals/stb_image.h - externals/tracy/* - externals/cmake-modules/* scripts/ps4_names.txt src/images/controller_icon.png src/images/exit_icon.png @@ -45,6 +42,18 @@ Files: CMakeSettings.json Copyright: shadPS4 Emulator Project License: GPL-2.0-or-later +Files: externals/cmake-modules/* +Copyright: 2009-2010 Iowa State University +License: BSL-1.0 + Files: externals/renderdoc/* Copyright: 2019-2024 Baldur Karlsson License: MIT + +Files: externals/stb_image.h +Copyright: 2017 Sean Barrett +License: MIT + +Files: externals/tracy/* +Copyright: 2017-2024 Bartosz Taudul +License: BSD-3-Clause diff --git a/LICENSES/BSL-1.0.txt b/LICENSES/BSL-1.0.txt new file mode 100644 index 000000000..2d87ab1a9 --- /dev/null +++ b/LICENSES/BSL-1.0.txt @@ -0,0 +1,7 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 21462523ded65dadad253f232912828080a54991 Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:07:10 +0200 Subject: [PATCH 045/109] Minor Qt GUI update (#363) * Minor Qt GUI update * Update gui_context_menus.h * Update gui_context_menus.h * Update game_info.cpp --- .github/workflows/windows-qt.yml | 4 +++- src/qt_gui/elf_viewer.cpp | 1 + src/qt_gui/elf_viewer.h | 1 + src/qt_gui/game_grid_frame.h | 1 + src/qt_gui/game_info.cpp | 3 ++- src/qt_gui/game_info.h | 1 + src/qt_gui/game_install_dialog.cpp | 10 ++++---- src/qt_gui/game_install_dialog.h | 1 + src/qt_gui/game_list_frame.cpp | 12 +++++----- src/qt_gui/game_list_frame.h | 1 + src/qt_gui/gui_context_menus.h | 14 +++++------ src/qt_gui/main_window.cpp | 1 + src/qt_gui/main_window.h | 1 + src/qt_gui/main_window_themes.cpp | 38 +++++++++++++++--------------- src/qt_gui/main_window_themes.h | 3 ++- src/qt_gui/main_window_ui.h | 16 ++++++------- src/qt_gui/pkg_viewer.cpp | 1 + src/qt_gui/pkg_viewer.h | 1 + src/qt_gui/trophy_viewer.h | 1 + 19 files changed, 63 insertions(+), 48 deletions(-) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 099ece0bc..019a8ab2f 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -29,8 +29,10 @@ jobs: uses: jurplel/install-qt-action@v4 with: version: 6.7.2 + host: windows + target: desktop arch: win64_msvc2019_64 - archives: qtsvg qtbase + archives: qtbase - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/src/qt_gui/elf_viewer.cpp b/src/qt_gui/elf_viewer.cpp index 47a25914c..72861d15f 100644 --- a/src/qt_gui/elf_viewer.cpp +++ b/src/qt_gui/elf_viewer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + #include "elf_viewer.h" ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { diff --git a/src/qt_gui/elf_viewer.h b/src/qt_gui/elf_viewer.h index fbf14d30d..a3b85223d 100644 --- a/src/qt_gui/elf_viewer.h +++ b/src/qt_gui/elf_viewer.h @@ -14,6 +14,7 @@ #include #include #include + #include "core/loader/elf.h" #include "game_list_frame.h" diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 19ac531bc..ce775315e 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -14,6 +14,7 @@ #include #include #include + #include "common/config.h" #include "game_info.h" #include "game_list_utils.h" diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index 39cdeb75a..0a472eaef 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -5,6 +5,7 @@ #include #include #include + #include "game_info.h" GameInfoClass::GameInfoClass() = default; @@ -42,4 +43,4 @@ void GameInfoClass::GetGameInfo(QWidget* parent) { &QProgressDialog::setValue); dialog.exec(); -} \ No newline at end of file +} diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index c137a5a60..b2b102e00 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -7,6 +7,7 @@ #include #include #include + #include "common/config.h" #include "core/file_format/psf.h" #include "game_list_utils.h" diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp index ab4fc2734..4b2b8528b 100644 --- a/src/qt_gui/game_install_dialog.cpp +++ b/src/qt_gui/game_install_dialog.cpp @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "game_install_dialog.h" - #include #include #include @@ -14,6 +12,8 @@ #include #include +#include "game_install_dialog.h" + GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) { auto layout = new QVBoxLayout(this); @@ -21,8 +21,8 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) { layout->addStretch(); layout->addWidget(SetupDialogActions()); - setWindowTitle("Shadps4 - Choose directory"); - setWindowIcon(QIcon(":/images/shadps4.ico")); + setWindowTitle("shadPS4 - Choose directory"); + setWindowIcon(QIcon(":images/shadps4.ico")); } GameInstallDialog::~GameInstallDialog() {} @@ -47,7 +47,7 @@ QWidget* GameInstallDialog::SetupGamesDirectory() { layout->addWidget(m_gamesDirectory); // Browse button. - auto browse = new QPushButton("..."); + auto browse = new QPushButton("Browse"); connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse); diff --git a/src/qt_gui/game_install_dialog.h b/src/qt_gui/game_install_dialog.h index abd447d93..6f439e81d 100644 --- a/src/qt_gui/game_install_dialog.h +++ b/src/qt_gui/game_install_dialog.h @@ -4,6 +4,7 @@ #pragma once #include + #include "common/config.h" #include "common/path_util.h" diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 4353964fe..2699c9615 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -190,17 +190,17 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) { QTableWidgetItem* item = new QTableWidgetItem(); QImage scaledPixmap; if (itemStr == "Japan") { - scaledPixmap = QImage(":/images/flag_jp.png"); + scaledPixmap = QImage(":images/flag_jp.png"); } else if (itemStr == "Europe") { - scaledPixmap = QImage(":/images/flag_eu.png"); + scaledPixmap = QImage(":images/flag_eu.png"); } else if (itemStr == "USA") { - scaledPixmap = QImage(":/images/flag_us.png"); + scaledPixmap = QImage(":images/flag_us.png"); } else if (itemStr == "Asia") { - scaledPixmap = QImage(":/images/flag_china.png"); + scaledPixmap = QImage(":images/flag_china.png"); } else if (itemStr == "World") { - scaledPixmap = QImage(":/images/flag_world.png"); + scaledPixmap = QImage(":images/flag_world.png"); } else { - scaledPixmap = QImage(":/images/flag_unk.png"); + scaledPixmap = QImage(":images/flag_unk.png"); } QWidget* widget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(widget); diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index e9f75afd4..d8bccf466 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -16,6 +16,7 @@ #include #include #include + #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 9f8953607..146d5c34d 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -165,12 +165,12 @@ public: if (createShortcutLinux(linkPath, ebootPath, iconPath)) { #endif QMessageBox::information( - nullptr, "Shortcut Creation", - QString("Shortcut created successfully:\n %1").arg(linkPath)); + nullptr, "Shortcut creation", + QString("Shortcut created successfully!\n %1").arg(linkPath)); } else { QMessageBox::critical( nullptr, "Error", - QString("Error creating shortcut:\n %1").arg(linkPath)); + QString("Error creating shortcut!\n %1").arg(linkPath)); } } else { QMessageBox::critical(nullptr, "Error", "Failed to convert icon."); @@ -183,11 +183,11 @@ public: if (createShortcutLinux(linkPath, ebootPath, iconPath)) { #endif QMessageBox::information( - nullptr, "Shortcut Creation", - QString("Shortcut created successfully:\n %1").arg(linkPath)); + nullptr, "Shortcut creation", + QString("Shortcut created successfully!\n %1").arg(linkPath)); } else { QMessageBox::critical(nullptr, "Error", - QString("Error creating shortcut:\n %1").arg(linkPath)); + QString("Error creating shortcut!\n %1").arg(linkPath)); } } } @@ -308,7 +308,7 @@ private: QFile shortcutFile(linkPath); if (!shortcutFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(nullptr, "Error", - QString("Error creating shortcut:\n %1").arg(linkPath)); + QString("Error creating shortcut!\n %1").arg(linkPath)); return false; } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 3f325ff4a..b3778be07 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -8,6 +8,7 @@ #include #include #include + #include "common/io_file.h" #include "common/version.h" #include "core/file_format/pkg.h" diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index a70137382..d1ef48dc7 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -10,6 +10,7 @@ #include #include #include + #include "common/config.h" #include "common/path_util.h" #include "core/file_format/psf.h" diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index 858bbb07a..c89fa5a00 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -7,25 +7,6 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { QPalette themePalette; switch (theme) { - case Theme::Light: - mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */" - "color: #000000; /* Black text */" - "padding: 5px;"); - themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray - themePalette.setColor(QPalette::WindowText, Qt::black); // Black - themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish - themePalette.setColor(QPalette::ToolTipBase, Qt::black); // Black - themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black - themePalette.setColor(QPalette::Text, Qt::black); // Black - themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray - themePalette.setColor(QPalette::ButtonText, Qt::black); // Black - themePalette.setColor(QPalette::BrightText, Qt::red); // Red - themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue - themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue - themePalette.setColor(QPalette::HighlightedText, Qt::white); // White - qApp->setPalette(themePalette); - break; - case Theme::Dark: mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */" "color: #ffffff; /* White text */" @@ -48,6 +29,25 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; + case Theme::Light: + mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */" + "color: #000000; /* Black text */" + "padding: 5px;"); + themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray + themePalette.setColor(QPalette::WindowText, Qt::black); // Black + themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish + themePalette.setColor(QPalette::ToolTipBase, Qt::black); // Black + themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black + themePalette.setColor(QPalette::Text, Qt::black); // Black + themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray + themePalette.setColor(QPalette::ButtonText, Qt::black); // Black + themePalette.setColor(QPalette::BrightText, Qt::red); // Red + themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue + themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue + themePalette.setColor(QPalette::HighlightedText, Qt::white); // White + qApp->setPalette(themePalette); + break; + case Theme::Green: mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */" "color: #ffffff; /* White text */" diff --git a/src/qt_gui/main_window_themes.h b/src/qt_gui/main_window_themes.h index 8b87fbce5..6da70e995 100644 --- a/src/qt_gui/main_window_themes.h +++ b/src/qt_gui/main_window_themes.h @@ -2,13 +2,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once + #include #include #include enum class Theme : int { - Light, Dark, + Light, Green, Blue, Violet, diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 7b5bf1816..69d718477 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -44,8 +44,8 @@ public: QAction* gameInstallPathAct; QAction* dumpGameListAct; QAction* pkgViewerAct; - QAction* setThemeLight; QAction* setThemeDark; + QAction* setThemeLight; QAction* setThemeGreen; QAction* setThemeBlue; QAction* setThemeViolet; @@ -76,7 +76,7 @@ public: MainWindow->setObjectName("MainWindow"); // MainWindow->resize(1280, 720); QIcon icon; - icon.addFile(QString::fromUtf8(":/images/shadps4.ico"), QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(QString::fromUtf8(":images/shadps4.ico"), QSize(), QIcon::Normal, QIcon::Off); MainWindow->setWindowIcon(icon); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); sizePolicy.setHorizontalStretch(0); @@ -136,13 +136,13 @@ public: pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); - setThemeLight = new QAction(MainWindow); - setThemeLight->setObjectName("setThemeLight"); - setThemeLight->setCheckable(true); - setThemeLight->setChecked(true); setThemeDark = new QAction(MainWindow); setThemeDark->setObjectName("setThemeDark"); setThemeDark->setCheckable(true); + setThemeDark->setChecked(true); + setThemeLight = new QAction(MainWindow); + setThemeLight->setObjectName("setThemeLight"); + setThemeLight->setCheckable(true); setThemeGreen = new QAction(MainWindow); setThemeGreen->setObjectName("setThemeGreen"); setThemeGreen->setCheckable(true); @@ -285,7 +285,7 @@ public: } // setupUi void retranslateUi(QMainWindow* MainWindow) { - MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr)); + MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "shadPS4", nullptr)); addElfFolderAct->setText( QCoreApplication::translate("MainWindow", "Open/Add Elf Folder", nullptr)); bootInstallPkgAct->setText( @@ -332,8 +332,8 @@ public: menuSettings->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr)); menuUtils->setTitle(QCoreApplication::translate("MainWindow", "Utils", nullptr)); menuThemes->setTitle(QCoreApplication::translate("MainWindow", "Themes", nullptr)); - setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr)); setThemeDark->setText(QCoreApplication::translate("MainWindow", "Dark", nullptr)); + setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr)); setThemeGreen->setText(QCoreApplication::translate("MainWindow", "Green", nullptr)); setThemeBlue->setText(QCoreApplication::translate("MainWindow", "Blue", nullptr)); setThemeViolet->setText(QCoreApplication::translate("MainWindow", "Violet", nullptr)); diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index cf0b21679..cd2ce2b6e 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -3,6 +3,7 @@ #include #include + #include "pkg_viewer.h" PKGViewer::PKGViewer(std::shared_ptr game_info_get, QWidget* parent, diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h index 0e0a87062..e040d5950 100644 --- a/src/qt_gui/pkg_viewer.h +++ b/src/qt_gui/pkg_viewer.h @@ -15,6 +15,7 @@ #include #include #include + #include "common/io_file.h" #include "core/file_format/pkg.h" #include "core/file_format/pkg_type.h" diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index ab79ac501..2b794593f 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -16,6 +16,7 @@ #include #include #include + #include "common/types.h" #include "core/file_format/trp.h" From 7308864537d2fd5880485dd11f90e0c1810caa1b Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 4 Aug 2024 10:49:43 -0300 Subject: [PATCH 046/109] SampleCountFlagBits::e16 - GetGpuClock64 (#360) * SampleCountFlagBits::e16 * GpuClock64 * GpuClock64 * Update pm4_cmds.h * Update pm4_cmds.h --- src/video_core/amdgpu/pm4_cmds.h | 11 +++++++++++ src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 9b44da024..e5f618cc2 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -652,6 +652,13 @@ struct PM4CmdReleaseMem { return data_lo | u64(data_hi) << 32; } + uint64_t GetGpuClock64() const { + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + auto ticks = std::chrono::duration_cast(duration).count(); + return static_cast(ticks); + } + void SignalFence(Platform::InterruptId irq_id) const { switch (data_sel.Value()) { case DataSelect::Data32Low: { @@ -662,6 +669,10 @@ struct PM4CmdReleaseMem { *Address() = DataQWord(); break; } + case DataSelect::GpuClock64: { + *Address() = GetGpuClock64(); + break; + } case DataSelect::PerfCounter: { *Address() = Common::FencedRDTSC(); break; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index e45391230..82ccf5afd 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -681,6 +681,8 @@ vk::SampleCountFlagBits NumSamples(u32 num_samples) { return vk::SampleCountFlagBits::e4; case 8: return vk::SampleCountFlagBits::e8; + case 16: + return vk::SampleCountFlagBits::e16; default: UNREACHABLE(); } From cdff4af38da1d832e35d8c057d698f38c64b2932 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 4 Aug 2024 17:17:34 -0300 Subject: [PATCH 047/109] add-SurfaceFormat (#365) [Debug] liverpool_to_vk.cpp:SurfaceFormat:395: Unreachable code! CUSA24620 Unknown data_format=7 and num_format=7 CUSA03082 Unknown data_format=2 and num_format=4 CUSA19345 Unknown data_format=34 and num_format=7 CUSA02411 Unknown data_format=1 and num_format=9 A duplicate was also removed. --- .../renderer_vulkan/liverpool_to_vk.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 82ccf5afd..01526265a 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -342,6 +342,7 @@ std::span GetAllFormats() { vk::Format::eR16G16Unorm, vk::Format::eR16G16B16A16Sscaled, vk::Format::eR16G16Sscaled, + vk::Format::eE5B9G9R9UfloatPack32, }; return formats; } @@ -410,10 +411,6 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eR16G16Unorm; } - if (data_format == AmdGpu::DataFormat::Format10_11_11 && - num_format == AmdGpu::NumberFormat::Float) { - return vk::Format::eB10G11R11UfloatPack32; - } if (data_format == AmdGpu::DataFormat::Format2_10_10_10 && num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eA2B10G10R10UnormPack32; @@ -550,6 +547,20 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu if (data_format == AmdGpu::DataFormat::Format8 && num_format == AmdGpu::NumberFormat::Srgb) { return vk::Format::eR8Srgb; } + if (data_format == AmdGpu::DataFormat::Format11_11_10 && + num_format == AmdGpu::NumberFormat::Float) { + return vk::Format::eB10G11R11UfloatPack32; + } + if (data_format == AmdGpu::DataFormat::Format16 && num_format == AmdGpu::NumberFormat::Uint) { + return vk::Format::eR16Uint; + } + if (data_format == AmdGpu::DataFormat::Format5_9_9_9 && + num_format == AmdGpu::NumberFormat::Float) { + return vk::Format::eE5B9G9R9UfloatPack32; + } + if (data_format == AmdGpu::DataFormat::Format8 && num_format == AmdGpu::NumberFormat::Snorm) { + return vk::Format::eR8Snorm; + } UNREACHABLE_MSG("Unknown data_format={} and num_format={}", u32(data_format), u32(num_format)); } From 159be2c7f436a91ee85f8f58465974ca64a9425c Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:45:28 +0300 Subject: [PATCH 048/109] video_core: Minor fixes (#366) * data_share: Fix DS instruction * vk_graphics_pipeline: Fix unnecessary invalidate * spirv: Remove subgroup id * vector_alu: Simplify mbcnt pattern * shader_recompiler: More instructions * clang format * kernel: Fix cond memory leak and reduce spam * liverpool: Print error on exception * build fix --- .../libraries/kernel/thread_management.cpp | 7 ++- .../backend/spirv/emit_spirv_warp.cpp | 2 +- .../backend/spirv/spirv_emit_context.cpp | 1 - .../backend/spirv/spirv_emit_context.h | 1 - .../frontend/translate/data_share.cpp | 22 ++++++---- .../frontend/translate/translate.h | 2 + .../frontend/translate/vector_alu.cpp | 43 +++++++++++-------- src/video_core/amdgpu/liverpool.h | 8 +++- .../renderer_vulkan/vk_compute_pipeline.cpp | 4 +- 9 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index c5237d0ad..3393138d2 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -727,6 +727,9 @@ int PS4_SYSV_ABI scePthreadCondDestroy(ScePthreadCond* cond) { LOG_INFO(Kernel_Pthread, "scePthreadCondDestroy, result={}", result); + delete *cond; + *cond = nullptr; + switch (result) { case 0: return SCE_OK; @@ -1142,7 +1145,7 @@ int PS4_SYSV_ABI scePthreadCondWait(ScePthreadCond* cond, ScePthreadMutex* mutex } int result = pthread_cond_wait(&(*cond)->cond, &(*mutex)->pth_mutex); - LOG_INFO(Kernel_Pthread, "scePthreadCondWait, result={}", result); + LOG_DEBUG(Kernel_Pthread, "scePthreadCondWait, result={}", result); switch (result) { case 0: @@ -1162,7 +1165,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) { } int result = pthread_condattr_destroy(&(*attr)->cond_attr); - LOG_INFO(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result); + LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result); switch (result) { case 0: diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index bd4ac0668..38afd90f1 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -11,7 +11,7 @@ Id SubgroupScope(EmitContext& ctx) { } Id EmitWarpId(EmitContext& ctx) { - return ctx.OpLoad(ctx.U32[1], ctx.subgroup_id); + UNREACHABLE(); } Id EmitLaneId(EmitContext& ctx) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index f7b30052b..8ca8b7a3d 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -225,7 +225,6 @@ void EmitContext::DefineInputs(const Info& info) { break; } case Stage::Fragment: - subgroup_id = DefineVariable(U32[1], spv::BuiltIn::SubgroupId, spv::StorageClass::Input); subgroup_local_invocation_id = DefineVariable( U32[1], spv::BuiltIn::SubgroupLocalInvocationId, spv::StorageClass::Input); Decorate(subgroup_local_invocation_id, spv::Decoration::Flat); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 34c13d3f9..2aa1bf780 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -180,7 +180,6 @@ public: Id workgroup_id{}; Id local_invocation_id{}; - Id subgroup_id{}; Id subgroup_local_invocation_id{}; Id image_u32{}; diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 148371660..532e024e2 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -48,7 +48,8 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnIn IR::VectorReg dst_reg{inst.dst[0].code}; if (is_pair) { // Pair loads are either 32 or 64-bit - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const u32 adj = bit_size == 32 ? 4 : 8; + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); if (bit_size == 32) { ir.SetVectorReg(dst_reg++, IR::U32{data0}); @@ -56,7 +57,7 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnIn ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1))); + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); if (bit_size == 32) { ir.SetVectorReg(dst_reg++, IR::U32{data1}); @@ -65,11 +66,13 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnIn ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); } } else if (bit_size == 64) { - const IR::Value data = ir.LoadShared(bit_size, is_signed, addr); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); } else { - const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr)}; + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; ir.SetVectorReg(dst_reg, data); } } @@ -79,7 +82,8 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnI const IR::VectorReg data0{inst.src[1].code}; const IR::VectorReg data1{inst.src[2].code}; if (is_pair) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const u32 adj = bit_size == 32 ? 4 : 8; + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); if (bit_size == 32) { ir.WriteShared(32, ir.GetVectorReg(data0), addr0); } else { @@ -87,7 +91,7 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnI 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), addr0); } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1))); + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); if (bit_size == 32) { ir.WriteShared(32, ir.GetVectorReg(data1), addr1); } else { @@ -96,11 +100,13 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnI addr1); } } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); const IR::Value data = ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, data, addr); + ir.WriteShared(bit_size, data, addr0); } else { - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); } } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 8d1b76833..fe4457d27 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -125,6 +125,7 @@ public: void V_ADD_F32(const GcnInst& inst); void V_CVT_OFF_F32_I4(const GcnInst& inst); void V_MED3_F32(const GcnInst& inst); + void V_MED3_I32(const GcnInst& inst); void V_FLOOR_F32(const GcnInst& inst); void V_SUB_F32(const GcnInst& inst); void V_RCP_F32(const GcnInst& inst); @@ -159,6 +160,7 @@ public: void V_SUB_I32(const GcnInst& inst); void V_LSHR_B32(const GcnInst& inst); void V_ASHRREV_I32(const GcnInst& inst); + void V_ASHR_I32(const GcnInst& inst); void V_MAD_U32_U24(const GcnInst& inst); void V_RNDNE_F32(const GcnInst& inst); void V_BCNT_U32_B32(const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 669ef7ca1..89428c44f 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -24,6 +24,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_LSHR_B32(inst); case Opcode::V_ASHRREV_I32: return V_ASHRREV_I32(inst); + case Opcode::V_ASHR_I32: + return V_ASHR_I32(inst); case Opcode::V_LSHRREV_B32: return V_LSHRREV_B32(inst); case Opcode::V_NOT_B32: @@ -183,6 +185,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_ADD_F32(inst); case Opcode::V_MED3_F32: return V_MED3_F32(inst); + case Opcode::V_MED3_I32: + return V_MED3_I32(inst); case Opcode::V_FLOOR_F32: return V_FLOOR_F32(inst); case Opcode::V_SUB_F32: @@ -479,6 +483,14 @@ void Translator::V_MED3_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); } +void Translator::V_MED3_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); + SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); +} + void Translator::V_FLOOR_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0], true)}; const IR::VectorReg dst_reg{inst.dst[0].code}; @@ -760,6 +772,12 @@ void Translator::V_ASHRREV_I32(const GcnInst& inst) { SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } +void Translator::V_ASHR_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + void Translator::V_MAD_U32_U24(const GcnInst& inst) { V_MAD_I32_I24(inst, false); } @@ -925,25 +943,12 @@ void Translator::V_FFBL_B32(const GcnInst& inst) { void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 lane_id = ir.LaneId(); - - const auto [warp_half, mask_shift] = [&]() -> std::pair { - if (profile.subgroup_size == 32) { - const IR::U32 warp_half = ir.BitwiseAnd(ir.WarpId(), ir.Imm32(1)); - return std::make_pair(warp_half, lane_id); - } - const IR::U32 warp_half = ir.ShiftRightLogical(lane_id, ir.Imm32(5)); - const IR::U32 mask_shift = ir.BitwiseAnd(lane_id, ir.Imm32(0x1F)); - return std::make_pair(warp_half, mask_shift); - }(); - - const IR::U32 thread_mask = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), mask_shift), ir.Imm32(1)); - const IR::U1 is_odd_warp = ir.INotEqual(warp_half, ir.Imm32(0)); - const IR::U32 mask = IR::U32{ir.Select(is_odd_warp, is_low ? ir.Imm32(~0U) : thread_mask, - is_low ? thread_mask : ir.Imm32(0))}; - const IR::U32 masked_value = ir.BitwiseAnd(src0, mask); - const IR::U32 result = ir.IAdd(src1, ir.BitCount(masked_value)); - SetDst(inst.dst[0], result); + if (!is_low) { + ASSERT(src0.IsImmediate() && src0.U32() == ~0U && src1.IsImmediate() && src1.U32() == 0U); + return; + } + ASSERT(src0.IsImmediate() && src0.U32() == ~0U); + SetDst(inst.dst[0], ir.LaneId()); } } // namespace Shader::Gcn diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index b0285809c..400af0315 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include @@ -1040,7 +1040,11 @@ private: return {}; } void unhandled_exception() { - UNREACHABLE(); + try { + std::rethrow_exception(std::current_exception()); + } catch (const std::exception& e) { + UNREACHABLE_MSG("Unhandled exception: {}", e.what()); + } } void return_void() {} struct empty {}; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 34f1e9cc4..d8e5f7fae 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -94,7 +94,9 @@ bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& s const auto vsharp = buffer.GetVsharp(info); const u32 size = vsharp.GetSize(); const VAddr address = vsharp.base_address; - texture_cache.OnCpuWrite(address); + if (buffer.is_storage) { + texture_cache.OnCpuWrite(address); + } const u32 offset = staging.Copy(address, size, buffer.is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment()); From 381ba8c7a591c43f58056f5246ccc7baab1ecb95 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:02:10 +0300 Subject: [PATCH 049/109] video_core: Implement guest buffer manager (#373) * video_core: Introduce buffer cache * video_core: Use multi level page table for caches * renderer_vulkan: Remove unused stream buffer * fix build * oops forgot optimize off --- CMakeLists.txt | 15 +- .../object_pool.h | 4 +- src/common/unique_function.h | 61 +++ .../libraries/kernel/thread_management.cpp | 17 +- src/core/libraries/pad/pad.cpp | 4 +- src/core/memory.cpp | 96 +--- src/core/memory.h | 30 +- src/core/module.cpp | 1 + .../spirv/emit_spirv_context_get_set.cpp | 30 +- .../backend/spirv/spirv_emit_context.cpp | 80 +-- .../backend/spirv/spirv_emit_context.h | 19 +- .../frontend/control_flow_graph.cpp | 2 +- .../frontend/control_flow_graph.h | 6 +- src/shader_recompiler/frontend/module.h | 10 - .../frontend/structured_control_flow.cpp | 22 +- .../frontend/structured_control_flow.h | 5 +- .../frontend/translate/translate.cpp | 1 + .../frontend/translate/vector_memory.cpp | 5 + src/shader_recompiler/ir/basic_block.cpp | 2 +- src/shader_recompiler/ir/basic_block.h | 6 +- .../ir/passes/resource_tracking_pass.cpp | 101 ++-- src/shader_recompiler/recompiler.cpp | 10 +- src/shader_recompiler/recompiler.h | 6 +- src/shader_recompiler/runtime_info.h | 21 +- src/video_core/amdgpu/liverpool.h | 2 +- src/video_core/amdgpu/resource.h | 4 + src/video_core/buffer_cache/buffer.cpp | 227 ++++++++ src/video_core/buffer_cache/buffer.h | 173 ++++++ src/video_core/buffer_cache/buffer_cache.cpp | 497 ++++++++++++++++++ src/video_core/buffer_cache/buffer_cache.h | 129 +++++ .../buffer_cache/memory_tracker_base.h | 175 ++++++ src/video_core/buffer_cache/range_set.h | 159 ++++++ src/video_core/buffer_cache/word_manager.h | 398 ++++++++++++++ src/video_core/multi_level_page_table.h | 65 +++ src/video_core/page_manager.cpp | 260 +++++++++ src/video_core/page_manager.h | 39 ++ .../renderer_vulkan/renderer_vulkan.cpp | 4 +- .../renderer_vulkan/renderer_vulkan.h | 6 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 63 ++- .../renderer_vulkan/vk_compute_pipeline.h | 10 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 132 ++--- .../renderer_vulkan/vk_graphics_pipeline.h | 15 +- .../renderer_vulkan/vk_instance.cpp | 12 +- .../renderer_vulkan/vk_pipeline_cache.h | 5 +- .../renderer_vulkan/vk_rasterizer.cpp | 87 +-- .../renderer_vulkan/vk_rasterizer.h | 27 +- src/video_core/renderer_vulkan/vk_scheduler.h | 7 +- .../renderer_vulkan/vk_stream_buffer.cpp | 241 --------- .../renderer_vulkan/vk_stream_buffer.h | 89 ---- src/video_core/texture_cache/image_info.cpp | 2 +- src/video_core/texture_cache/image_view.cpp | 15 +- .../texture_cache/texture_cache.cpp | 197 ++----- src/video_core/texture_cache/texture_cache.h | 71 +-- src/video_core/texture_cache/tile_manager.cpp | 66 ++- src/video_core/texture_cache/tile_manager.h | 5 +- 55 files changed, 2697 insertions(+), 1039 deletions(-) rename src/{shader_recompiler => common}/object_pool.h (98%) create mode 100755 src/common/unique_function.h delete mode 100644 src/shader_recompiler/frontend/module.h create mode 100644 src/video_core/buffer_cache/buffer.cpp create mode 100644 src/video_core/buffer_cache/buffer.h create mode 100644 src/video_core/buffer_cache/buffer_cache.cpp create mode 100644 src/video_core/buffer_cache/buffer_cache.h create mode 100644 src/video_core/buffer_cache/memory_tracker_base.h create mode 100644 src/video_core/buffer_cache/range_set.h create mode 100644 src/video_core/buffer_cache/word_manager.h create mode 100644 src/video_core/multi_level_page_table.h create mode 100644 src/video_core/page_manager.cpp create mode 100644 src/video_core/page_manager.h delete mode 100644 src/video_core/renderer_vulkan/vk_stream_buffer.cpp delete mode 100644 src/video_core/renderer_vulkan/vk_stream_buffer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 90ba4d83a..4df3db2bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,7 @@ set(COMMON src/common/logging/backend.cpp src/common/native_clock.h src/common/path_util.cpp src/common/path_util.h + src/common/object_pool.h src/common/polyfill_thread.h src/common/rdtsc.cpp src/common/rdtsc.h @@ -294,6 +295,7 @@ set(COMMON src/common/logging/backend.cpp src/common/thread.h src/common/types.h src/common/uint128.h + src/common/unique_function.h src/common/version.h src/common/ntapi.h src/common/ntapi.cpp @@ -367,7 +369,6 @@ set(CORE src/core/aerolib/stubs.cpp ) set(SHADER_RECOMPILER src/shader_recompiler/exception.h - src/shader_recompiler/object_pool.h src/shader_recompiler/profile.h src/shader_recompiler/recompiler.cpp src/shader_recompiler/recompiler.h @@ -451,6 +452,13 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/amdgpu/pm4_cmds.h src/video_core/amdgpu/pm4_opcodes.h src/video_core/amdgpu/resource.h + src/video_core/buffer_cache/buffer.cpp + src/video_core/buffer_cache/buffer.h + src/video_core/buffer_cache/buffer_cache.cpp + src/video_core/buffer_cache/buffer_cache.h + src/video_core/buffer_cache/memory_tracker_base.h + src/video_core/buffer_cache/range_set.h + src/video_core/buffer_cache/word_manager.h src/video_core/renderer_vulkan/liverpool_to_vk.cpp src/video_core/renderer_vulkan/liverpool_to_vk.h src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -479,8 +487,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/vk_scheduler.h src/video_core/renderer_vulkan/vk_shader_util.cpp src/video_core/renderer_vulkan/vk_shader_util.h - src/video_core/renderer_vulkan/vk_stream_buffer.cpp - src/video_core/renderer_vulkan/vk_stream_buffer.h src/video_core/renderer_vulkan/vk_swapchain.cpp src/video_core/renderer_vulkan/vk_swapchain.h src/video_core/texture_cache/image.cpp @@ -496,6 +502,9 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/types.h + src/video_core/page_manager.cpp + src/video_core/page_manager.h + src/video_core/multi_level_page_table.h src/video_core/renderdoc.cpp src/video_core/renderdoc.h ) diff --git a/src/shader_recompiler/object_pool.h b/src/common/object_pool.h similarity index 98% rename from src/shader_recompiler/object_pool.h rename to src/common/object_pool.h index 1398898ad..9e25e0c4c 100644 --- a/src/shader_recompiler/object_pool.h +++ b/src/common/object_pool.h @@ -8,7 +8,7 @@ #include #include -namespace Shader { +namespace Common { template requires std::is_destructible_v @@ -104,4 +104,4 @@ private: size_t new_chunk_size{}; }; -} // namespace Shader +} // namespace Common diff --git a/src/common/unique_function.h b/src/common/unique_function.h new file mode 100755 index 000000000..1891ec3c6 --- /dev/null +++ b/src/common/unique_function.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +/// General purpose function wrapper similar to std::function. +/// Unlike std::function, the captured values don't have to be copyable. +/// This class can be moved but not copied. +template +class UniqueFunction { + class CallableBase { + public: + virtual ~CallableBase() = default; + virtual ResultType operator()(Args&&...) = 0; + }; + + template + class Callable final : public CallableBase { + public: + Callable(Functor&& functor_) : functor{std::move(functor_)} {} + ~Callable() override = default; + + ResultType operator()(Args&&... args) override { + return functor(std::forward(args)...); + } + + private: + Functor functor; + }; + +public: + UniqueFunction() = default; + + template + UniqueFunction(Functor&& functor) + : callable{std::make_unique>(std::move(functor))} {} + + UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default; + UniqueFunction(UniqueFunction&& rhs) noexcept = default; + + UniqueFunction& operator=(const UniqueFunction&) = delete; + UniqueFunction(const UniqueFunction&) = delete; + + ResultType operator()(Args&&... args) const { + return (*callable)(std::forward(args)...); + } + + explicit operator bool() const noexcept { + return static_cast(callable); + } + +private: + std::unique_ptr callable; +}; + +} // namespace Common diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 3393138d2..48347ea53 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -465,7 +465,7 @@ int PS4_SYSV_ABI scePthreadMutexDestroy(ScePthreadMutex* mutex) { int result = pthread_mutex_destroy(&(*mutex)->pth_mutex); - LOG_INFO(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result); + LOG_DEBUG(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result); delete *mutex; *mutex = nullptr; @@ -725,7 +725,7 @@ int PS4_SYSV_ABI scePthreadCondDestroy(ScePthreadCond* cond) { } int result = pthread_cond_destroy(&(*cond)->cond); - LOG_INFO(Kernel_Pthread, "scePthreadCondDestroy, result={}", result); + LOG_DEBUG(Kernel_Pthread, "scePthreadCondDestroy, result={}", result); delete *cond; *cond = nullptr; @@ -811,8 +811,6 @@ int PS4_SYSV_ABI posix_pthread_cond_timedwait(ScePthreadCond* cond, ScePthreadMu } int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond) { - LOG_INFO(Kernel_Pthread, - "posix posix_pthread_cond_broadcast redirect to scePthreadCondBroadcast"); int result = scePthreadCondBroadcast(cond); if (result != 0) { int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP @@ -824,7 +822,6 @@ int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond) { } int PS4_SYSV_ABI posix_pthread_mutexattr_init(ScePthreadMutexattr* attr) { - // LOG_INFO(Kernel_Pthread, "posix pthread_mutexattr_init redirect to scePthreadMutexattrInit"); int result = scePthreadMutexattrInit(attr); if (result < 0) { int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP @@ -836,7 +833,6 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_init(ScePthreadMutexattr* attr) { } int PS4_SYSV_ABI posix_pthread_mutexattr_settype(ScePthreadMutexattr* attr, int type) { - // LOG_INFO(Kernel_Pthread, "posix pthread_mutex_init redirect to scePthreadMutexInit"); int result = scePthreadMutexattrSettype(attr, type); if (result < 0) { int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP @@ -861,7 +857,6 @@ int PS4_SYSV_ABI posix_pthread_once(pthread_once_t* once_control, void (*init_ro int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(ScePthreadMutexattr* attr, int protocol) { int result = scePthreadMutexattrSetprotocol(attr, protocol); - LOG_INFO(Kernel_Pthread, "redirect to scePthreadMutexattrSetprotocol: result = {}", result); if (result < 0) { UNREACHABLE(); } @@ -1295,8 +1290,6 @@ int PS4_SYSV_ABI posix_pthread_attr_setdetachstate(ScePthreadAttr* attr, int det int PS4_SYSV_ABI posix_pthread_create_name_np(ScePthread* thread, const ScePthreadAttr* attr, PthreadEntryFunc start_routine, void* arg, const char* name) { - LOG_INFO(Kernel_Pthread, "posix pthread_create redirect to scePthreadCreate: name = {}", name); - int result = scePthreadCreate(thread, attr, start_routine, arg, name); if (result != 0) { int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP @@ -1343,17 +1336,11 @@ int PS4_SYSV_ABI posix_pthread_cond_init(ScePthreadCond* cond, const ScePthreadC int PS4_SYSV_ABI posix_pthread_cond_signal(ScePthreadCond* cond) { int result = scePthreadCondSignal(cond); - LOG_INFO(Kernel_Pthread, - "posix posix_pthread_cond_signal redirect to scePthreadCondSignal, result = {}", - result); return result; } int PS4_SYSV_ABI posix_pthread_cond_destroy(ScePthreadCond* cond) { int result = scePthreadCondDestroy(cond); - LOG_INFO(Kernel_Pthread, - "posix posix_pthread_cond_destroy redirect to scePthreadCondDestroy, result = {}", - result); return result; } diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index e318e1523..064c71b84 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -470,7 +470,7 @@ int PS4_SYSV_ABI scePadSetUserColor() { } int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); + LOG_DEBUG(Lib_Pad, "(STUBBED) called"); return ORBIS_OK; } @@ -665,4 +665,4 @@ void RegisterlibScePad(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("7xA+hFtvBCA", "libScePad", 1, "libScePad", 1, 1, Func_EF103E845B6F0420); }; -} // namespace Libraries::Pad \ No newline at end of file +} // namespace Libraries::Pad diff --git a/src/core/memory.cpp b/src/core/memory.cpp index aa552d511..dc5ded41d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -7,7 +7,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" -#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" namespace Core { @@ -172,7 +172,7 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M if (type == VMAType::Direct) { new_vma.phys_base = phys_addr; - MapVulkanMemory(mapped_addr, size); + rasterizer->MapMemory(mapped_addr, size); } if (type == VMAType::Flexible) { flexible_usage += size; @@ -222,7 +222,7 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { const auto type = it->second.type; const bool has_backing = type == VMAType::Direct || type == VMAType::File; if (type == VMAType::Direct) { - UnmapVulkanMemory(virtual_addr, size); + rasterizer->UnmapMemory(virtual_addr, size); } if (type == VMAType::Flexible) { flexible_usage -= size; @@ -263,7 +263,7 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr } int MemoryManager::VirtualQuery(VAddr addr, int flags, - Libraries::Kernel::OrbisVirtualQueryInfo* info) { + ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { std::scoped_lock lk{mutex}; auto it = FindVMA(addr); @@ -293,7 +293,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, } int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, - Libraries::Kernel::OrbisQueryInfo* out_info) { + ::Libraries::Kernel::OrbisQueryInfo* out_info) { std::scoped_lock lk{mutex}; auto dmem_area = FindDmemArea(addr); @@ -333,13 +333,6 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si return ORBIS_OK; } -std::pair MemoryManager::GetVulkanBuffer(VAddr addr) { - auto it = mapped_memories.upper_bound(addr); - it = std::prev(it); - ASSERT(it != mapped_memories.end() && it->first <= addr); - return std::make_pair(*it->second.buffer, addr - it->first); -} - void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) { auto it = FindVMA(virtual_addr); @@ -455,85 +448,6 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t of return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area); }; -void MemoryManager::MapVulkanMemory(VAddr addr, size_t size) { - return; - const vk::Device device = instance->GetDevice(); - const auto memory_props = instance->GetPhysicalDevice().getMemoryProperties(); - void* host_pointer = reinterpret_cast(addr); - const auto host_mem_props = device.getMemoryHostPointerPropertiesEXT( - vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, host_pointer); - ASSERT(host_mem_props.memoryTypeBits != 0); - - int mapped_memory_type = -1; - auto find_mem_type_with_flag = [&](const vk::MemoryPropertyFlags flags) { - u32 host_mem_types = host_mem_props.memoryTypeBits; - while (host_mem_types != 0) { - // Try to find a cached memory type - mapped_memory_type = std::countr_zero(host_mem_types); - host_mem_types -= (1 << mapped_memory_type); - - if ((memory_props.memoryTypes[mapped_memory_type].propertyFlags & flags) == flags) { - return; - } - } - - mapped_memory_type = -1; - }; - - // First try to find a memory that is both coherent and cached - find_mem_type_with_flag(vk::MemoryPropertyFlagBits::eHostCoherent | - vk::MemoryPropertyFlagBits::eHostCached); - if (mapped_memory_type == -1) - // Then only coherent (lower performance) - find_mem_type_with_flag(vk::MemoryPropertyFlagBits::eHostCoherent); - - if (mapped_memory_type == -1) { - LOG_CRITICAL(Render_Vulkan, "No coherent memory available for memory mapping"); - mapped_memory_type = std::countr_zero(host_mem_props.memoryTypeBits); - } - - const vk::StructureChain alloc_info = { - vk::MemoryAllocateInfo{ - .allocationSize = size, - .memoryTypeIndex = static_cast(mapped_memory_type), - }, - vk::ImportMemoryHostPointerInfoEXT{ - .handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, - .pHostPointer = host_pointer, - }, - }; - - const auto [it, new_memory] = mapped_memories.try_emplace(addr); - ASSERT_MSG(new_memory, "Attempting to remap already mapped vulkan memory"); - - auto& memory = it->second; - memory.backing = device.allocateMemoryUnique(alloc_info.get()); - - constexpr vk::BufferUsageFlags MapFlags = - vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer | - vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | - vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer; - - const vk::StructureChain buffer_info = { - vk::BufferCreateInfo{ - .size = size, - .usage = MapFlags, - .sharingMode = vk::SharingMode::eExclusive, - }, - vk::ExternalMemoryBufferCreateInfoKHR{ - .handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, - }}; - memory.buffer = device.createBufferUnique(buffer_info.get()); - device.bindBufferMemory(*memory.buffer, *memory.backing, 0); -} - -void MemoryManager::UnmapVulkanMemory(VAddr addr, size_t size) { - return; - const auto it = mapped_memories.find(addr); - ASSERT(it != mapped_memories.end() && it->second.buffer_size == size); - mapped_memories.erase(it); -} - int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut) { std::scoped_lock lk{mutex}; diff --git a/src/core/memory.h b/src/core/memory.h index 2b3d07a78..6d0a977fc 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -3,20 +3,17 @@ #pragma once -#include +#include #include #include -#include -#include #include "common/enum.h" #include "common/singleton.h" #include "common/types.h" #include "core/address_space.h" #include "core/libraries/kernel/memory_management.h" -#include "video_core/renderer_vulkan/vk_common.h" namespace Vulkan { -class Instance; +class Rasterizer; } namespace Libraries::Kernel { @@ -128,8 +125,8 @@ public: explicit MemoryManager(); ~MemoryManager(); - void SetInstance(const Vulkan::Instance* instance_) { - instance = instance_; + void SetRasterizer(Vulkan::Rasterizer* rasterizer_) { + rasterizer = rasterizer_; } void SetTotalFlexibleSize(u64 size) { @@ -140,9 +137,7 @@ public: return total_flexible_size - flexible_usage; } - /// Returns the offset of the mapped virtual system managed memory base from where it usually - /// would be mapped. - [[nodiscard]] VAddr SystemReservedVirtualBase() noexcept { + VAddr SystemReservedVirtualBase() noexcept { return impl.SystemReservedVirtualBase(); } @@ -172,8 +167,6 @@ public: int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, PAddr* phys_addr_out, size_t* size_out); - std::pair GetVulkanBuffer(VAddr addr); - int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); @@ -218,10 +211,6 @@ private: DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area); - void MapVulkanMemory(VAddr addr, size_t size); - - void UnmapVulkanMemory(VAddr addr, size_t size); - private: AddressSpace impl; DMemMap dmem_map; @@ -229,14 +218,7 @@ private: std::recursive_mutex mutex; size_t total_flexible_size = 448_MB; size_t flexible_usage{}; - - struct MappedMemory { - vk::UniqueBuffer buffer; - vk::UniqueDeviceMemory backing; - size_t buffer_size; - }; - std::map mapped_memories; - const Vulkan::Instance* instance{}; + Vulkan::Rasterizer* rasterizer{}; }; using Memory = Common::Singleton; diff --git a/src/core/module.cpp b/src/core/module.cpp index d885b917f..775e1ef19 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -88,6 +88,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { aligned_base_size + TrampolineSize, MemoryProt::CpuReadWrite, MemoryMapFlags::Fixed, VMAType::Code, name, true); LoadOffset += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR); + LOG_INFO(Core_Linker, "Loading module {} to {}", name, fmt::ptr(*out_addr)); // Initialize trampoline generator. void* trampoline_addr = std::bit_cast(base_virtual_addr + aligned_base_size); 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 02480303f..40d6cdb75 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 @@ -21,6 +21,7 @@ Id VsOutputAttrPointer(EmitContext& ctx, VsOutput output) { case VsOutput::ClipDist7: { const u32 index = u32(output) - u32(VsOutput::ClipDist0); const Id clip_num{ctx.ConstU32(index)}; + ASSERT_MSG(Sirit::ValidId(ctx.clip_distances), "Clip distance used but not defined"); return ctx.OpAccessChain(ctx.output_f32, ctx.clip_distances, clip_num); } case VsOutput::CullDist0: @@ -33,6 +34,7 @@ Id VsOutputAttrPointer(EmitContext& ctx, VsOutput output) { case VsOutput::CullDist7: { const u32 index = u32(output) - u32(VsOutput::CullDist0); const Id cull_num{ctx.ConstU32(index)}; + ASSERT_MSG(Sirit::ValidId(ctx.cull_distances), "Cull distance used but not defined"); return ctx.OpAccessChain(ctx.output_f32, ctx.cull_distances, cull_num); } default: @@ -125,7 +127,12 @@ Id EmitReadConst(EmitContext& ctx) { } Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { - const auto& buffer = ctx.buffers[handle]; + auto& buffer = ctx.buffers[handle]; + if (!Sirit::ValidId(buffer.offset)) { + buffer.offset = ctx.GetBufferOffset(handle); + } + const Id offset_dwords{ctx.OpShiftRightLogical(ctx.U32[1], buffer.offset, ctx.ConstU32(2U))}; + index = ctx.OpIAdd(ctx.U32[1], index, offset_dwords); const Id ptr{ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index)}; return ctx.OpLoad(buffer.data_types->Get(1), ptr); } @@ -137,7 +144,7 @@ Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index) { Id EmitReadStepRate(EmitContext& ctx, int rate_idx) { return ctx.OpLoad( ctx.U32[1], ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]), - ctx.instance_step_rates, + ctx.push_data_block, rate_idx == 0 ? ctx.u32_zero_value : ctx.u32_one_value)); } @@ -221,7 +228,11 @@ Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { template static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { - const auto& buffer = ctx.buffers[handle]; + auto& buffer = ctx.buffers[handle]; + if (!Sirit::ValidId(buffer.offset)) { + buffer.offset = ctx.GetBufferOffset(handle); + } + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); if constexpr (N == 1) { const Id ptr{ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index)}; @@ -314,7 +325,7 @@ static Id ComponentOffset(EmitContext& ctx, Id address, u32 stride, u32 bit_offs } static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 comp) { - const auto& buffer = ctx.buffers[handle]; + auto& buffer = ctx.buffers[handle]; const auto format = buffer.buffer.GetDataFmt(); switch (format) { case AmdGpu::DataFormat::FormatInvalid: @@ -399,6 +410,11 @@ static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 com template static Id EmitLoadBufferFormatF32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { + auto& buffer = ctx.buffers[handle]; + if (!Sirit::ValidId(buffer.offset)) { + buffer.offset = ctx.GetBufferOffset(handle); + } + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); if constexpr (N == 1) { return GetBufferFormatValue(ctx, handle, address, 0); } else { @@ -428,7 +444,11 @@ Id EmitLoadBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id ad template static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { - const auto& buffer = ctx.buffers[handle]; + auto& buffer = ctx.buffers[handle]; + if (!Sirit::ValidId(buffer.offset)) { + buffer.offset = ctx.GetBufferOffset(handle); + } + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); if constexpr (N == 1) { const Id ptr{ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index)}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 8ca8b7a3d..cdf417fc4 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -46,9 +46,9 @@ EmitContext::EmitContext(const Profile& profile_, IR::Program& program, u32& bin stage{program.info.stage}, binding{binding_} { AddCapability(spv::Capability::Shader); DefineArithmeticTypes(); - DefineInterfaces(program); - DefineBuffers(info); - DefineImagesAndSamplers(info); + DefineInterfaces(); + DefineBuffers(); + DefineImagesAndSamplers(); DefineSharedMemory(); } @@ -117,9 +117,10 @@ void EmitContext::DefineArithmeticTypes() { full_result_u32x2 = Name(TypeStruct(U32[1], U32[1]), "full_result_u32x2"); } -void EmitContext::DefineInterfaces(const IR::Program& program) { - DefineInputs(program.info); - DefineOutputs(program.info); +void EmitContext::DefineInterfaces() { + DefinePushDataBlock(); + DefineInputs(); + DefineOutputs(); } Id GetAttributeType(EmitContext& ctx, AmdGpu::NumberFormat fmt) { @@ -164,6 +165,16 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f throw InvalidArgument("Invalid attribute type {}", fmt); } +Id EmitContext::GetBufferOffset(u32 binding) { + const u32 half = Shader::PushData::BufOffsetIndex + (binding >> 4); + const u32 comp = (binding & 0xf) >> 2; + const u32 offset = (binding & 0x3) << 3; + const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), + push_data_block, ConstU32(half), ConstU32(comp))}; + const Id value{OpLoad(U32[1], ptr)}; + return OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); +} + Id MakeDefaultValue(EmitContext& ctx, u32 default_value) { switch (default_value) { case 0: @@ -179,24 +190,13 @@ Id MakeDefaultValue(EmitContext& ctx, u32 default_value) { } } -void EmitContext::DefineInputs(const Info& info) { +void EmitContext::DefineInputs() { switch (stage) { case Stage::Vertex: { vertex_index = DefineVariable(U32[1], spv::BuiltIn::VertexIndex, spv::StorageClass::Input); base_vertex = DefineVariable(U32[1], spv::BuiltIn::BaseVertex, spv::StorageClass::Input); instance_id = DefineVariable(U32[1], spv::BuiltIn::InstanceIndex, spv::StorageClass::Input); - // Create push constants block for instance steps rates - const Id struct_type{Name(TypeStruct(U32[1], U32[1]), "instance_step_rates")}; - Decorate(struct_type, spv::Decoration::Block); - MemberName(struct_type, 0, "sr0"); - MemberName(struct_type, 1, "sr1"); - MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); - MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); - instance_step_rates = DefineVar(struct_type, spv::StorageClass::PushConstant); - Name(instance_step_rates, "step_rates"); - interfaces.push_back(instance_step_rates); - for (const auto& input : info.vs_inputs) { const Id type{GetAttributeType(*this, input.fmt)}; if (input.instance_step_rate == Info::VsInput::InstanceIdType::OverStepRate0 || @@ -260,19 +260,20 @@ void EmitContext::DefineInputs(const Info& info) { } } -void EmitContext::DefineOutputs(const Info& info) { +void EmitContext::DefineOutputs() { switch (stage) { case Stage::Vertex: { output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output); - const std::array zero{f32_zero_value, f32_zero_value, f32_zero_value, - f32_zero_value, f32_zero_value, f32_zero_value, - f32_zero_value, f32_zero_value}; - const Id type{TypeArray(F32[1], ConstU32(8U))}; - const Id initializer{ConstantComposite(type, zero)}; - clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance, spv::StorageClass::Output, - initializer); - cull_distances = DefineVariable(type, spv::BuiltIn::CullDistance, spv::StorageClass::Output, - initializer); + const bool has_extra_pos_stores = info.stores.Get(IR::Attribute::Position1) || + info.stores.Get(IR::Attribute::Position2) || + info.stores.Get(IR::Attribute::Position3); + if (has_extra_pos_stores) { + const Id type{TypeArray(F32[1], ConstU32(8U))}; + clip_distances = + DefineVariable(type, spv::BuiltIn::ClipDistance, spv::StorageClass::Output); + cull_distances = + DefineVariable(type, spv::BuiltIn::CullDistance, spv::StorageClass::Output); + } for (u32 i = 0; i < IR::NumParams; i++) { const IR::Attribute param{IR::Attribute::Param0 + i}; if (!info.stores.GetAny(param)) { @@ -304,7 +305,24 @@ void EmitContext::DefineOutputs(const Info& info) { } } -void EmitContext::DefineBuffers(const Info& info) { +void EmitContext::DefinePushDataBlock() { + // Create push constants block for instance steps rates + const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4]), "AuxData")}; + Decorate(struct_type, spv::Decoration::Block); + MemberName(struct_type, 0, "sr0"); + MemberName(struct_type, 1, "sr1"); + MemberName(struct_type, 2, "buf_offsets0"); + MemberName(struct_type, 3, "buf_offsets1"); + MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); + MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); + MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U); + MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U); + push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); + Name(push_data_block, "push_data"); + interfaces.push_back(push_data_block); +} + +void EmitContext::DefineBuffers() { boost::container::small_vector type_ids; for (u32 i = 0; const auto& buffer : info.buffers) { const auto* data_types = True(buffer.used_types & IR::Type::F32) ? &F32 : &U32; @@ -322,8 +340,8 @@ void EmitContext::DefineBuffers(const Info& info) { Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, 0, "data"); MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); + type_ids.push_back(record_array_type); } - type_ids.push_back(record_array_type); const auto storage_class = buffer.is_storage ? spv::StorageClass::StorageBuffer : spv::StorageClass::Uniform; @@ -430,7 +448,7 @@ Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { throw InvalidArgument("Invalid texture type {}", desc.type); } -void EmitContext::DefineImagesAndSamplers(const Info& info) { +void EmitContext::DefineImagesAndSamplers() { for (const auto& image_desc : info.images) { const VectorIds* data_types = [&] { switch (image_desc.nfmt) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 2aa1bf780..ff9ec4b71 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -40,6 +40,7 @@ public: ~EmitContext(); Id Def(const IR::Value& value); + Id GetBufferOffset(u32 binding); [[nodiscard]] Id DefineInput(Id type, u32 location) { const Id input_id{DefineVar(type, spv::StorageClass::Input)}; @@ -168,7 +169,7 @@ public: Id output_position{}; Id vertex_index{}; Id instance_id{}; - Id instance_step_rates{}; + Id push_data_block{}; Id base_vertex{}; Id frag_coord{}; Id front_facing{}; @@ -201,14 +202,15 @@ public: struct BufferDefinition { Id id; + Id offset; const VectorIds* data_types; Id pointer_type; AmdGpu::Buffer buffer; }; u32& binding; - boost::container::small_vector buffers; - boost::container::small_vector images; + boost::container::small_vector buffers; + boost::container::small_vector images; boost::container::small_vector samplers; Id sampler_type{}; @@ -227,11 +229,12 @@ public: private: void DefineArithmeticTypes(); - void DefineInterfaces(const IR::Program& program); - void DefineInputs(const Info& info); - void DefineOutputs(const Info& info); - void DefineBuffers(const Info& info); - void DefineImagesAndSamplers(const Info& info); + void DefineInterfaces(); + void DefineInputs(); + void DefineOutputs(); + void DefinePushDataBlock(); + void DefineBuffers(); + void DefineImagesAndSamplers(); void DefineSharedMemory(); SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id); diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 5eadae1b2..2925c05db 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -40,7 +40,7 @@ static IR::Condition MakeCondition(Opcode opcode) { } } -CFG::CFG(ObjectPool& block_pool_, std::span inst_list_) +CFG::CFG(Common::ObjectPool& block_pool_, std::span inst_list_) : block_pool{block_pool_}, inst_list{inst_list_} { index_to_pc.resize(inst_list.size() + 1); EmitLabels(); diff --git a/src/shader_recompiler/frontend/control_flow_graph.h b/src/shader_recompiler/frontend/control_flow_graph.h index 071900871..ebe614ee5 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.h +++ b/src/shader_recompiler/frontend/control_flow_graph.h @@ -8,10 +8,10 @@ #include #include +#include "common/object_pool.h" #include "common/types.h" #include "shader_recompiler/frontend/instruction.h" #include "shader_recompiler/ir/condition.h" -#include "shader_recompiler/object_pool.h" namespace Shader::Gcn { @@ -49,7 +49,7 @@ class CFG { using Label = u32; public: - explicit CFG(ObjectPool& block_pool, std::span inst_list); + explicit CFG(Common::ObjectPool& block_pool, std::span inst_list); [[nodiscard]] std::string Dot() const; @@ -59,7 +59,7 @@ private: void LinkBlocks(); public: - ObjectPool& block_pool; + Common::ObjectPool& block_pool; std::span inst_list; std::vector index_to_pc; boost::container::small_vector labels; diff --git a/src/shader_recompiler/frontend/module.h b/src/shader_recompiler/frontend/module.h deleted file mode 100644 index 3901f0219..000000000 --- a/src/shader_recompiler/frontend/module.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Shader::Gcn { - -void Translate(); - -} // namespace Shader::Gcn \ No newline at end of file diff --git a/src/shader_recompiler/frontend/structured_control_flow.cpp b/src/shader_recompiler/frontend/structured_control_flow.cpp index c8d738580..b50205d46 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/structured_control_flow.cpp @@ -287,7 +287,7 @@ bool NeedsLift(Node goto_stmt, Node label_stmt) noexcept { */ class GotoPass { public: - explicit GotoPass(CFG& cfg, ObjectPool& stmt_pool) : pool{stmt_pool} { + explicit GotoPass(CFG& cfg, Common::ObjectPool& stmt_pool) : pool{stmt_pool} { std::vector gotos{BuildTree(cfg)}; const auto end{gotos.rend()}; for (auto goto_stmt = gotos.rbegin(); goto_stmt != end; ++goto_stmt) { @@ -563,7 +563,7 @@ private: return parent_tree.insert(std::next(loop), *new_goto); } - ObjectPool& pool; + Common::ObjectPool& pool; Statement root_stmt{FunctionTag{}}; }; @@ -597,8 +597,9 @@ private: class TranslatePass { public: - TranslatePass(ObjectPool& inst_pool_, ObjectPool& block_pool_, - ObjectPool& stmt_pool_, Statement& root_stmt, + TranslatePass(Common::ObjectPool& inst_pool_, + Common::ObjectPool& block_pool_, + Common::ObjectPool& stmt_pool_, Statement& root_stmt, IR::AbstractSyntaxList& syntax_list_, std::span inst_list_, Info& info_, const Profile& profile_) : stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, @@ -808,9 +809,9 @@ private: return block_pool.Create(inst_pool); } - ObjectPool& stmt_pool; - ObjectPool& inst_pool; - ObjectPool& block_pool; + Common::ObjectPool& stmt_pool; + Common::ObjectPool& inst_pool; + Common::ObjectPool& block_pool; IR::AbstractSyntaxList& syntax_list; const Block dummy_flow_block{.is_dummy = true}; std::span inst_list; @@ -819,9 +820,10 @@ private: }; } // Anonymous namespace -IR::AbstractSyntaxList BuildASL(ObjectPool& inst_pool, ObjectPool& block_pool, - CFG& cfg, Info& info, const Profile& profile) { - ObjectPool stmt_pool{64}; +IR::AbstractSyntaxList BuildASL(Common::ObjectPool& inst_pool, + Common::ObjectPool& block_pool, CFG& cfg, Info& info, + const Profile& profile) { + Common::ObjectPool stmt_pool{64}; GotoPass goto_pass{cfg, stmt_pool}; Statement& root{goto_pass.RootStatement()}; IR::AbstractSyntaxList syntax_list; diff --git a/src/shader_recompiler/frontend/structured_control_flow.h b/src/shader_recompiler/frontend/structured_control_flow.h index da4ef1ff0..f5a540518 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.h +++ b/src/shader_recompiler/frontend/structured_control_flow.h @@ -7,7 +7,6 @@ #include "shader_recompiler/ir/abstract_syntax_list.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/value.h" -#include "shader_recompiler/object_pool.h" namespace Shader { struct Info; @@ -16,8 +15,8 @@ struct Profile; namespace Shader::Gcn { -[[nodiscard]] IR::AbstractSyntaxList BuildASL(ObjectPool& inst_pool, - ObjectPool& block_pool, CFG& cfg, +[[nodiscard]] IR::AbstractSyntaxList BuildASL(Common::ObjectPool& inst_pool, + Common::ObjectPool& block_pool, CFG& cfg, Info& info, const Profile& profile); } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index e8c2a31c9..b295c1bef 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -447,6 +447,7 @@ void Translator::EmitFetch(const GcnInst& inst) { .is_instance_data = true, }); instance_buf_handle = s32(info.buffers.size() - 1); + info.uses_step_rates = true; } const u32 num_components = AmdGpu::NumComponents(buffer.GetDataFmt()); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index c667968a4..3c6dfbda4 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -338,6 +338,11 @@ void Translator::BUFFER_LOAD_FORMAT(u32 num_dwords, bool is_typed, bool is_forma if (is_typed) { info.dmft.Assign(static_cast(mtbuf.dfmt)); info.nfmt.Assign(static_cast(mtbuf.nfmt)); + ASSERT(info.nfmt == AmdGpu::NumberFormat::Float && + (info.dmft == AmdGpu::DataFormat::Format32_32_32_32 || + info.dmft == AmdGpu::DataFormat::Format32_32_32 || + info.dmft == AmdGpu::DataFormat::Format32_32 || + info.dmft == AmdGpu::DataFormat::Format32)); } const IR::Value handle = diff --git a/src/shader_recompiler/ir/basic_block.cpp b/src/shader_recompiler/ir/basic_block.cpp index 622a6249d..60ba0647a 100644 --- a/src/shader_recompiler/ir/basic_block.cpp +++ b/src/shader_recompiler/ir/basic_block.cpp @@ -9,7 +9,7 @@ namespace Shader::IR { -Block::Block(ObjectPool& inst_pool_) : inst_pool{&inst_pool_} {} +Block::Block(Common::ObjectPool& inst_pool_) : inst_pool{&inst_pool_} {} Block::~Block() = default; diff --git a/src/shader_recompiler/ir/basic_block.h b/src/shader_recompiler/ir/basic_block.h index 5a7036c62..1eb11469c 100644 --- a/src/shader_recompiler/ir/basic_block.h +++ b/src/shader_recompiler/ir/basic_block.h @@ -9,10 +9,10 @@ #include #include +#include "common/object_pool.h" #include "common/types.h" #include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/value.h" -#include "shader_recompiler/object_pool.h" namespace Shader::IR { @@ -25,7 +25,7 @@ public: using reverse_iterator = InstructionList::reverse_iterator; using const_reverse_iterator = InstructionList::const_reverse_iterator; - explicit Block(ObjectPool& inst_pool_); + explicit Block(Common::ObjectPool& inst_pool_); ~Block(); Block(const Block&) = delete; @@ -153,7 +153,7 @@ public: private: /// Memory pool for instruction list - ObjectPool* inst_pool; + Common::ObjectPool* inst_pool; /// List of instructions in this block InstructionList instructions; diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index eaca8ce8c..97438f80a 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -173,10 +173,9 @@ bool IsImageStorageInstruction(const IR::Inst& inst) { class Descriptors { public: - explicit Descriptors(BufferResourceList& buffer_resources_, ImageResourceList& image_resources_, - SamplerResourceList& sampler_resources_) - : buffer_resources{buffer_resources_}, image_resources{image_resources_}, - sampler_resources{sampler_resources_} {} + explicit Descriptors(Info& info_) + : info{info_}, buffer_resources{info_.buffers}, image_resources{info_.images}, + sampler_resources{info_.samplers} {} u32 Add(const BufferResource& desc) { const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) { @@ -188,6 +187,7 @@ public: ASSERT(buffer.length == desc.length); buffer.is_storage |= desc.is_storage; buffer.used_types |= desc.used_types; + buffer.is_written |= desc.is_written; return index; } @@ -201,9 +201,16 @@ public: } u32 Add(const SamplerResource& desc) { - const u32 index{Add(sampler_resources, desc, [&desc](const auto& existing) { - return desc.sgpr_base == existing.sgpr_base && - desc.dword_offset == existing.dword_offset; + const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) { + if (desc.sgpr_base == existing.sgpr_base && + desc.dword_offset == existing.dword_offset) { + return true; + } + // Samplers with different bindings might still be the same. + const auto old_sharp = + info.ReadUd(existing.sgpr_base, existing.dword_offset); + const auto new_sharp = info.ReadUd(desc.sgpr_base, desc.dword_offset); + return old_sharp == new_sharp; })}; return index; } @@ -219,6 +226,7 @@ private: return static_cast(descriptors.size()) - 1; } + const Info& info; BufferResourceList& buffer_resources; ImageResourceList& image_resources; SamplerResourceList& sampler_resources; @@ -328,16 +336,6 @@ static bool IsLoadBufferFormat(const IR::Inst& inst) { } } -static bool IsReadConstBuffer(const IR::Inst& inst) { - switch (inst.GetOpcode()) { - case IR::Opcode::ReadConstBuffer: - case IR::Opcode::ReadConstBufferU32: - return true; - default: - return false; - } -} - static u32 BufferLength(const AmdGpu::Buffer& buffer) { const auto stride = buffer.GetStride(); if (stride < sizeof(f32)) { @@ -401,30 +399,37 @@ void PatchBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, IR::Inst* handle = inst.Arg(0).InstRecursive(); IR::Inst* producer = handle->Arg(0).InstRecursive(); const auto sharp = TrackSharp(producer); + const bool is_store = IsBufferStore(inst); buffer = info.ReadUd(sharp.sgpr_base, sharp.dword_offset); binding = descriptors.Add(BufferResource{ .sgpr_base = sharp.sgpr_base, .dword_offset = sharp.dword_offset, .length = BufferLength(buffer), .used_types = BufferDataType(inst, buffer.GetNumberFmt()), - .is_storage = IsBufferStore(inst) || buffer.GetSize() > MaxUboSize, + .is_storage = is_store || buffer.GetSize() > MaxUboSize, + .is_written = is_store, }); } + // Update buffer descriptor format. const auto inst_info = inst.Flags(); - IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; - // Replace handle with binding index in buffer resource list. - inst.SetArg(0, ir.Imm32(binding)); - ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable); + auto& buffer_desc = info.buffers[binding]; if (inst_info.is_typed) { - ASSERT(inst_info.nfmt == AmdGpu::NumberFormat::Float && - (inst_info.dmft == AmdGpu::DataFormat::Format32_32_32_32 || - inst_info.dmft == AmdGpu::DataFormat::Format32_32_32 || - inst_info.dmft == AmdGpu::DataFormat::Format32_32 || - inst_info.dmft == AmdGpu::DataFormat::Format32)); + buffer_desc.dfmt = inst_info.dmft; + buffer_desc.nfmt = inst_info.nfmt; + } else { + buffer_desc.dfmt = buffer.GetDataFmt(); + buffer_desc.nfmt = buffer.GetNumberFmt(); } - if (IsReadConstBuffer(inst)) { + // Replace handle with binding index in buffer resource list. + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + inst.SetArg(0, ir.Imm32(binding)); + ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable); + + // Address of constant buffer reads can be calculated at IR emittion time. + if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer || + inst.GetOpcode() == IR::Opcode::ReadConstBufferU32) { return; } @@ -434,10 +439,14 @@ void PatchBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, } } else { const u32 stride = buffer.GetStride(); - ASSERT_MSG(stride >= 4, "non-formatting load_buffer_* is not implemented for stride {}", - stride); + if (stride < 4) { + LOG_WARNING(Render_Vulkan, + "non-formatting load_buffer_* is not implemented for stride {}", stride); + } } + // Compute address of the buffer using the stride. + // Todo: What if buffer is rebound with different stride? IR::U32 address = ir.Imm32(inst_info.inst_offset.Value()); if (inst_info.index_enable) { const IR::U32 index = inst_info.offset_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 0)} @@ -587,39 +596,9 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } void ResourceTrackingPass(IR::Program& program) { - // When loading data from untyped buffer we don't have if it is float or integer. - // Most of the time it is float so that is the default. This pass detects float buffer loads - // combined with bitcasts and patches them to be integer loads. - for (IR::Block* const block : program.post_order_blocks) { - break; - for (IR::Inst& inst : block->Instructions()) { - if (inst.GetOpcode() != IR::Opcode::BitCastU32F32) { - continue; - } - // Replace the bitcast with a typed buffer read - IR::Inst* const arg_inst{inst.Arg(0).TryInstRecursive()}; - if (!arg_inst) { - continue; - } - const auto replace{[&](IR::Opcode new_opcode) { - inst.ReplaceOpcode(new_opcode); - inst.SetArg(0, arg_inst->Arg(0)); - inst.SetArg(1, arg_inst->Arg(1)); - inst.SetFlags(arg_inst->Flags()); - arg_inst->Invalidate(); - }}; - if (arg_inst->GetOpcode() == IR::Opcode::ReadConstBuffer) { - replace(IR::Opcode::ReadConstBufferU32); - } - if (arg_inst->GetOpcode() == IR::Opcode::LoadBufferF32) { - replace(IR::Opcode::LoadBufferU32); - } - } - } - // Iterate resource instructions and patch them after finding the sharp. auto& info = program.info; - Descriptors descriptors{info.buffers, info.images, info.samplers}; + Descriptors descriptors{info}; for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (IsBufferInstruction(inst)) { diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index d747c016b..69eec50f3 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -27,9 +27,9 @@ IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) { return blocks; } -IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, - std::span token, const Info&& info, - const Profile& profile) { +IR::Program TranslateProgram(Common::ObjectPool& inst_pool, + Common::ObjectPool& block_pool, std::span token, + const Info&& info, const Profile& profile) { // Ensure first instruction is expected. constexpr u32 token_mov_vcchi = 0xBEEB03FF; ASSERT_MSG(token[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); @@ -45,7 +45,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool gcn_block_pool{64}; + Common::ObjectPool gcn_block_pool{64}; Gcn::CFG cfg{gcn_block_pool, program.ins_list}; // Structurize control flow graph and create program. @@ -61,7 +61,7 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& inst_pool, - ObjectPool& block_pool, +[[nodiscard]] IR::Program TranslateProgram(Common::ObjectPool& inst_pool, + Common::ObjectPool& block_pool, std::span code, const Info&& info, const Profile& profile); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 277c38b72..4ab71c3b7 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -77,8 +77,11 @@ struct BufferResource { u32 length; IR::Type used_types; AmdGpu::Buffer inline_cbuf; - bool is_storage{false}; - bool is_instance_data{false}; + AmdGpu::DataFormat dfmt; + AmdGpu::NumberFormat nfmt; + bool is_storage{}; + bool is_instance_data{}; + bool is_written{}; constexpr AmdGpu::Buffer GetVsharp(const Info& info) const noexcept; }; @@ -105,6 +108,19 @@ struct SamplerResource { }; using SamplerResourceList = boost::container::static_vector; +struct PushData { + static constexpr size_t BufOffsetIndex = 2; + + u32 step0; + u32 step1; + std::array buf_offsets; + + void AddOffset(u32 binding, u32 offset) { + ASSERT(offset < 64 && binding < 32); + buf_offsets[binding] = offset; + } +}; + struct Info { struct VsInput { enum InstanceIdType : u8 { @@ -182,6 +198,7 @@ struct Info { bool uses_shared_u8{}; bool uses_shared_u16{}; bool uses_fp16{}; + bool uses_step_rates{}; bool translation_failed{}; // indicates that shader has unsupported instructions template diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 400af0315..3ebd9a971 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -496,7 +496,7 @@ struct Liverpool { template T Address() const { - return reinterpret_cast((base_addr_lo & ~1U) | u64(base_addr_hi) << 32); + return std::bit_cast((base_addr_lo & ~1U) | u64(base_addr_hi) << 32); } }; diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 01271792b..ef5bf1b66 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -363,6 +363,10 @@ struct Sampler { return raw0 != 0 || raw1 != 0; } + bool operator==(const Sampler& other) const noexcept { + return std::memcmp(this, &other, sizeof(Sampler)) == 0; + } + float LodBias() const noexcept { return static_cast(static_cast((lod_bias.Value() ^ 0x2000u) - 0x2000u)) / 256.0f; diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp new file mode 100644 index 000000000..e9498b352 --- /dev/null +++ b/src/video_core/buffer_cache/buffer.cpp @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "common/assert.h" +#include "video_core/buffer_cache/buffer.h" +#include "video_core/renderer_vulkan/liverpool_to_vk.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_platform.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" + +#include + +namespace VideoCore { + +constexpr vk::BufferUsageFlags AllFlags = + vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | + vk::BufferUsageFlagBits::eUniformTexelBuffer | vk::BufferUsageFlagBits::eStorageTexelBuffer | + vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer | + vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer; + +std::string_view BufferTypeName(MemoryUsage type) { + switch (type) { + case MemoryUsage::Upload: + return "Upload"; + case MemoryUsage::Download: + return "Download"; + case MemoryUsage::Stream: + return "Stream"; + case MemoryUsage::DeviceLocal: + return "DeviceLocal"; + default: + return "Invalid"; + } +} + +[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { + return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + : VkMemoryPropertyFlagBits{}; +} + +[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::Upload: + case MemoryUsage::Stream: + return VMA_ALLOCATION_CREATE_MAPPED_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + case MemoryUsage::Download: + return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + case MemoryUsage::DeviceLocal: + return {}; + } + return {}; +} + +[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + case MemoryUsage::Stream: + return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + case MemoryUsage::Upload: + case MemoryUsage::Download: + return VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + } + return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; +} + +UniqueBuffer::UniqueBuffer(vk::Device device_, VmaAllocator allocator_) + : device{device_}, allocator{allocator_} {} + +UniqueBuffer::~UniqueBuffer() { + if (buffer) { + vmaDestroyBuffer(allocator, buffer, allocation); + } +} + +void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usage, + VmaAllocationInfo* out_alloc_info) { + const VmaAllocationCreateInfo alloc_ci = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), + .usage = MemoryUsageVma(usage), + .requiredFlags = 0, + .preferredFlags = MemoryUsagePreferredVmaFlags(usage), + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + }; + + const VkBufferCreateInfo buffer_ci_unsafe = static_cast(buffer_ci); + VkBuffer unsafe_buffer{}; + VkResult result = vmaCreateBuffer(allocator, &buffer_ci_unsafe, &alloc_ci, &unsafe_buffer, + &allocation, out_alloc_info); + ASSERT_MSG(result == VK_SUCCESS, "Failed allocating buffer with error {}", + vk::to_string(vk::Result{result})); + buffer = vk::Buffer{unsafe_buffer}; +} + +Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_addr_, + u64 size_bytes_) + : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, usage{usage_}, + buffer{instance->GetDevice(), instance->GetAllocator()} { + // Create buffer object. + const vk::BufferCreateInfo buffer_ci = { + .size = size_bytes, + .usage = AllFlags, + }; + VmaAllocationInfo alloc_info{}; + buffer.Create(buffer_ci, usage, &alloc_info); + + if (instance->HasDebuggingToolAttached()) { + const auto device = instance->GetDevice(); + Vulkan::SetObjectName(device, Handle(), "Buffer {:#x} {} KiB", cpu_addr, size_bytes / 1024); + } + + // Map it if it is host visible. + VkMemoryPropertyFlags property_flags{}; + vmaGetAllocationMemoryProperties(instance->GetAllocator(), buffer.allocation, &property_flags); + if (alloc_info.pMappedData) { + mapped_data = std::span{std::bit_cast(alloc_info.pMappedData), size_bytes}; + } + is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +} + +vk::BufferView Buffer::View(u32 offset, u32 size, AmdGpu::DataFormat dfmt, + AmdGpu::NumberFormat nfmt) { + const auto it{std::ranges::find_if(views, [offset, size, dfmt, nfmt](const BufferView& view) { + return offset == view.offset && size == view.size && dfmt == view.dfmt && nfmt == view.nfmt; + })}; + if (it != views.end()) { + return it->handle; + } + views.push_back({ + .offset = offset, + .size = size, + .dfmt = dfmt, + .nfmt = nfmt, + .handle = instance->GetDevice().createBufferView({ + .buffer = buffer.buffer, + .format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt), + .offset = offset, + .range = size, + }), + }); + return views.back().handle; +} + +constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; +constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; + +StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_, + MemoryUsage usage, u64 size_bytes) + : Buffer{instance, usage, 0, size_bytes}, scheduler{scheduler_} { + ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); + ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); + const auto device = instance.GetDevice(); + if (instance.HasDebuggingToolAttached()) { + Vulkan::SetObjectName(device, Handle(), "StreamBuffer({}): {} KiB", BufferTypeName(usage), + size_bytes / 1024); + } +} + +std::pair StreamBuffer::Map(u64 size, u64 alignment) { + if (!is_coherent && usage == MemoryUsage::Stream) { + size = Common::AlignUp(size, instance->NonCoherentAtomSize()); + } + + ASSERT(size <= this->size_bytes); + mapped_size = size; + + if (alignment > 0) { + offset = Common::AlignUp(offset, alignment); + } + + if (offset + size > this->size_bytes) { + // The buffer would overflow, save the amount of used watches and reset the state. + invalidation_mark = current_watch_cursor; + current_watch_cursor = 0; + offset = 0; + + // Swap watches and reset waiting cursors. + std::swap(previous_watches, current_watches); + wait_cursor = 0; + wait_bound = 0; + } + + const u64 mapped_upper_bound = offset + size; + WaitPendingOperations(mapped_upper_bound); + return std::make_pair(mapped_data.data() + offset, offset); +} + +void StreamBuffer::Commit() { + if (!is_coherent) { + if (usage == MemoryUsage::Download) { + vmaInvalidateAllocation(instance->GetAllocator(), buffer.allocation, offset, + mapped_size); + } else { + vmaFlushAllocation(instance->GetAllocator(), buffer.allocation, offset, mapped_size); + } + } + + offset += mapped_size; + if (current_watch_cursor + 1 >= current_watches.size()) { + // Ensure that there are enough watches. + ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); + } + + auto& watch = current_watches[current_watch_cursor++]; + watch.upper_bound = offset; + watch.tick = scheduler.CurrentTick(); +} + +void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { + watches.resize(watches.size() + grow_size); +} + +void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { + if (!invalidation_mark) { + return; + } + while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { + auto& watch = previous_watches[wait_cursor]; + wait_bound = watch.upper_bound; + scheduler.Wait(watch.tick); + ++wait_cursor; + } +} + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h new file mode 100644 index 000000000..e0d9da08f --- /dev/null +++ b/src/video_core/buffer_cache/buffer.h @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/types.h" +#include "video_core/amdgpu/resource.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +VK_DEFINE_HANDLE(VmaAllocation) +VK_DEFINE_HANDLE(VmaAllocator) + +struct VmaAllocationInfo; + +namespace VideoCore { + +/// Hints and requirements for the backing memory type of a commit +enum class MemoryUsage { + DeviceLocal, ///< Requests device local buffer. + Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads + Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks + Stream, ///< Requests device local host visible buffer, falling back host memory. +}; + +struct UniqueBuffer { + explicit UniqueBuffer(vk::Device device, VmaAllocator allocator); + ~UniqueBuffer(); + + UniqueBuffer(const UniqueBuffer&) = delete; + UniqueBuffer& operator=(const UniqueBuffer&) = delete; + + UniqueBuffer(UniqueBuffer&& other) + : buffer{std::exchange(other.buffer, VK_NULL_HANDLE)}, + allocator{std::exchange(other.allocator, VK_NULL_HANDLE)}, + allocation{std::exchange(other.allocation, VK_NULL_HANDLE)} {} + UniqueBuffer& operator=(UniqueBuffer&& other) { + buffer = std::exchange(other.buffer, VK_NULL_HANDLE); + allocator = std::exchange(other.allocator, VK_NULL_HANDLE); + allocation = std::exchange(other.allocation, VK_NULL_HANDLE); + return *this; + } + + void Create(const vk::BufferCreateInfo& image_ci, MemoryUsage usage, + VmaAllocationInfo* out_alloc_info); + + operator vk::Buffer() const { + return buffer; + } + + vk::Device device; + VmaAllocator allocator; + VmaAllocation allocation; + vk::Buffer buffer{}; +}; + +class Buffer { +public: + explicit Buffer(const Vulkan::Instance& instance, MemoryUsage usage, VAddr cpu_addr_, + u64 size_bytes_); + + Buffer& operator=(const Buffer&) = delete; + Buffer(const Buffer&) = delete; + + Buffer& operator=(Buffer&&) = default; + Buffer(Buffer&&) = default; + + vk::BufferView View(u32 offset, u32 size, AmdGpu::DataFormat dfmt, AmdGpu::NumberFormat nfmt); + + /// Increases the likeliness of this being a stream buffer + void IncreaseStreamScore(int score) noexcept { + stream_score += score; + } + + /// Returns the likeliness of this being a stream buffer + [[nodiscard]] int StreamScore() const noexcept { + return stream_score; + } + + /// Returns true when vaddr -> vaddr+size is fully contained in the buffer + [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept { + return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes(); + } + + /// Returns the base CPU address of the buffer + [[nodiscard]] VAddr CpuAddr() const noexcept { + return cpu_addr; + } + + /// Returns the offset relative to the given CPU address + [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept { + return static_cast(other_cpu_addr - cpu_addr); + } + + size_t SizeBytes() const { + return size_bytes; + } + + vk::Buffer Handle() const noexcept { + return buffer; + } + +public: + VAddr cpu_addr = 0; + bool is_picked{}; + bool is_coherent{}; + int stream_score = 0; + size_t size_bytes = 0; + std::span mapped_data; + const Vulkan::Instance* instance{}; + MemoryUsage usage; + UniqueBuffer buffer; + struct BufferView { + u32 offset; + u32 size; + AmdGpu::DataFormat dfmt; + AmdGpu::NumberFormat nfmt; + vk::BufferView handle; + }; + std::vector views; +}; + +class StreamBuffer : public Buffer { +public: + explicit StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + MemoryUsage usage, u64 size_bytes_); + + /// Reserves a region of memory from the stream buffer. + std::pair Map(u64 size, u64 alignment = 0); + + /// Ensures that reserved bytes of memory are available to the GPU. + void Commit(); + + /// Maps and commits a memory region with user provided data + u64 Copy(VAddr src, size_t size, size_t alignment = 0) { + const auto [data, offset] = Map(size, alignment); + std::memcpy(data, reinterpret_cast(src), size); + Commit(); + return offset; + } + +private: + struct Watch { + u64 tick{}; + u64 upper_bound{}; + }; + + /// Increases the amount of watches available. + void ReserveWatches(std::vector& watches, std::size_t grow_size); + + /// Waits pending watches until requested upper bound. + void WaitPendingOperations(u64 requested_upper_bound); + +private: + Vulkan::Scheduler& scheduler; + u64 offset{}; + u64 mapped_size{}; + std::vector current_watches; + std::size_t current_watch_cursor{}; + std::optional invalidation_mark; + std::vector previous_watches; + std::size_t wait_cursor{}; + u64 wait_bound{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp new file mode 100644 index 000000000..7ab0d8171 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -0,0 +1,497 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "shader_recompiler/runtime_info.h" +#include "video_core/amdgpu/liverpool.h" +#include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/renderer_vulkan/liverpool_to_vk.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" + +namespace VideoCore { + +static constexpr size_t StagingBufferSize = 256_MB; +static constexpr size_t UboStreamBufferSize = 64_MB; + +BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, + const AmdGpu::Liverpool* liverpool_, PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, tracker{tracker_}, + staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, + stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, + memory_tracker{&tracker} { + // Ensure the first slot is used for the null buffer + void(slot_buffers.insert(instance, MemoryUsage::DeviceLocal, 0, 1)); +} + +BufferCache::~BufferCache() = default; + +void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { + std::scoped_lock lk{mutex}; + const bool is_tracked = IsRegionRegistered(device_addr, size); + if (!is_tracked) { + return; + } + // Mark the page as CPU modified to stop tracking writes. + SCOPE_EXIT { + memory_tracker.MarkRegionAsCpuModified(device_addr, size); + }; + if (!memory_tracker.IsRegionGpuModified(device_addr, size)) { + // Page has not been modified by the GPU, nothing to do. + return; + } +} + +void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { + boost::container::small_vector copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + memory_tracker.ForEachDownloadRange( + device_addr, size, [&](u64 device_addr_out, u64 range_size) { + const VAddr buffer_addr = buffer.CpuAddr(); + const auto add_download = [&](VAddr start, VAddr end, u64) { + const u64 new_offset = start - buffer_addr; + const u64 new_size = end - start; + copies.push_back(vk::BufferCopy{ + .srcOffset = new_offset, + .dstOffset = total_size_bytes, + .size = new_size, + }); + // Align up to avoid cache conflicts + constexpr u64 align = 64ULL; + constexpr u64 mask = ~(align - 1ULL); + total_size_bytes += (new_size + align - 1) & mask; + largest_copy = std::max(largest_copy, new_size); + }; + }); + if (total_size_bytes == 0) { + return; + } + const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + for (auto& copy : copies) { + // Modify copies to have the staging offset in mind + copy.dstOffset += offset; + } + staging_buffer.Commit(); + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyBuffer(buffer.buffer, staging_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); + } +} + +bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { + if (vs_info.vs_inputs.empty()) { + return false; + } + + std::array host_buffers; + std::array host_offsets; + boost::container::static_vector guest_buffers; + + struct BufferRange { + VAddr base_address; + VAddr end_address; + vk::Buffer vk_buffer; + u64 offset; + + size_t GetSize() const { + return end_address - base_address; + } + }; + + // Calculate buffers memory overlaps + bool has_step_rate = false; + boost::container::static_vector ranges{}; + for (const auto& input : vs_info.vs_inputs) { + if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || + input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) { + has_step_rate = true; + continue; + } + + const auto& buffer = vs_info.ReadUd(input.sgpr_base, input.dword_offset); + if (buffer.GetSize() == 0) { + continue; + } + guest_buffers.emplace_back(buffer); + ranges.emplace_back(buffer.base_address, buffer.base_address + buffer.GetSize()); + } + + std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { + return lhv.base_address < rhv.base_address; + }); + + boost::container::static_vector ranges_merged{ranges[0]}; + for (auto range : ranges) { + auto& prev_range = ranges_merged.back(); + if (prev_range.end_address < range.base_address) { + ranges_merged.emplace_back(range); + } else { + prev_range.end_address = std::max(prev_range.end_address, range.end_address); + } + } + + // Map buffers + for (auto& range : ranges_merged) { + const auto [buffer, offset] = ObtainBuffer(range.base_address, range.GetSize(), false); + range.vk_buffer = buffer->buffer; + range.offset = offset; + } + + // Bind vertex buffers + const size_t num_buffers = guest_buffers.size(); + for (u32 i = 0; i < num_buffers; ++i) { + const auto& buffer = guest_buffers[i]; + const auto host_buffer = std::ranges::find_if(ranges_merged, [&](const BufferRange& range) { + return (buffer.base_address >= range.base_address && + buffer.base_address < range.end_address); + }); + ASSERT(host_buffer != ranges_merged.cend()); + + host_buffers[i] = host_buffer->vk_buffer; + host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address; + } + + if (num_buffers > 0) { + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data()); + } + + return has_step_rate; +} + +u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { + // Emulate QuadList primitive type with CPU made index buffer. + const auto& regs = liverpool->regs; + if (regs.primitive_type == AmdGpu::Liverpool::PrimitiveType::QuadList) { + is_indexed = true; + + // Emit indices. + const u32 index_size = 3 * regs.num_indices; + const auto [data, offset] = stream_buffer.Map(index_size); + Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); + stream_buffer.Commit(); + + // Bind index buffer. + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); + return index_size / sizeof(u16); + } + if (!is_indexed) { + return regs.num_indices; + } + + // Figure out index type and size. + const bool is_index16 = + regs.index_buffer_type.index_type == AmdGpu::Liverpool::IndexType::Index16; + const vk::IndexType index_type = is_index16 ? vk::IndexType::eUint16 : vk::IndexType::eUint32; + const u32 index_size = is_index16 ? sizeof(u16) : sizeof(u32); + VAddr index_address = regs.index_base_address.Address(); + index_address += index_offset * index_size; + + // Bind index buffer. + const u32 index_buffer_size = regs.num_indices * index_size; + const auto [vk_buffer, offset] = ObtainBuffer(index_address, index_buffer_size, false); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.bindIndexBuffer(vk_buffer->Handle(), offset, index_type); + return regs.num_indices; +} + +std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written) { + std::scoped_lock lk{mutex}; + 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) { + // For small uniform buffers that have not been modified by gpu + // use device local stream buffer to reduce renderpass breaks. + const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); + return {&stream_buffer, offset}; + } + + const BufferId buffer_id = FindBuffer(device_addr, size); + Buffer& buffer = slot_buffers[buffer_id]; + SynchronizeBuffer(buffer, device_addr, size); + if (is_written) { + memory_tracker.MarkRegionAsGpuModified(device_addr, size); + } + return {&buffer, buffer.Offset(device_addr)}; +} + +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; + } + 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(end_addr, CACHING_PAGESIZE); + } + return false; +} + +bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { + return memory_tracker.IsRegionCpuModified(addr, size); +} + +BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { + if (device_addr == 0) { + return NULL_BUFFER_ID; + } + const u64 page = device_addr >> CACHING_PAGEBITS; + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + return CreateBuffer(device_addr, size); + } + const Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(device_addr, size)) { + return buffer_id; + } + return CreateBuffer(device_addr, size); +} + +BufferCache::OverlapResult BufferCache::ResolveOverlaps(VAddr device_addr, u32 wanted_size) { + static constexpr int STREAM_LEAP_THRESHOLD = 16; + boost::container::small_vector overlap_ids; + VAddr begin = device_addr; + VAddr end = device_addr + wanted_size; + int stream_score = 0; + bool has_stream_leap = false; + const auto expand_begin = [&](VAddr add_value) { + static constexpr VAddr min_page = CACHING_PAGESIZE + DEVICE_PAGESIZE; + if (add_value > begin - min_page) { + begin = min_page; + device_addr = DEVICE_PAGESIZE; + return; + } + begin -= add_value; + device_addr = begin - CACHING_PAGESIZE; + }; + const auto expand_end = [&](VAddr add_value) { + static constexpr VAddr max_page = 1ULL << MemoryTracker::MAX_CPU_PAGE_BITS; + if (add_value > max_page - end) { + end = max_page; + return; + } + end += add_value; + }; + if (begin == 0) { + return OverlapResult{ + .ids = std::move(overlap_ids), + .begin = begin, + .end = end, + .has_stream_leap = has_stream_leap, + }; + } + for (; device_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE); + device_addr += CACHING_PAGESIZE) { + const BufferId overlap_id = page_table[device_addr >> CACHING_PAGEBITS]; + if (!overlap_id) { + continue; + } + Buffer& overlap = slot_buffers[overlap_id]; + if (overlap.is_picked) { + continue; + } + overlap_ids.push_back(overlap_id); + overlap.is_picked = true; + const VAddr overlap_device_addr = overlap.CpuAddr(); + const bool expands_left = overlap_device_addr < begin; + if (expands_left) { + begin = overlap_device_addr; + } + const VAddr overlap_end = overlap_device_addr + overlap.SizeBytes(); + const bool expands_right = overlap_end > end; + if (overlap_end > end) { + end = overlap_end; + } + stream_score += overlap.StreamScore(); + if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) { + // When this memory region has been joined a bunch of times, we assume it's being used + // as a stream buffer. Increase the size to skip constantly recreating buffers. + has_stream_leap = true; + if (expands_right) { + expand_begin(CACHING_PAGESIZE * 128); + } + if (expands_left) { + expand_end(CACHING_PAGESIZE * 128); + } + } + } + return OverlapResult{ + .ids = std::move(overlap_ids), + .begin = begin, + .end = end, + .has_stream_leap = has_stream_leap, + }; +} + +void BufferCache::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, + bool accumulate_stream_score) { + Buffer& new_buffer = slot_buffers[new_buffer_id]; + Buffer& overlap = slot_buffers[overlap_id]; + if (accumulate_stream_score) { + new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1); + } + const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr(); + const vk::BufferCopy copy = { + .srcOffset = 0, + .dstOffset = dst_base_offset, + .size = overlap.SizeBytes(), + }; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + static constexpr vk::MemoryBarrier READ_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, + }; + static constexpr vk::MemoryBarrier WRITE_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + READ_BARRIER, {}, {}); + cmdbuf.copyBuffer(overlap.buffer, new_buffer.buffer, copy); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); + DeleteBuffer(overlap_id, true); +} + +BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { + const VAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); + device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); + wanted_size = static_cast(device_addr_end - device_addr); + const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); + const u32 size = static_cast(overlap.end - overlap.begin); + const BufferId new_buffer_id = + slot_buffers.insert(instance, MemoryUsage::DeviceLocal, overlap.begin, size); + auto& new_buffer = slot_buffers[new_buffer_id]; + const size_t size_bytes = new_buffer.SizeBytes(); + const auto cmdbuf = scheduler.CommandBuffer(); + scheduler.EndRendering(); + cmdbuf.fillBuffer(new_buffer.buffer, 0, size_bytes, 0); + for (const BufferId overlap_id : overlap.ids) { + JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); + } + Register(new_buffer_id); + return new_buffer_id; +} + +void BufferCache::Register(BufferId buffer_id) { + ChangeRegister(buffer_id); +} + +void BufferCache::Unregister(BufferId buffer_id) { + ChangeRegister(buffer_id); +} + +template +void BufferCache::ChangeRegister(BufferId buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + const auto size = buffer.SizeBytes(); + const VAddr device_addr_begin = buffer.CpuAddr(); + const VAddr device_addr_end = device_addr_begin + size; + const u64 page_begin = device_addr_begin / CACHING_PAGESIZE; + 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; + } else { + page_table[page] = BufferId{}; + } + } +} + +bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) { + boost::container::small_vector copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + VAddr buffer_start = buffer.CpuAddr(); + const auto add_copy = [&](VAddr device_addr_out, u64 range_size) { + copies.push_back(vk::BufferCopy{ + .srcOffset = total_size_bytes, + .dstOffset = device_addr_out - buffer_start, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }; + memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { + add_copy(device_addr_out, range_size); + // Prevent uploading to gpu modified regions. + // gpu_modified_ranges.ForEachNotInRange(device_addr_out, range_size, add_copy); + }); + if (total_size_bytes == 0) { + return true; + } + vk::Buffer src_buffer = staging_buffer.Handle(); + if (total_size_bytes < StagingBufferSize) { + const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + for (auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + // Apply the staging offset + 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, MemoryUsage::Upload, 0, total_size_bytes}; + src_buffer = temp_buffer.Handle(); + u8* const staging = temp_buffer.mapped_data.data(); + for (auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + } + scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable {}); + } + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + static constexpr vk::MemoryBarrier READ_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, + }; + static constexpr vk::MemoryBarrier WRITE_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + READ_BARRIER, {}, {}); + cmdbuf.copyBuffer(src_buffer, buffer.buffer, copies); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); + return false; +} + +void BufferCache::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { + // Mark the whole buffer as CPU written to stop tracking CPU writes + if (!do_not_mark) { + Buffer& buffer = slot_buffers[buffer_id]; + memory_tracker.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes()); + } + Unregister(buffer_id); + scheduler.DeferOperation([this, buffer_id] { slot_buffers.erase(buffer_id); }); +} + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h new file mode 100644 index 000000000..0dee87cf5 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include "common/div_ceil.h" +#include "common/slot_vector.h" +#include "common/types.h" +#include "video_core/buffer_cache/buffer.h" +#include "video_core/buffer_cache/memory_tracker_base.h" +#include "video_core/multi_level_page_table.h" + +namespace AmdGpu { +struct Liverpool; +} + +namespace Shader { +struct Info; +} + +namespace VideoCore { + +using BufferId = Common::SlotId; + +static constexpr BufferId NULL_BUFFER_ID{0}; + +static constexpr u32 NUM_VERTEX_BUFFERS = 32; + +class BufferCache { +public: + static constexpr u32 CACHING_PAGEBITS = 12; + static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS; + static constexpr u64 DEVICE_PAGESIZE = 4_KB; + + struct Traits { + using Entry = BufferId; + static constexpr size_t AddressSpaceBits = 39; + static constexpr size_t FirstLevelBits = 14; + static constexpr size_t PageBits = CACHING_PAGEBITS; + }; + using PageTable = MultiLevelPageTable; + + struct OverlapResult { + boost::container::small_vector ids; + VAddr begin; + VAddr end; + bool has_stream_leap = false; + }; + +public: + explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + const AmdGpu::Liverpool* liverpool, PageManager& tracker); + ~BufferCache(); + + /// Invalidates any buffer in the logical page range. + void InvalidateMemory(VAddr device_addr, u64 size); + + /// Binds host vertex buffers for the current draw. + bool BindVertexBuffers(const Shader::Info& vs_info); + + /// Bind host index buffer for the current draw. + u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); + + /// Obtains a buffer for the specified region. + [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written); + + /// Return true when a region is registered on the cache + [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); + + /// Return true when a CPU region is modified from the CPU + [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size); + +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); + } + } + + void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); + + [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); + + [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); + + void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); + + [[nodiscard]] BufferId CreateBuffer(VAddr device_addr, u32 wanted_size); + + void Register(BufferId buffer_id); + + void Unregister(BufferId buffer_id); + + template + void ChangeRegister(BufferId buffer_id); + + bool SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size); + + void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); + + const Vulkan::Instance& instance; + Vulkan::Scheduler& scheduler; + const AmdGpu::Liverpool* liverpool; + PageManager& tracker; + StreamBuffer staging_buffer; + StreamBuffer stream_buffer; + std::recursive_mutex mutex; + Common::SlotVector slot_buffers; + MemoryTracker memory_tracker; + PageTable page_table; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h new file mode 100644 index 000000000..375701c4c --- /dev/null +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" +#include "video_core/buffer_cache/word_manager.h" + +namespace VideoCore { + +class MemoryTracker { +public: + static constexpr size_t MAX_CPU_PAGE_BITS = 39; + static constexpr size_t HIGHER_PAGE_BITS = 22; + static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; + static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; + static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS); + static constexpr size_t MANAGER_POOL_SIZE = 32; + static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD; + using Manager = WordManager; + +public: + 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 { + return IteratePages( + query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { + return manager->template IsRegionModified(offset, size); + }); + } + + /// Returns true if a region has been modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { + return IteratePages( + query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { + return manager->template IsRegionModified(offset, size); + }); + } + + /// Mark region as CPU modified, notifying the device_tracker about this change + void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Unmark region as CPU modified, notifying the device_tracker about this change + void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Mark region as modified from the host GPU + void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Unmark region as modified from the host GPU + void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// 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) { + IteratePages(query_cpu_range, query_size, + [&func](Manager* manager, u64 offset, size_t size) { + manager->template ForEachModifiedRange( + manager->GetCpuAddr() + offset, size, func); + }); + } + + /// 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) { + IteratePages(query_cpu_range, query_size, + [&func](Manager* 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); + } + }); + } + +private: + /** + * @brief IteratePages Iterates L2 word manager page table. + * @param cpu_address Start byte cpu address + * @param size Size in bytes of the region of iterate. + * @param func Callback for each word manager. + * @return + */ + template + bool IteratePages(VAddr cpu_address, size_t size, Func&& func) { + using FuncReturn = typename std::invoke_result::type; + static constexpr bool BOOL_BREAK = std::is_same_v; + std::size_t remaining_size{size}; + std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; + u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; + while (remaining_size > 0) { + const std::size_t copy_amount{ + std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; + auto* manager{top_tier[page_index]}; + if (manager) { + if constexpr (BOOL_BREAK) { + if (func(manager, page_offset, copy_amount)) { + return true; + } + } else { + func(manager, page_offset, copy_amount); + } + } else if constexpr (create_region_on_fail) { + CreateRegion(page_index); + manager = top_tier[page_index]; + if constexpr (BOOL_BREAK) { + if (func(manager, page_offset, copy_amount)) { + return true; + } + } else { + func(manager, page_offset, copy_amount); + } + } + page_index++; + page_offset = 0; + remaining_size -= copy_amount; + } + return false; + } + + void CreateRegion(std::size_t page_index) { + const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; + if (free_managers.empty()) { + manager_pool.emplace_back(); + auto& last_pool = manager_pool.back(); + for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) { + std::construct_at(&last_pool[i], tracker, 0, HIGHER_PAGE_SIZE); + free_managers.push_back(&last_pool[i]); + } + } + // Each manager tracks a 4_MB virtual address space. + auto* new_manager = free_managers.back(); + new_manager->SetCpuAddress(base_cpu_addr); + free_managers.pop_back(); + top_tier[page_index] = new_manager; + } + + PageManager* tracker; + std::deque> manager_pool; + std::vector free_managers; + std::array top_tier{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/range_set.h b/src/video_core/buffer_cache/range_set.h new file mode 100644 index 000000000..fe54aff8f --- /dev/null +++ b/src/video_core/buffer_cache/range_set.h @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" + +namespace VideoCore { + +template +using RangeSetsAllocator = + boost::fast_pool_allocator; + +struct RangeSet { + using IntervalSet = + boost::icl::interval_set; + using IntervalType = typename IntervalSet::interval_type; + + explicit RangeSet() = default; + ~RangeSet() = default; + + void Add(VAddr base_address, size_t size) { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + m_ranges_set.add(interval); + } + + void Subtract(VAddr base_address, size_t size) { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + m_ranges_set.subtract(interval); + } + + template + void ForEach(Func&& func) const { + if (m_ranges_set.empty()) { + return; + } + auto it = m_ranges_set.begin(); + auto end_it = m_ranges_set.end(); + for (; it != end_it; it++) { + const VAddr inter_addr_end = it->upper(); + const VAddr inter_addr = it->lower(); + func(inter_addr, inter_addr_end); + } + } + + template + void ForEachInRange(VAddr base_addr, size_t size, Func&& func) const { + if (m_ranges_set.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_set.lower_bound(search_interval); + if (it == m_ranges_set.end()) { + return; + } + auto end_it = m_ranges_set.upper_bound(search_interval); + for (; it != end_it; it++) { + VAddr inter_addr_end = it->upper(); + VAddr inter_addr = it->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); + } + } + + IntervalSet m_ranges_set; +}; + +class RangeMap { +public: + using IntervalMap = + boost::icl::interval_map; + using IntervalType = typename IntervalMap::interval_type; + +public: + RangeMap() = default; + ~RangeMap() = default; + + RangeMap(RangeMap const&) = delete; + RangeMap& operator=(RangeMap const&) = delete; + + RangeMap(RangeMap&& other); + RangeMap& operator=(RangeMap&& other); + + void Add(VAddr base_address, size_t size, u64 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; + } + + 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, u64) { + 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; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h new file mode 100644 index 000000000..549d2a9ed --- /dev/null +++ b/src/video_core/buffer_cache/word_manager.h @@ -0,0 +1,398 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/div_ceil.h" +#include "common/types.h" +#include "video_core/page_manager.h" + +namespace VideoCore { + +constexpr u64 PAGES_PER_WORD = 64; +constexpr u64 BYTES_PER_PAGE = 4_KB; +constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; + +enum class Type { + CPU, + GPU, + Untracked, +}; + +/// Vector tracking modified pages tightly packed with small vector optimization +template +struct WordsArray { + /// Returns the pointer to the words state + [[nodiscard]] const u64* Pointer(bool is_short) const noexcept { + return is_short ? stack.data() : heap; + } + + /// Returns the pointer to the words state + [[nodiscard]] u64* Pointer(bool is_short) noexcept { + return is_short ? stack.data() : heap; + } + + std::array stack{}; ///< Small buffers storage + u64* heap; ///< Not-small buffers pointer to the storage +}; + +template +struct Words { + explicit Words() = default; + explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} { + num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD); + if (IsShort()) { + cpu.stack.fill(~u64{0}); + gpu.stack.fill(0); + untracked.stack.fill(~u64{0}); + } else { + // Share allocation between CPU and GPU pages and set their default values + u64* const alloc = new u64[num_words * 3]; + cpu.heap = alloc; + gpu.heap = alloc + num_words; + untracked.heap = alloc + num_words * 2; + std::fill_n(cpu.heap, num_words, ~u64{0}); + std::fill_n(gpu.heap, num_words, 0); + std::fill_n(untracked.heap, num_words, ~u64{0}); + } + // Clean up tailing bits + const u64 last_word_size = size_bytes % BYTES_PER_WORD; + const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE); + const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD; + const u64 last_word = (~u64{0} << shift) >> shift; + cpu.Pointer(IsShort())[NumWords() - 1] = last_word; + untracked.Pointer(IsShort())[NumWords() - 1] = last_word; + } + + ~Words() { + Release(); + } + + Words& operator=(Words&& rhs) noexcept { + Release(); + size_bytes = rhs.size_bytes; + num_words = rhs.num_words; + cpu = rhs.cpu; + gpu = rhs.gpu; + untracked = rhs.untracked; + rhs.cpu.heap = nullptr; + return *this; + } + + Words(Words&& rhs) noexcept + : size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu}, + untracked{rhs.untracked} { + rhs.cpu.heap = nullptr; + } + + Words& operator=(const Words&) = delete; + Words(const Words&) = delete; + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return num_words <= stack_words; + } + + /// Returns the number of words of the buffer + [[nodiscard]] size_t NumWords() const noexcept { + return num_words; + } + + /// Release buffer resources + void Release() { + if (!IsShort()) { + // CPU written words is the base for the heap allocation + delete[] cpu.heap; + } + } + + template + std::span Span() noexcept { + if constexpr (type == Type::CPU) { + return std::span(cpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::GPU) { + return std::span(gpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::Untracked) { + return std::span(untracked.Pointer(IsShort()), num_words); + } + } + + template + std::span Span() const noexcept { + if constexpr (type == Type::CPU) { + return std::span(cpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::GPU) { + return std::span(gpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::Untracked) { + return std::span(untracked.Pointer(IsShort()), num_words); + } + } + + u64 size_bytes = 0; + size_t num_words = 0; + WordsArray cpu; + WordsArray gpu; + WordsArray untracked; +}; + +template +class WordManager { +public: + explicit WordManager(PageManager* tracker_, VAddr cpu_addr_, u64 size_bytes) + : tracker{tracker_}, cpu_addr{cpu_addr_}, words{size_bytes} {} + + explicit WordManager() = default; + + void SetCpuAddress(VAddr new_cpu_addr) { + cpu_addr = new_cpu_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + static 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; + bits = (bits << limit_page_end) >> limit_page_end; + return bits; + } + + static 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; + return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE); + } + + template + void IterateWords(size_t offset, size_t size, Func&& func) const { + 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)); + const size_t end = static_cast(std::max(static_cast(offset + size), 0LL)); + if (start >= SizeBytes() || end <= start) { + return; + } + auto [start_word, start_page] = GetWordPage(start); + auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL); + const size_t num_words = NumWords(); + start_word = std::min(start_word, num_words); + end_word = std::min(end_word, num_words); + const size_t diff = end_word - start_word; + end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD; + end_word = std::min(end_word, num_words); + end_page += diff * PAGES_PER_WORD; + constexpr u64 base_mask{~0ULL}; + for (size_t word_index = start_word; word_index < end_word; word_index++) { + const u64 mask = ExtractBits(base_mask, start_page, end_page); + start_page = 0; + end_page -= PAGES_PER_WORD; + if constexpr (BOOL_BREAK) { + if (func(word_index, mask)) { + return; + } + } else { + func(word_index, mask); + } + } + } + + template + void IteratePages(u64 mask, Func&& func) const { + size_t offset = 0; + while (mask != 0) { + const size_t empty_bits = std::countr_zero(mask); + offset += empty_bits; + mask = mask >> empty_bits; + + const size_t continuous_bits = std::countr_one(mask); + func(offset, continuous_bits); + mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0; + offset += continuous_bits; + } + } + + /** + * Change the state of a range of pages + * + * @param dirty_addr Base address to mark or unmark as modified + * @param size Size in bytes to mark or unmark as modified + */ + template + void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { + std::span state_words = words.template Span(); + [[maybe_unused]] std::span untracked_words = words.template Span(); + IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::CPU) { + NotifyPageTracker(index, untracked_words[index], mask); + } + if constexpr (enable) { + state_words[index] |= mask; + if constexpr (type == Type::CPU) { + untracked_words[index] |= mask; + } + } else { + state_words[index] &= ~mask; + if constexpr (type == Type::CPU) { + untracked_words[index] &= ~mask; + } + } + }); + } + + /** + * Loop over each page in the given range, turn off those bits and notify the tracker if + * needed. Call the given function on each turned off range. + * + * @param query_cpu_range Base CPU address to loop over + * @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) { + static_assert(type != Type::Untracked); + + std::span state_words = words.template Span(); + [[maybe_unused]] std::span untracked_words = words.template Span(); + const size_t offset = query_cpu_range - cpu_addr; + bool pending = false; + size_t pending_offset{}; + size_t pending_pointer{}; + const auto release = [&]() { + func(cpu_addr + pending_offset * BYTES_PER_PAGE, + (pending_pointer - pending_offset) * BYTES_PER_PAGE); + }; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + const u64 word = state_words[index] & mask; + if constexpr (clear) { + if constexpr (type == Type::CPU) { + NotifyPageTracker(index, untracked_words[index], mask); + } + state_words[index] &= ~mask; + if constexpr (type == Type::CPU) { + untracked_words[index] &= ~mask; + } + } + const size_t base_offset = index * PAGES_PER_WORD; + IteratePages(word, [&](size_t pages_offset, size_t pages_size) { + const auto reset = [&]() { + pending_offset = base_offset + pages_offset; + pending_pointer = base_offset + pages_offset + pages_size; + }; + if (!pending) { + reset(); + pending = true; + return; + } + if (pending_pointer == base_offset + pages_offset) { + pending_pointer += pages_size; + return; + } + release(); + reset(); + }); + }); + if (pending) { + release(); + } + } + + /** + * Returns true when a region has been modified + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { + static_assert(type != Type::Untracked); + + const std::span state_words = words.template Span(); + [[maybe_unused]] const std::span untracked_words = + words.template Span(); + bool result = false; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + const u64 word = state_words[index] & mask; + if (word != 0) { + result = true; + return true; + } + return false; + }); + return result; + } + + /// Returns the number of words of the manager + [[nodiscard]] size_t NumWords() const noexcept { + return words.NumWords(); + } + + /// Returns the size in bytes of the manager + [[nodiscard]] u64 SizeBytes() const noexcept { + return words.size_bytes; + } + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return words.IsShort(); + } + +private: + template + u64* Array() noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + template + const u64* Array() const noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + /** + * Notify tracker about changes in the CPU tracking state of a word in the buffer + * + * @param word_index Index to the word to notify to the tracker + * @param current_bits Current state of the word + * @param new_bits New state of the word + * + * @tparam add_to_tracker True when the tracker should start tracking the new pages + */ + template + void NotifyPageTracker(u64 word_index, u64 current_bits, u64 new_bits) const { + 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); + }); + } + + PageManager* tracker; + VAddr cpu_addr = 0; + Words words; +}; + +} // namespace VideoCore diff --git a/src/video_core/multi_level_page_table.h b/src/video_core/multi_level_page_table.h new file mode 100644 index 000000000..527476f3b --- /dev/null +++ b/src/video_core/multi_level_page_table.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/object_pool.h" +#include "common/types.h" + +namespace VideoCore { + +template +class MultiLevelPageTable final { + using Entry = typename Traits::Entry; + + static constexpr size_t AddressSpaceBits = Traits::AddressSpaceBits; + static constexpr size_t FirstLevelBits = Traits::FirstLevelBits; + static constexpr size_t PageBits = Traits::PageBits; + static constexpr size_t FirstLevelShift = AddressSpaceBits - FirstLevelBits; + static constexpr size_t SecondLevelBits = FirstLevelShift - PageBits; + static constexpr size_t NumEntriesPerL1Page = 1ULL << SecondLevelBits; + + using L1Page = std::array; + +public: + explicit MultiLevelPageTable() : first_level_map{1ULL << FirstLevelBits, nullptr} {} + + ~MultiLevelPageTable() noexcept = default; + + [[nodiscard]] Entry* find(size_t page) { + const size_t l1_page = page >> SecondLevelBits; + const size_t l2_page = page & (NumEntriesPerL1Page - 1); + if (!first_level_map[l1_page]) { + return nullptr; + } + return &(*first_level_map[l1_page])[l2_page]; + } + + [[nodiscard]] const Entry& operator[](size_t page) const { + const size_t l1_page = page >> SecondLevelBits; + const size_t l2_page = page & (NumEntriesPerL1Page - 1); + if (!first_level_map[l1_page]) { + first_level_map[l1_page] = page_alloc.Create(); + } + return (*first_level_map[l1_page])[l2_page]; + } + + [[nodiscard]] Entry& operator[](size_t page) { + const size_t l1_page = page >> SecondLevelBits; + const size_t l2_page = page & (NumEntriesPerL1Page - 1); + if (!first_level_map[l1_page]) { + first_level_map[l1_page] = page_alloc.Create(); + } + return (*first_level_map[l1_page])[l2_page]; + } + +private: + std::vector first_level_map{}; + Common::ObjectPool page_alloc; +}; + +} // namespace VideoCore diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp new file mode 100644 index 000000000..6225f11ba --- /dev/null +++ b/src/video_core/page_manager.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/assert.h" +#include "common/error.h" +#include "video_core/page_manager.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" + +#ifndef _WIN64 +#include +#include +#include +#include +#include +#ifdef ENABLE_USERFAULTFD +#include +#endif +#else +#include +#endif + +namespace VideoCore { + +constexpr size_t PAGESIZE = 4_KB; +constexpr size_t PAGEBITS = 12; + +#ifdef _WIN64 +struct PageManager::Impl { + Impl(Vulkan::Rasterizer* rasterizer_) { + rasterizer = rasterizer_; + + veh_handle = AddVectoredExceptionHandler(0, GuestFaultSignalHandler); + ASSERT_MSG(veh_handle, "Failed to register an exception handler"); + } + + void OnMap(VAddr address, size_t size) {} + + void OnUnmap(VAddr address, size_t size) {} + + void Protect(VAddr address, size_t size, bool allow_write) { + DWORD prot = allow_write ? PAGE_READWRITE : PAGE_READONLY; + DWORD old_prot{}; + BOOL result = VirtualProtect(std::bit_cast(address), size, prot, &old_prot); + ASSERT_MSG(result != 0, "Region protection failed"); + } + + static LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept { + const u32 ec = pExp->ExceptionRecord->ExceptionCode; + if (ec == EXCEPTION_ACCESS_VIOLATION) { + const auto info = pExp->ExceptionRecord->ExceptionInformation; + if (info[0] == 1) { // Write violation + rasterizer->InvalidateMemory(info[1], sizeof(u64)); + return EXCEPTION_CONTINUE_EXECUTION; + } /* else { + UNREACHABLE(); + }*/ + } + return EXCEPTION_CONTINUE_SEARCH; // pass further + } + + inline static Vulkan::Rasterizer* rasterizer; + void* veh_handle{}; +}; +#elif ENABLE_USERFAULTFD +struct PageManager::Impl { + Impl(Vulkan::Rasterizer* rasterizer_) : rasterizer{rasterizer_} { + uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + ASSERT_MSG(uffd != -1, "{}", Common::GetLastErrorMsg()); + + // Request uffdio features from kernel. + uffdio_api api; + api.api = UFFD_API; + api.features = UFFD_FEATURE_THREAD_ID; + const int ret = ioctl(uffd, UFFDIO_API, &api); + ASSERT(ret == 0 && api.api == UFFD_API); + + // Create uffd handler thread + ufd_thread = std::jthread([&](std::stop_token token) { UffdHandler(token); }); + } + + void OnMap(VAddr address, size_t size) { + uffdio_register reg; + reg.range.start = address; + reg.range.len = size; + reg.mode = UFFDIO_REGISTER_MODE_WP; + const int ret = ioctl(uffd, UFFDIO_REGISTER, ®); + ASSERT_MSG(ret != -1, "Uffdio register failed"); + } + + void OnUnmap(VAddr address, size_t size) { + uffdio_range range; + range.start = address; + range.len = size; + const int ret = ioctl(uffd, UFFDIO_UNREGISTER, &range); + ASSERT_MSG(ret != -1, "Uffdio unregister failed"); + } + + void Protect(VAddr address, size_t size, bool allow_write) { + uffdio_writeprotect wp; + wp.range.start = address; + wp.range.len = size; + wp.mode = allow_write ? 0 : UFFDIO_WRITEPROTECT_MODE_WP; + const int ret = ioctl(uffd, UFFDIO_WRITEPROTECT, &wp); + ASSERT_MSG(ret != -1, "Uffdio writeprotect failed with error: {}", + Common::GetLastErrorMsg()); + } + + void UffdHandler(std::stop_token token) { + while (!token.stop_requested()) { + pollfd pollfd; + pollfd.fd = uffd; + pollfd.events = POLLIN; + + // Block until the descriptor is ready for data reads. + const int pollres = poll(&pollfd, 1, -1); + switch (pollres) { + case -1: + perror("Poll userfaultfd"); + continue; + break; + case 0: + continue; + case 1: + break; + default: + UNREACHABLE_MSG("Unexpected number of descriptors {} out of poll", pollres); + } + + // We don't want an error condition to have occured. + ASSERT_MSG(!(pollfd.revents & POLLERR), "POLLERR on userfaultfd"); + + // We waited until there is data to read, we don't care about anything else. + if (!(pollfd.revents & POLLIN)) { + continue; + } + + // Read message from kernel. + uffd_msg msg; + const int readret = read(uffd, &msg, sizeof(msg)); + ASSERT_MSG(readret != -1 || errno == EAGAIN, "Unexpected result of uffd read"); + if (errno == EAGAIN) { + continue; + } + ASSERT_MSG(readret == sizeof(msg), "Unexpected short read, exiting"); + ASSERT(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP); + + // Notify rasterizer about the fault. + const VAddr addr = msg.arg.pagefault.address; + const VAddr addr_page = Common::AlignDown(addr, PAGESIZE); + rasterizer->InvalidateMemory(addr_page, PAGESIZE); + } + } + + Vulkan::Rasterizer* rasterizer; + std::jthread ufd_thread; + int uffd; +}; +#else +struct PageManager::Impl { + Impl(Vulkan::Rasterizer* rasterizer_) { + rasterizer = rasterizer_; + +#ifdef __APPLE__ + // Read-only memory write results in SIGBUS on Apple. + static constexpr int SignalType = SIGBUS; +#else + static constexpr int SignalType = SIGSEGV; +#endif + sigset_t signal_mask; + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SignalType); + + using HandlerType = decltype(sigaction::sa_sigaction); + + struct sigaction guest_access_fault {}; + guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK; + guest_access_fault.sa_sigaction = &GuestFaultSignalHandler; + guest_access_fault.sa_mask = signal_mask; + sigaction(SignalType, &guest_access_fault, nullptr); + } + + void OnMap(VAddr address, size_t size) {} + + void OnUnmap(VAddr address, size_t size) {} + + void Protect(VAddr address, size_t size, bool allow_write) { + mprotect(reinterpret_cast(address), size, + PROT_READ | (allow_write ? PROT_WRITE : 0)); + } + + static void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) { + ucontext_t* ctx = reinterpret_cast(raw_context); + const VAddr address = reinterpret_cast(info->si_addr); +#ifdef __APPLE__ + const u32 err = ctx->uc_mcontext->__es.__err; +#else + const greg_t err = ctx->uc_mcontext.gregs[REG_ERR]; +#endif + if (err & 0x2) { + rasterizer->InvalidateMemory(address, sizeof(u64)); + } else { + // Read not supported! + UNREACHABLE(); + } + } + + inline static Vulkan::Rasterizer* rasterizer; +}; +#endif + +PageManager::PageManager(Vulkan::Rasterizer* rasterizer_) + : impl{std::make_unique(rasterizer_)}, rasterizer{rasterizer_} {} + +PageManager::~PageManager() = default; + +void PageManager::OnGpuMap(VAddr address, size_t size) { + impl->OnMap(address, size); +} + +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{mutex}; + 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; + 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}); + } +} + +} // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h new file mode 100644 index 000000000..0dc022aa5 --- /dev/null +++ b/src/video_core/page_manager.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/types.h" + +namespace Vulkan { +class Rasterizer; +} + +namespace VideoCore { + +class PageManager { +public: + explicit PageManager(Vulkan::Rasterizer* rasterizer); + ~PageManager(); + + /// Register a range of mapped gpu memory. + void OnGpuMap(VAddr address, size_t size); + + /// 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); + +private: + struct Impl; + std::unique_ptr impl; + Vulkan::Rasterizer* rasterizer; + std::mutex mutex; + boost::icl::interval_map cached_pages; +}; + +} // namespace VideoCore diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 6810bf34e..c78d629e4 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -67,8 +67,8 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* : window{window_}, liverpool{liverpool_}, instance{window, Config::getGpuId(), Config::vkValidationEnabled()}, draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, - texture_cache{instance, draw_scheduler} { - rasterizer = std::make_unique(instance, draw_scheduler, texture_cache, liverpool); + rasterizer{std::make_unique(instance, draw_scheduler, liverpool)}, + texture_cache{rasterizer->GetTextureCache()} { const u32 num_images = swapchain.GetImageCount(); const vk::Device device = instance.GetDevice(); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 3fe9267fe..8178c88de 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -47,7 +47,7 @@ public: Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, VAddr cpu_address, bool is_eop) { const auto info = VideoCore::ImageInfo{attribute, cpu_address}; - const auto image_id = texture_cache.FindImage(info, cpu_address); + const auto image_id = texture_cache.FindImage(info, false); auto& image = texture_cache.GetImage(image_id); return PrepareFrameInternal(image, is_eop); } @@ -61,7 +61,7 @@ public: const Libraries::VideoOut::BufferAttributeGroup& attribute, VAddr cpu_address) { vo_buffers_addr.emplace_back(cpu_address); const auto info = VideoCore::ImageInfo{attribute, cpu_address}; - const auto image_id = texture_cache.FindImage(info, cpu_address); + const auto image_id = texture_cache.FindImage(info, false); return texture_cache.GetImage(image_id); } @@ -88,7 +88,7 @@ private: Scheduler flip_scheduler; Swapchain swapchain; std::unique_ptr rasterizer; - VideoCore::TextureCache texture_cache; + VideoCore::TextureCache& texture_cache; vk::UniqueCommandPool command_pool; std::vector present_frames; std::queue free_queue; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index d8e5f7fae..8a98e9680 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -3,11 +3,10 @@ #include #include "common/alignment.h" -#include "core/memory.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" #include "video_core/texture_cache/texture_cache.h" namespace Vulkan { @@ -51,6 +50,12 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler }); } + const vk::PushConstantRange push_constants = { + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .offset = 0, + .size = sizeof(Shader::PushData), + }; + const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, .bindingCount = static_cast(bindings.size()), @@ -62,8 +67,8 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler const vk::PipelineLayoutCreateInfo layout_info = { .setLayoutCount = 1U, .pSetLayouts = &set_layout, - .pushConstantRangeCount = 0, - .pPushConstantRanges = nullptr, + .pushConstantRangeCount = 1U, + .pPushConstantRanges = &push_constants, }; pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info); @@ -82,35 +87,18 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler ComputePipeline::~ComputePipeline() = default; -bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& staging, +bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const { // Bind resource buffers and textures. boost::container::static_vector buffer_infos; boost::container::static_vector image_infos; boost::container::small_vector set_writes; + Shader::PushData push_data{}; u32 binding{}; - for (const auto& buffer : info.buffers) { + for (u32 i = 0; const auto& buffer : info.buffers) { const auto vsharp = buffer.GetVsharp(info); - const u32 size = vsharp.GetSize(); const VAddr address = vsharp.base_address; - if (buffer.is_storage) { - texture_cache.OnCpuWrite(address); - } - const u32 offset = staging.Copy(address, size, - buffer.is_storage ? instance.StorageMinAlignment() - : instance.UniformMinAlignment()); - buffer_infos.emplace_back(staging.Handle(), offset, size); - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = buffer.is_storage ? vk::DescriptorType::eStorageBuffer - : vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &buffer_infos.back(), - }); - // Most of the time when a metadata is updated with a shader it gets cleared. It means we // can skip the whole dispatch and update the tracked state instead. Also, it is not // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we will @@ -125,6 +113,31 @@ bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& s LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); } } + const u32 size = vsharp.GetSize(); + if (buffer.is_written) { + texture_cache.InvalidateMemory(address, size); + } + const u32 alignment = + buffer.is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); + const auto [vk_buffer, offset] = + buffer_cache.ObtainBuffer(address, size, buffer.is_written); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; + if (adjust != 0) { + ASSERT(adjust % 4 == 0); + push_data.AddOffset(i, adjust); + } + buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = buffer.is_storage ? vk::DescriptorType::eStorageBuffer + : vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &buffer_infos.back(), + }); + i++; } for (const auto& image_desc : info.images) { @@ -168,6 +181,8 @@ bool ComputePipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& s } const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), + &push_data); cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes); return true; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 4cdcccfcb..16de5635f 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -6,19 +6,15 @@ #include "shader_recompiler/runtime_info.h" #include "video_core/renderer_vulkan/vk_common.h" -namespace Core { -class MemoryManager; -} - namespace VideoCore { +class BufferCache; class TextureCache; -} +} // namespace VideoCore namespace Vulkan { class Instance; class Scheduler; -class StreamBuffer; class ComputePipeline { public: @@ -31,7 +27,7 @@ public: return *pipeline; } - bool BindResources(Core::MemoryManager* memory, StreamBuffer& staging, + bool BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; private: diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 7b00a9111..91ff999e5 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -5,13 +5,13 @@ #include #include +#include "common/alignment.h" #include "common/assert.h" -#include "core/memory.h" #include "video_core/amdgpu/resource.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" #include "video_core/texture_cache/texture_cache.h" namespace Vulkan { @@ -32,9 +32,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul BuildDescSetLayout(); const vk::PushConstantRange push_constants = { - .stageFlags = vk::ShaderStageFlagBits::eVertex, + .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, .offset = 0, - .size = 2 * sizeof(u32), + .size = sizeof(Shader::PushData), }; const vk::DescriptorSetLayout set_layout = *desc_layout; @@ -328,25 +328,43 @@ void GraphicsPipeline::BuildDescSetLayout() { desc_layout = instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci); } -void GraphicsPipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& staging, +void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, + VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const { - BindVertexBuffers(staging); - // Bind resource buffers and textures. boost::container::static_vector buffer_infos; boost::container::static_vector image_infos; boost::container::small_vector set_writes; + Shader::PushData push_data{}; u32 binding{}; for (const auto& stage : stages) { - for (const auto& buffer : stage.buffers) { + if (stage.uses_step_rates) { + push_data.step0 = regs.vgt_instance_step_rate_0; + push_data.step1 = regs.vgt_instance_step_rate_1; + } + for (u32 i = 0; const auto& buffer : stage.buffers) { const auto vsharp = buffer.GetVsharp(stage); - const VAddr address = vsharp.base_address; - const u32 size = vsharp.GetSize(); - const u32 offset = staging.Copy(address, size, - buffer.is_storage ? instance.StorageMinAlignment() - : instance.UniformMinAlignment()); - buffer_infos.emplace_back(staging.Handle(), offset, size); + if (vsharp) { + const VAddr address = vsharp.base_address; + if (texture_cache.IsMeta(address)) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)"); + } + const u32 size = vsharp.GetSize(); + const u32 alignment = buffer.is_storage ? instance.StorageMinAlignment() + : instance.UniformMinAlignment(); + const auto [vk_buffer, offset] = + buffer_cache.ObtainBuffer(address, size, buffer.is_written); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; + if (adjust != 0) { + ASSERT(adjust % 4 == 0); + push_data.AddOffset(i, adjust); + } + buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); + } else { + buffer_infos.emplace_back(VK_NULL_HANDLE, 0, VK_WHOLE_SIZE); + } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, .dstBinding = binding++, @@ -356,10 +374,7 @@ void GraphicsPipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); - - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)"); - } + i++; } boost::container::static_vector tsharps; @@ -406,86 +421,15 @@ void GraphicsPipeline::BindResources(Core::MemoryManager* memory, StreamBuffer& } } + const auto cmdbuf = scheduler.CommandBuffer(); if (!set_writes.empty()) { - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, set_writes); } -} - -void GraphicsPipeline::BindVertexBuffers(StreamBuffer& staging) const { - const auto& vs_info = stages[u32(Shader::Stage::Vertex)]; - if (vs_info.vs_inputs.empty()) { - return; - } - - std::array host_buffers; - std::array host_offsets; - boost::container::static_vector guest_buffers; - - struct BufferRange { - VAddr base_address; - VAddr end_address; - u64 offset; // offset in the mapped memory - - size_t GetSize() const { - return end_address - base_address; - } - }; - - // Calculate buffers memory overlaps - boost::container::static_vector ranges{}; - for (const auto& input : vs_info.vs_inputs) { - if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || - input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) { - continue; - } - - const auto& buffer = vs_info.ReadUd(input.sgpr_base, input.dword_offset); - if (buffer.GetSize() == 0) { - continue; - } - guest_buffers.emplace_back(buffer); - ranges.emplace_back(buffer.base_address, buffer.base_address + buffer.GetSize()); - } - std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { - return lhv.base_address < rhv.base_address; - }); - - boost::container::static_vector ranges_merged{ranges[0]}; - for (auto range : ranges) { - auto& prev_range = ranges_merged.back(); - if (prev_range.end_address < range.base_address) { - ranges_merged.emplace_back(range); - } else { - prev_range.end_address = std::max(prev_range.end_address, range.end_address); - } - } - - // Map buffers - for (auto& range : ranges_merged) { - range.offset = staging.Copy(range.base_address, range.GetSize(), 4); - } - - // Bind vertex buffers - const size_t num_buffers = guest_buffers.size(); - for (u32 i = 0; i < num_buffers; ++i) { - const auto& buffer = guest_buffers[i]; - const auto& host_buffer = std::ranges::find_if( - ranges_merged.cbegin(), ranges_merged.cend(), [&](const BufferRange& range) { - return (buffer.base_address >= range.base_address && - buffer.base_address < range.end_address); - }); - assert(host_buffer != ranges_merged.cend()); - - host_buffers[i] = staging.Handle(); - host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address; - } - - if (num_buffers > 0) { - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data()); - } + cmdbuf.pushConstants(*pipeline_layout, + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0U, + sizeof(push_data), &push_data); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, Handle()); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index e1564f8fd..f818d980b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -7,13 +7,10 @@ #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_common.h" -namespace Core { -class MemoryManager; -} - namespace VideoCore { +class BufferCache; class TextureCache; -} +} // namespace VideoCore namespace Vulkan { @@ -22,7 +19,6 @@ static constexpr u32 MaxShaderStages = 5; class Instance; class Scheduler; -class StreamBuffer; using Liverpool = AmdGpu::Liverpool; @@ -64,7 +60,7 @@ public: std::array modules); ~GraphicsPipeline(); - void BindResources(Core::MemoryManager* memory, StreamBuffer& staging, + void BindResources(const Liverpool::Regs& regs, VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; vk::Pipeline Handle() const noexcept { @@ -75,6 +71,10 @@ public: return *pipeline_layout; } + const Shader::Info& GetStage(Shader::Stage stage) const noexcept { + return stages[u32(stage)]; + } + bool IsEmbeddedVs() const noexcept { static constexpr size_t EmbeddedVsHash = 0x9b2da5cf47f8c29f; return key.stage_hashes[u32(Shader::Stage::Vertex)] == EmbeddedVsHash; @@ -90,7 +90,6 @@ public: private: void BuildDescSetLayout(); - void BindVertexBuffers(StreamBuffer& staging) const; private: const Instance& instance; diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 735303a3e..2d396daf0 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -204,7 +204,8 @@ bool Instance::CreateDevice() { // The next two extensions are required to be available together in order to support write masks color_write_en = add_extension(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); - const auto calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME); + const bool calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME); + const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. @@ -303,12 +304,19 @@ bool Instance::CreateDevice() { .workgroupMemoryExplicitLayoutScalarBlockLayout = true, .workgroupMemoryExplicitLayout8BitAccess = true, .workgroupMemoryExplicitLayout16BitAccess = true, - }}; + }, + vk::PhysicalDeviceRobustness2FeaturesEXT{ + .nullDescriptor = true, + }, + }; if (!color_write_en) { device_chain.unlink(); device_chain.unlink(); } + if (!robustness) { + device_chain.unlink(); + } try { device = physical_device.createDeviceUnique(device_chain.get()); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index a77b298b1..d41723ec8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -5,7 +5,6 @@ #include #include "shader_recompiler/ir/basic_block.h" -#include "shader_recompiler/object_pool.h" #include "shader_recompiler/profile.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" @@ -51,8 +50,8 @@ private: Shader::Profile profile{}; GraphicsPipelineKey graphics_key{}; u64 compute_key{}; - Shader::ObjectPool inst_pool; - Shader::ObjectPool block_pool; + Common::ObjectPool inst_pool; + Common::ObjectPool block_pool; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index ff5e97d5b..51de09f7a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -13,22 +13,17 @@ namespace Vulkan { -static constexpr vk::BufferUsageFlags VertexIndexFlags = - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | - vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eUniformBuffer | - vk::BufferUsageFlagBits::eStorageBuffer; - Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, - VideoCore::TextureCache& texture_cache_, AmdGpu::Liverpool* liverpool_) - : instance{instance_}, scheduler{scheduler_}, texture_cache{texture_cache_}, - liverpool{liverpool_}, memory{Core::Memory::Instance()}, - pipeline_cache{instance, scheduler, liverpool}, - vertex_index_buffer{instance, scheduler, VertexIndexFlags, 2_GB, BufferType::Upload} { + AmdGpu::Liverpool* liverpool_) + : instance{instance_}, scheduler{scheduler_}, page_manager{this}, + buffer_cache{instance, scheduler, liverpool_, page_manager}, + texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, + memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { if (!Config::nullGpu()) { liverpool->BindRasterizer(this); } - - memory->SetInstance(&instance); + memory->SetRasterizer(this); + wfi_event = instance.GetDevice().createEventUnique({}); } Rasterizer::~Rasterizer() = default; @@ -38,29 +33,24 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { const auto cmdbuf = scheduler.CommandBuffer(); const auto& regs = liverpool->regs; - const u32 num_indices = SetupIndexBuffer(is_indexed, index_offset); const GraphicsPipeline* pipeline = pipeline_cache.GetGraphicsPipeline(); if (!pipeline) { return; } try { - pipeline->BindResources(memory, vertex_index_buffer, texture_cache); + pipeline->BindResources(regs, buffer_cache, texture_cache); } catch (...) { UNREACHABLE(); } + const auto& vs_info = pipeline->GetStage(Shader::Stage::Vertex); + buffer_cache.BindVertexBuffers(vs_info); + const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, index_offset); + BeginRendering(); UpdateDynamicState(*pipeline); - cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle()); - - const u32 step_rates[] = { - regs.vgt_instance_step_rate_0, - regs.vgt_instance_step_rate_1, - }; - cmdbuf.pushConstants(pipeline->GetLayout(), vk::ShaderStageFlagBits::eVertex, 0u, - sizeof(step_rates), &step_rates); if (is_indexed) { cmdbuf.drawIndexed(num_indices, regs.num_instances.NumInstances(), 0, 0, 0); } else { @@ -82,8 +72,7 @@ void Rasterizer::DispatchDirect() { } try { - const auto has_resources = - pipeline->BindResources(memory, vertex_index_buffer, texture_cache); + const auto has_resources = pipeline->BindResources(buffer_cache, texture_cache); if (!has_resources) { return; } @@ -131,7 +120,7 @@ void Rasterizer::BeginRendering() { state.color_images[state.num_color_attachments] = image.image; state.color_attachments[state.num_color_attachments++] = { .imageView = *image_view.image_view, - .imageLayout = vk::ImageLayout::eGeneral, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, .loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, .storeOp = vk::AttachmentStoreOp::eStore, .clearValue = @@ -168,45 +157,19 @@ void Rasterizer::BeginRendering() { scheduler.BeginRendering(state); } -u32 Rasterizer::SetupIndexBuffer(bool& is_indexed, u32 index_offset) { - // Emulate QuadList primitive type with CPU made index buffer. - const auto& regs = liverpool->regs; - if (liverpool->regs.primitive_type == Liverpool::PrimitiveType::QuadList) { - // ASSERT_MSG(!is_indexed, "Using QuadList primitive with indexed draw"); - is_indexed = true; +void Rasterizer::InvalidateMemory(VAddr addr, u64 size) { + buffer_cache.InvalidateMemory(addr, size); + texture_cache.InvalidateMemory(addr, size); +} - // Emit indices. - const u32 index_size = 3 * regs.num_indices; - const auto [data, offset, _] = vertex_index_buffer.Map(index_size); - LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); - vertex_index_buffer.Commit(index_size); +void Rasterizer::MapMemory(VAddr addr, u64 size) { + page_manager.OnGpuMap(addr, size); +} - // Bind index buffer. - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(vertex_index_buffer.Handle(), offset, vk::IndexType::eUint16); - return index_size / sizeof(u16); - } - if (!is_indexed) { - return regs.num_indices; - } - - // Figure out index type and size. - const bool is_index16 = regs.index_buffer_type.index_type == Liverpool::IndexType::Index16; - const vk::IndexType index_type = is_index16 ? vk::IndexType::eUint16 : vk::IndexType::eUint32; - const u32 index_size = is_index16 ? sizeof(u16) : sizeof(u32); - - // Upload index data to stream buffer. - const auto index_address = regs.index_base_address.Address(); - const u32 index_buffer_size = (index_offset + regs.num_indices) * index_size; - const auto [data, offset, _] = vertex_index_buffer.Map(index_buffer_size); - std::memcpy(data, index_address, index_buffer_size); - vertex_index_buffer.Commit(index_buffer_size); - - // Bind index buffer. - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(vertex_index_buffer.Handle(), offset + index_offset * index_size, - index_type); - return regs.num_indices; +void Rasterizer::UnmapMemory(VAddr addr, u64 size) { + buffer_cache.InvalidateMemory(addr, size); + texture_cache.UnmapMemory(addr, size); + page_manager.OnGpuUnmap(addr, size); } void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 64dc87ef6..685ba6e00 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -3,8 +3,10 @@ #pragma once +#include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" +#include "video_core/texture_cache/texture_cache.h" namespace AmdGpu { struct Liverpool; @@ -14,10 +16,6 @@ namespace Core { class MemoryManager; } -namespace VideoCore { -class TextureCache; -} - namespace Vulkan { class Scheduler; @@ -26,9 +24,13 @@ class GraphicsPipeline; class Rasterizer { public: explicit Rasterizer(const Instance& instance, Scheduler& scheduler, - VideoCore::TextureCache& texture_cache, AmdGpu::Liverpool* liverpool); + AmdGpu::Liverpool* liverpool); ~Rasterizer(); + [[nodiscard]] VideoCore::TextureCache& GetTextureCache() noexcept { + return texture_cache; + } + void Draw(bool is_indexed, u32 index_offset = 0); void DispatchDirect(); @@ -36,12 +38,13 @@ public: void ScopeMarkerBegin(const std::string& str); void ScopeMarkerEnd(); + void InvalidateMemory(VAddr addr, u64 size); + void MapMemory(VAddr addr, u64 size); + void UnmapMemory(VAddr addr, u64 size); + u64 Flush(); private: - u32 SetupIndexBuffer(bool& is_indexed, u32 index_offset); - void MapMemory(VAddr addr, size_t size); - void BeginRendering(); void UpdateDynamicState(const GraphicsPipeline& pipeline); @@ -51,11 +54,13 @@ private: private: const Instance& instance; Scheduler& scheduler; - VideoCore::TextureCache& texture_cache; + VideoCore::PageManager page_manager; + VideoCore::BufferCache buffer_cache; + VideoCore::TextureCache texture_cache; AmdGpu::Liverpool* liverpool; Core::MemoryManager* memory; PipelineCache pipeline_cache; - StreamBuffer vertex_index_buffer; + vk::UniqueEvent wfi_event; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 48c3af7a4..b82d558ca 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -6,6 +6,7 @@ #include #include #include "common/types.h" +#include "common/unique_function.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" @@ -97,8 +98,8 @@ public: } /// Defers an operation until the gpu has reached the current cpu tick. - void DeferOperation(auto&& func) { - pending_ops.emplace(func, CurrentTick()); + void DeferOperation(Common::UniqueFunction&& func) { + pending_ops.emplace(std::move(func), CurrentTick()); } static std::mutex submit_mutex; @@ -115,7 +116,7 @@ private: vk::CommandBuffer current_cmdbuf; std::condition_variable_any event_cv; struct PendingOp { - std::function callback; + Common::UniqueFunction callback; u64 gpu_tick; }; std::queue pending_ops; diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp deleted file mode 100644 index 116f7896d..000000000 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/alignment.h" -#include "common/assert.h" -#include "video_core/renderer_vulkan/vk_instance.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" - -namespace Vulkan { - -namespace { - -std::string_view BufferTypeName(BufferType type) { - switch (type) { - case BufferType::Upload: - return "Upload"; - case BufferType::Download: - return "Download"; - case BufferType::Stream: - return "Stream"; - default: - return "Invalid"; - } -} - -vk::MemoryPropertyFlags MakePropertyFlags(BufferType type) { - switch (type) { - case BufferType::Upload: - return vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; - case BufferType::Download: - return vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostCached; - case BufferType::Stream: - return vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent; - default: - UNREACHABLE_MSG("Unknown buffer type {}", static_cast(type)); - return vk::MemoryPropertyFlagBits::eHostVisible; - } -} - -static std::optional FindMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, - vk::MemoryPropertyFlags wanted) { - for (u32 i = 0; i < properties.memoryTypeCount; ++i) { - const auto flags = properties.memoryTypes[i].propertyFlags; - if ((flags & wanted) == wanted) { - return i; - } - } - return std::nullopt; -} - -/// Get the preferred host visible memory type. -u32 GetMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, BufferType type) { - vk::MemoryPropertyFlags flags = MakePropertyFlags(type); - std::optional preferred_type = FindMemoryType(properties, flags); - - constexpr std::array remove_flags = { - vk::MemoryPropertyFlagBits::eHostCached, - vk::MemoryPropertyFlagBits::eHostCoherent, - }; - - for (u32 i = 0; i < remove_flags.size() && !preferred_type; i++) { - flags &= ~remove_flags[i]; - preferred_type = FindMemoryType(properties, flags); - } - ASSERT_MSG(preferred_type, "No suitable memory type found"); - return preferred_type.value(); -} - -constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; -constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; - -} // Anonymous namespace - -StreamBuffer::StreamBuffer(const Instance& instance_, Scheduler& scheduler_, - vk::BufferUsageFlags usage_, u64 size, BufferType type_) - : instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()}, - stream_buffer_size{size}, usage{usage_}, type{type_} { - CreateBuffers(size); - ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); - ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); -} - -StreamBuffer::~StreamBuffer() { - device.unmapMemory(memory); - device.destroyBuffer(buffer); - device.freeMemory(memory); -} - -std::tuple StreamBuffer::Map(u64 size, u64 alignment) { - if (!is_coherent && type == BufferType::Stream) { - size = Common::AlignUp(size, instance.NonCoherentAtomSize()); - } - - ASSERT(size <= stream_buffer_size); - mapped_size = size; - - if (alignment > 0) { - offset = Common::AlignUp(offset, alignment); - } - - bool invalidate{false}; - if (offset + size > stream_buffer_size) { - // The buffer would overflow, save the amount of used watches and reset the state. - invalidate = true; - invalidation_mark = current_watch_cursor; - current_watch_cursor = 0; - offset = 0; - - // Swap watches and reset waiting cursors. - std::swap(previous_watches, current_watches); - wait_cursor = 0; - wait_bound = 0; - } - - const u64 mapped_upper_bound = offset + size; - WaitPendingOperations(mapped_upper_bound); - - return std::make_tuple(mapped + offset, offset, invalidate); -} - -void StreamBuffer::Commit(u64 size) { - if (!is_coherent && type == BufferType::Stream) { - size = Common::AlignUp(size, instance.NonCoherentAtomSize()); - } - - ASSERT_MSG(size <= mapped_size, "Reserved size {} is too small compared to {}", mapped_size, - size); - - const vk::MappedMemoryRange range = { - .memory = memory, - .offset = offset, - .size = size, - }; - - if (!is_coherent && type == BufferType::Download) { - device.invalidateMappedMemoryRanges(range); - } else if (!is_coherent) { - device.flushMappedMemoryRanges(range); - } - - offset += size; - - if (current_watch_cursor + 1 >= current_watches.size()) { - // Ensure that there are enough watches. - ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); - } - auto& watch = current_watches[current_watch_cursor++]; - watch.upper_bound = offset; - watch.tick = scheduler.CurrentTick(); -} - -void StreamBuffer::CreateBuffers(u64 prefered_size) { - const vk::Device device = instance.GetDevice(); - const auto memory_properties = instance.GetPhysicalDevice().getMemoryProperties(); - const u32 preferred_type = GetMemoryType(memory_properties, type); - const vk::MemoryType mem_type = memory_properties.memoryTypes[preferred_type]; - const u32 preferred_heap = mem_type.heapIndex; - is_coherent = - static_cast(mem_type.propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent); - - // Substract from the preferred heap size some bytes to avoid getting out of memory. - const vk::DeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; - // As per DXVK's example, using `heap_size / 2` - const vk::DeviceSize allocable_size = heap_size / 2; - buffer = device.createBuffer({ - .size = std::min(prefered_size, allocable_size), - .usage = usage, - }); - - const auto requirements_chain = - device - .getBufferMemoryRequirements2( - {.buffer = buffer}); - - const auto& requirements = requirements_chain.get(); - const auto& dedicated_requirements = requirements_chain.get(); - - stream_buffer_size = static_cast(requirements.memoryRequirements.size); - - LOG_INFO(Render_Vulkan, "Creating {} buffer with size {} KiB with flags {}", - BufferTypeName(type), stream_buffer_size / 1024, - vk::to_string(mem_type.propertyFlags)); - - if (dedicated_requirements.prefersDedicatedAllocation) { - vk::StructureChain alloc_chain = - {}; - - auto& alloc_info = alloc_chain.get(); - alloc_info.allocationSize = requirements.memoryRequirements.size; - alloc_info.memoryTypeIndex = preferred_type; - - auto& dedicated_alloc_info = alloc_chain.get(); - dedicated_alloc_info.buffer = buffer; - - memory = device.allocateMemory(alloc_chain.get()); - } else { - memory = device.allocateMemory({ - .allocationSize = requirements.memoryRequirements.size, - .memoryTypeIndex = preferred_type, - }); - } - - device.bindBufferMemory(buffer, memory, 0); - mapped = reinterpret_cast(device.mapMemory(memory, 0, VK_WHOLE_SIZE)); - - if (instance.HasDebuggingToolAttached()) { - SetObjectName(device, buffer, "StreamBuffer({}): {} KiB {}", BufferTypeName(type), - stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags)); - SetObjectName(device, memory, "StreamBufferMemory({}): {} Kib {}", BufferTypeName(type), - stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags)); - } -} - -void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { - watches.resize(watches.size() + grow_size); -} - -void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { - if (!invalidation_mark) { - return; - } - while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { - auto& watch = previous_watches[wait_cursor]; - wait_bound = watch.upper_bound; - scheduler.Wait(watch.tick); - ++wait_cursor; - } -} - -u64 StreamBuffer::Copy(VAddr src, size_t size, size_t alignment /*= 0*/) { - const auto [data, offset, _] = Map(size, alignment); - std::memcpy(data, reinterpret_cast(src), size); - Commit(size); - return offset; -} - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h deleted file mode 100644 index f7957ac08..000000000 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.h +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/types.h" -#include "video_core/renderer_vulkan/vk_common.h" - -namespace Vulkan { - -enum class BufferType : u32 { - Upload = 0, - Download = 1, - Stream = 2, -}; - -class Instance; -class Scheduler; - -class StreamBuffer final { - static constexpr std::size_t MAX_BUFFER_VIEWS = 3; - -public: - explicit StreamBuffer(const Instance& instance, Scheduler& scheduler, - vk::BufferUsageFlags usage, u64 size, - BufferType type = BufferType::Stream); - ~StreamBuffer(); - - /** - * Reserves a region of memory from the stream buffer. - * @param size Size to reserve. - * @returns A pair of a raw memory pointer (with offset added), and the buffer offset - */ - std::tuple Map(u64 size, u64 alignment = 0); - - /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. - void Commit(u64 size); - - /// Maps and commits a memory region with user provided data - u64 Copy(VAddr src, size_t size, size_t alignment = 0); - - vk::Buffer Handle() const noexcept { - return buffer; - } - -private: - struct Watch { - u64 tick{}; - u64 upper_bound{}; - }; - - /// Creates Vulkan buffer handles committing the required the required memory. - void CreateBuffers(u64 prefered_size); - - /// Increases the amount of watches available. - void ReserveWatches(std::vector& watches, std::size_t grow_size); - - void WaitPendingOperations(u64 requested_upper_bound); - -private: - const Instance& instance; ///< Vulkan instance. - Scheduler& scheduler; ///< Command scheduler. - - vk::Device device; - vk::Buffer buffer; ///< Mapped buffer. - vk::DeviceMemory memory; ///< Memory allocation. - u8* mapped{}; ///< Pointer to the mapped memory - u64 stream_buffer_size{}; ///< Stream buffer size. - vk::BufferUsageFlags usage{}; - BufferType type; - - u64 offset{}; ///< Buffer iterator. - u64 mapped_size{}; ///< Size reserved for the current copy. - bool is_coherent{}; ///< True if the buffer is coherent - - std::vector current_watches; ///< Watches recorded in the current iteration. - std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation. - std::optional invalidation_mark; ///< Number of watches used in the previous cycle. - - std::vector previous_watches; ///< Watches used in the previous iteration. - std::size_t wait_cursor{}; ///< Last watch being waited for completion. - u64 wait_bound{}; ///< Highest offset being watched for completion. -}; - -} // namespace Vulkan diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index e01a61ae7..94917be0a 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -260,7 +260,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { case AmdGpu::TilingMode::Display_MacroTiled: case AmdGpu::TilingMode::Texture_MacroTiled: case AmdGpu::TilingMode::Depth_MacroTiled: { - ASSERT(!props.is_cube && !props.is_block); + // ASSERT(!props.is_cube && !props.is_block); ASSERT(num_samples == 1); std::tie(mip_info.pitch, mip_info.size) = ImageSizeMacroTiled(mip_w, mip_h, bpp, num_samples, image.tiling_index); diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index 04bedaffc..ef6163c4e 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -61,23 +61,24 @@ vk::Format TrySwizzleFormat(vk::Format format, u32 dst_sel) { return format; } -ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept - : is_storage{is_storage} { +ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage_) noexcept + : is_storage{is_storage_} { type = ConvertImageViewType(image.GetType()); format = Vulkan::LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); range.base.level = image.base_level; range.base.layer = image.base_array; range.extent.levels = image.last_level + 1; range.extent.layers = image.last_array + 1; - mapping.r = ConvertComponentSwizzle(image.dst_sel_x); - mapping.g = ConvertComponentSwizzle(image.dst_sel_y); - mapping.b = ConvertComponentSwizzle(image.dst_sel_z); - mapping.a = ConvertComponentSwizzle(image.dst_sel_w); + if (!is_storage) { + mapping.r = ConvertComponentSwizzle(image.dst_sel_x); + mapping.g = ConvertComponentSwizzle(image.dst_sel_y); + mapping.b = ConvertComponentSwizzle(image.dst_sel_z); + mapping.a = ConvertComponentSwizzle(image.dst_sel_w); + } // Check for unfortunate case of storage images being swizzled const u32 num_comps = AmdGpu::NumComponents(image.GetDataFmt()); const u32 dst_sel = image.DstSelect(); if (is_storage && !IsIdentityMapping(dst_sel, num_comps)) { - mapping = vk::ComponentMapping{}; if (auto new_format = TrySwizzleFormat(format, dst_sel); new_format != format) { format = new_format; return; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 7b8a55547..53596f8e3 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -3,103 +3,22 @@ #include #include "common/assert.h" -#include "common/config.h" -#include "core/virtual_memory.h" +#include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/texture_cache/texture_cache.h" #include "video_core/texture_cache/tile_manager.h" -#ifndef _WIN64 -#include -#include - -#define PAGE_NOACCESS PROT_NONE -#define PAGE_READWRITE (PROT_READ | PROT_WRITE) -#define PAGE_READONLY PROT_READ -#else -#include - -void mprotect(void* addr, size_t len, int prot) { - DWORD old_prot{}; - BOOL result = VirtualProtect(addr, len, prot, &old_prot); - ASSERT_MSG(result != 0, "Region protection failed"); -} - -#endif - namespace VideoCore { -static TextureCache* g_texture_cache = nullptr; - -#ifndef _WIN64 -void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) { - ucontext_t* ctx = reinterpret_cast(raw_context); - const VAddr address = reinterpret_cast(info->si_addr); - -#ifdef __APPLE__ - const u32 err = ctx->uc_mcontext->__es.__err; -#else - const greg_t err = ctx->uc_mcontext.gregs[REG_ERR]; -#endif - - if (err & 0x2) { - g_texture_cache->OnCpuWrite(address); - } else { - // Read not supported! - UNREACHABLE(); - } -} -#else -LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept { - const u32 ec = pExp->ExceptionRecord->ExceptionCode; - if (ec == EXCEPTION_ACCESS_VIOLATION) { - const auto info = pExp->ExceptionRecord->ExceptionInformation; - if (info[0] == 1) { // Write violation - g_texture_cache->OnCpuWrite(info[1]); - return EXCEPTION_CONTINUE_EXECUTION; - } /* else { - UNREACHABLE(); - }*/ - } - return EXCEPTION_CONTINUE_SEARCH; // pass further -} -#endif - static constexpr u64 StreamBufferSize = 512_MB; static constexpr u64 PageShift = 12; -TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_) - : instance{instance_}, scheduler{scheduler_}, - staging{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, StreamBufferSize, - Vulkan::BufferType::Upload}, +TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, + BufferCache& buffer_cache_, PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, buffer_cache{buffer_cache_}, tracker{tracker_}, + staging{instance, scheduler, MemoryUsage::Upload, StreamBufferSize}, tile_manager{instance, scheduler} { - -#ifndef _WIN64 -#ifdef __APPLE__ - // Read-only memory write results in SIGBUS on Apple. - static constexpr int SignalType = SIGBUS; -#else - static constexpr int SignalType = SIGSEGV; -#endif - - sigset_t signal_mask; - sigemptyset(&signal_mask); - sigaddset(&signal_mask, SignalType); - - using HandlerType = decltype(sigaction::sa_sigaction); - - struct sigaction guest_access_fault {}; - guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK; - guest_access_fault.sa_sigaction = &GuestFaultSignalHandler; - guest_access_fault.sa_mask = signal_mask; - sigaction(SignalType, &guest_access_fault, nullptr); -#else - veh_handle = AddVectoredExceptionHandler(0, GuestFaultSignalHandler); - ASSERT_MSG(veh_handle, "Failed to register an exception handler"); -#endif - g_texture_cache = this; - ImageInfo info; info.pixel_format = vk::Format::eR8G8B8A8Unorm; info.type = vk::ImageType::e2D; @@ -110,15 +29,11 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& void(slot_image_views.insert(instance, view_info, slot_images[null_id], null_id)); } -TextureCache::~TextureCache() { -#if _WIN64 - RemoveVectoredExceptionHandler(veh_handle); -#endif -} +TextureCache::~TextureCache() = default; -void TextureCache::OnCpuWrite(VAddr address) { - std::unique_lock lock{m_page_table}; - ForEachImageInRegion(address, 1 << PageShift, [&](ImageId image_id, Image& image) { +void TextureCache::InvalidateMemory(VAddr address, size_t size) { + std::unique_lock lock{mutex}; + ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { // Ensure image is reuploaded when accessed again. image.flags |= ImageFlagBits::CpuModified; // Untrack image, so the range is unprotected and the guest can write freely. @@ -126,8 +41,28 @@ void TextureCache::OnCpuWrite(VAddr address) { }); } +void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { + std::scoped_lock lk{mutex}; + + boost::container::small_vector deleted_images; + ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); + for (const ImageId id : deleted_images) { + Image& image = slot_images[id]; + if (True(image.flags & ImageFlagBits::Tracked)) { + UntrackImage(image, id); + } + // TODO: Download image data back to host. + UnregisterImage(id); + DeleteImage(id); + } +} + ImageId TextureCache::FindImage(const ImageInfo& info, bool refresh_on_create) { - std::unique_lock lock{m_page_table}; + if (info.guest_address == 0) [[unlikely]] { + return NULL_IMAGE_VIEW_ID; + } + + std::unique_lock lock{mutex}; boost::container::small_vector image_ids; ForEachImageInRegion( info.guest_address, info.guest_size_bytes, [&](ImageId image_id, Image& image) { @@ -183,10 +118,6 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo } ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { - if (info.guest_address == 0) [[unlikely]] { - return slot_image_views[NULL_IMAGE_VIEW_ID]; - } - const ImageId image_id = FindImage(info); Image& image = slot_images[image_id]; auto& usage = image.info.usage; @@ -310,10 +241,7 @@ void TextureCache::RefreshImage(Image& image) { buffer = *upload_buffer; } else { // Upload data to the staging buffer. - const auto [data, offset_, _] = staging.Map(image.info.guest_size_bytes, 16); - std::memcpy(data, (void*)image.info.guest_address, image.info.guest_size_bytes); - staging.Commit(image.info.guest_size_bytes); - offset = offset_; + offset = staging.Copy(image.info.guest_address, image.info.guest_size_bytes, 16); } const auto& num_layers = image.info.resources.layers; @@ -344,9 +272,6 @@ void TextureCache::RefreshImage(Image& image) { } cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); - - image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead); } vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) { @@ -362,8 +287,6 @@ void TextureCache::RegisterImage(ImageId image_id) { image.flags |= ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.info.guest_size_bytes, [this, image_id](u64 page) { page_table[page].push_back(image_id); }); - - image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eNone); } void TextureCache::UnregisterImage(ImageId image_id) { @@ -373,11 +296,11 @@ void TextureCache::UnregisterImage(ImageId image_id) { image.flags &= ~ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.info.guest_size_bytes, [this, image_id](u64 page) { const auto page_it = page_table.find(page); - if (page_it == page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PageShift); + if (page_it == nullptr) { + UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PageShift); return; } - auto& image_ids = page_it.value(); + auto& image_ids = *page_it; const auto vector_it = std::ranges::find(image_ids, image_id); if (vector_it == image_ids.end()) { ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", page << PageShift); @@ -393,7 +316,7 @@ void TextureCache::TrackImage(Image& image, ImageId image_id) { return; } image.flags |= ImageFlagBits::Tracked; - UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, 1); + tracker.UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, 1); } void TextureCache::UntrackImage(Image& image, ImageId image_id) { @@ -401,40 +324,34 @@ void TextureCache::UntrackImage(Image& image, ImageId image_id) { return; } image.flags &= ~ImageFlagBits::Tracked; - UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, -1); + tracker.UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, -1); } -void TextureCache::UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta) { - std::scoped_lock lk{mutex}; - 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; +void TextureCache::DeleteImage(ImageId image_id) { + Image& image = slot_images[image_id]; + ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); + ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered"); - const auto pages_interval = - decltype(cached_pages)::interval_type::right_open(page_start, page_end); - if (delta > 0) { - cached_pages.add({pages_interval, delta}); + // Remove any registered meta areas. + const auto& meta_info = image.info.meta_info; + if (meta_info.cmask_addr) { + surface_metas.erase(meta_info.cmask_addr); + } + if (meta_info.fmask_addr) { + surface_metas.erase(meta_info.fmask_addr); + } + if (meta_info.htile_addr) { + surface_metas.erase(meta_info.htile_addr); } - 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; - void* addr = reinterpret_cast(interval_start_addr); - if (delta > 0 && count == delta) { - mprotect(addr, interval_size, PAGE_READONLY); - } else if (delta < 0 && count == -delta) { - mprotect(addr, interval_size, PAGE_READWRITE); - } else { - ASSERT(count >= 0); + // Reclaim image and any image views it references. + scheduler.DeferOperation([this, image_id] { + Image& image = slot_images[image_id]; + for (const ImageViewId image_view_id : image.image_view_ids) { + slot_image_views.erase(image_view_id); } - } - - if (delta < 0) { - cached_pages.add({pages_interval, delta}); - } + slot_images.erase(image_id); + }); } } // namespace VideoCore diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index aef33bcf1..17a09898d 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -4,12 +4,11 @@ #pragma once #include -#include #include #include "common/slot_vector.h" #include "video_core/amdgpu/resource.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" +#include "video_core/multi_level_page_table.h" #include "video_core/texture_cache/image.h" #include "video_core/texture_cache/image_view.h" #include "video_core/texture_cache/sampler.h" @@ -21,31 +20,28 @@ struct BufferAttributeGroup; namespace VideoCore { +class BufferCache; +class PageManager; + class TextureCache { - // This is the page shift for adding images into the hash map. It isn't related to - // the page size of the guest or the host and is chosen for convenience. A number too - // small will increase the number of hash map lookups per image, while too large will - // increase the number of images per page. - static constexpr u64 PageBits = 20; - static constexpr u64 PageMask = (1ULL << PageBits) - 1; - - struct MetaDataInfo { - enum class Type { - CMask, - FMask, - HTile, - }; - - Type type; - bool is_cleared; + struct Traits { + using Entry = boost::container::small_vector; + static constexpr size_t AddressSpaceBits = 39; + static constexpr size_t FirstLevelBits = 9; + static constexpr size_t PageBits = 22; }; + using PageTable = MultiLevelPageTable; public: - explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); + explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + BufferCache& buffer_cache, PageManager& tracker); ~TextureCache(); /// Invalidates any image in the logical page range. - void OnCpuWrite(VAddr address); + void InvalidateMemory(VAddr address, size_t size); + + /// Evicts any images that overlap the unmapped range. + void UnmapMemory(VAddr cpu_addr, size_t size); /// Retrieves the image handle of the image with the provided attributes. [[nodiscard]] ImageId FindImage(const ImageInfo& info, bool refresh_on_create = true); @@ -101,8 +97,8 @@ private: template static void ForEachPage(PAddr addr, size_t size, Func&& func) { static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; - const u64 page_end = (addr + size - 1) >> PageBits; - for (u64 page = addr >> PageBits; page <= page_end; ++page) { + const u64 page_end = (addr + size - 1) >> Traits::PageBits; + for (u64 page = addr >> Traits::PageBits; page <= page_end; ++page) { if constexpr (RETURNS_BOOL) { if (func(page)) { break; @@ -120,14 +116,14 @@ private: boost::container::small_vector images; ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { const auto it = page_table.find(page); - if (it == page_table.end()) { + if (it == nullptr) { if constexpr (BOOL_BREAK) { return false; } else { return; } } - for (const ImageId image_id : it->second) { + for (const ImageId image_id : *it) { Image& image = slot_images[image_id]; if (image.flags & ImageFlagBits::Picked) { continue; @@ -166,25 +162,32 @@ private: /// Stop tracking CPU reads and writes for image void UntrackImage(Image& image, ImageId image_id); - /// Increase/decrease the number of surface in pages touching the specified region - void UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta); + /// Removes the image and any views/surface metas that reference it. + void DeleteImage(ImageId image_id); private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - Vulkan::StreamBuffer staging; + BufferCache& buffer_cache; + PageManager& tracker; + StreamBuffer staging; TileManager tile_manager; Common::SlotVector slot_images; Common::SlotVector slot_image_views; tsl::robin_map samplers; - tsl::robin_pg_map> page_table; - boost::icl::interval_map cached_pages; - tsl::robin_map surface_metas; + PageTable page_table; std::mutex mutex; -#ifdef _WIN64 - void* veh_handle{}; -#endif - std::mutex m_page_table; + + struct MetaDataInfo { + enum class Type { + CMask, + FMask, + HTile, + }; + Type type; + bool is_cleared; + }; + tsl::robin_map surface_metas; }; } // namespace VideoCore diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 4f199f81f..d3a7d7960 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -183,10 +183,12 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eB8G8R8A8Srgb: case vk::Format::eB8G8R8A8Unorm: case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Uint: case vk::Format::eR32Sfloat: case vk::Format::eR32Uint: case vk::Format::eR16G16Sfloat: return vk::Format::eR32Uint; + case vk::Format::eBc1RgbaSrgbBlock: case vk::Format::eBc1RgbaUnormBlock: case vk::Format::eBc4UnormBlock: case vk::Format::eR32G32Sfloat: @@ -200,11 +202,20 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eBc5UnormBlock: case vk::Format::eBc7SrgbBlock: case vk::Format::eBc7UnormBlock: + case vk::Format::eBc6HUfloatBlock: + case vk::Format::eR32G32B32A32Sfloat: return vk::Format::eR32G32B32A32Uint; default: break; } - LOG_ERROR(Render_Vulkan, "Unexpected format for demotion {}", vk::to_string(format)); + + // Log missing formats only once to avoid spamming the log. + static constexpr size_t MaxFormatIndex = 256; + static std::array logged_formats{}; + if (const u32 index = u32(format); !logged_formats[index]) { + LOG_ERROR(Render_Vulkan, "Unexpected format for demotion {}", vk::to_string(format)); + logged_formats[index] = true; + } return format; } @@ -236,8 +247,11 @@ struct DetilerParams { u32 sizes[14]; }; +static constexpr size_t StreamBufferSize = 128_MB; + TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) - : instance{instance}, scheduler{scheduler} { + : instance{instance}, scheduler{scheduler}, + stream_buffer{instance, scheduler, MemoryUsage::Stream, StreamBufferSize} { static const std::array detiler_shaders{ HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, @@ -336,8 +350,7 @@ TileManager::ScratchBuffer TileManager::AllocBuffer(u32 size, bool is_storage /* .flags = !is_storage ? VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT : static_cast(0), - .usage = is_large_buffer ? VMA_MEMORY_USAGE_AUTO_PREFER_HOST - : VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, .requiredFlags = !is_storage ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : static_cast(0), }; @@ -373,37 +386,46 @@ std::optional TileManager::TryDetile(Image& image) { const auto* detiler = GetDetiler(image); if (!detiler) { - LOG_ERROR(Render_Vulkan, "Unsupported tiled image: {} ({})", - vk::to_string(image.info.pixel_format), NameOf(image.info.tiling_mode)); + if (image.info.tiling_mode != AmdGpu::TilingMode::Texture_MacroTiled) { + LOG_ERROR(Render_Vulkan, "Unsupported tiled image: {} ({})", + vk::to_string(image.info.pixel_format), NameOf(image.info.tiling_mode)); + } return std::nullopt; } // Prepare input buffer - auto in_buffer = AllocBuffer(image.info.guest_size_bytes); - Upload(in_buffer, reinterpret_cast(image.info.guest_address), - image.info.guest_size_bytes); + const u32 image_size = image.info.guest_size_bytes; + const auto [in_buffer, in_offset] = [&] -> std::pair { + // Use stream buffer for smaller textures. + if (image_size <= StreamBufferSize) { + u32 offset = stream_buffer.Copy(image.info.guest_address, image_size); + return {stream_buffer.Handle(), offset}; + } + // Request temporary host buffer for larger sizes. + auto in_buffer = AllocBuffer(image_size); + const auto addr = reinterpret_cast(image.info.guest_address); + Upload(in_buffer, addr, image_size); + scheduler.DeferOperation([=, this]() { FreeBuffer(in_buffer); }); + return {in_buffer.first, 0}; + }(); // Prepare output buffer - auto out_buffer = AllocBuffer(image.info.guest_size_bytes, true); - - scheduler.DeferOperation([=, this]() { - FreeBuffer(in_buffer); - FreeBuffer(out_buffer); - }); + auto out_buffer = AllocBuffer(image_size, true); + scheduler.DeferOperation([=, this]() { FreeBuffer(out_buffer); }); auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, *detiler->pl); const vk::DescriptorBufferInfo input_buffer_info{ - .buffer = in_buffer.first, - .offset = 0, - .range = image.info.guest_size_bytes, + .buffer = in_buffer, + .offset = in_offset, + .range = image_size, }; const vk::DescriptorBufferInfo output_buffer_info{ .buffer = out_buffer.first, .offset = 0, - .range = image.info.guest_size_bytes, + .range = image_size, }; std::vector set_writes{ @@ -442,16 +464,16 @@ std::optional TileManager::TryDetile(Image& image) { cmdbuf.pushConstants(*detiler->pl_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(params), ¶ms); - ASSERT((image.info.guest_size_bytes % 64) == 0); + ASSERT((image_size % 64) == 0); const auto bpp = image.info.num_bits * (image.info.props.is_block ? 16u : 1u); - const auto num_tiles = image.info.guest_size_bytes / (64 * (bpp / 8)); + const auto num_tiles = image_size / (64 * (bpp / 8)); cmdbuf.dispatch(num_tiles, 1, 1); const vk::BufferMemoryBarrier post_barrier{ .srcAccessMask = vk::AccessFlagBits::eShaderWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .buffer = out_buffer.first, - .size = image.info.guest_size_bytes, + .size = image_size, }; cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index 9102da089..00765b1f8 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -4,7 +4,7 @@ #pragma once #include "common/types.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" +#include "video_core/buffer_cache/buffer.h" #include "video_core/texture_cache/image.h" namespace VideoCore { @@ -34,7 +34,7 @@ struct DetilerContext { class TileManager { public: - using ScratchBuffer = std::pair; + using ScratchBuffer = std::pair; TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); ~TileManager(); @@ -51,6 +51,7 @@ private: private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; + StreamBuffer stream_buffer; std::array detilers; }; From 341034fc3056005501f0d0bfaa59a78210a8bdbb Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:44:05 +0300 Subject: [PATCH 050/109] filter: Add random library --- src/common/logging/filter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 32576abe1..a514652d4 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -111,6 +111,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, ErrorDialog) \ SUB(Lib, ImeDialog) \ SUB(Lib, AvPlayer) \ + SUB(Lib, Random) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ From 3fd2abdd5b44eb3120d6cbbc1e74f55e3e6e99fc Mon Sep 17 00:00:00 2001 From: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:00:08 +0300 Subject: [PATCH 051/109] vk_graphics_pipeline: Fix regression --- .../backend/spirv/emit_spirv_context_get_set.cpp | 8 ++++---- .../backend/spirv/spirv_emit_context.cpp | 2 +- src/shader_recompiler/backend/spirv/spirv_emit_context.h | 1 + src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | 2 +- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 5 ++--- 5 files changed, 9 insertions(+), 9 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 40d6cdb75..e85272e96 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 @@ -129,7 +129,7 @@ Id EmitReadConst(EmitContext& ctx) { Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { auto& buffer = ctx.buffers[handle]; if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(handle); + buffer.offset = ctx.GetBufferOffset(buffer.global_binding); } const Id offset_dwords{ctx.OpShiftRightLogical(ctx.U32[1], buffer.offset, ctx.ConstU32(2U))}; index = ctx.OpIAdd(ctx.U32[1], index, offset_dwords); @@ -230,7 +230,7 @@ template static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(handle); + buffer.offset = ctx.GetBufferOffset(buffer.global_binding); } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); @@ -412,7 +412,7 @@ template static Id EmitLoadBufferFormatF32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(handle); + buffer.offset = ctx.GetBufferOffset(buffer.global_binding); } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); if constexpr (N == 1) { @@ -446,7 +446,7 @@ template static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { auto& buffer = ctx.buffers[handle]; if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(handle); + buffer.offset = ctx.GetBufferOffset(buffer.global_binding); } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index cdf417fc4..61b55437d 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -352,9 +352,9 @@ void EmitContext::DefineBuffers() { Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}", buffer.is_storage ? "ssbo" : "cbuf", buffer.sgpr_base)); - binding++; buffers.push_back({ .id = id, + .global_binding = binding++, .data_types = data_types, .pointer_type = pointer_type, .buffer = buffer.GetVsharp(info), diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index ff9ec4b71..0d090eb31 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -203,6 +203,7 @@ public: struct BufferDefinition { Id id; Id offset; + u32 global_binding; const VectorIds* data_types; Id pointer_type; AmdGpu::Buffer buffer; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 8a98e9680..21710a76a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -125,7 +125,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, const u32 adjust = offset - offset_aligned; if (adjust != 0) { ASSERT(adjust % 4 == 0); - push_data.AddOffset(i, adjust); + push_data.AddOffset(binding, adjust); } buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); set_writes.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 91ff999e5..5d87a1caf 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -343,7 +343,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, push_data.step0 = regs.vgt_instance_step_rate_0; push_data.step1 = regs.vgt_instance_step_rate_1; } - for (u32 i = 0; const auto& buffer : stage.buffers) { + for (const auto& buffer : stage.buffers) { const auto vsharp = buffer.GetVsharp(stage); if (vsharp) { const VAddr address = vsharp.base_address; @@ -359,7 +359,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, const u32 adjust = offset - offset_aligned; if (adjust != 0) { ASSERT(adjust % 4 == 0); - push_data.AddOffset(i, adjust); + push_data.AddOffset(binding, adjust); } buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); } else { @@ -374,7 +374,6 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); - i++; } boost::container::static_vector tsharps; From 254b9ffb5095f946b94d537bedd07701f9c9e44e Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:19:44 +0200 Subject: [PATCH 052/109] Workflows cleanup + misc fixes (#371) * Workflows cleanup * clang-format * SDL3: Disabling unnecessary options * Revert CMakeLists.txt changes --- .github/workflows/linux-qt.yml | 5 ++--- .github/workflows/linux.yml | 2 -- .github/workflows/macos-qt.yml | 5 ++--- .github/workflows/macos.yml | 2 -- .github/workflows/windows-qt.yml | 7 ------- .github/workflows/windows.yml | 12 +++--------- externals/CMakeLists.txt | 7 +++++++ src/qt_gui/main_window.cpp | 24 ++++++++++++------------ src/qt_gui/main_window_ui.h | 2 +- 9 files changed, 27 insertions(+), 39 deletions(-) diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml index 31f8df01e..5611ae50f 100644 --- a/.github/workflows/linux-qt.yml +++ b/.github/workflows/linux-qt.yml @@ -10,7 +10,6 @@ on: branches: [ "main" ] env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: @@ -19,8 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Fetch submodules - run: git submodule update --init --recursive + with: + submodules: recursive - name: Install misc packages run: > diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7ebb33651..ef77a16c8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -8,10 +8,8 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] - workflow_dispatch: env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml index e9a9aa4f3..4b3672dff 100644 --- a/.github/workflows/macos-qt.yml +++ b/.github/workflows/macos-qt.yml @@ -8,10 +8,8 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] - workflow_dispatch: env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: @@ -36,10 +34,11 @@ jobs: - name: Setup Qt uses: jurplel/install-qt-action@v4 with: + version: 6.7.2 host: mac target: desktop arch: clang_64 - version: 6.7.2 + archives: qtbase - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 910f0484e..e46401cb7 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -8,10 +8,8 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] - workflow_dispatch: env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 019a8ab2f..06a16eb5b 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -10,12 +10,8 @@ on: branches: [ "main" ] env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release -permissions: - contents: read - jobs: build: runs-on: windows-latest @@ -35,12 +31,9 @@ jobs: archives: qtbase - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON - name: Build - # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - name: Deploy diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 01ae3f9c3..46dc13a89 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -10,12 +10,8 @@ on: branches: [ "main" ] env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release -permissions: - contents: read - jobs: build: runs-on: windows-latest @@ -25,16 +21,14 @@ jobs: submodules: recursive - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL + - name: Build - # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - - name: Upload a Build Artifact + + - name: Upload executable uses: actions/upload-artifact@v4 with: name: shadps4-win64 - # A file, directory or wildcard pattern that describes what to upload path: | ${{github.workspace}}/build/Release/shadPS4.exe diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9ebdd8783..6426ef16d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -59,7 +59,14 @@ endif() # SDL3 if (NOT TARGET SDL3::SDL3) + set(SDL_DIRECTX OFF) + set(SDL_MMX OFF) + set(SDL_OPENGL OFF) + set(SDL_OPENGLES OFF) set(SDL_PIPEWIRE OFF) + set(SDL_RENDER_D3D OFF) + set(SDL_WASAPI OFF) + set(SDL_XINPUT OFF) add_subdirectory(sdl3) endif() diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index b3778be07..0b88a84f7 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -51,8 +51,8 @@ bool MainWindow::Init() { this->setStatusBar(statusBar.data()); // Update status bar int numGames = m_game_info->m_games.size(); - QString statusMessage = "Games: " + QString::number(numGames) + " (" + - QString::number(duration.count()) + "ms). Ready."; + QString statusMessage = + "Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)"; statusBar->showMessage(statusMessage); return true; } @@ -72,8 +72,8 @@ void MainWindow::CreateActions() { // create action group for themes m_theme_act_group = new QActionGroup(this); - m_theme_act_group->addAction(ui->setThemeLight); m_theme_act_group->addAction(ui->setThemeDark); + m_theme_act_group->addAction(ui->setThemeLight); m_theme_act_group->addAction(ui->setThemeGreen); m_theme_act_group->addAction(ui->setThemeBlue); m_theme_act_group->addAction(ui->setThemeViolet); @@ -344,14 +344,6 @@ void MainWindow::CreateConnects() { }); // Themes - connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { - m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Light)); - if (!isIconBlack) { - SetUiIcons(true); - isIconBlack = true; - } - }); connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Dark)); @@ -360,6 +352,14 @@ void MainWindow::CreateConnects() { isIconBlack = false; } }); + connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { + m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); + Config::setMainWindowTheme(static_cast(Theme::Light)); + if (!isIconBlack) { + SetUiIcons(true); + isIconBlack = true; + } + }); connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar); Config::setMainWindowTheme(static_cast(Theme::Green)); @@ -415,7 +415,7 @@ void MainWindow::RefreshGameTable() { m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); statusBar->clearMessage(); int numGames = m_game_info->m_games.size(); - QString statusMessage = "Games: " + QString::number(numGames) + ". Ready."; + QString statusMessage = "Games: " + QString::number(numGames); statusBar->showMessage(statusMessage); } diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 69d718477..06e5cf7fb 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -297,7 +297,7 @@ public: menuRecent->setTitle(QCoreApplication::translate("MainWindow", "Recent Games", nullptr)); exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr)); #if QT_CONFIG(tooltip) - exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr)); + exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit shadPS4", nullptr)); #endif // QT_CONFIG(tooltip) #if QT_CONFIG(statustip) exitAct->setStatusTip( From 351f2e10737b26bc2f82e44f770e051a354dbf8b Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:23:44 -0500 Subject: [PATCH 053/109] double click to open games --- src/qt_gui/main_window.cpp | 58 +++++++++++++++++++++----------------- src/qt_gui/main_window.h | 1 + 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 0b88a84f7..29d7c15fe 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -179,32 +179,11 @@ void MainWindow::CreateConnects() { } }); - connect(ui->playButton, &QPushButton::clicked, this, [this]() { - QString gamePath = ""; - int table_mode = Config::getTableMode(); - if (table_mode == 0) { - if (m_game_list_frame->currentItem()) { - int itemID = m_game_list_frame->currentItem()->row(); - gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); - } - } else if (table_mode == 1) { - if (m_game_grid_frame->cellClicked) { - int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + - m_game_grid_frame->crtColumn; - gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); - } - } else { - if (m_elf_viewer->currentItem()) { - int itemID = m_elf_viewer->currentItem()->row(); - gamePath = QString::fromStdString(m_elf_viewer->m_elf_list[itemID].toStdString()); - } - } - if (gamePath != "") { - AddRecentFiles(gamePath); - Core::Emulator emulator; - emulator.Run(gamePath.toUtf8().constData()); - } - }); + connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::StartGame); + connect(m_game_grid_frame.get(), &QTableWidget::cellDoubleClicked, this, + &MainWindow::StartGame); + connect(m_game_list_frame.get(), &QTableWidget::cellDoubleClicked, this, + &MainWindow::StartGame); connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() { if (isTableList) { @@ -386,6 +365,33 @@ void MainWindow::CreateConnects() { }); } +void MainWindow::StartGame() { + QString gamePath = ""; + int table_mode = Config::getTableMode(); + if (table_mode == 0) { + if (m_game_list_frame->currentItem()) { + int itemID = m_game_list_frame->currentItem()->row(); + gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); + } + } else if (table_mode == 1) { + if (m_game_grid_frame->cellClicked) { + int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + + m_game_grid_frame->crtColumn; + gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); + } + } else { + if (m_elf_viewer->currentItem()) { + int itemID = m_elf_viewer->currentItem()->row(); + gamePath = QString::fromStdString(m_elf_viewer->m_elf_list[itemID].toStdString()); + } + } + if (gamePath != "") { + AddRecentFiles(gamePath); + Core::Emulator emulator; + emulator.Run(gamePath.toUtf8().constData()); + } +} + void MainWindow::SearchGameTable(const QString& text) { if (isTableList) { for (int row = 0; row < m_game_list_frame->rowCount(); row++) { diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index d1ef48dc7..39a5d049e 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -39,6 +39,7 @@ public: bool Init(); void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg); void InstallDirectory(); + void StartGame(); private Q_SLOTS: void ConfigureGuiFromSettings(); From 7e5cc6162cfa5ff951bee48d440d0834fa8a7461 Mon Sep 17 00:00:00 2001 From: SamuelFontes Date: Thu, 8 Aug 2024 15:57:43 -0300 Subject: [PATCH 054/109] qt_gui: Refreshing game list after install directory change The game list wasn't being refreshed automaticly after a manual directory change on the QT GUI, now the RefreshGameTable will be called after the GameInstallDialog is executed. --- src/qt_gui/main_window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 29d7c15fe..646433ee7 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -583,6 +583,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int void MainWindow::InstallDirectory() { GameInstallDialog dlg; dlg.exec(); + RefreshGameTable(); } void MainWindow::SetLastUsedTheme() { From 564b2f63105ecb4cc1ead7a218084c1ee19ff07d Mon Sep 17 00:00:00 2001 From: SamuelFontes Date: Thu, 8 Aug 2024 16:14:35 -0300 Subject: [PATCH 055/109] 361: Game directory window appears every time qt_gui: When a command line argument is passed to the GUI version, it will always prompt to change the game directory. This happens because the "user" folder is created on the elf or eboot.bin location. This change will ignore the game install directory configuration at startup when an command line argument is passed. Since if a game was passed, it should start automatically as this is the expected behaviour. --- src/qt_gui/main.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 15a06c867..cff01cc2b 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -21,8 +21,11 @@ int main(int argc, char* argv[]) { Config::load(user_dir / "config.toml"); std::filesystem::create_directory(user_dir / "game_data"); + // Check if elf or eboot.bin path was passed as a command line argument + bool has_command_line_argument = argc > 1; + // Check if the game install directory is set - if (Config::getGameInstallDir() == "") { + if (Config::getGameInstallDir() == "" && !has_command_line_argument) { GameInstallDialog dlg; dlg.exec(); } @@ -35,7 +38,7 @@ int main(int argc, char* argv[]) { m_main_window->Init(); // Check for command line arguments - if (argc > 1) { + if (has_command_line_argument) { Core::Emulator emulator; emulator.Run(argv[1]); } From e5087877ae527fedc939791289fe843a57e69ee0 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 8 Aug 2024 22:31:14 +0300 Subject: [PATCH 056/109] revert some sdl switches --- externals/CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 6426ef16d..9ebdd8783 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -59,14 +59,7 @@ endif() # SDL3 if (NOT TARGET SDL3::SDL3) - set(SDL_DIRECTX OFF) - set(SDL_MMX OFF) - set(SDL_OPENGL OFF) - set(SDL_OPENGLES OFF) set(SDL_PIPEWIRE OFF) - set(SDL_RENDER_D3D OFF) - set(SDL_WASAPI OFF) - set(SDL_XINPUT OFF) add_subdirectory(sdl3) endif() From 48c58d5ce0ca430518a5c2fc237d66783a83dc3c Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:42:51 -0500 Subject: [PATCH 057/109] Kernel-Related Fixes (#386) * Fix OrbisKernelBatchMapEntry struct UE4 games and GTA V cause the BatchMap offset to overflow on Windows. Changing the type fixes this, and doesn't seem to cause any regressions on Windows or Linux. * Implement posix_sem_trywait Grand Theft Auto V needs this. * Add missing scePthreadAttrGetdetachstate NID Noticed this missing NID while testing games. --- src/core/libraries/kernel/memory_management.h | 2 +- src/core/libraries/kernel/thread_management.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 25a4a9f09..6735ead71 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -63,7 +63,7 @@ struct OrbisVirtualQueryInfo { struct OrbisKernelBatchMapEntry { void* start; - off_t offset; + size_t offset; size_t length; char protection; char type; diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 48347ea53..6be90cce3 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1360,6 +1360,10 @@ int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) { return sem_wait(sem); } +int PS4_SYSV_ABI posix_sem_trywait(sem_t* sem) { + return sem_trywait(sem); +} + #ifndef HAVE_SEM_TIMEDWAIT int sem_timedwait(sem_t* sem, const struct timespec* abstime) { int rc; @@ -1499,6 +1503,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WrOLvHU0yQM", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setspecific); LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetschedpolicy); LIB_FUNCTION("-Wreprtu0Qs", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetdetachstate); + LIB_FUNCTION("JaRMy+QcpeU", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetdetachstate); LIB_FUNCTION("eXbUSpEaTsA", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetinheritsched); LIB_FUNCTION("DzES9hQF4f4", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetschedparam); LIB_FUNCTION("nsYoNRywwNg", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrInit); @@ -1611,6 +1616,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Xs9hdiD7sAA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setschedparam); LIB_FUNCTION("pDuPEf3m4fI", "libScePosix", 1, "libkernel", 1, 1, posix_sem_init); LIB_FUNCTION("YCV5dGGBcCo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_wait); + LIB_FUNCTION("WBWzsRifCEA", "libScePosix", 1, "libkernel", 1, 1, posix_sem_trywait); LIB_FUNCTION("w5IHyvahg-o", "libScePosix", 1, "libkernel", 1, 1, posix_sem_timedwait); LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post); LIB_FUNCTION("cDW233RAwWo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_destroy); From ab56665d4b75e13e99e824dc531b8642d0377632 Mon Sep 17 00:00:00 2001 From: SleepingSnakezzz <71992016+SleepingSnakezzz@users.noreply.github.com> Date: Thu, 8 Aug 2024 22:43:21 +0200 Subject: [PATCH 058/109] Update latest build instructions.md (#385) Expands on the existing instructions for more clarity. --- documents/Quickstart/Quickstart.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 29c7ba499..4c51b288a 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -37,13 +37,18 @@ SPDX-License-Identifier: GPL-2.0-or-later - Windows 10 or Ubuntu 22.04 -## Have the latest WIP version +## How to run the latest Work-in-Progress builds of ShadPS4 -When you go to Github Release, you have the latest major versions (e.g. v0.0.3), but if you want to have the latest Work-In-Progress version, you can go to Actions on Github to download it (Please note a Github account is required to be able to download). +1. Go to and make sure you are logged into your GitHub account (important!) +2. On the left side of the page, select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line). ![image](https://github.com/user-attachments/assets/43f01bbf-236c-4d6d-98ac-f5a5badd4ce8) - +3. In the workflow list, select the latest entry with a green :white_check_mark: icon in front of it. (or the latest entry for whatever pull request you wish to test). ![image](https://github.com/user-attachments/assets/6365f407-867c-44ae-bf00-944f8d84a349) -After downloading the version suitable for you (Windows or Linux), you must unzip the file and then you can run it. Please note, there are two versions for each platform, a Qt version with user interface and one without (SDL Builds). +4. On the bottom of this page, select the name of the file, and it should start downloading. (If there is no file here, double check that you are indeed logged into a GitHub account, and that there is a green :white_check_mark: icon. ![image](https://github.com/user-attachments/assets/97924500-3911-4f90-ab63-ffae7e52700b) + +5. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder. + +6. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to. ## Install PKG files From 5a68224a13f9bcebcdc0c5b69839f9f60b574601 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 5 Aug 2024 19:27:03 +0300 Subject: [PATCH 059/109] update submodules , fixed sdl update , rewrote config with new toml11 v4 --- externals/fmt | 2 +- externals/glslang | 2 +- externals/magic_enum | 2 +- externals/sdl3 | 2 +- externals/toml11 | 2 +- externals/vulkan-headers | 2 +- src/common/config.cpp | 108 +++++++++++++++++---------------------- src/sdl_window.cpp | 4 +- 8 files changed, 55 insertions(+), 69 deletions(-) diff --git a/externals/fmt b/externals/fmt index bc8d32e96..15f939c3d 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit bc8d32e9643d2be5fc070abf2a50bc021544545d +Subproject commit 15f939c3de44d4e7521432b0c9d82f9358b24c2a diff --git a/externals/glslang b/externals/glslang index 52f68dc6b..e40c14a3e 160000 --- a/externals/glslang +++ b/externals/glslang @@ -1 +1 @@ -Subproject commit 52f68dc6b2a9d017b43161f31f13a6f44636ee7c +Subproject commit e40c14a3e007fac0e4f2e4164fdf14d1712355bd diff --git a/externals/magic_enum b/externals/magic_enum index 664ee62c1..dae6bbf16 160000 --- a/externals/magic_enum +++ b/externals/magic_enum @@ -1 +1 @@ -Subproject commit 664ee62c12570948b0e025d15b42d641fba8d54a +Subproject commit dae6bbf16c363e9ead4e628a47fdb02956a634f3 diff --git a/externals/sdl3 b/externals/sdl3 index f9a06c20e..881e2bc34 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit f9a06c20ed85fb1d6754fc2280d6183382217910 +Subproject commit 881e2bc344f43bf04927fb2045f46f3d3f611d36 diff --git a/externals/toml11 b/externals/toml11 index b389bbc4e..12c0f379f 160000 --- a/externals/toml11 +++ b/externals/toml11 @@ -1 +1 @@ -Subproject commit b389bbc4ebf90fa2fe7651de3046fb19f661ba3c +Subproject commit 12c0f379f2e865b4ce984758d5ae004f9de07d69 diff --git a/externals/vulkan-headers b/externals/vulkan-headers index b379292b2..595c8d479 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit b379292b2ab6df5771ba9870d53cf8b2c9295daf +Subproject commit 595c8d4794410a4e64b98dc58d27c0310d7ea2fd diff --git a/src/common/config.cpp b/src/common/config.cpp index 7e677f84a..c2ea1fe37 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -223,90 +223,76 @@ void load(const std::filesystem::path& path) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; } - if (data.contains("General")) { - auto generalResult = toml::expect(data.at("General")); - if (generalResult.is_ok()) { - auto general = generalResult.unwrap(); + const toml::value& general = data.at("General"); - isNeo = toml::find_or(general, "isPS4Pro", false); - isFullscreen = toml::find_or(general, "Fullscreen", false); - logFilter = toml::find_or(general, "logFilter", ""); - logType = toml::find_or(general, "logType", "sync"); - isShowSplash = toml::find_or(general, "showSplash", true); - } + isNeo = toml::find_or(general, "isPS4Pro", false); + isFullscreen = toml::find_or(general, "Fullscreen", false); + logFilter = toml::find_or(general, "logFilter", ""); + logType = toml::find_or(general, "logType", "sync"); + isShowSplash = toml::find_or(general, "showSplash", true); } + if (data.contains("GPU")) { - auto gpuResult = toml::expect(data.at("GPU")); - if (gpuResult.is_ok()) { - auto gpu = gpuResult.unwrap(); + const toml::value& gpu = data.at("GPU"); - screenWidth = toml::find_or(gpu, "screenWidth", screenWidth); - screenHeight = toml::find_or(gpu, "screenHeight", screenHeight); - isNullGpu = toml::find_or(gpu, "nullGpu", false); - shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); - shouldDumpPM4 = toml::find_or(gpu, "dumpPM4", false); - vblankDivider = toml::find_or(gpu, "vblankDivider", 1); - } + screenWidth = toml::find_or(gpu, "screenWidth", screenWidth); + screenHeight = toml::find_or(gpu, "screenHeight", screenHeight); + isNullGpu = toml::find_or(gpu, "nullGpu", false); + shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); + shouldDumpPM4 = toml::find_or(gpu, "dumpPM4", false); + vblankDivider = toml::find_or(gpu, "vblankDivider", 1); } + if (data.contains("Vulkan")) { - const auto vkResult = toml::expect(data.at("Vulkan")); - if (vkResult.is_ok()) { - auto vk = vkResult.unwrap(); + const toml::value& vk = data.at("Vulkan"); - gpuId = toml::find_or(vk, "gpuId", 0); - vkValidation = toml::find_or(vk, "validation", true); - vkValidationSync = toml::find_or(vk, "validation_sync", true); - rdocEnable = toml::find_or(vk, "rdocEnable", false); - } + gpuId = toml::find_or(vk, "gpuId", -1); + vkValidation = toml::find_or(vk, "validation", false); + vkValidationSync = toml::find_or(vk, "validation_sync", false); + rdocEnable = toml::find_or(vk, "rdocEnable", false); } + if (data.contains("Debug")) { - auto debugResult = toml::expect(data.at("Debug")); - if (debugResult.is_ok()) { - auto debug = debugResult.unwrap(); + const toml::value& debug = data.at("Debug"); - isDebugDump = toml::find_or(debug, "DebugDump", false); - } + isDebugDump = toml::find_or(debug, "DebugDump", false); } + if (data.contains("LLE")) { - auto lleResult = toml::expect(data.at("LLE")); - if (lleResult.is_ok()) { - auto lle = lleResult.unwrap(); + const toml::value& lle = data.at("LLE"); - isLibc = toml::find_or(lle, "libc", true); - } + isLibc = toml::find_or(lle, "libc", true); } - if (data.contains("GUI")) { - auto guiResult = toml::expect(data.at("GUI")); - if (guiResult.is_ok()) { - auto gui = guiResult.unwrap(); - m_icon_size = toml::find_or(gui, "iconSize", 0); - m_icon_size_grid = toml::find_or(gui, "iconSizeGrid", 0); - m_slider_pos = toml::find_or(gui, "sliderPos", 0); - m_slider_pos_grid = toml::find_or(gui, "sliderPosGrid", 0); - mw_themes = toml::find_or(gui, "theme", 0); - m_window_size_W = toml::find_or(gui, "mw_width", 0); - m_window_size_H = toml::find_or(gui, "mw_height", 0); - settings_install_dir = toml::find_or(gui, "installDir", ""); - main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); - main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); - main_window_geometry_w = toml::find_or(gui, "geometry_w", 0); - main_window_geometry_h = toml::find_or(gui, "geometry_h", 0); - m_pkg_viewer = toml::find_or>(gui, "pkgDirs", {}); - m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); - m_recent_files = toml::find_or>(gui, "recentFiles", {}); - m_table_mode = toml::find_or(gui, "gameTableMode", 0); - } + if (data.contains("GUI")) { + const toml::value& gui = data.at("GUI"); + + m_icon_size = toml::find_or(gui, "iconSize", 0); + m_icon_size_grid = toml::find_or(gui, "iconSizeGrid", 0); + m_slider_pos = toml::find_or(gui, "sliderPos", 0); + m_slider_pos_grid = toml::find_or(gui, "sliderPosGrid", 0); + mw_themes = toml::find_or(gui, "theme", 0); + m_window_size_W = toml::find_or(gui, "mw_width", 0); + m_window_size_H = toml::find_or(gui, "mw_height", 0); + settings_install_dir = toml::find_or(gui, "installDir", ""); + main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); + main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); + main_window_geometry_w = toml::find_or(gui, "geometry_w", 0); + main_window_geometry_h = toml::find_or(gui, "geometry_h", 0); + m_pkg_viewer = toml::find_or>(gui, "pkgDirs", {}); + m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); + m_recent_files = toml::find_or>(gui, "recentFiles", {}); + m_table_mode = toml::find_or(gui, "gameTableMode", 0); } } void save(const std::filesystem::path& path) { - toml::basic_value data; + toml::value data; std::error_code error; if (std::filesystem::exists(path, error)) { try { - data = toml::parse(path); + data = toml::parse(path); } catch (const std::exception& ex) { fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); return; diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 0d25cd3ff..5084a5ae7 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -45,8 +45,8 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ #if defined(SDL_PLATFORM_WIN32) window_info.type = WindowSystemType::Windows; - window_info.render_surface = - SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + window_info.render_surface = SDL_GetPointerProperty(SDL_GetWindowProperties(window), + SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); #elif defined(SDL_PLATFORM_LINUX) if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { window_info.type = WindowSystemType::X11; From 13331cdda940088bcdc74f1298f5b97df52bb2ad Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 5 Aug 2024 19:52:30 +0300 Subject: [PATCH 060/109] linux fix? --- src/sdl_window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 5084a5ae7..7133c87fc 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -50,8 +50,8 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ #elif defined(SDL_PLATFORM_LINUX) if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { window_info.type = WindowSystemType::X11; - window_info.display_connection = SDL_GetProperty(SDL_GetWindowProperties(window), - SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + window_info.display_connection = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); window_info.render_surface = (void*)SDL_GetNumberProperty( SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); } else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { From 4ffb812e94593e886bdb96e80c7de8d1166df5e1 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 5 Aug 2024 22:36:25 +0300 Subject: [PATCH 061/109] more linux fix? --- src/sdl_window.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 7133c87fc..5e1a4c952 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -56,10 +56,10 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); } else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { window_info.type = WindowSystemType::Wayland; - window_info.display_connection = SDL_GetProperty( + window_info.display_connection = SDL_GetPointerProperty( SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); - window_info.render_surface = SDL_GetProperty(SDL_GetWindowProperties(window), - SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + window_info.render_surface = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); } #elif defined(SDL_PLATFORM_MACOS) window_info.type = WindowSystemType::Metal; From 816700d34d30498e99070bc6f113ada320315e80 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 6 Aug 2024 18:43:09 +0300 Subject: [PATCH 062/109] fixed to scePadGetControllerInformation , (fixes CUSA04892 - power rangers multi user issue) --- src/core/libraries/pad/pad.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 064c71b84..b79e330ad 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -86,6 +86,18 @@ int PS4_SYSV_ABI scePadGetCapability() { int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { LOG_INFO(Lib_Pad, "called handle = {}", handle); std::memset(pInfo, 0, sizeof(OrbisPadControllerInformation)); + if (handle < 0) { + pInfo->touchPadInfo.pixelDensity = 1; + pInfo->touchPadInfo.resolution.x = 1920; + pInfo->touchPadInfo.resolution.y = 950; + pInfo->stickInfo.deadZoneLeft = 2; + pInfo->stickInfo.deadZoneRight = 2; + pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; + pInfo->connectedCount = 1; + pInfo->connected = false; + pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD; + return SCE_OK; + } pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; From a83ac4c05e3022d15ef9c95fd169dfd00d1c8c15 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 6 Aug 2024 19:28:17 +0300 Subject: [PATCH 063/109] removed duplicate libSceJson2.sprx loading --- src/emulator.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index a34ee359b..d1abf5d6b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -182,7 +182,7 @@ void Emulator::Run(const std::filesystem::path& file) { } void Emulator::LoadSystemModules(const std::filesystem::path& file) { - constexpr std::array ModulesToLoad{ + constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", nullptr}, {"libSceFiber.sprx", nullptr}, {"libSceUlt.sprx", nullptr}, @@ -191,8 +191,7 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) { {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, - {"libSceJpegEnc.sprx", nullptr}, - {"libSceJson2.sprx", nullptr}}, + {"libSceJpegEnc.sprx", nullptr}}, }; std::vector found_modules; From 7b7d1cb26f54cf20b15e3ebb658eaaa324e67c80 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 6 Aug 2024 20:23:21 +0300 Subject: [PATCH 064/109] added missing NIDs in thread management --- src/core/libraries/kernel/thread_management.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 6be90cce3..85e2d0e66 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1519,6 +1519,8 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("1tKyG7RlMJo", "libkernel", 1, "libkernel", 1, 1, scePthreadGetprio); LIB_FUNCTION("W0Hpm2X0uPE", "libkernel", 1, "libkernel", 1, 1, scePthreadSetprio); LIB_FUNCTION("GBUY7ywdULE", "libkernel", 1, "libkernel", 1, 1, scePthreadRename); + LIB_FUNCTION("F+yfmduIBB8", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetstackaddr); + LIB_FUNCTION("El+cQ20DynU", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetguardsize); LIB_FUNCTION("aI+OeCz8xrQ", "libkernel", 1, "libkernel", 1, 1, scePthreadSelf); LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_self); From 3ef69cae5e1f1115475150ddcff9609880fdd39c Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 7 Aug 2024 21:39:17 +0300 Subject: [PATCH 065/109] some pad fixes (fixing metal slug 3 and risk of rain) --- src/core/libraries/pad/pad.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index b79e330ad..8405a5390 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Generated By moduleGenerator +#include #include #include "common/logging/log.h" #include "core/libraries/error_codes.h" @@ -85,7 +86,6 @@ int PS4_SYSV_ABI scePadGetCapability() { int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { LOG_INFO(Lib_Pad, "called handle = {}", handle); - std::memset(pInfo, 0, sizeof(OrbisPadControllerInformation)); if (handle < 0) { pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; @@ -251,7 +251,9 @@ int PS4_SYSV_ABI scePadOutputReport() { } int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - std::memset(pData, 0, sizeof(OrbisPadData)); + if (handle != 1) { + UNREACHABLE_MSG("unknown handle"); + } int connected_count = 0; bool connected = false; Input::State states[64]; @@ -318,11 +320,12 @@ int PS4_SYSV_ABI scePadReadHistory() { int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { auto* controller = Common::Singleton::Instance(); - + if (handle != 1) { + UNREACHABLE_MSG("unknown handle"); + } int connectedCount = 0; bool isConnected = false; Input::State state; - std::memset(pData, 0, sizeof(OrbisPadData)); controller->ReadState(&state, &isConnected, &connectedCount); pData->buttons = state.buttonsState; pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; @@ -334,7 +337,7 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { pData->orientation.x = 0; pData->orientation.y = 0; pData->orientation.z = 0; - pData->orientation.w = 0; + pData->orientation.w = 1; pData->acceleration.x = 0.0f; pData->acceleration.y = 0.0f; pData->acceleration.z = 0.0f; From 6a2e09a1dfa77d7daa6a20c270b5df19dd76f107 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 8 Aug 2024 10:45:34 +0300 Subject: [PATCH 066/109] update submodules (possible mac fix?) --- externals/fmt | 2 +- externals/glslang | 2 +- externals/sdl3 | 2 +- externals/toml11 | 2 +- externals/xxhash | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/externals/fmt b/externals/fmt index 15f939c3d..9f0c0c468 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 15f939c3de44d4e7521432b0c9d82f9358b24c2a +Subproject commit 9f0c0c468be45357d85efc1788b7561fafbdebb1 diff --git a/externals/glslang b/externals/glslang index e40c14a3e..5398d55e3 160000 --- a/externals/glslang +++ b/externals/glslang @@ -1 +1 @@ -Subproject commit e40c14a3e007fac0e4f2e4164fdf14d1712355bd +Subproject commit 5398d55e33dff7d26fecdd2c35808add986c558c diff --git a/externals/sdl3 b/externals/sdl3 index 881e2bc34..b5b868044 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit 881e2bc344f43bf04927fb2045f46f3d3f611d36 +Subproject commit b5b868044fedc83959626fc433aa1a74e2c60200 diff --git a/externals/toml11 b/externals/toml11 index 12c0f379f..9481c477f 160000 --- a/externals/toml11 +++ b/externals/toml11 @@ -1 +1 @@ -Subproject commit 12c0f379f2e865b4ce984758d5ae004f9de07d69 +Subproject commit 9481c477f12f35107d22aa932267ca2f119f311b diff --git a/externals/xxhash b/externals/xxhash index a57f6cce2..ee65ff988 160000 --- a/externals/xxhash +++ b/externals/xxhash @@ -1 +1 @@ -Subproject commit a57f6cce2698049863af8c25787084ae0489d849 +Subproject commit ee65ff988bab34a184c700e2fbe1e1c5bc27485d From 250b2e4969eb2c86abdacf19ec7e01d7bd425938 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 8 Aug 2024 13:05:11 +0300 Subject: [PATCH 067/109] small playgo adjustments --- src/core/libraries/playgo/playgo.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/playgo/playgo.cpp b/src/core/libraries/playgo/playgo.cpp index a3af8b4c9..af98253f4 100644 --- a/src/core/libraries/playgo/playgo.cpp +++ b/src/core/libraries/playgo/playgo.cpp @@ -58,6 +58,7 @@ s32 PS4_SYSV_ABI scePlayGoGetLocus(OrbisPlayGoHandle handle, const OrbisPlayGoCh if (chunkIds[i] <= playgo->GetPlaygoHeader().mchunk_count) { outLoci[i] = OrbisPlayGoLocusValue::ORBIS_PLAYGO_LOCUS_LOCAL_FAST; } else { + outLoci[i] = ORBIS_PLAYGO_LOCUS_NOT_DOWNLOADED; return ORBIS_PLAYGO_ERROR_BAD_CHUNK_ID; } } @@ -70,12 +71,13 @@ s32 PS4_SYSV_ABI scePlayGoGetProgress(OrbisPlayGoHandle handle, const OrbisPlayG handle, *chunkIds, numberOfEntries); outProgress->progressSize = 0x10000; // todo? outProgress->totalSize = 0x10000; - return 0; + return ORBIS_OK; } s32 PS4_SYSV_ABI scePlayGoGetToDoList(OrbisPlayGoHandle handle, OrbisPlayGoToDo* outTodoList, u32 numberOfEntries, u32* outEntries) { - LOG_ERROR(Lib_PlayGo, "(STUBBED)called"); + LOG_ERROR(Lib_PlayGo, "(STUBBED)called handle = {} numberOfEntries = {}", handle, + numberOfEntries); if (handle != 1) return ORBIS_PLAYGO_ERROR_BAD_HANDLE; if (outTodoList == nullptr) From 425e5491a85ed1f4fad426a5990428008738a32c Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 9 Aug 2024 11:58:42 +0300 Subject: [PATCH 068/109] added setting for change language (reference to https://github.com/shadps4-emu/shadPS4/wiki/PS4-Modules#supported-languages for values) --- src/common/config.cpp | 13 +++++++++++++ src/common/config.h | 3 +++ src/core/libraries/pad/pad.cpp | 6 ------ src/core/libraries/system/systemservice.cpp | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index c2ea1fe37..a5a0be719 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -43,6 +43,8 @@ u32 m_window_size_H = 720; std::vector m_pkg_viewer; std::vector m_elf_viewer; std::vector m_recent_files; +// Settings +u32 m_language = 1; // english bool isLleLibc() { return isLibc; @@ -207,6 +209,9 @@ std::vector getRecentFiles() { return m_recent_files; } +u32 GetLanguage() { + return m_language; +} void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -285,6 +290,12 @@ void load(const std::filesystem::path& path) { m_recent_files = toml::find_or>(gui, "recentFiles", {}); m_table_mode = toml::find_or(gui, "gameTableMode", 0); } + + if (data.contains("Settings")) { + const toml::value& settings = data.at("Settings"); + + m_language = toml::find_or(settings, "language", 1); + } } void save(const std::filesystem::path& path) { toml::value data; @@ -339,6 +350,8 @@ void save(const std::filesystem::path& path) { data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; + data["Settings"]["language"] = m_language; + std::ofstream file(path, std::ios::out); file << data; file.close(); diff --git a/src/common/config.h b/src/common/config.h index 637ac7468..53c88ec9e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -64,4 +64,7 @@ std::vector getPkgViewer(); std::vector getElfViewer(); std::vector getRecentFiles(); +// settings +u32 GetLanguage(); + }; // namespace Config diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 8405a5390..d39935503 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -251,9 +251,6 @@ int PS4_SYSV_ABI scePadOutputReport() { } int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - if (handle != 1) { - UNREACHABLE_MSG("unknown handle"); - } int connected_count = 0; bool connected = false; Input::State states[64]; @@ -320,9 +317,6 @@ int PS4_SYSV_ABI scePadReadHistory() { int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { auto* controller = Common::Singleton::Instance(); - if (handle != 1) { - UNREACHABLE_MSG("unknown handle"); - } int connectedCount = 0; bool isConnected = false; Input::State state; diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index b704ccd93..d99ec7c7c 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1898,7 +1898,7 @@ s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(int param_id, int* value) { } switch (param_id) { case ORBIS_SYSTEM_SERVICE_PARAM_ID_LANG: - *value = ORBIS_SYSTEM_PARAM_LANG_ENGLISH_US; + *value = Config::GetLanguage(); break; case ORBIS_SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT: *value = ORBIS_SYSTEM_PARAM_DATE_FORMAT_DDMMYYYY; From d81dbc5b5bf387580f32285584366cdca209e11f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 9 Aug 2024 12:24:42 +0300 Subject: [PATCH 069/109] console language is better name --- src/common/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index a5a0be719..f676ab949 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -294,7 +294,7 @@ void load(const std::filesystem::path& path) { if (data.contains("Settings")) { const toml::value& settings = data.at("Settings"); - m_language = toml::find_or(settings, "language", 1); + m_language = toml::find_or(settings, "consoleLanguage", 1); } } void save(const std::filesystem::path& path) { @@ -350,7 +350,7 @@ void save(const std::filesystem::path& path) { data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; - data["Settings"]["language"] = m_language; + data["Settings"]["consoleLanguage"] = m_language; std::ofstream file(path, std::ios::out); file << data; From 61a6f633fd10cb9139b204ab1125c0e0e313bdaa Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 9 Aug 2024 12:56:03 +0300 Subject: [PATCH 070/109] mount temp dir --- src/emulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emulator.cpp b/src/emulator.cpp index d1abf5d6b..034b8706b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -128,6 +128,7 @@ void Emulator::Run(const std::filesystem::path& file) { std::filesystem::create_directory(mount_temp_dir); } mnt->Mount(mount_temp_dir, "/temp0"); // called in app_content ==> stat/mkdir + mnt->Mount(mount_temp_dir, "/temp"); const auto& mount_download_dir = Common::FS::GetUserPath(Common::FS::PathType::DownloadDir) / id; From 54b20e2938a4c4041e7c7e177da0e36316c3bedb Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 9 Aug 2024 13:04:00 +0300 Subject: [PATCH 071/109] update submodules for some neccesary sdl addons --- externals/fmt | 2 +- externals/glslang | 2 +- externals/sdl3 | 2 +- externals/toml11 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/externals/fmt b/externals/fmt index 9f0c0c468..c98518351 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 9f0c0c468be45357d85efc1788b7561fafbdebb1 +Subproject commit c98518351efd5a46f5d448e947e0b7242d197d07 diff --git a/externals/glslang b/externals/glslang index 5398d55e3..7c4d91e78 160000 --- a/externals/glslang +++ b/externals/glslang @@ -1 +1 @@ -Subproject commit 5398d55e33dff7d26fecdd2c35808add986c558c +Subproject commit 7c4d91e7819a1d27213aa3499953d54ae1a00e8f diff --git a/externals/sdl3 b/externals/sdl3 index b5b868044..4cc3410dc 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit b5b868044fedc83959626fc433aa1a74e2c60200 +Subproject commit 4cc3410dce50cefce98d3cf3cf1bc8eca83b862a diff --git a/externals/toml11 b/externals/toml11 index 9481c477f..fcb1d3d7e 160000 --- a/externals/toml11 +++ b/externals/toml11 @@ -1 +1 @@ -Subproject commit 9481c477f12f35107d22aa932267ca2f119f311b +Subproject commit fcb1d3d7e5885edfadbbe9572991dc4b3248af58 From 96fb00d41192e3c0a86eabf7d2b5d5a6e5fa2cd5 Mon Sep 17 00:00:00 2001 From: Dzmitry Dubrova Date: Fri, 9 Aug 2024 17:09:51 +0300 Subject: [PATCH 072/109] gui: Implement settings dialog --- CMakeLists.txt | 7 +- src/common/config.cpp | 78 ++++ src/common/config.h | 21 +- src/qt_gui/main_window.cpp | 6 + src/qt_gui/settings_dialog.cpp | 116 ++++++ src/qt_gui/settings_dialog.h | 28 ++ src/qt_gui/settings_dialog.ui | 646 +++++++++++++++++++++++++++++++++ 7 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 src/qt_gui/settings_dialog.cpp create mode 100644 src/qt_gui/settings_dialog.h create mode 100644 src/qt_gui/settings_dialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 4df3db2bf..b92dd9328 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,8 @@ if(ENABLE_QT_GUI) find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent) qt_standard_project_setup() set(CMAKE_AUTORCC ON) + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTOUIC ON) endif() set(AUDIO_CORE src/audio_core/sdl_audio.cpp @@ -546,10 +548,13 @@ set(QT_GUI src/qt_gui/elf_viewer.h src/qt_gui/main_window_themes.cpp src/qt_gui/main_window_themes.h + src/qt_gui/settings_dialog.cpp + src/qt_gui/settings_dialog.h + src/qt_gui/settings_dialog.ui src/qt_gui/main.cpp ${EMULATOR} ${RESOURCE_FILES} - ) +) endif() if (ENABLE_QT_GUI) diff --git a/src/common/config.cpp b/src/common/config.cpp index f676ab949..c105650ba 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -114,6 +114,66 @@ bool vkValidationSyncEnabled() { return vkValidationSync; } +void setScreenWidth(u32 width) { + screenWidth = width; +} + +void setScreenHeight(u32 height) { + screenHeight = height; +} + +void setDebugDump(bool enable) { + isDebugDump = enable; +} + +void setShowSplash(bool enable) { + isShowSplash = enable; +} + +void setNullGpu(bool enable) { + isNullGpu = enable; +} + +void setDumpShaders(bool enable) { + shouldDumpShaders = enable; +} + +void setDumpPM4(bool enable) { + shouldDumpPM4 = enable; +} + +void setVkValidation(bool enable) { + vkValidation = enable; +} + +void setVkSyncValidation(bool enable) { + vkValidationSync = enable; +} + +void setRdocEnabled(bool enable) { + rdocEnable = enable; +} + +void setVblankDiv(u32 value) { + vblankDivider = value; +} + +void setFullscreenMode(bool enable) { + isFullscreen = enable; +} + +void setNeoMode(bool enable) { + isNeo = enable; +} + +void setLogType(std::string type) { + logType = type; +} + +void setLogFilter(std::string type) { + logFilter = type; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -356,4 +416,22 @@ void save(const std::filesystem::path& path) { file << data; file.close(); } + +void setDefaultValues() { + isNeo = false; + isFullscreen = false; + screenWidth = 1280; + screenHeight = 720; + logFilter = ""; + logType = "async"; + isDebugDump = false; + isShowSplash = false; + isNullGpu = false; + shouldDumpShaders = false; + shouldDumpPM4 = false; + vblankDivider = 1; + vkValidation = false; + rdocEnable = false; +} + } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 53c88ec9e..6174b1e19 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -29,6 +29,24 @@ bool dumpPM4(); bool isRdocEnabled(); u32 vblankDiv(); +void setDebugDump(bool enable); +void setShowSplash(bool enable); +void setNullGpu(bool enable); +void setDumpShaders(bool enable); +void setDumpPM4(bool enable); +void setVblankDiv(u32 value); +void setScreenWidth(u32 width); +void setScreenHeight(u32 height); +void setFullscreenMode(bool enable); +void setNeoMode(bool enable); + +void setLogType(std::string type); +void setLogFilter(std::string type); + +void setVkValidation(bool enable); +void setVkSyncValidation(bool enable); +void setRdocEnabled(bool enable); + bool vkValidationEnabled(); bool vkValidationSyncEnabled(); @@ -64,7 +82,8 @@ std::vector getPkgViewer(); std::vector getElfViewer(); std::vector getRecentFiles(); +void setDefaultValues(); + // settings u32 GetLanguage(); - }; // namespace Config diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 646433ee7..55bd56402 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -15,6 +15,7 @@ #include "core/loader.h" #include "game_install_dialog.h" #include "main_window.h" +#include "settings_dialog.h" MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -185,6 +186,11 @@ void MainWindow::CreateConnects() { connect(m_game_list_frame.get(), &QTableWidget::cellDoubleClicked, this, &MainWindow::StartGame); + connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { + auto settingsDialog = new SettingsDialog(this); + settingsDialog->exec(); + }); + connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp new file mode 100644 index 000000000..88c91ef65 --- /dev/null +++ b/src/qt_gui/settings_dialog.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "settings_dialog.h" +#include "ui_settings_dialog.h" + +SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::SettingsDialog) { + ui->setupUi(this); + ui->tabWidgetSettings->setUsesScrollButtons(false); + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + + ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); + + LoadValuesFromConfig(); + + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, + [this, config_dir](QAbstractButton* button) { + if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { + Config::save(config_dir / "config.toml"); + QWidget::close(); + } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { + Config::save(config_dir / "config.toml"); + } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { + Config::setDefaultValues(); + LoadValuesFromConfig(); + } + }); + + connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, [this]() { + ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); + }); + + // GPU TAB + { + // TODO: Implement graphics device changing + + connect(ui->widthSpinBox, &QSpinBox::valueChanged, this, + [](int val) { Config::setScreenWidth(val); }); + + connect(ui->heightSpinBox, &QSpinBox::valueChanged, this, + [](int val) { Config::setScreenHeight(val); }); + + connect(ui->vblankSpinBox, &QSpinBox::valueChanged, this, + [](int val) { Config::setVblankDiv(val); }); + + connect(ui->dumpShadersCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setDumpShaders(val); }); + + connect(ui->nullGpuCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setNullGpu(val); }); + + connect(ui->dumpPM4CheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setDumpPM4(val); }); + } + + // GENERAL TAB + { + connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setFullscreenMode(val); }); + + connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setShowSplash(val); }); + + connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setNeoMode(val); }); + + connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this, + [](const QString& text) { Config::setLogType(text.toStdString()); }); + + connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this, + [](const QString& text) { Config::setLogFilter(text.toStdString()); }); + } + + // DEBUG TAB + { + connect(ui->debugDump, &QCheckBox::stateChanged, this, + [](int val) { Config::setDebugDump(val); }); + + connect(ui->vkValidationCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setVkValidation(val); }); + + connect(ui->vkSyncValidationCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setVkSyncValidation(val); }); + + connect(ui->rdocCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setRdocEnabled(val); }); + } +} + +void SettingsDialog::LoadValuesFromConfig() { + ui->widthSpinBox->setValue(Config::getScreenWidth()); + ui->heightSpinBox->setValue(Config::getScreenHeight()); + ui->vblankSpinBox->setValue(Config::vblankDiv()); + ui->dumpShadersCheckBox->setChecked(Config::dumpShaders()); + ui->nullGpuCheckBox->setChecked(Config::nullGpu()); + ui->dumpPM4CheckBox->setChecked(Config::dumpPM4()); + + ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode()); + ui->showSplashCheckBox->setChecked(Config::showSplash()); + ui->ps4proCheckBox->setChecked(Config::isNeoMode()); + ui->logTypeComboBox->setCurrentText(QString::fromStdString(Config::getLogType())); + ui->logFilterLineEdit->setText(QString::fromStdString(Config::getLogFilter())); + + ui->debugDump->setChecked(Config::debugDump()); + ui->vkValidationCheckBox->setChecked(Config::vkValidationEnabled()); + ui->vkSyncValidationCheckBox->setChecked(Config::vkValidationSyncEnabled()); + ui->rdocCheckBox->setChecked(Config::isRdocEnabled()); +} + +int SettingsDialog::exec() { + return QDialog::exec(); +} + +SettingsDialog::~SettingsDialog() {} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h new file mode 100644 index 000000000..2bffa795c --- /dev/null +++ b/src/qt_gui/settings_dialog.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/config.h" +#include "common/path_util.h" + +namespace Ui { +class SettingsDialog; +} + +class SettingsDialog : public QDialog { + Q_OBJECT +public: + explicit SettingsDialog(QWidget* parent = nullptr); + ~SettingsDialog(); + + int exec() override; + +private: + void LoadValuesFromConfig(); + + std::unique_ptr ui; +}; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui new file mode 100644 index 000000000..507980eb4 --- /dev/null +++ b/src/qt_gui/settings_dialog.ui @@ -0,0 +1,646 @@ + + + SettingsDialog + + + Qt::WindowModality::WindowModal + + + + 0 + 0 + 1024 + 768 + + + + + 0 + 0 + + + + Settings + + + + :/images/shadps4.ico:/images/shadps4.ico + + + + + + QFrame::Shape::NoFrame + + + true + + + + true + + + + 0 + 0 + 1006 + 720 + + + + + 0 + 0 + + + + 1 + + + + GPU + + + + + + + + + + Graphics Device + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + 6 + + + 0 + + + + + + + Width + + + + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 0 + + + 9999 + + + 1280 + + + + + + + + + + Height + + + + + + true + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 0 + + + 9999 + + + 720 + + + + + + + + + + + + + + 6 + + + 0 + + + + + + + Vblank Divider + + + + + + true + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 1 + + + 9999 + + + 1 + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + 12 + + + 12 + + + + + Additional Settings + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + Enable Shaders Dumping + + + + + + + Enable NULL GPU + + + + + + + Enable PM4 Dumping + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + General + + + + + + + + + + Emulator Settings + + + + + + Enable Fullscreen + + + + + + + Show Splash + + + + + + + Is PS4 Pro + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + + + + Logger Settings + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Log Type + + + + + + + async + + + + + sync + + + + + + + + + + + + + + 6 + + + 0 + + + + + + + Log Filter + + + + + + + + + + + + + + + + + + + + + + + Additional Settings + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + Debug + + + + + + + + true + + + General + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + + + + Enable Debug Dumping + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + Enable Vulkan Validation Layers + + + + + + + Enable Vulkan Synchronization Validation + + + + + + + Enable RenderDoc Debugging + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save + + + + + + + + From 3163cd135bcbedc29bb284ce9a624f6097bf2d6e Mon Sep 17 00:00:00 2001 From: Dzmitry Dubrova Date: Fri, 9 Aug 2024 18:19:35 +0300 Subject: [PATCH 073/109] gui: Add console language to settings --- src/common/config.cpp | 5 + src/common/config.h | 1 + src/qt_gui/settings_dialog.cpp | 8 + src/qt_gui/settings_dialog.ui | 288 +++++++++++++++++++++++++++++++-- 4 files changed, 289 insertions(+), 13 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index c105650ba..ebdd9c320 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -162,6 +162,10 @@ void setFullscreenMode(bool enable) { isFullscreen = enable; } +void setLanguage(u32 language) { + m_language = language; +} + void setNeoMode(bool enable) { isNeo = enable; } @@ -432,6 +436,7 @@ void setDefaultValues() { vblankDivider = 1; vkValidation = false; rdocEnable = false; + m_language = 1; } } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 6174b1e19..ad0aad221 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -38,6 +38,7 @@ void setVblankDiv(u32 value); void setScreenWidth(u32 width); void setScreenHeight(u32 height); void setFullscreenMode(bool enable); +void setLanguage(u32 language); void setNeoMode(bool enable); void setLogType(std::string type); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 88c91ef65..722abe7e0 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -32,6 +32,12 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Se ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); }); + // EMULATOR TAB + { + connect(ui->consoleLanguageComboBox, &QComboBox::currentIndexChanged, this, + [](int index) { Config::setLanguage(index); }); + } + // GPU TAB { // TODO: Implement graphics device changing @@ -90,6 +96,8 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Se } void SettingsDialog::LoadValuesFromConfig() { + ui->consoleLanguageComboBox->setCurrentIndex(Config::GetLanguage()); + ui->widthSpinBox->setValue(Config::getScreenWidth()); ui->heightSpinBox->setValue(Config::getScreenHeight()); ui->vblankSpinBox->setValue(Config::vblankDiv()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 507980eb4..4893bd613 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1,4 +1,7 @@ + + SettingsDialog @@ -54,31 +57,182 @@ - 1 + 0 - + - GPU + Emulator - + - + - + - + - Graphics Device + Console Language - + - + + + + Japanese + + + + + English (United States) + + + + + French (France) + + + + + Spanish (Spain) + + + + + German + + + + + Italian + + + + + Dutch + + + + + Portuguese (Portugal) + + + + + Russian + + + + + Korean + + + + + Traditional Chinese + + + + + Simplified Chinese + + + + + Finnish + + + + + Swedish + + + + + Danish + + + + + Norwegian + + + + + Polish + + + + + Portuguese (Brazil) + + + + + English (United Kingdom) + + + + + Turkish + + + + + Spanish (Latin America) + + + + + Arabic + + + + + French (Canada) + + + + + Czech + + + + + Hungarian + + + + + Greek + + + + + Romanian + + + + + Thai + + + + + Vietnamese + + + + + Indonesian + + + - + 0 @@ -96,7 +250,7 @@ - + 0 @@ -113,6 +267,114 @@ + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + + 12 + + + 12 + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + GPU + + + + + + + + + + Graphics Device + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + @@ -294,7 +556,7 @@ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter - + From 834e3a500e62d58f1fe94fb509dff0eaefd06b27 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 11 Aug 2024 13:16:50 +0300 Subject: [PATCH 074/109] added a fix for audio (seems that some games calls sceAudioOutInit twice) Thanks Roamic for tracing this! --- src/core/libraries/audio/audioout.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index cc7ce342a..eac3845f3 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -235,6 +235,9 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() { } int PS4_SYSV_ABI sceAudioOutInit() { + if (audio != nullptr) { + return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; + } audio = std::make_unique(); LOG_INFO(Lib_AudioOut, "called"); return ORBIS_OK; From 3e2d4d6b793fd14e918673fbacd40914a0714ea0 Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:29:57 +0200 Subject: [PATCH 075/109] Gnmdriver: More functions (#410) * libraries: gnmdriver: added `sceGnmGetGpuCoreClockFrequency` * libraries: gnmdriver: `sceGnmSetVgtControl` added * amdgpu: gpuclock64 in write eop packet --- src/core/libraries/gnmdriver/gnmdriver.cpp | 20 +++++++++++++++----- src/core/libraries/gnmdriver/gnmdriver.h | 5 +++-- src/video_core/amdgpu/pm4_cmds.h | 18 +++++++++++------- src/video_core/amdgpu/pm4_opcodes.h | 1 + 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 650252f95..c2ee6d592 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -956,9 +956,9 @@ int PS4_SYSV_ABI sceGnmGetGpuBlockStatus() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency() { - LOG_DEBUG(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency() { + LOG_TRACE(Lib_GnmDriver, "called"); + return Config::isNeoMode() ? 911'000'000 : 800'000'000; } int PS4_SYSV_ABI sceGnmGetGpuInfoStatus() { @@ -1706,8 +1706,18 @@ int PS4_SYSV_ABI sceGnmSetupMipStatsReport() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmSetVgtControl() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceGnmSetVgtControl(u32* cmdbuf, u32 size, u32 prim_group_sz_minus_one, + u32 partial_vs_wave_mode, u32 wd_switch_only_on_eop_mode) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (!cmdbuf || size != 3 || (prim_group_sz_minus_one >= 0x100) || + ((wd_switch_only_on_eop_mode | partial_vs_wave_mode) >= 2)) { + return -1; + } + + const u32 reg_value = + ((partial_vs_wave_mode & 1) << 0x10) | (prim_group_sz_minus_one & 0xffffu); + PM4CmdSetData::SetContextReg(cmdbuf, 0x2aau, reg_value); // IA_MULTI_VGT_PARAM return ORBIS_OK; } diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 8100b1164..84872297e 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -85,7 +85,7 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp(); int PS4_SYSV_ABI sceGnmGetEqEventType(); int PS4_SYSV_ABI sceGnmGetEqTimeStamp(); int PS4_SYSV_ABI sceGnmGetGpuBlockStatus(); -int PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency(); +u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency(); int PS4_SYSV_ABI sceGnmGetGpuInfoStatus(); int PS4_SYSV_ABI sceGnmGetLastWaitedAddress(); int PS4_SYSV_ABI sceGnmGetNumTcaUnits(); @@ -161,7 +161,8 @@ int PS4_SYSV_ABI sceGnmSetResourceUserData(); int PS4_SYSV_ABI sceGnmSetSpiEnableSqCounters(); int PS4_SYSV_ABI sceGnmSetSpiEnableSqCountersForUnitInstance(); int PS4_SYSV_ABI sceGnmSetupMipStatsReport(); -int PS4_SYSV_ABI sceGnmSetVgtControl(); +s32 PS4_SYSV_ABI sceGnmSetVgtControl(u32* cmdbuf, u32 size, u32 prim_group_sz_minus_one, + u32 partial_vs_wave_mode, u32 wd_switch_only_on_eop_mode); s32 PS4_SYSV_ABI sceGnmSetVsShader(u32* cmdbuf, u32 size, const u32* vs_regs, u32 shader_modifier); int PS4_SYSV_ABI sceGnmSetWaveLimitMultiplier(); int PS4_SYSV_ABI sceGnmSetWaveLimitMultipliers(); diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index e5f618cc2..5ab233fdc 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -282,6 +282,13 @@ enum class InterruptSelect : u32 { IrqUndocumented = 3, }; +static u64 GetGpuClock64() { + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + auto ticks = std::chrono::duration_cast(duration).count(); + return static_cast(ticks); +} + struct PM4CmdEventWriteEop { PM4Type3Header header; union { @@ -325,6 +332,10 @@ struct PM4CmdEventWriteEop { *Address() = DataQWord(); break; } + case DataSelect::GpuClock64: { + *Address() = GetGpuClock64(); + break; + } case DataSelect::PerfCounter: { *Address() = Common::FencedRDTSC(); break; @@ -652,13 +663,6 @@ struct PM4CmdReleaseMem { return data_lo | u64(data_hi) << 32; } - uint64_t GetGpuClock64() const { - auto now = std::chrono::high_resolution_clock::now(); - auto duration = now.time_since_epoch(); - auto ticks = std::chrono::duration_cast(duration).count(); - return static_cast(ticks); - } - void SignalFence(Platform::InterruptId irq_id) const { switch (data_sel.Value()) { case DataSelect::Data32Low: { diff --git a/src/video_core/amdgpu/pm4_opcodes.h b/src/video_core/amdgpu/pm4_opcodes.h index 8922c4ea3..fba0cbb9f 100644 --- a/src/video_core/amdgpu/pm4_opcodes.h +++ b/src/video_core/amdgpu/pm4_opcodes.h @@ -41,6 +41,7 @@ enum class PM4ItOpcode : u32 { CondIndirectBuffer = 0x3F, CopyData = 0x40, CommandProcessorDma = 0x41, + PfpSyncMe = 0x42, SurfaceSync = 0x43, CondWrite = 0x45, EventWrite = 0x46, From ace39957efa1f8b65902b7b197a9e8983dc99334 Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:46:45 +0200 Subject: [PATCH 076/109] Video Core: debug tools (#412) * video_core: better use of rdoc markers * renderer_vulkan: added gpu assisted validation * renderer_vulkan: make nv_checkpoints operational * video_core: unified Vulkan objects names --- src/common/config.cpp | 14 ++++ src/common/config.h | 2 + src/video_core/amdgpu/liverpool.cpp | 36 +++++++--- src/video_core/buffer_cache/buffer.cpp | 12 ++-- .../renderer_vulkan/vk_instance.cpp | 11 ++++ src/video_core/renderer_vulkan/vk_instance.h | 5 ++ .../renderer_vulkan/vk_platform.cpp | 65 ++++++++++++++++--- .../renderer_vulkan/vk_rasterizer.cpp | 30 ++++++++- .../renderer_vulkan/vk_rasterizer.h | 4 +- .../renderer_vulkan/vk_scheduler.cpp | 7 ++ src/video_core/texture_cache/image.cpp | 4 ++ 11 files changed, 161 insertions(+), 29 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index ebdd9c320..3cf9af150 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -25,7 +25,9 @@ static bool shouldDumpPM4 = false; static u32 vblankDivider = 1; static bool vkValidation = false; static bool vkValidationSync = false; +static bool vkValidationGpu = false; static bool rdocEnable = false; +static bool rdocMarkersEnable = false; // Gui std::string settings_install_dir = ""; u32 main_window_geometry_x = 400; @@ -102,6 +104,10 @@ bool isRdocEnabled() { return rdocEnable; } +bool isMarkersEnabled() { + return rdocMarkersEnable; +} + u32 vblankDiv() { return vblankDivider; } @@ -114,6 +120,10 @@ bool vkValidationSyncEnabled() { return vkValidationSync; } +bool vkValidationGpuEnabled() { + return vkValidationGpu; +} + void setScreenWidth(u32 width) { screenWidth = width; } @@ -319,7 +329,9 @@ void load(const std::filesystem::path& path) { gpuId = toml::find_or(vk, "gpuId", -1); vkValidation = toml::find_or(vk, "validation", false); vkValidationSync = toml::find_or(vk, "validation_sync", false); + vkValidationGpu = toml::find_or(vk, "validation_gpu", true); rdocEnable = toml::find_or(vk, "rdocEnable", false); + rdocMarkersEnable = toml::find_or(vk, "rdocMarkersEnable", false); } if (data.contains("Debug")) { @@ -394,7 +406,9 @@ void save(const std::filesystem::path& path) { data["Vulkan"]["gpuId"] = gpuId; data["Vulkan"]["validation"] = vkValidation; data["Vulkan"]["validation_sync"] = vkValidationSync; + data["Vulkan"]["validation_gpu"] = vkValidationGpu; data["Vulkan"]["rdocEnable"] = rdocEnable; + data["Vulkan"]["rdocMarkersEnable"] = rdocMarkersEnable; data["Debug"]["DebugDump"] = isDebugDump; data["LLE"]["libc"] = isLibc; data["GUI"]["theme"] = mw_themes; diff --git a/src/common/config.h b/src/common/config.h index ad0aad221..37ace79c3 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -27,6 +27,7 @@ bool nullGpu(); bool dumpShaders(); bool dumpPM4(); bool isRdocEnabled(); +bool isMarkersEnabled(); u32 vblankDiv(); void setDebugDump(bool enable); @@ -50,6 +51,7 @@ void setRdocEnabled(bool enable); bool vkValidationEnabled(); bool vkValidationSyncEnabled(); +bool vkValidationGpuEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index af1963eec..bd32b5b96 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -180,6 +180,17 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanSignal(Platform::InterruptId::GfxFlip); break; } + case PM4CmdNop::PayloadType::DebugMarkerPush: { + const auto marker_sz = nop->header.count.Value() * 2; + const std::string_view label{reinterpret_cast(&nop->data_block[1]), + marker_sz}; + rasterizer->ScopeMarkerBegin(label); + break; + } + case PM4CmdNop::PayloadType::DebugMarkerPop: { + rasterizer->ScopeMarkerEnd(); + break; + } default: break; } @@ -295,8 +306,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanindex_count; regs.draw_initiator = draw_index->draw_initiator; if (rasterizer) { - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DrawIndex2", reinterpret_cast(dcb.data()))); + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); + rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(true); rasterizer->ScopeMarkerEnd(); } @@ -308,8 +320,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanindex_count; regs.draw_initiator = draw_index_off->draw_initiator; if (rasterizer) { - rasterizer->ScopeMarkerBegin(fmt::format( - "dcb:{}:DrawIndexOffset2", reinterpret_cast(dcb.data()))); + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexOffset2", cmd_address)); + rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(true, draw_index_off->index_offset); rasterizer->ScopeMarkerEnd(); } @@ -320,8 +333,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanindex_count; regs.draw_initiator = draw_index->draw_initiator; if (rasterizer) { - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DrawIndexAuto", reinterpret_cast(dcb.data()))); + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); + rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(false); rasterizer->ScopeMarkerEnd(); } @@ -334,8 +348,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:Dispatch", reinterpret_cast(dcb.data()))); + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); + rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -486,8 +501,9 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { - rasterizer->ScopeMarkerBegin(fmt::format( - "acb[{}]:{}:Dispatch", vqid, reinterpret_cast(acb.data()))); + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); + rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index e9498b352..d112864d5 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -106,10 +106,8 @@ Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_ VmaAllocationInfo alloc_info{}; buffer.Create(buffer_ci, usage, &alloc_info); - if (instance->HasDebuggingToolAttached()) { - const auto device = instance->GetDevice(); - Vulkan::SetObjectName(device, Handle(), "Buffer {:#x} {} KiB", cpu_addr, size_bytes / 1024); - } + const auto device = instance->GetDevice(); + Vulkan::SetObjectName(device, Handle(), "Buffer {:#x}:{:#x}", cpu_addr, size_bytes); // Map it if it is host visible. VkMemoryPropertyFlags property_flags{}; @@ -152,10 +150,8 @@ StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); const auto device = instance.GetDevice(); - if (instance.HasDebuggingToolAttached()) { - Vulkan::SetObjectName(device, Handle(), "StreamBuffer({}): {} KiB", BufferTypeName(usage), - size_bytes / 1024); - } + Vulkan::SetObjectName(device, Handle(), "StreamBuffer({}):{:#x}", BufferTypeName(usage), + size_bytes); } std::pair StreamBuffer::Map(u64 size, u64 alignment) { diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 2d396daf0..eedba4c82 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -8,6 +8,7 @@ #include #include "common/assert.h" +#include "common/config.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -213,6 +214,13 @@ bool Instance::CreateDevice() { add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); + const bool has_sync2 = add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + + if (has_sync2) { + has_nv_checkpoints = Config::isMarkersEnabled() + ? add_extension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME) + : false; + } const auto family_properties = physical_device.getQueueFamilyProperties(); if (family_properties.empty()) { @@ -308,6 +316,9 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceRobustness2FeaturesEXT{ .nullDescriptor = true, }, + vk::PhysicalDeviceSynchronization2Features{ + .synchronization2 = has_sync2, + }, }; if (!color_write_en) { diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index a8c0dcf45..2f2397d64 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -88,6 +88,10 @@ public: return profiler_context; } + bool HasNvCheckpoints() const { + return has_nv_checkpoints; + } + /// Returns true when a known debugging tool is attached. bool HasDebuggingToolAttached() const { return has_renderdoc || has_nsight_graphics; @@ -259,6 +263,7 @@ private: bool debug_utils_supported{}; bool has_nsight_graphics{}; bool has_renderdoc{}; + bool has_nv_checkpoints{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 0915514b8..33113c58b 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -221,12 +221,61 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT vk::Bool32 enable_sync = enable_validation && Config::vkValidationSyncEnabled() ? vk::True : vk::False; - vk::LayerSettingEXT layer_set = { - .pLayerName = VALIDATION_LAYER_NAME, - .pSettingName = "validate_sync", - .type = vk::LayerSettingTypeEXT::eBool32, - .valueCount = 1, - .pValues = &enable_sync, + vk::Bool32 enable_gpuav = + enable_validation && Config::vkValidationSyncEnabled() ? vk::True : vk::False; + const char* gpuav_mode = enable_validation && Config::vkValidationGpuEnabled() + ? "GPU_BASED_GPU_ASSISTED" + : "GPU_BASED_NONE"; + const std::array layer_setings = { + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "validate_sync", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_sync, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "sync_queue_submit", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_sync, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "validate_gpu_based", + .type = vk::LayerSettingTypeEXT::eString, + .valueCount = 1, + .pValues = &gpuav_mode, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "gpuav_reserve_binding_slot", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_gpuav, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "gpuav_descriptor_checks", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_gpuav, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "gpuav_validate_indirect_buffer", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_gpuav, + }, + vk::LayerSettingEXT{ + .pLayerName = VALIDATION_LAYER_NAME, + .pSettingName = "gpuav_buffer_copies", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_gpuav, + }, }; vk::StructureChain instance_ci_chain = { @@ -238,8 +287,8 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT .ppEnabledExtensionNames = extensions.data(), }, vk::LayerSettingsCreateInfoEXT{ - .settingCount = 1, - .pSettings = &layer_set, + .settingCount = layer_setings.size(), + .pSettings = layer_setings.data(), }, }; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 51de09f7a..b6e43a1ad 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -230,16 +230,42 @@ void Rasterizer::UpdateDepthStencilState() { cmdbuf.setDepthBoundsTestEnable(depth.depth_bounds_enable); } -void Rasterizer::ScopeMarkerBegin(const std::string& str) { +void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { + if (!Config::isMarkersEnabled()) { + return; + } + const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ - .pLabelName = str.c_str(), + .pLabelName = str.data(), }); } void Rasterizer::ScopeMarkerEnd() { + if (!Config::isMarkersEnabled()) { + return; + } + const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.endDebugUtilsLabelEXT(); } +void Rasterizer::ScopedMarkerInsert(const std::string_view& str) { + if (!Config::isMarkersEnabled()) { + return; + } + + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = str.data(), + }); +} + +void Rasterizer::Breadcrumb(u64 id) { + if (!instance.HasNvCheckpoints()) { + return; + } + scheduler.CommandBuffer().setCheckpointNV(id); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 685ba6e00..a151ebc27 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -35,8 +35,10 @@ public: void DispatchDirect(); - void ScopeMarkerBegin(const std::string& str); + void ScopeMarkerBegin(const std::string_view& str); void ScopeMarkerEnd(); + void ScopedMarkerInsert(const std::string_view& str); + void Breadcrumb(u64 id); void InvalidateMemory(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index fb64285f1..c74f3d07d 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -158,6 +158,13 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { try { instance.GetGraphicsQueue().submit(submit_info, info.fence); } catch (vk::DeviceLostError& err) { + if (instance.HasNvCheckpoints()) { + const auto checkpoint_data = instance.GetGraphicsQueue().getCheckpointData2NV(); + for (const auto& cp : checkpoint_data) { + LOG_CRITICAL(Render_Vulkan, "{}: {:#x}", vk::to_string(cp.stage), + reinterpret_cast(cp.pCheckpointMarker)); + } + } UNREACHABLE_MSG("Device lost during submit: {}", err.what()); } diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index f7aef8471..f1148760e 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/config.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -154,6 +155,9 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, }; image.Create(image_ci); + + Vulkan::SetObjectName(instance->GetDevice(), (vk::Image)image, "Image {:#x}:{:#x}", + info.guest_address, info.guest_size_bytes); } void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, From 3d0fdf11f0781ac8b84f5e501d8837137a45e387 Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:23:01 +0200 Subject: [PATCH 077/109] Build stabilization (#413) * shader_recompiler: fix for float convert and debug asserts * libraries: kernel: correct return code on invalid semaphore * amdgpu: additional case for cb extents retrieval heuristic * removed redundant check in assert * amdgpu: fix for linear tiling mode detection fin color buffers * texture_cache: fix for unexpected scheduler flushes by detiler * renderer_vulkan: missing depth barrier * texture_cache: missed slices in rt view; + detiler format --- .../libraries/kernel/threads/semaphore.cpp | 12 +++++++ .../spirv/emit_spirv_context_get_set.cpp | 9 +---- .../backend/spirv/emit_spirv_image.cpp | 4 +-- .../backend/spirv/emit_spirv_instructions.h | 2 +- .../ir/passes/resource_tracking_pass.cpp | 6 ++-- src/shader_recompiler/runtime_info.h | 2 +- src/video_core/amdgpu/liverpool.cpp | 2 +- src/video_core/amdgpu/liverpool.h | 2 +- src/video_core/buffer_cache/buffer.h | 4 +++ .../renderer_vulkan/vk_rasterizer.cpp | 3 +- .../renderer_vulkan/vk_scheduler.cpp | 36 ++++++++++++++++--- src/video_core/renderer_vulkan/vk_scheduler.h | 3 +- src/video_core/texture_cache/image_info.cpp | 4 ++- src/video_core/texture_cache/image_view.cpp | 2 ++ src/video_core/texture_cache/tile_manager.cpp | 3 +- 15 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index 370dba445..5441c641a 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -174,10 +174,16 @@ s32 PS4_SYSV_ABI sceKernelCreateSema(OrbisKernelSema* sem, const char* pName, u3 } s32 PS4_SYSV_ABI sceKernelWaitSema(OrbisKernelSema sem, s32 needCount, u32* pTimeout) { + if (!sem) { + return ORBIS_KERNEL_ERROR_ESRCH; + } return sem->Wait(true, needCount, pTimeout); } s32 PS4_SYSV_ABI sceKernelSignalSema(OrbisKernelSema sem, s32 signalCount) { + if (!sem) { + return ORBIS_KERNEL_ERROR_ESRCH; + } if (!sem->Signal(signalCount)) { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -185,10 +191,16 @@ s32 PS4_SYSV_ABI sceKernelSignalSema(OrbisKernelSema sem, s32 signalCount) { } s32 PS4_SYSV_ABI sceKernelPollSema(OrbisKernelSema sem, s32 needCount) { + if (!sem) { + return ORBIS_KERNEL_ERROR_ESRCH; + } return sem->Wait(false, needCount, nullptr); } int PS4_SYSV_ABI sceKernelCancelSema(OrbisKernelSema sem, s32 setCount, s32* pNumWaitThreads) { + if (!sem) { + return ORBIS_KERNEL_ERROR_ESRCH; + } return sem->Cancel(setCount, pNumWaitThreads); } 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 e85272e96..bd34ed3db 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 @@ -386,19 +386,12 @@ static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 com if (is_signed) { value = ctx.OpBitFieldSExtract(ctx.S32[1], value, comp_offset, ctx.ConstU32(bit_width)); - value = ctx.OpConvertSToF(ctx.F32[1], value); } else { value = ctx.OpBitFieldUExtract(ctx.U32[1], value, comp_offset, ctx.ConstU32(bit_width)); - value = ctx.OpConvertUToF(ctx.F32[1], value); - } - } else { - if (is_signed) { - value = ctx.OpConvertSToF(ctx.F32[1], value); - } else { - value = ctx.OpConvertUToF(ctx.F32[1], value); } } + value = ctx.OpBitcast(ctx.F32[1], value); return ConvertValue(ctx, value, num_format, bit_width); } break; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 030d39485..e2d2c1ae0 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -33,14 +33,14 @@ Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id c operands.operands); } -Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, +Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); return ctx.OpImageSampleExplicitLod(ctx.F32[4], sampled_image, coords, - spv::ImageOperandsMask::Lod, ctx.ConstF32(0.f)); + spv::ImageOperandsMask::Lod, lod); } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 51899eb4d..0b2020f18 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -359,7 +359,7 @@ Id EmitConvertU32U16(EmitContext& ctx, Id value); Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, Id offset); -Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, +Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id offset); Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, Id bias_lc, const IR::Value& offset); diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 97438f80a..6c96faa3d 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -376,9 +376,11 @@ s32 TryHandleInlineCbuf(IR::Inst& inst, Info& info, Descriptors& descriptors, return -1; } // We have found this pattern. Build the sharp. - std::array buffer; + std::array buffer; buffer[0] = info.pgm_base + p0->Arg(0).U32() + p0->Arg(1).U32(); - buffer[1] = handle->Arg(2).U32() | handle->Arg(3).U64() << 32; + buffer[1] = 0; + buffer[2] = handle->Arg(2).U32(); + buffer[3] = handle->Arg(3).U32(); cbuf = std::bit_cast(buffer); // Assign a binding to this sharp. return descriptors.Add(BufferResource{ diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 4ab71c3b7..b936e06aa 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -116,7 +116,7 @@ struct PushData { std::array buf_offsets; void AddOffset(u32 binding, u32 offset) { - ASSERT(offset < 64 && binding < 32); + ASSERT(offset < 256 && binding < buf_offsets.size()); buf_offsets[binding] = offset; } }; diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index bd32b5b96..517f9d53a 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -237,7 +237,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spantype3.count; - if (nop_offset == 0x0e || nop_offset == 0x0d) { + if (nop_offset == 0x0e || nop_offset == 0x0d || nop_offset == 0x0b) { ASSERT_MSG(payload[nop_offset] == 0xc0001000, "NOP hint is missing in CB setup sequence"); last_cb_extent[col_buf_id].raw = payload[nop_offset + 1]; diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 3ebd9a971..e28b5680b 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -766,7 +766,7 @@ struct Liverpool { } TilingMode GetTilingMode() const { - return attrib.tile_mode_index; + return info.linear_general ? TilingMode::Display_Linear : attrib.tile_mode_index; } bool IsTiled() const { diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index e0d9da08f..d373fbffb 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -146,6 +146,10 @@ public: return offset; } + u64 GetFreeSize() const { + return size_bytes - offset - mapped_size; + } + private: struct Watch { u64 tick{}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b6e43a1ad..542624a0e 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -152,7 +152,8 @@ void Rasterizer::BeginRendering() { .stencil = regs.stencil_clear}}, }; texture_cache.TouchMeta(htile_address, false); - state.num_depth_attachments++; + state.has_depth = true; + state.has_stencil = image.info.usage.stencil; } scheduler.BeginRendering(state); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index c74f3d07d..a6c2536ba 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -38,8 +38,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { .layerCount = 1, .colorAttachmentCount = render_state.num_color_attachments, .pColorAttachments = render_state.color_attachments.data(), - .pDepthAttachment = - render_state.num_depth_attachments ? &render_state.depth_attachment : nullptr, + .pDepthAttachment = render_state.has_depth ? &render_state.depth_attachment : nullptr, }; current_cmdbuf.beginRendering(rendering_info); @@ -50,6 +49,8 @@ void Scheduler::EndRendering() { return; } is_rendering = false; + current_cmdbuf.endRendering(); + boost::container::static_vector barriers; for (size_t i = 0; i < render_state.num_color_attachments; ++i) { barriers.push_back(vk::ImageMemoryBarrier{ @@ -70,10 +71,35 @@ void Scheduler::EndRendering() { }, }); } - current_cmdbuf.endRendering(); + if (render_state.has_depth) { + barriers.push_back(vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, + .oldLayout = render_state.depth_attachment.imageLayout, + .newLayout = render_state.depth_attachment.imageLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = render_state.depth_image, + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eDepth | + (render_state.has_stencil ? vk::ImageAspectFlagBits::eStencil + : vk::ImageAspectFlagBits::eNone), + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); + } + if (!barriers.empty()) { - current_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eFragmentShader, + const auto src_stages = + vk::PipelineStageFlagBits::eColorAttachmentOutput | + (render_state.has_depth ? vk::PipelineStageFlagBits::eLateFragmentTests | + vk::PipelineStageFlagBits::eEarlyFragmentTests + : vk::PipelineStageFlagBits::eNone); + current_cmdbuf.pipelineBarrier(src_stages, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlagBits::eByRegion, {}, {}, barriers); } } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index b82d558ca..1140bfbc2 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -20,7 +20,8 @@ struct RenderState { vk::RenderingAttachmentInfo depth_attachment{}; vk::Image depth_image{}; u32 num_color_attachments{}; - u32 num_depth_attachments{}; + bool has_depth{}; + bool has_stencil{}; u32 width = std::numeric_limits::max(); u32 height = std::numeric_limits::max(); diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 94917be0a..17b78a6d5 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -189,6 +189,8 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice resources.layers = num_slices; meta_info.htile_addr = buffer.z_info.tile_surface_en ? htile_address : 0; usage.depth_target = true; + usage.stencil = + buffer.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid; guest_address = buffer.Address(); const auto depth_slice_sz = buffer.GetDepthSliceSize(); @@ -260,7 +262,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { case AmdGpu::TilingMode::Display_MacroTiled: case AmdGpu::TilingMode::Texture_MacroTiled: case AmdGpu::TilingMode::Depth_MacroTiled: { - // ASSERT(!props.is_cube && !props.is_block); + ASSERT(!props.is_block); ASSERT(num_samples == 1); std::tie(mip_info.pitch, mip_info.size) = ImageSizeMacroTiled(mip_w, mip_h, bpp, num_samples, image.tiling_index); diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index ef6163c4e..cbf77f2d8 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -92,6 +92,8 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, bool is_vo_surface) noexcept { const auto base_format = Vulkan::LiverpoolToVK::SurfaceFormat(col_buffer.info.format, col_buffer.NumFormat()); + range.base.layer = col_buffer.view.slice_start; + range.extent.layers = col_buffer.NumSlices(); format = Vulkan::LiverpoolToVK::AdjustColorBufferFormat( base_format, col_buffer.info.comp_swap.Value(), is_vo_surface); } diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index d3a7d7960..75fa378cd 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -194,6 +194,7 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eR32G32Sfloat: case vk::Format::eR32G32Uint: case vk::Format::eR16G16B16A16Unorm: + case vk::Format::eR16G16B16A16Sfloat: return vk::Format::eR32G32Uint; case vk::Format::eBc2SrgbBlock: case vk::Format::eBc2UnormBlock: @@ -397,7 +398,7 @@ std::optional TileManager::TryDetile(Image& image) { const u32 image_size = image.info.guest_size_bytes; const auto [in_buffer, in_offset] = [&] -> std::pair { // Use stream buffer for smaller textures. - if (image_size <= StreamBufferSize) { + if (image_size <= stream_buffer.GetFreeSize()) { u32 offset = stream_buffer.Copy(image.info.guest_address, image_size); return {stream_buffer.Handle(), offset}; } From 2ba3221fc94df83d79103b957fd8888296c4f305 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 12 Aug 2024 20:10:42 +0200 Subject: [PATCH 078/109] fix for Linux compilation (#416) --- src/video_core/amdgpu/liverpool.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index e28b5680b..779e55368 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -766,7 +766,8 @@ struct Liverpool { } TilingMode GetTilingMode() const { - return info.linear_general ? TilingMode::Display_Linear : attrib.tile_mode_index; + return info.linear_general ? TilingMode::Display_Linear + : attrib.tile_mode_index.Value(); } bool IsTiled() const { From a15a93997cbd4bcf14008926bcaf0db3994137ed Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 12 Aug 2024 22:52:21 +0200 Subject: [PATCH 079/109] unlink sync2 if not present (tentative fix for #418) --- src/video_core/renderer_vulkan/vk_instance.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index eedba4c82..a54f2e0c4 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -317,7 +317,7 @@ bool Instance::CreateDevice() { .nullDescriptor = true, }, vk::PhysicalDeviceSynchronization2Features{ - .synchronization2 = has_sync2, + .synchronization2 = true, }, }; @@ -328,12 +328,18 @@ bool Instance::CreateDevice() { if (!robustness) { device_chain.unlink(); } + if (!has_sync2) { + device_chain.unlink(); + } try { device = physical_device.createDeviceUnique(device_chain.get()); } catch (vk::ExtensionNotPresentError& err) { LOG_CRITICAL(Render_Vulkan, "Some required extensions are not available {}", err.what()); return false; + } catch (vk::FeatureNotPresentError& err) { + LOG_CRITICAL(Render_Vulkan, "Some required features are not available {}", err.what()); + return false; } VULKAN_HPP_DEFAULT_DISPATCHER.init(*device); From 284035d3e2089beb5a353101b919b3a0fbb410ce Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:41:26 -0700 Subject: [PATCH 080/109] Enable VK_EXT_robustness2 nullDescriptor only if supported. --- src/video_core/renderer_vulkan/vk_instance.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index a54f2e0c4..5beb57c44 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -164,7 +164,7 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceColorWriteEnableFeaturesEXT, vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR, - vk::PhysicalDeviceDepthClipControlFeaturesEXT>(); + vk::PhysicalDeviceDepthClipControlFeaturesEXT, vk::PhysicalDeviceRobustness2FeaturesEXT>(); const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties>(); @@ -325,7 +325,10 @@ bool Instance::CreateDevice() { device_chain.unlink(); device_chain.unlink(); } - if (!robustness) { + if (robustness) { + device_chain.get().nullDescriptor = + feature_chain.get().nullDescriptor; + } else { device_chain.unlink(); } if (!has_sync2) { From 18f179928007c99dd2f5a739cf04a7b49dbab45f Mon Sep 17 00:00:00 2001 From: Borchev <4501931+Borchev@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:05:30 -0700 Subject: [PATCH 081/109] Add partial unmap support (#322) * Add partial unmap support * undo accidental whitespace removal * Fix assertions * Adjust Reserve and Free functions for partial unmapping --- src/core/address_space.cpp | 24 ++++++++++++++++++++++-- src/core/address_space.h | 3 ++- src/core/memory.cpp | 37 +++++++++++++++++++++---------------- src/core/memory.h | 3 ++- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index c3e0d77aa..91c8b7a1f 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -454,8 +454,28 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 #endif } -void AddressSpace::Unmap(VAddr virtual_addr, size_t size, bool has_backing) { - return impl->Unmap(virtual_addr, size, has_backing); +void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma, + PAddr phys_base, bool is_exec, bool has_backing) { +#ifdef _WIN32 + // There does not appear to be comparable support for partial unmapping on Windows. + // Unfortunately, a least one title was found to require this. The workaround is to unmap + // the entire allocation and remap the portions outside of the requested unmapping range. + impl->Unmap(virtual_addr, size, has_backing); + + // TODO: Determine if any titles require partial unmapping support for flexible allocations. + ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size), + "Partial unmapping of flexible allocations is not supported"); + + if (start_in_vma != 0) { + Map(virtual_addr, start_in_vma, 0, phys_base, is_exec); + } + + if (end_in_vma != size) { + Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec); + } +#else + impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing); +#endif } void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) { diff --git a/src/core/address_space.h b/src/core/address_space.h index 29f74f568..53041bccb 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -91,7 +91,8 @@ public: void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd); /// Unmaps specified virtual memory area. - void Unmap(VAddr virtual_addr, size_t size, bool has_backing); + void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma, + PAddr phys_base, bool is_exec, bool has_backing); void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index dc5ded41d..eed5126c0 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -54,7 +54,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr; // Add the allocated region to the list and commit its pages. - auto& area = CarveDmemArea(free_addr, size); + auto& area = CarveDmemArea(free_addr, size)->second; area.memory_type = memory_type; area.is_free = false; return free_addr; @@ -63,9 +63,8 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, void MemoryManager::Free(PAddr phys_addr, size_t size) { std::scoped_lock lk{mutex}; - const auto dmem_area = FindDmemArea(phys_addr); - ASSERT(dmem_area != dmem_map.end() && dmem_area->second.base == phys_addr && - dmem_area->second.size == size); + auto dmem_area = CarveDmemArea(phys_addr, size); + ASSERT(dmem_area != dmem_map.end() && dmem_area->second.size >= size); // Release any dmem mappings that reference this physical block. std::vector> remove_list; @@ -74,10 +73,11 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { continue; } if (mapping.phys_base <= phys_addr && phys_addr < mapping.phys_base + mapping.size) { - LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", addr, - mapping.size); + auto vma_segment_start_addr = phys_addr - mapping.phys_base + addr; + LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", + vma_segment_start_addr, size); // Unmaping might erase from vma_map. We can't do it here. - remove_list.emplace_back(addr, mapping.size); + remove_list.emplace_back(vma_segment_start_addr, size); } } for (const auto& [addr, size] : remove_list) { @@ -104,8 +104,6 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem const auto& vma = FindVMA(mapped_addr)->second; // If the VMA is mapped, unmap the region first. if (vma.IsMapped()) { - ASSERT_MSG(vma.base == mapped_addr && vma.size == size, - "Region must match when reserving a mapped region"); UnmapMemory(mapped_addr, size); } const size_t remaining_size = vma.base + vma.size - mapped_addr; @@ -169,6 +167,7 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M new_vma.prot = prot; new_vma.name = name; new_vma.type = type; + new_vma.is_exec = is_exec; if (type == VMAType::Direct) { new_vma.phys_base = phys_addr; @@ -216,10 +215,16 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { std::scoped_lock lk{mutex}; const auto it = FindVMA(virtual_addr); - ASSERT_MSG(it->second.Contains(virtual_addr, size), + const auto& vma_base = it->second; + ASSERT_MSG(vma_base.Contains(virtual_addr, size), "Existing mapping does not contain requested unmap range"); - const auto type = it->second.type; + const auto vma_base_addr = vma_base.base; + const auto vma_base_size = vma_base.size; + const auto phys_base = vma_base.phys_base; + const bool is_exec = vma_base.is_exec; + const auto start_in_vma = virtual_addr - vma_base_addr; + const auto type = vma_base.type; const bool has_backing = type == VMAType::Direct || type == VMAType::File; if (type == VMAType::Direct) { rasterizer->UnmapMemory(virtual_addr, size); @@ -239,7 +244,8 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { MergeAdjacent(vma_map, new_it); // Unmap the memory region. - impl.Unmap(virtual_addr, size, has_backing); + impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec, + has_backing); TRACK_FREE(virtual_addr, "VMEM"); } @@ -397,13 +403,12 @@ MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size return vma_handle; } -DirectMemoryArea& MemoryManager::CarveDmemArea(PAddr addr, size_t size) { +MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) { auto dmem_handle = FindDmemArea(addr); ASSERT_MSG(dmem_handle != dmem_map.end(), "Physical address not in dmem_map"); const DirectMemoryArea& area = dmem_handle->second; - ASSERT_MSG(area.is_free && area.base <= addr, - "Adding an allocation to already allocated region"); + ASSERT_MSG(area.base <= addr, "Adding an allocation to already allocated region"); const PAddr start_in_area = addr - area.base; const PAddr end_in_vma = start_in_area + size; @@ -418,7 +423,7 @@ DirectMemoryArea& MemoryManager::CarveDmemArea(PAddr addr, size_t size) { dmem_handle = Split(dmem_handle, start_in_area); } - return dmem_handle->second; + return dmem_handle; } MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) { diff --git a/src/core/memory.h b/src/core/memory.h index 6d0a977fc..d58269678 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -84,6 +84,7 @@ struct VirtualMemoryArea { bool disallow_merge = false; std::string name = ""; uintptr_t fd = 0; + bool is_exec = false; bool Contains(VAddr addr, size_t size) const { return addr >= base && (addr + size) <= (base + this->size); @@ -205,7 +206,7 @@ private: VMAHandle CarveVMA(VAddr virtual_addr, size_t size); - DirectMemoryArea& CarveDmemArea(PAddr addr, size_t size); + DMemHandle CarveDmemArea(PAddr addr, size_t size); VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); From 5eecd089ab259d2ae6f7d4bca48ebfaa04736089 Mon Sep 17 00:00:00 2001 From: Lizardy <6063922+lzardy@users.noreply.github.com> Date: Tue, 13 Aug 2024 02:08:03 -0400 Subject: [PATCH 082/109] thread_management.cpp: Various Mandatory Threading Fixes | Resolve #398 (#394) * Handle empty mutex attribute - scePthreadMutexInit did not return default when the mutex attributes were empty, now it does * fix conditional unsafety * Update thread_management.cpp fix deref * accurate heap api - modified HeapAPI to a struct with preset function fields - utilized the full array parameter passed to _sceKernelRtldSetApplicationHeapAPI * fallback to std malloc * clang format * Declare all HeapAPI replacement functions - calloc, realloc, memalign, reallocalign, malloc_stats, malloc_stats_fast, malloc_usable_size - posix_memalign corrected parameters * resolve suggestions - `using` definition replacement for AppHeapAPI - linux uses heap_malloc, windows uses std::malloc --------- Co-authored-by: microsoftv <6063922+microsoftv@users.noreply.github.com> --- .../libraries/kernel/memory_management.cpp | 4 ++-- src/core/libraries/kernel/memory_management.h | 2 +- .../libraries/kernel/thread_management.cpp | 12 ++++++++-- src/core/linker.cpp | 20 ++++++++++++++--- src/core/linker.h | 22 +++++++++++++++---- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 94762c4a0..54c5860f4 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -212,9 +212,9 @@ s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* out_size) { return ORBIS_OK; } -void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func) { +void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]) { auto* linker = Common::Singleton::Instance(); - linker->SetHeapApiFunc(func); + linker->SetHeapAPI(func); } int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 6735ead71..378449cc5 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -98,7 +98,7 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize); s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); -void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func); +void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]); int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 85e2d0e66..cdd729da6 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -421,13 +421,21 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) { return addr; } -int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, +int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* mutex_attr, const char* name) { + const ScePthreadMutexattr* attr; + if (mutex == nullptr) { return SCE_KERNEL_ERROR_EINVAL; } - if (attr == nullptr) { + if (mutex_attr == nullptr) { attr = g_pthread_cxt->getDefaultMutexattr(); + } else { + if (*mutex_attr == nullptr) { + attr = g_pthread_cxt->getDefaultMutexattr(); + } else { + attr = mutex_attr; + } } *mutex = new PthreadMutexInternal{}; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index e4cbe5739..d4a15825b 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -305,7 +305,8 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { // Module was just loaded by above code. Allocate TLS block for it. Module* module = m_modules[module_index - 1].get(); const u32 init_image_size = module->tls.init_image_size; - u8* dest = reinterpret_cast(heap_api_func(module->tls.image_size)); + // TODO: Determine if Windows will crash from this + u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size)); const u8* src = reinterpret_cast(module->tls.image_virtual_addr); std::memcpy(dest, src, init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); @@ -335,10 +336,23 @@ void Linker::InitTlsForThread(bool is_primary) { &addr_out, tls_aligned, 3, 0, "SceKernelPrimaryTcbTls"); ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); } else { - if (heap_api_func) { - addr_out = heap_api_func(total_tls_size); + if (heap_api) { +#ifndef WIN32 + addr_out = heap_api->heap_malloc(total_tls_size); } else { addr_out = std::malloc(total_tls_size); +#else + // TODO: Windows tls malloc replacement, refer to rtld_tls_block_malloc + LOG_ERROR(Core_Linker, "TLS user malloc called, using std::malloc"); + addr_out = std::malloc(total_tls_size); + if (!addr_out) { + auto pth_id = pthread_self(); + auto handle = pthread_gethandle(pth_id); + ASSERT_MSG(addr_out, + "Cannot allocate TLS block defined for handle=%x, index=%d size=%d", + handle, pth_id, total_tls_size); + } +#endif } } diff --git a/src/core/linker.h b/src/core/linker.h index aee8c8fd3..ed1fe400c 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -46,7 +46,21 @@ struct EntryParams { const char* argv[3]; }; -using HeapApiFunc = PS4_SYSV_ABI void* (*)(size_t); +struct HeapAPI { + PS4_SYSV_ABI void* (*heap_malloc)(size_t); + PS4_SYSV_ABI void (*heap_free)(void*); + PS4_SYSV_ABI void* (*heap_calloc)(size_t, size_t); + PS4_SYSV_ABI void* (*heap_realloc)(void*, size_t); + PS4_SYSV_ABI void* (*heap_memalign)(size_t, size_t); + PS4_SYSV_ABI int (*heap_posix_memalign)(void**, size_t, size_t); + // NOTE: Fields below may be inaccurate + PS4_SYSV_ABI int (*heap_reallocalign)(void); + PS4_SYSV_ABI void (*heap_malloc_stats)(void); + PS4_SYSV_ABI int (*heap_malloc_stats_fast)(void); + PS4_SYSV_ABI size_t (*heap_malloc_usable_size)(void*); +}; + +using AppHeapAPI = HeapAPI*; class Linker { public: @@ -75,8 +89,8 @@ public: } } - void SetHeapApiFunc(void* func) { - heap_api_func = *reinterpret_cast(func); + void SetHeapAPI(void* func[]) { + heap_api = reinterpret_cast(func); } void AdvanceGenerationCounter() noexcept { @@ -104,7 +118,7 @@ private: size_t static_tls_size{}; u32 max_tls_index{}; u32 num_static_modules{}; - HeapApiFunc heap_api_func{}; + AppHeapAPI heap_api{}; std::vector> m_modules; Loader::SymbolsResolver m_hle_symbols{}; }; From dfcfd62d4f76d392b1754ce13e8a11154749a78d Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Tue, 13 Aug 2024 03:12:38 -0300 Subject: [PATCH 083/109] spirv: fix image sample lod/clamp/offset translation (#402) * spirv: fix image sample lod/clamp translation * spirv: fix image sample offsets * fix ImageSample opcodes & offset emission --- .../backend/spirv/emit_spirv_image.cpp | 45 ++++++++++++---- .../backend/spirv/emit_spirv_instructions.h | 6 +-- .../frontend/translate/vector_memory.cpp | 13 +++-- src/shader_recompiler/ir/ir_emitter.cpp | 44 +++++----------- src/shader_recompiler/ir/ir_emitter.h | 22 ++++---- src/shader_recompiler/ir/opcodes.inc | 8 +-- .../ir/passes/resource_tracking_pass.cpp | 51 ++++++++++++++----- 7 files changed, 112 insertions(+), 77 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index e2d2c1ae0..72a603270 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -21,14 +21,19 @@ struct ImageOperands { boost::container::static_vector operands; }; -Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, +Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, Id offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - operands.Add(spv::ImageOperandsMask::Offset, offset); + if (Sirit::ValidId(bias)) { + operands.Add(spv::ImageOperandsMask::Bias, bias); + } + if (Sirit::ValidId(offset)) { + operands.Add(spv::ImageOperandsMask::Offset, offset); + } return ctx.OpImageSampleImplicitLod(ctx.F32[4], sampled_image, coords, operands.mask, operands.operands); } @@ -39,27 +44,49 @@ Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id c const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); - return ctx.OpImageSampleExplicitLod(ctx.F32[4], sampled_image, coords, - spv::ImageOperandsMask::Lod, lod); + ImageOperands operands; + if (Sirit::ValidId(lod)) { + operands.Add(spv::ImageOperandsMask::Lod, lod); + } + if (Sirit::ValidId(offset)) { + operands.Add(spv::ImageOperandsMask::Offset, offset); + } + return ctx.OpImageSampleExplicitLod(ctx.F32[4], sampled_image, coords, operands.mask, + operands.operands); } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias_lc, const IR::Value& offset) { + Id bias, Id offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); - return ctx.OpImageSampleDrefImplicitLod(ctx.F32[1], sampled_image, coords, dref); + ImageOperands operands; + if (Sirit::ValidId(bias)) { + operands.Add(spv::ImageOperandsMask::Bias, bias); + } + if (Sirit::ValidId(offset)) { + operands.Add(spv::ImageOperandsMask::Offset, offset); + } + return ctx.OpImageSampleDrefImplicitLod(ctx.F32[1], sampled_image, coords, dref, operands.mask, + operands.operands); } Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias_lc, Id offset) { + Id lod, Id offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); - return ctx.OpImageSampleDrefExplicitLod(ctx.F32[1], sampled_image, coords, dref, - spv::ImageOperandsMask::Lod, ctx.ConstF32(0.f)); + ImageOperands operands; + if (Sirit::ValidId(lod)) { + operands.Add(spv::ImageOperandsMask::Lod, lod); + } + if (Sirit::ValidId(offset)) { + operands.Add(spv::ImageOperandsMask::Offset, offset); + } + return ctx.OpImageSampleDrefExplicitLod(ctx.F32[1], sampled_image, coords, dref, operands.mask, + operands.operands); } Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id offset2) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 0b2020f18..85c6eaac5 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -357,14 +357,14 @@ Id EmitConvertF64U64(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value); -Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias_lc, +Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, Id offset); Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id offset); Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias_lc, const IR::Value& offset); + Id bias, Id offset); Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias_lc, Id offset); + Id lod, Id offset); Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id offset2); Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id offset2, Id dref); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 3c6dfbda4..bb202e426 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -135,8 +135,8 @@ void Translator::IMAGE_SAMPLE(const GcnInst& inst) { // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction // Set Architecture - const IR::Value offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; + const IR::U32 offset = + flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; const IR::F32 bias = flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; const IR::F32 dref = @@ -168,18 +168,17 @@ void Translator::IMAGE_SAMPLE(const GcnInst& inst) { // Issue IR instruction, leaving unknown fields blank to patch later. const IR::Value texel = [&]() -> IR::Value { - const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; if (!flags.test(MimgModifier::Pcf)) { if (explicit_lod) { - return ir.ImageSampleExplicitLod(handle, body, lod, offset, info); + return ir.ImageSampleExplicitLod(handle, body, offset, info); } else { - return ir.ImageSampleImplicitLod(handle, body, bias, offset, {}, info); + return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); } } if (explicit_lod) { - return ir.ImageSampleDrefExplicitLod(handle, body, dref, lod, offset, info); + return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); } - return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, {}, info); + return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); }(); for (u32 i = 0; i < 4; i++) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 03404aca0..08b7fbbc0 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -16,18 +16,6 @@ namespace { UNREACHABLE_MSG("Invalid type = {}, functionName = {}, line = {}", u32(type), functionName, lineNumber); } - -Value MakeLodClampPair(IREmitter& ir, const F32& bias_lod, const F32& lod_clamp) { - if (!bias_lod.IsEmpty() && !lod_clamp.IsEmpty()) { - return ir.CompositeConstruct(bias_lod, lod_clamp); - } else if (!bias_lod.IsEmpty()) { - return bias_lod; - } else if (!lod_clamp.IsEmpty()) { - return lod_clamp; - } else { - return Value{}; - } -} } // Anonymous namespace U1 IREmitter::Imm1(bool value) const { @@ -1386,30 +1374,26 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value); } -Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias, - const Value& offset, const F32& lod_clamp, +Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, + const U32& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, body, bias, offset); +} + +Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& body, const U32& offset, TextureInstInfo info) { - const Value bias_lc{MakeLodClampPair(*this, bias, lod_clamp)}; - return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, coords, bias_lc, offset); + return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, body, IR::F32{}, offset); } -Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& coords, const F32& lod, - const Value& offset, TextureInstInfo info) { - return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, coords, lod, offset); -} - -F32 IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& coords, const F32& dref, - const F32& bias, const Value& offset, - const F32& lod_clamp, TextureInstInfo info) { - const Value bias_lc{MakeLodClampPair(*this, bias, lod_clamp)}; - return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, coords, dref, bias_lc, +F32 IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& body, const F32& dref, + const F32& bias, const U32& offset, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, body, dref, bias, offset); } -F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& coords, const F32& dref, - const F32& lod, const Value& offset, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, coords, dref, lod, +F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body, const F32& dref, + const U32& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, body, dref, IR::F32{}, offset); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index a65e46136..fda206394 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -241,19 +241,21 @@ public: [[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords, const Value& value, TextureInstInfo info); - [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& coords, - const F32& bias, const Value& offset, - const F32& lod_clamp, TextureInstInfo info); - [[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& coords, - const F32& lod, const Value& offset, + [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body, + const F32& bias, const U32& offset, TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& coords, + + [[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& body, + const U32& offset, TextureInstInfo info); + + [[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& body, const F32& dref, const F32& bias, - const Value& offset, const F32& lod_clamp, + const U32& offset, TextureInstInfo info); + + [[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& body, + const F32& dref, const U32& offset, TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& coords, - const F32& dref, const F32& lod, - const Value& offset, TextureInstInfo info); + [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, const IR::U1& skip_mips); [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index aa2fd3f82..46918bc39 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -298,10 +298,10 @@ OPCODE(ConvertU16U32, U16, U32, OPCODE(ConvertU32U16, U32, U16, ) // Image operations -OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, Opaque, Opaque, ) -OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, Opaque, Opaque, ) -OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, F32, Opaque, Opaque, ) -OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, F32, Opaque, Opaque, ) +OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, U32, ) +OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, U32, ) +OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, U32, ) +OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, U32, ) OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, Opaque, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 6c96faa3d..bacbac72a 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -567,25 +567,47 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip if (inst_info.has_offset) { // The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. - const bool is_gather = inst.GetOpcode() == IR::Opcode::ImageGather || - inst.GetOpcode() == IR::Opcode::ImageGatherDref; - const u32 arg_pos = is_gather ? 2 : (inst_info.is_depth ? 4 : 3); + const u32 arg_pos = [&]() -> u32 { + switch (inst.GetOpcode()) { + case IR::Opcode::ImageGather: + case IR::Opcode::ImageGatherDref: + return 2; + case IR::Opcode::ImageSampleExplicitLod: + case IR::Opcode::ImageSampleImplicitLod: + return 3; + case IR::Opcode::ImageSampleDrefExplicitLod: + case IR::Opcode::ImageSampleDrefImplicitLod: + return 4; + default: + break; + } + return inst_info.is_depth ? 4 : 3; + }(); const IR::Value arg = inst.Arg(arg_pos); ASSERT_MSG(arg.Type() == IR::Type::U32, "Unexpected offset type"); - const auto sign_ext = [&](u32 value) { return ir.Imm32(s32(value << 24) >> 24); }; - union { - u32 raw; - BitField<0, 6, u32> x; - BitField<8, 6, u32> y; - BitField<16, 6, u32> z; - } offset{arg.U32()}; - const IR::Value value = ir.CompositeConstruct(sign_ext(offset.x), sign_ext(offset.y)); + const auto f = [&](IR::Value value, u32 offset) -> auto { + return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(offset), ir.Imm32(6), true); + }; + + const auto x = f(arg, 0); + const auto y = f(arg, 8); + const auto z = f(arg, 16); + const IR::Value value = ir.CompositeConstruct(x, y, z); inst.SetArg(arg_pos, value); } if (inst_info.has_lod_clamp) { - // Final argument contains lod_clamp - const u32 arg_pos = inst_info.is_depth ? 5 : 4; + const u32 arg_pos = [&]() -> u32 { + switch (inst.GetOpcode()) { + case IR::Opcode::ImageSampleImplicitLod: + return 2; + case IR::Opcode::ImageSampleDrefImplicitLod: + return 3; + default: + break; + } + return inst_info.is_depth ? 5 : 4; + }(); inst.SetArg(arg_pos, arg); } if (inst_info.explicit_lod) { @@ -593,7 +615,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod || inst.GetOpcode() == IR::Opcode::ImageSampleDrefExplicitLod); const u32 pos = inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod ? 2 : 3; - inst.SetArg(pos, arg); + const IR::Value value = inst_info.force_level0 ? ir.Imm32(0.f) : arg; + inst.SetArg(pos, value); } } From 1fb0da9b897b9a2b9d31a42898b660ef7d01a848 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:21:48 +0300 Subject: [PATCH 084/109] video_core: Crucial buffer cache fixes + proper GPU clears (#414) * translator: Use templates for stronger type guarantees * spirv: Define buffer offsets upfront * Saves a lot of shader instructions * buffer_cache: Use dynamic vertex input when available * Fixes issues when games like dark souls rebind vertex buffers with different stride * externals: Update boost * spirv: Use runtime array for ssbos * ssbos can be large and typically their size will vary, especially in generic copy/clear cs shaders * fs: Lock when doing case insensitive search * Dark Souls does fs lookups from different threads * texture_cache: More precise invalidation from compute * Fixes unrelated render targets being cleared * texture_cache: Use hashes for protect gpu modified images from reupload * translator: Treat V_CNDMASK as float * Sometimes it can have input modifiers. Worst this will cause is some extra calls to uintBitsToFloat and opposite. But most often this is used as float anyway * translator: Small optimization for V_SAD_U32 * Fix review * clang format --- externals/ext-boost | 2 +- src/core/file_sys/fs.cpp | 1 + .../libraries/kernel/threads/semaphore.cpp | 3 - .../spirv/emit_spirv_context_get_set.cpp | 15 +- .../backend/spirv/emit_spirv_special.cpp | 4 +- .../backend/spirv/spirv_emit_context.cpp | 26 +- .../backend/spirv/spirv_emit_context.h | 5 +- .../frontend/translate/translate.cpp | 329 ++++++++---------- .../frontend/translate/translate.h | 8 +- .../frontend/translate/vector_alu.cpp | 147 ++++---- src/video_core/buffer_cache/buffer_cache.cpp | 41 +++ src/video_core/buffer_cache/buffer_cache.h | 6 + .../renderer_vulkan/renderer_vulkan.h | 4 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 5 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 3 + .../renderer_vulkan/vk_instance.cpp | 9 +- src/video_core/renderer_vulkan/vk_instance.h | 6 + .../renderer_vulkan/vk_pipeline_cache.cpp | 4 + src/video_core/texture_cache/image.cpp | 1 + src/video_core/texture_cache/image.h | 1 + .../texture_cache/texture_cache.cpp | 82 +++-- src/video_core/texture_cache/texture_cache.h | 15 +- src/video_core/texture_cache/tile_manager.cpp | 1 - 23 files changed, 372 insertions(+), 346 deletions(-) diff --git a/externals/ext-boost b/externals/ext-boost index 147b2de77..a04136add 160000 --- a/externals/ext-boost +++ b/externals/ext-boost @@ -1 +1 @@ -Subproject commit 147b2de7734f5dc3b9aeb1f4135ae15fcd44b9d7 +Subproject commit a04136add1e469f46d8ae8d3e8307779240a5c53 diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 2bcff191c..a6d5c3eac 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -54,6 +54,7 @@ std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) // If the path does not exist attempt to verify this. // Retrieve parent path until we find one that exists. + std::scoped_lock lk{m_mutex}; path_parts.clear(); auto current_path = host_path; while (!std::filesystem::exists(current_path)) { diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index 5441c641a..5304dc57b 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -9,7 +9,6 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" -#include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" namespace Libraries::Kernel { @@ -82,7 +81,6 @@ public: public: struct WaitingThread : public ListBaseHook { - std::string name; std::condition_variable cv; u32 priority; s32 need_count; @@ -90,7 +88,6 @@ public: bool was_cancled{}; explicit WaitingThread(s32 need_count, bool is_fifo) : need_count{need_count} { - name = scePthreadSelf()->name; if (is_fifo) { return; } 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 bd34ed3db..5eae058ac 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 @@ -128,11 +128,7 @@ Id EmitReadConst(EmitContext& ctx) { Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { auto& buffer = ctx.buffers[handle]; - if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(buffer.global_binding); - } - const Id offset_dwords{ctx.OpShiftRightLogical(ctx.U32[1], buffer.offset, ctx.ConstU32(2U))}; - index = ctx.OpIAdd(ctx.U32[1], index, offset_dwords); + index = ctx.OpIAdd(ctx.U32[1], index, buffer.offset_dwords); const Id ptr{ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index)}; return ctx.OpLoad(buffer.data_types->Get(1), ptr); } @@ -229,9 +225,6 @@ Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { template static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; - if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(buffer.global_binding); - } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); if constexpr (N == 1) { @@ -404,9 +397,6 @@ static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 com template static Id EmitLoadBufferFormatF32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; - if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(buffer.global_binding); - } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); if constexpr (N == 1) { return GetBufferFormatValue(ctx, handle, address, 0); @@ -438,9 +428,6 @@ Id EmitLoadBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id ad template static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { auto& buffer = ctx.buffers[handle]; - if (!Sirit::ValidId(buffer.offset)) { - buffer.offset = ctx.GetBufferOffset(buffer.global_binding); - } address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); if constexpr (N == 1) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index 891e41df7..3ed89692b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -6,7 +6,9 @@ namespace Shader::Backend::SPIRV { -void EmitPrologue(EmitContext& ctx) {} +void EmitPrologue(EmitContext& ctx) { + ctx.DefineBufferOffsets(); +} void EmitEpilogue(EmitContext& ctx) {} diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 61b55437d..55754d455 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -165,14 +165,18 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f throw InvalidArgument("Invalid attribute type {}", fmt); } -Id EmitContext::GetBufferOffset(u32 binding) { - const u32 half = Shader::PushData::BufOffsetIndex + (binding >> 4); - const u32 comp = (binding & 0xf) >> 2; - const u32 offset = (binding & 0x3) << 3; - const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), - push_data_block, ConstU32(half), ConstU32(comp))}; - const Id value{OpLoad(U32[1], ptr)}; - return OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); +void EmitContext::DefineBufferOffsets() { + for (auto& buffer : buffers) { + const u32 binding = buffer.binding; + const u32 half = Shader::PushData::BufOffsetIndex + (binding >> 4); + const u32 comp = (binding & 0xf) >> 2; + const u32 offset = (binding & 0x3) << 3; + const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), + push_data_block, ConstU32(half), ConstU32(comp))}; + const Id value{OpLoad(U32[1], ptr)}; + buffer.offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); + buffer.offset_dwords = OpShiftRightLogical(U32[1], buffer.offset, ConstU32(2U)); + } } Id MakeDefaultValue(EmitContext& ctx, u32 default_value) { @@ -327,7 +331,9 @@ void EmitContext::DefineBuffers() { for (u32 i = 0; const auto& buffer : info.buffers) { const auto* data_types = True(buffer.used_types & IR::Type::F32) ? &F32 : &U32; const Id data_type = (*data_types)[1]; - const Id record_array_type{TypeArray(data_type, ConstU32(buffer.length))}; + const Id record_array_type{buffer.is_storage + ? TypeRuntimeArray(data_type) + : TypeArray(data_type, ConstU32(buffer.length))}; const Id struct_type{TypeStruct(record_array_type)}; if (std::ranges::find(type_ids, record_array_type.value, &Id::value) == type_ids.end()) { Decorate(record_array_type, spv::Decoration::ArrayStride, 4); @@ -354,7 +360,7 @@ void EmitContext::DefineBuffers() { buffers.push_back({ .id = id, - .global_binding = binding++, + .binding = binding++, .data_types = data_types, .pointer_type = pointer_type, .buffer = buffer.GetVsharp(info), diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 0d090eb31..81237a9a3 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -40,7 +40,7 @@ public: ~EmitContext(); Id Def(const IR::Value& value); - Id GetBufferOffset(u32 binding); + void DefineBufferOffsets(); [[nodiscard]] Id DefineInput(Id type, u32 location) { const Id input_id{DefineVar(type, spv::StorageClass::Input)}; @@ -203,7 +203,8 @@ public: struct BufferDefinition { Id id; Id offset; - u32 global_binding; + Id offset_dwords; + u32 binding; const VectorIds* data_types; Id pointer_type; AmdGpu::Buffer buffer; diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index b295c1bef..8ffde7fb3 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -73,101 +73,190 @@ void Translator::EmitPrologue() { } } -template <> -IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { - IR::U32F32 value{}; +template +T Translator::GetSrc(const InstOperand& operand) { + constexpr bool is_float = std::is_same_v; - const bool is_float = operand.type == ScalarType::Float32 || force_flt; + const auto get_imm = [&](auto value) -> T { + if constexpr (is_float) { + return ir.Imm32(std::bit_cast(value)); + } else { + return ir.Imm32(std::bit_cast(value)); + } + }; + + T value{}; switch (operand.field) { case OperandField::ScalarGPR: - if (is_float) { - value = ir.GetScalarReg(IR::ScalarReg(operand.code)); - } else { - value = ir.GetScalarReg(IR::ScalarReg(operand.code)); - } + value = ir.GetScalarReg(IR::ScalarReg(operand.code)); break; case OperandField::VectorGPR: - if (is_float) { - value = ir.GetVectorReg(IR::VectorReg(operand.code)); - } else { - value = ir.GetVectorReg(IR::VectorReg(operand.code)); - } + value = ir.GetVectorReg(IR::VectorReg(operand.code)); break; case OperandField::ConstZero: - if (is_float) { - value = ir.Imm32(0.f); - } else { - value = ir.Imm32(0U); - } + value = get_imm(0U); break; case OperandField::SignedConstIntPos: - ASSERT(!force_flt); - value = ir.Imm32(operand.code - SignedConstIntPosMin + 1); + value = get_imm(operand.code - SignedConstIntPosMin + 1); break; case OperandField::SignedConstIntNeg: - ASSERT(!force_flt); - value = ir.Imm32(-s32(operand.code) + SignedConstIntNegMin - 1); + value = get_imm(-s32(operand.code) + SignedConstIntNegMin - 1); break; case OperandField::LiteralConst: - if (is_float) { - value = ir.Imm32(std::bit_cast(operand.code)); - } else { - value = ir.Imm32(operand.code); - } + value = get_imm(operand.code); break; case OperandField::ConstFloatPos_1_0: - if (is_float) { - value = ir.Imm32(1.f); - } else { - value = ir.Imm32(std::bit_cast(1.f)); - } + value = get_imm(1.f); break; case OperandField::ConstFloatPos_0_5: - value = ir.Imm32(0.5f); + value = get_imm(0.5f); break; case OperandField::ConstFloatPos_2_0: - value = ir.Imm32(2.0f); + value = get_imm(2.0f); break; case OperandField::ConstFloatPos_4_0: - value = ir.Imm32(4.0f); + value = get_imm(4.0f); break; case OperandField::ConstFloatNeg_0_5: - value = ir.Imm32(-0.5f); + value = get_imm(-0.5f); break; case OperandField::ConstFloatNeg_1_0: - if (is_float) { - value = ir.Imm32(-1.0f); - } else { - value = ir.Imm32(std::bit_cast(-1.0f)); - } + value = get_imm(-1.0f); break; case OperandField::ConstFloatNeg_2_0: - value = ir.Imm32(-2.0f); + value = get_imm(-2.0f); break; case OperandField::ConstFloatNeg_4_0: - value = ir.Imm32(-4.0f); + value = get_imm(-4.0f); break; case OperandField::VccLo: - if (force_flt) { + if constexpr (is_float) { value = ir.BitCast(ir.GetVccLo()); } else { value = ir.GetVccLo(); } break; case OperandField::VccHi: - if (force_flt) { + if constexpr (is_float) { value = ir.BitCast(ir.GetVccHi()); } else { value = ir.GetVccHi(); } break; case OperandField::M0: - return m0_value; + if constexpr (is_float) { + UNREACHABLE(); + } else { + return m0_value; + } default: UNREACHABLE(); } - if (is_float) { + if constexpr (is_float) { + if (operand.input_modifier.abs) { + value = ir.FPAbs(value); + } + if (operand.input_modifier.neg) { + value = ir.FPNeg(value); + } + } else { + if (operand.input_modifier.abs) { + UNREACHABLE(); + } + if (operand.input_modifier.neg) { + UNREACHABLE(); + } + } + return value; +} + +template IR::U32 Translator::GetSrc(const InstOperand&); +template IR::F32 Translator::GetSrc(const InstOperand&); + +template +T Translator::GetSrc64(const InstOperand& operand) { + constexpr bool is_float = std::is_same_v; + + const auto get_imm = [&](auto value) -> T { + if constexpr (is_float) { + return ir.Imm64(std::bit_cast(value)); + } else { + return ir.Imm64(std::bit_cast(value)); + } + }; + + T value{}; + switch (operand.field) { + case OperandField::ScalarGPR: { + const auto value_lo = ir.GetScalarReg(IR::ScalarReg(operand.code)); + const auto value_hi = ir.GetScalarReg(IR::ScalarReg(operand.code + 1)); + if constexpr (is_float) { + UNREACHABLE(); + } else { + value = ir.PackUint2x32(ir.CompositeConstruct(value_lo, value_hi)); + } + break; + } + case OperandField::VectorGPR: { + const auto value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); + const auto value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); + if constexpr (is_float) { + UNREACHABLE(); + } else { + value = ir.PackUint2x32(ir.CompositeConstruct(value_lo, value_hi)); + } + break; + } + case OperandField::ConstZero: + value = get_imm(0ULL); + break; + case OperandField::SignedConstIntPos: + value = get_imm(s64(operand.code) - SignedConstIntPosMin + 1); + break; + case OperandField::SignedConstIntNeg: + value = get_imm(-s64(operand.code) + SignedConstIntNegMin - 1); + break; + case OperandField::LiteralConst: + value = get_imm(u64(operand.code)); + break; + case OperandField::ConstFloatPos_1_0: + value = get_imm(1.0); + break; + case OperandField::ConstFloatPos_0_5: + value = get_imm(0.5); + break; + case OperandField::ConstFloatPos_2_0: + value = get_imm(2.0); + break; + case OperandField::ConstFloatPos_4_0: + value = get_imm(4.0); + break; + case OperandField::ConstFloatNeg_0_5: + value = get_imm(-0.5); + break; + case OperandField::ConstFloatNeg_1_0: + value = get_imm(-1.0); + break; + case OperandField::ConstFloatNeg_2_0: + value = get_imm(-2.0); + break; + case OperandField::ConstFloatNeg_4_0: + value = get_imm(-4.0); + break; + case OperandField::VccLo: + if constexpr (is_float) { + UNREACHABLE(); + } else { + value = ir.PackUint2x32(ir.CompositeConstruct(ir.GetVccLo(), ir.GetVccHi())); + } + break; + case OperandField::VccHi: + default: + UNREACHABLE(); + } + + if constexpr (is_float) { if (operand.input_modifier.abs) { value = ir.FPAbs(value); } @@ -178,148 +267,8 @@ IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { return value; } -template <> -IR::U32 Translator::GetSrc(const InstOperand& operand, bool force_flt) { - return GetSrc(operand, force_flt); -} - -template <> -IR::F32 Translator::GetSrc(const InstOperand& operand, bool) { - return GetSrc(operand, true); -} - -template <> -IR::U64F64 Translator::GetSrc64(const InstOperand& operand, bool force_flt) { - IR::Value value_hi{}; - IR::Value value_lo{}; - - bool immediate = false; - const bool is_float = operand.type == ScalarType::Float64 || force_flt; - switch (operand.field) { - case OperandField::ScalarGPR: - if (is_float) { - value_lo = ir.GetScalarReg(IR::ScalarReg(operand.code)); - value_hi = ir.GetScalarReg(IR::ScalarReg(operand.code + 1)); - } else if (operand.type == ScalarType::Uint64 || operand.type == ScalarType::Sint64) { - value_lo = ir.GetScalarReg(IR::ScalarReg(operand.code)); - value_hi = ir.GetScalarReg(IR::ScalarReg(operand.code + 1)); - } else { - UNREACHABLE(); - } - break; - case OperandField::VectorGPR: - if (is_float) { - value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); - value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); - } else if (operand.type == ScalarType::Uint64 || operand.type == ScalarType::Sint64) { - value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); - value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); - } else { - UNREACHABLE(); - } - break; - case OperandField::ConstZero: - immediate = true; - if (force_flt) { - value_lo = ir.Imm64(0.0); - } else { - value_lo = ir.Imm64(u64(0U)); - } - break; - case OperandField::SignedConstIntPos: - ASSERT(!force_flt); - immediate = true; - value_lo = ir.Imm64(s64(operand.code) - SignedConstIntPosMin + 1); - break; - case OperandField::SignedConstIntNeg: - ASSERT(!force_flt); - immediate = true; - value_lo = ir.Imm64(-s64(operand.code) + SignedConstIntNegMin - 1); - break; - case OperandField::LiteralConst: - immediate = true; - if (force_flt) { - UNREACHABLE(); // There is a literal double? - } else { - value_lo = ir.Imm64(u64(operand.code)); - } - break; - case OperandField::ConstFloatPos_1_0: - immediate = true; - if (force_flt) { - value_lo = ir.Imm64(1.0); - } else { - value_lo = ir.Imm64(std::bit_cast(f64(1.0))); - } - break; - case OperandField::ConstFloatPos_0_5: - immediate = true; - value_lo = ir.Imm64(0.5); - break; - case OperandField::ConstFloatPos_2_0: - immediate = true; - value_lo = ir.Imm64(2.0); - break; - case OperandField::ConstFloatPos_4_0: - immediate = true; - value_lo = ir.Imm64(4.0); - break; - case OperandField::ConstFloatNeg_0_5: - immediate = true; - value_lo = ir.Imm64(-0.5); - break; - case OperandField::ConstFloatNeg_1_0: - immediate = true; - value_lo = ir.Imm64(-1.0); - break; - case OperandField::ConstFloatNeg_2_0: - immediate = true; - value_lo = ir.Imm64(-2.0); - break; - case OperandField::ConstFloatNeg_4_0: - immediate = true; - value_lo = ir.Imm64(-4.0); - break; - case OperandField::VccLo: { - value_lo = ir.GetVccLo(); - value_hi = ir.GetVccHi(); - } break; - case OperandField::VccHi: - UNREACHABLE(); - default: - UNREACHABLE(); - } - - IR::Value value; - - if (immediate) { - value = value_lo; - } else if (is_float) { - throw NotImplementedException("required OpPackDouble2x32 implementation"); - } else { - IR::Value packed = ir.CompositeConstruct(value_lo, value_hi); - value = ir.PackUint2x32(packed); - } - - if (is_float) { - if (operand.input_modifier.abs) { - value = ir.FPAbs(IR::F32F64(value)); - } - if (operand.input_modifier.neg) { - value = ir.FPNeg(IR::F32F64(value)); - } - } - return IR::U64F64(value); -} - -template <> -IR::U64 Translator::GetSrc64(const InstOperand& operand, bool force_flt) { - return GetSrc64(operand, force_flt); -} -template <> -IR::F64 Translator::GetSrc64(const InstOperand& operand, bool) { - return GetSrc64(operand, true); -} +template IR::U64 Translator::GetSrc64(const InstOperand&); +template IR::F64 Translator::GetSrc64(const InstOperand&); void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { IR::U32F32 result = value; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index fe4457d27..2e12209dc 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -211,10 +211,10 @@ public: void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); private: - template - [[nodiscard]] T GetSrc(const InstOperand& operand, bool flt_zero = false); - template - [[nodiscard]] T GetSrc64(const InstOperand& operand, bool flt_zero = false); + template + [[nodiscard]] T GetSrc(const InstOperand& operand); + template + [[nodiscard]] T GetSrc64(const InstOperand& operand); void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 89428c44f..1bbc3c162 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "shader_recompiler/frontend/translate/translate.h" -#include "shader_recompiler/profile.h" namespace Shader::Gcn { @@ -312,7 +311,7 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { } void Translator::V_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); + SetDst(inst.dst[0], GetSrc(inst.src[0])); } void Translator::V_SAD(const GcnInst& inst) { @@ -321,14 +320,14 @@ void Translator::V_SAD(const GcnInst& inst) { } void Translator::V_MAC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0], true), GetSrc(inst.src[1], true), - GetSrc(inst.dst[0], true))); + SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), + GetSrc(inst.dst[0]))); } void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { const IR::VectorReg dst_reg{inst.dst[0].code}; const IR::Value vec_f32 = - ir.CompositeConstruct(GetSrc(inst.src[0], true), GetSrc(inst.src[1], true)); + ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); ir.SetVectorReg(dst_reg, ir.PackHalf2x16(vec_f32)); } @@ -339,13 +338,13 @@ void Translator::V_CVT_F32_F16(const GcnInst& inst) { } void Translator::V_CVT_F16_F32(const GcnInst& inst) { - const IR::F32 src0 = GetSrc(inst.src[0], true); + const IR::F32 src0 = GetSrc(inst.src[0]); const IR::F16 src0fp16 = ir.FPConvert(16, src0); SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); } void Translator::V_MUL_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0], true), GetSrc(inst.src[1], true))); + SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); } void Translator::V_CNDMASK_B32(const GcnInst& inst) { @@ -354,24 +353,8 @@ void Translator::V_CNDMASK_B32(const GcnInst& inst) { const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR ? ir.GetThreadBitScalarReg(flag_reg) : ir.GetVcc(); - - // We can treat the instruction as integer most of the time, but when a source is - // a floating point constant we will force the other as float for better readability - // The other operand is also higly likely to be float as well. - const auto is_float_const = [](OperandField field) { - return field >= OperandField::ConstFloatPos_0_5 && field <= OperandField::ConstFloatNeg_4_0; - }; - const bool has_flt_source = - is_float_const(inst.src[0].field) || is_float_const(inst.src[1].field); - IR::U32F32 src0 = GetSrc(inst.src[0], has_flt_source); - IR::U32F32 src1 = GetSrc(inst.src[1], has_flt_source); - if (src0.Type() == IR::Type::F32 && src1.Type() == IR::Type::U32) { - src1 = ir.BitCast(src1); - } - if (src1.Type() == IR::Type::F32 && src0.Type() == IR::Type::U32) { - src0 = ir.BitCast(src0); - } - const IR::Value result = ir.Select(flag, src1, src0); + const IR::Value result = + ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); ir.SetVectorReg(dst_reg, IR::U32F32{result}); } @@ -448,21 +431,21 @@ void Translator::V_CVT_F32_U32(const GcnInst& inst) { } void Translator::V_MAD_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 src2{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } void Translator::V_FRACT_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; const IR::VectorReg dst_reg{inst.dst[0].code}; ir.SetVectorReg(dst_reg, ir.Fract(src0)); } void Translator::V_ADD_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPAdd(src0, src1)); } @@ -476,9 +459,9 @@ void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { } void Translator::V_MED3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 src2{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); } @@ -492,32 +475,32 @@ void Translator::V_MED3_I32(const GcnInst& inst) { } void Translator::V_FLOOR_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; const IR::VectorReg dst_reg{inst.dst[0].code}; ir.SetVectorReg(dst_reg, ir.FPFloor(src0)); } void Translator::V_SUB_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPSub(src0, src1)); } void Translator::V_RCP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPRecip(src0)); } void Translator::V_FMA_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 src2{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; const IR::U1 result = [&] { switch (op) { case ConditionOp::F: @@ -557,8 +540,8 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { } void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); } @@ -569,40 +552,40 @@ void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { } void Translator::V_RSQ_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); } void Translator::V_SIN_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPSin(src0)); } void Translator::V_LOG_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPLog2(src0)); } void Translator::V_EXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPExp2(src0)); } void Translator::V_SQRT_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPSqrt(src0)); } void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); } void Translator::V_MIN3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 src2{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); } @@ -614,9 +597,9 @@ void Translator::V_MIN3_I32(const GcnInst& inst) { } void Translator::V_MADMK_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 k{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 k{GetSrc(inst.src[2])}; SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); } @@ -625,25 +608,25 @@ void Translator::V_CUBEMA_F32(const GcnInst& inst) { } void Translator::V_CUBESC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0], true)); + SetDst(inst.dst[0], GetSrc(inst.src[0])); } void Translator::V_CUBETC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[1], true)); + SetDst(inst.dst[0], GetSrc(inst.src[1])); } void Translator::V_CUBEID_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[2], true)); + SetDst(inst.dst[0], GetSrc(inst.src[2])); } void Translator::V_CVT_U32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); } void Translator::V_SUBREV_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPSub(src1, src0)); } @@ -727,9 +710,17 @@ void Translator::V_SAD_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 max{ir.IMax(src0, src1, false)}; - const IR::U32 min{ir.IMin(src0, src1, false)}; - SetDst(inst.dst[0], ir.IAdd(ir.ISub(max, min), src2)); + IR::U32 result; + if (src0.IsImmediate() && src0.U32() == 0U) { + result = src1; + } else if (src1.IsImmediate() && src1.U32() == 0U) { + result = src0; + } else { + const IR::U32 max{ir.IMax(src0, src1, false)}; + const IR::U32 min{ir.IMin(src0, src1, false)}; + result = ir.ISub(max, min); + } + SetDst(inst.dst[0], ir.IAdd(result, src2)); } void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { @@ -783,7 +774,7 @@ void Translator::V_MAD_U32_U24(const GcnInst& inst) { } void Translator::V_RNDNE_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPRoundEven(src0)); } @@ -794,14 +785,14 @@ void Translator::V_BCNT_U32_B32(const GcnInst& inst) { } void Translator::V_COS_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPCos(src0)); } void Translator::V_MAX3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; - const IR::F32 src1{GetSrc(inst.src[1], true)}; - const IR::F32 src2{GetSrc(inst.src[2], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); } @@ -813,7 +804,7 @@ void Translator::V_MAX3_U32(const GcnInst& inst) { } void Translator::V_CVT_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); } @@ -830,12 +821,12 @@ void Translator::V_MUL_LO_U32(const GcnInst& inst) { } void Translator::V_TRUNC_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPTrunc(src0)); } void Translator::V_CEIL_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.FPCeil(src0)); } @@ -899,18 +890,18 @@ void Translator::V_BFREV_B32(const GcnInst& inst) { } void Translator::V_LDEXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); } void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0], true)}; + const IR::F32 src0{GetSrc(inst.src[0])}; SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); } void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { - const IR::F32F64 src0{GetSrc(inst.src[0])}; + const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; IR::U1 value; if (src1.IsImmediate()) { diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 7ab0d8171..2246807ad 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -87,6 +87,15 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si } bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { + boost::container::small_vector attributes; + boost::container::small_vector bindings; + SCOPE_EXIT { + if (instance.IsVertexInputDynamicState()) { + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.setVertexInputEXT(bindings, attributes); + } + }; + if (vs_info.vs_inputs.empty()) { return false; } @@ -122,6 +131,21 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { } guest_buffers.emplace_back(buffer); ranges.emplace_back(buffer.base_address, buffer.base_address + buffer.GetSize()); + attributes.push_back({ + .location = input.binding, + .binding = input.binding, + .format = + Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()), + .offset = 0, + }); + bindings.push_back({ + .binding = input.binding, + .stride = buffer.GetStride(), + .inputRate = input.instance_step_rate == Shader::Info::VsInput::None + ? vk::VertexInputRate::eVertex + : vk::VertexInputRate::eInstance, + .divisor = 1, + }); } std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { @@ -224,6 +248,19 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b return {&buffer, buffer.Offset(device_addr)}; } +std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { + const u64 page = gpu_addr >> CACHING_PAGEBITS; + const BufferId buffer_id = page_table[page]; + if (buffer_id) { + const Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(gpu_addr, size)) { + return {&buffer, buffer.Offset(gpu_addr)}; + } + } + const u32 offset = staging_buffer.Copy(gpu_addr, size, 16); + return {&staging_buffer, offset}; +} + bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { const VAddr end_addr = addr + size; const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE); @@ -248,6 +285,10 @@ bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { return memory_tracker.IsRegionCpuModified(addr, size); } +bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) { + return memory_tracker.IsRegionGpuModified(addr, size); +} + BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { if (device_addr == 0) { return NULL_BUFFER_ID; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 0dee87cf5..33ea3f86b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -69,12 +69,18 @@ public: /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written); + /// Obtains a temporary buffer for usage in texture cache. + [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); + /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); /// Return true when a CPU region is modified from the CPU [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size); + /// Return true when a CPU region is modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); + private: template void ForEachBufferInRange(VAddr device_addr, u64 size, Func&& func) { diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 8178c88de..113b380eb 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -47,7 +47,7 @@ public: Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, VAddr cpu_address, bool is_eop) { const auto info = VideoCore::ImageInfo{attribute, cpu_address}; - const auto image_id = texture_cache.FindImage(info, false); + const auto image_id = texture_cache.FindImage(info); auto& image = texture_cache.GetImage(image_id); return PrepareFrameInternal(image, is_eop); } @@ -61,7 +61,7 @@ public: const Libraries::VideoOut::BufferAttributeGroup& attribute, VAddr cpu_address) { vo_buffers_addr.emplace_back(cpu_address); const auto info = VideoCore::ImageInfo{attribute, cpu_address}; - const auto image_id = texture_cache.FindImage(info, false); + const auto image_id = texture_cache.FindImage(info); return texture_cache.GetImage(image_id); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 21710a76a..62b50eeb1 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -96,7 +96,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, Shader::PushData push_data{}; u32 binding{}; - for (u32 i = 0; const auto& buffer : info.buffers) { + for (const auto& buffer : info.buffers) { const auto vsharp = buffer.GetVsharp(info); const VAddr address = vsharp.base_address; // Most of the time when a metadata is updated with a shader it gets cleared. It means we @@ -115,7 +115,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } const u32 size = vsharp.GetSize(); if (buffer.is_written) { - texture_cache.InvalidateMemory(address, size); + texture_cache.InvalidateMemory(address, size, true); } const u32 alignment = buffer.is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); @@ -137,7 +137,6 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); - i++; } for (const auto& image_desc : info.images) { diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 5d87a1caf..cf23ade26 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -145,6 +145,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul dynamic_states.push_back(vk::DynamicState::eColorWriteEnableEXT); dynamic_states.push_back(vk::DynamicState::eColorWriteMaskEXT); } + if (instance.IsVertexInputDynamicState()) { + dynamic_states.push_back(vk::DynamicState::eVertexInputEXT); + } const vk::PipelineDynamicStateCreateInfo dynamic_info = { .dynamicStateCount = static_cast(dynamic_states.size()), diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 5beb57c44..b60b78e13 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -202,6 +202,8 @@ bool Instance::CreateDevice() { add_extension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); workgroup_memory_explicit_layout = add_extension(VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); + vertex_input_dynamic_state = add_extension(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + // The next two extensions are required to be available together in order to support write masks color_write_en = add_extension(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); @@ -319,6 +321,9 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceSynchronization2Features{ .synchronization2 = true, }, + vk::PhysicalDeviceVertexInputDynamicStateFeaturesEXT{ + .vertexInputDynamicState = true, + }, }; if (!color_write_en) { @@ -331,8 +336,8 @@ bool Instance::CreateDevice() { } else { device_chain.unlink(); } - if (!has_sync2) { - device_chain.unlink(); + if (!vertex_input_dynamic_state) { + device_chain.unlink(); } try { diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 2f2397d64..4cb4741a5 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -132,6 +132,11 @@ public: return color_write_en; } + /// Returns true when VK_EXT_vertex_input_dynamic_state is supported. + bool IsVertexInputDynamicState() const { + return vertex_input_dynamic_state; + } + /// Returns the vendor ID of the physical device u32 GetVendorID() const { return properties.vendorID; @@ -257,6 +262,7 @@ private: bool external_memory_host{}; bool workgroup_memory_explicit_layout{}; bool color_write_en{}; + bool vertex_input_dynamic_state{}; u64 min_imported_host_pointer_alignment{}; u32 subgroup_size{}; bool tooling_info{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 8d27d252c..8a22b9256 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -209,6 +209,10 @@ void PipelineCache::RefreshGraphicsKey() { continue; } const auto* bininfo = Liverpool::GetBinaryInfo(*pgm); + if (!bininfo->Valid()) { + key.stage_hashes[i] = 0; + continue; + } key.stage_hashes[i] = bininfo->shader_hash; } } diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index f1148760e..bae4b89d4 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -117,6 +117,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, : instance{&instance_}, scheduler{&scheduler_}, info{info_}, image{instance->GetDevice(), instance->GetAllocator()}, cpu_addr{info.guest_address}, cpu_addr_end{cpu_addr + info.guest_size_bytes} { + mip_hashes.resize(info.resources.levels); ASSERT(info.pixel_format != vk::Format::eUndefined); // Here we force `eExtendedUsage` as don't know all image usage cases beforehand. In normal case // the texture cache should re-create the resource with the usage requested diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index b18f1002b..5a888346f 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -111,6 +111,7 @@ struct Image { vk::Flags pl_stage = vk::PipelineStageFlagBits::eAllCommands; vk::Flags access_mask = vk::AccessFlagBits::eNone; vk::ImageLayout layout = vk::ImageLayout::eUndefined; + boost::container::small_vector mip_hashes; }; } // namespace VideoCore diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 53596f8e3..6b14faac4 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -3,6 +3,7 @@ #include #include "common/assert.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -11,13 +12,11 @@ namespace VideoCore { -static constexpr u64 StreamBufferSize = 512_MB; static constexpr u64 PageShift = 12; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache& buffer_cache_, PageManager& tracker_) : instance{instance_}, scheduler{scheduler_}, buffer_cache{buffer_cache_}, tracker{tracker_}, - staging{instance, scheduler, MemoryUsage::Upload, StreamBufferSize}, tile_manager{instance, scheduler} { ImageInfo info; info.pixel_format = vk::Format::eR8G8B8A8Unorm; @@ -31,9 +30,12 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& TextureCache::~TextureCache() = default; -void TextureCache::InvalidateMemory(VAddr address, size_t size) { +void TextureCache::InvalidateMemory(VAddr address, size_t size, bool from_compute) { std::unique_lock lock{mutex}; ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { + if (from_compute && !image.Overlaps(address, size)) { + return; + } // Ensure image is reuploaded when accessed again. image.flags |= ImageFlagBits::CpuModified; // Untrack image, so the range is unprotected and the guest can write freely. @@ -57,7 +59,7 @@ void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { } } -ImageId TextureCache::FindImage(const ImageInfo& info, bool refresh_on_create) { +ImageId TextureCache::FindImage(const ImageInfo& info) { if (info.guest_address == 0) [[unlikely]] { return NULL_IMAGE_VIEW_ID; } @@ -87,12 +89,6 @@ ImageId TextureCache::FindImage(const ImageInfo& info, bool refresh_on_create) { image_id = image_ids[image_ids.size() > 1 ? 1 : 0]; } - Image& image = slot_images[image_id]; - if (True(image.flags & ImageFlagBits::CpuModified) && refresh_on_create) { - RefreshImage(image); - TrackImage(image, image_id); - } - return image_id; } @@ -119,6 +115,7 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { const ImageId image_id = FindImage(info); + UpdateImage(image_id); Image& image = slot_images[image_id]; auto& usage = image.info.usage; @@ -165,7 +162,8 @@ ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, const ImageViewInfo& view_info) { const ImageId image_id = FindImage(image_info); Image& image = slot_images[image_id]; - image.flags &= ~ImageFlagBits::CpuModified; + image.flags |= ImageFlagBits::GpuModified; + UpdateImage(image_id); image.Transit(vk::ImageLayout::eColorAttachmentOptimal, vk::AccessFlagBits::eColorAttachmentWrite | @@ -198,8 +196,9 @@ ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, const ImageViewInfo& view_info) { - const ImageId image_id = FindImage(image_info, false); + const ImageId image_id = FindImage(image_info); Image& image = slot_images[image_id]; + image.flags |= ImageFlagBits::GpuModified; image.flags &= ~ImageFlagBits::CpuModified; const auto new_layout = view_info.is_storage ? vk::ImageLayout::eDepthStencilAttachmentOptimal @@ -228,22 +227,6 @@ void TextureCache::RefreshImage(Image& image) { // Mark image as validated. image.flags &= ~ImageFlagBits::CpuModified; - scheduler.EndRendering(); - - const auto cmdbuf = scheduler.CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); - - vk::Buffer buffer{staging.Handle()}; - u32 offset{0}; - - auto upload_buffer = tile_manager.TryDetile(image); - if (upload_buffer) { - buffer = *upload_buffer; - } else { - // Upload data to the staging buffer. - offset = staging.Copy(image.info.guest_address, image.info.guest_size_bytes, 16); - } - const auto& num_layers = image.info.resources.layers; const auto& num_mips = image.info.resources.levels; ASSERT(num_mips == image.info.mips_layout.size()); @@ -254,12 +237,23 @@ void TextureCache::RefreshImage(Image& image) { const u32 height = std::max(image.info.size.height >> m, 1u); const u32 depth = image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; - const auto& [_, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + + // Protect GPU modified resources from accidental reuploads. + if (True(image.flags & ImageFlagBits::GpuModified) && + !buffer_cache.IsRegionGpuModified(image.info.guest_address + mip_ofs, mip_size)) { + const u8* addr = std::bit_cast(image.info.guest_address); + const u64 hash = XXH3_64bits(addr + mip_ofs, mip_size); + if (image.mip_hashes[m] == hash) { + continue; + } + image.mip_hashes[m] = hash; + } image_copy.push_back({ - .bufferOffset = offset + mip_ofs * num_layers, - .bufferRowLength = static_cast(mip_pitch), - .bufferImageHeight = static_cast(mip_height), + .bufferOffset = mip_ofs * num_layers, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), .imageSubresource{ .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = m, @@ -271,6 +265,30 @@ void TextureCache::RefreshImage(Image& image) { }); } + if (image_copy.empty()) { + return; + } + + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite, cmdbuf); + + const VAddr image_addr = image.info.guest_address; + const size_t image_size = image.info.guest_size_bytes; + vk::Buffer buffer{}; + u32 offset{}; + if (auto upload_buffer = tile_manager.TryDetile(image); upload_buffer) { + buffer = *upload_buffer; + } else { + const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); + buffer = vk_buffer->Handle(); + offset = buf_offset; + } + + for (auto& copy : image_copy) { + copy.bufferOffset += offset; + } + cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 17a09898d..b3af0ff11 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -38,13 +38,13 @@ public: ~TextureCache(); /// Invalidates any image in the logical page range. - void InvalidateMemory(VAddr address, size_t size); + void InvalidateMemory(VAddr address, size_t size, bool from_compute = false); /// Evicts any images that overlap the unmapped range. void UnmapMemory(VAddr cpu_addr, size_t size); /// Retrieves the image handle of the image with the provided attributes. - [[nodiscard]] ImageId FindImage(const ImageInfo& info, bool refresh_on_create = true); + [[nodiscard]] ImageId FindImage(const ImageInfo& info); /// Retrieves an image view with the properties of the specified image descriptor. [[nodiscard]] ImageView& FindTexture(const ImageInfo& image_info, @@ -58,6 +58,16 @@ public: [[nodiscard]] ImageView& FindDepthTarget(const ImageInfo& image_info, const ImageViewInfo& view_info); + /// Updates image contents if it was modified by CPU. + void UpdateImage(ImageId image_id) { + Image& image = slot_images[image_id]; + if (False(image.flags & ImageFlagBits::CpuModified)) { + return; + } + RefreshImage(image); + TrackImage(image, image_id); + } + /// Reuploads image contents. void RefreshImage(Image& image); @@ -170,7 +180,6 @@ private: Vulkan::Scheduler& scheduler; BufferCache& buffer_cache; PageManager& tracker; - StreamBuffer staging; TileManager tile_manager; Common::SlotVector slot_images; Common::SlotVector slot_image_views; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 75fa378cd..6447fde17 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -5,7 +5,6 @@ #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/texture_cache/image_view.h" -#include "video_core/texture_cache/texture_cache.h" #include "video_core/texture_cache/tile_manager.h" #include "video_core/host_shaders/detile_m32x1_comp.h" From d1a033b6afd93d2d36a176c6d0a91c0e85147e3e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 Aug 2024 00:30:47 -0700 Subject: [PATCH 085/109] Fix some Vulkan validation errors on macOS. (#420) --- .../renderer_vulkan/vk_instance.cpp | 27 ++++++++++++++++--- .../renderer_vulkan/vk_platform.cpp | 7 +++++ .../renderer_vulkan/vk_swapchain.cpp | 12 ++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index b60b78e13..66da030f1 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -164,7 +164,8 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceColorWriteEnableFeaturesEXT, vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR, - vk::PhysicalDeviceDepthClipControlFeaturesEXT, vk::PhysicalDeviceRobustness2FeaturesEXT>(); + vk::PhysicalDeviceDepthClipControlFeaturesEXT, vk::PhysicalDeviceRobustness2FeaturesEXT, + vk::PhysicalDevicePortabilitySubsetFeaturesKHR>(); const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties>(); @@ -198,7 +199,7 @@ bool Instance::CreateDevice() { external_memory_host = add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); custom_border_color = add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - add_extension(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); + const bool depth_clip_control = add_extension(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); add_extension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); workgroup_memory_explicit_layout = add_extension(VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); @@ -213,7 +214,7 @@ bool Instance::CreateDevice() { // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); - add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); + const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); const bool has_sync2 = add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); @@ -224,6 +225,11 @@ bool Instance::CreateDevice() { : false; } +#ifdef __APPLE__ + // Required by Vulkan spec if supported. + add_extension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif + const auto family_properties = physical_device.getQueueFamilyProperties(); if (family_properties.empty()) { LOG_CRITICAL(Render_Vulkan, "Physical device reported no queues."); @@ -324,12 +330,27 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceVertexInputDynamicStateFeaturesEXT{ .vertexInputDynamicState = true, }, +#ifdef __APPLE__ + feature_chain.get(), +#endif }; + if (!maintenance4) { + device_chain.unlink(); + } + if (!custom_border_color) { + device_chain.unlink(); + } if (!color_write_en) { device_chain.unlink(); device_chain.unlink(); } + if (!depth_clip_control) { + device_chain.unlink(); + } + if (!workgroup_memory_explicit_layout) { + device_chain.unlink(); + } if (robustness) { device_chain.get().nullDescriptor = feature_chain.get().nullDescriptor; diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 33113c58b..c73a8139d 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -157,6 +157,10 @@ std::vector GetInstanceExtensions(Frontend::WindowSystemType window break; } +#ifdef __APPLE__ + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); +#endif + if (window_type != Frontend::WindowSystemType::Headless) { extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); } @@ -285,6 +289,9 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT .ppEnabledLayerNames = layers.data(), .enabledExtensionCount = static_cast(extensions.size()), .ppEnabledExtensionNames = extensions.data(), +#ifdef __APPLE__ + .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, +#endif }, vk::LayerSettingsCreateInfoEXT{ .settingCount = layer_setings.size(), diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 20c99e302..16d5c2372 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -37,6 +37,16 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { instance.GetPresentQueueFamilyIndex(), }; + const auto modes = instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface); + const auto find_mode = [&modes](vk::PresentModeKHR requested) { + const auto it = + std::find_if(modes.begin(), modes.end(), + [&requested](vk::PresentModeKHR mode) { return mode == requested; }); + + return it != modes.end(); + }; + const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox); + const bool exclusive = queue_family_indices[0] == queue_family_indices[1]; const u32 queue_family_indices_count = exclusive ? 1u : 2u; const vk::SharingMode sharing_mode = @@ -55,7 +65,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { .pQueueFamilyIndices = queue_family_indices.data(), .preTransform = transform, .compositeAlpha = composite_alpha, - .presentMode = vk::PresentModeKHR::eMailbox, + .presentMode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate, .clipped = true, .oldSwapchain = nullptr, }; From bb159eafb9ff7662432529ffaeefdb745b17234f Mon Sep 17 00:00:00 2001 From: counter185 <33550839+counter185@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:54:08 +0200 Subject: [PATCH 086/109] Basic gamepad support through SDL (#407) * Add basic gamepad support through SDL * lightbar, vibration, code style changes * okay fine * one day clang format will finally pass --- src/core/libraries/pad/pad.cpp | 20 +++++++-- src/input/controller.cpp | 26 ++++++++++++ src/input/controller.h | 7 ++++ src/sdl_window.cpp | 75 ++++++++++++++++++++++++++++++++++ src/sdl_window.h | 4 ++ 5 files changed, 128 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index d39935503..c9e332d21 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -419,8 +419,14 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() { } int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) { - LOG_ERROR(Lib_Pad, "(STUBBED) called"); - return ORBIS_OK; + if (pParam != nullptr) { + LOG_INFO(Lib_Pad, "scePadSetLightBar called handle = {} rgb = {} {} {}", handle, pParam->r, + pParam->g, pParam->b); + auto* controller = Common::Singleton::Instance(); + controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b); + return ORBIS_OK; + } + return ORBIS_PAD_ERROR_INVALID_ARG; } int PS4_SYSV_ABI scePadSetLightBarBaseBrightness() { @@ -479,8 +485,14 @@ int PS4_SYSV_ABI scePadSetUserColor() { } int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) { - LOG_DEBUG(Lib_Pad, "(STUBBED) called"); - return ORBIS_OK; + if (pParam != nullptr) { + LOG_INFO(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, + pParam->smallMotor, pParam->largeMotor); + auto* controller = Common::Singleton::Instance(); + controller->SetVibration(pParam->smallMotor, pParam->largeMotor); + return ORBIS_OK; + } + return ORBIS_PAD_ERROR_INVALID_ARG; } int PS4_SYSV_ABI scePadSetVibrationForce() { diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 247e08ce8..4a3db1633 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "core/libraries/kernel/time_management.h" #include "core/libraries/pad/pad.h" #include "input/controller.h" @@ -117,4 +118,29 @@ void GameController::Axis(int id, Input::Axis axis, int value) { AddState(state); } +void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { + if (m_sdl_gamepad != nullptr) { + SDL_SetGamepadLED(m_sdl_gamepad, r, g, b); + } +} + +bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) { + if (m_sdl_gamepad != nullptr) { + return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF, + (largeMotor / 255.0f) * 0xFFFF, -1) == 0; + } + return true; +} + +void GameController::TryOpenSDLController() { + if (m_sdl_gamepad == nullptr || !SDL_GamepadConnected(m_sdl_gamepad)) { + int gamepad_count; + SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); + m_sdl_gamepad = gamepad_count > 0 ? SDL_OpenGamepad(gamepads[0]) : nullptr; + SDL_free(gamepads); + } + + SetLightBarRGB(0, 0, 255); +} + } // namespace Input diff --git a/src/input/controller.h b/src/input/controller.h index a16f7dd06..ef0991568 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -6,6 +6,8 @@ #include #include "common/types.h" +struct SDL_Gamepad; + namespace Input { enum class Axis { @@ -43,6 +45,9 @@ public: void CheckButton(int id, u32 button, bool isPressed); void AddState(const State& state); void Axis(int id, Input::Axis axis, int value); + void SetLightBarRGB(u8 r, u8 g, u8 b); + bool SetVibration(u8 smallMotor, u8 largeMotor); + void TryOpenSDLController(); private: struct StateInternal { @@ -57,6 +62,8 @@ private: u32 m_first_state = 0; std::array m_states; std::array m_private; + + SDL_Gamepad* m_sdl_gamepad = nullptr; }; } // namespace Input diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 5e1a4c952..9fd596699 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -43,6 +43,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_SetWindowFullscreen(window, Config::isFullscreenMode()); + SDL_InitSubSystem(SDL_INIT_GAMEPAD); + controller->TryOpenSDLController(); + #if defined(SDL_PLATFORM_WIN32) window_info.type = WindowSystemType::Windows; window_info.render_surface = SDL_GetPointerProperty(SDL_GetWindowProperties(window), @@ -92,6 +95,11 @@ void WindowSDL::waitEvent() { case SDL_EVENT_KEY_UP: onKeyPress(&event); break; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + onGamepadEvent(&event); + break; case SDL_EVENT_QUIT: is_open = false; break; @@ -276,4 +284,71 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { } } +void WindowSDL::onGamepadEvent(const SDL_Event* event) { + using Libraries::Pad::OrbisPadButtonDataOffset; + + u32 button = 0; + Input::Axis axis = Input::Axis::AxisMax; + switch (event->type) { + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + button = sdlGamepadToOrbisButton(event->gbutton.button); + if (button != 0) { + controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); + } + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + axis = event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX ? Input::Axis::LeftX + : event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFTY ? Input::Axis::LeftY + : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTX ? Input::Axis::RightX + : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTY ? Input::Axis::RightY + : event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ? Input::Axis::TriggerLeft + : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER ? Input::Axis::TriggerRight + : Input::Axis::AxisMax; + if (axis != Input::Axis::AxisMax) { + controller->Axis(0, axis, Input::GetAxis(-0x8000, 0x8000, event->gaxis.value)); + } + break; + } +} + +int WindowSDL::sdlGamepadToOrbisButton(u8 button) { + using Libraries::Pad::OrbisPadButtonDataOffset; + + switch (button) { + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT; + case SDL_GAMEPAD_BUTTON_SOUTH: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; + case SDL_GAMEPAD_BUTTON_NORTH: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; + case SDL_GAMEPAD_BUTTON_WEST: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; + case SDL_GAMEPAD_BUTTON_EAST: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; + case SDL_GAMEPAD_BUTTON_START: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; + case SDL_GAMEPAD_BUTTON_BACK: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3; + default: + return 0; + } +} + } // namespace Frontend diff --git a/src/sdl_window.h b/src/sdl_window.h index 02d011285..cf6c37116 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -7,6 +7,7 @@ #include "common/types.h" struct SDL_Window; +struct SDL_Gamepad; union SDL_Event; namespace Input { @@ -66,6 +67,9 @@ public: private: void onResize(); void onKeyPress(const SDL_Event* event); + void onGamepadEvent(const SDL_Event* event); + + int sdlGamepadToOrbisButton(u8 button); private: s32 width; From d8b9d82ffaa2e0931b154a646d1573e535bc951f Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:05:10 +0300 Subject: [PATCH 087/109] video_core: Various fixes (#423) * video_core: Various fixes * clang format --- src/core/libraries/kernel/libkernel.cpp | 1 - .../frontend/translate/translate.cpp | 2 +- .../ir/passes/resource_tracking_pass.cpp | 7 ++++ src/video_core/amdgpu/liverpool.cpp | 2 +- src/video_core/amdgpu/liverpool.h | 32 ++++++++++++++++++- .../renderer_vulkan/liverpool_to_vk.cpp | 2 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 8 +++++ 7 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index e2625819b..9657ba04b 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -360,7 +360,6 @@ int PS4_SYSV_ABI posix_connect() { } int PS4_SYSV_ABI _sigprocmask() { - LOG_DEBUG(Lib_Kernel, "STUBBED"); return ORBIS_OK; } diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 8ffde7fb3..d48e4defd 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -162,7 +162,7 @@ T Translator::GetSrc(const InstOperand& operand) { } } else { if (operand.input_modifier.abs) { - UNREACHABLE(); + LOG_WARNING(Render_Vulkan, "Input abs modifier on integer instruction"); } if (operand.input_modifier.neg) { UNREACHABLE(); diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index bacbac72a..e6d5c48c7 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -494,6 +494,13 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip const auto tsharp = TrackSharp(tsharp_handle); const auto image = info.ReadUd(tsharp.sgpr_base, tsharp.dword_offset); const auto inst_info = inst.Flags(); + if (!image.Valid()) { + LOG_ERROR(Render_Vulkan, "Shader compiled with unbound image!"); + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + inst.ReplaceUsesWith( + ir.CompositeConstruct(ir.Imm32(0.f), ir.Imm32(0.f), ir.Imm32(0.f), ir.Imm32(0.f))); + return; + } ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); u32 image_binding = descriptors.Add(ImageResource{ .sgpr_base = tsharp.sgpr_base, diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 517f9d53a..a9665a025 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -408,7 +408,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); + // ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); // Optimization: VO label waits are special because the emulator // will write to the label when presentation is finished. So if // there are no other submits to yield to we can sleep the thread diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 779e55368..98b4aba57 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -867,6 +867,33 @@ struct Liverpool { } }; + union ShaderStageEnable { + u32 raw; + BitField<0, 2, u32> ls_en; + BitField<2, 1, u32> hs_en; + BitField<3, 2, u32> es_en; + BitField<5, 1, u32> gs_en; + BitField<6, 1, u32> vs_en; + + bool IsStageEnabled(u32 stage) { + switch (stage) { + case 0: + case 1: + return true; + case 2: + return gs_en.Value(); + case 3: + return es_en.Value(); + case 4: + return hs_en.Value(); + case 5: + return ls_en.Value(); + default: + UNREACHABLE(); + } + } + }; + union Regs { struct { INSERT_PADDING_WORDS(0x2C08); @@ -945,7 +972,9 @@ struct Liverpool { INSERT_PADDING_WORDS(0xA2A8 - 0xA2A1 - 1); u32 vgt_instance_step_rate_0; u32 vgt_instance_step_rate_1; - INSERT_PADDING_WORDS(0xA2DF - 0xA2A9 - 1); + INSERT_PADDING_WORDS(0xA2D5 - 0xA2A9 - 1); + ShaderStageEnable stage_enable; + INSERT_PADDING_WORDS(9); PolygonOffset poly_offset; INSERT_PADDING_WORDS(0xA2F8 - 0xA2DF - 5); AaConfig aa_config; @@ -1140,6 +1169,7 @@ static_assert(GFX6_3D_REG_INDEX(index_buffer_type) == 0xA29F); static_assert(GFX6_3D_REG_INDEX(enable_primitive_id) == 0xA2A1); static_assert(GFX6_3D_REG_INDEX(vgt_instance_step_rate_0) == 0xA2A8); static_assert(GFX6_3D_REG_INDEX(vgt_instance_step_rate_1) == 0xA2A9); +static_assert(GFX6_3D_REG_INDEX(stage_enable) == 0xA2D5); static_assert(GFX6_3D_REG_INDEX(poly_offset) == 0xA2DF); static_assert(GFX6_3D_REG_INDEX(aa_config) == 0xA2F8); static_assert(GFX6_3D_REG_INDEX(color_buffers[0].base_address) == 0xA318); diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 01526265a..04e830c05 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -81,6 +81,8 @@ vk::PrimitiveTopology PrimitiveType(Liverpool::PrimitiveType type) { return vk::PrimitiveTopology::eTriangleListWithAdjacency; case Liverpool::PrimitiveType::AdjTriangleStrip: return vk::PrimitiveTopology::eTriangleStripWithAdjacency; + case Liverpool::PrimitiveType::PatchPrimitive: + return vk::PrimitiveTopology::ePatchList; case Liverpool::PrimitiveType::QuadList: // Needs to generate index buffer on the fly. return vk::PrimitiveTopology::eTriangleList; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 8a22b9256..38d1f51b2 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -115,6 +115,10 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, } const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { + // Tessellation is unsupported so skip the draw to avoid locking up the driver. + if (liverpool->regs.primitive_type == Liverpool::PrimitiveType::PatchPrimitive) { + return nullptr; + } RefreshGraphicsKey(); const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key); if (is_new) { @@ -203,6 +207,10 @@ void PipelineCache::RefreshGraphicsKey() { } for (u32 i = 0; i < MaxShaderStages; i++) { + if (!regs.stage_enable.IsStageEnabled(i)) { + key.stage_hashes[i] = 0; + continue; + } auto* pgm = regs.ProgramForStage(i); if (!pgm || !pgm->Address()) { key.stage_hashes[i] = 0; From ad3b6c793c379b0590a6ffed532c6be12b8bb099 Mon Sep 17 00:00:00 2001 From: Samuel Fontes <43213783+SamuelFontes@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:21:06 -0300 Subject: [PATCH 088/109] qt-gui: Added GPU device selection functionality (#399) * qt-gui: Added GPU device selection functionality * Getting list of GPU only when application starts * Fixed formatting * Fixed formatting * Fixed formatting * Added warning when GPU doesn't support API version. * Changed Unsupported Vulkan Version warning * Removed unused size checking on GetPhysicalDevices The method is only being called once so this doesn't make sense. It was some left over of me trying to get this done some other way. * Fix formatting * Fix formatting * SettingsDialog: Passing physical devices as span * Fixed formatting --- src/common/config.cpp | 5 +++++ src/common/config.h | 1 + src/qt_gui/main_window.cpp | 17 ++++++++++++++++- src/qt_gui/main_window.h | 3 +++ src/qt_gui/settings_dialog.cpp | 15 +++++++++++++-- src/qt_gui/settings_dialog.h | 3 ++- 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 3cf9af150..a65a5b596 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -124,6 +124,10 @@ bool vkValidationGpuEnabled() { return vkValidationGpu; } +void setGpuId(s32 selectedGpuId) { + gpuId = selectedGpuId; +} + void setScreenWidth(u32 width) { screenWidth = width; } @@ -451,6 +455,7 @@ void setDefaultValues() { vkValidation = false; rdocEnable = false; m_language = 1; + gpuId = -1; } } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 37ace79c3..970550281 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -36,6 +36,7 @@ void setNullGpu(bool enable); void setDumpShaders(bool enable); void setDumpPM4(bool enable); void setVblankDiv(u32 value); +void setGpuId(s32 selectedGpuId); void setScreenWidth(u32 width); void setScreenHeight(u32 height); void setFullscreenMode(bool enable); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 55bd56402..aec2e7a5d 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -16,6 +16,7 @@ #include "game_install_dialog.h" #include "main_window.h" #include "settings_dialog.h" +#include "video_core/renderer_vulkan/vk_instance.h" MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -39,6 +40,7 @@ bool MainWindow::Init() { CreateConnects(); SetLastUsedTheme(); SetLastIconSizeBullet(); + GetPhysicalDevices(); // show ui setMinimumSize(350, minimumSizeHint().height()); setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION))); @@ -158,6 +160,19 @@ void MainWindow::LoadGameLists() { } } +void MainWindow::GetPhysicalDevices() { + Vulkan::Instance instance(false, false); + auto physical_devices = instance.GetPhysicalDevices(); + for (const vk::PhysicalDevice physical_device : physical_devices) { + auto prop = physical_device.getProperties(); + QString name = QString::fromUtf8(prop.deviceName, -1); + if (prop.apiVersion < Vulkan::TargetVulkanApiVersion) { + name += " * Unsupported Vulkan Version"; + } + m_physical_devices.push_back(name); + } +} + void MainWindow::CreateConnects() { connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize); connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); @@ -187,7 +202,7 @@ void MainWindow::CreateConnects() { &MainWindow::StartGame); connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { - auto settingsDialog = new SettingsDialog(this); + auto settingsDialog = new SettingsDialog(m_physical_devices, this); settingsDialog->exec(); }); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 39a5d049e..35fd0bf6c 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -54,6 +54,7 @@ private: void CreateActions(); void CreateRecentGameActions(); void CreateDockWindows(); + void GetPhysicalDevices(); void LoadGameLists(); void CreateConnects(); void SetLastUsedTheme(); @@ -79,6 +80,8 @@ private: QScopedPointer m_elf_viewer; // Status Bar. QScopedPointer statusBar; + // Available GPU devices + std::vector m_physical_devices; PSF psf; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 722abe7e0..bde0eadab 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -4,13 +4,20 @@ #include "settings_dialog.h" #include "ui_settings_dialog.h" -SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::SettingsDialog) { +SettingsDialog::SettingsDialog(std::span physical_devices, QWidget* parent) + : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); + // Add list of available GPUs + ui->graphicsAdapterBox->addItem("Auto Select"); // -1, auto selection + for (const auto& device : physical_devices) { + ui->graphicsAdapterBox->addItem(device); + } + LoadValuesFromConfig(); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); @@ -40,7 +47,10 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Se // GPU TAB { - // TODO: Implement graphics device changing + // First options is auto selection -1, so gpuId on the GUI will always have to subtract 1 + // when setting and add 1 when getting to select the correct gpu in Qt + connect(ui->graphicsAdapterBox, &QComboBox::currentIndexChanged, this, + [](int index) { Config::setGpuId(index - 1); }); connect(ui->widthSpinBox, &QSpinBox::valueChanged, this, [](int val) { Config::setScreenWidth(val); }); @@ -98,6 +108,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Se void SettingsDialog::LoadValuesFromConfig() { ui->consoleLanguageComboBox->setCurrentIndex(Config::GetLanguage()); + ui->graphicsAdapterBox->setCurrentIndex(Config::getGpuId() + 1); ui->widthSpinBox->setValue(Config::getScreenWidth()); ui->heightSpinBox->setValue(Config::getScreenHeight()); ui->vblankSpinBox->setValue(Config::vblankDiv()); diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 2bffa795c..7d8701093 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -16,7 +17,7 @@ class SettingsDialog; class SettingsDialog : public QDialog { Q_OBJECT public: - explicit SettingsDialog(QWidget* parent = nullptr); + explicit SettingsDialog(std::span physical_devices, QWidget* parent = nullptr); ~SettingsDialog(); int exec() override; From 27cb218584d22231766079054a0157686b58a90c Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:36:11 +0200 Subject: [PATCH 089/109] video_core: CPU flip relay (#415) * video_core: cpu flip is propagated via gpu thread now * tentative fix for cpu flips racing * libraries: videoout: better flip status handling --- src/core/libraries/videoout/driver.cpp | 66 +++++++++++++------ src/core/libraries/videoout/driver.h | 3 +- src/core/libraries/videoout/video_out.cpp | 5 +- src/video_core/amdgpu/liverpool.cpp | 22 ++++++- src/video_core/amdgpu/liverpool.h | 11 ++++ .../renderer_vulkan/renderer_vulkan.h | 10 ++- .../texture_cache/texture_cache.cpp | 8 ++- src/video_core/texture_cache/texture_cache.h | 6 +- 8 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 97b1816e5..25de48a4d 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -9,6 +9,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time_management.h" #include "core/libraries/videoout/driver.h" +#include "core/platform.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" extern std::unique_ptr renderer; @@ -173,14 +174,19 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { // Update flip status. auto* port = req.port; - auto& flip_status = port->flip_status; - flip_status.count++; - flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); - flip_status.tsc = Libraries::Kernel::sceKernelReadTsc(); - flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc(); - flip_status.flipArg = req.flip_arg; - flip_status.currentBuffer = req.index; - flip_status.flipPendingNum = static_cast(requests.size()); + { + std::unique_lock lock{port->port_mutex}; + auto& flip_status = port->flip_status; + flip_status.count++; + flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); + flip_status.tsc = Libraries::Kernel::sceKernelReadTsc(); + flip_status.flipArg = req.flip_arg; + flip_status.currentBuffer = req.index; + if (req.eop) { + --flip_status.gcQueueNum; + } + --flip_status.flipPendingNum; + } // Trigger flip events for the port. for (auto& event : port->flip_events) { @@ -202,34 +208,54 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { + { + std::unique_lock lock{port->port_mutex}; + if (index != -1 && port->flip_status.flipPendingNum >= port->NumRegisteredBuffers()) { + LOG_ERROR(Lib_VideoOut, "Flip queue is full"); + return false; + } + + if (is_eop) { + ++port->flip_status.gcQueueNum; + } + ++port->flip_status.flipPendingNum; // integral GPU and CPU pending flips counter + port->flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc(); + } + + if (!is_eop) { + // Before processing the flip we need to ask GPU thread to flush command list as at this + // point VO surface is ready to be presented, and we will need have an actual state of + // Vulkan image at the time of frame presentation. + liverpool->SendCommand([=, this]() { + renderer->FlushDraw(); + SubmitFlipInternal(port, index, flip_arg, is_eop); + }); + } else { + SubmitFlipInternal(port, index, flip_arg, is_eop); + } + + return true; +} + +void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, + bool is_eop /*= false*/) { Vulkan::Frame* frame; if (index == -1) { - frame = renderer->PrepareBlankFrame(); + frame = renderer->PrepareBlankFrame(is_eop); } else { const auto& buffer = port->buffer_slots[index]; const auto& group = port->groups[buffer.group_index]; frame = renderer->PrepareFrame(group, buffer.address_left, is_eop); } - if (index != -1 && requests.size() >= port->NumRegisteredBuffers()) { - LOG_ERROR(Lib_VideoOut, "Flip queue is full"); - return false; - } - std::scoped_lock lock{mutex}; requests.push({ .frame = frame, .port = port, .index = index, .flip_arg = flip_arg, - .submit_tsc = Libraries::Kernel::sceKernelReadTsc(), .eop = is_eop, }); - - port->flip_status.flipPendingNum = static_cast(requests.size()); - port->flip_status.gcQueueNum = 0; - - return true; } void VideoOutDriver::PresentThread(std::stop_token token) { diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 104056ded..bee800602 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -29,6 +29,7 @@ struct VideoOutPort { std::vector flip_events; std::vector vblank_events; std::mutex vo_mutex; + std::mutex port_mutex; std::condition_variable vo_cv; std::condition_variable vblank_cv; int flip_rate = 0; @@ -93,7 +94,6 @@ private: VideoOutPort* port; s32 index; s64 flip_arg; - u64 submit_tsc; bool eop; operator bool() const noexcept { @@ -102,6 +102,7 @@ private: }; std::chrono::microseconds Flip(const Request& req); + void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); std::mutex mutex; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 15e14662a..acfcbad42 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -113,7 +113,9 @@ s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) { s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) { LOG_INFO(Lib_VideoOut, "called"); - s32 pending = driver->GetPort(handle)->flip_status.flipPendingNum; + auto* port = driver->GetPort(handle); + std::unique_lock lock{port->port_mutex}; + s32 pending = port->flip_status.flipPendingNum; return pending; } @@ -161,6 +163,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } + std::unique_lock lock{port->port_mutex}; *status = port->flip_status; LOG_INFO(Lib_VideoOut, diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index a9665a025..dce2d4b42 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -35,7 +35,7 @@ void Liverpool::Process(std::stop_token stoken) { { std::unique_lock lk{submit_mutex}; Common::CondvarWait(submit_cv, lk, stoken, - [this] { return num_submits != 0 || submit_done; }); + [this] { return num_commands || num_submits || submit_done; }); } if (stoken.stop_requested()) { break; @@ -45,7 +45,23 @@ void Liverpool::Process(std::stop_token stoken) { int qid = -1; - while (num_submits) { + while (num_submits || num_commands) { + + // Process incoming commands with high priority + while (num_commands) { + + Common::UniqueFunction callback{}; + { + std::unique_lock lk{submit_mutex}; + callback = std::move(command_queue.back()); + command_queue.pop(); + } + + callback(); + + --num_commands; + } + qid = (qid + 1) % NumTotalQueues; auto& queue = mapped_queues[qid]; @@ -219,7 +235,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span #include #include + #include "common/assert.h" #include "common/bit_field.h" #include "common/polyfill_thread.h" #include "common/types.h" +#include "common/unique_function.h" #include "video_core/amdgpu/pixel_format.h" #include "video_core/amdgpu/resource.h" @@ -1054,6 +1056,13 @@ public: rasterizer = rasterizer_; } + void SendCommand(Common::UniqueFunction&& func) { + std::scoped_lock lk{submit_mutex}; + command_queue.emplace(std::move(func)); + ++num_commands; + submit_cv.notify_one(); + } + private: struct Task { struct promise_type { @@ -1122,9 +1131,11 @@ private: Libraries::VideoOut::VideoOutPort* vo_port{}; std::jthread process_thread{}; std::atomic num_submits{}; + std::atomic num_commands{}; std::atomic submit_done{}; std::mutex submit_mutex; std::condition_variable_any submit_cv; + std::queue> command_queue{}; }; static_assert(GFX6_3D_REG_INDEX(ps_program) == 0x2C08); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 113b380eb..eab9d527c 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -48,13 +48,14 @@ public: VAddr cpu_address, bool is_eop) { const auto info = VideoCore::ImageInfo{attribute, cpu_address}; const auto image_id = texture_cache.FindImage(info); + texture_cache.UpdateImage(image_id, is_eop ? nullptr : &flip_scheduler); auto& image = texture_cache.GetImage(image_id); return PrepareFrameInternal(image, is_eop); } - Frame* PrepareBlankFrame() { + Frame* PrepareBlankFrame(bool is_eop) { auto& image = texture_cache.GetImage(VideoCore::NULL_IMAGE_ID); - return PrepareFrameInternal(image, true); + return PrepareFrameInternal(image, is_eop); } VideoCore::Image& RegisterVideoOutSurface( @@ -75,6 +76,11 @@ public: void Present(Frame* frame); void RecreateFrame(Frame* frame, u32 width, u32 height); + void FlushDraw() { + SubmitInfo info{}; + draw_scheduler.Flush(info); + } + private: Frame* PrepareFrameInternal(VideoCore::Image& image, bool is_eop = true); Frame* GetRenderFrame(); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 6b14faac4..6bc893b09 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -223,7 +223,7 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, return RegisterImageView(image_id, view_info); } -void TextureCache::RefreshImage(Image& image) { +void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler /*= nullptr*/) { // Mark image as validated. image.flags &= ~ImageFlagBits::CpuModified; @@ -269,8 +269,10 @@ void TextureCache::RefreshImage(Image& image) { return; } - scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); + auto* sched_ptr = custom_scheduler ? custom_scheduler : &scheduler; + sched_ptr->EndRendering(); + + const auto cmdbuf = sched_ptr->CommandBuffer(); image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite, cmdbuf); const VAddr image_addr = image.info.guest_address; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index b3af0ff11..137b60141 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -59,17 +59,17 @@ public: const ImageViewInfo& view_info); /// Updates image contents if it was modified by CPU. - void UpdateImage(ImageId image_id) { + void UpdateImage(ImageId image_id, Vulkan::Scheduler* custom_scheduler = nullptr) { Image& image = slot_images[image_id]; if (False(image.flags & ImageFlagBits::CpuModified)) { return; } - RefreshImage(image); + RefreshImage(image, custom_scheduler); TrackImage(image, image_id); } /// Reuploads image contents. - void RefreshImage(Image& image); + void RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler = nullptr); /// Retrieves the sampler that matches the provided S# descriptor. [[nodiscard]] vk::Sampler GetSampler(const AmdGpu::Sampler& sampler); From 6cc4a682fdeb4cee15120d05826c0f70681d10fc Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:18:46 +0200 Subject: [PATCH 090/109] core/memory: Fix error on virtual queries of reserved regions --- src/core/memory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index eed5126c0..6d0d581f9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -273,10 +273,10 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, std::scoped_lock lk{mutex}; auto it = FindVMA(addr); - if (!it->second.IsMapped() && flags == 1) { + if (it->second.type == VMAType::Free && flags == 1) { it++; } - if (!it->second.IsMapped()) { + if (it->second.type == VMAType::Free) { LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region"); return ORBIS_KERNEL_ERROR_EACCES; } From d332a5e6116491cd36603bae4f6bdb9d2723dd16 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:01:17 +0300 Subject: [PATCH 091/109] spirv: Simplify shared memory handling (#427) * spirv: Simplify shared memory handling * spirv: Ignore clip plane * spirv: Fix image offsets * ir_pass: Implement shared memory lowering pass * NVIDIA doesn't like using shared mem in fragment shader and softlocks driver * spirv: Add log for ignoring pos1 --- CMakeLists.txt | 1 + .../spirv/emit_spirv_context_get_set.cpp | 4 + .../backend/spirv/emit_spirv_image.cpp | 102 +++++++++------ .../backend/spirv/emit_spirv_instructions.h | 25 ++-- .../spirv/emit_spirv_shared_memory.cpp | 122 ++---------------- .../backend/spirv/spirv_emit_context.cpp | 34 ----- .../frontend/translate/vector_memory.cpp | 4 +- src/shader_recompiler/ir/ir_emitter.cpp | 18 +-- src/shader_recompiler/ir/ir_emitter.h | 13 +- src/shader_recompiler/ir/microinstruction.cpp | 2 - src/shader_recompiler/ir/opcodes.inc | 18 +-- src/shader_recompiler/ir/passes/ir_passes.h | 1 + .../passes/lower_shared_mem_to_registers.cpp | 39 ++++++ .../ir/passes/resource_tracking_pass.cpp | 56 ++++---- .../ir/passes/shader_info_collection_pass.cpp | 12 -- src/shader_recompiler/recompiler.cpp | 3 + src/shader_recompiler/runtime_info.h | 2 - 17 files changed, 182 insertions(+), 274 deletions(-) create mode 100644 src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b92dd9328..9153197cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -421,6 +421,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/ir/passes/dead_code_elimination_pass.cpp src/shader_recompiler/ir/passes/identity_removal_pass.cpp src/shader_recompiler/ir/passes/ir_passes.h + src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp src/shader_recompiler/ir/passes/resource_tracking_pass.cpp src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp 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 5eae058ac..02600b940 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 @@ -214,6 +214,10 @@ Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp) { } void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 element) { + if (attr == IR::Attribute::Position1) { + LOG_WARNING(Render_Vulkan, "Ignoring pos1 export"); + return; + } const Id pointer{OutputAttrPointer(ctx, attr, element)}; ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value)); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 72a603270..5526e5411 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -17,113 +17,133 @@ struct ImageOperands { operands.push_back(value); } + void AddOffset(EmitContext& ctx, const IR::Value& offset, + bool can_use_runtime_offsets = false) { + if (offset.IsEmpty()) { + return; + } + if (offset.IsImmediate()) { + const s32 operand = offset.U32(); + Add(spv::ImageOperandsMask::ConstOffset, ctx.ConstS32(operand)); + return; + } + IR::Inst* const inst{offset.InstRecursive()}; + if (inst->AreAllArgsImmediates()) { + switch (inst->GetOpcode()) { + case IR::Opcode::CompositeConstructU32x2: + Add(spv::ImageOperandsMask::ConstOffset, + ctx.ConstS32(static_cast(inst->Arg(0).U32()), + static_cast(inst->Arg(1).U32()))); + return; + case IR::Opcode::CompositeConstructU32x3: + Add(spv::ImageOperandsMask::ConstOffset, + ctx.ConstS32(static_cast(inst->Arg(0).U32()), + static_cast(inst->Arg(1).U32()), + static_cast(inst->Arg(2).U32()))); + return; + default: + break; + } + } + if (can_use_runtime_offsets) { + Add(spv::ImageOperandsMask::Offset, ctx.Def(offset)); + } else { + LOG_WARNING(Render_Vulkan, + "Runtime offset provided to unsupported image sample instruction"); + } + } + spv::ImageOperandsMask mask{}; boost::container::static_vector operands; }; Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, - Id offset) { + const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - if (Sirit::ValidId(bias)) { - operands.Add(spv::ImageOperandsMask::Bias, bias); - } - if (Sirit::ValidId(offset)) { - operands.Add(spv::ImageOperandsMask::Offset, offset); - } + operands.Add(spv::ImageOperandsMask::Bias, bias); + operands.AddOffset(ctx, offset); return ctx.OpImageSampleImplicitLod(ctx.F32[4], sampled_image, coords, operands.mask, operands.operands); } Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, - Id offset) { + const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - if (Sirit::ValidId(lod)) { - operands.Add(spv::ImageOperandsMask::Lod, lod); - } - if (Sirit::ValidId(offset)) { - operands.Add(spv::ImageOperandsMask::Offset, offset); - } + operands.Add(spv::ImageOperandsMask::Lod, lod); + operands.AddOffset(ctx, offset); return ctx.OpImageSampleExplicitLod(ctx.F32[4], sampled_image, coords, operands.mask, operands.operands); } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias, Id offset) { + Id bias, const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - if (Sirit::ValidId(bias)) { - operands.Add(spv::ImageOperandsMask::Bias, bias); - } - if (Sirit::ValidId(offset)) { - operands.Add(spv::ImageOperandsMask::Offset, offset); - } + operands.Add(spv::ImageOperandsMask::Bias, bias); + operands.AddOffset(ctx, offset); return ctx.OpImageSampleDrefImplicitLod(ctx.F32[1], sampled_image, coords, dref, operands.mask, operands.operands); } Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id lod, Id offset) { + Id lod, const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - if (Sirit::ValidId(lod)) { - operands.Add(spv::ImageOperandsMask::Lod, lod); - } - if (Sirit::ValidId(offset)) { - operands.Add(spv::ImageOperandsMask::Offset, offset); - } + operands.AddOffset(ctx, offset); + operands.Add(spv::ImageOperandsMask::Lod, lod); return ctx.OpImageSampleDrefExplicitLod(ctx.F32[1], sampled_image, coords, dref, operands.mask, operands.operands); } -Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id offset2) { +Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, + const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); const u32 comp = inst->Flags().gather_comp.Value(); ImageOperands operands; - operands.Add(spv::ImageOperandsMask::Offset, offset); + operands.AddOffset(ctx, offset); return ctx.OpImageGather(ctx.F32[4], sampled_image, coords, ctx.ConstU32(comp), operands.mask, operands.operands); } -Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, - Id offset2, Id dref) { +Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, + const IR::Value& offset, Id dref) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - operands.Add(spv::ImageOperandsMask::Offset, offset); + operands.AddOffset(ctx, offset); return ctx.OpImageDrefGather(ctx.F32[4], sampled_image, coords, dref, operands.mask, operands.operands); } -Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id lod, - Id ms) { +Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const IR::Value& offset, + Id lod, Id ms) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id result_type = texture.data_types->Get(4); - if (Sirit::ValidId(lod)) { - return ctx.OpBitcast(ctx.F32[4], ctx.OpImageFetch(result_type, image, coords, - spv::ImageOperandsMask::Lod, lod)); - } else { - return ctx.OpBitcast(ctx.F32[4], ctx.OpImageFetch(result_type, image, coords)); - } + ImageOperands operands; + operands.AddOffset(ctx, offset); + operands.Add(spv::ImageOperandsMask::Lod, lod); + return ctx.OpBitcast( + ctx.F32[4], ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands)); } Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 85c6eaac5..f868527f7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -93,15 +93,9 @@ Id EmitUndefU8(EmitContext& ctx); Id EmitUndefU16(EmitContext& ctx); Id EmitUndefU32(EmitContext& ctx); Id EmitUndefU64(EmitContext& ctx); -Id EmitLoadSharedU8(EmitContext& ctx, Id offset); -Id EmitLoadSharedS8(EmitContext& ctx, Id offset); -Id EmitLoadSharedU16(EmitContext& ctx, Id offset); -Id EmitLoadSharedS16(EmitContext& ctx, Id offset); Id EmitLoadSharedU32(EmitContext& ctx, Id offset); Id EmitLoadSharedU64(EmitContext& ctx, Id offset); Id EmitLoadSharedU128(EmitContext& ctx, Id offset); -void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value); -void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value); @@ -358,18 +352,19 @@ Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value); Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, - Id offset); + const IR::Value& offset); Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, - Id offset); + const IR::Value& offset); Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id bias, Id offset); + Id bias, const IR::Value& offset); Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, - Id lod, Id offset); -Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id offset2); -Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, - Id offset2, Id dref); -Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id offset, Id lod, - Id ms); + Id lod, const IR::Value& offset); +Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, + const IR::Value& offset); +Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, + const IR::Value& offset, Id dref); +Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const IR::Value& offset, + Id lod, Id ms); Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords); Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index 1582d9dd2..57ea476f1 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -5,99 +5,25 @@ #include "shader_recompiler/backend/spirv/spirv_emit_context.h" namespace Shader::Backend::SPIRV { -namespace { -Id Pointer(EmitContext& ctx, Id pointer_type, Id array, Id offset, u32 shift) { - const Id shift_id{ctx.ConstU32(shift)}; - const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - return ctx.OpAccessChain(pointer_type, array, ctx.u32_zero_value, index); -} -Id Word(EmitContext& ctx, Id offset) { +Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { const Id shift_id{ctx.ConstU32(2U)}; const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; return ctx.OpLoad(ctx.U32[1], pointer); } -std::pair ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count) { - const Id shift{ctx.OpShiftLeftLogical(ctx.U32[1], offset, ctx.ConstU32(3U))}; - const Id bit{ctx.OpBitwiseAnd(ctx.U32[1], shift, ctx.ConstU32(mask))}; - const Id count_id{ctx.ConstU32(count)}; - return {bit, count_id}; -} -} // Anonymous namespace - -Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; - return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); - } else { - const auto [bit, count]{ExtractArgs(ctx, offset, 24, 8)}; - return ctx.OpBitFieldUExtract(ctx.U32[1], Word(ctx, offset), bit, count); - } -} - -Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; - return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); - } else { - const auto [bit, count]{ExtractArgs(ctx, offset, 24, 8)}; - return ctx.OpBitFieldSExtract(ctx.U32[1], Word(ctx, offset), bit, count); - } -} - -Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; - return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); - } else { - const auto [bit, count]{ExtractArgs(ctx, offset, 16, 16)}; - return ctx.OpBitFieldUExtract(ctx.U32[1], Word(ctx, offset), bit, count); - } -} - -Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; - return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); - } else { - const auto [bit, count]{ExtractArgs(ctx, offset, 16, 16)}; - return ctx.OpBitFieldSExtract(ctx.U32[1], Word(ctx, offset), bit, count); - } -} - -Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; - return ctx.OpLoad(ctx.U32[1], pointer); - } else { - return Word(ctx, offset); - } -} - Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; - return ctx.OpLoad(ctx.U32[2], pointer); - } else { - const Id shift_id{ctx.ConstU32(2U)}; - const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; - const Id next_index{ctx.OpIAdd(ctx.U32[1], base_index, ctx.ConstU32(1U))}; - const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, base_index)}; - const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_index)}; - return ctx.OpCompositeConstruct(ctx.U32[2], ctx.OpLoad(ctx.U32[1], lhs_pointer), - ctx.OpLoad(ctx.U32[1], rhs_pointer)); - } + const Id shift_id{ctx.ConstU32(2U)}; + const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; + const Id next_index{ctx.OpIAdd(ctx.U32[1], base_index, ctx.ConstU32(1U))}; + const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, base_index)}; + const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_index)}; + return ctx.OpCompositeConstruct(ctx.U32[2], ctx.OpLoad(ctx.U32[1], lhs_pointer), + ctx.OpLoad(ctx.U32[1], rhs_pointer)); } Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; - return ctx.OpLoad(ctx.U32[4], pointer); - } const Id shift_id{ctx.ConstU32(2U)}; const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; std::array values{}; @@ -109,35 +35,14 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { return ctx.OpCompositeConstruct(ctx.U32[4], values); } -void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; - ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value)); -} - -void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { - const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; - ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); -} - void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { - Id pointer{}; - if (ctx.profile.support_explicit_workgroup_layout) { - pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); - } else { - const Id shift{ctx.ConstU32(2U)}; - const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; - pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset); - } + const Id shift{ctx.ConstU32(2U)}; + const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; + const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset); ctx.OpStore(pointer, value); } void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; - ctx.OpStore(pointer, value); - return; - } const Id shift{ctx.ConstU32(2U)}; const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; const Id next_offset{ctx.OpIAdd(ctx.U32[1], word_offset, ctx.ConstU32(1U))}; @@ -148,11 +53,6 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { - const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; - ctx.OpStore(pointer, value); - return; - } const Id shift{ctx.ConstU32(2U)}; const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)}; for (u32 i = 0; i < 4; ++i) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 55754d455..fef0666a2 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -513,43 +513,9 @@ void EmitContext::DefineSharedMemory() { if (info.shared_memory_size == 0) { info.shared_memory_size = DefaultSharedMemSize; } - const auto make{[&](Id element_type, u32 element_size) { - const u32 num_elements{Common::DivCeil(info.shared_memory_size, element_size)}; - const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; - Decorate(array_type, spv::Decoration::ArrayStride, element_size); - - const Id struct_type{TypeStruct(array_type)}; - MemberDecorate(struct_type, 0U, spv::Decoration::Offset, 0U); - Decorate(struct_type, spv::Decoration::Block); - - const Id pointer{TypePointer(spv::StorageClass::Workgroup, struct_type)}; - const Id element_pointer{TypePointer(spv::StorageClass::Workgroup, element_type)}; - const Id variable{AddGlobalVariable(pointer, spv::StorageClass::Workgroup)}; - Decorate(variable, spv::Decoration::Aliased); - interfaces.push_back(variable); - - return std::make_tuple(variable, element_pointer, pointer); - }}; - if (profile.support_explicit_workgroup_layout) { - AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); - AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); - if (info.uses_shared_u8) { - AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR); - std::tie(shared_memory_u8, shared_u8, std::ignore) = make(U8, 1); - } - if (info.uses_shared_u16) { - AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); - std::tie(shared_memory_u16, shared_u16, std::ignore) = make(U16, 2); - } - std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make(U32[1], 4); - std::tie(shared_memory_u32x2, shared_u32x2, std::ignore) = make(U32[2], 8); - std::tie(shared_memory_u32x4, shared_u32x4, std::ignore) = make(U32[4], 16); - return; - } const u32 num_elements{Common::DivCeil(info.shared_memory_size, 4U)}; const Id type{TypeArray(U32[1], ConstU32(num_elements))}; shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type); - shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]); shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup); interfaces.push_back(shared_memory_u32); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index bb202e426..f708b9fbc 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -250,10 +250,10 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) { const IR::Value texel = [&]() -> IR::Value { const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; if (!flags.test(MimgModifier::Pcf)) { - return ir.ImageGather(handle, body, offset, {}, info); + return ir.ImageGather(handle, body, offset, info); } ASSERT(mimg.dmask & 1); // should be always 1st (R) component - return ir.ImageGatherDref(handle, body, offset, {}, dref, info); + return ir.ImageGatherDref(handle, body, offset, dref, info); }(); // For gather4 instructions dmask selects which component to read and must have diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 08b7fbbc0..3ff347fb3 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -259,10 +259,6 @@ void IREmitter::SetAttribute(IR::Attribute attribute, const F32& value, u32 comp Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { switch (bit_size) { - case 8: - return Inst(is_signed ? Opcode::LoadSharedS8 : Opcode::LoadSharedU8, offset); - case 16: - return Inst(is_signed ? Opcode::LoadSharedS16 : Opcode::LoadSharedU16, offset); case 32: return Inst(Opcode::LoadSharedU32, offset); case 64: @@ -276,12 +272,6 @@ Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) { switch (bit_size) { - case 8: - Inst(Opcode::WriteSharedU8, offset, value); - break; - case 16: - Inst(Opcode::WriteSharedU16, offset, value); - break; case 32: Inst(Opcode::WriteSharedU32, offset, value); break; @@ -1398,13 +1388,13 @@ F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body } Value IREmitter::ImageGather(const Value& handle, const Value& coords, const Value& offset, - const Value& offset2, TextureInstInfo info) { - return Inst(Opcode::ImageGather, Flags{info}, handle, coords, offset, offset2); + TextureInstInfo info) { + return Inst(Opcode::ImageGather, Flags{info}, handle, coords, offset); } Value IREmitter::ImageGatherDref(const Value& handle, const Value& coords, const Value& offset, - const Value& offset2, const F32& dref, TextureInstInfo info) { - return Inst(Opcode::ImageGatherDref, Flags{info}, handle, coords, offset, offset2, dref); + const F32& dref, TextureInstInfo info) { + return Inst(Opcode::ImageGatherDref, Flags{info}, handle, coords, offset, dref); } Value IREmitter::ImageFetch(const Value& handle, const Value& coords, const Value& offset, diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index fda206394..c226edacf 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -256,18 +256,17 @@ public: const F32& dref, const U32& offset, TextureInstInfo info); - [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, - const IR::U1& skip_mips); - [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, - const IR::U1& skip_mips, TextureInstInfo info); + [[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod, + const U1& skip_mips); + [[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod, + const U1& skip_mips, TextureInstInfo info); [[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info); [[nodiscard]] Value ImageGather(const Value& handle, const Value& coords, const Value& offset, - const Value& offset2, TextureInstInfo info); + TextureInstInfo info); [[nodiscard]] Value ImageGatherDref(const Value& handle, const Value& coords, - const Value& offset, const Value& offset2, const F32& dref, - TextureInstInfo info); + const Value& offset, const F32& dref, TextureInstInfo info); [[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset, const U32& lod, const U32& multisampling, TextureInstInfo info); [[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords, diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index aa03e3d6e..5d413c8a7 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -59,8 +59,6 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::WriteSharedU128: case Opcode::WriteSharedU64: case Opcode::WriteSharedU32: - case Opcode::WriteSharedU16: - case Opcode::WriteSharedU8: case Opcode::ImageWrite: case Opcode::ImageAtomicIAdd32: case Opcode::ImageAtomicSMin32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 46918bc39..0e25b777c 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -26,15 +26,9 @@ OPCODE(WorkgroupMemoryBarrier, Void, OPCODE(DeviceMemoryBarrier, Void, ) // Shared memory operations -OPCODE(LoadSharedU8, U32, U32, ) -OPCODE(LoadSharedS8, U32, U32, ) -OPCODE(LoadSharedU16, U32, U32, ) -OPCODE(LoadSharedS16, U32, U32, ) OPCODE(LoadSharedU32, U32, U32, ) OPCODE(LoadSharedU64, U32x2, U32, ) OPCODE(LoadSharedU128, U32x4, U32, ) -OPCODE(WriteSharedU8, Void, U32, U32, ) -OPCODE(WriteSharedU16, Void, U32, U32, ) OPCODE(WriteSharedU32, Void, U32, U32, ) OPCODE(WriteSharedU64, Void, U32, U32x2, ) OPCODE(WriteSharedU128, Void, U32, U32x4, ) @@ -298,12 +292,12 @@ OPCODE(ConvertU16U32, U16, U32, OPCODE(ConvertU32U16, U32, U16, ) // Image operations -OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, U32, ) -OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, U32, ) -OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, U32, ) -OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, U32, ) -OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, Opaque, ) -OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, Opaque, F32, ) +OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) +OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, Opaque, ) +OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, Opaque, ) +OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, Opaque, ) +OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, ) +OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, ) diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index bf2ba4d66..7e2b962b5 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -14,5 +14,6 @@ void DeadCodeEliminationPass(IR::Program& program); void ConstantPropagationPass(IR::BlockList& program); void ResourceTrackingPass(IR::Program& program); void CollectShaderInfoPass(IR::Program& program); +void LowerSharedMemToRegisters(IR::Program& program); } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp new file mode 100644 index 000000000..a87cf31b1 --- /dev/null +++ b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "shader_recompiler/ir/program.h" + +namespace Shader::Optimization { + +void LowerSharedMemToRegisters(IR::Program& program) { + boost::container::small_vector ds_writes; + Info& info{program.info}; + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + const auto opcode = inst.GetOpcode(); + if (opcode == IR::Opcode::WriteSharedU32 || opcode == IR::Opcode::WriteSharedU64) { + ds_writes.emplace_back(&inst); + continue; + } + if (opcode == IR::Opcode::LoadSharedU32 || opcode == IR::Opcode::LoadSharedU64) { + // Search for write instruction with same offset + const IR::Inst* prod = inst.Arg(0).InstRecursive(); + const auto it = std::ranges::find_if(ds_writes, [&](const IR::Inst* write) { + const IR::Inst* write_prod = write->Arg(0).InstRecursive(); + return write_prod->Arg(1).U32() == prod->Arg(1).U32() && + write_prod->Arg(0) == prod->Arg(0); + }); + ASSERT(it != ds_writes.end()); + // Replace data read with value written. + inst.ReplaceUsesWith((*it)->Arg(1)); + } + } + } + // We should have eliminated everything. Invalidate data write instructions. + for (const auto inst : ds_writes) { + inst->Invalidate(); + } +} + +} // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index e6d5c48c7..b3d2311e2 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -171,6 +171,22 @@ bool IsImageStorageInstruction(const IR::Inst& inst) { } } +u32 ImageOffsetArgumentPosition(const IR::Inst& inst) { + switch (inst.GetOpcode()) { + case IR::Opcode::ImageGather: + case IR::Opcode::ImageGatherDref: + return 2; + case IR::Opcode::ImageSampleExplicitLod: + case IR::Opcode::ImageSampleImplicitLod: + return 3; + case IR::Opcode::ImageSampleDrefExplicitLod: + case IR::Opcode::ImageSampleDrefImplicitLod: + return 4; + default: + UNREACHABLE(); + } +} + class Descriptors { public: explicit Descriptors(Info& info_) @@ -574,33 +590,29 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip if (inst_info.has_offset) { // The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. - const u32 arg_pos = [&]() -> u32 { - switch (inst.GetOpcode()) { - case IR::Opcode::ImageGather: - case IR::Opcode::ImageGatherDref: - return 2; - case IR::Opcode::ImageSampleExplicitLod: - case IR::Opcode::ImageSampleImplicitLod: - return 3; - case IR::Opcode::ImageSampleDrefExplicitLod: - case IR::Opcode::ImageSampleDrefImplicitLod: - return 4; - default: - break; - } - return inst_info.is_depth ? 4 : 3; - }(); + const u32 arg_pos = ImageOffsetArgumentPosition(inst); const IR::Value arg = inst.Arg(arg_pos); ASSERT_MSG(arg.Type() == IR::Type::U32, "Unexpected offset type"); - const auto f = [&](IR::Value value, u32 offset) -> auto { + + const auto read = [&](u32 offset) -> auto { return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(offset), ir.Imm32(6), true); }; - const auto x = f(arg, 0); - const auto y = f(arg, 8); - const auto z = f(arg, 16); - const IR::Value value = ir.CompositeConstruct(x, y, z); - inst.SetArg(arg_pos, value); + switch (image.GetType()) { + case AmdGpu::ImageType::Color1D: + case AmdGpu::ImageType::Color1DArray: + inst.SetArg(arg_pos, read(0)); + break; + case AmdGpu::ImageType::Color2D: + case AmdGpu::ImageType::Color2DArray: + inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8))); + break; + case AmdGpu::ImageType::Color3D: + inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8), read(16))); + break; + default: + UNREACHABLE(); + } } if (inst_info.has_lod_clamp) { 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 7100b3844..52087a653 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -16,18 +16,6 @@ void Visit(Info& info, IR::Inst& inst) { info.stores.Set(inst.Arg(0).Attribute(), inst.Arg(2).U32()); break; } - case IR::Opcode::LoadSharedS8: - case IR::Opcode::LoadSharedU8: - case IR::Opcode::WriteSharedU8: - info.uses_shared_u8 = true; - info.uses_shared = true; - break; - case IR::Opcode::LoadSharedS16: - case IR::Opcode::LoadSharedU16: - case IR::Opcode::WriteSharedU16: - info.uses_shared_u16 = true; - info.uses_shared = true; - break; case IR::Opcode::LoadSharedU32: case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU32: diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 69eec50f3..0f9fd6d41 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -58,6 +58,9 @@ IR::Program TranslateProgram(Common::ObjectPool& inst_pool, Shader::Optimization::SsaRewritePass(program.post_order_blocks); Shader::Optimization::ResourceTrackingPass(program); Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); + if (program.info.stage != Stage::Compute) { + Shader::Optimization::LowerSharedMemToRegisters(program); + } Shader::Optimization::IdentityRemovalPass(program.blocks); Shader::Optimization::DeadCodeEliminationPass(program); Shader::Optimization::CollectShaderInfoPass(program); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index b936e06aa..9b592e128 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -195,8 +195,6 @@ struct Info { bool has_image_query{}; bool uses_group_quad{}; bool uses_shared{}; - bool uses_shared_u8{}; - bool uses_shared_u16{}; bool uses_fp16{}; bool uses_step_rates{}; bool translation_failed{}; // indicates that shader has unsupported instructions From 5f963772a07a5dc93b64fd98d3693c125295acfe Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:43:00 -0500 Subject: [PATCH 092/109] scePthreadAttrSetstack implementation (#391) * scePthreadAttrSetstack implementation Used by Final Fantasy XV * Address Comments Verify parameters before calling the pthread_attr_setstack function. Swap uses of SCE prefix with ORBIS prefix. * Quick fix Addresses the newest review and appears to fix issues caused in games by my previous commit. --- src/core/libraries/kernel/thread_management.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index cdd729da6..6319b7c2f 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -1094,6 +1094,19 @@ int PS4_SYSV_ABI scePthreadAttrGetstack(ScePthreadAttr* attr, void** addr, size_ return SCE_KERNEL_ERROR_EINVAL; } +int PS4_SYSV_ABI scePthreadAttrSetstack(ScePthreadAttr* attr, void* addr, size_t size) { + if (attr == nullptr || *attr == nullptr || addr == nullptr || size < 0x4000) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + int result = pthread_attr_setstack(&(*attr)->pth_attr, addr, size); + LOG_INFO(Kernel_Pthread, "scePthreadAttrSetstack: result = {}", result); + + if (result == 0) { + return ORBIS_OK; + } + return ORBIS_KERNEL_ERROR_EINVAL; +} + int PS4_SYSV_ABI scePthreadJoin(ScePthread thread, void** res) { int result = pthread_join(thread->pth, res); LOG_INFO(Kernel_Pthread, "scePthreadJoin result = {}", result); @@ -1550,6 +1563,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("B5GmVDKwpn0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_yield); LIB_FUNCTION("-quPa4SEJUw", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstack); + LIB_FUNCTION("Bvn74vj6oLo", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetstack); LIB_FUNCTION("Ru36fiTtJzA", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstackaddr); LIB_FUNCTION("-fA+7ZlGDQs", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstacksize); LIB_FUNCTION("14bOACANTBo", "libkernel", 1, "libkernel", 1, 1, scePthreadOnce); From 6f4e1a47b9caa534956f2206533151c6cbe23bb8 Mon Sep 17 00:00:00 2001 From: Dzmitry Dubrova Date: Wed, 14 Aug 2024 21:37:05 +0300 Subject: [PATCH 093/109] core: misc changes (#430) * core: misc changes * video_core: add some formats for detiling * clang format --- .../libraries/kernel/memory_management.cpp | 13 +++++++++++-- src/core/libraries/np_manager/np_manager.cpp | 7 +++++-- src/core/libraries/np_manager/np_manager.h | 18 +++++++++++++++++- .../backend/spirv/spirv_emit_context.cpp | 4 ++++ .../renderer_vulkan/liverpool_to_vk.cpp | 4 ++++ .../renderer_vulkan/vk_swapchain.cpp | 1 + src/video_core/texture_cache/tile_manager.cpp | 2 ++ 7 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 54c5860f4..826d47973 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -74,13 +74,22 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE size_t* sizeOut) { LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", searchStart, searchEnd, alignment); + + if (searchEnd <= searchStart) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + if (searchEnd > SCE_KERNEL_MAIN_DMEM_SIZE) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + auto* memory = Core::Memory::Instance(); PAddr physAddr; - s32 size = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, sizeOut); + s32 result = + memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, sizeOut); *physAddrOut = static_cast(physAddr); - return size; + return result; } s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index 33308abc1..fd4e31f54 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -974,8 +974,11 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetNpId() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId) { + LOG_ERROR(Lib_NpManager, "(DUMMY) called"); + + std::string name = "shadps4"; + strcpy(npId->handle.data, name.c_str()); return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 5b11355ad..5955a40b4 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -11,6 +11,22 @@ class SymbolsResolver; namespace Libraries::NpManager { +constexpr int ORBIS_NP_ONLINEID_MAX_LENGTH = 16; + +typedef int OrbisUserServiceUserId; + +struct OrbisNpOnlineId { + char data[ORBIS_NP_ONLINEID_MAX_LENGTH]; + char term; + char dummy[3]; +}; + +struct OrbisNpId { + OrbisNpOnlineId handle; + u8 opt[8]; + u8 reserved[8]; +}; + int PS4_SYSV_ABI Func_EF4378573542A508(); int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromKernel(); int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromPool(); @@ -204,7 +220,7 @@ int PS4_SYSV_ABI sceNpGetAccountLanguage2(); int PS4_SYSV_ABI sceNpGetAccountLanguageA(); int PS4_SYSV_ABI sceNpGetGamePresenceStatus(); int PS4_SYSV_ABI sceNpGetGamePresenceStatusA(); -int PS4_SYSV_ABI sceNpGetNpId(); +int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId); int PS4_SYSV_ABI sceNpGetNpReachabilityState(); int PS4_SYSV_ABI sceNpGetOnlineId(); int PS4_SYSV_ABI sceNpGetParentalControlInfo(); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index fef0666a2..4b732ecd4 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -407,6 +407,10 @@ spv::ImageFormat GetFormat(const AmdGpu::Image& image) { image.GetNumberFmt() == AmdGpu::NumberFormat::Float) { return spv::ImageFormat::Rgba16f; } + if (image.GetDataFmt() == AmdGpu::DataFormat::Format16_16_16_16 && + image.GetNumberFmt() == AmdGpu::NumberFormat::Unorm) { + return spv::ImageFormat::Rgba16; + } if (image.GetDataFmt() == AmdGpu::DataFormat::Format8 && image.GetNumberFmt() == AmdGpu::NumberFormat::Unorm) { return spv::ImageFormat::R8; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 04e830c05..4fc32ab28 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -341,6 +341,7 @@ std::span GetAllFormats() { vk::Format::eR32Sint, vk::Format::eR32Uint, vk::Format::eBc6HUfloatBlock, + vk::Format::eBc6HSfloatBlock, vk::Format::eR16G16Unorm, vk::Format::eR16G16B16A16Sscaled, vk::Format::eR16G16Sscaled, @@ -542,6 +543,9 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu if (data_format == AmdGpu::DataFormat::FormatBc6 && num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eBc6HUfloatBlock; } + if (data_format == AmdGpu::DataFormat::FormatBc6 && num_format == AmdGpu::NumberFormat::Snorm) { + return vk::Format::eBc6HSfloatBlock; + } if (data_format == AmdGpu::DataFormat::Format8_8_8_8 && num_format == AmdGpu::NumberFormat::Sint) { return vk::Format::eR8G8B8A8Sint; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 16d5c2372..dcc19bf3b 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -93,6 +93,7 @@ bool Swapchain::AcquireNextImage() { case vk::Result::eSuboptimalKHR: case vk::Result::eErrorSurfaceLostKHR: case vk::Result::eErrorOutOfDateKHR: + case vk::Result::eErrorUnknown: needs_recreation = true; break; default: diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 6447fde17..f08f2094c 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -186,6 +186,7 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eR32Sfloat: case vk::Format::eR32Uint: case vk::Format::eR16G16Sfloat: + case vk::Format::eR16G16Unorm: return vk::Format::eR32Uint; case vk::Format::eBc1RgbaSrgbBlock: case vk::Format::eBc1RgbaUnormBlock: @@ -193,6 +194,7 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eR32G32Sfloat: case vk::Format::eR32G32Uint: case vk::Format::eR16G16B16A16Unorm: + case vk::Format::eR16G16B16A16Uint: case vk::Format::eR16G16B16A16Sfloat: return vk::Format::eR32G32Uint; case vk::Format::eBc2SrgbBlock: From 9adc6382201abf609bebb69e841a3de80b15554e Mon Sep 17 00:00:00 2001 From: psucien <168137814+psucien@users.noreply.github.com> Date: Thu, 15 Aug 2024 00:15:07 +0200 Subject: [PATCH 094/109] shader_recompiler: basic implementation of `BUFFER_STORE_FORMAT_` (#431) * shader_recompiler: basic implementation of buffer store w\ fmt conversion * added `Format16` dfmt --- .../spirv/emit_spirv_context_get_set.cpp | 92 +++++++++++++++++++ .../backend/spirv/emit_spirv_instructions.h | 4 + .../frontend/translate/translate.h | 2 +- .../frontend/translate/vector_memory.cpp | 35 +++++-- src/shader_recompiler/ir/ir_emitter.cpp | 20 ++++ src/shader_recompiler/ir/ir_emitter.h | 2 + src/shader_recompiler/ir/microinstruction.cpp | 4 + src/shader_recompiler/ir/opcodes.inc | 4 + .../ir/passes/resource_tracking_pass.cpp | 12 +++ 9 files changed, 165 insertions(+), 10 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 02600b940..bbf259fe8 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 @@ -467,4 +467,96 @@ void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address EmitStoreBufferF32xN<1>(ctx, handle, address, value); } +static Id ConvertF32ToFormat(EmitContext& ctx, Id value, AmdGpu::NumberFormat format, + u32 bit_width) { + switch (format) { + case AmdGpu::NumberFormat::Unorm: + return ctx.OpConvertFToU( + ctx.U32[1], ctx.OpFMul(ctx.F32[1], value, ctx.ConstF32(float(UXBitsMax(bit_width))))); + case AmdGpu::NumberFormat::Uint: + return ctx.OpBitcast(ctx.U32[1], value); + case AmdGpu::NumberFormat::Float: + return value; + default: + UNREACHABLE_MSG("Unsupported number fromat for conversion: {}", + magic_enum::enum_name(format)); + } +} + +template +static void EmitStoreBufferFormatF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { + auto& buffer = ctx.buffers[handle]; + const auto format = buffer.buffer.GetDataFmt(); + const auto num_format = buffer.buffer.GetNumberFmt(); + + switch (format) { + case AmdGpu::DataFormat::FormatInvalid: + return; + case AmdGpu::DataFormat::Format8_8_8_8: + case AmdGpu::DataFormat::Format16: + case AmdGpu::DataFormat::Format32: + case AmdGpu::DataFormat::Format32_32_32_32: { + ASSERT(N == AmdGpu::NumComponents(format)); + + address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index); + + Id packed_value{}; + for (u32 i = 0; i < N; i++) { + const u32 bit_width = AmdGpu::ComponentBits(format, i); + const u32 bit_offset = AmdGpu::ComponentOffset(format, i) % 32; + + const Id comp{ConvertF32ToFormat( + ctx, N == 1 ? value : ctx.OpCompositeExtract(ctx.F32[1], value, i), num_format, + bit_width)}; + + if (bit_width == 32) { + if constexpr (N == 1) { + ctx.OpStore(ptr, comp); + } else { + const Id index_i = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, + ctx.u32_zero_value, index_i); + ctx.OpStore(ptr, comp); + } + } else { + if (i == 0) { + packed_value = comp; + } else { + packed_value = + ctx.OpBitFieldInsert(ctx.U32[1], packed_value, comp, + ctx.ConstU32(bit_offset), ctx.ConstU32(bit_width)); + } + + if (i == N - 1) { + ctx.OpStore(ptr, packed_value); + } + } + } + } break; + default: + UNREACHABLE_MSG("Invalid format for conversion: {}", magic_enum::enum_name(format)); + } +} + +void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferFormatF32xN<1>(ctx, handle, address, value); +} + +void EmitStoreBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, + Id value) { + EmitStoreBufferFormatF32xN<2>(ctx, handle, address, value); +} + +void EmitStoreBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, + Id value) { + EmitStoreBufferFormatF32xN<3>(ctx, handle, address, value); +} + +void EmitStoreBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, + Id value) { + EmitStoreBufferFormatF32xN<4>(ctx, handle, address, value); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index f868527f7..8a0fcd4b8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -76,6 +76,10 @@ void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp); Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp); diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 2e12209dc..9ebcb1163 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -186,7 +186,7 @@ public: // Vector Memory void BUFFER_LOAD_FORMAT(u32 num_dwords, bool is_typed, bool is_format, const GcnInst& inst); - void BUFFER_STORE_FORMAT(u32 num_dwords, bool is_typed, const GcnInst& inst); + void BUFFER_STORE_FORMAT(u32 num_dwords, bool is_typed, bool is_format, const GcnInst& inst); // Vector interpolation void V_INTERP_P2_F32(const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index f708b9fbc..63f6c3b47 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -53,6 +53,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { case Opcode::IMAGE_GET_RESINFO: return IMAGE_GET_RESINFO(inst); + // Buffer load operations case Opcode::TBUFFER_LOAD_FORMAT_X: return BUFFER_LOAD_FORMAT(1, true, true, inst); case Opcode::TBUFFER_LOAD_FORMAT_XY: @@ -61,6 +62,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_LOAD_FORMAT(3, true, true, inst); case Opcode::TBUFFER_LOAD_FORMAT_XYZW: return BUFFER_LOAD_FORMAT(4, true, true, inst); + case Opcode::BUFFER_LOAD_FORMAT_X: return BUFFER_LOAD_FORMAT(1, false, true, inst); case Opcode::BUFFER_LOAD_FORMAT_XY: @@ -69,6 +71,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_LOAD_FORMAT(3, false, true, inst); case Opcode::BUFFER_LOAD_FORMAT_XYZW: return BUFFER_LOAD_FORMAT(4, false, true, inst); + case Opcode::BUFFER_LOAD_DWORD: return BUFFER_LOAD_FORMAT(1, false, false, inst); case Opcode::BUFFER_LOAD_DWORDX2: @@ -77,16 +80,25 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_LOAD_FORMAT(3, false, false, inst); case Opcode::BUFFER_LOAD_DWORDX4: return BUFFER_LOAD_FORMAT(4, false, false, inst); + + // Buffer store operations case Opcode::BUFFER_STORE_FORMAT_X: - case Opcode::BUFFER_STORE_DWORD: - return BUFFER_STORE_FORMAT(1, false, inst); - case Opcode::BUFFER_STORE_DWORDX2: - return BUFFER_STORE_FORMAT(2, false, inst); - case Opcode::BUFFER_STORE_DWORDX3: - return BUFFER_STORE_FORMAT(3, false, inst); + return BUFFER_STORE_FORMAT(1, false, true, inst); + case Opcode::BUFFER_STORE_FORMAT_XY: + return BUFFER_STORE_FORMAT(2, false, true, inst); + case Opcode::BUFFER_STORE_FORMAT_XYZ: + return BUFFER_STORE_FORMAT(3, false, true, inst); case Opcode::BUFFER_STORE_FORMAT_XYZW: + return BUFFER_STORE_FORMAT(4, false, true, inst); + + case Opcode::BUFFER_STORE_DWORD: + return BUFFER_STORE_FORMAT(1, false, false, inst); + case Opcode::BUFFER_STORE_DWORDX2: + return BUFFER_STORE_FORMAT(2, false, false, inst); + case Opcode::BUFFER_STORE_DWORDX3: + return BUFFER_STORE_FORMAT(3, false, false, inst); case Opcode::BUFFER_STORE_DWORDX4: - return BUFFER_STORE_FORMAT(4, false, inst); + return BUFFER_STORE_FORMAT(4, false, false, inst); default: LogMissingOpcode(inst); } @@ -359,7 +371,8 @@ void Translator::BUFFER_LOAD_FORMAT(u32 num_dwords, bool is_typed, bool is_forma } } -void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, bool is_typed, const GcnInst& inst) { +void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, bool is_typed, bool is_format, + const GcnInst& inst) { const auto& mtbuf = inst.control.mtbuf; const IR::VectorReg vaddr{inst.src[0].code}; const IR::ScalarReg sharp{inst.src[2].code * 4}; @@ -410,7 +423,11 @@ void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, bool is_typed, const GcnIns const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(sharp), ir.GetScalarReg(sharp + 1), ir.GetScalarReg(sharp + 2), ir.GetScalarReg(sharp + 3)); - ir.StoreBuffer(num_dwords, handle, address, value, info); + if (is_format) { + ir.StoreBufferFormat(num_dwords, handle, address, value, info); + } else { + ir.StoreBuffer(num_dwords, handle, address, value, info); + } } void Translator::IMAGE_GET_LOD(const GcnInst& inst) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 3ff347fb3..4271ac359 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -347,6 +347,26 @@ void IREmitter::StoreBuffer(int num_dwords, const Value& handle, const Value& ad } } +void IREmitter::StoreBufferFormat(int num_dwords, const Value& handle, const Value& address, + const Value& data, BufferInstInfo info) { + switch (num_dwords) { + case 1: + Inst(Opcode::StoreBufferFormatF32, Flags{info}, handle, address, data); + break; + case 2: + Inst(Opcode::StoreBufferFormatF32x2, Flags{info}, handle, address, data); + break; + case 3: + Inst(Opcode::StoreBufferFormatF32x3, Flags{info}, handle, address, data); + break; + case 4: + Inst(Opcode::StoreBufferFormatF32x4, Flags{info}, handle, address, data); + break; + default: + UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords); + } +} + U32 IREmitter::LaneId() { return Inst(Opcode::LaneId); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index c226edacf..59ced93e3 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -93,6 +93,8 @@ public: BufferInstInfo info); void StoreBuffer(int num_dwords, const Value& handle, const Value& address, const Value& data, BufferInstInfo info); + void StoreBufferFormat(int num_dwords, const Value& handle, const Value& address, + const Value& data, BufferInstInfo info); [[nodiscard]] U32 LaneId(); [[nodiscard]] U32 WarpId(); diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index 5d413c8a7..a8166125e 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -55,6 +55,10 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::StoreBufferF32x2: case Opcode::StoreBufferF32x3: case Opcode::StoreBufferF32x4: + case Opcode::StoreBufferFormatF32: + case Opcode::StoreBufferFormatF32x2: + case Opcode::StoreBufferFormatF32x3: + case Opcode::StoreBufferFormatF32x4: case Opcode::StoreBufferU32: case Opcode::WriteSharedU128: case Opcode::WriteSharedU64: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 0e25b777c..4c6122a83 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -82,6 +82,10 @@ OPCODE(StoreBufferF32, Void, Opaq OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, ) OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, ) OPCODE(StoreBufferF32x4, Void, Opaque, Opaque, F32x4, ) +OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, F32, ) +OPCODE(StoreBufferFormatF32x2, Void, Opaque, Opaque, F32x2, ) +OPCODE(StoreBufferFormatF32x3, Void, Opaque, Opaque, F32x3, ) +OPCODE(StoreBufferFormatF32x4, Void, Opaque, Opaque, F32x4, ) OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, ) // Vector utility diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index b3d2311e2..97fc5b999 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -37,6 +37,10 @@ bool IsBufferInstruction(const IR::Inst& inst) { case IR::Opcode::StoreBufferF32x2: case IR::Opcode::StoreBufferF32x3: case IR::Opcode::StoreBufferF32x4: + case IR::Opcode::StoreBufferFormatF32: + case IR::Opcode::StoreBufferFormatF32x2: + case IR::Opcode::StoreBufferFormatF32x3: + case IR::Opcode::StoreBufferFormatF32x4: case IR::Opcode::StoreBufferU32: return true; default: @@ -73,6 +77,10 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { case IR::Opcode::LoadBufferFormatF32x2: case IR::Opcode::LoadBufferFormatF32x3: case IR::Opcode::LoadBufferFormatF32x4: + case IR::Opcode::StoreBufferFormatF32: + case IR::Opcode::StoreBufferFormatF32x2: + case IR::Opcode::StoreBufferFormatF32x3: + case IR::Opcode::StoreBufferFormatF32x4: switch (num_format) { case AmdGpu::NumberFormat::Unorm: case AmdGpu::NumberFormat::Snorm: @@ -112,6 +120,10 @@ bool IsBufferStore(const IR::Inst& inst) { case IR::Opcode::StoreBufferF32x2: case IR::Opcode::StoreBufferF32x3: case IR::Opcode::StoreBufferF32x4: + case IR::Opcode::StoreBufferFormatF32: + case IR::Opcode::StoreBufferFormatF32x2: + case IR::Opcode::StoreBufferFormatF32x3: + case IR::Opcode::StoreBufferFormatF32x4: case IR::Opcode::StoreBufferU32: return true; default: From 8c77d4dde661ff565e4a3dccc72a0b7028178e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:33:10 +0200 Subject: [PATCH 095/109] Ability to change username (#432) --- CMakeLists.txt | 57 ++++++++++---------- src/common/config.cpp | 7 +++ src/common/config.h | 1 + src/core/libraries/np_manager/np_manager.cpp | 3 +- src/emulator.cpp | 2 +- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9153197cb..3685b7f85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ if(ENABLE_QT_GUI) set(CMAKE_AUTOUIC ON) endif() -set(AUDIO_CORE src/audio_core/sdl_audio.cpp +set(AUDIO_CORE src/audio_core/sdl_audio.cpp src/audio_core/sdl_audio.h ) @@ -527,34 +527,33 @@ set(EMULATOR src/emulator.cpp if(ENABLE_QT_GUI) qt_add_resources(RESOURCE_FILES src/shadps4.qrc) -set(QT_GUI - src/qt_gui/main_window_ui.h - src/qt_gui/main_window.cpp - src/qt_gui/main_window.h - src/qt_gui/gui_context_menus.h - src/qt_gui/game_list_utils.h - src/qt_gui/game_info.cpp - src/qt_gui/game_info.h - src/qt_gui/game_list_frame.cpp - src/qt_gui/game_list_frame.h - src/qt_gui/game_grid_frame.cpp - src/qt_gui/game_grid_frame.h - src/qt_gui/game_install_dialog.cpp - src/qt_gui/game_install_dialog.h - src/qt_gui/pkg_viewer.cpp - src/qt_gui/pkg_viewer.h - src/qt_gui/trophy_viewer.cpp - src/qt_gui/trophy_viewer.h - src/qt_gui/elf_viewer.cpp - src/qt_gui/elf_viewer.h - src/qt_gui/main_window_themes.cpp - src/qt_gui/main_window_themes.h - src/qt_gui/settings_dialog.cpp - src/qt_gui/settings_dialog.h - src/qt_gui/settings_dialog.ui - src/qt_gui/main.cpp - ${EMULATOR} - ${RESOURCE_FILES} +set(QT_GUI src/qt_gui/main_window_ui.h + src/qt_gui/main_window.cpp + src/qt_gui/main_window.h + src/qt_gui/gui_context_menus.h + src/qt_gui/game_list_utils.h + src/qt_gui/game_info.cpp + src/qt_gui/game_info.h + src/qt_gui/game_list_frame.cpp + src/qt_gui/game_list_frame.h + src/qt_gui/game_grid_frame.cpp + src/qt_gui/game_grid_frame.h + src/qt_gui/game_install_dialog.cpp + src/qt_gui/game_install_dialog.h + src/qt_gui/pkg_viewer.cpp + src/qt_gui/pkg_viewer.h + src/qt_gui/trophy_viewer.cpp + src/qt_gui/trophy_viewer.h + src/qt_gui/elf_viewer.cpp + src/qt_gui/elf_viewer.h + src/qt_gui/main_window_themes.cpp + src/qt_gui/main_window_themes.h + src/qt_gui/settings_dialog.cpp + src/qt_gui/settings_dialog.h + src/qt_gui/settings_dialog.ui + src/qt_gui/main.cpp + ${EMULATOR} + ${RESOURCE_FILES} ) endif() diff --git a/src/common/config.cpp b/src/common/config.cpp index a65a5b596..8ff294493 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -16,6 +16,7 @@ static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static std::string logFilter; static std::string logType = "async"; +static std::string userName = "shadPS4"; static bool isDebugDump = false; static bool isLibc = true; static bool isShowSplash = false; @@ -80,6 +81,10 @@ std::string getLogType() { return logType; } +std::string getUserName() { + return userName; +} + bool debugDump() { return isDebugDump; } @@ -313,6 +318,7 @@ void load(const std::filesystem::path& path) { isFullscreen = toml::find_or(general, "Fullscreen", false); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); + userName = toml::find_or(general, "userName", "shadPS4"); isShowSplash = toml::find_or(general, "showSplash", true); } @@ -400,6 +406,7 @@ void save(const std::filesystem::path& path) { data["General"]["Fullscreen"] = isFullscreen; data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; + data["General"]["userName"] = userName; data["General"]["showSplash"] = isShowSplash; data["GPU"]["screenWidth"] = screenWidth; data["GPU"]["screenHeight"] = screenHeight; diff --git a/src/common/config.h b/src/common/config.h index 970550281..6c9547058 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -15,6 +15,7 @@ bool isNeoMode(); bool isFullscreenMode(); std::string getLogFilter(); std::string getLogType(); +std::string getUserName(); u32 getScreenWidth(); u32 getScreenHeight(); diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index fd4e31f54..c657fbf60 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Generated By moduleGenerator +#include "common/config.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" @@ -977,7 +978,7 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() { int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId) { LOG_ERROR(Lib_NpManager, "(DUMMY) called"); - std::string name = "shadps4"; + std::string name = Config::getUserName(); strcpy(npId->handle.data, name.c_str()); return ORBIS_OK; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 034b8706b..4990b4aab 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -10,6 +10,7 @@ #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" +#include "common/scm_rev.h" #include "common/singleton.h" #include "common/version.h" #include "core/file_format/playgo_chunk.h" @@ -26,7 +27,6 @@ #include "core/linker.h" #include "core/memory.h" #include "emulator.h" -#include "src/common/scm_rev.h" #include "video_core/renderdoc.h" Frontend::WindowSDL* g_window = nullptr; From a0fb47b0ab267543bd5dc6d4253855d75adf8462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:06:09 +0200 Subject: [PATCH 096/109] Qt-GUI: Adding User Name selection (#440) * Qt-GUI: Adding User Name selection * fix * fix 2 * fix 3 (thanks Poly) * Change the username emplacement --- src/common/config.cpp | 5 +++++ src/common/config.h | 1 + src/qt_gui/settings_dialog.cpp | 4 ++++ src/qt_gui/settings_dialog.ui | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+) diff --git a/src/common/config.cpp b/src/common/config.cpp index 8ff294493..24db6b039 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -197,6 +197,10 @@ void setLogFilter(std::string type) { logFilter = type; } +void setUserName(std::string type) { + userName = type; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -453,6 +457,7 @@ void setDefaultValues() { screenHeight = 720; logFilter = ""; logType = "async"; + userName = "shadPS4"; isDebugDump = false; isShowSplash = false; isNullGpu = false; diff --git a/src/common/config.h b/src/common/config.h index 6c9547058..3006f2e2a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -43,6 +43,7 @@ void setScreenHeight(u32 height); void setFullscreenMode(bool enable); void setLanguage(u32 language); void setNeoMode(bool enable); +void setUserName(std::string type); void setLogType(std::string type); void setLogFilter(std::string type); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index bde0eadab..fd2df0fc1 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -43,6 +43,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge { connect(ui->consoleLanguageComboBox, &QComboBox::currentIndexChanged, this, [](int index) { Config::setLanguage(index); }); + + connect(ui->userNameLineEdit, &QLineEdit::textChanged, this, + [](const QString& text) { Config::setUserName(text.toStdString()); }); } // GPU TAB @@ -121,6 +124,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->ps4proCheckBox->setChecked(Config::isNeoMode()); ui->logTypeComboBox->setCurrentText(QString::fromStdString(Config::getLogType())); ui->logFilterLineEdit->setText(QString::fromStdString(Config::getLogFilter())); + ui->userNameLineEdit->setText(QString::fromStdString(Config::getUserName())); ui->debugDump->setChecked(Config::debugDump()); ui->vkValidationCheckBox->setChecked(Config::vkValidationEnabled()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 4893bd613..148799c5b 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -231,6 +231,32 @@ + + + + 6 + + + 0 + + + + + + + Username + + + + + + + + + + + + From d45563f92cb52025eb569ee8bd750b323501fc97 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 15 Aug 2024 09:43:27 -0300 Subject: [PATCH 097/109] gpu: handle primitive restart index register (#438) --- src/video_core/amdgpu/liverpool.h | 5 ++++- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 4 +++- src/video_core/renderer_vulkan/vk_graphics_pipeline.h | 1 + src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 778bd7a59..706da8ec5 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -932,7 +932,9 @@ struct Liverpool { INSERT_PADDING_WORDS(0xA094 - 0xA08E - 2); std::array viewport_scissors; std::array viewport_depths; - INSERT_PADDING_WORDS(0xA105 - 0xA0D4); + INSERT_PADDING_WORDS(0xA103 - 0xA0D4); + u32 primitive_reset_index; + INSERT_PADDING_WORDS(1); BlendConstants blend_constants; INSERT_PADDING_WORDS(0xA10B - 0xA105 - 4); StencilControl stencil_control; @@ -1158,6 +1160,7 @@ static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017); static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E); static_assert(GFX6_3D_REG_INDEX(color_shader_mask) == 0xA08F); static_assert(GFX6_3D_REG_INDEX(viewport_scissors) == 0xA094); +static_assert(GFX6_3D_REG_INDEX(primitive_reset_index) == 0xA103); static_assert(GFX6_3D_REG_INDEX(stencil_control) == 0xA10B); static_assert(GFX6_3D_REG_INDEX(viewports) == 0xA10F); static_assert(GFX6_3D_REG_INDEX(clip_user_data) == 0xA16F); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index cf23ade26..0c516dbac 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -86,8 +86,10 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { .topology = LiverpoolToVK::PrimitiveType(key.prim_type), - .primitiveRestartEnable = false, + .primitiveRestartEnable = key.prim_restart_index != 0, }; + ASSERT_MSG(key.prim_restart_index == 0 || key.prim_restart_index == 0xFFFF, + "Primitive restart index other than 0xFFFF is not supported"); const vk::PipelineRasterizationStateCreateInfo raster_state = { .depthClampEnable = false, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index f818d980b..fc5070913 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -39,6 +39,7 @@ struct GraphicsPipelineKey { Liverpool::StencilRefMask stencil_ref_front; Liverpool::StencilRefMask stencil_ref_back; Liverpool::PrimitiveType prim_type; + u32 prim_restart_index; Liverpool::PolygonMode polygon_mode; Liverpool::CullMode cull_mode; Liverpool::FrontFace front_face; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 38d1f51b2..0a94ce6d8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -165,6 +165,7 @@ void PipelineCache::RefreshGraphicsKey() { key.stencil_ref_front = regs.stencil_ref_front; key.stencil_ref_back = regs.stencil_ref_back; key.prim_type = regs.primitive_type; + key.prim_restart_index = regs.primitive_reset_index; key.polygon_mode = regs.polygon_control.PolyMode(); key.cull_mode = regs.polygon_control.CullingMode(); key.clip_space = regs.clipper_control.clip_space; From 0b1d7839a34ce9422b41be98a7b8b91a88187b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:49:13 +0200 Subject: [PATCH 098/109] Qt-GUI: Cleaning the option menu (#443) --- src/qt_gui/settings_dialog.cpp | 39 +- src/qt_gui/settings_dialog.ui | 818 ++++++++++++++++----------------- 2 files changed, 411 insertions(+), 446 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index fd2df0fc1..ca47f3310 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -39,13 +39,28 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); }); - // EMULATOR TAB + // GENERAL TAB { + connect(ui->userNameLineEdit, &QLineEdit::textChanged, this, + [](const QString& text) { Config::setUserName(text.toStdString()); }); + connect(ui->consoleLanguageComboBox, &QComboBox::currentIndexChanged, this, [](int index) { Config::setLanguage(index); }); - connect(ui->userNameLineEdit, &QLineEdit::textChanged, this, - [](const QString& text) { Config::setUserName(text.toStdString()); }); + connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setFullscreenMode(val); }); + + connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setShowSplash(val); }); + + connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setNeoMode(val); }); + + connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this, + [](const QString& text) { Config::setLogType(text.toStdString()); }); + + connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this, + [](const QString& text) { Config::setLogFilter(text.toStdString()); }); } // GPU TAB @@ -74,24 +89,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge [](int val) { Config::setDumpPM4(val); }); } - // GENERAL TAB - { - connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this, - [](int val) { Config::setFullscreenMode(val); }); - - connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this, - [](int val) { Config::setShowSplash(val); }); - - connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this, - [](int val) { Config::setNeoMode(val); }); - - connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this, - [](const QString& text) { Config::setLogType(text.toStdString()); }); - - connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this, - [](const QString& text) { Config::setLogFilter(text.toStdString()); }); - } - // DEBUG TAB { connect(ui->debugDump, &QCheckBox::stateChanged, this, diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 148799c5b..3302f9e62 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1,7 +1,6 @@ - SettingsDialog @@ -46,8 +45,8 @@ 0 0 - 1006 - 720 + 1002 + 710 @@ -59,273 +58,428 @@ 0 - + - Emulator + General - + - + - + - + - Console Language + Emulator Settings - + - + + + 6 + + + 0 + - - Japanese - - - - - English (United States) - - - - - French (France) - - - - - Spanish (Spain) - - - - - German - - - - - Italian - - - - - Dutch - - - - - Portuguese (Portugal) - - - - - Russian - - - - - Korean - - - - - Traditional Chinese - - - - - Simplified Chinese - - - - - Finnish - - - - - Swedish - - - - - Danish - - - - - Norwegian - - - - - Polish - - - - - Portuguese (Brazil) - - - - - English (United Kingdom) - - - - - Turkish - - - - - Spanish (Latin America) - - - - - Arabic - - - - - French (Canada) - - - - - Czech - - - - - Hungarian - - - - - Greek - - - - - Romanian - - - - - Thai - - - - - Vietnamese - - - - - Indonesian - + + + + + Username + + + + + + + + + + + + + + + Console Language + + + + + + + Japanese + + + + + English (United States) + + + + + French (France) + + + + + Spanish (Spain) + + + + + German + + + + + Italian + + + + + Dutch + + + + + Portuguese (Portugal) + + + + + Russian + + + + + Korean + + + + + Traditional Chinese + + + + + Simplified Chinese + + + + + Finnish + + + + + Swedish + + + + + Danish + + + + + Norwegian + + + + + Polish + + + + + Portuguese (Brazil) + + + + + English (United Kingdom) + + + + + Turkish + + + + + Spanish (Latin America) + + + + + Arabic + + + + + French (Canada) + + + + + Czech + + + + + Hungarian + + + + + Greek + + + + + Romanian + + + + + Thai + + + + + Vietnamese + + + + + Indonesian + + + + + + + + + Enable Fullscreen + + + + + + + Show Splash + + + + + + + Is PS4 Pro + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + - - - 6 + + + Logger Settings - - 0 - - - - - - - Username + + + + + + 0 - + + 0 + + + 0 + + + 0 + + + + + Log Type + + + + + + + async + + + + + sync + + + + + + + + + + + + + + 6 + + + 0 + + + - + + + Log Filter + + + + + + + - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - + + + - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - 12 - - - 12 - + + + + + Additional Settings + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + 12 + + + 12 + + + @@ -645,192 +799,6 @@ - - - General - - - - - - - - - - Emulator Settings - - - - - - Enable Fullscreen - - - - - - - Show Splash - - - - - - - Is PS4 Pro - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - - - - - - Logger Settings - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Log Type - - - - - - - async - - - - - sync - - - - - - - - - - - - - - 6 - - - 0 - - - - - - - Log Filter - - - - - - - - - - - - - - - - - - - - - - - Additional Settings - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - Debug From d32e5848393cd2235c5bb162a9f4d8292b0de3f7 Mon Sep 17 00:00:00 2001 From: psucien Date: Thu, 15 Aug 2024 17:41:53 +0200 Subject: [PATCH 099/109] libraries: vide_out: redundant assert removed --- src/core/libraries/videoout/video_out.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index acfcbad42..ab9eac76f 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -200,7 +200,6 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, const void* param) { LOG_INFO(Lib_VideoOut, "called"); - ASSERT(userId == UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || userId == 0); ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN); if (index != 0) { From da9b26fa1e0a4b9e34dd9f08e7632676c7bfee9a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 15 Aug 2024 19:41:42 +0300 Subject: [PATCH 100/109] tagged 0.2.0 release --- src/common/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/version.h b/src/common/version.h index 92fd18fb2..708d9ec4a 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.1.1 WIP"; -constexpr bool isRelease = false; +constexpr char VERSION[] = "0.2.0"; +constexpr bool isRelease = true; } // namespace Common From e96e66eedd9a966cb2873972886d086615401d24 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 15 Aug 2024 19:58:18 +0300 Subject: [PATCH 101/109] starting 0.2.1 --- src/common/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/version.h b/src/common/version.h index 708d9ec4a..80de187b0 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.2.0"; -constexpr bool isRelease = true; +constexpr char VERSION[] = "0.2.1 WIP"; +constexpr bool isRelease = false; } // namespace Common From b5c69189e56d8813b10adb2d2bcf17426b3bdac0 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 26 Jul 2024 18:34:36 +0300 Subject: [PATCH 102/109] avplayer WIP --- .gitmodules | 3 + CMakeLists.txt | 12 +- externals/CMakeLists.txt | 5 + externals/ffmpeg-core | 1 + src/common/logging/filter.cpp | 2 +- src/core/file_sys/fs.cpp | 4 +- src/core/file_sys/fs.h | 2 +- src/core/libraries/audio/audioout.cpp | 5 +- src/core/libraries/avplayer/avplayer.cpp | 396 ++++++++--- src/core/libraries/avplayer/avplayer.h | 302 +++++++- .../libraries/avplayer/avplayer_common.cpp | 120 ++++ src/core/libraries/avplayer/avplayer_common.h | 179 +++++ .../avplayer/avplayer_data_streamer.h | 20 + .../avplayer/avplayer_file_streamer.cpp | 93 +++ .../avplayer/avplayer_file_streamer.h | 40 ++ src/core/libraries/avplayer/avplayer_impl.cpp | 186 +++++ src/core/libraries/avplayer/avplayer_impl.h | 65 ++ .../libraries/avplayer/avplayer_source.cpp | 658 ++++++++++++++++++ src/core/libraries/avplayer/avplayer_source.h | 169 +++++ .../libraries/avplayer/avplayer_state.cpp | 481 +++++++++++++ src/core/libraries/avplayer/avplayer_state.h | 85 +++ src/core/libraries/error_codes.h | 13 + src/core/libraries/kernel/thread_management.h | 16 +- src/core/libraries/kernel/time_management.h | 1 + 24 files changed, 2721 insertions(+), 137 deletions(-) create mode 160000 externals/ffmpeg-core create mode 100644 src/core/libraries/avplayer/avplayer_common.cpp create mode 100644 src/core/libraries/avplayer/avplayer_common.h create mode 100644 src/core/libraries/avplayer/avplayer_data_streamer.h create mode 100644 src/core/libraries/avplayer/avplayer_file_streamer.cpp create mode 100644 src/core/libraries/avplayer/avplayer_file_streamer.h create mode 100644 src/core/libraries/avplayer/avplayer_impl.cpp create mode 100644 src/core/libraries/avplayer/avplayer_impl.h create mode 100644 src/core/libraries/avplayer/avplayer_source.cpp create mode 100644 src/core/libraries/avplayer/avplayer_source.h create mode 100644 src/core/libraries/avplayer/avplayer_state.cpp create mode 100644 src/core/libraries/avplayer/avplayer_state.h diff --git a/.gitmodules b/.gitmodules index 3a9d8f42f..e96f33ecd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,3 +61,6 @@ [submodule "externals/date"] path = externals/date url = https://github.com/HowardHinnant/date.git +[submodule "externals/ffmpeg-core"] + path = externals/ffmpeg-core + url = https://github.com/RPCS3/ffmpeg-core.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3685b7f85..c01e9e980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,16 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/disc_map/disc_map.cpp src/core/libraries/disc_map/disc_map.h src/core/libraries/disc_map/disc_map_codes.h + src/core/libraries/avplayer/avplayer_common.cpp + src/core/libraries/avplayer/avplayer_common.h + src/core/libraries/avplayer/avplayer_file_streamer.cpp + src/core/libraries/avplayer/avplayer_file_streamer.h + src/core/libraries/avplayer/avplayer_impl.cpp + src/core/libraries/avplayer/avplayer_impl.h + src/core/libraries/avplayer/avplayer_source.cpp + src/core/libraries/avplayer/avplayer_source.h + src/core/libraries/avplayer/avplayer_state.cpp + src/core/libraries/avplayer/avplayer_state.h src/core/libraries/avplayer/avplayer.cpp src/core/libraries/avplayer/avplayer.h ) @@ -588,7 +598,7 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API ffmpeg) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) if (APPLE) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9ebdd8783..bc313232d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -47,6 +47,11 @@ else() endif() endif() +if (NOT TARGET ffmpeg) + set(ARCHITECTURE "x86_64") + add_subdirectory(ffmpeg-core) +endif() + # Zlib-Ng if (NOT TARGET zlib-ng::zlib) set(ZLIB_ENABLE_TESTS OFF) diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core new file mode 160000 index 000000000..e30b7d7fe --- /dev/null +++ b/externals/ffmpeg-core @@ -0,0 +1 @@ +Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0 diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index a514652d4..2c4a20dea 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -106,12 +106,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, DiscMap) \ SUB(Lib, Png) \ SUB(Lib, PlayGo) \ + SUB(Lib, Random) \ SUB(Lib, Usbd) \ SUB(Lib, Ajm) \ SUB(Lib, ErrorDialog) \ SUB(Lib, ImeDialog) \ SUB(Lib, AvPlayer) \ - SUB(Lib, Random) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index a6d5c3eac..40d8212bb 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -25,9 +25,9 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) { +std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf - auto corrected_path = guest_directory; + std::string corrected_path(guest_directory); size_t pos = corrected_path.find("//"); while (pos != std::string::npos) { corrected_path.replace(pos, 2, "/"); diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index d636f8bff..b0fb63242 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -31,7 +31,7 @@ public: void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void UnmountAll(); - std::filesystem::path GetHostPath(const std::string& guest_directory); + std::filesystem::path GetHostPath(std::string_view guest_directory); const MntPair* GetMount(const std::string& guest_path) { const auto it = std::ranges::find_if( diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index eac3845f3..08929383b 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -175,7 +175,6 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() { } int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { - int type = 0; int channels_num = 0; @@ -235,11 +234,11 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() { } int PS4_SYSV_ABI sceAudioOutInit() { + LOG_INFO(Lib_AudioOut, "called"); if (audio != nullptr) { return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; } audio = std::make_unique(); - LOG_INFO(Lib_AudioOut, "called"); return ORBIS_OK; } @@ -324,10 +323,12 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() { } s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { + LOG_TRACE(Lib_AudioOut, "called"); return audio->AudioOutOutput(handle, ptr); } int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { + LOG_TRACE(Lib_AudioOut, "called"); for (u32 i = 0; i < num; i++) { if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0) return err; diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index dd9f42b26..41f8d0766 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -1,21 +1,36 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// Generated By moduleGenerator #include "avplayer.h" + +#include "avplayer_impl.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" +#include + namespace Libraries::AvPlayer { -int PS4_SYSV_ABI sceAvPlayerAddSource() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +using namespace Kernel; + +s32 PS4_SYSV_ABI sceAvPlayerAddSource(SceAvPlayerHandle handle, const char* filename) { + LOG_TRACE(Lib_AvPlayer, "filename = {}", filename); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->AddSource(filename); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerAddSourceEx() { +s32 PS4_SYSV_ABI sceAvPlayerAddSourceEx(SceAvPlayerHandle handle, SceAvPlayerUriType uriType, + SceAvPlayerSourceDetails* sourceDetails) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } @@ -24,122 +39,307 @@ int PS4_SYSV_ABI sceAvPlayerChangeStream() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerClose() { +s32 PS4_SYSV_ABI sceAvPlayerClose(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + delete handle; + return ORBIS_OK; +} + +u64 PS4_SYSV_ABI sceAvPlayerCurrentTime(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->CurrentTime(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerDisableStream(SceAvPlayerHandle handle, u32 stream_id) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerEnableStream(SceAvPlayerHandle handle, u32 stream_id) { + LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->EnableStream(stream_id); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetAudioData(SceAvPlayerHandle handle, SceAvPlayerFrameInfo* p_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || p_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetAudioData(*p_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerGetStreamInfo(SceAvPlayerHandle handle, u32 stream_id, + SceAvPlayerStreamInfo* p_info) { + LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id); + if (handle == nullptr || p_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetStreamInfo(stream_id, *p_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetVideoData(SceAvPlayerHandle handle, + SceAvPlayerFrameInfo* video_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || video_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetVideoData(*video_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle, + SceAvPlayerFrameInfoEx* video_info) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || video_info == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetVideoData(*video_info); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +constexpr u32 GetPriority(u32 base, u32 offset) { + // (27D <= base_priority <= 2FC) + offset <= 2FF + return std::min(std::min(std::max(637u, base), 764u) + offset, 767u); +} + +SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (data == nullptr) { + return nullptr; + } + + if (data->memory_replacement.allocate == nullptr || + data->memory_replacement.allocate_texture == nullptr || + data->memory_replacement.deallocate == nullptr || + data->memory_replacement.deallocate_texture == nullptr) { + LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation."); + return nullptr; + } + + ThreadPriorities priorities{}; + const u32 base_priority = data->base_priority != 0 ? data->base_priority : 700; + priorities.video_decoder_priority = GetPriority(base_priority, 5); + priorities.audio_decoder_priority = GetPriority(base_priority, 6); + priorities.demuxer_priority = GetPriority(base_priority, 9); + priorities.controller_priority = GetPriority(base_priority, 2); + // priorities.http_streaming_priority = GetPriority(base_priority, 10); + // priorities.file_streaming_priority = GetPriority(priorities.http_streaming_priority, 15); + // priorities.maxPriority = priorities.http_streaming_priority; + + const auto player = new AvPlayer(); + player->Init(*data, priorities); + return player; +} + +s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, + SceAvPlayerHandle* p_player) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (p_data == nullptr || p_player == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + if (p_data->memory_replacement.allocate == nullptr || + p_data->memory_replacement.allocate_texture == nullptr || + p_data->memory_replacement.deallocate == nullptr || + p_data->memory_replacement.deallocate_texture == nullptr) { + LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation."); + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + SceAvPlayerInitData data = {}; + data.memory_replacement = p_data->memory_replacement; + data.file_replacement = p_data->file_replacement; + data.event_replacement = p_data->event_replacement; + data.default_language = p_data->default_language; + data.num_output_video_framebuffers = p_data->num_output_video_framebuffers; + data.auto_start = p_data->auto_start; + + ThreadPriorities priorities{}; + s32 base_priority = 0; + const auto res = scePthreadGetprio(scePthreadSelf(), &base_priority); + if (res != 0 || base_priority == 0) { + base_priority = 700; + } + + if (p_data->video_decoder_priority != 0) { + priorities.video_decoder_priority = p_data->video_decoder_priority; + } else { + priorities.video_decoder_priority = GetPriority(base_priority, 5); + } + priorities.video_decoder_affinity = p_data->video_decoder_affinity; + + if (p_data->audio_decoder_priority != 0) { + priorities.audio_decoder_priority = p_data->audio_decoder_priority; + } else { + priorities.audio_decoder_priority = GetPriority(base_priority, 6); + } + priorities.audio_decoder_affinity = p_data->audio_decoder_affinity; + + if (p_data->controller_priority != 0) { + priorities.controller_priority = p_data->controller_priority; + } else { + priorities.controller_priority = GetPriority(base_priority, 2); + } + priorities.controller_affinity = p_data->controller_affinity; + + if (p_data->demuxer_priority != 0) { + priorities.demuxer_priority = p_data->demuxer_priority; + } else { + priorities.demuxer_priority = GetPriority(base_priority, 9); + } + priorities.demuxer_affinity = p_data->demuxer_affinity; + + // if (p_data->http_streaming_priority != 0) { + // priorities.http_streaming_priority = p_data->http_streaming_priority; + // } else { + // priorities.http_streaming_priority = GetPriority(base_priority, 10); + // } + // priorities.http_streaming_affinity = p_data->http_streaming_affinity; + + // if (p_data->file_streaming_priority != 0) { + // priorities.file_streaming_priority = p_data->file_streaming_priority; + // } else { + // priorities.file_streaming_priority = GetPriority(base_priority, 15); + // } + // priorities.http_streaming_affinity = p_data->http_streaming_affinity; + + const auto player = new AvPlayer(); + player->Init(data, priorities); + *p_player = player; + return ORBIS_OK; +} + +bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + LOG_TRACE(Lib_AvPlayer, "returning ORBIS_AVPLAYER_ERROR_INVALID_PARAMS"); + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->IsActive(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t jump_time_msec) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerPause(SceAvPlayerHandle handle) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerPostInit(SceAvPlayerHandle handle, SceAvPlayerPostInitData* data) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr || data == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->PostInit(*data); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; +} + +s32 PS4_SYSV_ABI sceAvPlayerPrintf(const char* format, ...) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerCurrentTime() { +s32 PS4_SYSV_ABI sceAvPlayerResume(SceAvPlayerHandle handle) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle, + SceAvPlayerAvSyncMode sync_mode) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback logCb, void* user_data) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerDisableStream() { +s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerEnableStream() { +s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(SceAvPlayerHandle handle, s32 trick_speed) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } return ORBIS_OK; } -int PS4_SYSV_ABI sceAvPlayerGetAudioData() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->Start(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerGetStreamInfo() { +s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + return handle->Stop(); } -int PS4_SYSV_ABI sceAvPlayerGetVideoData() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) { + LOG_TRACE(Lib_AvPlayer, "called"); + if (handle == nullptr) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + const auto res = handle->GetStreamCount(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } -int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerInit() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerInitEx() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerIsActive() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerJumpToTime() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPause() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPostInit() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerPrintf() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerResume() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetLogCallback() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetLooping() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStart() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStop() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerStreamCount() { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAvPlayerVprintf() { +s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index 39a619ee1..f5589441a 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -11,33 +11,279 @@ class SymbolsResolver; namespace Libraries::AvPlayer { -int PS4_SYSV_ABI sceAvPlayerAddSource(); -int PS4_SYSV_ABI sceAvPlayerAddSourceEx(); -int PS4_SYSV_ABI sceAvPlayerChangeStream(); -int PS4_SYSV_ABI sceAvPlayerClose(); -int PS4_SYSV_ABI sceAvPlayerCurrentTime(); -int PS4_SYSV_ABI sceAvPlayerDisableStream(); -int PS4_SYSV_ABI sceAvPlayerEnableStream(); -int PS4_SYSV_ABI sceAvPlayerGetAudioData(); -int PS4_SYSV_ABI sceAvPlayerGetStreamInfo(); -int PS4_SYSV_ABI sceAvPlayerGetVideoData(); -int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(); -int PS4_SYSV_ABI sceAvPlayerInit(); -int PS4_SYSV_ABI sceAvPlayerInitEx(); -int PS4_SYSV_ABI sceAvPlayerIsActive(); -int PS4_SYSV_ABI sceAvPlayerJumpToTime(); -int PS4_SYSV_ABI sceAvPlayerPause(); -int PS4_SYSV_ABI sceAvPlayerPostInit(); -int PS4_SYSV_ABI sceAvPlayerPrintf(); -int PS4_SYSV_ABI sceAvPlayerResume(); -int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(); -int PS4_SYSV_ABI sceAvPlayerSetLogCallback(); -int PS4_SYSV_ABI sceAvPlayerSetLooping(); -int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(); -int PS4_SYSV_ABI sceAvPlayerStart(); -int PS4_SYSV_ABI sceAvPlayerStop(); -int PS4_SYSV_ABI sceAvPlayerStreamCount(); -int PS4_SYSV_ABI sceAvPlayerVprintf(); +class AvPlayer; + +using SceAvPlayerHandle = AvPlayer*; + +enum SceAvPlayerUriType { SCE_AVPLAYER_URI_TYPE_SOURCE = 0 }; + +struct SceAvPlayerUri { + const char* name; + u32 length; +}; + +enum SceAvPlayerSourceType { + SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN = 0, + SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4 = 1, + SCE_AVPLAYER_SOURCE_TYPE_HLS = 8 +}; + +struct SceAvPlayerSourceDetails { + SceAvPlayerUri uri; + u8 reserved1[64]; + SceAvPlayerSourceType source_type; + u8 reserved2[44]; +}; + +struct SceAvPlayerAudio { + u16 channel_count; + u8 reserved1[2]; + u32 sample_rate; + u32 size; + u8 language_code[4]; +}; + +struct SceAvPlayerVideo { + u32 width; + u32 height; + f32 aspect_ratio; + u8 language_code[4]; +}; + +struct SceAvPlayerTextPosition { + u16 top; + u16 left; + u16 bottom; + u16 right; +}; + +struct SceAvPlayerTimedText { + u8 language_code[4]; + u16 text_size; + u16 font_size; + SceAvPlayerTextPosition position; +}; + +union SceAvPlayerStreamDetails { + u8 reserved[16]; + SceAvPlayerAudio audio; + SceAvPlayerVideo video; + SceAvPlayerTimedText subs; +}; + +struct SceAvPlayerFrameInfo { + u8* pData; + u8 reserved[4]; + u64 timestamp; + SceAvPlayerStreamDetails details; +}; + +struct SceAvPlayerStreamInfo { + u32 type; + u8 reserved[4]; + SceAvPlayerStreamDetails details; + u64 duration; + u64 start_time; +}; + +struct SceAvPlayerAudioEx { + u16 channel_count; + u8 reserved[2]; + u32 sample_rate; + u32 size; + u8 language_code[4]; + u8 reserved1[64]; +}; + +struct SceAvPlayerVideoEx { + u32 width; + u32 height; + f32 aspect_ratio; + u8 language_code[4]; + u32 framerate; + u32 crop_left_offset; + u32 crop_right_offset; + u32 crop_top_offset; + u32 crop_bottom_offset; + u32 pitch; + u8 luma_bit_depth; + u8 chroma_bit_depth; + bool video_full_range_flag; + u8 reserved1[37]; +}; + +struct SceAvPlayerTimedTextEx { + u8 language_code[4]; + u8 reserved[12]; + u8 reserved1[64]; +}; + +union SceAvPlayerStreamDetailsEx { + SceAvPlayerAudioEx audio; + SceAvPlayerVideoEx video; + SceAvPlayerTimedTextEx subs; + u8 reserved1[80]; +}; + +struct SceAvPlayerFrameInfoEx { + void* pData; + u8 reserved[4]; + u64 timestamp; + SceAvPlayerStreamDetailsEx details; +}; + +typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocate)(void* p, u32 align, u32 size); +typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocate)(void* p, void* mem); +typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocateTexture)(void* p, u32 align, u32 size); +typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocateTexture)(void* p, void* mem); + +struct SceAvPlayerMemAllocator { + void* object_ptr; + SceAvPlayerAllocate allocate; + SceAvPlayerDeallocate deallocate; + SceAvPlayerAllocateTexture allocate_texture; + SceAvPlayerDeallocateTexture deallocate_texture; +}; + +typedef s32 PS4_SYSV_ABI (*SceAvPlayerOpenFile)(void* p, const char* name); +typedef s32 PS4_SYSV_ABI (*SceAvPlayerCloseFile)(void* p); +typedef s32 PS4_SYSV_ABI (*SceAvPlayerReadOffsetFile)(void* p, u8* buf, u64 pos, u32 len); +typedef u64 PS4_SYSV_ABI (*SceAvPlayerSizeFile)(void* p); + +struct SceAvPlayerFileReplacement { + void* object_ptr; + SceAvPlayerOpenFile open; + SceAvPlayerCloseFile close; + SceAvPlayerReadOffsetFile readOffset; + SceAvPlayerSizeFile size; +}; + +typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data); + +struct SceAvPlayerEventReplacement { + void* object_ptr; + SceAvPlayerEventCallback event_callback; +}; + +enum SceAvPlayerDebuglevels { + SCE_AVPLAYER_DBG_NONE, + SCE_AVPLAYER_DBG_INFO, + SCE_AVPLAYER_DBG_WARNINGS, + SCE_AVPLAYER_DBG_ALL +}; + +struct SceAvPlayerInitData { + SceAvPlayerMemAllocator memory_replacement; + SceAvPlayerFileReplacement file_replacement; + SceAvPlayerEventReplacement event_replacement; + SceAvPlayerDebuglevels debug_level; + u32 base_priority; + s32 num_output_video_framebuffers; + bool auto_start; + u8 reserved[3]; + const char* default_language; +}; + +struct SceAvPlayerInitDataEx { + size_t this_size; + SceAvPlayerMemAllocator memory_replacement; + SceAvPlayerFileReplacement file_replacement; + SceAvPlayerEventReplacement event_replacement; + const char* default_language; + SceAvPlayerDebuglevels debug_level; + u32 audio_decoder_priority; + u32 audio_decoder_affinity; + u32 video_decoder_priority; + u32 video_decoder_affinity; + u32 demuxer_priority; + u32 demuxer_affinity; + u32 controller_priority; + u32 controller_affinity; + u32 http_streaming_priority; + u32 http_streaming_affinity; + u32 file_streaming_priority; + u32 file_streaming_affinity; + s32 num_output_video_framebuffers; + bool auto_start; + u8 reserved[3]; +}; + +enum SceAvPlayerStreamType { + SCE_AVPLAYER_VIDEO, + SCE_AVPLAYER_AUDIO, + SCE_AVPLAYER_TIMEDTEXT, + SCE_AVPLAYER_UNKNOWN +}; + +enum SceAvPlayerVideoDecoderType { + SCE_AVPLAYER_VIDEO_DECODER_TYPE_DEFAULT = 0, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_RESERVED1, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE, + SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE2 +}; + +enum SceAvPlayerAudioDecoderType { + SCE_AVPLAYER_AUDIO_DECODER_TYPE_DEFAULT = 0, + SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED1, + SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED2 +}; + +struct SceAvPlayerDecoderInit { + union { + SceAvPlayerVideoDecoderType video_type; + SceAvPlayerAudioDecoderType audio_type; + u8 reserved[4]; + } decoderType; + union { + struct { + s32 cpu_affinity_mask; + s32 cpu_thread_priority; + u8 decode_pipeline_depth; + u8 compute_pipe_id; + u8 compute_queue_id; + u8 enable_interlaced; + u8 reserved[16]; + } avcSw2; + struct { + u8 audio_channel_order; + u8 reserved[27]; + } aac; + u8 reserved[28]; + } decoderParams; +}; + +struct SceAvPlayerHTTPCtx { + u32 http_context_id; + u32 ssl_context_id; +}; + +struct SceAvPlayerPostInitData { + u32 demux_video_buffer_size; + SceAvPlayerDecoderInit video_decoder_init; + SceAvPlayerDecoderInit audio_decoder_init; + SceAvPlayerHTTPCtx http_context; + u8 reserved[56]; +}; + +enum SceAvPlayerAvSyncMode { + SCE_AVPLAYER_AV_SYNC_MODE_DEFAULT = 0, + SCE_AVPLAYER_AV_SYNC_MODE_NONE +}; + +typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args); + +enum SceAvPlayerEvents { + SCE_AVPLAYER_STATE_STOP = 0x01, + SCE_AVPLAYER_STATE_READY = 0x02, + SCE_AVPLAYER_STATE_PLAY = 0x03, + SCE_AVPLAYER_STATE_PAUSE = 0x04, + SCE_AVPLAYER_STATE_BUFFERING = 0x05, + SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10, + SCE_AVPLAYER_WARNING_ID = 0x20, + SCE_AVPLAYER_ENCRYPTION = 0x30, + SCE_AVPLAYER_DRM_ERROR = 0x40 +}; void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::AvPlayer \ No newline at end of file + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp new file mode 100644 index 000000000..3536c030d --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer.h" +#include "avplayer_common.h" + +#include // std::equal +#include // std::tolower +#include // std::string_view + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +Kernel::ScePthreadMutex CreateMutex(int type, const char* name) { + ScePthreadMutexattr attr{}; + ScePthreadMutex mutex{}; + if (scePthreadMutexattrInit(&attr) == 0) { + if (scePthreadMutexattrSettype(&attr, type) == 0) { + if (scePthreadMutexInit(&mutex, &attr, name) != 0) { + if (mutex != nullptr) { + scePthreadMutexDestroy(&mutex); + } + return nullptr; + } + } + } + if (attr != nullptr) { + scePthreadMutexattrDestroy(&attr); + } + return mutex; +} + +ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params) { + ScePthreadAttr attr; + if (scePthreadAttrInit(&attr) != 0) { + return nullptr; + } + if (scePthreadAttrSetinheritsched(&attr, 0) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + + SceKernelSchedParam param{.sched_priority = static_cast(params.priority)}; + if (scePthreadAttrSetschedparam(&attr, ¶m) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (scePthreadAttrSetstacksize(&attr, std::min(params.stack_size, 0x4000u)) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (scePthreadAttrSetdetachstate(&attr, 0) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + if (params.affinity > 0) { + if (scePthreadAttrSetaffinity(&attr, params.affinity) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + } + + ScePthread thread{}; + if (scePthreadCreate(&thread, &attr, func, params.p_user_data, params.thread_name) != 0) { + scePthreadAttrDestroy(&attr); + return nullptr; + } + + scePthreadAttrDestroy(&attr); + return thread; +} + +static bool ichar_equals(char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); +} + +static bool iequals(std::string_view l, std::string_view r) { + return std::ranges::equal(l, r, ichar_equals); +} + +SceAvPlayerSourceType GetSourceType(std::string_view path) { + if (path.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + + std::string_view name = path; + if (path.find("://") != std::string_view::npos) { + // This path is a URI. Strip HTTP parameters from it. + // schema://server.domain/path/file.ext/and/beyond?param=value#paragraph -> + // -> schema://server.domain/path/to/file.ext/and/beyond + name = path.substr(0, path.find_first_of("?#")); + if (name.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + } + + // schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond + auto ext = name.substr(name.rfind('.')); + if (ext.empty()) { + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; + } + + // .ext/and/beyond -> .ext + ext = ext.substr(0, ext.find('/')); + + if (iequals(ext, ".mp4") || iequals(ext, ".m4v") || iequals(ext, ".m3d") || + iequals(ext, ".m4a") || iequals(ext, ".mov")) { + return SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4; + } + + if (iequals(ext, ".m3u8")) { + return SCE_AVPLAYER_SOURCE_TYPE_HLS; + } + + return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_common.h b/src/core/libraries/avplayer/avplayer_common.h new file mode 100644 index 000000000..5faf23884 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/kernel/thread_management.h" + +#include +#include +#include + +#define AVPLAYER_IS_ERROR(x) ((x) < 0) + +namespace Libraries::AvPlayer { + +enum class AvState { + Initial, + AddingSource, + Ready, + Play, + Stop, + EndOfFile, + Pause, + C0x08, + Jump, + TrickMode, + C0x0B, + Buffering, + Starting, + C0x0E, + C0x0F, + C0x10, + Error, +}; + +enum class AvEventType { + ChangeFlowState = 21, + WarningId = 22, + RevertState = 30, + AddSource = 40, + Error = 255, +}; + +struct ThreadPriorities { + u32 audio_decoder_priority; + u32 audio_decoder_affinity; + u32 video_decoder_priority; + u32 video_decoder_affinity; + u32 demuxer_priority; + u32 demuxer_affinity; + u32 controller_priority; + u32 controller_affinity; + // u32 http_streaming_priority; + // u32 http_streaming_affinity; + // u32 file_streaming_priority; + // u32 file_streaming_affinity; + // u32 maxPriority; + // u32 maxAffinity; +}; + +union AvPlayerEventData { + u32 num_frames; // 20 + AvState state; // AvEventType::ChangeFlowState + s32 error; // AvEventType::WarningId + u32 attempt; // AvEventType::AddSource +}; + +struct AvPlayerEvent { + AvEventType event; + AvPlayerEventData payload; +}; + +Kernel::ScePthreadMutex CreateMutex(int type, const char* name); + +class PthreadMutex { +public: + using ScePthreadMutex = Kernel::ScePthreadMutex; + + PthreadMutex() = default; + + PthreadMutex(const PthreadMutex&) = delete; + PthreadMutex& operator=(const PthreadMutex&) = delete; + + PthreadMutex(PthreadMutex&& r) : m_mutex(r.m_mutex) { + r.m_mutex = nullptr; + } + PthreadMutex& operator=(PthreadMutex&& r) { + std::swap(m_mutex, r.m_mutex); + return *this; + } + + PthreadMutex(int type, const char* name) : m_mutex(CreateMutex(type, name)) {} + ~PthreadMutex() { + if (m_mutex != nullptr) { + Kernel::scePthreadMutexDestroy(&m_mutex); + } + } + + operator ScePthreadMutex() { + return m_mutex; + } + + int Lock() { + return Kernel::scePthreadMutexLock(&m_mutex); + } + + int Unlock() { + return Kernel::scePthreadMutexUnlock(&m_mutex); + } + + // implement BasicLockable to use std::lock_guard + // NOLINTNEXTLINE(readability-identifier-naming) + void lock() { + ASSERT_MSG(Lock() >= 0, "Could not lock the mutex"); + } + + // NOLINTNEXTLINE(readability-identifier-naming) + void unlock() { + ASSERT_MSG(Unlock() >= 0, "Could not unlock the mutex"); + } + + operator bool() { + return m_mutex != nullptr; + } + +private: + ScePthreadMutex m_mutex{}; +}; + +template +class AvPlayerQueue { +public: + AvPlayerQueue() : m_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayer0StlHandler") {} + + size_t Size() { + return m_queue.size(); + } + + void Push(T&& value) { + std::lock_guard guard(m_mutex); + m_queue.emplace(std::forward(value)); + } + + std::optional Pop() { + if (Size() == 0) { + return std::nullopt; + } + std::lock_guard guard(m_mutex); + auto result = std::move(m_queue.front()); + m_queue.pop(); + return result; + } + + void Clear() { + std::lock_guard guard(m_mutex); + m_queue = {}; + } + +private: + PthreadMutex m_mutex{}; + std::queue m_queue{}; +}; + +struct ThreadParameters { + void* p_user_data; + const char* thread_name; + u32 stack_size; + u32 priority; + u32 affinity; +}; + +Kernel::ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params); +SceAvPlayerSourceType GetSourceType(std::string_view path); + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_data_streamer.h b/src/core/libraries/avplayer/avplayer_data_streamer.h new file mode 100644 index 000000000..04097bb4d --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_data_streamer.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" + +#include "common/types.h" + +struct AVIOContext; + +namespace Libraries::AvPlayer { + +class IDataStreamer { +public: + virtual ~IDataStreamer() = default; + virtual AVIOContext* GetContext() = 0; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp new file mode 100644 index 000000000..1c54c4545 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_file_streamer.h" + +#include "avplayer_common.h" + +#include + +extern "C" { +#include +#include +} + +#define AVPLAYER_AVIO_BUFFER_SIZE 4096 + +namespace Libraries::AvPlayer { + +AvPlayerFileStreamer::AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, + std::string_view path) + : m_file_replacement(file_replacement) { + Init(path); +} + +AvPlayerFileStreamer::~AvPlayerFileStreamer() { + if (m_avio_context != nullptr) { + avio_context_free(&m_avio_context); + } + if (m_avio_buffer != nullptr) { + av_free(m_avio_buffer); + } + if (m_file_replacement.close != nullptr && m_fd >= 0) { + const auto close = m_file_replacement.close; + const auto ptr = m_file_replacement.object_ptr; + close(ptr); + } +} + +s32 AvPlayerFileStreamer::Init(std::string_view path) { + const auto ptr = m_file_replacement.object_ptr; + m_fd = m_file_replacement.open(ptr, path.data()); + if (m_fd < 0) { + return -1; + } + m_file_size = m_file_replacement.size(ptr); + m_avio_buffer = reinterpret_cast(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE)); + m_avio_context = + avio_alloc_context(m_avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this, + &AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek); + return 0; +} + +s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) { + const auto self = reinterpret_cast(opaque); + if (self->m_position >= self->m_file_size) { + return AVERROR_EOF; + } + if (self->m_position + size > self->m_file_size) { + size = self->m_file_size - self->m_position; + } + const auto read_offset = self->m_file_replacement.readOffset; + const auto ptr = self->m_file_replacement.object_ptr; + const auto bytes_read = read_offset(ptr, buffer, self->m_position, size); + if (size != 0 && bytes_read == 0) { + return AVERROR_EOF; + } + self->m_position += bytes_read; + return bytes_read; +} + +s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { + const auto self = reinterpret_cast(opaque); + if (whence & AVSEEK_SIZE) { + return self->m_file_size; + } + + if (whence == SEEK_CUR) { + self->m_position = + std::min(u64(std::max(0ll, s64(self->m_position) + offset)), self->m_file_size); + return self->m_position; + } else if (whence == SEEK_SET) { + self->m_position = std::min(u64(std::max(0ll, offset)), self->m_file_size); + return self->m_position; + } else if (whence == SEEK_END) { + self->m_position = + std::min(u64(std::max(0ll, s64(self->m_file_size) + offset)), self->m_file_size); + return self->m_position; + } + + return -1; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.h b/src/core/libraries/avplayer/avplayer_file_streamer.h new file mode 100644 index 000000000..9f1442f98 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" + +#include +#include + +struct AVIOContext; + +namespace Libraries::AvPlayer { + +class AvPlayerFileStreamer : public IDataStreamer { +public: + AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, std::string_view path); + ~AvPlayerFileStreamer(); + + AVIOContext* GetContext() override { + return m_avio_context; + } + +private: + s32 Init(std::string_view path); + + static s32 ReadPacket(void* opaque, u8* buffer, s32 size); + static s64 Seek(void* opaque, s64 buffer, int whence); + + SceAvPlayerFileReplacement m_file_replacement; + + int m_fd = -1; + u64 m_position{}; + u64 m_file_size{}; + u8* m_avio_buffer{}; + AVIOContext* m_avio_context{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp new file mode 100644 index 000000000..58793da7f --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_common.h" +#include "avplayer_file_streamer.h" +#include "avplayer_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" + +using namespace Libraries::Kernel; + +namespace Libraries::AvPlayer { + +void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { + const auto* const self = reinterpret_cast(handle); + const auto allocate = self->m_init_data_original.memory_replacement.allocate; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return allocate(ptr, alignment, size); +} + +void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { + const auto* const self = reinterpret_cast(handle); + const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return deallocate(ptr, memory); +} + +void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { + const auto* const self = reinterpret_cast(handle); + const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return allocate(ptr, alignment, size); +} + +void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { + const auto* const self = reinterpret_cast(handle); + const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; + const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; + return deallocate(ptr, memory); +} + +int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto open = self->m_init_data_original.file_replacement.open; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return open(ptr, filename); +} + +int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto close = self->m_init_data_original.file_replacement.close; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return close(ptr); +} + +int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto read_offset = self->m_init_data_original.file_replacement.readOffset; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return read_offset(ptr, buffer, position, length); +} + +u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { + auto const self = reinterpret_cast(handle); + std::lock_guard guard(self->m_file_io_mutex); + + const auto size = self->m_init_data_original.file_replacement.size; + const auto ptr = self->m_init_data_original.file_replacement.object_ptr; + return size(ptr); +} + +AvPlayer::AvPlayer() : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerFileIOLock") {} + +void AvPlayer::Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) { + m_init_data = data; + m_init_data_original = data; + + m_init_data.memory_replacement.object_ptr = this; + m_init_data.memory_replacement.allocate = &AvPlayer::Allocate; + m_init_data.memory_replacement.deallocate = &AvPlayer::Deallocate; + m_init_data.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture; + m_init_data.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture; + if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr || + data.file_replacement.readOffset == nullptr || data.file_replacement.size == nullptr) { + m_init_data.file_replacement = {}; + } else { + m_init_data.file_replacement.object_ptr = this; + m_init_data.file_replacement.open = &AvPlayer::OpenFile; + m_init_data.file_replacement.close = &AvPlayer::CloseFile; + m_init_data.file_replacement.readOffset = &AvPlayer::ReadOffsetFile; + m_init_data.file_replacement.size = &AvPlayer::SizeFile; + } + + m_state = std::make_unique(m_init_data, priorities); +} + +s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) { + m_post_init_data = data; + return ORBIS_OK; +} + +s32 AvPlayer::AddSource(std::string_view path) { + if (path.empty()) { + return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + } + + if (AVPLAYER_IS_ERROR(m_state->AddSource(path, GetSourceType(path)))) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + + return ORBIS_OK; +} + +s32 AvPlayer::GetStreamCount() { + return m_state->GetStreamCount(); +} + +s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + if (AVPLAYER_IS_ERROR(m_state->GetStreamInfo(stream_index, info))) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; +} + +s32 AvPlayer::EnableStream(u32 stream_id) { + if (m_state == nullptr) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return m_state->EnableStream(stream_id); +} + +s32 AvPlayer::Start() { + return m_state->Start(); +} + +bool AvPlayer::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetVideoData(video_info); +} + +bool AvPlayer::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetVideoData(video_info); +} + +bool AvPlayer::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (m_state == nullptr) { + return false; + } + return m_state->GetAudioData(audio_info); +} + +bool AvPlayer::IsActive() { + if (m_state == nullptr) { + return false; + } + return m_state->IsActive(); +} + +u64 AvPlayer::CurrentTime() { + if (m_state == nullptr) { + return 0; + } + return m_state->CurrentTime(); +} + +s32 AvPlayer::Stop() { + if (m_state == nullptr || !m_state->Stop()) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h new file mode 100644 index 000000000..fe3abcd1b --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" +#include "avplayer_state.h" + +#include "core/libraries/kernel/thread_management.h" + +extern "C" { +#include +#include +} + +#include +#include + +namespace Libraries::AvPlayer { + +class AvPlayer { +public: + // Memory Replacement + static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI Deallocate(void* handle, void* memory); + static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory); + + // File Replacement + static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename); + static int PS4_SYSV_ABI CloseFile(void* handle); + static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length); + static u64 PS4_SYSV_ABI SizeFile(void* handle); + + AvPlayer(); + + void Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); + + s32 PostInit(const SceAvPlayerPostInitData& data); + s32 AddSource(std::string_view filename); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_id); + s32 Start(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + bool IsActive(); + u64 CurrentTime(); + s32 Stop(); + +private: + using ScePthreadMutex = Kernel::ScePthreadMutex; + + std::unique_ptr m_state{}; + SceAvPlayerInitData m_init_data{}; + SceAvPlayerInitData m_init_data_original{}; + SceAvPlayerPostInitData m_post_init_data{}; + PthreadMutex m_file_io_mutex{}; + + std::atomic_bool m_has_source{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp new file mode 100644 index 000000000..c1dc6d579 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -0,0 +1,658 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_source.h" + +#include "avplayer_file_streamer.h" + +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "core/libraries/kernel/time_management.h" + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state) : m_state(state) {} + +AvPlayerSource::~AvPlayerSource() { + if (!m_video_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_video_frame_storage.data()); + } + if (!m_audio_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_audio_frame_storage.data()); + } + Stop(); +} + +s32 AvPlayerSource::Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, + SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type) { + if (m_avformat_context != nullptr) { + return -1; + } + + m_priorities = priorities; + m_memory_replacement = memory_replacement; + + m_avformat_context = avformat_alloc_context(); + if (m_avformat_context == nullptr) { + return -1; + } + if (file_replacement.open != nullptr) { + m_up_data_streamer = std::make_unique(file_replacement, path); + m_avformat_context->pb = m_up_data_streamer->GetContext(); + if (avformat_open_input(&m_avformat_context, nullptr, nullptr, nullptr) < 0) { + return -1; + } + } else { + const auto mnt = Common::Singleton::Instance(); + const auto filepath = mnt->GetHostPath(path); + if (AVPLAYER_IS_ERROR(avformat_open_input(&m_avformat_context, filepath.string().c_str(), + nullptr, nullptr))) { + return -1; + } + } + + return 0; +} + +bool AvPlayerSource::FindStreamInfo() { + if (m_avformat_context == nullptr) { + return false; + } + if (m_avformat_context->nb_streams > 0) { + return true; + } + return avformat_find_stream_info(m_avformat_context, nullptr) == 0; +} + +s32 AvPlayerSource::GetStreamCount() { + if (m_avformat_context == nullptr) { + return -1; + } + LOG_DEBUG(Lib_AvPlayer, "num streams: {}", m_avformat_context->nb_streams); + return m_avformat_context->nb_streams; +} + +static s32 CodecTypeToStreamType(AVMediaType codec_type) { + switch (codec_type) { + case AVMediaType::AVMEDIA_TYPE_VIDEO: + return SCE_AVPLAYER_VIDEO; + case AVMediaType::AVMEDIA_TYPE_AUDIO: + return SCE_AVPLAYER_AUDIO; + case AVMediaType::AVMEDIA_TYPE_SUBTITLE: + return SCE_AVPLAYER_TIMEDTEXT; + default: + LOG_ERROR(Lib_AvPlayer, "Unexpected AVMediaType {}", magic_enum::enum_name(codec_type)); + return -1; + } +} + +static f32 AVRationalToF32(AVRational rational) { + return f32(rational.num) / rational.den; +} + +s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + info = {}; + if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + return -1; + } + const auto p_stream = m_avformat_context->streams[stream_index]; + if (p_stream == nullptr || p_stream->codecpar == nullptr) { + return -1; + } + info.type = CodecTypeToStreamType(p_stream->codecpar->codec_type); + info.start_time = p_stream->start_time; + info.duration = p_stream->duration; + const auto p_lang_node = av_dict_get(p_stream->metadata, "language", nullptr, 0); + if (p_lang_node == nullptr) { + return -1; + } + LOG_DEBUG(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value); + switch (info.type) { + case SCE_AVPLAYER_VIDEO: + LOG_DEBUG(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); + info.details.video.aspect_ratio = AVRationalToF32(p_stream->codecpar->sample_aspect_ratio); + info.details.video.width = p_stream->codecpar->width; + info.details.video.height = p_stream->codecpar->height; + std::memcpy(info.details.video.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + case SCE_AVPLAYER_AUDIO: + LOG_DEBUG(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index); + info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels; + info.details.audio.sample_rate = p_stream->codecpar->sample_rate; + info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0 + std::memcpy(info.details.audio.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + case SCE_AVPLAYER_TIMEDTEXT: + LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index); + info.details.subs.font_size = 12; + info.details.subs.text_size = 12; + std::memcpy(info.details.subs.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + break; + default: + return -1; + } + return 0; +} + +static AVPixelFormat GetPreferredVideoFormat(AVCodecContext* s, const AVPixelFormat* fmt) { + auto curr = fmt; + while (*curr != AV_PIX_FMT_NONE) { + LOG_TRACE(Lib_AvPlayer, "Supported format: {}", magic_enum::enum_name(*fmt)); + if (*curr == AV_PIX_FMT_NV12) { + return AV_PIX_FMT_NV12; + } + ++curr; + } + return AV_PIX_FMT_NONE; +} + +s32 AvPlayerSource::EnableStream(u32 stream_index) { + if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + return -1; + } + const auto stream = m_avformat_context->streams[stream_index]; + const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id); + if (decoder == nullptr) { + return -1; + } + switch (stream->codecpar->codec_type) { + case AVMediaType::AVMEDIA_TYPE_VIDEO: { + m_video_stream_index = stream_index; + m_video_codec_context = + AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); + if (avcodec_parameters_to_context(m_video_codec_context.get(), stream->codecpar) < 0) { + return -1; + } + if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) { + return -1; + } + LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index); + break; + } + case AVMediaType::AVMEDIA_TYPE_AUDIO: { + m_audio_stream_index = stream_index; + m_audio_codec_context = + AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); + if (avcodec_parameters_to_context(m_audio_codec_context.get(), stream->codecpar) < 0) { + return -1; + } + if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) { + return -1; + } + LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); + break; + } + default: + LOG_WARNING(Lib_AvPlayer, "Unknown stream type {} for stream {}", + magic_enum::enum_name(stream->codecpar->codec_type), stream_index); + break; + } + return 0; +} + +u8* AvPlayerSource::GetVideoBuffer(AVFrame* frame) { + const auto size = (frame->width * frame->height * 3) / 2; + if (m_video_frame_storage.size() < size) { + if (!m_video_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_video_frame_storage.data()); + } + const auto ptr = reinterpret_cast( + m_memory_replacement.allocate(m_memory_replacement.object_ptr, frame->width, size)); + m_video_frame_storage = std::span(ptr, size); + } + return m_video_frame_storage.data(); +} + +u8* AvPlayerSource::GetAudioBuffer(AVFrame* frame) { + const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16); + if (m_audio_frame_storage.size() < size) { + if (!m_audio_frame_storage.empty()) { + m_memory_replacement.deallocate(m_memory_replacement.object_ptr, + m_audio_frame_storage.data()); + } + const auto ptr = reinterpret_cast( + m_memory_replacement.allocate(m_memory_replacement.object_ptr, 0x40, size)); + m_audio_frame_storage = std::span(ptr, size); + } + return m_audio_frame_storage.data(); +} + +void AvPlayerSource::SetLooping(bool is_looping) { + m_is_looping = is_looping; +} + +std::optional AvPlayerSource::HasFrames(u32 num_frames) { + return m_video_frames.Size() > num_frames; +} + +s32 AvPlayerSource::Start() { + if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { + return -1; + } + { + ThreadParameters demuxer_params{ + .p_user_data = this, + .thread_name = "AvPlayer_Demuxer", + .stack_size = 0x4000, + .priority = m_priorities.demuxer_priority, + .affinity = m_priorities.demuxer_affinity, + }; + m_demuxer_thread = CreateThread(&DemuxerThread, demuxer_params); + if (m_demuxer_thread == nullptr) { + return -1; + } + } + if (m_video_codec_context != nullptr) { + ThreadParameters video_decoder_params{ + .p_user_data = this, + .thread_name = "AvPlayer_VideoDecoder", + .stack_size = 0x4000, + .priority = m_priorities.video_decoder_priority, + .affinity = m_priorities.video_decoder_affinity, + }; + m_video_decoder_thread = CreateThread(&VideoDecoderThread, video_decoder_params); + if (m_video_decoder_thread == nullptr) { + return -1; + } + } + if (m_audio_codec_context != nullptr) { + ThreadParameters audio_decoder_params{ + .p_user_data = this, + .thread_name = "AvPlayer_AudioDecoder", + .stack_size = 0x4000, + .priority = m_priorities.audio_decoder_priority, + .affinity = m_priorities.audio_decoder_affinity, + }; + m_audio_decoder_thread = CreateThread(&AudioDecoderThread, audio_decoder_params); + if (m_audio_decoder_thread == nullptr) { + return -1; + } + } + m_start_time = std::chrono::high_resolution_clock::now(); + return 0; +} + +bool AvPlayerSource::Stop() { + m_is_stop = true; + + void* res = nullptr; + if (m_video_decoder_thread != nullptr) { + scePthreadJoin(m_video_decoder_thread, &res); + } + if (m_audio_decoder_thread != nullptr) { + scePthreadJoin(m_audio_decoder_thread, &res); + } + if (m_demuxer_thread != nullptr) { + scePthreadJoin(m_demuxer_thread, &res); + } + return true; +} + +bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) { + SceAvPlayerFrameInfoEx info{}; + if (!GetVideoData(info)) { + return false; + } + video_info = {}; + video_info.timestamp = u64(info.timestamp); + video_info.pData = reinterpret_cast(info.pData); + video_info.details.video.aspect_ratio = info.details.video.aspect_ratio; + video_info.details.video.width = info.details.video.width; + video_info.details.video.height = info.details.video.height; + return true; +} + +static void CopyNV12Data(u8* dst, const AVFrame& src) { + std::memcpy(dst, src.data[0], src.width * src.height); + std::memcpy(dst + src.width * src.height, src.data[1], (src.width * src.height) / 2); +} + +bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + while (m_video_frames.Size() == 0 && !m_is_eof) { + sceKernelUsleep(5000); + } + + auto frame = m_video_frames.Pop(); + if (!frame.has_value()) { + return false; + } + + auto& up_frame = *frame; + + const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_video_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + { + using namespace std::chrono; + auto elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + while (elapsed_time < timestamp) { + sceKernelUsleep((timestamp - elapsed_time) * 1000); + elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + } + } + + auto buffer = GetVideoBuffer(up_frame.get()); + + CopyNV12Data(buffer, *up_frame); + + video_info = {}; + video_info.timestamp = timestamp; + video_info.pData = buffer; + video_info.details.video.width = up_frame->width; + video_info.details.video.height = up_frame->height; + video_info.details.video.aspect_ratio = AVRationalToF32(up_frame->sample_aspect_ratio); + video_info.details.video.pitch = up_frame->linesize[0]; + video_info.details.video.luma_bit_depth = 8; + video_info.details.video.chroma_bit_depth = 8; + + m_last_video_timestamp = timestamp; + return true; +} + +bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + while (m_audio_frames.Size() == 0 && !m_is_eof) { + sceKernelUsleep(5000); + } + + auto frame = m_audio_frames.Pop(); + if (!frame.has_value()) { + return false; + } + + const auto& up_frame = *frame; + + const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_audio_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + { + using namespace std::chrono; + auto elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + while (elapsed_time < timestamp) { + sceKernelUsleep((timestamp - elapsed_time) * 1000); + elapsed_time = + duration_cast(high_resolution_clock::now() - m_start_time).count(); + } + } + + auto buffer = GetAudioBuffer(up_frame.get()); + const auto size = up_frame->ch_layout.nb_channels * up_frame->nb_samples * sizeof(u16); + std::memcpy(buffer, up_frame->data[0], size); + + audio_info = {}; + audio_info.timestamp = timestamp; + audio_info.pData = m_audio_frame_storage.data(); + audio_info.details.audio.size = u32(m_audio_frame_storage.size()); + audio_info.details.audio.channel_count = up_frame->ch_layout.nb_channels; + return true; +} + +u64 AvPlayerSource::CurrentTime() { + // using namespace std::chrono; + // return duration_cast(high_resolution_clock::now() - m_start_time).count(); + return m_last_video_timestamp; +} + +bool AvPlayerSource::IsActive() { + return !m_is_stop && (!m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 || + m_video_frames.Size() != 0 || m_audio_frames.Size() != 0); +} + +void AvPlayerSource::ReleaseAVPacket(AVPacket* packet) { + if (packet != nullptr) { + av_packet_free(&packet); + } +} + +void AvPlayerSource::ReleaseAVFrame(AVFrame* frame) { + if (frame != nullptr) { + av_frame_free(&frame); + } +} + +void AvPlayerSource::ReleaseAVCodecContext(AVCodecContext* context) { + if (context != nullptr) { + avcodec_free_context(&context); + } +} + +void AvPlayerSource::ReleaseSWRContext(SwrContext* context) { + if (context != nullptr) { + swr_free(&context); + } +} + +void AvPlayerSource::ReleaseSWSContext(SwsContext* context) { + if (context != nullptr) { + sws_freeContext(context); + } +} + +void* AvPlayerSource::DemuxerThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Demuxer Thread started"); + const auto self = reinterpret_cast(opaque); + if (!self->m_audio_stream_index.has_value() && !self->m_video_stream_index.has_value()) { + return nullptr; + } + + while (!self->m_is_stop) { + if (self->m_video_packets.Size() > 60) { + sceKernelUsleep(5000); + continue; + } + AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); + const auto res = av_read_frame(self->m_avformat_context, up_packet.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in demuxer"); + break; + } else { + LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); + self->m_state.OnError(); + scePthreadExit(0); + } + break; + } + if (up_packet->stream_index == self->m_video_stream_index) { + self->m_video_packets.Push(std::move(up_packet)); + } else if (up_packet->stream_index == self->m_audio_stream_index) { + self->m_audio_packets.Push(std::move(up_packet)); + } + } + + self->m_is_eof = true; + + void* res; + if (self->m_video_decoder_thread) { + scePthreadJoin(self->m_video_decoder_thread, &res); + } + if (self->m_audio_decoder_thread) { + scePthreadJoin(self->m_audio_decoder_thread, &res); + } + self->m_state.OnEOF(); + + LOG_TRACE(Lib_AvPlayer, "Demuxer Thread exited normaly"); + scePthreadExit(0); +} + +AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { + auto nv12_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; + nv12_frame->pts = frame.pts; + nv12_frame->pkt_dts = frame.pkt_dts; + nv12_frame->format = AV_PIX_FMT_NV12; + nv12_frame->width = frame.width; + nv12_frame->height = frame.height; + nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio; + + av_frame_get_buffer(nv12_frame.get(), 0); + + if (m_sws_context == nullptr) { + m_sws_context = + SWSContextPtr(sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format), + frame.width, frame.height, AV_PIX_FMT_NV12, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr), + &ReleaseSWSContext); + } + const auto res = sws_scale(m_sws_context.get(), frame.data, frame.linesize, 0, frame.height, + nv12_frame->data, nv12_frame->linesize); + if (res < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res)); + return AVFramePtr{nullptr, &ReleaseAVFrame}; + } + return nv12_frame; +} + +void* AvPlayerSource::VideoDecoderThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread started"); + const auto self = reinterpret_cast(opaque); + + while ((!self->m_is_eof || self->m_video_packets.Size() != 0) && !self->m_is_stop) { + if (self->m_video_frames.Size() > 60 || self->m_video_packets.Size() == 0) { + sceKernelUsleep(5000); + continue; + } + const auto packet = self->m_video_packets.Pop(); + if (!packet.has_value()) { + continue; + } + auto res = avcodec_send_packet(self->m_video_codec_context.get(), packet->get()); + if (res < 0 && res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + while (res >= 0) { + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); + res = avcodec_receive_frame(self->m_video_codec_context.get(), up_frame.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in video decoder"); + scePthreadExit(nullptr); + } else if (res != AVERROR(EAGAIN)) { + LOG_ERROR(Lib_AvPlayer, + "Could not receive frame from the video codec. Error = {}", + av_err2str(res)); + self->m_state.OnError(); + scePthreadExit(nullptr); + } + } else { + LOG_TRACE(Lib_AvPlayer, "Producing Video Frame. Num Frames: {}", + self->m_video_frames.Size()); + if (up_frame->format != AV_PIX_FMT_NV12) { + self->m_video_frames.Push(self->ConvertVideoFrame(*up_frame)); + } else { + self->m_video_frames.Push(std::move(up_frame)); + } + } + } + } + + LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + scePthreadExit(nullptr); +} + +AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { + auto pcm16_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; + pcm16_frame->pts = frame.pts; + pcm16_frame->pkt_dts = frame.pkt_dts; + pcm16_frame->format = AV_SAMPLE_FMT_S16; + pcm16_frame->ch_layout = frame.ch_layout; + pcm16_frame->sample_rate = frame.sample_rate; + + if (m_swr_context == nullptr) { + SwrContext* swr_context = nullptr; + AVChannelLayout in_ch_layout = frame.ch_layout; + AVChannelLayout out_ch_layout = frame.ch_layout; + swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame.sample_rate, + &in_ch_layout, AVSampleFormat(frame.format), frame.sample_rate, 0, + nullptr); + m_swr_context = SWRContextPtr(swr_context, &ReleaseSWRContext); + swr_init(m_swr_context.get()); + } + const auto res = swr_convert_frame(m_swr_context.get(), pcm16_frame.get(), &frame); + if (res < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res)); + return AVFramePtr{nullptr, &ReleaseAVFrame}; + } + return pcm16_frame; +} + +void* AvPlayerSource::AudioDecoderThread(void* opaque) { + LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread started"); + const auto self = reinterpret_cast(opaque); + + while ((!self->m_is_eof || self->m_audio_packets.Size() != 0) && !self->m_is_stop) { + if (self->m_audio_frames.Size() > 60 || self->m_audio_packets.Size() == 0) { + sceKernelUsleep(5000); + continue; + } + const auto packet = self->m_audio_packets.Pop(); + if (!packet.has_value()) { + continue; + } + auto res = avcodec_send_packet(self->m_audio_codec_context.get(), packet->get()); + if (res < 0 && res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + while (res >= 0) { + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); + res = avcodec_receive_frame(self->m_audio_codec_context.get(), up_frame.get()); + if (res < 0) { + if (res == AVERROR_EOF) { + LOG_TRACE(Lib_AvPlayer, "EOF reached in audio decoder"); + scePthreadExit(nullptr); + } else if (res != AVERROR(EAGAIN)) { + self->m_state.OnError(); + LOG_ERROR(Lib_AvPlayer, + "Could not receive frame from the audio codec. Error = {}", + av_err2str(res)); + scePthreadExit(nullptr); + } + } else { + if (up_frame->format != AV_SAMPLE_FMT_S16) { + self->m_audio_frames.Push(self->ConvertAudioFrame(*up_frame)); + } else { + self->m_audio_frames.Push(std::move(up_frame)); + } + } + } + } + + LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + scePthreadExit(nullptr); +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h new file mode 100644 index 000000000..81f77a627 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_common.h" +#include "avplayer_data_streamer.h" + +#include "common/types.h" +#include "core/libraries/kernel/thread_management.h" + +#include +#include +#include +#include + +struct AVCodecContext; +struct AVFormatContext; +struct AVFrame; +struct AVIOContext; +struct AVPacket; +struct SwrContext; +struct SwsContext; + +namespace Libraries::AvPlayer { + +class AvPlayerStateCallback { +public: + virtual ~AvPlayerStateCallback() = default; + + virtual void OnWarning(u32 id) = 0; + virtual void OnError() = 0; + virtual void OnEOF() = 0; +}; + +class FrameBuffer { +public: + FrameBuffer(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) noexcept + : m_memory_replacement(memory_replacement), + m_data(Allocate(memory_replacement, align, size), size) {} + + ~FrameBuffer() { + if (!m_data.empty()) { + Deallocate(m_memory_replacement, m_data.data()); + m_data = {}; + } + } + + FrameBuffer(const FrameBuffer&) noexcept = delete; + FrameBuffer& operator=(const FrameBuffer&) noexcept = delete; + + FrameBuffer(FrameBuffer&& r) noexcept + : m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) { + r.m_data = {}; + }; + + FrameBuffer& operator=(FrameBuffer&& r) noexcept { + m_memory_replacement = r.m_memory_replacement; + std::swap(m_data, r.m_data); + return *this; + } + + u8* GetBuffer() const noexcept { + return m_data.data(); + } + +private: + static u8* Allocate(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) { + return reinterpret_cast( + memory_replacement.allocate(memory_replacement.object_ptr, align, size)); + } + + static void Deallocate(const SceAvPlayerMemAllocator& memory_replacement, void* ptr) { + memory_replacement.deallocate(memory_replacement.object_ptr, ptr); + } + + SceAvPlayerMemAllocator m_memory_replacement; + std::span m_data; +}; + +class AvPlayerSource { +public: + AvPlayerSource(AvPlayerStateCallback& state); + ~AvPlayerSource(); + + s32 Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, + SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type); + + bool FindStreamInfo(); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_index); + void SetLooping(bool is_looping); + std::optional HasFrames(u32 num_frames); + s32 Start(); + bool Stop(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + u64 CurrentTime(); + bool IsActive(); + +private: + using ScePthread = Kernel::ScePthread; + + static void* PS4_SYSV_ABI DemuxerThread(void* opaque); + static void* PS4_SYSV_ABI VideoDecoderThread(void* opaque); + static void* PS4_SYSV_ABI AudioDecoderThread(void* opaque); + + static void ReleaseAVPacket(AVPacket* packet); + static void ReleaseAVFrame(AVFrame* frame); + static void ReleaseAVCodecContext(AVCodecContext* context); + static void ReleaseSWRContext(SwrContext* context); + static void ReleaseSWSContext(SwsContext* context); + + using AVPacketPtr = std::unique_ptr; + using AVFramePtr = std::unique_ptr; + using AVCodecContextPtr = std::unique_ptr; + using SWRContextPtr = std::unique_ptr; + using SWSContextPtr = std::unique_ptr; + + u8* GetVideoBuffer(AVFrame* frame); + u8* GetAudioBuffer(AVFrame* frame); + + AVFramePtr ConvertAudioFrame(const AVFrame& frame); + AVFramePtr ConvertVideoFrame(const AVFrame& frame); + + u64 m_last_video_timestamp{}; + + AvPlayerStateCallback& m_state; + + ThreadPriorities m_priorities; + SceAvPlayerMemAllocator m_memory_replacement; + + std::atomic_bool m_is_looping = false; + std::atomic_bool m_is_eof = false; + std::atomic_bool m_is_stop = false; + std::unique_ptr m_up_data_streamer; + + AVFormatContext* m_avformat_context{}; + + AvPlayerQueue m_audio_packets; + AvPlayerQueue m_video_packets; + + AvPlayerQueue m_audio_frames; + AvPlayerQueue m_video_frames; + + std::span m_video_frame_storage; + std::span m_audio_frame_storage; + + AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; + AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext}; + + std::optional m_video_stream_index{}; + std::optional m_audio_stream_index{}; + + ScePthread m_demuxer_thread{}; + ScePthread m_video_decoder_thread{}; + ScePthread m_audio_decoder_thread{}; + + SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext}; + SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext}; + + std::chrono::high_resolution_clock::time_point m_start_time{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp new file mode 100644 index 000000000..3048819ec --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -0,0 +1,481 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "avplayer_file_streamer.h" +#include "avplayer_source.h" +#include "avplayer_state.h" + +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/time_management.h" + +#include + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id, + void* event_data) { + auto const self = reinterpret_cast(opaque); + + if (event_id == SCE_AVPLAYER_STATE_READY) { + s32 video_stream_index = -1; + s32 audio_stream_index = -1; + s32 timedtext_stream_index = -1; + const s32 stream_count = self->GetStreamCount(); + if (AVPLAYER_IS_ERROR(stream_count)) { + return; + } + if (stream_count == 0) { + self->Stop(); + return; + } + for (u32 stream_index = 0; stream_index < stream_count; ++stream_index) { + SceAvPlayerStreamInfo info{}; + self->GetStreamInfo(stream_index, info); + + const std::string_view default_language( + reinterpret_cast(self->m_default_language)); + switch (info.type) { + case SCE_AVPLAYER_VIDEO: + if (video_stream_index == -1) { + if (default_language.empty()) { + video_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + video_stream_index = stream_index; + } + } + break; + case SCE_AVPLAYER_AUDIO: + if (audio_stream_index == -1) { + if (default_language.empty()) { + audio_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + audio_stream_index = stream_index; + } + } + break; + case SCE_AVPLAYER_TIMEDTEXT: + if (timedtext_stream_index == -1) { + if (default_language.empty()) { + timedtext_stream_index = stream_index; + break; + } + if (default_language == + reinterpret_cast(info.details.video.language_code)) { + timedtext_stream_index = stream_index; + } + } + break; + } + } + + if (video_stream_index != -1) { + self->EnableStream(video_stream_index); + } + if (audio_stream_index != -1) { + self->EnableStream(audio_stream_index); + } + if (timedtext_stream_index != -1) { + self->EnableStream(timedtext_stream_index); + } + self->Start(); + return; + } + + const auto callback = self->m_user_event_replacement.event_callback; + const auto ptr = self->m_user_event_replacement.object_ptr; + if (callback != nullptr) { + callback(ptr, event_id, 0, event_data); + } +} + +// Called inside GAME thread +AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, + const ThreadPriorities& priorities) + : m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerEventHandler"), + m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerStateMachine") { + if (init_data.event_replacement.event_callback == nullptr || init_data.auto_start) { + m_auto_start = true; + m_event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; + m_event_replacement.object_ptr = this; + } else { + m_event_replacement = init_data.event_replacement; + } + m_user_event_replacement = init_data.event_replacement; + + const auto& memory_replacement = init_data.memory_replacement; + if (memory_replacement.allocate != nullptr && memory_replacement.deallocate != nullptr && + memory_replacement.allocate_texture != nullptr && + memory_replacement.deallocate_texture != nullptr) { + m_memory_replacement = memory_replacement; + } + + if (init_data.event_replacement.event_callback != nullptr) { + m_event_replacement = init_data.event_replacement; + m_auto_start = init_data.auto_start; + } else { + m_auto_start = true; + } + + m_file_replacement = init_data.file_replacement; + m_thread_priorities = priorities; + + if (init_data.default_language != nullptr) { + std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language)); + } + SetState(AvState::Initial); + StartControllerThread(); +} + +AvPlayerState::~AvPlayerState() { + m_event_queue.Clear(); + if (m_up_source && m_current_state == AvState::Play) { + m_up_source->Stop(); + OnPlaybackStateChanged(AvState::Stop); + } +} + +// Called inside GAME thread +s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) { + if (path.empty()) { + return -1; + } + + if (m_up_source) { + return -1; + } + + m_up_source = std::make_unique(*this); + if (AVPLAYER_IS_ERROR(m_up_source->Init(path, m_memory_replacement, m_file_replacement, + m_thread_priorities, source_type))) { + return -1; + } + AddSourceEvent(); + + return 0; +} + +// Called inside GAME thread +s32 AvPlayerState::GetStreamCount() { + return m_up_source->GetStreamCount(); +} + +// Called inside GAME thread +s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + return m_up_source->GetStreamInfo(stream_index, info); +} + +// Called inside GAME thread +s32 AvPlayerState::Start() { + if (m_up_source == nullptr) { + return -1; + } + return m_up_source->Start(); +} + +void* AvPlayerState::AvControllerThread(void* p_user_data) { + AvPlayerState* self = reinterpret_cast(p_user_data); + while (self->m_quit.load() == 0) { + if (self->m_event_queue.Size() != 0) { + self->ProcessEvent(); + continue; + } + sceKernelUsleep(5000); + self->UpdateBufferingState(); + } + scePthreadExit(0); +} + +// Called inside GAME thread +void AvPlayerState::AddSourceEvent() { + SetState(AvState::AddingSource); + m_event_queue.Push(AvPlayerEvent{ + .event = AvEventType::AddSource, + }); +} + +// Called inside GAME thread +int AvPlayerState::StartControllerThread() { + m_quit.store(0); + + ThreadParameters params{ + .p_user_data = this, + .thread_name = "AvPlayer_Controller", + .stack_size = 0x4000, + .priority = m_thread_priority, + .affinity = m_thread_affinity, + }; + m_controller_thread = CreateThread(&AvControllerThread, params); + if (m_controller_thread == nullptr) { + return -1; + } + return 0; +} + +// Called inside GAME thread +s32 AvPlayerState::EnableStream(u32 stream_id) { + if (m_up_source == nullptr) { + return -1; + } + return m_up_source->EnableStream(stream_id); +} + +// Called inside GAME thread +bool AvPlayerState::Stop() { + if (m_up_source == nullptr) { + return false; + } + SetState(AvState::Stop); + OnPlaybackStateChanged(AvState::Stop); + return m_up_source->Stop(); +} + +bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetVideoData(video_info); +} + +bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetVideoData(video_info); +} + +bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (m_up_source == nullptr) { + return false; + } + return m_up_source->GetAudioData(audio_info); +} + +bool AvPlayerState::IsActive() { + if (m_up_source == nullptr) { + return false; + } + return m_current_state != AvState::Stop && m_current_state != AvState::Error && + m_up_source->IsActive(); +} + +u64 AvPlayerState::CurrentTime() { + if (m_up_source == nullptr) { + return 0; + } + return m_up_source->CurrentTime(); +} + +void AvPlayerState::OnWarning(u32 id) { + EmitEvent(SCE_AVPLAYER_WARNING_ID, &id); +} + +void AvPlayerState::OnError() { + SetState(AvState::Error); + OnPlaybackStateChanged(AvState::Error); +} + +void AvPlayerState::OnEOF() { + Stop(); +} + +// Called inside CONTROLLER thread +void AvPlayerState::OnPlaybackStateChanged(AvState state) { + switch (state) { + case AvState::Ready: { + EmitEvent(SCE_AVPLAYER_STATE_READY); + break; + } + case AvState::Play: { + EmitEvent(SCE_AVPLAYER_STATE_PLAY); + break; + } + case AvState::Stop: { + EmitEvent(SCE_AVPLAYER_STATE_STOP); + break; + } + case AvState::Pause: { + EmitEvent(SCE_AVPLAYER_STATE_PAUSE); + break; + } + case AvState::Buffering: { + EmitEvent(SCE_AVPLAYER_STATE_BUFFERING); + break; + } + default: + break; + } +} + +// Called inside CONTROLLER and GAME threads +bool AvPlayerState::SetState(AvState state) { + std::lock_guard guard(m_state_machine_mutex); + + if (!IsStateTransitionValid(state)) { + return false; + } + m_previous_state.store(m_current_state); + m_current_state.store(state); + return true; +} + +// Called inside CONTROLLER thread +std::optional AvPlayerState::OnBufferingCheckEvent(u32 num_frames) { + if (!m_up_source) { + return std::nullopt; + } + return m_up_source->HasFrames(num_frames); +} + +// Called inside CONTROLLER thread +void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { + LOG_INFO(Lib_AvPlayer, "Sending event to the game: id = {}", magic_enum::enum_name(event_id)); + const auto callback = m_event_replacement.event_callback; + if (callback) { + const auto ptr = m_event_replacement.object_ptr; + callback(ptr, event_id, 0, event_data); + } +} + +// Called inside CONTROLLER thread +int AvPlayerState::ProcessEvent() { + if (m_current_state.load() == AvState::Jump) { + return -2; + } + + std::lock_guard guard(m_event_handler_mutex); + + auto event = m_event_queue.Pop(); + if (!event.has_value()) { + return -1; + } + switch (event->event) { + case AvEventType::RevertState: { + SetState(m_previous_state.load()); + break; + } + case AvEventType::AddSource: { + if (m_up_source->FindStreamInfo()) { + SetState(AvState::Ready); + OnPlaybackStateChanged(AvState::Ready); + } else { + OnWarning(ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED); + SetState(AvState::Error); + } + break; + } + case AvEventType::Error: { + OnWarning(event->payload.error); + SetState(AvState::Error); + break; + } + default: + break; + } + + return 0; +} + +// Called inside CONTROLLER thread +int AvPlayerState::UpdateBufferingState() { + if (m_current_state == AvState::Buffering) { + const auto has_frames = OnBufferingCheckEvent(10); + if (!has_frames.has_value()) { + return -1; + } + if (has_frames.value()) { + const auto state = + m_previous_state >= AvState::C0x0B ? m_previous_state.load() : AvState::Play; + SetState(state); + OnPlaybackStateChanged(state); + } + } else if (m_current_state == AvState::Play) { + const auto has_frames = OnBufferingCheckEvent(0); + if (!has_frames.has_value()) { + return -1; + } + if (!has_frames.value()) { + SetState(AvState::Buffering); + OnPlaybackStateChanged(AvState::Buffering); + } + } + return 0; +} + +bool AvPlayerState::IsStateTransitionValid(AvState state) { + switch (state) { + case AvState::Play: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Pause: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Jump: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::TrickMode: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::TrickMode: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + // case AvState::C0x08: + case AvState::Jump: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + case AvState::Buffering: { + switch (m_current_state.load()) { + case AvState::Stop: + case AvState::EndOfFile: + case AvState::Pause: + // case AvState::C0x08: + case AvState::Starting: + case AvState::Error: + return false; + default: + return true; + } + } + default: + return true; + } +} + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h new file mode 100644 index 000000000..fc822884d --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "avplayer.h" +#include "avplayer_data_streamer.h" +#include "avplayer_source.h" + +#include "core/libraries/kernel/thread_management.h" + +#include + +namespace Libraries::AvPlayer { + +class Stream; +class AvDecoder; + +class AvPlayerState : public AvPlayerStateCallback { +public: + AvPlayerState(const SceAvPlayerInitData& init_data, const ThreadPriorities& priorities); + ~AvPlayerState(); + + s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); + s32 GetStreamCount(); + s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); + s32 EnableStream(u32 stream_id); + s32 Start(); + bool Stop(); + bool GetAudioData(SceAvPlayerFrameInfo& audio_info); + bool GetVideoData(SceAvPlayerFrameInfo& video_info); + bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); + bool IsActive(); + u64 CurrentTime(); + +private: + using ScePthreadMutex = Kernel::ScePthreadMutex; + using ScePthread = Kernel::ScePthread; + + // Event Replacement + static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id, + void* event_data); + + void OnWarning(u32 id) override; + void OnError() override; + void OnEOF() override; + + void OnPlaybackStateChanged(AvState state); + std::optional OnBufferingCheckEvent(u32 num_frames); + + void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr); + bool SetState(AvState state); + + static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); + + void AddSourceEvent(); + int StartControllerThread(); + int ProcessEvent(); + int UpdateBufferingState(); + bool IsStateTransitionValid(AvState state); + + std::unique_ptr m_up_source; + + SceAvPlayerMemAllocator m_memory_replacement{}; + SceAvPlayerFileReplacement m_file_replacement{}; + SceAvPlayerEventReplacement m_event_replacement{}; + SceAvPlayerEventReplacement m_user_event_replacement{}; + ThreadPriorities m_thread_priorities{}; + bool m_auto_start{}; + u8 m_default_language[4]{}; + + std::atomic_int32_t m_quit; + std::atomic m_current_state; + std::atomic m_previous_state; + u32 m_thread_priority; + u32 m_thread_affinity; + std::atomic_uint32_t m_some_event_result{}; + + PthreadMutex m_state_machine_mutex{}; + PthreadMutex m_event_handler_mutex{}; + ScePthread m_controller_thread{}; + AvPlayerQueue m_event_queue{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 5eabaaf67..74aeef674 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -457,5 +457,18 @@ constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622; +// AvPlayer library +constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001; +constexpr int ORBIS_AVPLAYER_ERROR_OPERATION_FAILED = 0x806A0002; +constexpr int ORBIS_AVPLAYER_ERROR_NO_MEMORY = 0x806A0003; +constexpr int ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED = 0x806A0004; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_FILE_NONINTERLEAVED = 0x806A00A0; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_LOOPING_BACK = 0x806A00A1; +constexpr int ORBIS_AVPLAYER_ERROR_WAR_JUMP_COMPLETE = 0x806A00A3; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_MARLIN_ENCRY = 0x806A00B0; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_PLAYREADY_ENCRY = 0x806A00B4; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5; +constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF; + // AppContent library constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index c5935275f..bd555ccc6 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -158,19 +158,24 @@ void init_pthreads(); void pthreadInitSelfMainThread(); int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr); +int PS4_SYSV_ABI scePthreadAttrDestroy(ScePthreadAttr* attr); int PS4_SYSV_ABI scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate); int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched); int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr, const SceKernelSchedParam* param); int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy); -ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask); +int PS4_SYSV_ABI scePthreadAttrSetstacksize(ScePthreadAttr* attr, size_t stack_size); + +ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask); int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask); int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr, PthreadEntryFunc start_routine, void* arg, const char* name); - +[[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr); +int PS4_SYSV_ABI scePthreadJoin(ScePthread thread, void** res); +int PS4_SYSV_ABI scePthreadGetprio(ScePthread thread, int* prio); int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); /*** @@ -178,11 +183,14 @@ int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); */ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name); +int PS4_SYSV_ABI scePthreadMutexDestroy(ScePthreadMutex* mutex); +int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); +int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); + +int PS4_SYSV_ABI scePthreadMutexattrDestroy(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrInit(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type); int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol); -int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); -int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); /**** * Cond calls */ diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index a28f8c133..8934171d0 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -50,6 +50,7 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); +int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym); From e33ff10212113a4756d88f125a325e1a55168f68 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Mon, 12 Aug 2024 19:01:02 +0300 Subject: [PATCH 103/109] Added some logs, fixed some crashes, fixed align. --- src/audio_core/sdl_audio.cpp | 2 +- src/core/libraries/avplayer/avplayer.cpp | 16 +- src/core/libraries/avplayer/avplayer.h | 2 + .../avplayer/avplayer_file_streamer.cpp | 33 +- .../avplayer/avplayer_file_streamer.h | 5 +- src/core/libraries/avplayer/avplayer_impl.cpp | 18 +- src/core/libraries/avplayer/avplayer_impl.h | 6 +- .../libraries/avplayer/avplayer_source.cpp | 406 ++++++++++-------- src/core/libraries/avplayer/avplayer_source.h | 67 +-- .../libraries/avplayer/avplayer_state.cpp | 160 +++---- src/core/libraries/avplayer/avplayer_state.h | 10 +- 11 files changed, 398 insertions(+), 327 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 141d338e8..aac67d8cd 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -100,7 +100,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { int result = SDL_PutAudioStreamData(port.stream, ptr, port.samples_num * port.sample_size * port.channels_num); // TODO find a correct value 8192 is estimated - while (SDL_GetAudioStreamAvailable(port.stream) > 8192) { + while (SDL_GetAudioStreamAvailable(port.stream) > 65536) { SDL_Delay(0); } diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 41f8d0766..e8e99bcaa 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -9,7 +9,9 @@ #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" -#include +#include // std::max, std::min + +#include // va_list namespace Libraries::AvPlayer { @@ -148,9 +150,7 @@ SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { // priorities.file_streaming_priority = GetPriority(priorities.http_streaming_priority, 15); // priorities.maxPriority = priorities.http_streaming_priority; - const auto player = new AvPlayer(); - player->Init(*data, priorities); - return player; + return new AvPlayer(*data, priorities); } s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, @@ -225,9 +225,7 @@ s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, // } // priorities.http_streaming_affinity = p_data->http_streaming_affinity; - const auto player = new AvPlayer(); - player->Init(data, priorities); - *p_player = player; + *p_player = new AvPlayer(data, priorities); return ORBIS_OK; } @@ -290,13 +288,13 @@ s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle, return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback logCb, void* user_data) { +s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* user_data) { LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, looping = {}", loop_flag); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index f5589441a..e88419c19 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -5,6 +5,8 @@ #include "common/types.h" +#include // size_t + namespace Core::Loader { class SymbolsResolver; } diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 1c54c4545..43055fd59 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -12,23 +12,30 @@ extern "C" { #include } +#include // std::max, std::min + #define AVPLAYER_AVIO_BUFFER_SIZE 4096 namespace Libraries::AvPlayer { -AvPlayerFileStreamer::AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, +AvPlayerFileStreamer::AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path) : m_file_replacement(file_replacement) { - Init(path); + const auto ptr = m_file_replacement.object_ptr; + m_fd = m_file_replacement.open(ptr, path.data()); + ASSERT(m_fd >= 0); + m_file_size = m_file_replacement.size(ptr); + // avio_buffer is deallocated in `avio_context_free` + const auto avio_buffer = reinterpret_cast(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE)); + m_avio_context = + avio_alloc_context(avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this, + &AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek); } AvPlayerFileStreamer::~AvPlayerFileStreamer() { if (m_avio_context != nullptr) { avio_context_free(&m_avio_context); } - if (m_avio_buffer != nullptr) { - av_free(m_avio_buffer); - } if (m_file_replacement.close != nullptr && m_fd >= 0) { const auto close = m_file_replacement.close; const auto ptr = m_file_replacement.object_ptr; @@ -36,20 +43,6 @@ AvPlayerFileStreamer::~AvPlayerFileStreamer() { } } -s32 AvPlayerFileStreamer::Init(std::string_view path) { - const auto ptr = m_file_replacement.object_ptr; - m_fd = m_file_replacement.open(ptr, path.data()); - if (m_fd < 0) { - return -1; - } - m_file_size = m_file_replacement.size(ptr); - m_avio_buffer = reinterpret_cast(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE)); - m_avio_context = - avio_alloc_context(m_avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this, - &AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek); - return 0; -} - s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) { const auto self = reinterpret_cast(opaque); if (self->m_position >= self->m_file_size) { @@ -61,7 +54,7 @@ s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) { const auto read_offset = self->m_file_replacement.readOffset; const auto ptr = self->m_file_replacement.object_ptr; const auto bytes_read = read_offset(ptr, buffer, self->m_position, size); - if (size != 0 && bytes_read == 0) { + if (bytes_read == 0 && size != 0) { return AVERROR_EOF; } self->m_position += bytes_read; diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.h b/src/core/libraries/avplayer/avplayer_file_streamer.h index 9f1442f98..658ce8c1e 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.h +++ b/src/core/libraries/avplayer/avplayer_file_streamer.h @@ -15,7 +15,7 @@ namespace Libraries::AvPlayer { class AvPlayerFileStreamer : public IDataStreamer { public: - AvPlayerFileStreamer(SceAvPlayerFileReplacement& file_replacement, std::string_view path); + AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path); ~AvPlayerFileStreamer(); AVIOContext* GetContext() override { @@ -23,8 +23,6 @@ public: } private: - s32 Init(std::string_view path); - static s32 ReadPacket(void* opaque, u8* buffer, s32 size); static s64 Seek(void* opaque, s64 buffer, int whence); @@ -33,7 +31,6 @@ private: int m_fd = -1; u64 m_position{}; u64 m_file_size{}; - u8* m_avio_buffer{}; AVIOContext* m_avio_context{}; }; diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 58793da7f..f08356bd0 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -77,11 +77,9 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { return size(ptr); } -AvPlayer::AvPlayer() : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerFileIOLock") {} - -void AvPlayer::Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) { - m_init_data = data; - m_init_data_original = data; +AvPlayer::AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) + : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_FileIO"), m_init_data(data), + m_init_data_original(data) { m_init_data.memory_replacement.object_ptr = this; m_init_data.memory_replacement.allocate = &AvPlayer::Allocate; @@ -120,6 +118,9 @@ s32 AvPlayer::AddSource(std::string_view path) { } s32 AvPlayer::GetStreamCount() { + if (m_state == nullptr) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } return m_state->GetStreamCount(); } @@ -130,11 +131,14 @@ s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { return ORBIS_OK; } -s32 AvPlayer::EnableStream(u32 stream_id) { +s32 AvPlayer::EnableStream(u32 stream_index) { if (m_state == nullptr) { return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; } - return m_state->EnableStream(stream_id); + if (!m_state->EnableStream(stream_index)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return ORBIS_OK; } s32 AvPlayer::Start() { diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index fe3abcd1b..98f959e6e 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -33,15 +33,13 @@ public: static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length); static u64 PS4_SYSV_ABI SizeFile(void* handle); - AvPlayer(); - - void Init(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); + AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); s32 PostInit(const SceAvPlayerPostInitData& data); s32 AddSource(std::string_view filename); s32 GetStreamCount(); s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); - s32 EnableStream(u32 stream_id); + s32 EnableStream(u32 stream_index); s32 Start(); bool GetAudioData(SceAvPlayerFrameInfo& audio_info); bool GetVideoData(SceAvPlayerFrameInfo& video_info); diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index c1dc6d579..c3d3dc55a 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -23,67 +23,47 @@ namespace Libraries::AvPlayer { using namespace Kernel; -AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state) : m_state(state) {} - -AvPlayerSource::~AvPlayerSource() { - if (!m_video_frame_storage.empty()) { - m_memory_replacement.deallocate(m_memory_replacement.object_ptr, - m_video_frame_storage.data()); - } - if (!m_audio_frame_storage.empty()) { - m_memory_replacement.deallocate(m_memory_replacement.object_ptr, - m_audio_frame_storage.data()); - } - Stop(); -} - -s32 AvPlayerSource::Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, - SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, - SceAvPlayerSourceType source_type) { - if (m_avformat_context != nullptr) { - return -1; - } - - m_priorities = priorities; - m_memory_replacement = memory_replacement; - - m_avformat_context = avformat_alloc_context(); - if (m_avformat_context == nullptr) { - return -1; - } - if (file_replacement.open != nullptr) { - m_up_data_streamer = std::make_unique(file_replacement, path); - m_avformat_context->pb = m_up_data_streamer->GetContext(); - if (avformat_open_input(&m_avformat_context, nullptr, nullptr, nullptr) < 0) { - return -1; - } +AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, + const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type) + : m_state(state), m_priorities(priorities), m_memory_replacement(init_data.memory_replacement), + m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { + AVFormatContext* context = avformat_alloc_context(); + if (init_data.file_replacement.open != nullptr) { + m_up_data_streamer = + std::make_unique(init_data.file_replacement, path); + context->pb = m_up_data_streamer->GetContext(); + ASSERT(!AVPLAYER_IS_ERROR(avformat_open_input(&context, nullptr, nullptr, nullptr))); } else { const auto mnt = Common::Singleton::Instance(); const auto filepath = mnt->GetHostPath(path); - if (AVPLAYER_IS_ERROR(avformat_open_input(&m_avformat_context, filepath.string().c_str(), - nullptr, nullptr))) { - return -1; - } + ASSERT(!AVPLAYER_IS_ERROR( + avformat_open_input(&context, filepath.string().c_str(), nullptr, nullptr))); } + m_avformat_context = AVFormatContextPtr(context, &ReleaseAVFormatContext); +} - return 0; +AvPlayerSource::~AvPlayerSource() { + Stop(); } bool AvPlayerSource::FindStreamInfo() { if (m_avformat_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not find stream info. NULL context."); return false; } if (m_avformat_context->nb_streams > 0) { return true; } - return avformat_find_stream_info(m_avformat_context, nullptr) == 0; + return avformat_find_stream_info(m_avformat_context.get(), nullptr) == 0; } s32 AvPlayerSource::GetStreamCount() { if (m_avformat_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream count. NULL context."); return -1; } - LOG_DEBUG(Lib_AvPlayer, "num streams: {}", m_avformat_context->nb_streams); + LOG_INFO(Lib_AvPlayer, "Stream Count: {}", m_avformat_context->nb_streams); return m_avformat_context->nb_streams; } @@ -108,70 +88,68 @@ static f32 AVRationalToF32(AVRational rational) { s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { info = {}; if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info.", stream_index); return -1; } const auto p_stream = m_avformat_context->streams[stream_index]; if (p_stream == nullptr || p_stream->codecpar == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. NULL stream.", stream_index); return -1; } info.type = CodecTypeToStreamType(p_stream->codecpar->codec_type); info.start_time = p_stream->start_time; info.duration = p_stream->duration; const auto p_lang_node = av_dict_get(p_stream->metadata, "language", nullptr, 0); - if (p_lang_node == nullptr) { - return -1; + if (p_lang_node != nullptr) { + LOG_INFO(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value); + } else { + LOG_WARNING(Lib_AvPlayer, "Stream {} language is unknown", stream_index); } - LOG_DEBUG(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value); switch (info.type) { case SCE_AVPLAYER_VIDEO: - LOG_DEBUG(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); + LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); info.details.video.aspect_ratio = AVRationalToF32(p_stream->codecpar->sample_aspect_ratio); info.details.video.width = p_stream->codecpar->width; info.details.video.height = p_stream->codecpar->height; - std::memcpy(info.details.video.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + if (p_lang_node != nullptr) { + std::memcpy(info.details.video.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + } break; case SCE_AVPLAYER_AUDIO: - LOG_DEBUG(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index); + LOG_INFO(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index); info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels; info.details.audio.sample_rate = p_stream->codecpar->sample_rate; info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0 - std::memcpy(info.details.audio.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + if (p_lang_node != nullptr) { + std::memcpy(info.details.audio.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + } break; case SCE_AVPLAYER_TIMEDTEXT: LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index); info.details.subs.font_size = 12; info.details.subs.text_size = 12; - std::memcpy(info.details.subs.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + if (p_lang_node != nullptr) { + std::memcpy(info.details.subs.language_code, p_lang_node->value, + std::min(strlen(p_lang_node->value), 3ull)); + } break; default: + LOG_ERROR(Lib_AvPlayer, "Stream {} type is unknown: {}.", stream_index, info.type); return -1; } return 0; } -static AVPixelFormat GetPreferredVideoFormat(AVCodecContext* s, const AVPixelFormat* fmt) { - auto curr = fmt; - while (*curr != AV_PIX_FMT_NONE) { - LOG_TRACE(Lib_AvPlayer, "Supported format: {}", magic_enum::enum_name(*fmt)); - if (*curr == AV_PIX_FMT_NV12) { - return AV_PIX_FMT_NV12; - } - ++curr; - } - return AV_PIX_FMT_NONE; -} - -s32 AvPlayerSource::EnableStream(u32 stream_index) { +bool AvPlayerSource::EnableStream(u32 stream_index) { if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) { - return -1; + return false; } const auto stream = m_avformat_context->streams[stream_index]; const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id); if (decoder == nullptr) { - return -1; + return false; } switch (stream->codecpar->codec_type) { case AVMediaType::AVMEDIA_TYPE_VIDEO: { @@ -179,10 +157,18 @@ s32 AvPlayerSource::EnableStream(u32 stream_index) { m_video_codec_context = AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); if (avcodec_parameters_to_context(m_video_codec_context.get(), stream->codecpar) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.", + stream_index); + return false; } if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for video stream {}.", stream_index); + return false; + } + const auto width = m_video_codec_context->width; + const auto size = (width * m_video_codec_context->height * 3) / 2; + for (u64 index = 0; index < m_num_output_video_framebuffers; ++index) { + m_video_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index); break; @@ -192,10 +178,19 @@ s32 AvPlayerSource::EnableStream(u32 stream_index) { m_audio_codec_context = AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext); if (avcodec_parameters_to_context(m_audio_codec_context.get(), stream->codecpar) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.", + stream_index); + return false; } if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) { - return -1; + LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for audio stream {}.", stream_index); + return false; + } + const auto num_channels = m_audio_codec_context->ch_layout.nb_channels; + const auto align = num_channels * sizeof(u16); + const auto size = num_channels * sizeof(u16) * 1024; + for (u64 index = 0; index < 2; ++index) { + m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); break; @@ -205,35 +200,7 @@ s32 AvPlayerSource::EnableStream(u32 stream_index) { magic_enum::enum_name(stream->codecpar->codec_type), stream_index); break; } - return 0; -} - -u8* AvPlayerSource::GetVideoBuffer(AVFrame* frame) { - const auto size = (frame->width * frame->height * 3) / 2; - if (m_video_frame_storage.size() < size) { - if (!m_video_frame_storage.empty()) { - m_memory_replacement.deallocate(m_memory_replacement.object_ptr, - m_video_frame_storage.data()); - } - const auto ptr = reinterpret_cast( - m_memory_replacement.allocate(m_memory_replacement.object_ptr, frame->width, size)); - m_video_frame_storage = std::span(ptr, size); - } - return m_video_frame_storage.data(); -} - -u8* AvPlayerSource::GetAudioBuffer(AVFrame* frame) { - const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16); - if (m_audio_frame_storage.size() < size) { - if (!m_audio_frame_storage.empty()) { - m_memory_replacement.deallocate(m_memory_replacement.object_ptr, - m_audio_frame_storage.data()); - } - const auto ptr = reinterpret_cast( - m_memory_replacement.allocate(m_memory_replacement.object_ptr, 0x40, size)); - m_audio_frame_storage = std::span(ptr, size); - } - return m_audio_frame_storage.data(); + return true; } void AvPlayerSource::SetLooping(bool is_looping) { @@ -241,11 +208,12 @@ void AvPlayerSource::SetLooping(bool is_looping) { } std::optional AvPlayerSource::HasFrames(u32 num_frames) { - return m_video_frames.Size() > num_frames; + return m_video_frames.Size() > num_frames || m_is_eof; } s32 AvPlayerSource::Start() { if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context."); return -1; } { @@ -258,6 +226,7 @@ s32 AvPlayerSource::Start() { }; m_demuxer_thread = CreateThread(&DemuxerThread, demuxer_params); if (m_demuxer_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create DEMUXER thread."); return -1; } } @@ -271,6 +240,7 @@ s32 AvPlayerSource::Start() { }; m_video_decoder_thread = CreateThread(&VideoDecoderThread, video_decoder_params); if (m_video_decoder_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create VIDEO DECODER thread."); return -1; } } @@ -284,6 +254,7 @@ s32 AvPlayerSource::Start() { }; m_audio_decoder_thread = CreateThread(&AudioDecoderThread, audio_decoder_params); if (m_audio_decoder_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create AUDIO DECODER thread."); return -1; } } @@ -292,6 +263,11 @@ s32 AvPlayerSource::Start() { } bool AvPlayerSource::Stop() { + if (m_is_stop) { + LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped."); + return false; + } + m_is_stop = true; void* res = nullptr; @@ -304,6 +280,18 @@ bool AvPlayerSource::Stop() { if (m_demuxer_thread != nullptr) { scePthreadJoin(m_demuxer_thread, &res); } + m_audio_packets.Clear(); + m_video_packets.Clear(); + m_audio_frames.Clear(); + m_video_frames.Clear(); + if (m_current_audio_frame.has_value()) { + m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + m_current_audio_frame.reset(); + } + if (m_current_video_frame.has_value()) { + m_video_buffers.Push(std::move(m_current_video_frame.value())); + m_current_video_frame.reset(); + } return true; } @@ -333,44 +321,27 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { auto frame = m_video_frames.Pop(); if (!frame.has_value()) { + LOG_WARNING(Lib_AvPlayer, "Could get video frame: no frames."); return false; } - auto& up_frame = *frame; - - const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; - const auto stream = m_avformat_context->streams[m_video_stream_index.value()]; - const auto time_base = stream->time_base; - const auto den = time_base.den; - const auto num = time_base.num; - const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; - { using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < timestamp) { - sceKernelUsleep((timestamp - elapsed_time) * 1000); + while (elapsed_time < frame->info.timestamp) { + sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } } - auto buffer = GetVideoBuffer(up_frame.get()); - - CopyNV12Data(buffer, *up_frame); - - video_info = {}; - video_info.timestamp = timestamp; - video_info.pData = buffer; - video_info.details.video.width = up_frame->width; - video_info.details.video.height = up_frame->height; - video_info.details.video.aspect_ratio = AVRationalToF32(up_frame->sample_aspect_ratio); - video_info.details.video.pitch = up_frame->linesize[0]; - video_info.details.video.luma_bit_depth = 8; - video_info.details.video.chroma_bit_depth = 8; - - m_last_video_timestamp = timestamp; + // return the buffer to the queue + if (m_current_video_frame.has_value()) { + m_video_buffers.Push(std::move(m_current_video_frame.value())); + } + m_current_video_frame = std::move(frame->buffer); + video_info = frame->info; return true; } @@ -381,45 +352,38 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { + LOG_WARNING(Lib_AvPlayer, "Could get audio frame: no frames."); return false; } - const auto& up_frame = *frame; - - const auto pkt_dts = u64(up_frame->pkt_dts) * 1000; - const auto stream = m_avformat_context->streams[m_audio_stream_index.value()]; - const auto time_base = stream->time_base; - const auto den = time_base.den; - const auto num = time_base.num; - const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; - { using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < timestamp) { - sceKernelUsleep((timestamp - elapsed_time) * 1000); + while (elapsed_time < frame->info.timestamp) { + sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } } - auto buffer = GetAudioBuffer(up_frame.get()); - const auto size = up_frame->ch_layout.nb_channels * up_frame->nb_samples * sizeof(u16); - std::memcpy(buffer, up_frame->data[0], size); + // return the buffer to the queue + if (m_current_audio_frame.has_value()) { + m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + } + m_current_audio_frame = std::move(frame->buffer); audio_info = {}; - audio_info.timestamp = timestamp; - audio_info.pData = m_audio_frame_storage.data(); - audio_info.details.audio.size = u32(m_audio_frame_storage.size()); - audio_info.details.audio.channel_count = up_frame->ch_layout.nb_channels; + audio_info.timestamp = frame->info.timestamp; + audio_info.pData = reinterpret_cast(frame->info.pData); + audio_info.details.audio.size = frame->info.details.audio.size; + audio_info.details.audio.channel_count = frame->info.details.audio.channel_count; return true; } u64 AvPlayerSource::CurrentTime() { - // using namespace std::chrono; - // return duration_cast(high_resolution_clock::now() - m_start_time).count(); - return m_last_video_timestamp; + using namespace std::chrono; + return duration_cast(high_resolution_clock::now() - m_start_time).count(); } bool AvPlayerSource::IsActive() { @@ -457,12 +421,19 @@ void AvPlayerSource::ReleaseSWSContext(SwsContext* context) { } } +void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { + if (context != nullptr) { + avformat_close_input(&context); + } +} + void* AvPlayerSource::DemuxerThread(void* opaque) { - LOG_TRACE(Lib_AvPlayer, "Demuxer Thread started"); const auto self = reinterpret_cast(opaque); if (!self->m_audio_stream_index.has_value() && !self->m_video_stream_index.has_value()) { + LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); return nullptr; } + LOG_INFO(Lib_AvPlayer, "Demuxer Thread started"); while (!self->m_is_stop) { if (self->m_video_packets.Size() > 60) { @@ -470,10 +441,10 @@ void* AvPlayerSource::DemuxerThread(void* opaque) { continue; } AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); - const auto res = av_read_frame(self->m_avformat_context, up_packet.get()); + const auto res = av_read_frame(self->m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_TRACE(Lib_AvPlayer, "EOF reached in demuxer"); + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer"); break; } else { LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); @@ -500,7 +471,7 @@ void* AvPlayerSource::DemuxerThread(void* opaque) { } self->m_state.OnEOF(); - LOG_TRACE(Lib_AvPlayer, "Demuxer Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); scePthreadExit(0); } @@ -531,12 +502,47 @@ AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& fram return nv12_frame; } +Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame) { + ASSERT(frame.format == AV_PIX_FMT_NV12); + + auto p_buffer = buffer.GetBuffer(); + CopyNV12Data(p_buffer, frame); + + const auto pkt_dts = u64(frame.pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_video_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + return Frame{ + .buffer = std::move(buffer), + .info = + { + .pData = p_buffer, + .timestamp = timestamp, + .details = + { + .video = + { + .width = u32(frame.width), + .height = u32(frame.height), + .aspect_ratio = AVRationalToF32(frame.sample_aspect_ratio), + .pitch = u32(frame.linesize[0]), + .luma_bit_depth = 8, + .chroma_bit_depth = 8, + }, + }, + }, + }; +} + void* AvPlayerSource::VideoDecoderThread(void* opaque) { - LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread started"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); const auto self = reinterpret_cast(opaque); while ((!self->m_is_eof || self->m_video_packets.Size() != 0) && !self->m_is_stop) { - if (self->m_video_frames.Size() > 60 || self->m_video_packets.Size() == 0) { + if (self->m_video_packets.Size() == 0) { sceKernelUsleep(5000); continue; } @@ -544,6 +550,7 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { if (!packet.has_value()) { continue; } + auto res = avcodec_send_packet(self->m_video_codec_context.get(), packet->get()); if (res < 0 && res != AVERROR(EAGAIN)) { self->m_state.OnError(); @@ -552,11 +559,15 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { scePthreadExit(nullptr); } while (res >= 0) { + if (self->m_video_buffers.Size() == 0 && !self->m_is_stop) { + sceKernelUsleep(5000); + continue; + } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); res = avcodec_receive_frame(self->m_video_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_TRACE(Lib_AvPlayer, "EOF reached in video decoder"); + LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder"); scePthreadExit(nullptr); } else if (res != AVERROR(EAGAIN)) { LOG_ERROR(Lib_AvPlayer, @@ -566,18 +577,26 @@ void* AvPlayerSource::VideoDecoderThread(void* opaque) { scePthreadExit(nullptr); } } else { - LOG_TRACE(Lib_AvPlayer, "Producing Video Frame. Num Frames: {}", - self->m_video_frames.Size()); - if (up_frame->format != AV_PIX_FMT_NV12) { - self->m_video_frames.Push(self->ConvertVideoFrame(*up_frame)); - } else { - self->m_video_frames.Push(std::move(up_frame)); + auto buffer = self->m_video_buffers.Pop(); + if (!buffer.has_value()) { + // Video buffers queue was cleared. This means that player was stopped. + break; } + if (up_frame->format != AV_PIX_FMT_NV12) { + const auto nv12_frame = self->ConvertVideoFrame(*up_frame); + self->m_video_frames.Push( + self->PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); + } else { + self->m_video_frames.Push( + self->PrepareVideoFrame(std::move(buffer.value()), *up_frame)); + } + LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", + self->m_video_frames.Size()); } } } - LOG_TRACE(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); scePthreadExit(nullptr); } @@ -607,12 +626,45 @@ AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& fram return pcm16_frame; } +Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame) { + ASSERT(frame.format == AV_SAMPLE_FMT_S16); + ASSERT(frame.nb_samples <= 1024); + + auto p_buffer = buffer.GetBuffer(); + const auto size = frame.ch_layout.nb_channels * frame.nb_samples * sizeof(u16); + std::memcpy(p_buffer, frame.data[0], size); + + const auto pkt_dts = u64(frame.pkt_dts) * 1000; + const auto stream = m_avformat_context->streams[m_audio_stream_index.value()]; + const auto time_base = stream->time_base; + const auto den = time_base.den; + const auto num = time_base.num; + const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts; + + return Frame{ + .buffer = std::move(buffer), + .info = + { + .pData = p_buffer, + .timestamp = timestamp, + .details = + { + .audio = + { + .channel_count = u16(frame.ch_layout.nb_channels), + .size = u32(size), + }, + }, + }, + }; +} + void* AvPlayerSource::AudioDecoderThread(void* opaque) { - LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread started"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); const auto self = reinterpret_cast(opaque); while ((!self->m_is_eof || self->m_audio_packets.Size() != 0) && !self->m_is_stop) { - if (self->m_audio_frames.Size() > 60 || self->m_audio_packets.Size() == 0) { + if (self->m_audio_packets.Size() == 0) { sceKernelUsleep(5000); continue; } @@ -628,11 +680,15 @@ void* AvPlayerSource::AudioDecoderThread(void* opaque) { scePthreadExit(nullptr); } while (res >= 0) { + if (self->m_audio_buffers.Size() == 0 && !self->m_is_stop) { + sceKernelUsleep(5000); + continue; + } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); res = avcodec_receive_frame(self->m_audio_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_TRACE(Lib_AvPlayer, "EOF reached in audio decoder"); + LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder"); scePthreadExit(nullptr); } else if (res != AVERROR(EAGAIN)) { self->m_state.OnError(); @@ -642,16 +698,26 @@ void* AvPlayerSource::AudioDecoderThread(void* opaque) { scePthreadExit(nullptr); } } else { - if (up_frame->format != AV_SAMPLE_FMT_S16) { - self->m_audio_frames.Push(self->ConvertAudioFrame(*up_frame)); - } else { - self->m_audio_frames.Push(std::move(up_frame)); + auto buffer = self->m_audio_buffers.Pop(); + if (!buffer.has_value()) { + // Audio buffers queue was cleared. This means that player was stopped. + break; } + if (up_frame->format != AV_SAMPLE_FMT_S16) { + const auto pcm16_frame = self->ConvertAudioFrame(*up_frame); + self->m_audio_frames.Push( + self->PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); + } else { + self->m_audio_frames.Push( + self->PrepareAudioFrame(std::move(buffer.value()), *up_frame)); + } + LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", + self->m_audio_frames.Size()); } } } - LOG_TRACE(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); scePthreadExit(nullptr); } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index 81f77a627..67ad44d88 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -38,11 +38,13 @@ class FrameBuffer { public: FrameBuffer(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) noexcept : m_memory_replacement(memory_replacement), - m_data(Allocate(memory_replacement, align, size), size) {} + m_data(Allocate(memory_replacement, align, size)) { + ASSERT_MSG(m_data, "Could not allocated frame buffer."); + } ~FrameBuffer() { - if (!m_data.empty()) { - Deallocate(m_memory_replacement, m_data.data()); + if (m_data != nullptr) { + Deallocate(m_memory_replacement, m_data); m_data = {}; } } @@ -52,17 +54,16 @@ public: FrameBuffer(FrameBuffer&& r) noexcept : m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) { - r.m_data = {}; + r.m_data = nullptr; }; FrameBuffer& operator=(FrameBuffer&& r) noexcept { - m_memory_replacement = r.m_memory_replacement; std::swap(m_data, r.m_data); return *this; } u8* GetBuffer() const noexcept { - return m_data.data(); + return m_data; } private: @@ -75,23 +76,26 @@ private: memory_replacement.deallocate(memory_replacement.object_ptr, ptr); } - SceAvPlayerMemAllocator m_memory_replacement; - std::span m_data; + const SceAvPlayerMemAllocator& m_memory_replacement; + u8* m_data = nullptr; +}; + +struct Frame { + FrameBuffer buffer; + SceAvPlayerFrameInfoEx info; }; class AvPlayerSource { public: - AvPlayerSource(AvPlayerStateCallback& state); + AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, + const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + SceAvPlayerSourceType source_type); ~AvPlayerSource(); - s32 Init(std::string_view path, SceAvPlayerMemAllocator& memory_replacement, - SceAvPlayerFileReplacement& file_replacement, ThreadPriorities& priorities, - SceAvPlayerSourceType source_type); - bool FindStreamInfo(); s32 GetStreamCount(); s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); - s32 EnableStream(u32 stream_index); + bool EnableStream(u32 stream_index); void SetLooping(bool is_looping); std::optional HasFrames(u32 num_frames); s32 Start(); @@ -114,52 +118,55 @@ private: static void ReleaseAVCodecContext(AVCodecContext* context); static void ReleaseSWRContext(SwrContext* context); static void ReleaseSWSContext(SwsContext* context); + static void ReleaseAVFormatContext(AVFormatContext* context); using AVPacketPtr = std::unique_ptr; using AVFramePtr = std::unique_ptr; using AVCodecContextPtr = std::unique_ptr; using SWRContextPtr = std::unique_ptr; using SWSContextPtr = std::unique_ptr; - - u8* GetVideoBuffer(AVFrame* frame); - u8* GetAudioBuffer(AVFrame* frame); + using AVFormatContextPtr = std::unique_ptr; AVFramePtr ConvertAudioFrame(const AVFrame& frame); AVFramePtr ConvertVideoFrame(const AVFrame& frame); - u64 m_last_video_timestamp{}; + Frame PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame); + Frame PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame); AvPlayerStateCallback& m_state; - ThreadPriorities m_priorities; - SceAvPlayerMemAllocator m_memory_replacement; + ThreadPriorities m_priorities{}; + SceAvPlayerMemAllocator m_memory_replacement{}; + u64 m_num_output_video_framebuffers{}; std::atomic_bool m_is_looping = false; std::atomic_bool m_is_eof = false; std::atomic_bool m_is_stop = false; + std::unique_ptr m_up_data_streamer; - AVFormatContext* m_avformat_context{}; + AvPlayerQueue m_audio_buffers; + AvPlayerQueue m_video_buffers; AvPlayerQueue m_audio_packets; AvPlayerQueue m_video_packets; - AvPlayerQueue m_audio_frames; - AvPlayerQueue m_video_frames; + AvPlayerQueue m_audio_frames; + AvPlayerQueue m_video_frames; - std::span m_video_frame_storage; - std::span m_audio_frame_storage; + std::optional m_current_video_frame; + std::optional m_current_audio_frame; - AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; - AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext}; - - std::optional m_video_stream_index{}; - std::optional m_audio_stream_index{}; + std::optional m_video_stream_index{}; + std::optional m_audio_stream_index{}; ScePthread m_demuxer_thread{}; ScePthread m_video_decoder_thread{}; ScePthread m_audio_decoder_thread{}; + AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext}; + AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; + AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext}; SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext}; SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index 3048819ec..e7aa45715 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -39,38 +39,29 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i switch (info.type) { case SCE_AVPLAYER_VIDEO: if (video_stream_index == -1) { - if (default_language.empty()) { - video_stream_index = stream_index; - break; - } - if (default_language == - reinterpret_cast(info.details.video.language_code)) { - video_stream_index = stream_index; - } + video_stream_index = stream_index; + } + if (!default_language.empty() && + default_language == reinterpret_cast(info.details.video.language_code)) { + video_stream_index = stream_index; } break; case SCE_AVPLAYER_AUDIO: if (audio_stream_index == -1) { - if (default_language.empty()) { - audio_stream_index = stream_index; - break; - } - if (default_language == - reinterpret_cast(info.details.video.language_code)) { - audio_stream_index = stream_index; - } + audio_stream_index = stream_index; + } + if (!default_language.empty() && + default_language == reinterpret_cast(info.details.video.language_code)) { + audio_stream_index = stream_index; } break; case SCE_AVPLAYER_TIMEDTEXT: - if (timedtext_stream_index == -1) { - if (default_language.empty()) { - timedtext_stream_index = stream_index; - break; - } - if (default_language == - reinterpret_cast(info.details.video.language_code)) { - timedtext_stream_index = stream_index; - } + if (default_language.empty()) { + timedtext_stream_index = stream_index; + break; + } + if (default_language == reinterpret_cast(info.details.video.language_code)) { + timedtext_stream_index = stream_index; } break; } @@ -89,8 +80,9 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i return; } - const auto callback = self->m_user_event_replacement.event_callback; - const auto ptr = self->m_user_event_replacement.object_ptr; + // Pass other events to the game + const auto callback = self->m_event_replacement.event_callback; + const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { callback(ptr, event_id, 0, event_data); } @@ -99,34 +91,15 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i // Called inside GAME thread AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, const ThreadPriorities& priorities) - : m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerEventHandler"), - m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayerStateMachine") { - if (init_data.event_replacement.event_callback == nullptr || init_data.auto_start) { + : m_init_data(init_data), m_event_replacement(init_data.event_replacement), + m_thread_priorities(priorities), + m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_EventHandler"), + m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_StateMachine") { + if (m_event_replacement.event_callback == nullptr || init_data.auto_start) { m_auto_start = true; - m_event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; - m_event_replacement.object_ptr = this; - } else { - m_event_replacement = init_data.event_replacement; + m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; + m_init_data.event_replacement.object_ptr = this; } - m_user_event_replacement = init_data.event_replacement; - - const auto& memory_replacement = init_data.memory_replacement; - if (memory_replacement.allocate != nullptr && memory_replacement.deallocate != nullptr && - memory_replacement.allocate_texture != nullptr && - memory_replacement.deallocate_texture != nullptr) { - m_memory_replacement = memory_replacement; - } - - if (init_data.event_replacement.event_callback != nullptr) { - m_event_replacement = init_data.event_replacement; - m_auto_start = init_data.auto_start; - } else { - m_auto_start = true; - } - - m_file_replacement = init_data.file_replacement; - m_thread_priorities = priorities; - if (init_data.default_language != nullptr) { std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language)); } @@ -135,54 +108,67 @@ AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, } AvPlayerState::~AvPlayerState() { - m_event_queue.Clear(); if (m_up_source && m_current_state == AvState::Play) { m_up_source->Stop(); - OnPlaybackStateChanged(AvState::Stop); } + m_quit = true; + if (m_controller_thread != nullptr) { + void* res = nullptr; + scePthreadJoin(m_controller_thread, &res); + } + m_event_queue.Clear(); } // Called inside GAME thread s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) { if (path.empty()) { + LOG_ERROR(Lib_AvPlayer, "File path is empty."); return -1; } - if (m_up_source) { + if (m_up_source != nullptr) { + LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); return -1; } - m_up_source = std::make_unique(*this); - if (AVPLAYER_IS_ERROR(m_up_source->Init(path, m_memory_replacement, m_file_replacement, - m_thread_priorities, source_type))) { - return -1; - } + m_up_source = std::make_unique(*this, path, m_init_data, m_thread_priorities, + source_type); AddSourceEvent(); - return 0; } // Called inside GAME thread s32 AvPlayerState::GetStreamCount() { + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source."); + return -1; + } return m_up_source->GetStreamCount(); } // Called inside GAME thread s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index); + return -1; + } return m_up_source->GetStreamInfo(stream_index, info); } // Called inside GAME thread s32 AvPlayerState::Start() { - if (m_up_source == nullptr) { + if (m_up_source == nullptr || m_up_source->Start() < 0) { + LOG_ERROR(Lib_AvPlayer, "Could not start playback."); return -1; } - return m_up_source->Start(); + SetState(AvState::Play); + OnPlaybackStateChanged(AvState::Play); + return 0; } void* AvPlayerState::AvControllerThread(void* p_user_data) { AvPlayerState* self = reinterpret_cast(p_user_data); - while (self->m_quit.load() == 0) { + while (!self->m_quit) { if (self->m_event_queue.Size() != 0) { self->ProcessEvent(); continue; @@ -201,6 +187,16 @@ void AvPlayerState::AddSourceEvent() { }); } +void AvPlayerState::WarningEvent(s32 id) { + m_event_queue.Push(AvPlayerEvent{ + .event = AvEventType::WarningId, + .payload = + { + .error = id, + }, + }); +} + // Called inside GAME thread int AvPlayerState::StartControllerThread() { m_quit.store(0); @@ -214,25 +210,28 @@ int AvPlayerState::StartControllerThread() { }; m_controller_thread = CreateThread(&AvControllerThread, params); if (m_controller_thread == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not create CONTROLLER thread."); return -1; } return 0; } // Called inside GAME thread -s32 AvPlayerState::EnableStream(u32 stream_id) { +bool AvPlayerState::EnableStream(u32 stream_index) { if (m_up_source == nullptr) { - return -1; + return false; } - return m_up_source->EnableStream(stream_id); + return m_up_source->EnableStream(stream_index); } // Called inside GAME thread bool AvPlayerState::Stop() { - if (m_up_source == nullptr) { + if (m_up_source == nullptr || m_current_state == AvState::Stop) { + return false; + } + if (!SetState(AvState::Stop)) { return false; } - SetState(AvState::Stop); OnPlaybackStateChanged(AvState::Stop); return m_up_source->Stop(); } @@ -268,13 +267,16 @@ bool AvPlayerState::IsActive() { u64 AvPlayerState::CurrentTime() { if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source."); return 0; } return m_up_source->CurrentTime(); } +// May be called from different threads void AvPlayerState::OnWarning(u32 id) { - EmitEvent(SCE_AVPLAYER_WARNING_ID, &id); + // Forward to CONTROLLER thread + WarningEvent(id); } void AvPlayerState::OnError() { @@ -282,9 +284,7 @@ void AvPlayerState::OnError() { OnPlaybackStateChanged(AvState::Error); } -void AvPlayerState::OnEOF() { - Stop(); -} +void AvPlayerState::OnEOF() {} // Called inside CONTROLLER thread void AvPlayerState::OnPlaybackStateChanged(AvState state) { @@ -319,6 +319,8 @@ bool AvPlayerState::SetState(AvState state) { std::lock_guard guard(m_state_machine_mutex); if (!IsStateTransitionValid(state)) { + LOG_ERROR(Lib_AvPlayer, "Invalid state transition: {} -> {}", + magic_enum::enum_name(m_current_state.load()), magic_enum::enum_name(state)); return false; } m_previous_state.store(m_current_state); @@ -337,16 +339,16 @@ std::optional AvPlayerState::OnBufferingCheckEvent(u32 num_frames) { // Called inside CONTROLLER thread void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { LOG_INFO(Lib_AvPlayer, "Sending event to the game: id = {}", magic_enum::enum_name(event_id)); - const auto callback = m_event_replacement.event_callback; + const auto callback = m_init_data.event_replacement.event_callback; if (callback) { - const auto ptr = m_event_replacement.object_ptr; + const auto ptr = m_init_data.event_replacement.object_ptr; callback(ptr, event_id, 0, event_data); } } // Called inside CONTROLLER thread int AvPlayerState::ProcessEvent() { - if (m_current_state.load() == AvState::Jump) { + if (m_current_state == AvState::Jump) { return -2; } @@ -357,6 +359,10 @@ int AvPlayerState::ProcessEvent() { return -1; } switch (event->event) { + case AvEventType::WarningId: { + OnWarning(event->payload.error); + break; + } case AvEventType::RevertState: { SetState(m_previous_state.load()); break; diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index fc822884d..cfddab098 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -24,7 +24,7 @@ public: s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); s32 GetStreamCount(); s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info); - s32 EnableStream(u32 stream_id); + bool EnableStream(u32 stream_index); s32 Start(); bool Stop(); bool GetAudioData(SceAvPlayerFrameInfo& audio_info); @@ -54,6 +54,8 @@ private: static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); void AddSourceEvent(); + void WarningEvent(s32 id); + int StartControllerThread(); int ProcessEvent(); int UpdateBufferingState(); @@ -61,15 +63,13 @@ private: std::unique_ptr m_up_source; - SceAvPlayerMemAllocator m_memory_replacement{}; - SceAvPlayerFileReplacement m_file_replacement{}; + SceAvPlayerInitData m_init_data{}; SceAvPlayerEventReplacement m_event_replacement{}; - SceAvPlayerEventReplacement m_user_event_replacement{}; ThreadPriorities m_thread_priorities{}; bool m_auto_start{}; u8 m_default_language[4]{}; - std::atomic_int32_t m_quit; + std::atomic_bool m_quit; std::atomic m_current_state; std::atomic m_previous_state; u32 m_thread_priority; From 0d6e8e227a7c71223ad5d154538a1e1213a661d3 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Wed, 14 Aug 2024 21:30:44 +0300 Subject: [PATCH 104/109] Fixed some sound and threading issues. Details: * Switched SDL audio mutex to RW lock. This fixes games that continiously call SetVolume in a different thread (like Ghostbusters) * Added contition to buffer audio packets independent of video packets. This fixes choppy audio across many games. * Increased the number of audio frame buffers from 2 to 4. Just in case. * Migrated to std::jthread and std::mutex from pthreads. * Fixed a race condition with joins on avplayer close that caused a crash. --- src/audio_core/sdl_audio.cpp | 8 +- src/audio_core/sdl_audio.h | 4 +- src/core/libraries/audio/audioout.cpp | 4 +- src/core/libraries/avplayer/avplayer.cpp | 67 +---- src/core/libraries/avplayer/avplayer.h | 1 + .../libraries/avplayer/avplayer_common.cpp | 59 ---- src/core/libraries/avplayer/avplayer_common.h | 90 +------ .../avplayer/avplayer_file_streamer.cpp | 6 +- src/core/libraries/avplayer/avplayer_impl.cpp | 43 +-- src/core/libraries/avplayer/avplayer_impl.h | 34 +-- .../libraries/avplayer/avplayer_source.cpp | 254 ++++++++---------- src/core/libraries/avplayer/avplayer_source.h | 25 +- .../libraries/avplayer/avplayer_state.cpp | 74 ++--- src/core/libraries/avplayer/avplayer_state.h | 21 +- src/core/libraries/kernel/thread_management.h | 16 +- src/core/libraries/kernel/time_management.h | 1 - src/core/libraries/save_data/savedata.cpp | 5 +- 17 files changed, 231 insertions(+), 481 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index aac67d8cd..9c8c5991c 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -13,7 +13,7 @@ namespace Audio { int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq, Libraries::AudioOut::OrbisAudioOutParamFormat format) { using Libraries::AudioOut::OrbisAudioOutParamFormat; - std::scoped_lock lock{m_mutex}; + std::unique_lock lock{m_mutex}; for (int id = 0; id < portsOut.size(); id++) { auto& port = portsOut[id]; if (!port.isOpen) { @@ -88,7 +88,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq, } s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; if (!port.isOpen) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; @@ -109,7 +109,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) { bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) { using Libraries::AudioOut::OrbisAudioOutParamFormat; - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; if (!port.isOpen) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; @@ -147,7 +147,7 @@ bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) { } bool SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) { - std::scoped_lock lock{m_mutex}; + std::shared_lock lock{m_mutex}; auto& port = portsOut[handle - 1]; *type = port.type; *channels_num = port.channels_num; diff --git a/src/audio_core/sdl_audio.h b/src/audio_core/sdl_audio.h index d20c44559..7844bd61b 100644 --- a/src/audio_core/sdl_audio.h +++ b/src/audio_core/sdl_audio.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include "core/libraries/audio/audioout.h" @@ -32,7 +32,7 @@ private: int volume[8] = {}; SDL_AudioStream* stream = nullptr; }; - std::mutex m_mutex; + std::shared_mutex m_mutex; std::array portsOut; // main up to 8 ports , BGM 1 port , voice up to 4 ports , // personal up to 4 ports , padspk up to 5 ports , aux 1 port }; diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 08929383b..cb676afc1 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -234,7 +234,7 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() { } int PS4_SYSV_ABI sceAudioOutInit() { - LOG_INFO(Lib_AudioOut, "called"); + LOG_TRACE(Lib_AudioOut, "called"); if (audio != nullptr) { return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; } @@ -323,12 +323,10 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() { } s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { - LOG_TRACE(Lib_AudioOut, "called"); return audio->AudioOutOutput(handle, ptr); } int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { - LOG_TRACE(Lib_AudioOut, "called"); for (u32 i = 0; i < num; i++) { if (auto err = audio->AudioOutOutput(param[i].handle, param[i].ptr); err != 0) return err; diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index e8e99bcaa..8fccc6590 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -11,8 +11,6 @@ #include // std::max, std::min -#include // va_list - namespace Libraries::AvPlayer { using namespace Kernel; @@ -140,17 +138,7 @@ SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { return nullptr; } - ThreadPriorities priorities{}; - const u32 base_priority = data->base_priority != 0 ? data->base_priority : 700; - priorities.video_decoder_priority = GetPriority(base_priority, 5); - priorities.audio_decoder_priority = GetPriority(base_priority, 6); - priorities.demuxer_priority = GetPriority(base_priority, 9); - priorities.controller_priority = GetPriority(base_priority, 2); - // priorities.http_streaming_priority = GetPriority(base_priority, 10); - // priorities.file_streaming_priority = GetPriority(priorities.http_streaming_priority, 15); - // priorities.maxPriority = priorities.http_streaming_priority; - - return new AvPlayer(*data, priorities); + return new AvPlayer(*data); } s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, @@ -176,56 +164,7 @@ s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, data.num_output_video_framebuffers = p_data->num_output_video_framebuffers; data.auto_start = p_data->auto_start; - ThreadPriorities priorities{}; - s32 base_priority = 0; - const auto res = scePthreadGetprio(scePthreadSelf(), &base_priority); - if (res != 0 || base_priority == 0) { - base_priority = 700; - } - - if (p_data->video_decoder_priority != 0) { - priorities.video_decoder_priority = p_data->video_decoder_priority; - } else { - priorities.video_decoder_priority = GetPriority(base_priority, 5); - } - priorities.video_decoder_affinity = p_data->video_decoder_affinity; - - if (p_data->audio_decoder_priority != 0) { - priorities.audio_decoder_priority = p_data->audio_decoder_priority; - } else { - priorities.audio_decoder_priority = GetPriority(base_priority, 6); - } - priorities.audio_decoder_affinity = p_data->audio_decoder_affinity; - - if (p_data->controller_priority != 0) { - priorities.controller_priority = p_data->controller_priority; - } else { - priorities.controller_priority = GetPriority(base_priority, 2); - } - priorities.controller_affinity = p_data->controller_affinity; - - if (p_data->demuxer_priority != 0) { - priorities.demuxer_priority = p_data->demuxer_priority; - } else { - priorities.demuxer_priority = GetPriority(base_priority, 9); - } - priorities.demuxer_affinity = p_data->demuxer_affinity; - - // if (p_data->http_streaming_priority != 0) { - // priorities.http_streaming_priority = p_data->http_streaming_priority; - // } else { - // priorities.http_streaming_priority = GetPriority(base_priority, 10); - // } - // priorities.http_streaming_affinity = p_data->http_streaming_affinity; - - // if (p_data->file_streaming_priority != 0) { - // priorities.file_streaming_priority = p_data->file_streaming_priority; - // } else { - // priorities.file_streaming_priority = GetPriority(base_priority, 15); - // } - // priorities.http_streaming_affinity = p_data->http_streaming_affinity; - - *p_player = new AvPlayer(data, priorities); + *p_player = new AvPlayer(data); return ORBIS_OK; } @@ -320,7 +259,7 @@ s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) { } s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); + LOG_TRACE(Lib_AvPlayer, "called"); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index e88419c19..360f06b65 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -5,6 +5,7 @@ #include "common/types.h" +#include // va_list #include // size_t namespace Core::Loader { diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp index 3536c030d..306603e29 100644 --- a/src/core/libraries/avplayer/avplayer_common.cpp +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -12,65 +12,6 @@ namespace Libraries::AvPlayer { using namespace Kernel; -Kernel::ScePthreadMutex CreateMutex(int type, const char* name) { - ScePthreadMutexattr attr{}; - ScePthreadMutex mutex{}; - if (scePthreadMutexattrInit(&attr) == 0) { - if (scePthreadMutexattrSettype(&attr, type) == 0) { - if (scePthreadMutexInit(&mutex, &attr, name) != 0) { - if (mutex != nullptr) { - scePthreadMutexDestroy(&mutex); - } - return nullptr; - } - } - } - if (attr != nullptr) { - scePthreadMutexattrDestroy(&attr); - } - return mutex; -} - -ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params) { - ScePthreadAttr attr; - if (scePthreadAttrInit(&attr) != 0) { - return nullptr; - } - if (scePthreadAttrSetinheritsched(&attr, 0) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - - SceKernelSchedParam param{.sched_priority = static_cast(params.priority)}; - if (scePthreadAttrSetschedparam(&attr, ¶m) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - if (scePthreadAttrSetstacksize(&attr, std::min(params.stack_size, 0x4000u)) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - if (scePthreadAttrSetdetachstate(&attr, 0) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - if (params.affinity > 0) { - if (scePthreadAttrSetaffinity(&attr, params.affinity) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - } - - ScePthread thread{}; - if (scePthreadCreate(&thread, &attr, func, params.p_user_data, params.thread_name) != 0) { - scePthreadAttrDestroy(&attr); - return nullptr; - } - - scePthreadAttrDestroy(&attr); - return thread; -} - static bool ichar_equals(char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); diff --git a/src/core/libraries/avplayer/avplayer_common.h b/src/core/libraries/avplayer/avplayer_common.h index 5faf23884..a53696ecf 100644 --- a/src/core/libraries/avplayer/avplayer_common.h +++ b/src/core/libraries/avplayer/avplayer_common.h @@ -31,9 +31,6 @@ enum class AvState { C0x0B, Buffering, Starting, - C0x0E, - C0x0F, - C0x10, Error, }; @@ -45,23 +42,6 @@ enum class AvEventType { Error = 255, }; -struct ThreadPriorities { - u32 audio_decoder_priority; - u32 audio_decoder_affinity; - u32 video_decoder_priority; - u32 video_decoder_affinity; - u32 demuxer_priority; - u32 demuxer_affinity; - u32 controller_priority; - u32 controller_affinity; - // u32 http_streaming_priority; - // u32 http_streaming_affinity; - // u32 file_streaming_priority; - // u32 file_streaming_affinity; - // u32 maxPriority; - // u32 maxAffinity; -}; - union AvPlayerEventData { u32 num_frames; // 20 AvState state; // AvEventType::ChangeFlowState @@ -74,68 +54,9 @@ struct AvPlayerEvent { AvPlayerEventData payload; }; -Kernel::ScePthreadMutex CreateMutex(int type, const char* name); - -class PthreadMutex { -public: - using ScePthreadMutex = Kernel::ScePthreadMutex; - - PthreadMutex() = default; - - PthreadMutex(const PthreadMutex&) = delete; - PthreadMutex& operator=(const PthreadMutex&) = delete; - - PthreadMutex(PthreadMutex&& r) : m_mutex(r.m_mutex) { - r.m_mutex = nullptr; - } - PthreadMutex& operator=(PthreadMutex&& r) { - std::swap(m_mutex, r.m_mutex); - return *this; - } - - PthreadMutex(int type, const char* name) : m_mutex(CreateMutex(type, name)) {} - ~PthreadMutex() { - if (m_mutex != nullptr) { - Kernel::scePthreadMutexDestroy(&m_mutex); - } - } - - operator ScePthreadMutex() { - return m_mutex; - } - - int Lock() { - return Kernel::scePthreadMutexLock(&m_mutex); - } - - int Unlock() { - return Kernel::scePthreadMutexUnlock(&m_mutex); - } - - // implement BasicLockable to use std::lock_guard - // NOLINTNEXTLINE(readability-identifier-naming) - void lock() { - ASSERT_MSG(Lock() >= 0, "Could not lock the mutex"); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - void unlock() { - ASSERT_MSG(Unlock() >= 0, "Could not unlock the mutex"); - } - - operator bool() { - return m_mutex != nullptr; - } - -private: - ScePthreadMutex m_mutex{}; -}; - template class AvPlayerQueue { public: - AvPlayerQueue() : m_mutex(PTHREAD_MUTEX_ERRORCHECK, "SceAvPlayer0StlHandler") {} - size_t Size() { return m_queue.size(); } @@ -161,19 +82,10 @@ public: } private: - PthreadMutex m_mutex{}; + std::mutex m_mutex{}; std::queue m_queue{}; }; -struct ThreadParameters { - void* p_user_data; - const char* thread_name; - u32 stack_size; - u32 priority; - u32 affinity; -}; - -Kernel::ScePthread CreateThread(Kernel::PthreadEntryFunc func, const ThreadParameters& params); SceAvPlayerSourceType GetSourceType(std::string_view path); } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 43055fd59..4b1ddb6e6 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -69,14 +69,14 @@ s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { if (whence == SEEK_CUR) { self->m_position = - std::min(u64(std::max(0ll, s64(self->m_position) + offset)), self->m_file_size); + std::min(u64(std::max(0i64, s64(self->m_position) + offset)), self->m_file_size); return self->m_position; } else if (whence == SEEK_SET) { - self->m_position = std::min(u64(std::max(0ll, offset)), self->m_file_size); + self->m_position = std::min(u64(std::max(0i64, offset)), self->m_file_size); return self->m_position; } else if (whence == SEEK_END) { self->m_position = - std::min(u64(std::max(0ll, s64(self->m_file_size) + offset)), self->m_file_size); + std::min(u64(std::max(0i64, s64(self->m_file_size) + offset)), self->m_file_size); return self->m_position; } diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index f08356bd0..1114254f8 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -77,29 +77,30 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { return size(ptr); } -AvPlayer::AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities) - : m_file_io_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_FileIO"), m_init_data(data), - m_init_data_original(data) { - - m_init_data.memory_replacement.object_ptr = this; - m_init_data.memory_replacement.allocate = &AvPlayer::Allocate; - m_init_data.memory_replacement.deallocate = &AvPlayer::Deallocate; - m_init_data.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture; - m_init_data.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture; +SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) { + SceAvPlayerInitData result = data; + result.memory_replacement.object_ptr = this; + result.memory_replacement.allocate = &AvPlayer::Allocate; + result.memory_replacement.deallocate = &AvPlayer::Deallocate; + result.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture; + result.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture; if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr || data.file_replacement.readOffset == nullptr || data.file_replacement.size == nullptr) { - m_init_data.file_replacement = {}; + result.file_replacement = {}; } else { - m_init_data.file_replacement.object_ptr = this; - m_init_data.file_replacement.open = &AvPlayer::OpenFile; - m_init_data.file_replacement.close = &AvPlayer::CloseFile; - m_init_data.file_replacement.readOffset = &AvPlayer::ReadOffsetFile; - m_init_data.file_replacement.size = &AvPlayer::SizeFile; + result.file_replacement.object_ptr = this; + result.file_replacement.open = &AvPlayer::OpenFile; + result.file_replacement.close = &AvPlayer::CloseFile; + result.file_replacement.readOffset = &AvPlayer::ReadOffsetFile; + result.file_replacement.size = &AvPlayer::SizeFile; } - - m_state = std::make_unique(m_init_data, priorities); + return result; } +AvPlayer::AvPlayer(const SceAvPlayerInitData& data) + : m_init_data(StubInitData(data)), m_init_data_original(data), + m_state(std::make_unique(m_init_data)) {} + s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) { m_post_init_data = data; return ORBIS_OK; @@ -109,11 +110,9 @@ s32 AvPlayer::AddSource(std::string_view path) { if (path.empty()) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } - if (AVPLAYER_IS_ERROR(m_state->AddSource(path, GetSourceType(path)))) { return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; } - return ORBIS_OK; } @@ -121,7 +120,11 @@ s32 AvPlayer::GetStreamCount() { if (m_state == nullptr) { return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; } - return m_state->GetStreamCount(); + const auto res = m_state->GetStreamCount(); + if (AVPLAYER_IS_ERROR(res)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } + return res; } s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index 98f959e6e..df4acfee0 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -9,6 +9,8 @@ #include "core/libraries/kernel/thread_management.h" +#include + extern "C" { #include #include @@ -21,19 +23,7 @@ namespace Libraries::AvPlayer { class AvPlayer { public: - // Memory Replacement - static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size); - static void PS4_SYSV_ABI Deallocate(void* handle, void* memory); - static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size); - static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory); - - // File Replacement - static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename); - static int PS4_SYSV_ABI CloseFile(void* handle); - static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length); - static u64 PS4_SYSV_ABI SizeFile(void* handle); - - AvPlayer(const SceAvPlayerInitData& data, const ThreadPriorities& priorities); + AvPlayer(const SceAvPlayerInitData& data); s32 PostInit(const SceAvPlayerPostInitData& data); s32 AddSource(std::string_view filename); @@ -51,13 +41,27 @@ public: private: using ScePthreadMutex = Kernel::ScePthreadMutex; - std::unique_ptr m_state{}; + // Memory Replacement + static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI Deallocate(void* handle, void* memory); + static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size); + static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory); + + // File Replacement + static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename); + static int PS4_SYSV_ABI CloseFile(void* handle); + static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length); + static u64 PS4_SYSV_ABI SizeFile(void* handle); + + SceAvPlayerInitData StubInitData(const SceAvPlayerInitData& data); + SceAvPlayerInitData m_init_data{}; SceAvPlayerInitData m_init_data_original{}; SceAvPlayerPostInitData m_post_init_data{}; - PthreadMutex m_file_io_mutex{}; + std::mutex m_file_io_mutex{}; std::atomic_bool m_has_source{}; + std::unique_ptr m_state{}; }; } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index c3d3dc55a..e235b2c2c 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -24,9 +24,9 @@ namespace Libraries::AvPlayer { using namespace Kernel; AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, - const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, + const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type) - : m_state(state), m_priorities(priorities), m_memory_replacement(init_data.memory_replacement), + : m_state(state), m_memory_replacement(init_data.memory_replacement), m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { AVFormatContext* context = avformat_alloc_context(); if (init_data.file_replacement.open != nullptr) { @@ -113,7 +113,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.video.height = p_stream->codecpar->height; if (p_lang_node != nullptr) { std::memcpy(info.details.video.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; case SCE_AVPLAYER_AUDIO: @@ -123,7 +123,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0 if (p_lang_node != nullptr) { std::memcpy(info.details.audio.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; case SCE_AVPLAYER_TIMEDTEXT: @@ -132,7 +132,7 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) info.details.subs.text_size = 12; if (p_lang_node != nullptr) { std::memcpy(info.details.subs.language_code, p_lang_node->value, - std::min(strlen(p_lang_node->value), 3ull)); + std::min(strlen(p_lang_node->value), size_t(3))); } break; default: @@ -189,7 +189,7 @@ bool AvPlayerSource::EnableStream(u32 stream_index) { const auto num_channels = m_audio_codec_context->ch_layout.nb_channels; const auto align = num_channels * sizeof(u16); const auto size = num_channels * sizeof(u16) * 1024; - for (u64 index = 0; index < 2; ++index) { + for (u64 index = 0; index < 4; ++index) { m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size)); } LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index); @@ -212,78 +212,41 @@ std::optional AvPlayerSource::HasFrames(u32 num_frames) { } s32 AvPlayerSource::Start() { + std::unique_lock lock(m_state_mutex); + if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context."); return -1; } - { - ThreadParameters demuxer_params{ - .p_user_data = this, - .thread_name = "AvPlayer_Demuxer", - .stack_size = 0x4000, - .priority = m_priorities.demuxer_priority, - .affinity = m_priorities.demuxer_affinity, - }; - m_demuxer_thread = CreateThread(&DemuxerThread, demuxer_params); - if (m_demuxer_thread == nullptr) { - LOG_ERROR(Lib_AvPlayer, "Could not create DEMUXER thread."); - return -1; - } - } - if (m_video_codec_context != nullptr) { - ThreadParameters video_decoder_params{ - .p_user_data = this, - .thread_name = "AvPlayer_VideoDecoder", - .stack_size = 0x4000, - .priority = m_priorities.video_decoder_priority, - .affinity = m_priorities.video_decoder_affinity, - }; - m_video_decoder_thread = CreateThread(&VideoDecoderThread, video_decoder_params); - if (m_video_decoder_thread == nullptr) { - LOG_ERROR(Lib_AvPlayer, "Could not create VIDEO DECODER thread."); - return -1; - } - } - if (m_audio_codec_context != nullptr) { - ThreadParameters audio_decoder_params{ - .p_user_data = this, - .thread_name = "AvPlayer_AudioDecoder", - .stack_size = 0x4000, - .priority = m_priorities.audio_decoder_priority, - .affinity = m_priorities.audio_decoder_affinity, - }; - m_audio_decoder_thread = CreateThread(&AudioDecoderThread, audio_decoder_params); - if (m_audio_decoder_thread == nullptr) { - LOG_ERROR(Lib_AvPlayer, "Could not create AUDIO DECODER thread."); - return -1; - } - } + m_demuxer_thread = std::jthread([this](std::stop_token stop) { this->DemuxerThread(stop); }); + m_video_decoder_thread = + std::jthread([this](std::stop_token stop) { this->VideoDecoderThread(stop); }); + m_audio_decoder_thread = + std::jthread([this](std::stop_token stop) { this->AudioDecoderThread(stop); }); m_start_time = std::chrono::high_resolution_clock::now(); return 0; } bool AvPlayerSource::Stop() { - if (m_is_stop) { + std::unique_lock lock(m_state_mutex); + + if (!HasRunningThreads()) { LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped."); return false; } - m_is_stop = true; - - void* res = nullptr; - if (m_video_decoder_thread != nullptr) { - scePthreadJoin(m_video_decoder_thread, &res); + m_video_decoder_thread.request_stop(); + m_audio_decoder_thread.request_stop(); + m_demuxer_thread.request_stop(); + if (m_demuxer_thread.joinable()) { + m_demuxer_thread.join(); } - if (m_audio_decoder_thread != nullptr) { - scePthreadJoin(m_audio_decoder_thread, &res); + if (m_video_decoder_thread.joinable()) { + m_video_decoder_thread.join(); } - if (m_demuxer_thread != nullptr) { - scePthreadJoin(m_demuxer_thread, &res); + if (m_audio_decoder_thread.joinable()) { + m_audio_decoder_thread.join(); } - m_audio_packets.Clear(); - m_video_packets.Clear(); - m_audio_frames.Clear(); - m_video_frames.Clear(); if (m_current_audio_frame.has_value()) { m_audio_buffers.Push(std::move(m_current_audio_frame.value())); m_current_audio_frame.reset(); @@ -292,10 +255,19 @@ bool AvPlayerSource::Stop() { m_video_buffers.Push(std::move(m_current_video_frame.value())); m_current_video_frame.reset(); } + + m_audio_packets.Clear(); + m_video_packets.Clear(); + m_audio_frames.Clear(); + m_video_frames.Clear(); return true; } bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) { + if (!IsActive()) { + return false; + } + SceAvPlayerFrameInfoEx info{}; if (!GetVideoData(info)) { return false; @@ -315,8 +287,13 @@ static void CopyNV12Data(u8* dst, const AVFrame& src) { } bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + if (!IsActive()) { + return false; + } + + using namespace std::chrono; while (m_video_frames.Size() == 0 && !m_is_eof) { - sceKernelUsleep(5000); + std::this_thread::sleep_for(milliseconds(5)); } auto frame = m_video_frames.Pop(); @@ -326,11 +303,10 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } { - using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); while (elapsed_time < frame->info.timestamp) { - sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); + std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } @@ -346,8 +322,13 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + if (!IsActive()) { + return false; + } + + using namespace std::chrono; while (m_audio_frames.Size() == 0 && !m_is_eof) { - sceKernelUsleep(5000); + std::this_thread::sleep_for(milliseconds(5)); } auto frame = m_audio_frames.Pop(); @@ -357,11 +338,10 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { } { - using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); while (elapsed_time < frame->info.timestamp) { - sceKernelUsleep((frame->info.timestamp - elapsed_time) * 1000); + std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); } @@ -387,8 +367,8 @@ u64 AvPlayerSource::CurrentTime() { } bool AvPlayerSource::IsActive() { - return !m_is_stop && (!m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 || - m_video_frames.Size() != 0 || m_audio_frames.Size() != 0); + return !m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 || + m_video_frames.Size() != 0 || m_audio_frames.Size() != 0; } void AvPlayerSource::ReleaseAVPacket(AVPacket* packet) { @@ -427,52 +407,51 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { } } -void* AvPlayerSource::DemuxerThread(void* opaque) { - const auto self = reinterpret_cast(opaque); - if (!self->m_audio_stream_index.has_value() && !self->m_video_stream_index.has_value()) { +void AvPlayerSource::DemuxerThread(std::stop_token stop) { + using namespace std::chrono; + if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) { LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); - return nullptr; + return; } LOG_INFO(Lib_AvPlayer, "Demuxer Thread started"); - while (!self->m_is_stop) { - if (self->m_video_packets.Size() > 60) { - sceKernelUsleep(5000); + while (!stop.stop_requested()) { + if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) { + std::this_thread::sleep_for(milliseconds(5)); continue; } AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket); - const auto res = av_read_frame(self->m_avformat_context.get(), up_packet.get()); + const auto res = av_read_frame(m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer"); break; } else { LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); - self->m_state.OnError(); - scePthreadExit(0); + m_state.OnError(); + return; } break; } - if (up_packet->stream_index == self->m_video_stream_index) { - self->m_video_packets.Push(std::move(up_packet)); - } else if (up_packet->stream_index == self->m_audio_stream_index) { - self->m_audio_packets.Push(std::move(up_packet)); + if (up_packet->stream_index == m_video_stream_index) { + m_video_packets.Push(std::move(up_packet)); + } else if (up_packet->stream_index == m_audio_stream_index) { + m_audio_packets.Push(std::move(up_packet)); } } - self->m_is_eof = true; + m_is_eof = true; void* res; - if (self->m_video_decoder_thread) { - scePthreadJoin(self->m_video_decoder_thread, &res); + if (m_video_decoder_thread.joinable()) { + m_video_decoder_thread.join(); } - if (self->m_audio_decoder_thread) { - scePthreadJoin(self->m_audio_decoder_thread, &res); + if (m_audio_decoder_thread.joinable()) { + m_audio_decoder_thread.join(); } - self->m_state.OnEOF(); + m_state.OnEOF(); LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); - scePthreadExit(0); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { @@ -537,67 +516,63 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame }; } -void* AvPlayerSource::VideoDecoderThread(void* opaque) { +void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { + using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); - const auto self = reinterpret_cast(opaque); - - while ((!self->m_is_eof || self->m_video_packets.Size() != 0) && !self->m_is_stop) { - if (self->m_video_packets.Size() == 0) { - sceKernelUsleep(5000); + while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { + if (m_video_packets.Size() == 0) { + std::this_thread::sleep_for(milliseconds(5)); continue; } - const auto packet = self->m_video_packets.Pop(); + const auto packet = m_video_packets.Pop(); if (!packet.has_value()) { continue; } - auto res = avcodec_send_packet(self->m_video_codec_context.get(), packet->get()); + auto res = avcodec_send_packet(m_video_codec_context.get(), packet->get()); if (res < 0 && res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } while (res >= 0) { - if (self->m_video_buffers.Size() == 0 && !self->m_is_stop) { - sceKernelUsleep(5000); + if (m_video_buffers.Size() == 0 && !stop.stop_requested()) { + std::this_thread::sleep_for(milliseconds(5)); continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); - res = avcodec_receive_frame(self->m_video_codec_context.get(), up_frame.get()); + res = avcodec_receive_frame(m_video_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder"); - scePthreadExit(nullptr); + return; } else if (res != AVERROR(EAGAIN)) { LOG_ERROR(Lib_AvPlayer, "Could not receive frame from the video codec. Error = {}", av_err2str(res)); - self->m_state.OnError(); - scePthreadExit(nullptr); + m_state.OnError(); + return; } } else { - auto buffer = self->m_video_buffers.Pop(); + auto buffer = m_video_buffers.Pop(); if (!buffer.has_value()) { // Video buffers queue was cleared. This means that player was stopped. break; } if (up_frame->format != AV_PIX_FMT_NV12) { - const auto nv12_frame = self->ConvertVideoFrame(*up_frame); - self->m_video_frames.Push( - self->PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); + const auto nv12_frame = ConvertVideoFrame(*up_frame); + m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *nv12_frame)); } else { - self->m_video_frames.Push( - self->PrepareVideoFrame(std::move(buffer.value()), *up_frame)); + m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame)); } LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", - self->m_video_frames.Size()); + m_video_frames.Size()); } } } LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); - scePthreadExit(nullptr); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { @@ -659,66 +634,67 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame }; } -void* AvPlayerSource::AudioDecoderThread(void* opaque) { +void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { + using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); - const auto self = reinterpret_cast(opaque); - - while ((!self->m_is_eof || self->m_audio_packets.Size() != 0) && !self->m_is_stop) { - if (self->m_audio_packets.Size() == 0) { - sceKernelUsleep(5000); + while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { + if (m_audio_packets.Size() == 0) { + std::this_thread::sleep_for(milliseconds(5)); continue; } - const auto packet = self->m_audio_packets.Pop(); + const auto packet = m_audio_packets.Pop(); if (!packet.has_value()) { continue; } - auto res = avcodec_send_packet(self->m_audio_codec_context.get(), packet->get()); + auto res = avcodec_send_packet(m_audio_codec_context.get(), packet->get()); if (res < 0 && res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } while (res >= 0) { - if (self->m_audio_buffers.Size() == 0 && !self->m_is_stop) { - sceKernelUsleep(5000); + if (m_audio_buffers.Size() == 0 && !stop.stop_requested()) { + std::this_thread::sleep_for(milliseconds(5)); continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); - res = avcodec_receive_frame(self->m_audio_codec_context.get(), up_frame.get()); + res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get()); if (res < 0) { if (res == AVERROR_EOF) { LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder"); - scePthreadExit(nullptr); + return; } else if (res != AVERROR(EAGAIN)) { - self->m_state.OnError(); + m_state.OnError(); LOG_ERROR(Lib_AvPlayer, "Could not receive frame from the audio codec. Error = {}", av_err2str(res)); - scePthreadExit(nullptr); + return; } } else { - auto buffer = self->m_audio_buffers.Pop(); + auto buffer = m_audio_buffers.Pop(); if (!buffer.has_value()) { // Audio buffers queue was cleared. This means that player was stopped. break; } if (up_frame->format != AV_SAMPLE_FMT_S16) { - const auto pcm16_frame = self->ConvertAudioFrame(*up_frame); - self->m_audio_frames.Push( - self->PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); + const auto pcm16_frame = ConvertAudioFrame(*up_frame); + m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame)); } else { - self->m_audio_frames.Push( - self->PrepareAudioFrame(std::move(buffer.value()), *up_frame)); + m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame)); } LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", - self->m_audio_frames.Size()); + m_audio_frames.Size()); } } } LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); - scePthreadExit(nullptr); +} + +bool AvPlayerSource::HasRunningThreads() const { + return m_demuxer_thread.joinable() || m_video_decoder_thread.joinable() || + m_audio_decoder_thread.joinable(); } } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index 67ad44d88..c2f9ac029 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -12,8 +12,11 @@ #include #include +#include #include +#include #include +#include struct AVCodecContext; struct AVFormatContext; @@ -88,8 +91,7 @@ struct Frame { class AvPlayerSource { public: AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, - const SceAvPlayerInitData& init_data, ThreadPriorities& priorities, - SceAvPlayerSourceType source_type); + const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type); ~AvPlayerSource(); bool FindStreamInfo(); @@ -109,10 +111,6 @@ public: private: using ScePthread = Kernel::ScePthread; - static void* PS4_SYSV_ABI DemuxerThread(void* opaque); - static void* PS4_SYSV_ABI VideoDecoderThread(void* opaque); - static void* PS4_SYSV_ABI AudioDecoderThread(void* opaque); - static void ReleaseAVPacket(AVPacket* packet); static void ReleaseAVFrame(AVFrame* frame); static void ReleaseAVCodecContext(AVCodecContext* context); @@ -127,6 +125,12 @@ private: using SWSContextPtr = std::unique_ptr; using AVFormatContextPtr = std::unique_ptr; + void DemuxerThread(std::stop_token stop); + void VideoDecoderThread(std::stop_token stop); + void AudioDecoderThread(std::stop_token stop); + + bool HasRunningThreads() const; + AVFramePtr ConvertAudioFrame(const AVFrame& frame); AVFramePtr ConvertVideoFrame(const AVFrame& frame); @@ -135,13 +139,11 @@ private: AvPlayerStateCallback& m_state; - ThreadPriorities m_priorities{}; SceAvPlayerMemAllocator m_memory_replacement{}; u64 m_num_output_video_framebuffers{}; std::atomic_bool m_is_looping = false; std::atomic_bool m_is_eof = false; - std::atomic_bool m_is_stop = false; std::unique_ptr m_up_data_streamer; @@ -160,9 +162,10 @@ private: std::optional m_video_stream_index{}; std::optional m_audio_stream_index{}; - ScePthread m_demuxer_thread{}; - ScePthread m_video_decoder_thread{}; - ScePthread m_audio_decoder_thread{}; + std::mutex m_state_mutex; + std::jthread m_demuxer_thread{}; + std::jthread m_video_decoder_thread{}; + std::jthread m_audio_decoder_thread{}; AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext}; AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index e7aa45715..061d1da64 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -89,12 +89,8 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i } // Called inside GAME thread -AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data, - const ThreadPriorities& priorities) - : m_init_data(init_data), m_event_replacement(init_data.event_replacement), - m_thread_priorities(priorities), - m_event_handler_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_EventHandler"), - m_state_machine_mutex(PTHREAD_MUTEX_ERRORCHECK, "AvPlayer_StateMachine") { +AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data) + : m_init_data(init_data), m_event_replacement(init_data.event_replacement) { if (m_event_replacement.event_callback == nullptr || init_data.auto_start) { m_auto_start = true; m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback; @@ -111,10 +107,9 @@ AvPlayerState::~AvPlayerState() { if (m_up_source && m_current_state == AvState::Play) { m_up_source->Stop(); } - m_quit = true; - if (m_controller_thread != nullptr) { - void* res = nullptr; - scePthreadJoin(m_controller_thread, &res); + if (m_controller_thread.joinable()) { + m_controller_thread.request_stop(); + m_controller_thread.join(); } m_event_queue.Clear(); } @@ -131,8 +126,7 @@ s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source return -1; } - m_up_source = std::make_unique(*this, path, m_init_data, m_thread_priorities, - source_type); + m_up_source = std::make_unique(*this, path, m_init_data, source_type); AddSourceEvent(); return 0; } @@ -166,17 +160,17 @@ s32 AvPlayerState::Start() { return 0; } -void* AvPlayerState::AvControllerThread(void* p_user_data) { - AvPlayerState* self = reinterpret_cast(p_user_data); - while (!self->m_quit) { - if (self->m_event_queue.Size() != 0) { - self->ProcessEvent(); +void AvPlayerState::AvControllerThread(std::stop_token stop) { + using std::chrono::milliseconds; + + while (!stop.stop_requested()) { + if (m_event_queue.Size() != 0) { + ProcessEvent(); continue; } - sceKernelUsleep(5000); - self->UpdateBufferingState(); + std::this_thread::sleep_for(milliseconds(5)); + UpdateBufferingState(); } - scePthreadExit(0); } // Called inside GAME thread @@ -198,22 +192,9 @@ void AvPlayerState::WarningEvent(s32 id) { } // Called inside GAME thread -int AvPlayerState::StartControllerThread() { - m_quit.store(0); - - ThreadParameters params{ - .p_user_data = this, - .thread_name = "AvPlayer_Controller", - .stack_size = 0x4000, - .priority = m_thread_priority, - .affinity = m_thread_affinity, - }; - m_controller_thread = CreateThread(&AvControllerThread, params); - if (m_controller_thread == nullptr) { - LOG_ERROR(Lib_AvPlayer, "Could not create CONTROLLER thread."); - return -1; - } - return 0; +void AvPlayerState::StartControllerThread() { + m_controller_thread = + std::jthread([this](std::stop_token stop) { this->AvControllerThread(stop); }); } // Called inside GAME thread @@ -262,7 +243,7 @@ bool AvPlayerState::IsActive() { return false; } return m_current_state != AvState::Stop && m_current_state != AvState::Error && - m_up_source->IsActive(); + m_current_state != AvState::EndOfFile && m_up_source->IsActive(); } u64 AvPlayerState::CurrentTime() { @@ -284,7 +265,9 @@ void AvPlayerState::OnError() { OnPlaybackStateChanged(AvState::Error); } -void AvPlayerState::OnEOF() {} +void AvPlayerState::OnEOF() { + SetState(AvState::EndOfFile); +} // Called inside CONTROLLER thread void AvPlayerState::OnPlaybackStateChanged(AvState state) { @@ -347,16 +330,16 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { } // Called inside CONTROLLER thread -int AvPlayerState::ProcessEvent() { +void AvPlayerState::ProcessEvent() { if (m_current_state == AvState::Jump) { - return -2; + return; } std::lock_guard guard(m_event_handler_mutex); auto event = m_event_queue.Pop(); if (!event.has_value()) { - return -1; + return; } switch (event->event) { case AvEventType::WarningId: { @@ -385,16 +368,14 @@ int AvPlayerState::ProcessEvent() { default: break; } - - return 0; } // Called inside CONTROLLER thread -int AvPlayerState::UpdateBufferingState() { +void AvPlayerState::UpdateBufferingState() { if (m_current_state == AvState::Buffering) { const auto has_frames = OnBufferingCheckEvent(10); if (!has_frames.has_value()) { - return -1; + return; } if (has_frames.value()) { const auto state = @@ -405,14 +386,13 @@ int AvPlayerState::UpdateBufferingState() { } else if (m_current_state == AvState::Play) { const auto has_frames = OnBufferingCheckEvent(0); if (!has_frames.has_value()) { - return -1; + return; } if (!has_frames.value()) { SetState(AvState::Buffering); OnPlaybackStateChanged(AvState::Buffering); } } - return 0; } bool AvPlayerState::IsStateTransitionValid(AvState state) { diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index cfddab098..8aacc5752 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -10,6 +10,9 @@ #include "core/libraries/kernel/thread_management.h" #include +#include +#include +#include namespace Libraries::AvPlayer { @@ -18,7 +21,7 @@ class AvDecoder; class AvPlayerState : public AvPlayerStateCallback { public: - AvPlayerState(const SceAvPlayerInitData& init_data, const ThreadPriorities& priorities); + AvPlayerState(const SceAvPlayerInitData& init_data); ~AvPlayerState(); s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type); @@ -51,34 +54,32 @@ private: void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr); bool SetState(AvState state); - static void* PS4_SYSV_ABI AvControllerThread(void* p_user_data); + void AvControllerThread(std::stop_token stop); void AddSourceEvent(); void WarningEvent(s32 id); - int StartControllerThread(); - int ProcessEvent(); - int UpdateBufferingState(); + void StartControllerThread(); + void ProcessEvent(); + void UpdateBufferingState(); bool IsStateTransitionValid(AvState state); std::unique_ptr m_up_source; SceAvPlayerInitData m_init_data{}; SceAvPlayerEventReplacement m_event_replacement{}; - ThreadPriorities m_thread_priorities{}; bool m_auto_start{}; u8 m_default_language[4]{}; - std::atomic_bool m_quit; std::atomic m_current_state; std::atomic m_previous_state; u32 m_thread_priority; u32 m_thread_affinity; std::atomic_uint32_t m_some_event_result{}; - PthreadMutex m_state_machine_mutex{}; - PthreadMutex m_event_handler_mutex{}; - ScePthread m_controller_thread{}; + std::mutex m_state_machine_mutex{}; + std::mutex m_event_handler_mutex{}; + std::jthread m_controller_thread{}; AvPlayerQueue m_event_queue{}; }; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index bd555ccc6..c5935275f 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -158,24 +158,19 @@ void init_pthreads(); void pthreadInitSelfMainThread(); int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr); -int PS4_SYSV_ABI scePthreadAttrDestroy(ScePthreadAttr* attr); int PS4_SYSV_ABI scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate); int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched); int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr, const SceKernelSchedParam* param); int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy); +ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask); -int PS4_SYSV_ABI scePthreadAttrSetstacksize(ScePthreadAttr* attr, size_t stack_size); - -ScePthread PS4_SYSV_ABI scePthreadSelf(); int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask); int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask); int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr, PthreadEntryFunc start_routine, void* arg, const char* name); -[[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr); -int PS4_SYSV_ABI scePthreadJoin(ScePthread thread, void** res); -int PS4_SYSV_ABI scePthreadGetprio(ScePthread thread, int* prio); + int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); /*** @@ -183,14 +178,11 @@ int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio); */ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name); -int PS4_SYSV_ABI scePthreadMutexDestroy(ScePthreadMutex* mutex); -int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); -int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); - -int PS4_SYSV_ABI scePthreadMutexattrDestroy(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrInit(ScePthreadMutexattr* attr); int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type); int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol); +int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex); +int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex); /**** * Cond calls */ diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index 8934171d0..a28f8c133 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -50,7 +50,6 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); -int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 64237994e..98ff4fdd2 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -792,10 +792,11 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { } s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data)); - if (std::string(mountPoint->data).empty()) { + if (mountPoint->data == nullptr) { + LOG_WARNING(Lib_SaveData, "mountPoint = nullptr"); return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; } + LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(1) / game_serial / mountPoint->data; auto* mnt = Common::Singleton::Instance(); From 5c4ac98d499ac10392dfda4e4559c0db659a1fb5 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Wed, 14 Aug 2024 22:00:01 +0300 Subject: [PATCH 105/109] fixing build on linux and mac --- src/audio_core/sdl_audio.cpp | 10 +++++++--- src/core/libraries/avplayer/avplayer.cpp | 7 ------- src/core/libraries/avplayer/avplayer_file_streamer.cpp | 6 +++--- src/core/libraries/avplayer/avplayer_source.h | 3 +-- src/core/libraries/avplayer/avplayer_state.h | 3 +-- src/core/libraries/save_data/savedata.cpp | 5 ++--- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 9c8c5991c..f544c52f9 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "sdl_audio.h" + +#include "common/assert.h" +#include "core/libraries/error_codes.h" + #include #include #include -#include "common/assert.h" -#include "core/libraries/error_codes.h" -#include "sdl_audio.h" + +#include // std::unique_lock namespace Audio { diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 8fccc6590..8a1152a34 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -9,8 +9,6 @@ #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" -#include // std::max, std::min - namespace Libraries::AvPlayer { using namespace Kernel; @@ -119,11 +117,6 @@ bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle, return res; } -constexpr u32 GetPriority(u32 base, u32 offset) { - // (27D <= base_priority <= 2FC) + offset <= 2FF - return std::min(std::min(std::max(637u, base), 764u) + offset, 767u); -} - SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) { LOG_TRACE(Lib_AvPlayer, "called"); if (data == nullptr) { diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp index 4b1ddb6e6..dc1386a47 100644 --- a/src/core/libraries/avplayer/avplayer_file_streamer.cpp +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -69,14 +69,14 @@ s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) { if (whence == SEEK_CUR) { self->m_position = - std::min(u64(std::max(0i64, s64(self->m_position) + offset)), self->m_file_size); + std::min(u64(std::max(s64(0), s64(self->m_position) + offset)), self->m_file_size); return self->m_position; } else if (whence == SEEK_SET) { - self->m_position = std::min(u64(std::max(0i64, offset)), self->m_file_size); + self->m_position = std::min(u64(std::max(s64(0), offset)), self->m_file_size); return self->m_position; } else if (whence == SEEK_END) { self->m_position = - std::min(u64(std::max(0i64, s64(self->m_file_size) + offset)), self->m_file_size); + std::min(u64(std::max(s64(0), s64(self->m_file_size) + offset)), self->m_file_size); return self->m_position; } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index c2f9ac029..b3fbb30d6 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -8,15 +8,14 @@ #include "avplayer_data_streamer.h" #include "common/types.h" +#include "common/polyfill_thread.h" #include "core/libraries/kernel/thread_management.h" #include #include #include #include -#include #include -#include struct AVCodecContext; struct AVFormatContext; diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index 8aacc5752..eed3265fe 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -7,12 +7,11 @@ #include "avplayer_data_streamer.h" #include "avplayer_source.h" +#include "common/polyfill_thread.h" #include "core/libraries/kernel/thread_management.h" #include #include -#include -#include namespace Libraries::AvPlayer { diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 98ff4fdd2..20496d76d 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -792,11 +792,10 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { } s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - if (mountPoint->data == nullptr) { - LOG_WARNING(Lib_SaveData, "mountPoint = nullptr"); + LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); + if (std::string_view(mountPoint->data).empty()) { return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; } - LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(1) / game_serial / mountPoint->data; auto* mnt = Common::Singleton::Instance(); From b3ef959b25a25f4657fc761d0ca27dde50dbfec5 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Thu, 15 Aug 2024 21:59:59 +0300 Subject: [PATCH 106/109] Fixed threading, migrated to CVs, added looping --- .gitmodules | 2 +- src/core/libraries/avplayer/avplayer.cpp | 7 +- src/core/libraries/avplayer/avplayer_impl.cpp | 7 ++ src/core/libraries/avplayer/avplayer_impl.h | 1 + .../libraries/avplayer/avplayer_source.cpp | 103 +++++++++++------- src/core/libraries/avplayer/avplayer_source.h | 47 +++++++- .../libraries/avplayer/avplayer_state.cpp | 40 +++++-- src/core/libraries/avplayer/avplayer_state.h | 3 + 8 files changed, 161 insertions(+), 49 deletions(-) diff --git a/.gitmodules b/.gitmodules index e96f33ecd..60fb5fbbf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -63,4 +63,4 @@ url = https://github.com/HowardHinnant/date.git [submodule "externals/ffmpeg-core"] path = externals/ffmpeg-core - url = https://github.com/RPCS3/ffmpeg-core.git + url = https://github.com/shadps4-emu/ext-ffmpeg-core diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 8a1152a34..78e03d78c 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -230,6 +230,9 @@ s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } + if (!handle->SetLooping(loop_flag)) { + return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED; + } return ORBIS_OK; } @@ -256,7 +259,9 @@ s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) { if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } - return handle->Stop(); + const auto res = handle->Stop(); + LOG_TRACE(Lib_AvPlayer, "returning {}", res); + return res; } s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) { diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 1114254f8..cdfff8277 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -190,4 +190,11 @@ s32 AvPlayer::Stop() { return ORBIS_OK; } +bool AvPlayer::SetLooping(bool is_looping) { + if (m_state == nullptr) { + return false; + } + return m_state->SetLooping(is_looping); +} + } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index df4acfee0..09989d399 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -37,6 +37,7 @@ public: bool IsActive(); u64 CurrentTime(); s32 Stop(); + bool SetLooping(bool is_looping); private: using ScePthreadMutex = Kernel::ScePthreadMutex; diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index e235b2c2c..964e0fbdb 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -27,7 +27,8 @@ AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view pa const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type) : m_state(state), m_memory_replacement(init_data.memory_replacement), - m_num_output_video_framebuffers(init_data.num_output_video_framebuffers) { + m_num_output_video_framebuffers( + std::min(std::max(2, init_data.num_output_video_framebuffers), 16)) { AVFormatContext* context = avformat_alloc_context(); if (init_data.file_replacement.open != nullptr) { m_up_data_streamer = @@ -208,7 +209,7 @@ void AvPlayerSource::SetLooping(bool is_looping) { } std::optional AvPlayerSource::HasFrames(u32 num_frames) { - return m_video_frames.Size() > num_frames || m_is_eof; + return m_video_packets.Size() > num_frames || m_is_eof; } s32 AvPlayerSource::Start() { @@ -255,6 +256,7 @@ bool AvPlayerSource::Stop() { m_video_buffers.Push(std::move(m_current_video_frame.value())); m_current_video_frame.reset(); } + m_stop_cv.Notify(); m_audio_packets.Clear(); m_video_packets.Clear(); @@ -291,30 +293,30 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { return false; } - using namespace std::chrono; - while (m_video_frames.Size() == 0 && !m_is_eof) { - std::this_thread::sleep_for(milliseconds(5)); - } + m_video_frames_cv.Wait([this]() { return m_video_frames.Size() != 0 || m_is_eof; }); auto frame = m_video_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get video frame: no frames."); + LOG_WARNING(Lib_AvPlayer, "Could get video frame. EOF reached."); return false; } { + using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < frame->info.timestamp) { - std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); - elapsed_time = - duration_cast(high_resolution_clock::now() - m_start_time).count(); + if (elapsed_time < frame->info.timestamp) { + if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), + [&]() { return elapsed_time >= frame->info.timestamp; })) { + return false; + } } } // return the buffer to the queue if (m_current_video_frame.has_value()) { m_video_buffers.Push(std::move(m_current_video_frame.value())); + m_video_buffers_cv.Notify(); } m_current_video_frame = std::move(frame->buffer); video_info = frame->info; @@ -326,30 +328,30 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { return false; } - using namespace std::chrono; - while (m_audio_frames.Size() == 0 && !m_is_eof) { - std::this_thread::sleep_for(milliseconds(5)); - } + m_audio_frames_cv.Wait([this]() { return m_audio_frames.Size() != 0 || m_is_eof; }); auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { - LOG_WARNING(Lib_AvPlayer, "Could get audio frame: no frames."); + LOG_WARNING(Lib_AvPlayer, "Could get audio frame. EOF reached."); return false; } { + using namespace std::chrono; auto elapsed_time = duration_cast(high_resolution_clock::now() - m_start_time).count(); - while (elapsed_time < frame->info.timestamp) { - std::this_thread::sleep_for(milliseconds(frame->info.timestamp - elapsed_time)); - elapsed_time = - duration_cast(high_resolution_clock::now() - m_start_time).count(); + if (elapsed_time < frame->info.timestamp) { + if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), + [&]() { return elapsed_time >= frame->info.timestamp; })) { + return false; + } } } // return the buffer to the queue if (m_current_audio_frame.has_value()) { m_audio_buffers.Push(std::move(m_current_audio_frame.value())); + m_audio_buffers_cv.Notify(); } m_current_audio_frame = std::move(frame->buffer); @@ -424,8 +426,26 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { const auto res = av_read_frame(m_avformat_context.get(), up_packet.get()); if (res < 0) { if (res == AVERROR_EOF) { - LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer"); - break; + if (m_is_looping) { + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Looping the source..."); + avio_seek(m_avformat_context->pb, 0, SEEK_SET); + if (m_video_stream_index.has_value()) { + const auto index = m_video_stream_index.value(); + const auto stream = m_avformat_context->streams[index]; + avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration, + 0); + } + if (m_audio_stream_index.has_value()) { + const auto index = m_audio_stream_index.value(); + const auto stream = m_avformat_context->streams[index]; + avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration, + 0); + } + continue; + } else { + LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Exiting."); + break; + } } else { LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res); m_state.OnError(); @@ -435,14 +455,20 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { } if (up_packet->stream_index == m_video_stream_index) { m_video_packets.Push(std::move(up_packet)); + m_video_packets_cv.Notify(); } else if (up_packet->stream_index == m_audio_stream_index) { m_audio_packets.Push(std::move(up_packet)); + m_audio_packets_cv.Notify(); } } m_is_eof = true; - void* res; + m_video_packets_cv.Notify(); + m_audio_packets_cv.Notify(); + m_video_frames_cv.Notify(); + m_audio_frames_cv.Notify(); + if (m_video_decoder_thread.joinable()) { m_video_decoder_thread.join(); } @@ -457,7 +483,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { auto nv12_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; nv12_frame->pts = frame.pts; - nv12_frame->pkt_dts = frame.pkt_dts; + nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; nv12_frame->format = AV_PIX_FMT_NV12; nv12_frame->width = frame.width; nv12_frame->height = frame.height; @@ -520,8 +546,8 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { - if (m_video_packets.Size() == 0) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_video_packets_cv.Wait( + stop, [this]() { return m_video_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_video_packets.Pop(); @@ -537,8 +563,10 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (m_video_buffers.Size() == 0 && !stop.stop_requested()) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_video_buffers_cv.Wait(stop, [this]() { return m_video_buffers.Size() != 0; })) { + break; + } + if (m_video_buffers.Size() == 0) { continue; } auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); @@ -566,8 +594,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { } else { m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame)); } - LOG_TRACE(Lib_AvPlayer, "Produced Video Frame. Num Frames: {}", - m_video_frames.Size()); + m_video_frames_cv.Notify(); } } } @@ -578,7 +605,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { auto pcm16_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame}; pcm16_frame->pts = frame.pts; - pcm16_frame->pkt_dts = frame.pkt_dts; + pcm16_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; pcm16_frame->format = AV_SAMPLE_FMT_S16; pcm16_frame->ch_layout = frame.ch_layout; pcm16_frame->sample_rate = frame.sample_rate; @@ -638,8 +665,8 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { - if (m_audio_packets.Size() == 0) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_audio_packets_cv.Wait( + stop, [this]() { return m_audio_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_audio_packets.Pop(); @@ -654,10 +681,13 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (m_audio_buffers.Size() == 0 && !stop.stop_requested()) { - std::this_thread::sleep_for(milliseconds(5)); + if (!m_audio_buffers_cv.Wait(stop, [this]() { return m_audio_buffers.Size() != 0; })) { + break; + } + if (m_audio_buffers.Size() == 0) { continue; } + auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame); res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get()); if (res < 0) { @@ -683,8 +713,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { } else { m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame)); } - LOG_TRACE(Lib_AvPlayer, "Produced Audio Frame. Num Frames: {}", - m_audio_frames.Size()); + m_audio_frames_cv.Notify(); } } } diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h index b3fbb30d6..7144e7ee4 100644 --- a/src/core/libraries/avplayer/avplayer_source.h +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -7,12 +7,13 @@ #include "avplayer_common.h" #include "avplayer_data_streamer.h" -#include "common/types.h" #include "common/polyfill_thread.h" +#include "common/types.h" #include "core/libraries/kernel/thread_management.h" #include #include +#include #include #include #include @@ -87,6 +88,36 @@ struct Frame { SceAvPlayerFrameInfoEx info; }; +class EventCV { +public: + template + void Wait(Pred pred) { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, std::move(pred)); + } + + template + bool Wait(std::stop_token stop, Pred pred) { + std::unique_lock lock(m_mutex); + return m_cv.wait(lock, std::move(stop), std::move(pred)); + } + + template + bool WaitFor(std::chrono::duration timeout, Pred pred) { + std::unique_lock lock(m_mutex); + return m_cv.wait_for(lock, timeout, std::move(pred)); + } + + void Notify() { + std::unique_lock lock(m_mutex); + m_cv.notify_all(); + } + +private: + std::mutex m_mutex{}; + std::condition_variable_any m_cv{}; +}; + class AvPlayerSource { public: AvPlayerSource(AvPlayerStateCallback& state, std::string_view path, @@ -139,7 +170,7 @@ private: AvPlayerStateCallback& m_state; SceAvPlayerMemAllocator m_memory_replacement{}; - u64 m_num_output_video_framebuffers{}; + u32 m_num_output_video_framebuffers{}; std::atomic_bool m_is_looping = false; std::atomic_bool m_is_eof = false; @@ -161,7 +192,17 @@ private: std::optional m_video_stream_index{}; std::optional m_audio_stream_index{}; - std::mutex m_state_mutex; + EventCV m_audio_packets_cv{}; + EventCV m_audio_frames_cv{}; + EventCV m_audio_buffers_cv{}; + + EventCV m_video_packets_cv{}; + EventCV m_video_frames_cv{}; + EventCV m_video_buffers_cv{}; + + EventCV m_stop_cv{}; + + std::mutex m_state_mutex{}; std::jthread m_demuxer_thread{}; std::jthread m_video_decoder_thread{}; std::jthread m_audio_decoder_thread{}; diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index 061d1da64..884cd9408 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -104,8 +104,9 @@ AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data) } AvPlayerState::~AvPlayerState() { - if (m_up_source && m_current_state == AvState::Play) { - m_up_source->Stop(); + { + std::unique_lock lock(m_source_mutex); + m_up_source.reset(); } if (m_controller_thread.joinable()) { m_controller_thread.request_stop(); @@ -121,18 +122,22 @@ s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source return -1; } - if (m_up_source != nullptr) { - LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); - return -1; - } + { + std::unique_lock lock(m_source_mutex); + if (m_up_source != nullptr) { + LOG_ERROR(Lib_AvPlayer, "Only one source is supported."); + return -1; + } - m_up_source = std::make_unique(*this, path, m_init_data, source_type); + m_up_source = std::make_unique(*this, path, m_init_data, source_type); + } AddSourceEvent(); return 0; } // Called inside GAME thread s32 AvPlayerState::GetStreamCount() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source."); return -1; @@ -142,6 +147,7 @@ s32 AvPlayerState::GetStreamCount() { // Called inside GAME thread s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index); return -1; @@ -151,6 +157,7 @@ s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) // Called inside GAME thread s32 AvPlayerState::Start() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr || m_up_source->Start() < 0) { LOG_ERROR(Lib_AvPlayer, "Could not start playback."); return -1; @@ -199,6 +206,7 @@ void AvPlayerState::StartControllerThread() { // Called inside GAME thread bool AvPlayerState::EnableStream(u32 stream_index) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -207,6 +215,7 @@ bool AvPlayerState::EnableStream(u32 stream_index) { // Called inside GAME thread bool AvPlayerState::Stop() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr || m_current_state == AvState::Stop) { return false; } @@ -218,6 +227,7 @@ bool AvPlayerState::Stop() { } bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -225,6 +235,7 @@ bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) { } bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -232,6 +243,7 @@ bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { } bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -239,6 +251,7 @@ bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) { } bool AvPlayerState::IsActive() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { return false; } @@ -247,6 +260,7 @@ bool AvPlayerState::IsActive() { } u64 AvPlayerState::CurrentTime() { + std::shared_lock lock(m_source_mutex); if (m_up_source == nullptr) { LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source."); return 0; @@ -254,6 +268,16 @@ u64 AvPlayerState::CurrentTime() { return m_up_source->CurrentTime(); } +bool AvPlayerState::SetLooping(bool is_looping) { + std::shared_lock lock(m_source_mutex); + if (m_up_source == nullptr) { + LOG_ERROR(Lib_AvPlayer, "Could not set loop flag. No source."); + return false; + } + m_up_source->SetLooping(is_looping); + return true; +} + // May be called from different threads void AvPlayerState::OnWarning(u32 id) { // Forward to CONTROLLER thread @@ -313,6 +337,7 @@ bool AvPlayerState::SetState(AvState state) { // Called inside CONTROLLER thread std::optional AvPlayerState::OnBufferingCheckEvent(u32 num_frames) { + std::shared_lock lock(m_source_mutex); if (!m_up_source) { return std::nullopt; } @@ -351,6 +376,7 @@ void AvPlayerState::ProcessEvent() { break; } case AvEventType::AddSource: { + std::shared_lock lock(m_source_mutex); if (m_up_source->FindStreamInfo()) { SetState(AvState::Ready); OnPlaybackStateChanged(AvState::Ready); diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index eed3265fe..ff80b6cea 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -12,6 +12,7 @@ #include #include +#include namespace Libraries::AvPlayer { @@ -34,6 +35,7 @@ public: bool GetVideoData(SceAvPlayerFrameInfoEx& video_info); bool IsActive(); u64 CurrentTime(); + bool SetLooping(bool is_looping); private: using ScePthreadMutex = Kernel::ScePthreadMutex; @@ -76,6 +78,7 @@ private: u32 m_thread_affinity; std::atomic_uint32_t m_some_event_result{}; + std::shared_mutex m_source_mutex{}; std::mutex m_state_machine_mutex{}; std::mutex m_event_handler_mutex{}; std::jthread m_controller_thread{}; From 62741434db967488884dd8d72fb99f0c5bef9fb9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:22:35 -0700 Subject: [PATCH 107/109] Enable -fexperimental-library when using clang libc++ --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3685b7f85..09262d830 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,15 @@ if(HAVE_SEM_TIMEDWAIT OR WIN32) add_compile_options(-DHAVE_SEM_TIMEDWAIT) 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) + check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP) + if(LIBCPP) + add_compile_options(-fexperimental-library) + endif() +endif() + add_subdirectory(externals) include_directories(src) From 23dddca1f0febc085d0254064a60c5a526575576 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Fri, 16 Aug 2024 10:30:48 +0300 Subject: [PATCH 108/109] last minute fixes --- src/core/libraries/avplayer/avplayer.cpp | 6 ++--- .../libraries/avplayer/avplayer_source.cpp | 25 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 78e03d78c..bd1f6b503 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -172,8 +172,8 @@ bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) { return res; } -s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t jump_time_msec) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t time) { + LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, time (msec) = {}", time); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } @@ -226,7 +226,7 @@ s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* } s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) { - LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, looping = {}", loop_flag); + LOG_TRACE(Lib_AvPlayer, "called, looping = {}", loop_flag); if (handle == nullptr) { return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; } diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 964e0fbdb..776d389f0 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -82,7 +82,7 @@ static s32 CodecTypeToStreamType(AVMediaType codec_type) { } } -static f32 AVRationalToF32(AVRational rational) { +static f32 AVRationalToF32(const AVRational& rational) { return f32(rational.num) / rational.den; } @@ -109,7 +109,8 @@ s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) switch (info.type) { case SCE_AVPLAYER_VIDEO: LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index); - info.details.video.aspect_ratio = AVRationalToF32(p_stream->codecpar->sample_aspect_ratio); + info.details.video.aspect_ratio = + f32(p_stream->codecpar->width) / p_stream->codecpar->height; info.details.video.width = p_stream->codecpar->width; info.details.video.height = p_stream->codecpar->height; if (p_lang_node != nullptr) { @@ -293,7 +294,7 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { return false; } - m_video_frames_cv.Wait([this]() { return m_video_frames.Size() != 0 || m_is_eof; }); + m_video_frames_cv.Wait([this] { return m_video_frames.Size() != 0 || m_is_eof; }); auto frame = m_video_frames.Pop(); if (!frame.has_value()) { @@ -307,7 +308,7 @@ bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) { duration_cast(high_resolution_clock::now() - m_start_time).count(); if (elapsed_time < frame->info.timestamp) { if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), - [&]() { return elapsed_time >= frame->info.timestamp; })) { + [&] { return elapsed_time >= frame->info.timestamp; })) { return false; } } @@ -328,7 +329,7 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { return false; } - m_audio_frames_cv.Wait([this]() { return m_audio_frames.Size() != 0 || m_is_eof; }); + m_audio_frames_cv.Wait([this] { return m_audio_frames.Size() != 0 || m_is_eof; }); auto frame = m_audio_frames.Pop(); if (!frame.has_value()) { @@ -342,7 +343,7 @@ bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) { duration_cast(high_resolution_clock::now() - m_start_time).count(); if (elapsed_time < frame->info.timestamp) { if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time), - [&]() { return elapsed_time >= frame->info.timestamp; })) { + [&] { return elapsed_time >= frame->info.timestamp; })) { return false; } } @@ -546,8 +547,8 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { - if (!m_video_packets_cv.Wait( - stop, [this]() { return m_video_packets.Size() != 0 || m_is_eof; })) { + if (!m_video_packets_cv.Wait(stop, + [this] { return m_video_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_video_packets.Pop(); @@ -563,7 +564,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (!m_video_buffers_cv.Wait(stop, [this]() { return m_video_buffers.Size() != 0; })) { + if (!m_video_buffers_cv.Wait(stop, [this] { return m_video_buffers.Size() != 0; })) { break; } if (m_video_buffers.Size() == 0) { @@ -665,8 +666,8 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { - if (!m_audio_packets_cv.Wait( - stop, [this]() { return m_audio_packets.Size() != 0 || m_is_eof; })) { + if (!m_audio_packets_cv.Wait(stop, + [this] { return m_audio_packets.Size() != 0 || m_is_eof; })) { continue; } const auto packet = m_audio_packets.Pop(); @@ -681,7 +682,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { return; } while (res >= 0) { - if (!m_audio_buffers_cv.Wait(stop, [this]() { return m_audio_buffers.Size() != 0; })) { + if (!m_audio_buffers_cv.Wait(stop, [this] { return m_audio_buffers.Size() != 0; })) { break; } if (m_audio_buffers.Size() == 0) { From c1fb5d5bca4d066b9e2087cc522299b63568debb Mon Sep 17 00:00:00 2001 From: Herman Semenov Date: Fri, 16 Aug 2024 08:36:05 +0000 Subject: [PATCH 109/109] core,shader_recompiler: added const ref filesystem::path and removed if type size less 16 (#446) --- src/core/file_format/pkg.cpp | 2 +- src/core/file_format/pkg.h | 2 +- src/core/file_format/trp.cpp | 4 ++-- src/core/file_format/trp.h | 4 ++-- src/shader_recompiler/ir/attribute.h | 2 +- src/shader_recompiler/ir/condition.h | 2 +- src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp | 8 ++++---- src/shader_recompiler/runtime_info.h | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index 6d5fb0d4a..336d81019 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -350,7 +350,7 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem:: return true; } -void PKG::ExtractFiles(const int& index) { +void PKG::ExtractFiles(const int index) { int inode_number = fsTable[index].inode; int inode_type = fsTable[index].type; std::string inode_name = fsTable[index].name; diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h index 3fef6c1c4..b6b09a191 100644 --- a/src/core/file_format/pkg.h +++ b/src/core/file_format/pkg.h @@ -104,7 +104,7 @@ public: ~PKG(); bool Open(const std::filesystem::path& filepath); - void ExtractFiles(const int& index); + void ExtractFiles(const int index); bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, std::string& failreason); diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index b4d4c95e2..f122709e4 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -6,7 +6,7 @@ TRP::TRP() = default; TRP::~TRP() = default; -void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) { +void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) { std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat"; Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read); if (!npbindFile.IsOpen()) { @@ -27,7 +27,7 @@ static void removePadding(std::vector& vec) { } } -bool TRP::Extract(std::filesystem::path trophyPath) { +bool TRP::Extract(const std::filesystem::path& trophyPath) { std::string title = trophyPath.filename().string(); std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; if (!std::filesystem::exists(gameSysDir)) { diff --git a/src/core/file_format/trp.h b/src/core/file_format/trp.h index 6d1f13bd9..56f490026 100644 --- a/src/core/file_format/trp.h +++ b/src/core/file_format/trp.h @@ -33,8 +33,8 @@ class TRP { public: TRP(); ~TRP(); - bool Extract(std::filesystem::path trophyPath); - void GetNPcommID(std::filesystem::path trophyPath, int index); + bool Extract(const std::filesystem::path& trophyPath); + void GetNPcommID(const std::filesystem::path& trophyPath, int index); private: Crypto crypto; diff --git a/src/shader_recompiler/ir/attribute.h b/src/shader_recompiler/ir/attribute.h index 3f95ff7ac..2c67411ff 100644 --- a/src/shader_recompiler/ir/attribute.h +++ b/src/shader_recompiler/ir/attribute.h @@ -105,7 +105,7 @@ struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } - auto format(const Shader::IR::Attribute& attribute, format_context& ctx) const { + auto format(const Shader::IR::Attribute attribute, format_context& ctx) const { return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute)); } }; diff --git a/src/shader_recompiler/ir/condition.h b/src/shader_recompiler/ir/condition.h index 4b60be674..da986c48d 100644 --- a/src/shader_recompiler/ir/condition.h +++ b/src/shader_recompiler/ir/condition.h @@ -44,7 +44,7 @@ constexpr std::string_view NameOf(Condition condition) { template <> struct fmt::formatter : formatter { - auto format(const Shader::IR::Condition& cond, format_context& ctx) const { + auto format(const Shader::IR::Condition cond, format_context& ctx) const { return formatter::format(NameOf(cond), ctx); } }; diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 805914924..eef73a659 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -129,19 +129,19 @@ IR::Opcode UndefOpcode(IR::VectorReg) noexcept { return IR::Opcode::UndefU32; } -IR::Opcode UndefOpcode(const VccLoTag&) noexcept { +IR::Opcode UndefOpcode(const VccLoTag) noexcept { return IR::Opcode::UndefU32; } -IR::Opcode UndefOpcode(const SccLoTag&) noexcept { +IR::Opcode UndefOpcode(const SccLoTag) noexcept { return IR::Opcode::UndefU32; } -IR::Opcode UndefOpcode(const VccHiTag&) noexcept { +IR::Opcode UndefOpcode(const VccHiTag) noexcept { return IR::Opcode::UndefU32; } -IR::Opcode UndefOpcode(const FlagTag&) noexcept { +IR::Opcode UndefOpcode(const FlagTag) noexcept { return IR::Opcode::UndefU1; } diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 9b592e128..674d7c5f9 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -226,7 +226,7 @@ struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } - auto format(const Shader::Stage& stage, format_context& ctx) const { + auto format(const Shader::Stage stage, format_context& ctx) const { constexpr static std::array names = {"fs", "vs", "gs", "es", "hs", "ls", "cs"}; return fmt::format_to(ctx.out(), "{}", names[static_cast(stage)]); }

+hGuT$h)5&aF9V2Ylvwa92-c7PfTY&Ec)R%xTO! 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 z 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