From 87971a28f5d48fbcbae90aa74e6921728a86edac Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 13 Sep 2026 09:59:24 +0400 Subject: [PATCH] gpu: isolate tessellation analysis and SPIR-V emission --- .../shader/recompiler/ShaderRecompiler.cpp | 335 +----------------- .../shader/recompiler/ShaderRecompiler.h | 3 - .../shader/recompiler/Tessellation.cpp | 330 +++++++++++++++++ src/graphics/shader/recompiler/Tessellation.h | 21 ++ .../backend/spirv/spirvEmitterFlow.cpp | 76 ---- .../backend/spirv/spirvEmitterInternal.h | 4 + .../backend/spirv/spirvEmitterModule.cpp | 81 +---- .../spirv/spirvEmitterTessellation.cpp | 161 +++++++++ .../frontend/decode/ShaderDecoder.cpp | 19 + .../frontend/decode/ShaderDecoder.h | 1 + src/graphics/shader/shader.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 1 + 12 files changed, 544 insertions(+), 489 deletions(-) create mode 100644 src/graphics/shader/recompiler/Tessellation.cpp create mode 100644 src/graphics/shader/recompiler/Tessellation.h create mode 100644 src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index f34827951..05b03c915 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -1,4 +1,5 @@ #include "graphics/shader/recompiler/ShaderRecompiler.h" +#include "graphics/shader/recompiler/Tessellation.h" #include "common/assert.h" #include "common/logging/log.h" @@ -465,30 +466,10 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded return data; } -Decoder::Program DecodeFrontProgram(std::span front) { - Decoder::Program result; - uint32_t front_words = 0; - while (front_words < front.size()) { - auto& inst = result.instructions.emplace_back(); - Decoder::DecodeInstruction(front, front_words, inst); - front_words += inst.word_count; - if (inst.opcode == Decoder::Opcode::S_SETPC_B64) { - EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr || - inst.src0.reg != 6u); - break; - } - EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM); - } - EXIT_IF(result.instructions.empty() || - result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64); - result.code = front.first(front_words); - return result; -} - Decoder::Program DecodeFusedProgram(std::span front, std::span back, std::vector& joined_code) { EXIT_IF(back.empty()); - auto result = DecodeFrontProgram(front); + auto result = Decoder::DecodeFrontProgram(front); const auto front_words = static_cast(result.code.size()); joined_code.assign(result.code.begin(), result.code.end()); joined_code.insert(joined_code.end(), back.begin(), back.end()); @@ -511,316 +492,6 @@ Decoder::Program DecodeFusedProgram(std::span front, std::span 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 < DecodedDstSize(inst) && 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; -} - -} // namespace - -void AnalyzeTessellationPrograms(std::span local, std::span control, - ShaderTessellationInputInfo& info) { - const auto local_program = 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); -} - -namespace { - -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; -} - -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", StageName(options.stage), - reads, writes, factors); -} - } // namespace TranslateResult TranslateProgram(std::span code, const CompileOptions& options) { @@ -851,7 +522,7 @@ TranslateResult TranslateProgram(std::span code, const CompileOp if (!options.back_code.empty()) { decoded = DecodeFusedProgram(code, options.back_code, joined_code); } else if (options.stage == ShaderType::Local) { - decoded = DecodeFrontProgram(code); + decoded = Decoder::DecodeFrontProgram(code); // The separately compiled hull half runs in the next Vulkan stage. auto& handoff = decoded.instructions.back(); handoff.opcode = Decoder::Opcode::S_ENDPGM; diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index 8b096eee4..29b3a40e7 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -37,9 +37,6 @@ struct CompileResult { IR::Program program; }; -void AnalyzeTessellationPrograms(std::span local, std::span control, - ShaderTessellationInputInfo& info); - [[nodiscard]] TranslateResult TranslateProgram(std::span code, const CompileOptions& options); [[nodiscard]] CompileResult CompileProgram(TranslateResult translated, diff --git a/src/graphics/shader/recompiler/Tessellation.cpp b/src/graphics/shader/recompiler/Tessellation.cpp new file mode 100644 index 000000000..cd436259c --- /dev/null +++ b/src/graphics/shader/recompiler/Tessellation.cpp @@ -0,0 +1,330 @@ +#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 diff --git a/src/graphics/shader/recompiler/Tessellation.h b/src/graphics/shader/recompiler/Tessellation.h new file mode 100644 index 000000000..63186df84 --- /dev/null +++ b/src/graphics/shader/recompiler/Tessellation.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace Libs::Graphics { +struct ShaderTessellationInputInfo; +} + +namespace Libs::Graphics::ShaderRecompiler { + +struct CompileOptions; +namespace IR { +struct Program; +} + +void AnalyzeTessellationPrograms(std::span local, std::span control, + ShaderTessellationInputInfo& info); +void LowerTessellationMemory(IR::Program& program, const CompileOptions& options); + +} // namespace Libs::Graphics::ShaderRecompiler diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 039c5d32a..629e5ad52 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -533,82 +533,6 @@ uint32_t EmitLaneId(EmitterState& state) { : EmitSubgroupLocalInvocationId(state); } -namespace { - -uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { - auto& state = ctx.state; - using Attribute = IR::TessellationAttribute; - const auto kind = static_cast(inst.Arg(0).U32()); - const auto& tess = state.input_info.vertex->tess; - const auto variable = state.tess_variables.at(static_cast(kind)); - EXIT_IF(variable == 0); - const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; - const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; - const auto pointer = state.builder.AllocateId(); - if (kind == Attribute::Factor) { - EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); - const auto index = inst.Arg(1).U32() / 4u; - const auto outer = index < 3u; - EXIT_NOT_IMPLEMENTED(index >= 4u); - state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), - pointer, outer ? variable : state.tess_inner_variable, - ConstantU32(state, outer ? index : index - 3u)); - return pointer; - } - auto address = ctx.Arg(inst, 1); - if (kind == Attribute::PatchOutput) { - address = - EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base)); - } - const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; - const auto stride = local ? tess.ls_stride : tess.hs_stride; - const auto offset = kind == Attribute::PatchOutput ? address - : EmitBinaryU32(state, spv::OpUMod, address, - ConstantU32(state, stride)); - const auto attribute = - EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4)); - const auto component = - EmitBinaryU32(state, spv::OpBitwiseAnd, - EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)), - ConstantU32(state, 3)); - const auto type = TypePointer(state, storage, TypeU32(state)); - if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) { - state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, - component); - } else { - const auto vertex = - kind == Attribute::ControlOutput - ? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0) - : EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride)); - state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute, - component); - } - return pointer; -} - -} // namespace - -uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { - return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] { - const auto value = ctx.state.builder.AllocateId(); - ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, - TessellationPointer(ctx, inst)); - return value; - }); -} - -void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { - EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] { - auto value = ctx.Arg(inst, 2); - if (inst.Arg(0).U32() == static_cast(IR::TessellationAttribute::Factor)) { - const auto floating = ctx.state.builder.AllocateId(); - ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value); - value = floating; - } - ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value); - }); -} - uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) { auto& state = ctx.state; const auto result = state.builder.AllocateId(); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 632c9b0db..a2e9eda09 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -317,7 +317,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value); uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components); +uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage, + const char* name); void DefineModule(EmitterState& state); +void DefineTessellationInterfaces(EmitterState& state); +void DefineTessellationExecutionModes(EmitterState& state); void DefineMeshOutputs(EmitterState& state); void EmitMeshEntryPoint(EmitterState& state); void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index fa5a81d59..add068bb1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -334,8 +334,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki } } -namespace { - uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage, const char* name) { const auto variable = @@ -345,6 +343,8 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag return variable; } +namespace { + uint32_t BuiltInForInput(IR::StageInputKind kind) { switch (kind) { case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex; @@ -544,70 +544,6 @@ void DefineOutputs(EmitterState& state) { } } -void DefineTessellationInterfaces(EmitterState& state) { - using Attribute = IR::TessellationAttribute; - std::array used {}; - uint32_t patch_begin = UINT32_MAX, patch_end = 0; - for (const auto* block: state.program.blocks) { - for (const auto& inst: *block) { - if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute && - inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute) - continue; - const auto kind = inst.Arg(0).U32(); - used.at(kind) = true; - if (kind == static_cast(Attribute::PatchOutput)) { - EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); - patch_begin = std::min(patch_begin, inst.Arg(1).U32()); - patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u); - } - } - } - if (std::ranges::none_of(used, [](bool value) { return value; })) return; - const auto& tess = state.input_info.vertex->tess; - const auto array = [&](uint32_t type, uint32_t count) { - return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count)); - }; - for (uint32_t index = 0; index < used.size(); index++) { - if (!used[index]) continue; - const auto kind = static_cast(index); - const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; - const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; - uint32_t type; - if (kind == Attribute::Factor) { - type = array(TypeF32(state), 4u); - } else if (kind == Attribute::PatchOutput) { - state.tess_patch_base = patch_begin; - type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u); - } else { - const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; - const auto stride = local ? tess.ls_stride : tess.hs_stride; - type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u); - if (kind != Attribute::LocalOutput) { - type = array(type, local ? tess.input_control_points : tess.output_control_points); - } - } - auto& variable = state.tess_variables[index]; - variable = DefineInterfaceVariable(state, type, storage, "tess_attributes"); - if (kind == Attribute::Factor) { - state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, - spv::BuiltInTessLevelOuter); - state.tess_inner_variable = - DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner"); - state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, - spv::DecorationBuiltIn, spv::BuiltInTessLevelInner); - state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, - spv::DecorationPatch); - } else { - state.builder.AddAnnotation( - spv::OpDecorate, variable, spv::DecorationLocation, - kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u); - } - if (kind == Attribute::Factor || kind == Attribute::PatchOutput) { - state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); - } - } -} - } // namespace void DefineModule(EmitterState& state) { @@ -639,18 +575,7 @@ void DefineModule(EmitterState& state) { } if (state.program.stage == ShaderType::TessellationControl || state.program.stage == ShaderType::TessellationEvaluation) { - const auto& tess = state.input_info.vertex->tess; - EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u || - tess.output_topology != 2u); - state.builder.RequireCapability(spv::CapabilityTessellation); - if (state.program.stage == ShaderType::TessellationControl) { - state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices, - tess.output_control_points); - } else { - state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles); - state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd); - state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw); - } + DefineTessellationExecutionModes(state); } state.entry_label = state.builder.AllocateId(); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp new file mode 100644 index 000000000..f71359ae0 --- /dev/null +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp @@ -0,0 +1,161 @@ +#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h" + +#include + +namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { +namespace { + +uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { + auto& state = ctx.state; + using Attribute = IR::TessellationAttribute; + const auto kind = static_cast(inst.Arg(0).U32()); + const auto& tess = state.input_info.vertex->tess; + const auto variable = state.tess_variables.at(static_cast(kind)); + EXIT_IF(variable == 0); + const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; + const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; + const auto pointer = state.builder.AllocateId(); + if (kind == Attribute::Factor) { + EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); + const auto index = inst.Arg(1).U32() / 4u; + const auto outer = index < 3u; + EXIT_NOT_IMPLEMENTED(index >= 4u); + state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), + pointer, outer ? variable : state.tess_inner_variable, + ConstantU32(state, outer ? index : index - 3u)); + return pointer; + } + auto address = ctx.Arg(inst, 1); + if (kind == Attribute::PatchOutput) { + address = + EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base)); + } + const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; + const auto stride = local ? tess.ls_stride : tess.hs_stride; + const auto offset = kind == Attribute::PatchOutput ? address + : EmitBinaryU32(state, spv::OpUMod, address, + ConstantU32(state, stride)); + const auto attribute = + EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4)); + const auto component = + EmitBinaryU32(state, spv::OpBitwiseAnd, + EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)), + ConstantU32(state, 3)); + const auto type = TypePointer(state, storage, TypeU32(state)); + if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) { + state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, + component); + } else { + const auto vertex = + kind == Attribute::ControlOutput + ? EmitLaneId(state) + : EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride)); + state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute, + component); + } + return pointer; +} + +} // namespace + +void DefineTessellationInterfaces(EmitterState& state) { + using Attribute = IR::TessellationAttribute; + std::array used {}; + uint32_t patch_begin = UINT32_MAX, patch_end = 0; + for (const auto* block: state.program.blocks) { + for (const auto& inst: *block) { + if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute && + inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute) + continue; + const auto kind = inst.Arg(0).U32(); + used.at(kind) = true; + if (kind == static_cast(Attribute::PatchOutput)) { + EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); + patch_begin = std::min(patch_begin, inst.Arg(1).U32()); + patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u); + } + } + } + if (std::ranges::none_of(used, [](bool value) { return value; })) return; + const auto& tess = state.input_info.vertex->tess; + const auto array = [&](uint32_t type, uint32_t count) { + return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count)); + }; + for (uint32_t index = 0; index < used.size(); index++) { + if (!used[index]) continue; + const auto kind = static_cast(index); + const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput; + const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput; + uint32_t type; + if (kind == Attribute::Factor) { + type = array(TypeF32(state), 4u); + } else if (kind == Attribute::PatchOutput) { + state.tess_patch_base = patch_begin; + type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u); + } else { + const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; + const auto stride = local ? tess.ls_stride : tess.hs_stride; + type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u); + if (kind != Attribute::LocalOutput) { + type = array(type, local ? tess.input_control_points : tess.output_control_points); + } + } + auto& variable = state.tess_variables[index]; + variable = DefineInterfaceVariable(state, type, storage, "tess_attributes"); + if (kind == Attribute::Factor) { + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, + spv::BuiltInTessLevelOuter); + state.tess_inner_variable = + DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner"); + state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, + spv::DecorationBuiltIn, spv::BuiltInTessLevelInner); + state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable, + spv::DecorationPatch); + } else { + state.builder.AddAnnotation( + spv::OpDecorate, variable, spv::DecorationLocation, + kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u); + } + if (kind == Attribute::Factor || kind == Attribute::PatchOutput) { + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); + } + } +} + +void DefineTessellationExecutionModes(EmitterState& state) { + const auto& tess = state.input_info.vertex->tess; + EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u || + tess.output_topology != 2u); + state.builder.RequireCapability(spv::CapabilityTessellation); + if (state.program.stage == ShaderType::TessellationControl) { + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices, + tess.output_control_points); + } else { + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles); + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd); + state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw); + } +} + +uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { + return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] { + const auto value = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, + TessellationPointer(ctx, inst)); + return value; + }); +} + +void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { + EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] { + auto value = ctx.Arg(inst, 2); + if (inst.Arg(0).U32() == static_cast(IR::TessellationAttribute::Factor)) { + const auto floating = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value); + value = floating; + } + ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value); + }); +} + +} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 8bf1bc311..c6f08e545 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -381,6 +381,25 @@ void DecodeInstruction(std::span code, uint32_t word_index, Inst } } +Program DecodeFrontProgram(std::span front) { + Program result; + uint32_t front_words = 0; + while (front_words < front.size()) { + auto& inst = result.instructions.emplace_back(); + DecodeInstruction(front, front_words, inst); + front_words += inst.word_count; + if (inst.opcode == Opcode::S_SETPC_B64) { + EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Sgpr || inst.src0.reg != 6u); + break; + } + EXIT_NOT_IMPLEMENTED(inst.opcode == Opcode::S_ENDPGM); + } + EXIT_IF(result.instructions.empty() || + result.instructions.back().opcode != Opcode::S_SETPC_B64); + result.code = front.first(front_words); + return result; +} + void DecodeProgram(std::span code, Program& program) { program.instructions.clear(); program.instructions.reserve(code.size()); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 09363948b..6bce89a61 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -722,6 +722,7 @@ struct Program { Family GetInstructionFamily(uint32_t word); // The output object must be freshly initialized. void DecodeInstruction(std::span code, uint32_t word_index, Instruction& inst); +Program DecodeFrontProgram(std::span front); void DecodeProgram(std::span code, Program& program); bool IsConditionalBranch(Opcode opcode); bool IsDirectBranch(Opcode opcode); diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index cfa7b5d76..520a06a43 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -12,6 +12,7 @@ #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" +#include "graphics/shader/recompiler/Tessellation.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/shaderCompiler.h" #include "graphics/shader/shaderVertexMetadata.h" diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d091a4242..c11e6b2ad 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -36,6 +36,7 @@ #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/presentation/window/windowInternal.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" +#include "graphics/shader/recompiler/Tessellation.h" #include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h" #include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"