From b89e1531a6335be5193a3780e242f3004553f8ed Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:16:16 -0500 Subject: [PATCH] sync(upstream): integrate Batch 33 dialogs, savedata events, reaper thread, and macos tls relinker fixes --- core/libs/nid/include/nid/ElfPatcher.hpp | 1 + core/libs/nid/src/ElfNidPatcher.cpp | 50 +++++ core/libs/nid/src/PeNidPatcher.cpp | 99 +++++++-- core/libs/nid/tests/test_gnu_hash.py | 84 ++++++- core/libs/nid/tests/test_pe_references.py | 177 +++++++++++++++ core/libs/prx/libSceAgc/CMakeLists.txt | 1 + .../include/Driver/Draw/DrawPipeline.hpp | 7 + .../src/Driver/Draw/DrawPipeline.cpp | 49 ++++- .../Graphics/include/Recorder.hpp | 1 + .../libSceAgcDriver/Graphics/src/Recorder.cpp | 14 +- .../tests/execution/ImageSampleVccSampler.cpp | 166 ++++++++++++++ core/libs/prx/libSceCdlgG2P/Export.cpp | 1 + .../libScePrecompiledShaders/CMakeLists.txt | 15 ++ .../prx/libScePrecompiledShaders/Export.cpp | 39 ++++ .../include/PrecompiledShaders.hpp | 24 ++ .../libs/prx/libSceSaveData.native/Export.cpp | 8 + core/libs/prx/libSceVoiceChat/Export.cpp | 206 ++++++++++++++++++ core/libs/tests/GuestG2PDialog.cpp | 9 + core/libs/tests/GuestGenericCategory.cpp | 2 +- core/libs/tests/GuestPrecompiledShaders.cpp | 89 ++++++++ core/libs/tests/GuestSaveDataConvert.cpp | 6 + core/relinker/CMakeLists.txt | 3 + .../elfpatcher/src/macos/MacOsImage.cpp | 5 +- .../src/analysis/CodeInstructionCollector.cpp | 2 +- .../UnusedNidFilter/EhFrameReader.cpp | 5 +- .../tests/UnwindRecordBoundsTests.cpp | 5 +- .../relinker/tests/test_init_array_roots.py | 4 +- .../relinker/tests/test_macos_tls_overlap.py | 50 +++++ docs/dev/debt/libScePrecompiledShaders.md | 5 + docs/dev/debt/libSceSaveData.native.md | 2 +- docs/dev/debt/libSceVoiceChat.md | 1 + docs/dev/debt/shader.md | 1 + 32 files changed, 1091 insertions(+), 40 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp create mode 100644 core/libs/prx/libScePrecompiledShaders/CMakeLists.txt create mode 100644 core/libs/prx/libScePrecompiledShaders/Export.cpp create mode 100644 core/libs/prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp create mode 100644 core/libs/tests/GuestPrecompiledShaders.cpp create mode 100644 core/relinker/relinker/tests/test_macos_tls_overlap.py create mode 100644 docs/dev/debt/libScePrecompiledShaders.md diff --git a/core/libs/nid/include/nid/ElfPatcher.hpp b/core/libs/nid/include/nid/ElfPatcher.hpp index a0194d7faea4..0415b4eb7f88 100644 --- a/core/libs/nid/include/nid/ElfPatcher.hpp +++ b/core/libs/nid/include/nid/ElfPatcher.hpp @@ -91,6 +91,7 @@ inline bool IsDynamicNameTag(std::int64_t tag) { return tag == kDtNeeded || tag == kDtSoname || tag == kDtRpath || tag == kDtRunpath; } constexpr std::uint32_t kShtDynamic = 6u; +constexpr std::uint32_t kShtHash = 5u; constexpr std::uint32_t kShtGnuVerdef = 0x6ffffffdu; constexpr std::uint32_t kShtGnuVerneed = 0x6ffffffeu; constexpr std::uint32_t kShtDynsym = 11u; diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index b5470147d977..8f6ae302102d 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -27,6 +27,44 @@ std::uint32_t GnuHash(const std::string& name) { return h; } +std::uint32_t SysvHash(const std::string& name) { + std::uint32_t hash = 0u; + for (const unsigned char byte : name) { + hash = (hash << 4u) + byte; + const auto high = hash & 0xf0000000u; + hash ^= high >> 24u; + hash &= ~high; + } + return hash; +} + +void ValidateSysvHash(const std::vector& elf, std::size_t offset, std::size_t size, std::size_t symCount) { + using namespace Internal; + RequireElfRange(elf, offset, size, "hash"); + if (size < 8u) throw std::runtime_error("invalid ELF hash header"); + const auto buckets = Read(elf, offset); + const auto chains = Read(elf, offset + 4u); + if (!buckets || chains != symCount || 8u + (std::uint64_t{buckets} + chains) * 4u > size) + throw std::runtime_error("invalid ELF hash geometry"); +} + +void RebuildSysvHash(std::vector& elf, std::size_t offset, const std::vector& symbolNames) { + using namespace Internal; + const auto bucketCount = Read(elf, offset); + std::vector buckets(bucketCount, 0u); + std::vector chains(symbolNames.size(), 0u); + for (std::size_t index = 1u; index < symbolNames.size(); ++index) { + if (symbolNames[index].empty()) continue; + const auto bucket = SysvHash(symbolNames[index]) % bucketCount; + chains[index] = buckets[bucket]; + buckets[bucket] = static_cast(index); + } + for (std::size_t index = 0u; index < buckets.size(); ++index) + Write(elf, offset + 8u + index * 4u, buckets[index]); + for (std::size_t index = 0u; index < chains.size(); ++index) + Write(elf, offset + 8u + buckets.size() * 4u + index * 4u, chains[index]); +} + GnuHashLayout ReadGnuHashLayout(const std::vector& elf, std::size_t sectionOffset) { using namespace Internal; @@ -328,6 +366,8 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& std::size_t dynSymOffset = 0u, dynSymSize = 0u; std::size_t gnuHashOffset = 0u, gnuHashSize = 0u; + std::size_t sysvHashOffset = 0u, sysvHashSize = 0u; + std::uint32_t sysvHashLink = 0u; std::size_t versymOffset = 0u; std::uint32_t dynSymSectionIndex = 0u; std::uint32_t gnuHashSymOffset = 0u; @@ -346,6 +386,11 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& gnuHashOffset = static_cast(shdr.sh_offset); gnuHashSize = static_cast(shdr.sh_size); } + if (shdr.sh_type == kShtHash) { + sysvHashOffset = static_cast(shdr.sh_offset); + sysvHashSize = static_cast(shdr.sh_size); + sysvHashLink = shdr.sh_link; + } if (shdr.sh_type == kShtGnuVersym) versymOffset = static_cast(shdr.sh_offset); if (shdr.sh_type == kShtGnuVerdef) @@ -356,6 +401,10 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& const std::size_t symCount = dynSymSize / sizeof(Elf64_Sym); if (symCount == 0u) throw std::runtime_error(".dynsym is empty"); + if (sysvHashOffset != 0u) { + if (sysvHashLink != dynSymSectionIndex) throw std::runtime_error("invalid ELF hash symbol table link"); + ValidateSysvHash(elf, sysvHashOffset, sysvHashSize, symCount); + } if (gnuHashOffset != 0u) { ValidateGnuHash(elf, gnuHashOffset, gnuHashSize, symCount); gnuHashSymOffset = Read(elf, gnuHashOffset + 4u); @@ -510,6 +559,7 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& FixupRelocationSymbolIndices(elf, ehdr, dynSymSectionIndex, oldToNewIndex); RebuildGnuHash(elf, gnuHashOffset, gnuHashSize, symCount, newSymbolNames); } + if (sysvHashOffset != 0u) RebuildSysvHash(elf, sysvHashOffset, newSymbolNames); } } diff --git a/core/libs/nid/src/PeNidPatcher.cpp b/core/libs/nid/src/PeNidPatcher.cpp index 1c0862d63d42..58fb8ade0acc 100644 --- a/core/libs/nid/src/PeNidPatcher.cpp +++ b/core/libs/nid/src/PeNidPatcher.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace Nid { @@ -116,7 +117,45 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l if (stringsRegionStart == std::numeric_limits::max()) throw std::runtime_error("could not determine strings region start"); - const std::uint32_t rawLimit = ((edataSection.SizeOfRawData + fileAlignment - 1u) / fileAlignment) * fileAlignment; + std::uint32_t rawLimit = ((edataSection.SizeOfRawData + fileAlignment - 1u) / fileAlignment) * fileAlignment; + const auto dllName = ReadCStr(pe, RvaToOffset(pe, exportTable.Name, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); + const bool repackDllName = exportTable.Name >= edataSection.VirtualAddress && + exportTable.Name - edataSection.VirtualAddress < rawLimit; + if (repackDllName) stringsRegionStart = std::min(stringsRegionStart, exportTable.Name - edataSection.VirtualAddress); + struct Forwarder { + std::size_t EntryOffset; + std::string Target; + }; + std::vector forwarders; + std::vector directRvas; + const auto functionsArrayOffset = RvaToOffset(pe, exportTable.AddressOfFunctions, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + for (std::uint32_t index = 0u; index < exportTable.NumberOfFunctions; ++index) { + const auto entryOffset = functionsArrayOffset + index * sizeof(std::uint32_t); + const auto rva = Read(pe, entryOffset); + if (rva < exportDir.VirtualAddress || std::uint64_t{rva} - exportDir.VirtualAddress >= exportDir.Size) { + directRvas.push_back(rva); + continue; + } + const auto target = ReadCStr(pe, RvaToOffset(pe, rva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader)); + if (target.size() + 1u > exportDir.Size - (rva - exportDir.VirtualAddress)) + throw std::runtime_error("forwarder string outside export directory"); + forwarders.push_back({entryOffset, target}); + stringsRegionStart = std::min(stringsRegionStart, rva - edataSection.VirtualAddress); + } + for (const auto rva : directRvas) { + if (rva >= edataSection.VirtualAddress && rva - edataSection.VirtualAddress >= stringsRegionStart) + rawLimit = std::min(rawLimit, rva - edataSection.VirtualAddress); + } + const auto reserveTable = [&](std::uint32_t rva, std::uint64_t size) { + if (rva < edataSection.VirtualAddress || rva - edataSection.VirtualAddress >= rawLimit) return; + const auto end = std::uint64_t{rva - edataSection.VirtualAddress} + size; + if (end > rawLimit) throw std::runtime_error("export table outside edata section"); + stringsRegionStart = std::max(stringsRegionStart, static_cast(end)); + }; + reserveTable(exportDir.VirtualAddress, sizeof(PeExportDirectory)); + reserveTable(exportTable.AddressOfFunctions, std::uint64_t{exportTable.NumberOfFunctions} * 4u); + reserveTable(exportTable.AddressOfNames, std::uint64_t{exportTable.NumberOfNames} * 4u); + reserveTable(exportTable.AddressOfNameOrdinals, std::uint64_t{exportTable.NumberOfNames} * 2u); std::vector finalNames(exportTable.NumberOfNames); for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { @@ -125,28 +164,48 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l finalNames[i] = it->second; } - std::uint32_t requiredSize = stringsRegionStart; - for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) - requiredSize += static_cast(finalNames[i].size()) + 1u; - + std::vector strings; + std::unordered_map stringRvas; + const auto storeString = [&](const std::string& name) { + const auto existing = stringRvas.find(name); + if (existing != stringRvas.end()) return existing->second; + const auto rva = edataSection.VirtualAddress + stringsRegionStart + static_cast(strings.size()); + stringRvas.emplace(name, rva); + strings.insert(strings.end(), name.begin(), name.end()); + strings.push_back(0u); + return rva; + }; + std::vector forwarderRvas; + for (const auto& forwarder : forwarders) forwarderRvas.push_back(storeString(forwarder.Target)); + if (!forwarders.empty() && (std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart < exportDir.VirtualAddress || + std::uint64_t{edataSection.VirtualAddress} + stringsRegionStart + strings.size() > std::uint64_t{exportDir.VirtualAddress} + exportDir.Size)) + throw std::runtime_error("not enough export directory space to repack forwarders"); + const auto dllNameRva = repackDllName ? storeString(dllName) : exportTable.Name; + std::vector finalNameRvas; + for (const auto& name : finalNames) finalNameRvas.push_back(storeString(name)); + const auto requiredSize = std::uint64_t{stringsRegionStart} + strings.size(); if (requiredSize > rawLimit) throw std::runtime_error("not enough raw space in edata section to repack export names"); - - std::uint32_t writeOffset = stringsRegionStart; - for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { - const std::uint32_t newNameRva = edataSection.VirtualAddress + writeOffset; - const std::size_t newNameFileOffset = static_cast(edataSection.PointerToRawData) + writeOffset; - - if (newNameFileOffset + finalNames[i].size() + 1u > pe.size()) - throw std::runtime_error("repack write out of file bounds"); - - std::memcpy(pe.data() + newNameFileOffset, finalNames[i].data(), finalNames[i].size()); - pe[newNameFileOffset + finalNames[i].size()] = 0u; - Write(pe, namesArrayOffset + i * 4u, newNameRva); - writeOffset += static_cast(finalNames[i].size()) + 1u; + const auto directoryEnd = std::max(std::uint64_t{exportDir.VirtualAddress} + exportDir.Size, + std::uint64_t{edataSection.VirtualAddress} + requiredSize); + for (const auto rva : directRvas) { + if (rva != 0u && rva >= exportDir.VirtualAddress && std::uint64_t{rva} < directoryEnd) + throw std::runtime_error("repacked export directory would cover a direct export"); } - - const std::uint32_t newVirtualSize = std::max(edataSection.VirtualSize, writeOffset); + const auto directorySize = directoryEnd - exportDir.VirtualAddress; + if (directorySize > std::numeric_limits::max()) throw std::runtime_error("export directory too large"); + const auto stringFileOffset = static_cast(edataSection.PointerToRawData) + stringsRegionStart; + if (stringFileOffset > pe.size() || strings.size() > pe.size() - stringFileOffset) + throw std::runtime_error("repack write out of file bounds"); + std::memcpy(pe.data() + stringFileOffset, strings.data(), strings.size()); + for (std::size_t index = 0u; index < finalNameRvas.size(); ++index) + Write(pe, namesArrayOffset + index * 4u, finalNameRvas[index]); + for (std::size_t index = 0u; index < forwarders.size(); ++index) + Write(pe, forwarders[index].EntryOffset, forwarderRvas[index]); + Write(pe, exportDirOffset + offsetof(PeExportDirectory, Name), dllNameRva); + Write(pe, dataDirectoryOffset + offsetof(PeDataDirectory, Size), static_cast(directorySize)); + + const std::uint32_t newVirtualSize = std::max(edataSection.VirtualSize, static_cast(requiredSize)); auto patchedSection = edataSection; patchedSection.VirtualSize = newVirtualSize; Write(pe, edataSectionOffset, patchedSection); diff --git a/core/libs/nid/tests/test_gnu_hash.py b/core/libs/nid/tests/test_gnu_hash.py index 3b2d27bbce4b..2e46a70f3210 100644 --- a/core/libs/nid/tests/test_gnu_hash.py +++ b/core/libs/nid/tests/test_gnu_hash.py @@ -3,7 +3,32 @@ import unittest from pathlib import Path -from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash +from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash, sections + + +def sysv_hash(name): + value = 0 + for byte in name.encode("utf-8"): + value = ((value << 4) + byte) & 0xFFFFFFFF + high = value & 0xF0000000 + value = (value ^ (high >> 24)) & ~high + return value + + +def sysv_lookup(image, name): + table = next(section for section in sections(image) if section[1] == 5) + buckets, chains = struct.unpack_from("= chains or index in visited: + raise AssertionError("invalid SysV hash chain") + visited.add(index) + if entries[index][0] == name: + return index + index = struct.unpack_from(" 512: + raise ValueError("fixture exceeds the export section") + image[512:512 + len(data)] = data + return image + + +def forwarding_exports(image, bits): + directory_offset = 112 if bits == 64 else 96 + export_rva, export_size = struct.unpack_from("") diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp index d89415137c83..1ed47f9ff384 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp @@ -46,6 +46,8 @@ class DrawPipeline { }; DrawPipeline() = default; void run(); + void retire(Item&& item); + void reap(); void rethrowFailure(); void report(std::chrono::steady_clock::time_point now); @@ -59,6 +61,11 @@ class DrawPipeline { bool committerWaiting = false; bool stopped = false; std::thread thread; + std::mutex retireMutex; + std::condition_variable reapWake; + std::deque retired; + std::thread reaper; + bool reaperWaiting = false; std::uint64_t commits = 0; std::uint64_t commitNs = 0; std::uint64_t commitErrors = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp index a4a9c516f5ba..9d14bf42dc8b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp @@ -142,14 +142,47 @@ void DrawPipeline::run() { stopped = true; if (!shutdown) Driver::Get().ReportFailure(fatal); } - std::lock_guard lock(mutex); - items.pop_front(); - ++commits; - commitNs += spent; - if (failed) ++commitErrors; - if (fatal != nullptr && failure == nullptr) failure = fatal; - outstanding.fetch_sub(1, std::memory_order_release); - if (idleWaiters != 0) idle.notify_all(); + Item done; + { + std::lock_guard lock(mutex); + done = std::move(items.front()); + items.pop_front(); + ++commits; + commitNs += spent; + if (failed) ++commitErrors; + if (fatal != nullptr && failure == nullptr) failure = fatal; + outstanding.fetch_sub(1, std::memory_order_release); + if (idleWaiters != 0) idle.notify_all(); + } + retire(std::move(done)); + } +} + +void DrawPipeline::retire(Item&& item) { + static const bool reaperEnabled = std::getenv("APS5_NO_COMMIT_REAPER") == nullptr; + if (!reaperEnabled) { + const Item dropped = std::move(item); + return; + } + constexpr std::size_t ReapBatch = 64; + std::lock_guard lock(retireMutex); + retired.push_back(std::move(item)); + if (!reaper.joinable()) reaper = std::thread([this] { reap(); }); + if (retired.size() >= ReapBatch && reaperWaiting) reapWake.notify_one(); +} + +void DrawPipeline::reap() { + for (;;) { + std::deque batch; + { + std::unique_lock lock(retireMutex); + reaperWaiting = true; + reapWake.wait_for(lock, std::chrono::milliseconds(2), [&] { return retired.size() >= 64; }); + reaperWaiting = false; + if (retired.empty()) continue; + batch.swap(retired); + } + batch.clear(); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index f475bfd96e7c..a58de74ce446 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -795,6 +795,7 @@ class Recorder { // Batches popped from inFlight whose completions have not run yet: their writes stay in the // snapshot (a rebuild from inside a completion must not drop them) until finish returns. std::vector finishing; + mutable std::shared_ptr published; std::uint64_t submissions = 0; std::size_t inFlightKeptBytes = 0; std::uint64_t writeNoteCount = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index f2d015fc9738..1f7c2c59c78e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -656,13 +656,17 @@ bool SnapshotOverlaps(std::uint64_t address, std::size_t bytes) { } // Whether one merged range of the snapshot contains [address, end) entirely. -bool SnapshotCovers(std::uint64_t address, std::uint64_t end) { - const auto snapshot = pendingWrites.load(std::memory_order_acquire); +bool SnapshotCovers(const WriteRanges* snapshot, std::uint64_t address, std::uint64_t end) { if (snapshot == nullptr || snapshot->empty()) return false; const auto it = std::partition_point(snapshot->begin(), snapshot->end(), [&](const auto& range) { return range.second <= address; }); return it != snapshot->end() && it->first <= address && end <= it->second; } +bool SnapshotCovers(std::uint64_t address, std::uint64_t end) { + const auto snapshot = pendingWrites.load(std::memory_order_acquire); + return SnapshotCovers(snapshot.get(), address, end); +} + // Attribution of the pending-write syncs the hook makes (the [hooksync] line every 10 s, under // APS5_PROFILE_DRAW; APS5_NO_HOOKSYNC_PROFILE=1 leaves only the plain counts). Every sync is charged // to the packet the accessing thread executes and the read site it named (or, when it named none, @@ -2371,7 +2375,7 @@ void Recorder::readSamples(Batch& batch) { void Recorder::readGpuTiming(Batch& batch) { if (batch.queries == VK_NULL_HANDLE || batch.timedKeys.empty()) return; std::vector stamps(batch.timedKeys.size() * 2); - const auto result = context.Function("vkGetQueryPoolResults")(context.device, batch.queries, 0, static_cast(stamps.size()), stamps.size() * sizeof(std::uint64_t), stamps.data(), sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + const auto result = context.Function("vkGetQueryPoolResults")(context.device, batch.queries, 0, static_cast(stamps.size()), stamps.size() * sizeof(std::uint64_t), stamps.data(), sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT); if (result != VK_SUCCESS) return; const auto period = context.limits.timestampPeriod; if (!GpuTimingEnabled()) { @@ -2598,6 +2602,7 @@ bool Recorder::noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel const auto end = address + bytes; open->writes.emplace_back(address, end); open->writeNotes.push_back(++writeNoteCount); + if (published != nullptr && !SnapshotCovers(published.get(), address, end)) published = nullptr; open->writeValues.push_back(static_cast(value)); if (!ownLabel) markOverwritten(address, end); // A poller waiting on this range learns that the open batch may now hold its producer. @@ -2625,6 +2630,7 @@ void Recorder::noteWriteOn(Batch& batch, std::uint64_t address, std::size_t byte // moves as well, so a poller re-consults the label table for a completion label. batch.writes.emplace_back(address, address + bytes); batch.writeNotes.push_back(++writeNoteCount); + if (published != nullptr && !SnapshotCovers(published.get(), address, address + bytes)) published = nullptr; batch.writeValues.push_back(-1); if (!ownLabel) markOverwritten(address, address + bytes); if (activeRecorder == this) writeGeneration.fetch_add(1, std::memory_order_release); @@ -2713,6 +2719,7 @@ void Recorder::publishPendingWrites(std::spanbegin(), merged->end(), [](const auto& range) { return range.second > range.first; }); publishedWriteRanges = merged; + published = merged; pendingWrites.store(std::move(merged), std::memory_order_release); publishGeneration.fetch_add(1, std::memory_order_release); ++snapshotRebuilds; @@ -2728,6 +2735,7 @@ bool Recorder::overlaps(const Batch& batch, std::uint64_t address, std::uint64_t bool Recorder::PendingWriteOverlaps(std::uint64_t address, std::size_t bytes) const { if (bytes == 0) return false; + if (published != nullptr && !SnapshotOverlaps(published.get(), address, bytes)) return false; const auto end = address + bytes; if (open != nullptr && overlaps(*open, address, end)) return true; for (const auto& batch : inFlight) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp new file mode 100644 index 000000000000..d5f05cabc71b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp @@ -0,0 +1,166 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Size = 8; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t SwizzleX111 = 0x24cu; +constexpr std::uint32_t IdentitySwizzle = 0xfacu; +constexpr std::uint32_t SampleX = 3; +constexpr std::uint32_t SampleY = 5; +constexpr float Tolerance = 1.0f / 64.0f; +constexpr std::array LinearSampler{0x00000092u, 0x00fff000u, 0x00500000u, 0u}; + +alignas(256) std::array Output{}; +alignas(256) std::array Integers{}; +alignas(256) std::array Colors{}; + +alignas(256) constexpr std::array Code{ + 0x34060085u, 0x7e2802ffu, 0x3ef00000u, 0x7e2a02ffu, 0x3f300000u, 0xf09c0f08u, 0x00610a14u, 0xf09c0f08u, + 0x00640e14u, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xe0781010u, 0x80000e03u, 0xbf810000u, 0xbf810000u, +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t PitchBytes(std::uint32_t format, std::size_t storage) { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, format, Size, Size, 1u); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= storage, "image sample VCC sampler: a texture does not fit its storage"); + return mips[0].pitchBytes; +} + +std::int32_t Integer(std::uint32_t x, std::uint32_t y) { + return -static_cast(100u * x + 7u * y + 1u); +} + +void Fill() { + const auto integerPitch = PitchBytes(Format32SInt, Integers.size()); + const auto colorPitch = PitchBytes(Format8888UNorm, Colors.size()); + Integers.fill(0x5au); + Colors.fill(0x5au); + for (std::uint32_t y = 0; y < Size; ++y) { + for (std::uint32_t x = 0; x < Size; ++x) { + const auto bits = static_cast(Integer(x, y)); + auto* integer = &Integers[static_cast(y) * integerPitch + x * 4u]; + for (std::uint32_t byte = 0; byte < 4u; ++byte) integer[byte] = static_cast(bits >> (byte * 8u)); + auto* color = &Colors[static_cast(y) * colorPitch + x * 4u]; + color[0] = x > SampleX ? 255u : 0u; + color[1] = 0u; + color[2] = 0u; + color[3] = 255u; + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::array TextureDescriptor(const void* texels, std::uint32_t format, std::uint32_t swizzle) { + const auto address = static_cast(reinterpret_cast(texels)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + swizzle | (Type2D << 28u), + 0u, + 0u, + 0u, + 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool border) { + Output.fill(0xdeadbeefu); + std::vector userData(24, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto integers = TextureDescriptor(Integers.data(), Format32SInt, SwizzleX111); + const auto colors = TextureDescriptor(Colors.data(), Format8888UNorm, IdentitySwizzle); + std::copy(output.begin(), output.end(), userData.begin()); + std::copy(integers.begin(), integers.end(), userData.begin() + 4); + auto sampler = LinearSampler; + if (border) { sampler[0] = 0x1b6u; sampler[3] = 2u << 30u; } + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + std::copy(colors.begin(), colors.end(), userData.begin() + 16); + alignas(256) std::array words{}; + words[0] = 0xbee8030cu; + words[1] = 0xbee9030du; + words[2] = 0xbeea030eu; + words[3] = 0xbeeb030fu; + std::copy(Code.begin(), Code.end(), words.begin() + 4); + for (const auto index : {10u, 12u}) words[index] = (words[index] & ~(31u << 21u)) | (26u << 21u); + if (border) { words[6] = 0xbf800000u; words[8] = 0xbf800000u; } + const std::span code(words); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool border) { + Fill(); + Run(device, waveSize, border); + const std::array integer{border ? 1u : static_cast(Integer(SampleX, SampleY)), 1u, 1u, 1u}; + const std::array color = border ? std::array{1.0f, 1.0f, 1.0f, 1.0f} : std::array{0.25f, 0.0f, 0.0f, 1.0f}; + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto actual = Output[lane * 8u + component]; + Require(actual == integer[component], "32_SINT image_sample_lz through a bilinear sampler shared with 8_8_8_8_UNORM: lane " + std::to_string(lane) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + Hex(integer[component])); + } + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto bits = Output[lane * 8u + 4u + component]; + const auto actual = std::bit_cast(bits); + Require(std::fabs(actual - color[component]) <= Tolerance, "8_8_8_8_UNORM image_sample_lz through a bilinear sampler shared with 32_SINT: lane " + std::to_string(lane) + " component " + std::to_string(component) + " is " + Hex(bits) + ", expected " + std::to_string(color[component])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const auto waveSize : {32u, 64u}) { + Check(*device, waveSize, false); + Check(*device, waveSize, true); + } + std::puts("image sample VCC sampler tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceCdlgG2P/Export.cpp b/core/libs/prx/libSceCdlgG2P/Export.cpp index d772b2900352..082b4bb3317d 100644 --- a/core/libs/prx/libSceCdlgG2P/Export.cpp +++ b/core/libs/prx/libSceCdlgG2P/Export.cpp @@ -59,6 +59,7 @@ int APS5_VABI sceG2PDialogGetResult(void* result) { int APS5_VABI sceG2PDialogClose(void) { if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + g_status = COMMON_DIALOG_STATUS_FINISHED; return 0; } diff --git a/core/libs/prx/libScePrecompiledShaders/CMakeLists.txt b/core/libs/prx/libScePrecompiledShaders/CMakeLists.txt new file mode 100644 index 000000000000..20a83645ed6c --- /dev/null +++ b/core/libs/prx/libScePrecompiledShaders/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libScePrecompiledShaders SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePrecompiledShaders PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePrecompiledShaders) + +if(BUILD_TESTING) + add_test_executable(guest_precompiled_shaders_tests ${LIBS_INCLUDE_DIR}/tests/GuestPrecompiledShaders.cpp) + target_include_directories(guest_precompiled_shaders_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_precompiled_shaders_tests PRIVATE libScePrecompiledShaders glslang SPIRV glslang-default-resource-limits libc) + configure_windows_unwind(guest_precompiled_shaders_tests) + add_test(NAME guest_precompiled_shaders COMMAND guest_precompiled_shaders_tests) + if(WIN32) + set_tests_properties(guest_precompiled_shaders PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePrecompiledShaders/Export.cpp b/core/libs/prx/libScePrecompiledShaders/Export.cpp new file mode 100644 index 000000000000..c2e1ae813495 --- /dev/null +++ b/core/libs/prx/libScePrecompiledShaders/Export.cpp @@ -0,0 +1,39 @@ +#include "prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp" + +namespace { + +constexpr char TexmapVertex[] = R"(APS5GLSL +#version 310 es +in vec4 a_vertex; +uniform mat4 u_projectionMatrix; +uniform mat4 u_modelViewMatrix; +uniform mat4 u_textureSpaceMatrix; +out vec2 v_texcoord; +void main() +{ + gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_vertex.xy, 0.0, 1.0); + v_texcoord = (u_textureSpaceMatrix * vec4(a_vertex.zw, 0.0, 1.0)).xy; +} +)"; + +constexpr char TexmapFragment[] = R"(APS5GLSL +#version 310 es +precision mediump float; +uniform sampler2D s_sampler; +uniform float u_opacity; +in vec2 v_texcoord; +out vec4 fragColor; +void main() +{ + vec4 color = texture(s_sampler, v_texcoord); + fragColor = vec4(color.rgb, color.a * u_opacity); +} +)"; + +} + +extern "C" const PrecompiledShaders::Entry scePrecompiledShaderEntries_nid_postfix[] = { + {"texmap/v_2.vert", 0, 0, sizeof(TexmapVertex) - 1, TexmapVertex}, + {"texmap/f_2.frag", 0, 0, sizeof(TexmapFragment) - 1, TexmapFragment}, + {nullptr, 0, 0, 0, nullptr}, +}; diff --git a/core/libs/prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp b/core/libs/prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp new file mode 100644 index 000000000000..f4745a0642f8 --- /dev/null +++ b/core/libs/prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp @@ -0,0 +1,24 @@ +#ifndef CORE_LIBS_PRX_LIBSCEPRECOMPILEDSHADERS_INCLUDE_PRECOMPILEDSHADERS_HPP +#define CORE_LIBS_PRX_LIBSCEPRECOMPILEDSHADERS_INCLUDE_PRECOMPILEDSHADERS_HPP + +#include +#include +#include + +namespace PrecompiledShaders { + +struct Entry { + const char* name; + std::uint64_t reserved0; + std::uint64_t reserved1; + std::uint64_t size; + const void* data; +}; + +static_assert(sizeof(Entry) == 0x28 && offsetof(Entry, size) == 0x18 && offsetof(Entry, data) == 0x20); + +inline constexpr std::string_view SourceMagic = "APS5GLSL\n"; + +} + +#endif diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 3fdccdba5eb0..5e39b9e92d4c 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -36,6 +36,7 @@ static std::string save_root() { static std::mutex g_state_mutex; static std::deque g_events; constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2; +constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_CONVERT = 5; static bool dir_name_match(const char* str, const char* pattern) { if (pattern == nullptr || pattern[0] == '\0') { @@ -951,6 +952,13 @@ static int convert(const SaveDataConvert* param) { return SAVE_DATA_ERROR_NOT_FOUND; } g_convertProgress = 1.0f; + SaveDataEvent event{}; + event.type = SAVE_DATA_EVENT_TYPE_CONVERT; + event.error_code = SAVE_DATA_OK; + event.user_id = param->user_id; + event.dir_name = *param->dir_name; + std::lock_guard lock(g_state_mutex); + g_events.push_back(event); return SAVE_DATA_OK; } diff --git a/core/libs/prx/libSceVoiceChat/Export.cpp b/core/libs/prx/libSceVoiceChat/Export.cpp index 03569b489083..aa3728e42e56 100644 --- a/core/libs/prx/libSceVoiceChat/Export.cpp +++ b/core/libs/prx/libSceVoiceChat/Export.cpp @@ -65,4 +65,210 @@ VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestCreateVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestJoinVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestLeaveVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestDeleteVoiceChatGroup) + +int APS5_VABI sceVoiceChatAbortRequest(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetActiveChannel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetMicState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequest0(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequest1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequestAcsTts(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequestAction(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequestSetMemberVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartAccessibilityEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartChannelEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartDaemonEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartMemberEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartRangedMemberEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStartVoiceEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopAccessibilityEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopChannelEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopDaemonEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopMemberEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopRangedMemberEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatStopVoiceEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/tests/GuestG2PDialog.cpp b/core/libs/tests/GuestG2PDialog.cpp index e896a87e42f1..b3e0cb2b8553 100644 --- a/core/libs/tests/GuestG2PDialog.cpp +++ b/core/libs/tests/GuestG2PDialog.cpp @@ -59,4 +59,13 @@ int main() { Require(sceG2PDialogTerminate() == 0); Require(sceG2PDialogTerminate() == kErrNotInitialized); Require(sceG2PDialogGetStatus() == kStatusNone); + + Require(sceG2PDialogInitialize() == 0); + Require(sceG2PDialogGetStatus() == kStatusInitialized); + Require(sceG2PDialogClose() == 0); + Require(sceG2PDialogGetStatus() == kStatusFinished); + result[0] = -1; + Require(sceG2PDialogGetResult(result) == 0); + Require(result[0] == kResultUserCanceled); + Require(sceG2PDialogTerminate() == 0); } diff --git a/core/libs/tests/GuestGenericCategory.cpp b/core/libs/tests/GuestGenericCategory.cpp index d0d32df25563..52188fc0cabb 100644 --- a/core/libs/tests/GuestGenericCategory.cpp +++ b/core/libs/tests/GuestGenericCategory.cpp @@ -8,7 +8,7 @@ #include #include -struct Category { const void* const* vtable; }; +struct Category { void* const* vtable; }; struct ErrorCode { int value; const Category* category; }; using Message = GuestLocale::String* (APS5_VABI *)(GuestLocale::String*, const Category*, int); using Name = const char* (APS5_VABI *)(const Category*); diff --git a/core/libs/tests/GuestPrecompiledShaders.cpp b/core/libs/tests/GuestPrecompiledShaders.cpp new file mode 100644 index 000000000000..2c836520b69c --- /dev/null +++ b/core/libs/tests/GuestPrecompiledShaders.cpp @@ -0,0 +1,89 @@ +#include "prx/libScePrecompiledShaders/include/PrecompiledShaders.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" const PrecompiledShaders::Entry scePrecompiledShaderEntries_nid_postfix[]; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Precompiled shader check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static const PrecompiledShaders::Entry* Find(const char* name) { + for (const auto* entry = scePrecompiledShaderEntries_nid_postfix; entry->name; ++entry) + if (std::strcmp(entry->name, name) == 0) return entry; + return nullptr; +} + +static std::string Source(const PrecompiledShaders::Entry* entry) { + const std::string_view bytes(static_cast(entry->data), entry->size); + Require(bytes.starts_with(PrecompiledShaders::SourceMagic)); + Require(std::strlen(static_cast(entry->data)) == entry->size); + return std::string(bytes.substr(PrecompiledShaders::SourceMagic.size())); +} + +static std::unique_ptr Parse(EShLanguage stage, const std::string& source) { + auto shader = std::make_unique(stage); + const char* text = source.c_str(); + shader->setStrings(&text, 1); + shader->setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, 100); + shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); + shader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); + shader->setEnvInputVulkanRulesRelaxed(); + shader->setAutoMapBindings(true); + shader->setAutoMapLocations(true); + const bool parsed = shader->parse(GetDefaultResources(), 310, true, static_cast(EShMsgSpvRules | EShMsgVulkanRules)); + if (!parsed) std::fprintf(stderr, "%s\n", shader->getInfoLog()); + Require(parsed); + return shader; +} + +int main() { + int count = 0; + for (const auto* entry = scePrecompiledShaderEntries_nid_postfix; entry->name; ++entry) { + Require(entry->reserved0 == 0 && entry->reserved1 == 0 && entry->data != nullptr); + ++count; + } + Require(count == 2); + const auto* vertex = Find("texmap/v_2.vert"); + const auto* fragment = Find("texmap/f_2.frag"); + Require(vertex && fragment && !Find("texmap/v_2.frag")); + + Require(glslang::InitializeProcess()); + { + const auto vertexShader = Parse(EShLangVertex, Source(vertex)); + const auto fragmentShader = Parse(EShLangFragment, Source(fragment)); + glslang::TProgram program; + program.addShader(vertexShader.get()); + program.addShader(fragmentShader.get()); + const bool linked = program.link(static_cast(EShMsgSpvRules | EShMsgVulkanRules)); + if (!linked) std::fprintf(stderr, "%s\n", program.getInfoLog()); + Require(linked && program.buildReflection()); + + std::map uniforms; + for (int index = 0; index < program.getNumUniformVariables(); ++index) + uniforms[program.getUniformName(index)] = program.getUniformType(index); + Require(uniforms.size() == 5); + Require(uniforms["u_projectionMatrix"] == 0x8B5C && uniforms["u_modelViewMatrix"] == 0x8B5C && uniforms["u_textureSpaceMatrix"] == 0x8B5C); + Require(uniforms["u_opacity"] == 0x1406 && uniforms["s_sampler"] == 0x8B5E); + Require(program.getNumPipeInputs() == 1 && std::strcmp(program.getPipeInput(0).name.c_str(), "a_vertex") == 0 && program.getPipeInput(0).glDefineType == 0x8B52); + std::vector spirv; + glslang::GlslangToSpv(*program.getIntermediate(EShLangFragment), spirv); + Require(!spirv.empty()); + } + glslang::FinalizeProcess(); +} diff --git a/core/libs/tests/GuestSaveDataConvert.cpp b/core/libs/tests/GuestSaveDataConvert.cpp index 580d22e8920c..df8f6743cec9 100644 --- a/core/libs/tests/GuestSaveDataConvert.cpp +++ b/core/libs/tests/GuestSaveDataConvert.cpp @@ -15,6 +15,7 @@ int APS5_VABI sceSaveDataConvert(const SaveDataConvert*); int APS5_VABI sceSaveDataGetConvertProgress(float*); int APS5_VABI sceSaveDataInitialize3(const void*); int APS5_VABI sceSaveDataTerminate(); +int APS5_VABI sceSaveDataGetEventResult(const void*, SaveDataEvent*); } namespace { @@ -67,6 +68,11 @@ int main() { Check(progress < 1.0f, "convert progress is below 1.0f after failed convert"); Check(progress == 0.0f, "convert progress reports 0.0f after failed convert"); Check(Convert("kept", sizeof("kept")) == 0, "an existing save converts"); + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0, "successful conversion queues completion"); + Check(event.type == 5 && event.error_code == 0, "conversion event reports successful conversion"); + Check(event.user_id == 0x10000000 && std::strcmp(event.dir_name.data, "kept") == 0, "conversion event identifies the user and save"); + Check(sceSaveDataGetEventResult(nullptr, &event) == SaveDataErrorNotFound, "conversion event is delivered once"); Check(Read(kept) == std::vector{'d', 'a', 't', 'a'}, "the converted save keeps its files"); Check(sceSaveDataGetConvertProgress(&progress) == 0, "get convert progress succeeds after successful convert"); Check(progress == 1.0f, "convert progress reports 1.0f after successful convert"); diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 6c46cd2a2826..c1c1d4380bec 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -465,6 +465,9 @@ if(BUILD_TESTING) set_tests_properties(needed_libraries PROPERTIES TIMEOUT 30) add_test(NAME macos_output COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_output.py $) + add_test(NAME macos_tls_overlap COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_tls_overlap.py $) + set_tests_properties(macos_tls_overlap PROPERTIES TIMEOUT 60) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 838d0d6c8bb6..196084a66eef 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -218,16 +218,19 @@ TlsScan ScanTls(const std::vector& source, const std::vector(rva) + static_cast(info.Length) + info.BranchDisp; if (target >= 0 && target <= std::numeric_limits::max()) scan.BranchTargets.insert(static_cast(target)); } - if (info.SegmentPrefix == 0) continue; + if (info.SegmentPrefix == 0 || (insidePrevious && info.SegmentPrefix != 0x64)) continue; const auto position = info.OpcodeOffset; bool hasOperandSizePrefix = false; bool supportedPrefixes = info.SegmentPrefix == 0x64; diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index 5389f7773ac2..8c5e47547388 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -276,7 +276,7 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector segment.FileSize) throw RelinkerException("Strict filter: EH frame table exceeds segment", position); - result.push_back(readFde(begin, fde)); + auto region = readFde(begin, fde); + if (region.End != region.Begin) result.push_back(std::move(region)); } } return result; @@ -211,7 +212,7 @@ class Reader { const auto cie = readCie(cieField - cieOffset); const auto begin = encoded(position, cie.PointerEncoding); const auto size = encoded(position, cie.PointerEncoding & 0x0F); - if (begin != expectedBegin || size == 0 || size > std::numeric_limits::max() - begin) + if (begin != expectedBegin || size > std::numeric_limits::max() - begin) throw RelinkerException("Strict filter: inconsistent FDE function range", address); StrictCodeRegion region{begin, begin + size, {}}; if (cie.Personality != 0) region.ExtraTargets.push_back(cie.Personality); diff --git a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp index a0b85295737c..31f110f1a882 100644 --- a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp +++ b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp @@ -34,6 +34,9 @@ int main() { Domain::ProgramHeader funcSeg{}; funcSeg.Type = 1; funcSeg.MappedAddress = 0x800; funcSeg.FileSize = 0x20; const auto below = Relinker::UnusedNidFilter::ReadExceptionFunctions(belowBytes, {funcSeg, load, eh}, {}, {}); Require(below.size() == 1 && below[0].Begin == 0x800 && below[0].End == 0x810); + auto empty = Fixture(); + Io::WriteU64(empty, 112, 0); + Require(Relinker::UnusedNidFilter::ReadExceptionFunctions(empty, {load, eh}, {}, {}).empty()); for (int kind = 0; kind < 10; ++kind) { auto bytes = Fixture(); auto segment = load; switch (kind) { @@ -64,6 +67,6 @@ int main() { catch (const std::exception&) { rejected = true; } Require(rejected); } - std::cout << "13 unwind record cases passed\n"; + std::cout << "14 unwind record cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/relinker/relinker/tests/test_init_array_roots.py b/core/relinker/relinker/tests/test_init_array_roots.py index 5f847a8eb70d..a886cc17ed54 100644 --- a/core/relinker/relinker/tests/test_init_array_roots.py +++ b/core/relinker/relinker/tests/test_init_array_roots.py @@ -40,7 +40,7 @@ def image(array_tag=None, array_size_tag=None, array_va=0, slots=(), relative=() elf[0x200] = 0xC3 elf[FUNCTION:FUNCTION + 8] = b"\x48\x8b\x05" + struct.pack(".blocks`; a save without a kept size takes that of its next mount that names one, and otherwise reports `SAVE_DATA_BLOCKS_MAX`. The search infos (blocks, free blocks and 32 reserved bytes per returned name, following shadPS4's `OrbisSaveDataSearchInfo`), `sceSaveDataGetMountInfo` and the two block keys use that size and, as free, what the save's files leave of it, each file rounded up to 32 KiB blocks; the console's file system overhead is not known and not counted. Keys other than 0, 1, 2, 3 and 5 (including 4) and orders above 1 return `SCE_SAVE_DATA_ERROR_PARAMETER`; shadPS4 and KytyPS5 only reject keys above 5, so key 4 is rejected here because no source names it. In the directory name pattern `%` matches any run of characters, none included, and `_` exactly one, as the comments of shadPS4's matcher describe; also not confirmed on hardware diff --git a/docs/dev/debt/libSceVoiceChat.md b/docs/dev/debt/libSceVoiceChat.md index 17e7eb1875ed..f112d0dff5c6 100644 --- a/docs/dev/debt/libSceVoiceChat.md +++ b/docs/dev/debt/libSceVoiceChat.md @@ -2,4 +2,5 @@ ### Unknown function info +- [libSceVoiceChat](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - voice chat channel management, audio event, and gain control interfaces; stubs throw until voice chat daemon subsystem is implemented - [sceVoiceChatRequestCreateVoiceChatGroup, sceVoiceChatRequestJoinVoiceChatGroup, sceVoiceChatRequestLeaveVoiceChatGroup, sceVoiceChatRequestDeleteVoiceChatGroup](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - assumed to take a request id and two pointers like the other `sceVoiceChatRequest*` calls; offline, every argument is ignored and `SIGNED_OUT` is returned, as for those diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index 47813f3bdebe..5ad27b1c3fa7 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -10,6 +10,7 @@ - [MSAA-address image loads on 2D textures](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) - 32-bit `image_load` with DIM 2D_MSAA reads a non-multisampled 2D T# as the same DIM 2D load, ignoring the sample index. Measured on RX 6650 XT (gfx1032), wave32 and wave64, for R32_UINT, R32_FLOAT, RGBA8_UNORM and RGBA8_SRGB, sample indices 0/1/2/7/31/0xffffffff and in-range/out-of-range coordinates; IEEE 0, DX10 clamp 1, denorm32 0, denorm16 3, round32/16 0, fp16 overflow 0. Pinned in ImageMsaaAddressOnPlane.cpp. Not measured on gfx1013. Stores, atomics, mip-bearing or A16 addresses, packed accesses, converted formats, depth-bit views, array-MSAA addresses and image-table accesses are not enabled by this compatibility mode. +- [VCC-backed MIMG samplers](../../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp): the s104/s105/vcc_lo/vcc_hi tuple follows LLVM's [SGPR register classes](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L452) and [gfx10 sampler operand](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/MIMGInstructions.td#L1379), using the existing VCC word reads. Recompiled wave32/wave64 reads are checked by [bilinear and white-border cases](../../../core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleVccSampler.cpp); raw RDNA execution of this tuple on gfx1013 or gfx1036 is unmeasured. - [MUBUF/MTBUF descriptors ending in VCC](../../../core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp): the four dword descriptor at s104 is s104, s105, vcc_lo and vcc_hi, as LLVM's [VCCTuple/SReg_128](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L422) and [buffer resource operands](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/BUFInstructions.td) describe. Existing translation maps scalar codes106/107 to the raw VCC words. Recompiled loads/stores, both words, bounds, formats and wave32/wave64 execute on NVIDIA and AMD Vulkan; raw gfx10/gfx1013 hardware is not measured. Higher descriptor bases remain refused; the normal SGPR bound is unchanged. - [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin, an equality inferred, not measured. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places - [Front-face input with `FRONT_FACE_ALL_BITS`](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (shader recompiler) - with `SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS` set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit ([radv_pipeline.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/vulkan/radv_pipeline.c#L5953)) and ACO tests the input with `v_cmp_lg_u32 0` ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/compiler/aco_instruction_selection.cpp#L8123-L8128)), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with `v_cmp_lt_u32 0`. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (`fgt 0`, `fsign`; [ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c))