fix(agc): store pending results when queues go idle

This commit is contained in:
MarcZX100 2026-10-11 16:39:36 +00:00
parent a11c8f7f70
commit d57d3f0000
6 changed files with 140 additions and 0 deletions

View File

@ -57,6 +57,7 @@ public:
// mutex through the flush hook, so a thread submitting on another queue's behalf (a WAIT_REG_MEM
// poller, a compute worker's between-packet flush) passes false: it must only submit, never wait.
void SubmitRecorded(bool reapFirst = true);
void StoreIdleResults(bool reapFirst = true);
// Unlocked drain: SubmitAndEpoch (under the mutex) returns the serial covering every recorded
// batch (0: nothing in flight); WaitRecorded(serial) waits for it WITHOUT the mutex (the caller
// holds a shared_ptr to this device); ReapRecorded(serial) (under the mutex) runs the completions

View File

@ -104,6 +104,10 @@ void Driver::run(std::uint32_t id) noexcept {
while (!ready()) {
if (!completionsPending() || (id != 0 && Graphics::Recorder::PendingCompletionLabels() == 0)) {
lock.unlock();
if (const auto localDevice = device.Load()) localDevice->StoreIdleResults(id == 0);
lock.lock();
if (ready()) break;
if (id == 0) queue0Dormant.store(true, std::memory_order_relaxed);
changed.wait(lock, ready);
if (id == 0) queue0Dormant.store(false, std::memory_order_relaxed);

View File

@ -1443,6 +1443,14 @@ bool OpportunisticReap() {
}
void VulkanDevice::StoreIdleResults(bool reapFirst) {
if (!Graphics::StorageTexture::AnyPending()) return;
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
Graphics::StorageTexture::FlushAllPending("idle");
SubmitRecorded(reapFirst);
}
void VulkanDevice::SubmitRecorded(bool reapFirst) {
if (!state->recorder) return;
// Finished batches are retired first (a non-blocking fence status check on the front of the

View File

@ -176,6 +176,7 @@ public:
// overlapping pending image, as before).
static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr);
static void FlushAllPending(const char* reason);
static bool AnyPending();
static bool StoreAtFlipRequested(const char* value);
// See PendingSerial: a change of a surface's source outside the registry (a unit shadow
// retile) moves it too.

View File

@ -2970,6 +2970,12 @@ void StorageTexture::FlushAllPending(const char* reason) {
static_cast<void>(FlushPending(0, std::numeric_limits<std::size_t>::max(), nullptr, reason));
}
bool StorageTexture::AnyPending() {
auto& pending = Pending();
std::lock_guard lock(pending.mutex);
return pending.textures.begin() != pending.textures.end();
}
bool StorageTexture::StoreAtFlipRequested(const char* value) {
if (value == nullptr) return false;
if (std::strcmp(value, "1") == 0) return true;

View File

@ -0,0 +1,120 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using AgcDriver::Graphics::StorageTexture;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 8;
constexpr std::uint32_t Side = 256;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t TileR64KBX = 0x1b;
constexpr std::uint32_t Type2D = 9;
constexpr std::size_t BlockBytes = 0x60000;
constexpr std::uint32_t Untouched = 0xdeadbeefu;
alignas(256) constexpr std::array<std::uint32_t, 7> StoreCode{
0x7e040300, 0x7e060280, 0x7e0202ff, 0x12345678u, 0xf0200108, 0x00020102, 0xbf810000,
};
std::uint64_t AddressOf(const void* data) {
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
}
std::array<std::uint32_t, 8> TextureDescriptor(const std::uint32_t* texels) {
const auto address = AddressOf(texels);
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u),
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
0xfacu | (TileR64KBX << 20u) | (Type2D << 28u),
0u,
0u,
0u,
0u,
};
}
void Store(AgcDriver::VulkanDevice& device, const std::uint32_t* texels) {
std::vector<std::uint32_t> userData(16, 0u);
const auto texture = TextureDescriptor(texels);
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
const std::span<const std::uint32_t> code(StoreCode);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
std::vector<std::uint32_t> storage((BlockBytes + 0x10000u) / 4u);
auto* texels = reinterpret_cast<std::uint32_t*>((reinterpret_cast<std::uintptr_t>(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu});
std::fill(texels, texels + BlockBytes / 4u, Untouched);
{
GuestAllocations::Mutation mutation;
mutation.Add(texels, BlockBytes, true, true, true);
}
const auto surface = AddressOf(texels);
const auto bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(texels))).guestBytes;
const volatile std::uint32_t* cpu = texels;
const auto stored = [&] {
for (std::size_t index = 0; index < bytes / 4u; ++index) {
if (cpu[index] == 0x12345678u) return true;
}
return false;
};
Store(*device, texels);
Require(StorageTexture::FindPending(surface, bytes) != nullptr, "the store left no pending image");
Require(!stored(), "the pending result reached guest memory before the idle store");
device->StoreIdleResults();
Require(StorageTexture::FindPending(surface, bytes) == nullptr, "the idle store left the image pending");
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!stored() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(stored(), "the idle store did not bring the result to guest memory without a further submission");
device->StoreIdleResults();
device->WaitIdle();
{
GuestAllocations::Mutation mutation;
mutation.Remove(texels);
}
std::puts("store idle results tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}