From 68c2b0f746a1f4b41cdd6afaf3f83b8429b718c3 Mon Sep 17 00:00:00 2001 From: eljackjoestar-cmd Date: Mon, 12 Oct 2026 13:33:45 +0400 Subject: [PATCH] fix(agc): prioritize demanded shader preparation --- .../include/Driver/Shaders/ShaderRegistry.hpp | 4 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 54 +++++++++++++++---- .../tests/execution/PreparedPublication.cpp | 40 ++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index ad90c2db7..7f286632d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -66,6 +66,7 @@ struct PreparedShaders : PreparedShaderState { std::mutex mutex; std::condition_variable settled; std::exception_ptr failure; + std::atomic demanded{false}; bool pending = false; }; @@ -102,7 +103,8 @@ std::optional RegisteredFloatMode(const Shade std::shared_ptr RegisteredHeader(const ShaderRegistry& registry, const Shader* shader); std::shared_ptr RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list types); void PublishRegisteredShader(std::shared_ptr& registry, const std::shared_ptr& snapshot); -void SubmitRegistrationPreparation(std::function task); +void SubmitRegistrationPreparation(std::function task, const PreparedShaders* prepared = nullptr); +void PrioritizeRegistrationPreparation(PreparedShaders& prepared); void SubmitLibraryPrebuild(std::function task); void CloseRegistrationPreparation(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 61cd0ee94..bc60a4956 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -141,6 +141,7 @@ bool RawGraphicsDrawn() { namespace { void AwaitRegisteredPreparation(PreparedShaders& prepared, std::unique_lock& lock) { + if (prepared.pending) PrioritizeRegistrationPreparation(prepared); prepared.settled.wait(lock, [&] { return !prepared.pending; }); if (prepared.failure != nullptr) std::rethrow_exception(prepared.failure); } @@ -738,10 +739,18 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap class RegistrationPreparePool { public: static RegistrationPreparePool& Instance() { - static auto* pool = new RegistrationPreparePool(std::max(2u, std::thread::hardware_concurrency() / 2u)); + static auto* pool = [] { + auto* created = new RegistrationPreparePool(std::max(2u, std::thread::hardware_concurrency() / 2u)); + PreparationSlot().store(created); + return created; + }(); return *pool; } + static RegistrationPreparePool* Existing() { + return PreparationSlot().load(); + } + static RegistrationPreparePool& Prebuild() { static auto* pool = [] { const char* text = std::getenv("APS5_PREBUILD_THREADS"); @@ -757,6 +766,11 @@ public: } private: + static std::atomic& PreparationSlot() { + static std::atomic slot{nullptr}; + return slot; + } + static std::atomic& PrebuildSlot() { static std::atomic slot{nullptr}; return slot; @@ -764,11 +778,12 @@ private: public: - void Submit(std::function task) { + void Submit(std::function task, const PreparedShaders* prepared = nullptr) { { std::unique_lock lock(mutex); if (threads != 0 && !closed) { - tasks.push_back(std::move(task)); + if (prepared != nullptr && prepared->demanded.load()) tasks.push_front({std::move(task), prepared}); + else tasks.push_back({std::move(task), prepared}); lock.unlock(); ready.notify_one(); return; @@ -777,6 +792,14 @@ public: task(); } + void Prioritize(const PreparedShaders& prepared) { + std::lock_guard lock(mutex); + const auto found = std::ranges::find_if(tasks, [&](const auto& task) { return task.prepared == &prepared; }); + if (found == tasks.end()) return; + if (std::getenv("APS5_TRACE_PREPARATION_WAIT") != nullptr) std::fprintf(stderr, "[shader-prepare] demanded queued task at position %zu of %zu\n", static_cast(std::distance(tasks.begin(), found)), tasks.size()); + std::rotate(tasks.begin(), found, std::next(found)); + } + void Close() { std::unique_lock lock(mutex); closed = true; @@ -802,7 +825,7 @@ private: auto& pool = *static_cast(argument); if (PerformanceTrace::Configured()) PerformanceTrace::BindThread("shader prepare", TraceThread::DefaultCapacity / 8); for (;;) { - std::function task; + Task task; { std::unique_lock lock(pool.mutex); pool.ready.wait(lock, [&] { return !pool.tasks.empty(); }); @@ -810,8 +833,8 @@ private: pool.tasks.pop_front(); ++pool.running; } - task(); - task = nullptr; + task.function(); + task = {}; { std::lock_guard lock(pool.mutex); --pool.running; @@ -821,10 +844,15 @@ private: return nullptr; } + struct Task { + std::function function; + const PreparedShaders* prepared = nullptr; + }; + std::mutex mutex; std::condition_variable ready; std::condition_variable idle; - std::deque> tasks; + std::deque tasks; unsigned threads = 0; unsigned running = 0; bool closed = false; @@ -836,6 +864,7 @@ bool AsyncRegistrationPrepare() { } void PrepareRegisteredInBackground(std::shared_ptr snapshot, std::shared_ptr plan) { + const auto* preparedKey = snapshot->prepared.get(); SubmitRegistrationPreparation([snapshot = std::move(snapshot), plan = std::move(plan)] { PerformanceContext timingContext(FrameTiming::Preparation()); std::vector entries; @@ -857,13 +886,18 @@ void PrepareRegisteredInBackground(std::shared_ptr snapsho prepared.pending = false; } prepared.settled.notify_all(); - }); + }, preparedKey); } } -void SubmitRegistrationPreparation(std::function task) { - RegistrationPreparePool::Instance().Submit(std::move(task)); +void SubmitRegistrationPreparation(std::function task, const PreparedShaders* prepared) { + RegistrationPreparePool::Instance().Submit(std::move(task), prepared); +} + +void PrioritizeRegistrationPreparation(PreparedShaders& prepared) { + prepared.demanded.store(true); + if (auto* pool = RegistrationPreparePool::Existing()) pool->Prioritize(prepared); } void SubmitLibraryPrebuild(std::function task) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp index d96b7a9ec..d0a96ffef 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp @@ -4,13 +4,16 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "Optimization/ResourceProgram.hpp" #include +#include #include #include #include #include #include #include +#include #include +#include #include namespace { @@ -300,6 +303,41 @@ void TransactionLock() { Require(excluded, "a transaction ran beside one that had read prepared state"); } +void PreparationPriority(bool beforeSubmit) { + const auto workers = std::max(2u, std::thread::hardware_concurrency() / 2u); + std::barrier started(static_cast(workers + 1u)); + std::counting_semaphore<> release(0); + std::latch finished(static_cast(workers)); + for (unsigned index = 0; index < workers; ++index) { + SubmitRegistrationPreparation([&] { + started.arrive_and_wait(); + release.acquire(); + finished.count_down(); + }); + } + started.arrive_and_wait(); + PreparedShaders prepared; + std::atomic order{0}; + std::promise background; + std::promise demanded; + auto backgroundDone = background.get_future(); + auto demandedDone = demanded.get_future(); + SubmitRegistrationPreparation([&] { background.set_value(order.fetch_add(1u)); }); + if (beforeSubmit) PrioritizeRegistrationPreparation(prepared); + SubmitRegistrationPreparation([&] { demanded.set_value(order.fetch_add(1u)); }, &prepared); + if (!beforeSubmit) { + PrioritizeRegistrationPreparation(prepared); + PrioritizeRegistrationPreparation(prepared); + } + release.release(); + const bool settled = demandedDone.wait_for(std::chrono::seconds(5)) == std::future_status::ready; + const bool backgroundSettled = backgroundDone.wait_for(std::chrono::seconds(5)) == std::future_status::ready; + release.release(static_cast(workers - 1u)); + finished.wait(); + Require(settled && backgroundSettled, "preparation queue did not complete its work"); + Require(demandedDone.get() == 0u && backgroundDone.get() == 1u && order.load() == 2u, "demanded preparation waited behind background work or ran twice"); +} + } int main() { @@ -308,6 +346,8 @@ int main() { HeaderSelection(); FusedHullHeaderSelection(); TransactionLock(); + PreparationPriority(false); + PreparationPriority(true); auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; Registration(*device);