diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp index 91b97871d7..19a1ca8811 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp @@ -1,12 +1,37 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ControlFlow/RequestSerializer.hpp" +#include #include #include +#include #include namespace AgcDriver::DriverDetail { +namespace { + +std::optional> selectedPrograms() { + const char* value = std::getenv("APS5_DUMP_SHADERS"); + if (value == nullptr || std::string_view(value) == "1") return std::nullopt; + std::set programs; + std::string_view rest(value); + while (!rest.empty()) { + const auto comma = rest.find(','); + auto token = rest.substr(0, comma); + rest = comma == std::string_view::npos ? std::string_view{} : rest.substr(comma + 1); + if (token.starts_with("0x") || token.starts_with("0X")) token.remove_prefix(2); + std::uint64_t address = 0; + const auto [end, error] = std::from_chars(token.data(), token.data() + token.size(), address, 16); + if (token.empty() || error != std::errc{} || end != token.data() + token.size()) return std::nullopt; + programs.insert(address); + } + if (programs.empty()) return std::nullopt; + return programs; +} + +} + void Driver::reportSkip(const char* kind, const std::string& what) { static AgcDriver::Mutex reportedMutex; static std::set reported; @@ -56,6 +81,7 @@ std::string Driver::dumpRequest(std::uint64_t address, const ShaderRecompiler::R char hex[32]; std::snprintf(hex, sizeof(hex), "%llx", static_cast(address)); const std::string name = "shader_" + std::string(hex) + std::string(suffix) + ".req"; + if (const auto selected = selectedPrograms(); selected.has_value() && !selected->contains(address)) return {}; std::lock_guard lock(dumpMutex); if (!dumped.insert(name).second) return name; try { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 94b7c946eb..1e5cd00571 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -195,6 +195,50 @@ void Run(AgcDriver::VulkanDevice& device) { ExpectFailure([&] { static_cast(AgcDriver::DriverDetail::SourceHandleFor(snapshot, 0, request)); }, "does not refer to registered code"); } +void DumpSelection(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 char* previous = std::getenv("APS5_DUMP_SHADERS"); + const std::string saved = previous != nullptr ? previous : ""; + const auto setCapture = [](const char* value) { +#ifdef _WIN32 + Require(_putenv_s("APS5_DUMP_SHADERS", value) == 0, "cannot set shader capture"); +#else + Require(setenv("APS5_DUMP_SHADERS", value, 1) == 0, "cannot set shader capture"); +#endif + }; + const auto written = [](const std::string& name) { + const bool exists = std::ifstream(name, std::ios::binary).is_open(); + std::remove(name.c_str()); + return exists; + }; + const std::string excludedPath = "shader_12345cafe_excluded.req"; + const std::string listedPath = "shader_12345cafe_listed.req"; + const std::string otherPath = "shader_feedface_unlisted.req"; + std::remove(excludedPath.c_str()); + std::remove(listedPath.c_str()); + std::remove(otherPath.c_str()); + setCapture("feedface"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, "_excluded").empty(), "a program outside the APS5_DUMP_SHADERS list was named as dumped"); + Require(!written(excludedPath), "a program outside the APS5_DUMP_SHADERS list was dumped"); + setCapture("feedface,0x12345CAFE"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, "_excluded") == excludedPath, "a program dropped by an earlier list was not dumped once listed"); + Require(written(excludedPath), "a listed program's request was not written after an earlier list dropped it"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, "_listed") == listedPath, "a listed program was not dumped"); + Require(written(listedPath), "a listed program's request was not written"); + setCapture("12345cafe,not-an-address"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(0xfeedfaceull, request, "_unlisted") == otherPath, "a value that is not an address list did not dump every program"); + Require(written(otherPath), "a value that is not an address list did not write the 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 +} + void FailureCapture(AgcDriver::VulkanDevice& device) { using namespace ShaderRecompiler; const std::array code{0xffffffffu}; @@ -1543,6 +1587,7 @@ int main(int argc, char** argv) { Run(*device); RegisteredVertexWorkgroup(device->Target()); FailureCapture(*device); + DumpSelection(*device); PendingRegistrationPreparation(*device); AwaitedRegistrationPreparation(); Require(argc != 2 || std::string_view(argv[1]) != "--fail-before-registration", "injected failure before registration"); diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index b74ec48890..cd7a74ae73 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -148,7 +148,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_DUMP_QUEUE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | dumps the first submissions of the queue with this hex id | | `APS5_DUMP_REJECTED` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp` | | writes a rejected submission to rejected_submission_.bin | | `APS5_DUMP_SCALE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 4 | keeps every n-th pixel in each direction of a dumped frame (1 to 16384; any other value fails) | -| `APS5_DUMP_SHADERS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | writes each prepared shader's request to shader_
.req | +| `APS5_DUMP_SHADERS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | writes each prepared shader's request to shader_
.req; a comma-separated list of hex program addresses (for example `807b90a100,0x807b925400`) writes only those programs' requests, and any other value (such as `1`) writes every request | | `APS5_DUMP_STORAGE` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | selects storage textures to dump by hex address or WxHxL[:format] extent as storage_
_.raw | | `APS5_DUMP_STORAGE_GATE` | path | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | with `APS5_DUMP_STORAGE`, waits for this path to exist and reads matching live images directly without guest writeback; at most eight addresses, 8 MiB linear storage per image; writes `storage_direct_
_.raw` with the existing pitch, height and VkFormat header and first-mip pixels | | `APS5_TEST_DIRECT_READBACK` | set | `core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp` | | runs the clean-volume gated live-image readback regression in the layered volume execution test |