diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index f262862165..38886bd9c8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -297,6 +297,10 @@ struct HostImports { std::uint8_t reportNoted = 0; std::map spaceUses; std::uint64_t budgetRefusals = 0; + std::array recentImports{}; + std::uint64_t recentEpoch = std::numeric_limits::max(); + std::size_t recentSize = 0; + std::size_t recentNext = 0; }; HostImports& Imports() { @@ -304,6 +308,31 @@ HostImports& Imports() { return imports; } +bool recentImportsEnabled() { + static const bool enabled = std::getenv("APS5_NO_RECENT_IMPORTS") == nullptr; + return enabled; +} + +const HostImport* recentImport(HostImports& state, std::uint64_t begin, std::uint64_t end) { + if (state.recentEpoch != state.epoch || state.recentSize != state.imports.size()) return nullptr; + for (const auto* entry : state.recentImports) { + if (entry != nullptr && begin >= entry->base && end <= entry->base + entry->bytes) { + entry->lastUse = ++state.useCounter; + return entry; + } + } + return nullptr; +} + +void rememberImport(HostImports& state, const HostImport* entry) { + if (state.recentEpoch != state.epoch || state.recentSize != state.imports.size()) { + state.recentImports = {}; + state.recentEpoch = state.epoch; + state.recentSize = state.imports.size(); + } + state.recentImports[state.recentNext++ % state.recentImports.size()] = entry; +} + #ifdef _WIN32 void waitSettleBatch(const ImportSettle& settle, VkDevice device, const SettleBatch& batch) { if (batch.busy && settle.waitForFences != nullptr) Check(settle.waitForFences(device, 1, &batch.fence, VK_TRUE, 60'000'000'000ull), "vkWaitForFences (settle batch)"); @@ -4023,8 +4052,15 @@ const HostImport* HostImportFor(const Context& context, std::uint64_t address, s #endif // A hit is only valid while the registry has not changed since the imports were reconciled. if (!importsStale(context, state)) { + if (recentImportsEnabled()) { + if (const auto* entry = recentImport(state, address, address + bytes)) { + entry->lastUse = ++state.useCounter; + return entry; + } + } if (const auto* entry = findImport(state, address, address + bytes)) { entry->lastUse = ++state.useCounter; + if (recentImportsEnabled()) rememberImport(state, entry); return entry; } } diff --git a/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp b/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp index 8d0d9cddc7..384299be2c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostImportRetireHold.cpp @@ -360,6 +360,23 @@ void BusyRetireOutlivesALaterBatch() { Expect(!Live(retired), "the dropped import outlived every batch of its hold"); } +void RecentImportFollowsReplacement() { + Heap heap; + ActiveRecorder active; + const auto context = mockContext(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* first = HostImportFor(context, heap.Address(), 4096); + Expect(first != nullptr, "the first range was not imported"); + if (first == nullptr) return; + Expect(HostImportFor(context, heap.Address(), 4096) == first, "a repeated lookup did not find the same import"); + const auto retired = first->buffer; + heap.Grow(); + const auto* grown = HostImportFor(context, heap.Address(RangeBytes), 4096); + Expect(grown != nullptr && grown->buffer != retired, "the grown run was not imported as a new import"); + if (grown == nullptr) return; + Expect(HostImportFor(context, heap.Address(), 4096) == grown, "a lookup after the replacement served the retired import instead of the one that replaced it"); +} + } int main() { @@ -374,6 +391,7 @@ int main() { IdleRetireOutlivesTheHold(); BusyRetireOutlivesALaterBatch(); AgcDriver::Tests::RunHostImportFailureTests(mockContext(), false); + RecentImportFollowsReplacement(); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index ec1450f8b0..19cc8dcfb4 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -305,6 +305,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_PROC_TABLE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | resolves the device's Vulkan entry points per call instead of filling the function table once | | `APS5_NO_QUEUED_DRAW_READ_ORDER` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp` | | stops queued draws and DMA copies from holding back write-backs over the guest memory they read at commit | | `APS5_NO_QUEUED_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | disables the queued-label table | +| `APS5_NO_RECENT_IMPORTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | looks every host import up in the import map instead of first checking the last four found while the map is unchanged | | `APS5_NO_RECORDED_CLEAR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | waits for a storage image's fast clear in a batch of its own instead of recording it | | `APS5_NO_RECORDER_SYNC_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | gives the draws the recorder waits for their own batch instead of recording them with the wait | | `APS5_NO_RECORD_COPIED_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | waits for copied-write draws at once instead of listing their write-back for later |