diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 92b900e7d..4b0c934dd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -536,6 +536,8 @@ struct ImageMirrors { std::uint64_t builds = 0; std::uint64_t heapRefills = 0; std::uint64_t heapUnwatched = 0; + std::uint64_t heapImported = 0; + std::uint64_t heapImportedBytes = 0; std::uint64_t subranges = 0; std::uint64_t rebuilds = 0; std::uint64_t refreshes = 0; @@ -905,6 +907,23 @@ void sweepMirrors() { } } +void releaseImportedMirrors(const Context& context, const std::vector& imported) { + if (imported.empty()) return; + std::vector> released; + auto& state = Mirrors(); + std::lock_guard lock(state.mutex); + if (state.device != context.device || state.entries.empty()) return; + for (const auto base : imported) { + const auto found = state.entries.find(base); + if (found == state.entries.end() || !found->second->heap) continue; + state.heapBytes -= found->second->bytes; + ++state.heapImported; + state.heapImportedBytes += found->second->bytes; + released.push_back(std::move(found->second)); + state.entries.erase(found); + } +} + void reportMirrors() { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; @@ -927,7 +946,7 @@ void reportMirrors() { } heapBytes = state.heapBytes; } - AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast(state.builds), static_cast(state.subranges), static_cast(state.rebuilds), static_cast(state.refreshes), static_cast(state.blocksCompared), static_cast(state.blocksCopied), static_cast(state.syncs), static_cast(state.heapRefills), static_cast(state.heapUnwatched)); + AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu, %llu heap mirrors released for imports (%.1f MiB)\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast(state.builds), static_cast(state.subranges), static_cast(state.rebuilds), static_cast(state.refreshes), static_cast(state.blocksCompared), static_cast(state.blocksCopied), static_cast(state.syncs), static_cast(state.heapRefills), static_cast(state.heapUnwatched), static_cast(state.heapImported), state.heapImportedBytes / 1048576.0); } // Deferred lease release. The lease an address-based build takes (AcquireRegistered) is dropped by its @@ -1450,6 +1469,7 @@ void GuestBufferMemory::AcquireRegistered() { // must not happen under it. regions.reserve(lease.size()); std::vector copies; + std::vector imported; for (const auto& range : lease) { if (!range->readable) continue; validate(range->address, range->bytes); @@ -1466,6 +1486,7 @@ void GuestBufferMemory::AcquireRegistered() { // Reused by Upload while no import was dropped since (see importsEpoch). region.direct = entry; regions.push_back(std::move(region)); + imported.push_back(range->address); continue; } if (mirrorsEnabled()) { @@ -1488,6 +1509,7 @@ void GuestBufferMemory::AcquireRegistered() { const char* reason = !mirrorsEnabled() ? "mirrors disabled by APS5_NO_LEASE_MIRROR" : copied.sparse ? "unreadable pages" : !range->releasable ? "image mirror refused" : "outside the write-watched arena"; copies.push_back({copied.begin, copied.end, committed, reason}); } + releaseImportedMirrors(context, imported); regionsSorted = sortedLookup; static const std::uint64_t copyLimit = [] { const char* value = std::getenv("APS5_ADDRESS_COPY_MAX_MIB"); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index f803903fc..2126099fc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -36,6 +36,61 @@ ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) { return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false}; } +void importedHeapMirrorTests(const Context& context, const BdaTestAccess& access) { + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: heap mirrors of imported ranges not tested\n"; + return; + } + constexpr std::size_t bytes = 1u << 20u; + void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, bytes); +#ifdef _WIN32 + GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes); +#endif + std::memset(block, 0x11, bytes); + const auto address = reinterpret_cast(block); + auto importing = context; + importing.hostImportAlignment = bytes; + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, bytes, true, false); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + const auto build = [&](const Context& with) { + GuestAllocations::GuestAllocationsEnd_nid_postfix(GuestAllocations::GuestAllocationsBegin_nid_postfix()); + GuestBufferMemory leased(with); + leased.AcquireRegistered(); + leased.Upload(true); + const auto ranges = leased.AddressRanges(); + const auto found = std::find_if(ranges.begin(), ranges.end(), [&](const auto& range) { return range.begin == address && range.end == address + bytes; }); + const auto device = found != ranges.end() ? found->deviceAddress : 0; + leased.WriteBack(); + return device; + }; + const auto before = MirrorCounters(); + registry(true); + Require(build(context) != 0, "the heap range is missing from the BDA table of a build without host imports"); + const auto mirrored = MirrorCounters(); + Require(mirrored.heapMirrors == before.heapMirrors + 1 && mirrored.heapBytes == before.heapBytes + bytes, "a range built without host imports was not heap mirrored"); + Require(build(importing) != 0 && HostImportCovers(importing, address, bytes), "the heap mirrored range was not imported once imports were available"); + const auto imported = MirrorCounters(); + Require(imported.heapMirrors == before.heapMirrors && imported.heapBytes == before.heapBytes, "a heap mirror outlived the host import that serves its range"); + Require(build(importing) != 0 && MirrorCounters().heapMirrors == before.heapMirrors && MirrorCounters().rebuilds == imported.rebuilds, "an imported range was heap mirrored again"); + static_cast(block)[bytes / 2] = 0x22; + const auto device = build(context); + const auto again = MirrorCounters(); + Require(device != 0 && again.heapMirrors == before.heapMirrors + 1 && again.heapBytes == before.heapBytes + bytes && again.rebuilds == imported.rebuilds + 1, "a range no import serves was not heap mirrored again"); + Require(access.addressBytes(device)[bytes / 2] == std::byte{0x22} && access.addressBytes(device)[bytes / 2 + 1] == std::byte{0x11}, "a heap mirror made after its import missed the guest bytes"); + registry(false); + Require(HostImportFor(importing, address, bytes) == nullptr && !HostImportCovers(importing, address, bytes), "the import outlived its range"); + Require(build(context) == 0, "the unregistered heap range is still in the BDA table"); + Require(MirrorCounters().heapMirrors == before.heapMirrors && MirrorCounters().heapBytes == before.heapBytes, "the heap mirror outlived its range"); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, bytes); +#endif + GuestArena::GuestArenaRelease_nid_postfix(block, bytes); +} + void heapMirrorTests(const Context& context, const BdaTestAccess& access) { if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { std::cout << "guest arena unavailable or not write-watched: heap mirrors not tested\n"; @@ -386,6 +441,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { if (dropped.enabled) Require(dropped.waiterDrops == after.waiterDrops + 1 && LeaseCounters().cacheDrops == dropped.waiterDrops, "the free did not drop the cached address space"); } heapMirrorTests(context, access); + importedHeapMirrorTests(context, access); Require(AddressCopyOverflow({{0x1000, 0x3000, 0x2000, "uncommitted pages"}}, 0x2000).empty(), "copies within the limit were refused"); const auto copies = AddressCopyOverflow({{0x1000, 0x2000, 0x1000, "not mirrored"}, {0x10000, 0x30000, 0x18000, "uncommitted pages"}}, 0x2000); Require(!copies.empty() && copies.find("0x10000+0x20000 (0.1 MiB committed, uncommitted pages)") < copies.find("0x1000+0x1000"), "the copy limit does not name the largest copy first"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index a035d3bf6..a22b85ecd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1300,6 +1300,12 @@ VKAPI_ATTR void VKAPI_CALL mockCmdBindDescriptorSets(VkCommandBuffer, VkPipeline mock.boundSets = count; } +VKAPI_ATTR VkResult VKAPI_CALL mockGetMemoryHostPointerProperties(VkDevice, VkExternalMemoryHandleTypeFlagBits type, const void* pointer, VkMemoryHostPointerPropertiesEXT* properties) { + Require(type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT && pointer != nullptr, "invalid host pointer import query"); + properties->memoryTypeBits = 1; + return VK_SUCCESS; +} + VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const VkBufferDeviceAddressInfo* info) { Require(mock.bufferMemory.contains(info->buffer), "BDA buffer was not bound"); Require((mock.bufferUsage.at(info->buffer) & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0, "BDA buffer usage is missing"); @@ -1369,6 +1375,7 @@ VKAPI_ATTR void VKAPI_CALL mockCmdUpdateBuffer(VkCommandBuffer, VkBuffer buffer, PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { static const std::map table{ {"vkGetBufferDeviceAddressKHR", reinterpret_cast(mockGetBufferDeviceAddress)}, + {"vkGetMemoryHostPointerPropertiesEXT", reinterpret_cast(mockGetMemoryHostPointerProperties)}, {"vkCreateBuffer", reinterpret_cast(mockCreateBuffer)}, {"vkGetBufferMemoryRequirements", reinterpret_cast(mockGetBufferMemoryRequirements)}, {"vkAllocateMemory", reinterpret_cast(mockAllocateMemory)},