#include "graphics/shader/recompiler/ShaderRecompiler.h" #include "common/assert.h" #include "common/logging/log.h" #include "graphics/shader/recompiler/cfg/ShaderCFG.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h" #include "graphics/shader/recompiler/ir/BindingLayout.h" #include "graphics/shader/recompiler/ir/ReadLaneElimination.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceTracking.h" #include "graphics/shader/recompiler/ir/ScalarProvenance.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderInfoCollection.h" #include "graphics/shader/recompiler/ir/SrtPatcher.h" #include "graphics/shader/recompiler/ir/SrtWalker.h" #include #include #include #include #include #include #include namespace Libs::Graphics::ShaderRecompiler { namespace { bool ReadZeroMemory(void*, uint64_t, uint32_t* value) { if (value == nullptr) { return false; } *value = 0; return true; } const char* GetDumpLabel(const CompileOptions& options) { return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler"; } std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) { std::string dump = "CFG:\n"; dump += CFG::GraphToString(cfg); dump += "\nIR:\n"; dump += IR::ProgramToString(ir); return dump; } const char* StageName(ShaderType stage) { switch (stage) { case ShaderType::Compute: return "CS"; case ShaderType::Vertex: return "VS"; case ShaderType::Pixel: return "PS"; default: return "unknown"; } } std::string FormatCfgFailure(const CFG::Graph& cfg, const CompileOptions& options, const std::string& reason) { const auto block_id = cfg.failure_block != UINT32_MAX ? cfg.failure_block : cfg.entry_block; return CFG::FormatBlockDiagnostic(cfg, block_id, StageName(options.stage), reason); } bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) { switch (inst.op) { case IR::Opcode::SLoadDword: case IR::Opcode::SBufferLoadDword: case IR::Opcode::BufferLoadUbyte: case IR::Opcode::BufferLoadSbyte: case IR::Opcode::BufferLoadUshort: case IR::Opcode::BufferLoadSshort: case IR::Opcode::BufferLoadDword: case IR::Opcode::BufferStoreByte: case IR::Opcode::BufferStoreShort: case IR::Opcode::BufferStoreDword: case IR::Opcode::AtomicSwapU32: case IR::Opcode::AtomicAddU32: case IR::Opcode::AtomicSubU32: case IR::Opcode::AtomicSMinI32: case IR::Opcode::AtomicUMinU32: case IR::Opcode::AtomicSMaxI32: case IR::Opcode::AtomicUMaxU32: case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicXorU32: case IR::Opcode::FlatLoadUbyte: case IR::Opcode::FlatLoadSbyte: case IR::Opcode::FlatLoadUshort: case IR::Opcode::FlatLoadSshort: case IR::Opcode::FlatLoadDword: case IR::Opcode::FlatStoreByte: case IR::Opcode::FlatStoreShort: case IR::Opcode::FlatStoreDword: case IR::Opcode::DsMinF32: case IR::Opcode::DsMaxF32: case IR::Opcode::DsWriteByte: case IR::Opcode::DsWriteShort: case IR::Opcode::DsWriteB32: case IR::Opcode::DsWriteAddtidB32: case IR::Opcode::DsAppend: case IR::Opcode::DsConsume: case IR::Opcode::ImageStore: case IR::Opcode::Export: return true; default: return false; } } bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, std::string* reason) { for (const auto& block: ir.blocks) { if (!block.terminator.loop_header) { continue; } for (const auto& inst: block.instructions) { if (!InstructionMaySplitSpirvBlock(inst)) { continue; } if (reason != nullptr) { *reason = fmt::format("loop header block {} contains an instruction whose SPIR-V " "lowering emits internal control flow: {}", block.id, IR::InstructionToString(inst).c_str()); } return true; } } return false; } enum class EmbeddedFetchValueType { Unknown, Constant, AttribTable, Attrib, BufferTable, Buffer, Index }; struct EmbeddedFetchSgprInfo { EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown; int attrib_id = 0; uint32_t value = 0; std::vector prolog_loads; }; struct EmbeddedFetchVgprInfo { EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown; }; using EmbeddedFetchVectorLanes = std::map; uint64_t EmbeddedFetchVectorLaneKey(uint32_t reg, uint32_t lane) { return (static_cast(reg) << 32u) | lane; } uint32_t EmbeddedFetchLane(uint32_t lane, uint32_t wave_size) { return wave_size == 32 || wave_size == 64 ? lane % wave_size : lane; } void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg) { const auto first = lanes->lower_bound(EmbeddedFetchVectorLaneKey(reg, 0)); const auto last = lanes->lower_bound(EmbeddedFetchVectorLaneKey(reg + 1u, 0)); lanes->erase(first, last); } struct EmbeddedFetchLoad { uint32_t pc = 0; int attrib_id = -1; uint32_t components = 0; std::vector prolog_loads; }; struct EmbeddedFetchData { std::vector loads; int32_t vertex_offset_sgpr = -1; }; bool IsDecodedSgpr(const Decoder::Operand& op) { return op.kind == Decoder::OperandKind::Sgpr || op.kind == Decoder::OperandKind::VccLo || op.kind == Decoder::OperandKind::VccHi; } uint32_t DecodedSgprReg(const Decoder::Operand& op) { switch (op.kind) { case Decoder::OperandKind::VccLo: return 106u; case Decoder::OperandKind::VccHi: return 107u; default: return op.reg; } } bool IsDecodedVgpr(const Decoder::Operand& op) { return op.kind == Decoder::OperandKind::Vgpr; } uint32_t DecodedDstSize(const Decoder::Instruction& inst) { return std::max(inst.data_dwords, 1u); } uint32_t EmbeddedFetchDstSize(const Decoder::Instruction& inst) { return inst.opcode == Decoder::Opcode::VMadU64U32 ? 2u : DecodedDstSize(inst); } bool EmbeddedFetchHasBranch(Decoder::Opcode opcode) { switch (opcode) { case Decoder::Opcode::SSetpcB64: case Decoder::Opcode::SBranch: case Decoder::Opcode::SCbranchScc0: case Decoder::Opcode::SCbranchScc1: case Decoder::Opcode::SCbranchVccz: case Decoder::Opcode::SCbranchVccnz: case Decoder::Opcode::SCbranchExecz: case Decoder::Opcode::SCbranchExecnz: return true; default: return false; } } void ClearEmbeddedFetchSgprs(std::array& sgprs, const Decoder::Operand& dst, uint32_t size) { if (!IsDecodedSgpr(dst)) { return; } const auto register_id = DecodedSgprReg(dst); for (uint32_t i = 0; i < size && register_id + i < sgprs.size(); i++) { sgprs[register_id + i] = {}; } } bool TryDecodedOperandConstant(const std::array& sgprs, const Decoder::Operand& op, uint32_t& value) { switch (op.kind) { case Decoder::OperandKind::LiteralConstant: case Decoder::OperandKind::IntegerInlineConstant: case Decoder::OperandKind::FloatInlineConstant: value = op.value; return true; case Decoder::OperandKind::Null: value = 0; return true; default: break; } if (IsDecodedSgpr(op) && DecodedSgprReg(op) < sgprs.size() && sgprs[DecodedSgprReg(op)].type == EmbeddedFetchValueType::Constant) { value = sgprs[DecodedSgprReg(op)].value; return true; } return false; } bool TryDecodedSmemOffset(const std::array& sgprs, const Decoder::Instruction& inst, uint32_t& raw_offset) { uint32_t base = 0; if (!TryDecodedOperandConstant(sgprs, inst.src1, base)) { return false; } const auto value = static_cast(base) + inst.offset; if (value > 0xffffffffull) { return false; } raw_offset = static_cast(value); return true; } bool IsEmbeddedFetchSLoad(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::SLoadDword: case Decoder::Opcode::SLoadDwordx2: case Decoder::Opcode::SLoadDwordx4: case Decoder::Opcode::SLoadDwordx8: case Decoder::Opcode::SLoadDwordx16: return true; default: return false; } } bool IsEmbeddedFetchBufferLoad(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::BufferLoadFormatX: case Decoder::Opcode::BufferLoadFormatXy: case Decoder::Opcode::BufferLoadFormatXyz: case Decoder::Opcode::BufferLoadFormatXyzw: return true; default: return false; } } bool IsEmbeddedFetchAttribPropagationAlu(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::SBfeU32: case Decoder::Opcode::SAndB32: case Decoder::Opcode::SAddI32: case Decoder::Opcode::SAddU32: case Decoder::Opcode::SLshlB32: return true; default: return false; } } int BufferTableAttribFromOffset(uint32_t raw_offset, int dword) { return static_cast((raw_offset + static_cast(dword) * 4u) / 16u); } void AppendUniquePcs(std::vector& dst, const std::vector& src) { dst.reserve(dst.size() + src.size()); for (auto pc: src) { if (std::find(dst.begin(), dst.end(), pc) == dst.end()) { dst.push_back(pc); } } } EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded, const ShaderVertexInputInfo* input_info, uint32_t user_data_base, uint32_t user_data_count, uint32_t wave_size) { EmbeddedFetchData data; if (input_info == nullptr || !input_info->fetch_embedded) { return data; } data.loads.reserve(input_info->resources_num); int32_t offset_candidate = -1; bool offset_conflict = false; const int shift_regs = 8; const int attrib_reg = input_info->fetch_attrib_reg + shift_regs; const int buffer_reg = input_info->fetch_buffer_reg + shift_regs; std::array sgprs {}; std::array vgprs {}; EmbeddedFetchVectorLanes vector_lanes; const bool track_vector_lanes = std::none_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& inst) { return EmbeddedFetchHasBranch(inst.opcode); }); if (attrib_reg >= 0 && attrib_reg < static_cast(sgprs.size())) { sgprs[attrib_reg].type = EmbeddedFetchValueType::AttribTable; } if (attrib_reg + 1 >= 0 && attrib_reg + 1 < static_cast(sgprs.size())) { sgprs[attrib_reg + 1].type = EmbeddedFetchValueType::AttribTable; } if (buffer_reg >= 0 && buffer_reg < static_cast(sgprs.size())) { sgprs[buffer_reg].type = EmbeddedFetchValueType::BufferTable; } if (buffer_reg + 1 >= 0 && buffer_reg + 1 < static_cast(sgprs.size())) { sgprs[buffer_reg + 1].type = EmbeddedFetchValueType::BufferTable; } for (const auto& inst: decoded.instructions) { // Fetch shaders accumulate the draw's vertex offset in v0. The PS5 NGG ABI // seeds S_NGG_VERTEX_INDEX in v5 and applies the same offset there before fetching. const bool vertex_index_accumulator = IsDecodedVgpr(inst.dst) && (inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5)); uint32_t sad_zero = 0; const bool vertex_offset_add = vertex_index_accumulator && IsDecodedSgpr(inst.src0) && ((inst.opcode == Decoder::Opcode::VAddI32 && IsDecodedVgpr(inst.src1) && inst.src1.reg == inst.dst.reg) || (user_data_base == 8 && inst.dst.reg == 5 && inst.opcode == Decoder::Opcode::VSadU32 && IsDecodedVgpr(inst.src2) && inst.src2.reg == inst.dst.reg && TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0)); if (data.loads.empty() && vertex_offset_add) { const auto reg = DecodedSgprReg(inst.src0); if (reg >= user_data_base && reg - user_data_base < user_data_count) { if (offset_candidate >= 0 && offset_candidate != static_cast(reg)) { offset_conflict = true; } else { offset_candidate = static_cast(reg); } } } switch (inst.opcode) { case Decoder::Opcode::VWritelaneB32: { uint32_t lane = 0; if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) { vgprs[inst.dst.reg] = {}; } if (track_vector_lanes && IsDecodedVgpr(inst.dst) && IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size() && TryDecodedOperandConstant(sgprs, inst.src1, lane)) { vector_lanes[EmbeddedFetchVectorLaneKey(inst.dst.reg, EmbeddedFetchLane(lane, wave_size))] = sgprs[DecodedSgprReg(inst.src0)]; } else if (IsDecodedVgpr(inst.dst)) { ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg); } break; } case Decoder::Opcode::VReadlaneB32: { uint32_t lane = 0; if (track_vector_lanes && IsDecodedSgpr(inst.dst) && DecodedSgprReg(inst.dst) < sgprs.size() && IsDecodedVgpr(inst.src0) && TryDecodedOperandConstant(sgprs, inst.src1, lane)) { const auto found = vector_lanes.find(EmbeddedFetchVectorLaneKey( inst.src0.reg, EmbeddedFetchLane(lane, wave_size))); sgprs[DecodedSgprReg(inst.dst)] = found != vector_lanes.end() ? found->second : EmbeddedFetchSgprInfo {}; } else if (IsDecodedSgpr(inst.dst)) { ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1); } break; } case Decoder::Opcode::SMovB32: if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size()) { sgprs[DecodedSgprReg(inst.dst)] = sgprs[DecodedSgprReg(inst.src0)]; } else if (IsDecodedSgpr(inst.dst)) { uint32_t value = 0; if (TryDecodedOperandConstant(sgprs, inst.src0, value)) { auto& dst = sgprs[DecodedSgprReg(inst.dst)]; dst.type = EmbeddedFetchValueType::Constant; dst.value = value; dst.prolog_loads.clear(); } else { ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1); } } break; case Decoder::Opcode::SMovkI32: if (IsDecodedSgpr(inst.dst)) { auto& dst = sgprs[DecodedSgprReg(inst.dst)]; dst.type = EmbeddedFetchValueType::Constant; dst.value = inst.src0.value; dst.prolog_loads.clear(); } break; default: if (IsEmbeddedFetchSLoad(inst)) { if (IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size() && sgprs[DecodedSgprReg(inst.src0)].type == EmbeddedFetchValueType::AttribTable) { uint32_t raw_offset = 0; if (TryDecodedSmemOffset(sgprs, inst, raw_offset)) { const auto register_id = DecodedSgprReg(inst.dst); const int index = static_cast(raw_offset / 4u); for (uint32_t i = 0; i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) { auto& dst = sgprs[register_id + i]; dst.type = EmbeddedFetchValueType::Attrib; dst.attrib_id = index + static_cast(i); dst.prolog_loads = {inst.pc}; } } else { ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst)); } } else if (IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size() && sgprs[DecodedSgprReg(inst.src0)].type == EmbeddedFetchValueType::BufferTable) { const auto register_id = DecodedSgprReg(inst.dst); uint32_t raw_offset = 0; if (TryDecodedSmemOffset(sgprs, inst, raw_offset)) { for (uint32_t i = 0; i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) { auto& dst = sgprs[register_id + i]; dst.type = EmbeddedFetchValueType::Buffer; dst.attrib_id = BufferTableAttribFromOffset(raw_offset, static_cast(i)); dst.prolog_loads = {inst.pc}; } } else if (IsDecodedSgpr(inst.src1) && DecodedSgprReg(inst.src1) < sgprs.size() && sgprs[DecodedSgprReg(inst.src1)].type == EmbeddedFetchValueType::Attrib && (inst.offset & 0x3u) == 0) { for (uint32_t i = 0; i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) { auto& dst = sgprs[register_id + i]; dst.type = EmbeddedFetchValueType::Buffer; dst.attrib_id = sgprs[DecodedSgprReg(inst.src1)].attrib_id; dst.prolog_loads = sgprs[DecodedSgprReg(inst.src1)].prolog_loads; dst.prolog_loads.push_back(inst.pc); } } else { ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst)); } } else { ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst)); } } else if (inst.opcode == Decoder::Opcode::VCndmaskB32) { if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) { ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg); } if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size() && IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 && IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) { vgprs[inst.dst.reg].type = EmbeddedFetchValueType::Index; } } else if (IsEmbeddedFetchAttribPropagationAlu(inst)) { if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size() && sgprs[DecodedSgprReg(inst.src0)].type == EmbeddedFetchValueType::Attrib) { sgprs[DecodedSgprReg(inst.dst)] = sgprs[DecodedSgprReg(inst.src0)]; } else if (IsDecodedSgpr(inst.dst)) { uint32_t src0 = 0; uint32_t src1 = 0; if (TryDecodedOperandConstant(sgprs, inst.src0, src0) && TryDecodedOperandConstant(sgprs, inst.src1, src1)) { auto& dst = sgprs[DecodedSgprReg(inst.dst)]; dst.type = EmbeddedFetchValueType::Constant; switch (inst.opcode) { case Decoder::Opcode::SAndB32: dst.value = src0 & src1; break; case Decoder::Opcode::SLshlB32: dst.value = src0 << (src1 & 31u); break; case Decoder::Opcode::SBfeU32: dst.value = src0 >> (src1 & 31u); break; default: dst.value = src0 + src1; break; } dst.prolog_loads.clear(); } else { ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1); } } } else if (IsEmbeddedFetchBufferLoad(inst)) { if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgprs.size() && vgprs[inst.src0.reg].type == EmbeddedFetchValueType::Index && IsDecodedSgpr(inst.src1) && DecodedSgprReg(inst.src1) < sgprs.size() && sgprs[DecodedSgprReg(inst.src1)].type == EmbeddedFetchValueType::Buffer) { const auto& buffer = sgprs[DecodedSgprReg(inst.src1)]; EmbeddedFetchLoad load; load.pc = inst.pc; load.attrib_id = buffer.attrib_id; load.components = DecodedDstSize(inst); load.prolog_loads = buffer.prolog_loads; if (data.loads.empty() && !offset_conflict) { data.vertex_offset_sgpr = offset_candidate; } data.loads.push_back(load); } } break; } if (inst.opcode == Decoder::Opcode::VMovreldB32) { vector_lanes.clear(); } else if (inst.opcode != Decoder::Opcode::VWritelaneB32 && IsDecodedVgpr(inst.dst)) { for (uint32_t i = 0; i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgprs.size(); i++) { ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg + i); } } } return data; } const EmbeddedFetchLoad* FindEmbeddedFetchLoad(const std::vector& loads, uint32_t pc) { for (const auto& load: loads) { if (load.pc == pc) { return &load; } } return nullptr; } bool EmbeddedFetchPcInList(const std::vector& pcs, uint32_t pc) { return std::find(pcs.begin(), pcs.end(), pc) != pcs.end(); } bool IsIrFetchPrologLoad(const IR::Instruction& inst) { return inst.op == IR::Opcode::SLoadDword || inst.op == IR::Opcode::SBufferLoadDword; } int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo* input_info, const EmbeddedFetchLoad& load) { if (input_info == nullptr) { return -1; } if (load.attrib_id >= 0 && load.attrib_id < input_info->resources_num && input_info->resources_dst[load.attrib_id].attr_id == load.attrib_id) { return load.attrib_id; } for (int i = 0; i < input_info->resources_num; i++) { const auto& dst = input_info->resources_dst[i]; if (dst.attr_id == load.attrib_id && load.components <= static_cast(std::max(dst.registers_num, 1))) { return i; } } for (int i = 0; i < input_info->resources_num; i++) { if (input_info->resources_dst[i].attr_id == load.attrib_id) { return i; } } return -1; } uint32_t RewriteEmbeddedVertexFetches(IR::Program& ir, const ShaderVertexInputInfo* input_info, const std::vector& loads) { if (input_info == nullptr || loads.empty()) { return 0; } std::vector prolog_pcs; prolog_pcs.reserve(loads.size()); for (const auto& load: loads) { AppendUniquePcs(prolog_pcs, load.prolog_loads); } auto* mutable_input_info = const_cast(input_info); uint32_t rewritten = 0; for (auto& block: ir.blocks) { for (auto& inst: block.instructions) { if (IsIrFetchPrologLoad(inst) && EmbeddedFetchPcInList(prolog_pcs, inst.pc)) { auto pc = inst.pc; inst = {}; inst.pc = pc; inst.op = IR::Opcode::ControlNop; continue; } if (inst.op != IR::Opcode::BufferLoadDword) { continue; } const auto* load = FindEmbeddedFetchLoad(loads, inst.pc); if (load == nullptr || inst.memory.component_index >= load->components) { continue; } const auto resource_id = ResolveEmbeddedFetchResource(input_info, *load); if (resource_id < 0 || resource_id >= input_info->resources_num) { LOGF("ShaderRecompiler VS embedded fetch remap failed: pc=0x%08" PRIx32 " attrib=%d resources=%d\n", inst.pc, load->attrib_id, input_info->resources_num); continue; } inst.op = IR::Opcode::LoadInputF32; inst.input_info.attr = static_cast(resource_id); inst.input_info.chan = inst.memory.component_index; inst.memory = {}; inst.src_count = 0; mutable_input_info->resource_fetch_components[resource_id] = std::max(mutable_input_info->resource_fetch_components[resource_id], static_cast(inst.input_info.chan + 1u)); rewritten++; } } return rewritten; } } // namespace bool TryRecompile(std::span code, const CompileOptions& options, CompileResult& result, std::string* error) { if (code.empty()) { if (error != nullptr) { *error = "invalid shader recompiler input"; } return false; } if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex && options.stage != ShaderType::Pixel) { if (error != nullptr) { *error = "shader recompiler supports compute, vertex, and pixel stages"; } return false; } const auto compile_begin = std::chrono::steady_clock::now(); const auto phase_ms = [&compile_begin]() { return static_cast(std::chrono::duration_cast( std::chrono::steady_clock::now() - compile_begin) .count()); }; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " code_words=%" PRIu64 " decode\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(code.size())); Decoder::Program decoded; if (!Decoder::DecodeProgram(code, decoded, error)) { return false; } LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(decoded.instructions.size()), phase_ms()); std::string decoded_dump; if (options.dump_ir) { decoded_dump = Decoder::ProgramToString(decoded); if (options.early_dump) { LOGF("%s decoded RDNA2 (early):\n%s", GetDumpLabel(options), decoded_dump.c_str()); } } CFG::Graph cfg; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); if (!CFG::BuildGraph(decoded, cfg, error)) { return false; } LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(cfg.blocks.size()), static_cast(cfg.natural_loops.size()), static_cast(cfg.back_edges.size()), phase_ms()); bool dispatcher_fallback = false; std::string dispatcher_reason; if (cfg.irreducible) { dispatcher_fallback = true; dispatcher_reason = cfg.unsupported_reason; const auto diagnostic = FormatCfgFailure(cfg, options, cfg.unsupported_reason); LOGF("%s irreducible CFG detected: %s\n", GetDumpLabel(options), diagnostic.c_str()); } else { std::string structure_error; const auto unstructured_cfg = cfg; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); if (!CFG::Structurize(cfg, &structure_error)) { const auto diagnostic = FormatCfgFailure(cfg, options, structure_error); LOGF("%s structured CFG bug/failure: %s\n", GetDumpLabel(options), diagnostic.c_str()); dispatcher_fallback = true; dispatcher_reason = structure_error; const auto failure_kind = cfg.failure_kind; const auto failure_block = cfg.failure_block; cfg = unstructured_cfg; cfg.unsupported = true; cfg.failure_kind = failure_kind; cfg.failure_block = failure_block; cfg.unsupported_reason = structure_error; } else { LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options), static_cast(cfg.blocks.size())); } LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64 " loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(cfg.blocks.size()), static_cast(cfg.natural_loops.size()), phase_ms()); } IR::Program ir; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR LowerProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); if (!IR::LowerProgram(decoded, cfg, options.stage, options.wave_size, ir, error)) { return false; } ir.lane_mask_mode = options.lane_mask_mode; ir.shader_hash = options.shader_hash; ir.user_data_base = options.user_data_base; ir.user_data_count = options.user_data_count; LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR LowerProgram blocks=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(ir.blocks.size()), phase_ms()); EmbeddedFetchData embedded_fetch; if (options.stage == ShaderType::Vertex && options.vertex_input_info != nullptr && options.vertex_input_info->fetch_embedded) { embedded_fetch = DetectEmbeddedVertexFetch(decoded, options.vertex_input_info, ir.user_data_base, ir.user_data_count, options.wave_size); auto rewritten = RewriteEmbeddedVertexFetches(ir, options.vertex_input_info, embedded_fetch.loads); if (rewritten > 0 || !embedded_fetch.loads.empty()) { LOGF("%s embedded vertex fetch rewrite: detected=%" PRIu64 " rewritten=%" PRIu32 "\n", GetDumpLabel(options), static_cast(embedded_fetch.loads.size()), rewritten); } } if (!IR::BuildScalarProvenance(ir, error)) { return false; } std::string srt_error; if (!IR::BuildSrtPlan(ir, &srt_error)) { LOGF("%s SRT planning failed: %s\n", GetDumpLabel(options), srt_error.c_str()); if (error != nullptr) { *error = std::move(srt_error); } return false; } ir.dispatcher_fallback = dispatcher_fallback; if (!dispatcher_reason.empty()) { ir.fallback_reason = dispatcher_reason; } if (!IR::PatchSrtReads(ir, error) || !IR::TrackResources(ir, error)) { return false; } if (options.stage == ShaderType::Vertex) { ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr; } if (!dispatcher_fallback) { std::string emitter_reason; if (NeedsDispatcherForStructuredLoopHeader(ir, &emitter_reason)) { dispatcher_fallback = true; dispatcher_reason = emitter_reason; ir.dispatcher_fallback = true; ir.cfg_failure_kind = CFG::FailureKind::StructuredControlFlow; ir.fallback_reason = emitter_reason; LOGF("%s structured CFG emitter fallback: %s\n", GetDumpLabel(options), emitter_reason.c_str()); } } IR::ResourceSnapshot resources; if (options.resource_snapshot != nullptr) { resources = *options.resource_snapshot; } else { IR::SrtRuntime runtime; std::array zero_user_data {}; if (options.user_data == nullptr) { for (uint32_t i = 2; i < zero_user_data.size(); i += 4) { zero_user_data[i] = UINT32_MAX; } } const auto* user_data = options.user_data != nullptr ? options.user_data : zero_user_data.data(); runtime.user_data = std::span(user_data, options.user_data_count); runtime.shader_base = options.shader_base != 0 ? options.shader_base : reinterpret_cast(code.data()); runtime.read_memory = options.read_memory; if (runtime.read_memory == nullptr && options.user_data == nullptr) { runtime.read_memory = ReadZeroMemory; } runtime.userdata = options.read_memory_data; runtime.flat_memory_base = options.flat_memory_base; if (!IR::MaterializeResources(ir, runtime, resources, error)) { return false; } } if (!IR::SpecializeResources(ir, resources, error)) { return false; } ShaderVertexInputInfo default_vertex {}; ShaderPixelInputInfo default_pixel {}; ShaderComputeInputInfo default_compute {}; IR::ShaderInfoOptions info_options; info_options.vertex = options.vertex_input_info != nullptr ? options.vertex_input_info : &default_vertex; info_options.pixel = options.pixel_input_info != nullptr ? options.pixel_input_info : &default_pixel; info_options.compute = options.compute_input_info != nullptr ? options.compute_input_info : &default_compute; if (!IR::CollectShaderInfo(ir, info_options, error)) { return false; } IR::BindingLayoutOptions layout_options; layout_options.descriptor_set = options.descriptor_set; layout_options.push_constant_offset = options.push_constant_offset; if (!IR::AllocateBindings(ir, layout_options, error)) { return false; } const auto read_lane_stats = IR::EliminateReadLane(ir); if (read_lane_stats.rewritten_reads != 0) { LOGF("%s read-lane elimination: reads=%" PRIu32 " shadow_writes=%" PRIu32 "\n", GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.shadow_writes); } std::string ir_dump; if (options.dump_ir) { ir_dump = MakeIrDump(cfg, ir); if (options.early_dump) { LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str()); } } std::vector spirv; std::string emit_error; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); if (!Spirv::EmitProgram(ir, resources, options.vertex_input_info, options.pixel_input_info, options.compute_input_info, spirv, &emit_error)) { if (dispatcher_fallback && error != nullptr) { *error = fmt::format("dispatcher fallback failed after {}: {}", dispatcher_reason.c_str(), emit_error.c_str()); LOGF("%s dispatcher fallback emit failed: %s\n", GetDumpLabel(options), error->c_str()); } else if (error != nullptr) { *error = emit_error; } return false; } LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(spirv.size()), phase_ms()); if (dispatcher_fallback) { LOGF("%s dispatcher fallback used: %s\n", GetDumpLabel(options), dispatcher_reason.c_str()); } result.spirv = std::move(spirv); result.program = std::move(ir); result.resources = std::move(resources); if (options.dump_ir) { result.decoded_dump = std::move(decoded_dump); result.ir_dump = std::move(ir_dump); } else { result.decoded_dump.clear(); result.ir_dump.clear(); } return true; } } // namespace Libs::Graphics::ShaderRecompiler