// agc_shader_replay ...: replays compute recompile requests the driver saved with // APS5_DUMP_SHADERS=1, running the same resource analysis and recompile steps without the game. #include "ControlFlow/GraphBuilder.hpp" #include "ControlFlow/RequestSerializer.hpp" #include "ControlFlow/Structurizer.hpp" #include "Optimization/ResourceMaterializer.hpp" #include "Optimization/ResourceProgram.hpp" #include "Recompiler.hpp" #include "RdnaDecoder/RdnaInstructionDecoder.hpp" #if ANYPS5_ENABLE_SPIRV_TOOLS #include "spirv-tools/libspirv.hpp" #endif #include #include #include #include #include namespace { std::string ReadText(const char* path) { std::ifstream file(path, std::ios::binary); std::ostringstream stream; stream << file.rdbuf(); auto text = stream.str(); while (!text.empty() && (text.back() == '\n' || text.back() == '\r' || text.back() == ' ')) text.pop_back(); return text; } // The error text without the serialized request that Recompile appends. std::string FirstLine(const std::string& text) { const auto request = text.find("\nRecompileRequest:"); auto shown = request == std::string::npos ? text : text.substr(0, request); if (shown.size() > 2000) shown.resize(2000); return shown; } bool g_disassemble = false; bool g_assembly = false; bool g_memory = false; bool g_spirv = false; bool g_prepare = false; bool g_graph = false; bool g_maintenance8 = false; bool g_code = false; bool Replay(const char* path) { auto request = ShaderRecompiler::RequestSerializer{}.Deserialize(ReadText(path)); std::printf("%s: %zu code words, %zu user data, %zu memory regions, wave%u\n", path, request.request.shader.code.size(), request.request.context.userData.size(), request.request.context.memory.size(), request.request.context.waveSize); if (request.request.context.compute.has_value()) { const auto& compute = *request.request.context.compute; std::printf(" compute: threads %ux%ux%u, lds %u dwords, group ids %d%d%d, tg size %d, thread id components %u\n", compute.numThreads[0], compute.numThreads[1], compute.numThreads[2], compute.ldsSizeDwords, compute.groupIdEnable[0], compute.groupIdEnable[1], compute.groupIdEnable[2], compute.tgSizeEnable, compute.threadIdComponentCount); if (compute.PartialGroups()) std::printf(" partial groups: dispatch of %ux%ux%u threads\n", compute.partialThreads[0], compute.partialThreads[1], compute.partialThreads[2]); } if (request.request.graphics.has_value()) { const auto& graphics = *request.request.graphics; std::printf(" graphics: %zu linked programs, draw %u indices of %u bytes at 0x%llx, %u instances\n", graphics.linkedPrograms.size(), graphics.draw.indexCount, graphics.draw.indexElementBytes, static_cast(graphics.draw.indexAddress), graphics.draw.instanceCount); if (graphics.mesh.has_value()) { const auto& mesh = *graphics.mesh; std::printf(" mesh: input primitive %u, %u primitives / %u vertices per group, max %u vertices / %u primitives, %u threads, lds %u dwords, provoking %u, ESGS item %u\n", mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize); } } if (g_memory) { // --mem: the captured inputs. User data words are printed; each memory region is written to // mem__.bin next to the request for inspection with other tools. const auto& context = request.request.context; for (std::size_t i = 0; i < context.userData.size(); ++i) std::printf(" user[%zu] = 0x%08x\n", i, context.userData[i]); for (const auto& region : context.memory) { char name[96]; std::snprintf(name, sizeof(name), "mem_%llx_%llx.bin", static_cast(request.request.shader.codeAddress), static_cast(region.guestAddress)); if (std::FILE* file = std::fopen(name, "wb")) { std::fwrite(region.bytes.data(), 1, region.bytes.size(), file); std::fclose(file); } std::printf(" region 0x%llx + 0x%zx -> %s\n", static_cast(region.guestAddress), region.bytes.size(), name); } } if (g_code) { std::string name(path); name = name.substr(name.find_last_of("/\\") + 1) + ".code"; const auto& code = request.request.shader.code; std::ofstream file(name, std::ios::binary); file.write(reinterpret_cast(code.data()), static_cast(code.size() * sizeof(code[0]))); if (!file) { std::printf(" could not write %s\n", name.c_str()); return false; } std::printf(" code -> %s\n", name.c_str()); } if (g_assembly) { const auto program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(request.request.shader.code); // Raw first words carry what the text omits (branch offsets, waitcnt fields). for (const auto& instruction : program.instructions) std::printf("raw=%08x %s\n", instruction.rawWords[0], ShaderRecompiler::RdnaInstructionToString(instruction).c_str()); } if (g_graph) { const auto program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(request.request.shader.code); auto graph = ShaderRecompiler::GraphBuilder{}.Build(program); std::printf("control flow graph:\n%s", ShaderRecompiler::GraphToString(graph).c_str()); try { ShaderRecompiler::Structurizer{}.Structurize(graph); std::printf("structured control flow graph:\n%s", ShaderRecompiler::GraphToString(graph).c_str()); } catch (const std::exception& error) { std::printf(" structurization failed: %s\n", FirstLine(error.what()).c_str()); return false; } } try { auto program = ShaderRecompiler::PrepareResourceProgram(request.request); constexpr ShaderRecompiler::ResourceMaterializer materializer; static_cast(materializer.ExtractPlan(program)); } catch (const std::exception& error) { std::printf(" resource analysis failed: %s\n", FirstLine(error.what()).c_str()); return false; } if (g_prepare) { try { const auto handle = ShaderRecompiler::PrepareShader(request.request); std::printf(" prepared: %zu SPIR-V words\n", ShaderRecompiler::GetPreparedArtifact(*handle).spirv.Words().size()); return true; } catch (const std::exception& error) { std::printf(" prepare failed: %s\n", FirstLine(error.what()).c_str()); return false; } } try { if (g_maintenance8) request.request.target.nonConstantImageOffsets = true; const auto result = ShaderRecompiler::Recompile(request.request); std::printf(" recompiled: %zu SPIR-V words\n", result.spirv.size()); if (g_spirv) { std::string name(path); name = name.substr(name.find_last_of("/\\") + 1) + ".spv"; std::ofstream(name, std::ios::binary).write(reinterpret_cast(result.spirv.data()), static_cast(result.spirv.size() * sizeof(result.spirv[0]))); } if (g_memory) { for (const auto& binding : result.bindings) { if (binding.role != ShaderRecompiler::DescriptorRole::GuestBuffers) continue; for (std::size_t i = 0; i + 4 <= binding.guestDescriptor.size(); i += 4) { const auto* v = binding.guestDescriptor.data() + i; const bool written = i / 4 >= binding.bufferWritten.size() || binding.bufferWritten[i / 4]; std::printf(" buffer %zu: V# %08x %08x %08x %08x%s\n", i / 4, v[0], v[1], v[2], v[3], written ? " written" : ""); } } } #if ANYPS5_ENABLE_SPIRV_TOOLS if (g_disassemble) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); std::string text; if (tools.Disassemble(result.spirv, &text, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) std::printf("%s\n", text.c_str()); } #else if (g_disassemble) std::printf(" (--dis needs ANYPS5_ENABLE_SPIRV_TOOLS=ON)\n"); #endif return true; } catch (const std::exception& error) { std::printf(" recompile failed: %s\n", FirstLine(error.what()).c_str()); return false; } } } int main(int argc, char** argv) { if (argc < 2) { std::fprintf(stderr, "usage: agc_shader_replay [--dis] [--asm] [--cfg] [--mem] [--spv] [--prepare] [--code] [--maintenance8] ...\n the driver writes shader_
.req files when APS5_DUMP_SHADERS is set\n"); return 2; } int failures = 0; for (int i = 1; i < argc; ++i) { if (std::string(argv[i]) == "--dis") { g_disassemble = true; continue; } if (std::string(argv[i]) == "--asm") { g_assembly = true; continue; } if (std::string(argv[i]) == "--cfg") { g_graph = true; continue; } if (std::string(argv[i]) == "--maintenance8") { g_maintenance8 = true; continue; } if (std::string(argv[i]) == "--mem") { g_memory = true; continue; } if (std::string(argv[i]) == "--code") { g_code = true; continue; } if (std::string(argv[i]) == "--prepare") { g_prepare = true; continue; } if (std::string(argv[i]) == "--spv") { g_spirv = true; continue; } try { if (!Replay(argv[i])) ++failures; } catch (const std::exception& error) { std::printf("%s: could not load request: %s\n", argv[i], FirstLine(error.what()).c_str()); ++failures; } } return failures == 0 ? 0 : 1; }