diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 7aa6e9a89..3810be45b 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -907,6 +907,10 @@ if(BUILD_TESTING) set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endforeach() + set_tests_properties(agc_driver_prepared_shaders + agc_driver_prepared_shaders_indirect agc_driver_prepared_shaders_uncached + agc_driver_prepared_shaders_debug agc_driver_prepared_shaders_debug_failure + PROPERTIES RESOURCE_LOCK shader_failure_capture) endif() option(AGC_BUILD_VISUAL_TEST "Enable the standalone AGC SPIR-V visual test" OFF) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index ba6a2fc8a..0b92d3e8d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -62,6 +62,7 @@ public: void ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType); static void FoldDrawOffsets(const ShaderRecompiler::RecompileResult& result, const DrawProgram& program, Pm4::DrawParameters& parameters); static std::optional ClassifyIndirectDraw(const ShaderRecompiler::RecompileResult& result, const Graphics::State& graphics, const DrawProgram& frontProgram, const std::shared_ptr& localDevice, Pm4::DrawParameters& drawParameters, bool traceIndirect); + static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); private: friend class SampledReadScope; @@ -136,7 +137,6 @@ private: static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr); static void validate(const Submission& submission, const std::uint32_t* guest = nullptr); static void reportSkip(const char* kind, const std::string& what); - static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); static bool matchesFillKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); static bool fillClearEnabled(); static bool fillClearExactOnly(); 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 f40d915a6..f0a2e97bb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -14,6 +14,10 @@ #include #include +namespace ShaderRecompiler { +class ShaderPreparationContext; +} + namespace AgcDriver::DriverDetail { struct ShaderSnapshot; @@ -71,6 +75,8 @@ struct ShaderSnapshot { std::shared_ptr ReadRawComputeShader(std::uint64_t address); +std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr); + std::uint64_t NullPixelProgramAddress(); std::optional RegisteredFloatMode(const ShaderSnapshot& snapshot); void PublishRegisteredShader(std::shared_ptr& registry, const std::shared_ptr& snapshot); 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 be2666a59..e3e92b1a5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -20,6 +20,17 @@ namespace AgcDriver::DriverDetail { +std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation) { + try { + return ShaderRecompiler::PrepareShader(request, preparation); + } catch (...) { + try { + if (std::getenv("APS5_DUMP_SHADERS") != nullptr) static_cast(Driver::dumpRequest(request.shader.codeAddress, request)); + } catch (...) {} + throw; + } +} + std::shared_ptr ReadRawComputeShader(std::uint64_t address) { GuestMemory::CheckRange(reinterpret_cast(address), sizeof(std::uint32_t), 256); static std::mutex cacheMutex; @@ -181,7 +192,7 @@ std::shared_ptr SourceHandleFor(const Shad if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader"); APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast(snapshot.codeAddress)); } - auto handle = ShaderRecompiler::PrepareShader(request); + auto handle = PrepareShaderWithDiagnostics(request); snapshot.prepared->entries.push_back({codeOffset, handle}); return handle; } @@ -235,7 +246,7 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader"); APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast(snapshot.codeAddress)); } - auto handle = ShaderRecompiler::PrepareShader(request); + auto handle = PrepareShaderWithDiagnostics(request); invocationRequest = request; invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*handle); auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, handle, key); @@ -409,7 +420,7 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap ShaderRecompiler::ShaderPreparationContext preparation; const auto append = [&] { PerformanceTimer timing("Shader.PrepareArtifact"); - entries.push_back({codeOffset, ShaderRecompiler::PrepareShader(request, &preparation)}); + entries.push_back({codeOffset, PrepareShaderWithDiagnostics(request, &preparation)}); }; append(); if (compute) { @@ -468,7 +479,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } if (handle == nullptr) { PerformanceTimer timing("Shader.PrepareArtifact"); - handle = ShaderRecompiler::PrepareShader(request); + handle = PrepareShaderWithDiagnostics(request); } const auto bytes = handle->artifact->bindings.pushConstantSizeBytes; require(bytes <= capacity - pushOffset, "prepared graphics stages exceed the push constant block"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 850518ede..47c49c205 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -1,5 +1,6 @@ #include "VulkanTestDevice.hpp" #include "Optimization/ResourceProgram.hpp" +#include "ControlFlow/RequestSerializer.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" @@ -13,6 +14,10 @@ #include #include #include +#include +#include +#include +#include namespace { @@ -138,6 +143,52 @@ void Run(AgcDriver::VulkanDevice& device) { ExpectFailure([&] { static_cast(AgcDriver::DriverDetail::SourceHandleFor(snapshot, 0, request)); }, "artifact is missing"); } +void FailureCapture(AgcDriver::VulkanDevice& device) { + using namespace ShaderRecompiler; + const std::array code{0xffffffffu}; + const std::vector header(2048, std::byte{0x5a}); + RecompileRequest request{{ShaderStage::Compute, 0x12345cafeull, code, 0x20000, header}, + {32, 0, {}, ShaderComputeStageInfo{{1, 1, 1}, 0, {}, false, 1, {}}, {}, {}, {}}, device.ComputeTarget(32), {0, 0, 0, 128}}; + const auto failure = [&](auto action) { + try { action(); } + catch (const std::exception& error) { return std::string(error.what()); } + throw std::runtime_error("invalid shader preparation succeeded"); + }; + const auto expected = failure([&] { static_cast(PrepareShader(request)); }); + const char* previous = std::getenv("APS5_DUMP_SHADERS"); + const std::string saved = previous != nullptr ? previous : ""; + const std::string path = "shader_12345cafe.req"; + std::remove(path.c_str()); +#ifdef _WIN32 + Require(_putenv_s("APS5_DUMP_SHADERS", "") == 0, "cannot disable shader capture"); +#else + Require(unsetenv("APS5_DUMP_SHADERS") == 0, "cannot disable shader capture"); +#endif + Require(failure([&] { static_cast(AgcDriver::DriverDetail::PrepareShaderWithDiagnostics(request)); }) == expected, "disabled shader capture changed the preparation failure"); + Require(!std::ifstream(path, std::ios::binary).is_open(), "disabled shader capture created a request"); +#ifdef _WIN32 + Require(_putenv_s("APS5_DUMP_SHADERS", "1") == 0, "cannot enable shader capture"); +#else + Require(setenv("APS5_DUMP_SHADERS", "1", 1) == 0, "cannot enable shader capture"); +#endif + const auto actual = failure([&] { static_cast(AgcDriver::DriverDetail::PrepareShaderWithDiagnostics(request)); }); +#ifdef _WIN32 + Require(_putenv_s("APS5_DUMP_SHADERS", saved.c_str()) == 0, "cannot restore shader capture"); +#else + Require((previous != nullptr ? setenv("APS5_DUMP_SHADERS", saved.c_str(), 1) : unsetenv("APS5_DUMP_SHADERS")) == 0, "cannot restore shader capture"); +#endif + Require(actual == expected, "shader capture changed the preparation failure"); + std::ifstream file(path, std::ios::binary); + std::ostringstream text; + text << file.rdbuf(); + file.close(); + std::remove(path.c_str()); + const RequestSerializer serializer; + Require(text.str().size() > 1024 && text.str() == serializer.Serialize(request), "shader failure capture was incomplete"); + const auto replay = serializer.Deserialize(text.str()); + Require(serializer.Serialize(replay.request) == text.str(), "shader failure capture changed on replay"); +} + void PrepareMultisampledStorage(AgcDriver::VulkanDevice& device) { using namespace ShaderRecompiler; std::array code{0xd7460000u, 0x0401060cu, 0xd7460001u, 0x0405060du, 0x7e04020eu, 0x7e060280u, 0x7e080208u, 0x7e0a0209u, 0x7e0c020au, 0x7e0e020bu, 0xf0200f38u, 0x00000400u, 0xbf810000u}; @@ -325,6 +376,7 @@ int main(int argc, char** argv) { auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; Run(*device); + FailureCapture(*device); Require(argc != 2 || std::string_view(argv[1]) != "--fail-before-registration", "injected failure before registration"); PrepareMultisampledStorage(*device); device.reset();