diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index df0c3d73d6..5376d833c6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -172,6 +172,9 @@ using AddressDrawReuseCounts = std::array #include #include +#include #include #include #include @@ -2273,8 +2274,15 @@ std::function indirectRecordCheck(const IndirectRecord* indirect) { }; } +std::atomic spaceKeepFailures{0}; +std::atomic stopAfterResolve{0}; + std::shared_ptr> reserveSpaceKeep(Recorder& recorder, const ShaderResources& resources) { if (!resources.SpaceReuse()) return nullptr; + if (spaceKeepFailures.load(std::memory_order_relaxed) != 0) { + spaceKeepFailures.fetch_sub(1, std::memory_order_relaxed); + throw std::bad_alloc(); + } auto slot = std::make_shared>(); recorder.Keep(slot); return slot; @@ -3231,6 +3239,11 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara bool recordable = recordDraws && recorder != nullptr && dumpLimit == 0 && std::all_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.resident != nullptr || binding.multisampled; }); auto& resolved = prepared.resolved; resolved = resolveDrawResources(context, state, draw, shaders, snapshots, indexBytes, recordable, outcome, timer); + if (stopAfterResolve.load(std::memory_order_relaxed) != 0) { + const auto stop = stopAfterResolve.exchange(0, std::memory_order_relaxed); + if (stop == 1) return false; + if (stop == 2) throw std::runtime_error("injected failure after the draw's resources were resolved"); + } auto& resources = resolved.resources; for (std::size_t index = 0; index < targets.size(); ++index) { auto& binding = targets[index]; @@ -4169,6 +4182,14 @@ const char* AddressDrawReuseName(AddressDrawReuse outcome) { return static_cast(outcome) < AddressDrawReuseNames.size() ? AddressDrawReuseNames[static_cast(outcome)] : "?"; } +void SetSpaceKeepFailuresForTest(std::uint32_t count) { + spaceKeepFailures.store(count, std::memory_order_relaxed); +} + +void SetDrawStopAfterResolveForTest(DrawStopForTest stop) { + stopAfterResolve.store(static_cast(stop), std::memory_order_relaxed); +} + std::uint64_t AddressDrawReuseVerifications() { return addressDrawReuseVerifications().load(std::memory_order_relaxed); } diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_address_draw_space_reuse_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_address_draw_space_reuse_tests.cmake index 015adaf554..bcf7c0bf2e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_address_draw_space_reuse_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_address_draw_space_reuse_tests.cmake @@ -17,4 +17,10 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_address_draw_space_reuse_verify PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_address_draw_space_reuse_held COMMAND agc_driver_address_draw_space_reuse_tests --held) + set_tests_properties(agc_driver_address_draw_space_reuse_held PROPERTIES ENVIRONMENT "APS5_RELEASE_ON_UNLOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_driver_address_draw_space_reuse_held PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/AddressDrawSpaceReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/AddressDrawSpaceReuse.cpp index fb8d252f50..97e10bfdb7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/AddressDrawSpaceReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/AddressDrawSpaceReuse.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -843,6 +844,110 @@ void MultisampleTargetOutside(Scene& scene) { Require(outcomes[AddressDrawReuse::Reused] == 1 && outcomes.Total() == 1 && Builds() == built, "address draw space reuse: a draw after a resident multisample target appeared outside its space counted " + outcomes.Text() + " and " + std::to_string(Builds() - built) + " builds, expected one reuse"); } +class InjectedFailures { +public: + InjectedFailures(std::uint32_t keeps, AgcDriver::Graphics::DrawStopForTest stop) { + AgcDriver::Graphics::SetSpaceKeepFailuresForTest(keeps); + AgcDriver::Graphics::SetDrawStopAfterResolveForTest(stop); + } + + ~InjectedFailures() { + AgcDriver::Graphics::SetSpaceKeepFailuresForTest(0); + AgcDriver::Graphics::SetDrawStopAfterResolveForTest(AgcDriver::Graphics::DrawStopForTest::None); + } + + InjectedFailures(const InjectedFailures&) = delete; + InjectedFailures& operator=(const InjectedFailures&) = delete; +}; + +struct KeepFailure { + const char* what; + std::uint32_t keeps; + AgcDriver::Graphics::DrawStopForTest stop; +}; + +constexpr std::array KeepFailures{{ + {"an unrecorded draw that returned early", 1, AgcDriver::Graphics::DrawStopForTest::ReturnAfterResolve}, + {"a draw whose preparation threw", 1, AgcDriver::Graphics::DrawStopForTest::ThrowAfterResolve}, + {"a recorded draw", 2, AgcDriver::Graphics::DrawStopForTest::None}, +}}; + +std::size_t Retained() { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + return AgcDriver::Graphics::RetainedSpaceCount(); +} + +std::string RetainedText(std::size_t now, std::size_t before) { + return std::to_string(now) + " retained, " + std::to_string(before) + " before"; +} + +void FailKeep(Scene& scene, const KeepFailure& failure) { + using AgcDriver::Graphics::AddressDrawReuse; + const Outcomes outcomes; + bool reported = false; + { + const InjectedFailures injected(failure.keeps, failure.stop); + try { + scene.Draw(); + } catch (const std::bad_alloc&) { + reported = true; + } + } + Require(reported, std::string("address draw space reuse: ") + failure.what + " whose space keep failed did not report the failure"); + Require(outcomes[AddressDrawReuse::Reused] == 1 && outcomes.Total() == 1, std::string("address draw space reuse: ") + failure.what + " whose space keep failed counted " + outcomes.Text() + ", expected one reuse"); +} + +void CompleteHeldBatch(Scene& scene) { + const std::array pattern{}; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(scene.device.FillBuffer(scene.spare.Address(), 16, pattern), "address draw space reuse: the fill that completes the held batch was not recorded on the GPU"); + } + scene.Wait(); +} + +void HeldSpaceRetaken(Scene& scene) { + using AgcDriver::Graphics::AddressDrawReuse; + scene.Store(scene.constants, 0, 110); + for (const auto& failure : KeepFailures) { + Require(scene.DrawnColor() == 110 && scene.DrawnColor() == 110, std::string("address draw space reuse: the draws before ") + failure.what + " whose space keep failed did not read the guest constant"); + const auto retained = Retained(); + FailKeep(scene, failure); + CompleteHeldBatch(scene); + const Outcomes outcomes; + const auto built = Builds(); + const auto color = scene.DrawnColor(); + Require(color == 110, std::string("address draw space reuse: the draw after ") + failure.what + " whose space keep failed read " + std::to_string(color) + " instead of 110"); + Require(outcomes[AddressDrawReuse::Reused] == 1 && outcomes.Total() == 1 && Builds() == built, std::string("address draw space reuse: a draw after the batch that held the space of ") + failure.what + " whose space keep failed completed counted " + outcomes.Text() + " and " + std::to_string(Builds() - built) + " builds, expected the held space retaken and reused"); + Require(Retained() == retained, std::string("address draw space reuse: a space held by its cached build after ") + failure.what + " whose space keep failed was retained (" + RetainedText(Retained(), retained) + ")"); + } +} + +void HeldSpaceInUse(Scene& scene) { + using AgcDriver::Graphics::AddressDrawReuse; + scene.Store(scene.constants, 0, 100); + for (const auto& failure : KeepFailures) { + Require(scene.DrawnColor() == 100 && scene.DrawnColor() == 100, std::string("address draw space reuse: the draws before ") + failure.what + " whose space keep failed did not read the guest constant"); + const auto retained = Retained(); + FailKeep(scene, failure); + { + const Outcomes outcomes; + const auto built = Builds(); + scene.Draw(); + Require(outcomes[AddressDrawReuse::InUse] == 1 && outcomes.Total() == 1 && Builds() == built + 1, std::string("address draw space reuse: a draw before the batch that held the space of ") + failure.what + " whose space keep failed completed counted " + outcomes.Text() + " and " + std::to_string(Builds() - built) + " builds, expected one refusal for a space in use and a build"); + const auto kept = Retained(); + Require(kept == retained + 1, std::string("address draw space reuse: the space held by ") + failure.what + " whose space keep failed was not retained when a fresh build replaced its cached build (" + RetainedText(kept, retained) + ")"); + const auto color = scene.Color(); + Require(color == 100, std::string("address draw space reuse: the fresh build after ") + failure.what + " whose space keep failed read " + std::to_string(color) + " instead of 100"); + } + const Outcomes outcomes; + const auto color = scene.DrawnColor(); + Require(color == 100, std::string("address draw space reuse: the draw after the batch that held the space of ") + failure.what + " completed read " + std::to_string(color) + " instead of 100"); + Require(outcomes[AddressDrawReuse::Reused] == 1 && outcomes.Total() == 1, std::string("address draw space reuse: the draw after the batch that held the space of ") + failure.what + " completed counted " + outcomes.Text() + ", expected the fresh build reused"); + Require(Retained() == retained, std::string("address draw space reuse: the retained space of ") + failure.what + " whose space keep failed was not released once its batch completed (" + RetainedText(Retained(), retained) + ")"); + } +} + void Disabled(Scene& scene) { scene.Store(scene.constants, 0, 70); Require(scene.DrawnColor() == 70, "address draw space reuse: the first draw with reuse off did not read the guest constant"); @@ -885,6 +990,11 @@ constexpr std::array DefaultCases{{ {"multisample-target-outside", MultisampleTargetOutside}, }}; +constexpr std::array HeldCases{{ + {"held-space-retaken", HeldSpaceRetaken}, + {"held-space-in-use", HeldSpaceInUse}, +}}; + constexpr std::array TransitionCases{{ {"unchanged", UnchangedInputs}, {"idle-free", IdleFree}, @@ -897,8 +1007,9 @@ int main(int argc, char** argv) { try { const std::string mode = argc > 1 ? argv[1] : ""; const std::string only = mode == "--case" && argc > 2 ? argv[2] : ""; - Require(mode.empty() || mode == "--off" || mode == "--transitions" || mode == "--verify" || !only.empty(), "usage: [--off | --transitions | --verify | --case ]"); + Require(mode.empty() || mode == "--off" || mode == "--transitions" || mode == "--verify" || mode == "--held" || !only.empty(), "usage: [--off | --transitions | --verify | --held | --case ]"); Require(mode != "--verify" || std::getenv("APS5_VERIFY_ADDRESS_DRAW_REUSE") != nullptr, "address draw space reuse: --verify needs APS5_VERIFY_ADDRESS_DRAW_REUSE=1"); + Require(mode != "--held" || std::getenv("APS5_RELEASE_ON_UNLOCK") != nullptr, "address draw space reuse: --held needs APS5_RELEASE_ON_UNLOCK=1"); LimitPinWait(); const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; @@ -913,7 +1024,7 @@ int main(int argc, char** argv) { if (mode == "--off") { Disabled(scene); } else { - const std::span cases = mode == "--transitions" ? std::span(TransitionCases) : std::span(DefaultCases); + const std::span cases = mode == "--transitions" ? std::span(TransitionCases) : mode == "--held" ? std::span(HeldCases) : std::span(DefaultCases); bool ran = false; for (const auto& entry : cases) { if (!only.empty() && only != entry.name) continue;