#include "graphics/shader/recompiler/ShaderRecompiler.h" #include "common/assert.h" #include "common/logging/log.h" #include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h" #include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/frontend/translate/Translate.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/BindingLayout.h" #include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" #include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h" #include "graphics/shader/recompiler/ir/passes/SharedMemoryBarrier.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include "graphics/shader/recompiler/ir/passes/SsaRewrite.h" #include #include #include #include #include #include #include namespace Libs::Graphics::ShaderRecompiler { namespace { const char* GetDumpLabel(const CompileOptions& options) { return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler"; } std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) { std::string dump = "CFG:\n"; dump += cfg; dump += "\nIR:\n"; dump += fmt::format("mode={} scratch_dwords={}\n", ir.dispatcher_fallback ? "dispatcher" : "structured", ir.scratch_dwords); 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"; } } void LogDispatcherFallback(const CompileOptions& options, const CFG::Graph& cfg, const char* phase, const std::string& reason) { const auto* block = cfg.FindBlock(cfg.failure_block); const auto start = block != nullptr ? block->start_pc : UINT32_MAX; const auto end = block != nullptr ? block->end_pc : UINT32_MAX; const auto predecessors = block != nullptr ? block->predecessors.size() : 0u; const auto successors = block != nullptr ? block->successors.size() : 0u; LOGF("%s CFG dispatcher fallback: stage=%s hash=0x%016" PRIx64 " phase=%s failure=%s block=%" PRIu32 " pc=0x%08" PRIx32 "..0x%08" PRIx32 " preds=%" PRIu64 " succs=%" PRIu64 " blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64 " reason=%s\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, phase, CFG::FailureKindToString(cfg.failure_kind).c_str(), cfg.failure_block, start, end, static_cast(predecessors), static_cast(successors), static_cast(cfg.blocks.size()), static_cast(cfg.natural_loops.size()), static_cast(cfg.back_edges.size()), reason.c_str()); } 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); } using EmbeddedFetchLoad = Frontend::EmbeddedFetchLoad; using EmbeddedFetchData = Frontend::EmbeddedFetchPlan; 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::V_MAD_U64_U32 ? 2u : DecodedDstSize(inst); } bool EmbeddedFetchHasBranch(Decoder::Opcode opcode) { switch (opcode) { case Decoder::Opcode::S_SETPC_B64: case Decoder::Opcode::S_BRANCH: case Decoder::Opcode::S_CBRANCH_SCC0: case Decoder::Opcode::S_CBRANCH_SCC1: case Decoder::Opcode::S_CBRANCH_VCCZ: case Decoder::Opcode::S_CBRANCH_VCCNZ: case Decoder::Opcode::S_CBRANCH_EXECZ: case Decoder::Opcode::S_CBRANCH_EXECNZ: 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::S_LOAD_DWORD: case Decoder::Opcode::S_LOAD_DWORDX2: case Decoder::Opcode::S_LOAD_DWORDX4: case Decoder::Opcode::S_LOAD_DWORDX8: case Decoder::Opcode::S_LOAD_DWORDX16: return true; default: return false; } } bool IsEmbeddedFetchBufferLoad(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::BUFFER_LOAD_FORMAT_X: case Decoder::Opcode::BUFFER_LOAD_FORMAT_XY: case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ: case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW: return true; default: return false; } } bool IsEmbeddedFetchAttribPropagationAlu(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::S_BFE_U32: case Decoder::Opcode::S_AND_B32: case Decoder::Opcode::S_ADD_I32: case Decoder::Opcode::S_ADD_U32: case Decoder::Opcode::S_LSHL_B32: return true; default: return false; } } int BufferTableAttribFromOffset(uint32_t raw_offset, int dword) { return static_cast((raw_offset + static_cast(dword) * 4u) / 16u); } 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; data.loads.reserve(input_info->resources_num); int32_t vertex_offset_candidate = -1; int32_t instance_offset_candidate = -1; bool vertex_offset_conflict = false; bool instance_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 S_NGG_INSTANCE_INDEX in v8, then applies the // corresponding direct-draw offsets before fetching. const bool vertex_index_accumulator = IsDecodedVgpr(inst.dst) && (inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5)); const bool instance_index_accumulator = IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u)); uint32_t sad_zero = 0; const bool index_offset_add = (vertex_index_accumulator || instance_index_accumulator) && IsDecodedSgpr(inst.src0) && ((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) && inst.src1.reg == inst.dst.reg) || (user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) && inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) && inst.src2.reg == inst.dst.reg && TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0)); if (data.loads.empty() && index_offset_add) { const auto reg = DecodedSgprReg(inst.src0); if (reg >= user_data_base && reg - user_data_base < user_data_count) { auto& candidate = vertex_index_accumulator ? vertex_offset_candidate : instance_offset_candidate; auto& conflict = vertex_index_accumulator ? vertex_offset_conflict : instance_offset_conflict; if (candidate >= 0 && candidate != static_cast(reg)) { conflict = true; } else { candidate = static_cast(reg); } } } switch (inst.opcode) { case Decoder::Opcode::V_WRITELANE_B32: { 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::V_READLANE_B32: { 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::S_MOV_B32: 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::S_MOVK_I32: 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::V_CNDMASK_B32) { 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::S_AND_B32: dst.value = src0 & src1; break; case Decoder::Opcode::S_LSHL_B32: dst.value = src0 << (src1 & 31u); break; case Decoder::Opcode::S_BFE_U32: 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()) { if (!vertex_offset_conflict) { data.vertex_offset_sgpr = vertex_offset_candidate; } if (!instance_offset_conflict) { data.instance_offset_sgpr = instance_offset_candidate; } } data.loads.push_back(load); } } break; } if (inst.opcode == Decoder::Opcode::V_MOVRELD_B32) { vector_lanes.clear(); } else if (inst.opcode != Decoder::Opcode::V_WRITELANE_B32 && 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; } } // namespace TranslateResult TranslateProgram(std::span code, const CompileOptions& options) { if (code.empty()) { EXIT("shader recompiler input is empty\n"); } if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex && options.stage != ShaderType::Pixel) { EXIT("shader recompiler received unsupported stage %u\n", static_cast(options.stage)); } 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; Decoder::DecodeProgram(code, decoded); 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()); } } LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); auto cfg = CFG::BuildGraph(decoded); 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; LogDispatcherFallback(options, cfg, "build", dispatcher_reason); } else { 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)) { dispatcher_fallback = true; dispatcher_reason = cfg.unsupported_reason; const auto failure_kind = cfg.failure_kind; const auto failure_block = cfg.failure_block; LogDispatcherFallback(options, cfg, "structurize", dispatcher_reason); cfg = unstructured_cfg; cfg.unsupported = true; cfg.failure_kind = failure_kind; cfg.failure_block = failure_block; cfg.unsupported_reason = dispatcher_reason; } 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()); } const ShaderVertexInputInfo* vertex = nullptr; const ShaderPixelInputInfo* pixel = nullptr; const ShaderComputeInputInfo* compute = nullptr; switch (options.stage) { case ShaderType::Vertex: vertex = options.input_info.vertex; break; case ShaderType::Pixel: pixel = options.input_info.pixel; break; case ShaderType::Compute: compute = options.input_info.compute; break; default: break; } EmbeddedFetchData embedded_fetch; if (options.stage == ShaderType::Vertex && vertex->fetch_embedded) { embedded_fetch = DetectEmbeddedVertexFetch(decoded, vertex, options.user_data_base, static_cast(options.user_data.size()), options.wave_size); if (!embedded_fetch.loads.empty()) { LOGF("%s embedded vertex fetch plan: detected=%" PRIu64 "\n", GetDumpLabel(options), static_cast(embedded_fetch.loads.size())); } } Frontend::TranslateOptions translate_options { .stage = options.stage, .wave_size = options.wave_size, .shader_hash = options.shader_hash, .user_data_base = options.user_data_base, .user_data_count = static_cast(options.user_data.size()), .scratch_dwords = options.scratch_dwords, .dispatcher_fallback = dispatcher_fallback, .cfg_failure_kind = cfg.failure_kind, .fallback_reason = dispatcher_reason.empty() ? cfg.unsupported_reason : dispatcher_reason, .vertex = vertex, .pixel = pixel, .compute = compute, .embedded_fetch = embedded_fetch.loads.empty() ? nullptr : &embedded_fetch, }; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(ir.blocks.size()), phase_ms()); IR::RewriteToSsa(ir.blocks); IR::ConstantPropagationPass(ir.blocks); IR::ResolveControlFlowIdentities(ir); IR::RemoveIdentities(ir.blocks); IR::EliminateDeadCode(ir.blocks); const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size); if (read_lane_stats.rewritten_reads != 0) { LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options), read_lane_stats.rewritten_reads); IR::ConstantPropagationPass(ir.blocks); IR::ResolveControlFlowIdentities(ir); IR::RemoveIdentities(ir.blocks); IR::EliminateDeadCode(ir.blocks); } if (options.stage == ShaderType::Compute) { const auto lds_barriers = IR::InsertSharedMemoryBarriers(ir, ir.wave_size, *compute); if (lds_barriers.inserted_barriers != 0) { LOGF("%s wave64 LDS synchronization: barriers=%" PRIu32 "\n", GetDumpLabel(options), lds_barriers.inserted_barriers); } } IR::BuildSrtPlan(ir); IR::EliminateDeadCode(ir.blocks); IR::TrackResources(ir); IR::EliminateDeadCode(ir.blocks); if (options.stage == ShaderType::Vertex) { ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr; ir.info.instance_offset_sgpr = embedded_fetch.instance_offset_sgpr; } TranslateResult result; result.program = std::move(ir); if (options.dump_ir) { result.decoded_dump = std::move(decoded_dump); result.cfg_dump = CFG::GraphToString(cfg); } return result; } CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options, const IR::ResourceSpecialization& specialization, uint32_t push_data_start_dword) { const auto emit_begin = std::chrono::steady_clock::now(); auto& ir = translated.program; IR::ApplyResourceSpecialization(ir, specialization); const ShaderVertexInputInfo* vertex = nullptr; const ShaderPixelInputInfo* pixel = nullptr; const ShaderComputeInputInfo* compute = nullptr; switch (options.stage) { case ShaderType::Vertex: vertex = options.input_info.vertex; break; case ShaderType::Pixel: pixel = options.input_info.pixel; break; case ShaderType::Compute: compute = options.input_info.compute; break; default: EXIT("invalid shader stage\n"); } IR::ShaderInfoOptions info_options; info_options.vertex = vertex; info_options.pixel = pixel; info_options.compute = compute; IR::CollectShaderInfo(ir, info_options); IR::AllocateBindings(ir, push_data_start_dword); Spirv::AnalyzeProgramRequirements(ir); std::string ir_dump; if (options.dump_ir) { ir_dump = MakeIrDump(translated.cfg_dump, ir); if (options.early_dump) { LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str()); } } LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); auto spirv = Spirv::EmitProgram(ir, options.input_info); 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()), static_cast(std::chrono::duration_cast( std::chrono::steady_clock::now() - emit_begin) .count())); CompileResult result; result.spirv = std::move(spirv); result.program = std::move(ir); if (options.dump_ir) { result.decoded_dump = std::move(translated.decoded_dump); result.ir_dump = std::move(ir_dump); } return result; } } // namespace Libs::Graphics::ShaderRecompiler