diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 5f2e43115..660622656 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3585,7 +3585,10 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { recordStep(PhaseRecordMarks); // Only copied written buffers (and BDA fault checks) need work once the GPU is done; without them // the batch can signal its labels from the GPU. - if (resources.NeedsCompletion()) { + if (resources.NeedsCompletion() && resources.Reusable()) { + auto kept = record.resources; + recorder.OnComplete([kept] { kept->CheckFaults(); }); + } else if (resources.NeedsCompletion()) { // Listed for DispatchIndirect until the write-back ran (a non-reusable object, so once). auto writers = state->copiedWriters; auto kept = record.resources; @@ -3873,7 +3876,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec // object the resource cache serves under this content key (reusable: no lease, no copied // writes, every direct region import- or mirror-served), so a hit's proof is the template's // Revalidate and nothing needs completion work. - if (recipeOut != nullptr && DispatchRecipes() && cacheable && resources->Reusable() && objects != nullptr && !contentKey.empty()) { + if (recipeOut != nullptr && DispatchRecipes() && cacheable && resources->Reusable() && !resources->NeedsCompletion() && objects != nullptr && !contentKey.empty()) { auto recipe = std::make_shared(); recipe->device = state->device; recipe->templateRef = resources; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 28370a397..737327b9b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -188,6 +188,8 @@ public: bool Reusable() const { return reusable; } static bool NeverReusable(std::span shaders); const std::vector>& SampledTextures() const { return textures; } + bool ChecksFaults() const { return bda != nullptr; } + void CheckFaults() const; // `shaders` are the stages the object was built from, in build order (a recorded draw's vertex // and fragment stages, or one compute stage): their bindings are walked like the build did. // How a Revalidate proved (or refused) the object, for the [recipe] line: the proof path taken diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 344261741..77d53b380 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1498,7 +1498,8 @@ void keepRecordedDraw(Recorder& recorder, const std::shared_ptr }); writers->push_back(resources); } else if (completion) { - recorder.OnComplete([resources] { resources->WriteBackBuffers(); }); + if (resources->Reusable()) recorder.OnComplete([resources] { resources->CheckFaults(); }); + else recorder.OnComplete([resources] { resources->WriteBackBuffers(); }); } for (const auto& resident : residents) { if (resident != nullptr) resident->MarkDirty(); @@ -2421,7 +2422,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con SharedResourceCache().Touch(recipe.key); countCache(&DrawProfile::cacheHits); timer.phase(PhaseLookup); - outcome.completion = false; + outcome.completion = resources->ChecksFaults(); outcome.recorded = true; // The pipeline is the recipe's while the pipeline store still holds it (its device's teardown // or its eviction bound drops it: a miss). The framebuffer is the recipe's while the pipeline's @@ -2435,6 +2436,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con const auto recordStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; RecordedDraw record; record.recorder = recorder; + record.completion = outcome.completion; record.resources = std::move(resources); record.pipeline = std::move(pipeline); record.framebuffer = std::move(framebuffer); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 9ac9ff9bd..0fbb3ef2a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1563,12 +1563,21 @@ std::atomic resourceCacheTouches{0}; } +namespace { + +bool LoopGuardEnabled() { + static const bool enabled = std::getenv("APS5_LOOP_GUARD") != nullptr; + return enabled; +} + +} + void ShaderResources::noteReusable() { // Taken whether or not the object turns out reusable: the lookups' records are this build's. captureValidation(); reusable = false; directRegions.clear(); - if (NeedsCompletion() || HoldsLease()) return; + if (guestMemory.HasCopiedWrites() || HoldsLease() || usesBda || (bda != nullptr && LoopGuardEnabled())) return; if (TemplateDataRefresh() && std::any_of(allocations.begin(), allocations.end(), [](const Allocation& allocation) { return allocation.buffer != nullptr && !allocation.guest && allocation.size > MaxRefreshBytes; })) return; const auto regions = guestMemory.DirectRegions(); if (!regions.has_value()) return; @@ -1586,8 +1595,9 @@ void ShaderResources::noteReusable() { } bool ShaderResources::NeverReusable(std::span shaders) { - return std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { - return shader.program != nullptr && std::any_of(shader.program->bindings.begin(), shader.program->bindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer; }); + const bool loopGuard = LoopGuardEnabled(); + return std::any_of(shaders.begin(), shaders.end(), [loopGuard](const CompiledShader& shader) { + return shader.program != nullptr && std::any_of(shader.program->bindings.begin(), shader.program->bindings.end(), [loopGuard](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || (loopGuard && binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer); }); }); } @@ -3446,6 +3456,10 @@ void ShaderResources::MarkGpuWrites(Recorder& recorder) { AgcDriver::ProfilePrint_nid_no_patch("[buffers] descriptor elements bound: %llu total, %llu written, %llu read-only; %llu pending-write notes skipped\n", static_cast(elements), static_cast(elements - readOnly), static_cast(readOnly), static_cast(counters.notesSkipped.load())); } +void ShaderResources::CheckFaults() const { + if (bda) bda->CheckFault(); +} + void ShaderResources::WriteBackBuffers() { if (bda) bda->CheckFault(); if (SkipWriteBack()) return; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 932eb95ac..43db11be7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -697,7 +697,8 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { const std::array stages{{{ShaderRecompiler::ShaderStage::TessellationControl, &control, 0}}}; const std::array unusedSnapshots{{{0, source}}}; ShaderResources resources(disabled, stages, ColorTarget{}, 0, 0, 0, unusedSnapshots); - Require(ShaderResources::NeverReusable(stages) && !resources.Reusable(), "a fault-buffer build is reusable"); + Require(!ShaderResources::NeverReusable(stages), "a fault-buffer build is refused the resource cache"); + Require(!resources.Reusable(), "a fault-buffer build with copied writes is reusable"); Require(resources.NeedsCompletion() && !resources.UsesAddressTable(), "a fault-buffer build reaches guest memory through the BDA table"); const auto fault = access.bytes(access.descriptor(5).buffer); for (const auto byte : fault) Require(byte == std::byte{}, "rect-list fault buffer was not initialized"); @@ -710,6 +711,19 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { resources.WriteBack(); Require(guest[1] == changed, "rect-list fault-only path lost guest buffer writes"); } + { + ShaderRecompiler::RecompileResult control; + control.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version; + control.bindings = {binding(Role::FaultBuffer, 5)}; + const std::array stages{{{ShaderRecompiler::ShaderStage::TessellationControl, &control, 0}}}; + ShaderResources resources(disabled, stages, ColorTarget{}, 0, 0, 0, {}); + Require(!ShaderResources::NeverReusable(stages) && resources.Reusable() && resources.ChecksFaults(), "a fault-only build is not reusable"); + resources.CheckFaults(); + const auto fault = access.bytes(access.descriptor(5).buffer); + const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::InvalidRectangle, 0, 0, 0, 0, 0}; + std::memcpy(fault.data(), &report, sizeof(report)); + reject([&] { resources.CheckFaults(); }, "rect-list requires"); + } { ShaderResources resources(context, compiled, snapshots); Require(ShaderResources::NeverReusable(std::span(&compiled, 1)) && !resources.Reusable(), "an address-based build is reusable");