diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index ea07b09d4..2b0bdc8c5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -273,6 +273,7 @@ public: // `recipe`, when given, receives the DrawRecipe a recorded, cacheable, reusable, direct draw // built for its draw-cache entry (design_cpu_final M8); null otherwise. void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr); + std::optional KnownDrawRejection(const Graphics::State& graphics, std::span shaders) const; // A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after // the packet's labels; Rebuild when the recipe's device is not this one or DrawWithRecipe // missed (nothing recorded: the caller runs Draw with the hit's stages and re-attaches). diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index b345e4776..f38b2a916 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -4866,6 +4866,10 @@ private: fold(*programResults[0], direct); if (graphics.rectList && patched.contains(0)) buildRectList(); snapshot(); + if (auto known = localDevice->KnownDrawRejection(graphics, stages)) { + rejected = std::move(*known); + return DrawVerdict::Rejected; + } lockForDraw(); noteDrawWriters(stages, submission.queue); phase(DrawRowVectors); @@ -4891,6 +4895,12 @@ private: recipe = findDrawRecipe(drawKey, recipeStages); if (recipe == nullptr) VulkanDevice::NoteDrawRecipeMiss(VulkanDevice::DrawRecipePrecheck::NoRecipe); } + if (recipe == nullptr) { + if (auto known = localDevice->KnownDrawRejection(graphics, stages)) { + rejected = std::move(*known); + return DrawVerdict::Rejected; + } + } lockForDraw(); noteDrawWriters(stages, submission.queue); phase(DrawRowVectors); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 2509d238c..5db14ed8d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2196,6 +2196,10 @@ VulkanDevice::IndirectDrawSupport VulkanDevice::DrawIndirectSupport() const { return {state->drawIndirectFirstInstance, state->multiDrawIndirect, state->drawIndirectCount}; } +std::optional VulkanDevice::KnownDrawRejection(const Graphics::State& graphics, std::span shaders) const { + return Graphics::KnownValidationFailure(graphicsContext(), shaders, graphics); +} + void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipe) { // Two stdout lines per draw cost ~1.3 ms per frame of the queue-0 worker (part of it under the // GPU mutex); APS5_TRACE_DRAWS=1 restores them. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 531d44eee..4f76ca6b4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -1,3 +1,5 @@ +#include +#include #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DRAW_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DRAW_HPP @@ -13,6 +15,7 @@ namespace AgcDriver::Graphics { // built for its draw-cache entry (design_cpu_final M8; null otherwise, and always under // APS5_NO_DRAW_RECIPE=1). void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr); +std::optional KnownValidationFailure(const Context& context, std::span shaders, const State& state); std::array MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 88b37d143..28f822893 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -369,12 +369,9 @@ void countCache(std::uint64_t DrawProfile::*counter) { // fragment results the key already names (the pipeline key treats them the same way). `memoized` // says whether the memo applied, `hit` whether it answered. APS5_NO_VALIDATE_CACHE=1 validates // every draw. -std::set CachedFragmentOutputs(const Context& context, std::span shaders, const State& state, bool& memoized, bool& hit) { +bool ValidationKey(const Context& context, std::span shaders, const State& state, std::vector& key) { using Stage = ShaderRecompiler::ShaderStage; static const bool disabled = std::getenv("APS5_NO_VALIDATE_CACHE") != nullptr; - memoized = false; - hit = false; - std::vector key; const auto add = [&](auto value) { key.push_back(static_cast(value)); }; bool keyed = !disabled; if (keyed) { @@ -435,19 +432,47 @@ std::set CachedFragmentOutputs(const Context& context, std::span< add(tessellation.outputTopology); } } + return keyed; +} + +std::mutex& validationMutex() { static std::mutex mutex; + return mutex; +} + +std::map, std::string>& validationFailures() { + static std::map, std::string> failures; + return failures; +} + +std::set CachedFragmentOutputs(const Context& context, std::span shaders, const State& state, bool& memoized, bool& hit) { + memoized = false; + hit = false; + std::vector key; + const bool keyed = ValidationKey(context, shaders, state, key); static std::map, std::set> memo; if (keyed) { memoized = true; - std::lock_guard lock(mutex); + std::lock_guard lock(validationMutex()); if (const auto found = memo.find(key); found != memo.end()) { hit = true; return found->second; } } - auto outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing); + std::set outputs; + try { + outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing); + } catch (const std::exception& error) { + if (keyed) { + std::lock_guard lock(validationMutex()); + auto& failures = validationFailures(); + if (failures.size() >= 1024) failures.clear(); + failures.emplace(std::move(key), error.what()); + } + throw; + } if (keyed) { - std::lock_guard lock(mutex); + std::lock_guard lock(validationMutex()); // A handful of configurations recur; a runaway key space is dropped wholesale. if (memo.size() >= 1024) memo.clear(); memo.emplace(std::move(key), outputs); @@ -1238,6 +1263,15 @@ bool RecordDraws() { } +std::optional KnownValidationFailure(const Context& context, std::span shaders, const State& state) { + std::vector key; + if (!ValidationKey(context, shaders, state, key)) return std::nullopt; + std::lock_guard lock(validationMutex()); + const auto& failures = validationFailures(); + if (const auto found = failures.find(key); found != failures.end()) return found->second; + return std::nullopt; +} + void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipeOut) { PerformanceTimer timing("Graphics.Draw"); // APS5_PROFILE_DRAW prints the time of each phase of the draw (microseconds) and the [draws] totals.