From 5a280ced8762f06c3499cfab8cf4b094cc23a3dd Mon Sep 17 00:00:00 2001 From: boykopovar Date: Fri, 25 Sep 2026 08:22:43 +0300 Subject: [PATCH] perf(agc): keep render targets on gpu across acquire_mem --- .../libSceAgcDriver/Execution/include/Pm4.hpp | 1 + .../Execution/include/VulkanDevice.hpp | 2 + .../libSceAgcDriver/Execution/src/Driver.cpp | 20 +++++-- .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 6 +++ .../Execution/src/VulkanDevice.cpp | 24 +++++++++ .../Graphics/include/DrawQueue.hpp | 2 + .../include/GraphicsPipelineCache.hpp | 2 + .../Graphics/include/RenderCache.hpp | 1 + .../Graphics/include/Resources.hpp | 1 + .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 +- .../Graphics/src/DrawQueue.cpp | 9 ++++ .../Graphics/src/GraphicsPipelineCache.cpp | 21 +++++++- .../Graphics/src/RenderCache.cpp | 6 +++ .../Graphics/src/Resources.cpp | 10 ++++ core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 54 +++++++++++++++++++ .../prx/libSceVideoOut/src/DisplayWindow.cpp | 2 +- 16 files changed, 155 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 6d5ab9b76c..88c1055664 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -25,6 +25,7 @@ std::string_view UnsupportedReason(std::uint32_t header); void Validate(std::span packet, std::uint32_t queue); void Execute(std::span packet, QueueState& queue); bool AccessesMemory(std::uint32_t header); +bool UsesGpuCacheBarrier(std::span packet); std::array ResolveDispatch(std::span packet, const QueueState& queue); DrawParameters ResolveDraw(std::span packet, const QueueState& queue); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 2d468b3902..50f172636d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -19,6 +19,8 @@ public: VulkanDevice& operator=(const VulkanDevice&) = delete; ShaderRecompiler::SpirvTarget Target() const; void WaitIdle(); + void WaitDraws(); + void AcquireGpuMemory(); void ResolveMemory(std::uint64_t address, std::size_t bytes, bool writable); void* Window() const; void Resize(std::uint32_t width, std::uint32_t height); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index a221b2015e..0e067bac33 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -227,8 +227,8 @@ public: if (presenting->Presentable()) { if (buffer != nullptr) { require(buffer->width == window.width && buffer->height == window.height, "display buffer extent differs from output"); - presenting->WaitIdle(); - timing.Mark("device_idle_wait"); + presenting->WaitDraws(); + timing.Mark("draw_wait"); presenting->PresentDisplayBuffer(*buffer); timing.Mark("present_display_buffer"); } else { @@ -564,8 +564,20 @@ private: if (opcode == 0x37 || opcode == 0x40 || opcode == 0x50 || opcode == 0x42 || opcode == 0x46 || opcode == 0x58 || header == FlipPacketHeader) { std::lock_guard gpuLock(gpuMutex); timing.Mark("gpu_mutex_wait"); - if (device != nullptr) device->WaitIdle(); - timing.Mark("device_idle_wait"); + const auto eventType = opcode == 0x46 ? packet[1] & 0x3fu : 0u; + const auto memoryTransfer = opcode == 0x37 || opcode == 0x40 || opcode == 0x50; + const auto waitDraws = memoryTransfer || opcode == 0x42 || (opcode == 0x46 && (eventType == 0x07 || eventType == 0x0f || eventType == 0x10)); + const auto gpuCacheBarrier = opcode == 0x58 && Pm4::UsesGpuCacheBarrier(packet); + if (device != nullptr) { + if (gpuCacheBarrier) device->AcquireGpuMemory(); + else if (waitDraws) device->WaitDraws(); + else { + const auto scope = header == FlipPacketHeader ? "Driver.FlipWait" : opcode == 0x58 ? "Driver.AcquireMemoryWait" : "Driver.CacheEventWait"; + PerformanceTimer waitTiming(scope); + device->WaitIdle(); + } + } + timing.Mark(gpuCacheBarrier ? "gpu_cache_barrier" : waitDraws ? "draw_wait" : "device_idle_wait"); } if (header == FlipPacketHeader) { CheckFailure(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index c22d4a4402..af771512db 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -266,6 +266,12 @@ void Validate(std::span packet, std::uint32_t queue) { } } +bool UsesGpuCacheBarrier(std::span packet) { + require(!packet.empty() && ((packet[0] >> 8u) & 0xffu) == 0x58, "cache barrier requires ACQUIRE_MEM"); + Validate(packet, 0); + return packet.size() == 8 && (packet[7] & 0xfc00u) == 0; +} + bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { case 0x16: case 0x2d: case 0x35: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 8ab6637604..cf874be94a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -482,8 +482,32 @@ VulkanDevice::~VulkanDevice() = default; void VulkanDevice::WaitIdle() { PerformanceTimer timing("Vulkan.WaitIdle"); state->drawQueue->Wait(); + timing.Mark("draw_wait"); state->renderCache->Flush(); + timing.Mark("render_cache_flush"); check(state->DeviceFunction("vkDeviceWaitIdle")(state->device), "vkDeviceWaitIdle"); + timing.Mark("device_wait"); +} + +void VulkanDevice::WaitDraws() { + PerformanceTimer timing("Vulkan.WaitDraws"); + state->drawQueue->Wait(); +} + +void VulkanDevice::AcquireGpuMemory() { + PerformanceTimer timing("Vulkan.AcquireGpuMemory"); + state->drawQueue->Wait(); + timing.Mark("draw_wait"); + const auto context = graphicsContext(); + Graphics::CommandBatch batch(context); + VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + context.Function("vkCmdPipelineBarrier")(batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); + batch.SubmitAndWait(); + timing.Mark("gpu_barrier"); + state->renderCache->InvalidateClean(); + timing.Mark("invalidate_clean"); } void* VulkanDevice::Window() const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp index 45907de576..6195af0d84 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp @@ -8,6 +8,7 @@ namespace AgcDriver::Graphics { class DrawQueue { public: ~DrawQueue(); + std::unique_ptr Begin(const Context& context); void Submit(std::unique_ptr commands, std::shared_ptr resources, std::shared_ptr storage); void Resolve(std::uint64_t address, std::size_t bytes); void Wait(); @@ -19,6 +20,7 @@ private: std::unique_ptr commands; }; std::vector pending; + std::vector> available; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GraphicsPipelineCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GraphicsPipelineCache.hpp index fc131f305d..47e999cd67 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GraphicsPipelineCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GraphicsPipelineCache.hpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/RenderCache.hpp" #include +#include namespace AgcDriver::Graphics { @@ -20,6 +21,7 @@ private: }; Context context; std::list entries; + std::map, std::list::iterator> lookup; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/RenderCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/RenderCache.hpp index 1766d26ffc..0b1af36bc1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/RenderCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/RenderCache.hpp @@ -39,6 +39,7 @@ public: std::shared_ptr Find(std::uint64_t address) const; void Resolve(std::uint64_t address, std::size_t bytes, bool writable); void Flush(); + void InvalidateClean(); private: Context context; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp index ad772654f2..5aca2f2136 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp @@ -59,6 +59,7 @@ public: void SubmitAndWait(); void Submit(); void Wait(); + void Reset(); private: void release() noexcept; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 6bb0b0410c..07d912b686 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -107,7 +107,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& storage->pipeline = context.graphicsPipelines->Get(state, storage->color, *resources, shaders); auto& pipeline = *storage->pipeline; timing.Mark("pipeline_cache"); - auto batch = std::make_unique(context); + auto batch = context.drawQueue->Begin(context); const auto commands = batch->Handle(); VkMemoryBarrier upload{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; upload.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp index 007ba53d7f..5fc498e557 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp @@ -9,6 +9,14 @@ DrawQueue::~DrawQueue() { for (auto& entry : pending) entry.commands.reset(); } +std::unique_ptr DrawQueue::Begin(const Context& context) { + if (available.empty()) return std::make_unique(context); + auto commands = std::move(available.back()); + available.pop_back(); + commands->Reset(); + return commands; +} + void DrawQueue::Submit(std::unique_ptr commands, std::shared_ptr resources, std::shared_ptr storage) { if (pending.size() >= 64) Wait(); pending.push_back({std::move(storage), std::move(resources), std::move(commands)}); @@ -33,6 +41,7 @@ void DrawQueue::Wait() { timing.Mark("fence_wait"); entry.resources->WriteBack(); timing.Mark("resources_writeback"); + available.push_back(std::move(entry.commands)); } pending.clear(); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GraphicsPipelineCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GraphicsPipelineCache.cpp index 4738de2392..a57717c7b5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GraphicsPipelineCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GraphicsPipelineCache.cpp @@ -1,7 +1,9 @@ #include "prx/libSceAgcDriver/Graphics/include/GraphicsPipelineCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" +#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include #include +#include namespace AgcDriver::Graphics { namespace { @@ -15,6 +17,7 @@ void append(std::vector& key, const TValue& value) { std::vector makeKey(const Context& context, const State& state, const std::shared_ptr& target, const ShaderResources& resources, std::span shaders) { std::vector key; + key.reserve(512); append(key, target ? target->Target().View() : VK_NULL_HANDLE); append(key, state.hasColorTarget); append(key, state.rectList); @@ -94,18 +97,32 @@ std::vector makeKey(const Context& context, const State& state, const } std::shared_ptr GraphicsPipelineCache::Get(const State& state, const std::shared_ptr& target, const ShaderResources& resources, std::span shaders) { + PerformanceTimer timing("Graphics.PipelineCache"); auto key = makeKey(context, state, target, resources, shaders); - for (auto it = entries.begin(); it != entries.end(); ++it) { - if (it->key != key) continue; + timing.Mark("key"); + const auto found = lookup.find(key); + if (found != lookup.end()) { + const auto it = found->second; auto pipeline = it->pipeline; entries.splice(entries.end(), entries, it); + timing.Mark("hit"); return pipeline; } + timing.Mark("miss"); auto pipeline = std::make_shared(context, state, target ? &target->Target() : nullptr, resources, shaders); + timing.Mark("create"); entries.push_back({std::move(key), target, pipeline}); + try { + const auto it = std::prev(entries.end()); + Require(lookup.emplace(it->key, it).second, "duplicate graphics pipeline cache key"); + } catch (...) { + entries.pop_back(); + throw; + } while (entries.size() > 128) { const auto it = std::find_if(entries.begin(), entries.end(), [](const auto& entry) { return entry.pipeline.use_count() == 1; }); if (it == entries.end()) break; + lookup.erase(it->key); entries.erase(it); } return pipeline; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/RenderCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/RenderCache.cpp index 06dd062777..ac2134ce1e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/RenderCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/RenderCache.cpp @@ -131,4 +131,10 @@ void RenderCache::Flush() { Resolve(0, std::numeric_limits::max(), true); } +void RenderCache::InvalidateClean() { + for (const auto& [address, entry] : entries) { + if (!entry->Dirty()) entry->Invalidate(); + } +} + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index d077135aff..5e244016f4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -172,6 +172,16 @@ void CommandBatch::SubmitAndWait() { Wait(); } +void CommandBatch::Reset() { + Require(submitted && !pending, "command batch must complete before reuse"); + Check(context.Function("vkResetFences")(context.device, 1, &fence), "vkResetFences graphics"); + Check(context.Function("vkResetCommandBuffer")(commands, 0), "vkResetCommandBuffer graphics"); + VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + Check(context.Function("vkBeginCommandBuffer")(commands, &begin), "vkBeginCommandBuffer graphics"); + submitted = false; +} + void CommandBatch::Submit() { PerformanceTimer timing("Graphics.Submit"); Require(!submitted, "command batch has already been submitted"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4331fba306..75629c05f3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -228,6 +229,44 @@ void testCopies() { #endif } +void testMemorySynchronization() { + struct MemoryState { + std::uint32_t source = 0; + std::uint32_t destination = 0; + bool read = false; + bool written = false; + } memory; + AgcDriver::QueueState state; + const auto resolve = [](void* context, std::uint64_t address, std::size_t bytes, bool writable) { + auto& memory = *static_cast(context); + check(bytes == sizeof(std::uint32_t), "memory transfer resolved an unrelated range"); + if (writable) { + check(address == reinterpret_cast(&memory.destination), "memory transfer resolved an unrelated destination"); + memory.written = true; + } else { + check(address == reinterpret_cast(&memory.source), "memory transfer resolved an unrelated source"); + memory.source = 42; + memory.read = true; + } + }; + const AgcDriver::GuestMemory::MemoryAccessScope scope(&memory, resolve); + execute(state, makePacket(0x37, {0x100, low(&memory.destination), high(&memory.destination), 17})); + check(memory.written && !memory.read && memory.destination == 17, "WRITE_DATA did not synchronize its destination"); + for (const auto opcode : {0x40u, 0x50u}) { + memory = {}; + const auto packet = opcode == 0x40 + ? makePacket(opcode, {0x101, low(&memory.source), high(&memory.source), low(&memory.destination), high(&memory.destination)}) + : makePacket(opcode, {0x60000000, low(&memory.source), high(&memory.source), low(&memory.destination), high(&memory.destination), 4}); + execute(state, packet); + check(memory.read && memory.written && memory.destination == 42, "memory copy used stale data before range synchronization"); + } + const AgcDriver::GuestMemory::MemoryAccessScope rejecting(&memory, [](void*, std::uint64_t, std::size_t, bool) { + throw std::runtime_error("range synchronization failed"); + }); + expectFailure([&] { execute(state, makePacket(0x37, {0x100, low(&memory.destination), high(&memory.destination), 99})); }, "range synchronization failed"); + check(memory.destination == 42, "failed synchronization changed the destination"); +} + void testEventWrite() { for (const auto eventType : {0x07u, 0x0fu, 0x10u}) { AgcDriver::Pm4::Validate(makePacket(0x46, {0x400u | eventType}), 0); @@ -262,6 +301,20 @@ void testEventWrite() { void testAcquireMem() { const auto captured = makePacket(0x58, {0x02007fc0, 0, 0, 0, 0, 10, 0x200}); AgcDriver::Pm4::Validate(captured, 0); + check(AgcDriver::Pm4::UsesGpuCacheBarrier(captured), "L1 acquire must preserve GPU render targets"); + for (const auto flags : {0u, 0x200u, 0x3ffu, 0x10200u, 0x20200u}) { + auto packet = captured; + packet[7] = flags; + check(AgcDriver::Pm4::UsesGpuCacheBarrier(packet), "GPU cache acquire requires an unnecessary host writeback"); + } + for (const auto flags : {0x400u, 0x800u, 0x1000u, 0x4000u, 0x8000u}) { + auto packet = captured; + packet[7] = flags; + check(!AgcDriver::Pm4::UsesGpuCacheBarrier(packet), "L2 acquire lost host synchronization"); + } + check(!AgcDriver::Pm4::UsesGpuCacheBarrier(makePacket(0x58, {0x00800000, 0xffffffff, 0, 0, 0, 10})), "legacy acquire lost host synchronization"); + expectFailure([] { AgcDriver::Pm4::UsesGpuCacheBarrier({}); }, "requires ACQUIRE_MEM"); + expectFailure([] { AgcDriver::Pm4::UsesGpuCacheBarrier(makePacket(0x58, {0, 0, 0, 0, 0, 0, 0x2000})); }, "cache discard"); AgcDriver::Pm4::Validate(makePacket(0x58, {0x82007fc0, 1, 0, 0xffffffff, 0, 0xffff, 0x200}), 0); AgcDriver::Pm4::Validate(makePacket(0x58, {0x80000000, 0, 0, 0, 0, 10, 0x200}), 0x20); AgcDriver::Pm4::Validate(makePacket(0x58, {0x00800000, 0xffffffff, 0, 0, 0, 10}), 0); @@ -359,6 +412,7 @@ int main(int argc, char** argv) { testAutoDraw(); testMemory(); testCopies(); + testMemorySynchronization(); testEventWrite(); testAcquireMem(); testDriverSubmission(); diff --git a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp index 9be8982bbd..f861a8e14e 100644 --- a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp +++ b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp @@ -98,7 +98,7 @@ void DisplayWindow::UpdateTitle() { fpsFrames = 0; } char text[160]; - std::snprintf(text, sizeof(text), "%s | %llu", title.value, static_cast(frameNum)); + std::snprintf(text, sizeof(text), "%s | FPS: %.2f (%llu)", title.value, currentFps, static_cast(frameNum)); SDL_SetWindowTitle(window, text); }