perf(agc): keep render targets on gpu across acquire_mem

This commit is contained in:
boykopovar committed 2026-09-25 08:22:43 +03:00
1 parent c91eda8e9a
commit 5a280ced87
16 files changed
+155 -8

No files matched your search

@@ -25,6 +25,7 @@ std::string_view UnsupportedReason(std::uint32_t header);
void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
void Execute(std::span<const std::uint32_t> packet, QueueState& queue);
bool AccessesMemory(std::uint32_t header);
bool UsesGpuCacheBarrier(std::span<const std::uint32_t> packet);
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue);
DrawParameters ResolveDraw(std::span<const std::uint32_t> packet, const QueueState& queue);
@@ -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);
@@ -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();
@@ -266,6 +266,12 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
}
}
bool UsesGpuCacheBarrier(std::span<const std::uint32_t> 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;
@@ -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<PFN_vkDeviceWaitIdle>("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<PFN_vkCmdPipelineBarrier>("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 {
@@ -8,6 +8,7 @@ namespace AgcDriver::Graphics {
class DrawQueue {
public:
~DrawQueue();
std::unique_ptr<CommandBatch> Begin(const Context& context);
void Submit(std::unique_ptr<CommandBatch> commands, std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> storage);
void Resolve(std::uint64_t address, std::size_t bytes);
void Wait();
@@ -19,6 +20,7 @@ private:
std::unique_ptr<CommandBatch> commands;
};
std::vector<Entry> pending;
std::vector<std::unique_ptr<CommandBatch>> available;
};
}
@@ -4,6 +4,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
#include "prx/libSceAgcDriver/Graphics/include/RenderCache.hpp"
#include <list>
#include <map>
namespace AgcDriver::Graphics {
@@ -20,6 +21,7 @@ private:
};
Context context;
std::list<Entry> entries;
std::map<std::vector<std::byte>, std::list<Entry>::iterator> lookup;
};
}
@@ -39,6 +39,7 @@ public:
std::shared_ptr<ResidentColor> 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;
@@ -59,6 +59,7 @@ public:
void SubmitAndWait();
void Submit();
void Wait();
void Reset();
private:
void release() noexcept;
@@ -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<CommandBatch>(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;
@@ -9,6 +9,14 @@ DrawQueue::~DrawQueue() {
for (auto& entry : pending) entry.commands.reset();
}
std::unique_ptr<CommandBatch> DrawQueue::Begin(const Context& context) {
if (available.empty()) return std::make_unique<CommandBatch>(context);
auto commands = std::move(available.back());
available.pop_back();
commands->Reset();
return commands;
}
void DrawQueue::Submit(std::unique_ptr<CommandBatch> commands, std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> 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();
}
@@ -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 <type_traits>
#include <algorithm>
#include <iterator>
namespace AgcDriver::Graphics {
namespace {
@@ -15,6 +17,7 @@ void append(std::vector<std::byte>& key, const TValue& value) {
std::vector<std::byte> makeKey(const Context& context, const State& state, const std::shared_ptr<ResidentColor>& target, const ShaderResources& resources, std::span<const CompiledShader> shaders) {
std::vector<std::byte> 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<std::byte> makeKey(const Context& context, const State& state, const
}
std::shared_ptr<Pipeline> GraphicsPipelineCache::Get(const State& state, const std::shared_ptr<ResidentColor>& target, const ShaderResources& resources, std::span<const CompiledShader> 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<Pipeline>(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;
@@ -131,4 +131,10 @@ void RenderCache::Flush() {
Resolve(0, std::numeric_limits<std::size_t>::max(), true);
}
void RenderCache::InvalidateClean() {
for (const auto& [address, entry] : entries) {
if (!entry->Dirty()) entry->Invalidate();
}
}
}
@@ -172,6 +172,16 @@ void CommandBatch::SubmitAndWait() {
Wait();
}
void CommandBatch::Reset() {
Require(submitted && !pending, "command batch must complete before reuse");
Check(context.Function<PFN_vkResetFences>("vkResetFences")(context.device, 1, &fence), "vkResetFences graphics");
Check(context.Function<PFN_vkResetCommandBuffer>("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<PFN_vkBeginCommandBuffer>("vkBeginCommandBuffer")(commands, &begin), "vkBeginCommandBuffer graphics");
submitted = false;
}
void CommandBatch::Submit() {
PerformanceTimer timing("Graphics.Submit");
Require(!submitted, "command batch has already been submitted");
@@ -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<MemoryState*>(context);
check(bytes == sizeof(std::uint32_t), "memory transfer resolved an unrelated range");
if (writable) {
check(address == reinterpret_cast<std::uintptr_t>(&memory.destination), "memory transfer resolved an unrelated destination");
memory.written = true;
} else {
check(address == reinterpret_cast<std::uintptr_t>(&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();
@@ -98,7 +98,7 @@ void DisplayWindow::UpdateTitle() {
fpsFrames = 0;
}
char text[160];
std::snprintf(text, sizeof(text), "%s | %llu", title.value, static_cast<unsigned long long>(frameNum));
std::snprintf(text, sizeof(text), "%s | FPS: %.2f (%llu)", title.value, currentFps, static_cast<unsigned long long>(frameNum));
SDL_SetWindowTitle(window, text);
}