fix(diagnostics): capture failed shader preparation requests

This commit is contained in:
Julio Cacko 2026-10-09 12:17:11 -03:00 committed by Adrià Franch
parent 23ed501f4a
commit 097aaa698b
5 changed files with 78 additions and 5 deletions

View File

@ -907,6 +907,10 @@ if(BUILD_TESTING)
set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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)

View File

@ -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<Graphics::IndirectDrawPath> ClassifyIndirectDraw(const ShaderRecompiler::RecompileResult& result, const Graphics::State& graphics, const DrawProgram& frontProgram, const std::shared_ptr<VulkanDevice>& 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<const std::uint32_t> 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<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute);
static bool fillClearEnabled();
static bool fillClearExactOnly();

View File

@ -14,6 +14,10 @@
#include <string>
#include <vector>
namespace ShaderRecompiler {
class ShaderPreparationContext;
}
namespace AgcDriver::DriverDetail {
struct ShaderSnapshot;
@ -71,6 +75,8 @@ struct ShaderSnapshot {
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address);
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);
std::uint64_t NullPixelProgramAddress();
std::optional<ShaderRecompiler::ShaderFloatMode> RegisteredFloatMode(const ShaderSnapshot& snapshot);
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);

View File

@ -20,6 +20,17 @@
namespace AgcDriver::DriverDetail {
std::shared_ptr<const ShaderRecompiler::SourceHandle> 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<void>(Driver::dumpRequest(request.shader.codeAddress, request));
} catch (...) {}
throw;
}
}
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address) {
GuestMemory::CheckRange(reinterpret_cast<const void*>(address), sizeof(std::uint32_t), 256);
static std::mutex cacheMutex;
@ -181,7 +192,7 @@ std::shared_ptr<const ShaderRecompiler::SourceHandle> 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<unsigned long long>(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<unsigned long long>(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<PreparedShaders::Entry> 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<PreparedGraphicsStage> 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");

View File

@ -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 <vector>
#include <future>
#include <barrier>
#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <sstream>
namespace {
@ -138,6 +143,52 @@ void Run(AgcDriver::VulkanDevice& device) {
ExpectFailure([&] { static_cast<void>(AgcDriver::DriverDetail::SourceHandleFor(snapshot, 0, request)); }, "artifact is missing");
}
void FailureCapture(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 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<void>(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<void>(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<void>(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<std::uint32_t, 13> 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();