feat(agc): APS5_DUMP_SHADERS takes a list of program addresses

A comma-separated list of hex program addresses (optional 0x prefix) now
writes only those programs' requests, so one expensive shader can be
captured without serializing every request a title prepares. Any other
value, such as 1, keeps writing every request. A filtered-out program
returns no name, so diagnostics never point at a file that was not
written, and it stays eligible for a later list.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
KianandClaude Opus 5.5 committed 2026-10-28 23:26:48 +10:00
1 parent 750cf6cf48
commit ae305dfd5d
3 files changed
+72 -1

No files matched your search

@@ -1,12 +1,37 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
#include "ControlFlow/RequestSerializer.hpp"
#include <charconv>
#include <cstdlib>
#include <functional>
#include <optional>
#include <string_view>
namespace AgcDriver::DriverDetail {
namespace {
std::optional<std::set<std::uint64_t>> selectedPrograms() {
const char* value = std::getenv("APS5_DUMP_SHADERS");
if (value == nullptr || std::string_view(value) == "1") return std::nullopt;
std::set<std::uint64_t> 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<std::size_t> 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<unsigned long long>(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 {
@@ -195,6 +195,50 @@ void Run(AgcDriver::VulkanDevice& device) {
ExpectFailure([&] { static_cast<void>(AgcDriver::DriverDetail::SourceHandleFor(snapshot, 0, request)); }, "does not refer to registered code");
}
void DumpSelection(AgcDriver::VulkanDevice& device) {
using namespace ShaderRecompiler;
const std::array<std::uint32_t, 1> code{0xffffffffu};
const std::vector<std::byte> 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<std::uint32_t, 1> 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");
+1 -1
View File
@@ -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_<n>.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_<address>.req |
| `APS5_DUMP_SHADERS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | writes each prepared shader's request to shader_<address>.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_<address>_<n>.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_<address>_<n>.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 |