diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index f537ad995c..bd49d5c46a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -3451,6 +3451,15 @@ std::optional GuestBufferMemory::CachedAddressTa Require(uploaded && !committed, "guest GPU address ranges are not available"); if (writes.empty()) return CachedTable{space->serial, &space->ranges}; if (writeTableRanges != nullptr) return CachedTable{writeTableSerial, writeTableRanges.get()}; + const auto& spaceRanges = space->ranges; + const bool inWritable = std::all_of(writes.begin(), writes.end(), [&](const auto& written) { + auto at = std::partition_point(spaceRanges.begin(), spaceRanges.end(), [&](const auto& range) { return range.end <= written.first; }); + for (; at != spaceRanges.end() && at->begin < written.second; ++at) { + if ((at->permissions & ShaderRecompiler::BdaAbi::Write) == 0u) return false; + } + return true; + }); + if (inWritable) return CachedTable{space->serial, &space->ranges}; std::lock_guard lock(space->tableMutex); const auto found = space->writeTables.find(writes); if (found != space->writeTables.end()) { diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 5a1f81054e..a2fd8b445b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -119,6 +119,59 @@ void importSpanTests(const Context& context) { ::operator delete(block, std::align_val_t{piece}); } +void spaceWritableWritesTests(const Context& context) { + if (!AddressSpaceCounters().enabled) return; + constexpr std::size_t piece = 65536; + auto* writable = static_cast(::operator new(piece, std::align_val_t{piece})); + auto* readOnly = static_cast(::operator new(piece, std::align_val_t{piece})); + std::memset(writable, 0x11, piece); + std::memset(readOnly, 0x22, piece); + auto importing = context; + importing.hostImportAlignment = piece; + const auto change = [&](std::uint8_t* range, bool add, bool canWrite) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, range, piece, true, canWrite, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, range); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + change(writable, true, true); + change(readOnly, true, false); + const auto same = [](const std::vector& left, const std::vector& right) { + return left.size() == right.size() && (left.empty() || std::memcmp(left.data(), right.data(), left.size() * sizeof(left.front())) == 0); + }; + struct Built { + std::uint64_t serial; + std::vector ranges; + }; + const auto build = [&](std::initializer_list> written, const char* step) { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + for (const auto& [offset, bytes] : written) leased.AddWritable(reinterpret_cast(writable) + offset, bytes); + leased.Upload(true); + const auto cached = leased.CachedAddressTable(); + Require(cached.has_value() && cached->ranges != nullptr, std::string("a build of the space alone has no cached table: ") + step); + Built result{cached->serial, leased.AddressRanges()}; + Require(same(*cached->ranges, result.ranges), std::string("the cached table differs from AddressRanges(): ") + step); + leased.WriteBack(); + return result; + }; + const auto plain = build({}, "no writes"); + const auto once = build({{256, 512}}, "one write in a writable import"); + const auto twice = build({{4096, 64}, {8192, 128}}, "two writes in a writable import"); + Require(once.serial == plain.serial && twice.serial == plain.serial, "writes in a writable import did not bind the space's table"); + Require(same(once.ranges, plain.ranges) && same(twice.ranges, plain.ranges), "writes in a writable import changed the BDA table"); + const auto writableBegin = reinterpret_cast(writable); + const auto readOnlyBegin = reinterpret_cast(readOnly); + for (const auto& range : once.ranges) { + if (range.begin == writableBegin) Require((range.permissions & ShaderRecompiler::BdaAbi::Write) != 0u, "the writable import lost its write permission"); + if (range.begin == readOnlyBegin) Require((range.permissions & ShaderRecompiler::BdaAbi::Write) == 0u, "the read-only import gained write permission"); + } + change(writable, false, true); + change(readOnly, false, false); + ::operator delete(writable, std::align_val_t{piece}); + ::operator delete(readOnly, std::align_val_t{piece}); +} + void failedImportRemapTests(const Context& context, const BdaTestAccess& access) { constexpr std::size_t piece = 65536; auto* block = static_cast(::operator new(4 * piece, std::align_val_t{piece})); @@ -1239,6 +1292,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importSpanTests(context); failedImportRemapTests(context, access); spaceImportRunTests(context); + spaceWritableWritesTests(context); importWindowTests(context); heapMirrorTests(context, access); importedHeapMirrorTests(context, access);