#include "graphics/shader/recompiler/Tessellation.h" #include "common/assert.h" #include "common/logging/log.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include #include #include #include namespace Libs::Graphics::ShaderRecompiler { namespace { struct TessellationAddress { enum class Kind { Unknown, Affine, PackedControlPoint }; Kind kind = Kind::Unknown; uint32_t coefficient = 0; uint32_t constant = 0; }; uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, uint32_t control_points, uint32_t input_stride) { using namespace Decoder; using Address = TessellationAddress; using Kind = Address::Kind; std::array registers {}; registers[local ? 3u : 1u] = local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint}; const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; }; const auto read = [&](const Operand& operand) -> Address { if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel || operand.op_sel_hi || operand.negate_hi || operand.dpp) return {}; Address value; if (operand.kind == OperandKind::IntegerInlineConstant || operand.kind == OperandKind::LiteralConstant) { value = constant(operand.value); } else if (operand.kind == OperandKind::Vgpr) { value = registers.at(operand.reg); } // The native packed HS ID's low byte is the patch ordinal. The logical // interface addresses one patch, as in the frontend's v1 initialization. if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u); return operand.sdwa_sel == 6u ? value : Address {}; }; const auto affine = [](uint64_t coefficient, uint64_t offset) { return coefficient <= UINT32_MAX && offset <= UINT32_MAX ? Address {Kind::Affine, static_cast(coefficient), static_cast(offset)} : Address {}; }; const auto add = [&](Address lhs, Address rhs) { return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine ? affine(uint64_t {lhs.coefficient} + rhs.coefficient, uint64_t {lhs.constant} + rhs.constant) : Address {}; }; const auto multiply = [&](Address lhs, Address rhs) { if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) || (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u)) return constant(0u); if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {}; if (rhs.coefficient != 0u) std::swap(lhs, rhs); return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant, uint64_t {lhs.constant} * rhs.constant) : Address {}; }; const auto low24 = [&](Address value) { if (value.kind == Kind::Affine && value.coefficient == 0u) return constant(value.constant & 0xffffffu); return value.kind == Kind::Affine && uint64_t {value.coefficient} * (control_points - 1u) + value.constant <= 0xffffffu ? value : Address {}; }; uint32_t stride = 0; for (const auto& inst: program.instructions) { // Stage exits preserve the active path's definitions. An internal join // would require merging register definitions. EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) && inst.branch_target != program.instructions.back().pc); const bool local_store = inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32; const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD || inst.opcode == Opcode::BUFFER_STORE_DWORDX2 || inst.opcode == Opcode::BUFFER_STORE_DWORDX3 || inst.opcode == Opcode::BUFFER_STORE_DWORDX4; const bool control_store = !local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u; const bool control_read = !local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32); if ((local && local_store) || control_store || control_read) { const auto address = read(inst.src0); if (address.kind != Kind::Affine) { EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS", inst.pc); } if (address.coefficient != 0u) { const auto expected = control_read ? input_stride : stride; EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u || (expected != 0u && expected != address.coefficient)); if (!control_read) stride = address.coefficient; } } // Store vdata is a source, despite occupying the decoder's dst field. if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue; const auto lhs = read(inst.src0); const auto rhs = read(inst.src1); const auto third = read(inst.src2); Address value; switch (inst.opcode) { case Opcode::V_BFE_U32: if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine && rhs.coefficient == 0u && third.kind == Kind::Affine && third.coefficient == 0u) { if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u}; if (rhs.constant == 0u && third.constant == 8u) value = constant(0u); } break; case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break; case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break; case Opcode::V_LSHL_ADD_U32: if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u) value = add(multiply(lhs, constant(1u << rhs.constant)), third); break; case Opcode::V_LSHLREV_B32: if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u) value = multiply(rhs, constant(1u << lhs.constant)); break; case Opcode::V_SUB_NC_U32: if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine && lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant) value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant); break; default: break; } if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp || inst.dst.dpp) value = {}; for (uint32_t index = 0; index < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) { registers[inst.dst.reg + index] = {}; } registers.at(inst.dst.reg) = value; } EXIT_NOT_IMPLEMENTED(stride == 0u); return stride; } const IR::Inst* TessellationBufferBase(const IR::Inst& inst) { if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u) return nullptr; const auto* base = inst.Arg(3).Resolve().TryInstruction(); return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base : nullptr; } IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value, IR::Value predicate, std::unordered_map& resolved) { using namespace IR; value = value.Resolve(); auto* source = value.TryInstruction(); if (source == nullptr) return value; if (const auto found = resolved.find(source); found != resolved.end()) return found->second; if (source->GetOpcode() == ValueOpcode::SelectU32) { return source->Arg(0).Resolve() == predicate.Resolve() ? ActiveAddress(block, before, source->Arg(1), predicate, resolved) : value; } if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32) return value; const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved); const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved); if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) { auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs}, source->Flags()); value = Value(&*copy); } resolved.emplace(source, value); return value; } } // namespace void AnalyzeTessellationPrograms(std::span local, std::span control, ShaderTessellationInputInfo& info) { const auto local_program = Decoder::DecodeFrontProgram(local); Decoder::Program control_program; Decoder::DecodeProgram(control, control_program); info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u); info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points, info.ls_stride); LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n", info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride); } void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) { using namespace IR; if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl && options.stage != ShaderType::TessellationEvaluation) { return; } const auto& tess = options.input_info.vertex->tess; // Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to // a store guarded by the same EXEC predicate, but must remain intact elsewhere. for (auto* block: program.blocks) { for (auto it = block->begin(); it != block->end(); ++it) { if (TessellationBufferBase(*it) == nullptr) continue; std::unordered_map resolved; it->SetArg( 2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved)); } } ConstantPropagationPass(program.blocks); uint32_t reads = 0, writes = 0, factors = 0; for (auto* block: program.blocks) { for (auto it = block->begin(); it != block->end(); ++it) { auto& inst = *it; const auto shared = SharedAccessOf(inst.GetOpcode()); const auto buffer = BufferAccessOf(inst.GetOpcode()); if (shared == SharedAccess::None && buffer == BufferAccess::None) { continue; } const auto& memory = program.memory_info.at(inst.Flags().index); bool write = false; uint32_t components = 0; TessellationAttribute kind; Value address, predicate; if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) { write = shared == SharedAccess::Write; EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || (options.stage == ShaderType::Local ? !write : options.stage != ShaderType::TessellationControl || shared != SharedAccess::Read)); kind = write ? TessellationAttribute::LocalOutput : TessellationAttribute::ControlInput; components = SharedComponentCount(inst.GetOpcode()); address = inst.Arg(0); predicate = inst.Arg(inst.NumArgs() - 1u); } else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) { const auto* base = TessellationBufferBase(inst); if (base == nullptr) { continue; } EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen || !memory.offen || buffer == BufferAccess::Atomic); write = buffer == BufferAccess::Write; components = BufferComponentCount(inst.GetOpcode()); address = inst.Arg(2).Resolve(); predicate = inst.Arg(inst.NumArgs() - 1u); if (base->Arg(0).U32() == 1u) { EXIT_NOT_IMPLEMENTED(!write || options.stage != ShaderType::TessellationControl); kind = TessellationAttribute::Factor; factors += components; } else { kind = write ? TessellationAttribute::ControlOutput : TessellationAttribute::EvaluationInput; EXIT_NOT_IMPLEMENTED(write ? options.stage != ShaderType::TessellationControl : options.stage != ShaderType::TessellationEvaluation); if (address.IsImmediate() && address.U32() >= tess.hs_stride * tess.output_control_points) { EXIT_NOT_IMPLEMENTED(!write); kind = TessellationAttribute::PatchOutput; } } } else { continue; } const auto emit = [&](ValueOpcode opcode, std::initializer_list args) { return Value(&*block->PrependNewInst(it, opcode, args)); }; std::array values; for (uint32_t component = 0; component < components; component++) { const auto offset = memory.offset + 4u * component; const auto byte_address = offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)}); if (write) { Value data = shared != SharedAccess::None ? inst.Arg(component + 1u) : components == 1u ? inst.Arg(4) : emit(components == 2u ? ValueOpcode::CompositeExtractU32x2 : components == 3u ? ValueOpcode::CompositeExtractU32x3 : ValueOpcode::CompositeExtractU32x4, {inst.Arg(4), Value(component)}); emit(ValueOpcode::SetTessellationAttribute, {Value(static_cast(kind)), byte_address, data, predicate}); writes++; } else { values[component] = emit(ValueOpcode::GetTessellationAttribute, {Value(static_cast(kind)), byte_address, predicate}); reads++; } } if (!write) { Value replacement = values[0]; if (components == 2u) replacement = emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]}); if (components == 3u) replacement = emit(ValueOpcode::CompositeConstructU32x3, {values[0], values[1], values[2]}); if (components == 4u) replacement = emit(ValueOpcode::CompositeConstructU32x4, {values[0], values[1], values[2], values[3]}); inst.ReplaceUsesWith(replacement); } inst.Invalidate(); } } ConstantPropagationPass(program.blocks); RemoveIdentities(program.blocks); EliminateDeadCode(program.blocks); LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n", options.stage == ShaderType::Local ? "LS" : options.stage == ShaderType::TessellationControl ? "HS" : "TES", reads, writes, factors); } } // namespace Libs::Graphics::ShaderRecompiler