mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
892 lines
32 KiB
C++
892 lines
32 KiB
C++
#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 <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <fmt/format.h>
|
|
#include <map>
|
|
#include <span>
|
|
#include <utility>
|
|
|
|
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<uint32_t> prolog_loads;
|
|
};
|
|
|
|
struct EmbeddedFetchVgprInfo {
|
|
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
|
|
};
|
|
|
|
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
|
|
|
|
uint64_t EmbeddedFetchVectorLaneKey(uint32_t reg, uint32_t lane) {
|
|
return (static_cast<uint64_t>(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<uint32_t> prolog_loads;
|
|
};
|
|
|
|
struct EmbeddedFetchData {
|
|
std::vector<EmbeddedFetchLoad> 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<EmbeddedFetchSgprInfo, 108>& 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<EmbeddedFetchSgprInfo, 108>& 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<EmbeddedFetchSgprInfo, 108>& 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<uint64_t>(base) + inst.offset;
|
|
if (value > 0xffffffffull) {
|
|
return false;
|
|
}
|
|
raw_offset = static_cast<uint32_t>(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<int>((raw_offset + static_cast<uint32_t>(dword) * 4u) / 16u);
|
|
}
|
|
|
|
void AppendUniquePcs(std::vector<uint32_t>& dst, const std::vector<uint32_t>& 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<EmbeddedFetchSgprInfo, 108> sgprs {};
|
|
std::array<EmbeddedFetchVgprInfo, 256> 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<int>(sgprs.size())) {
|
|
sgprs[attrib_reg].type = EmbeddedFetchValueType::AttribTable;
|
|
}
|
|
if (attrib_reg + 1 >= 0 && attrib_reg + 1 < static_cast<int>(sgprs.size())) {
|
|
sgprs[attrib_reg + 1].type = EmbeddedFetchValueType::AttribTable;
|
|
}
|
|
if (buffer_reg >= 0 && buffer_reg < static_cast<int>(sgprs.size())) {
|
|
sgprs[buffer_reg].type = EmbeddedFetchValueType::BufferTable;
|
|
}
|
|
if (buffer_reg + 1 >= 0 && buffer_reg + 1 < static_cast<int>(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<int32_t>(reg)) {
|
|
offset_conflict = true;
|
|
} else {
|
|
offset_candidate = static_cast<int32_t>(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<int>(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<int>(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<int>(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<EmbeddedFetchLoad>& loads,
|
|
uint32_t pc) {
|
|
for (const auto& load: loads) {
|
|
if (load.pc == pc) {
|
|
return &load;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool EmbeddedFetchPcInList(const std::vector<uint32_t>& 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<uint32_t>(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<EmbeddedFetchLoad>& loads) {
|
|
if (input_info == nullptr || loads.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
std::vector<uint32_t> prolog_pcs;
|
|
prolog_pcs.reserve(loads.size());
|
|
for (const auto& load: loads) {
|
|
AppendUniquePcs(prolog_pcs, load.prolog_loads);
|
|
}
|
|
|
|
auto* mutable_input_info = const_cast<ShaderVertexInputInfo*>(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<uint32_t>(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<int>(inst.input_info.chan + 1u));
|
|
rewritten++;
|
|
}
|
|
}
|
|
|
|
return rewritten;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool TryRecompile(std::span<const uint32_t> 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<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
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<uint64_t>(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<uint64_t>(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<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
|
static_cast<uint64_t>(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<uint64_t>(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<uint64_t>(cfg.blocks.size()),
|
|
static_cast<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint32_t, 64> 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<const uint32_t>(user_data, options.user_data_count);
|
|
runtime.shader_base = options.shader_base != 0 ? options.shader_base
|
|
: reinterpret_cast<uint64_t>(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<uint32_t> 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<uint64_t>(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
|