diff --git a/src/graphics/shader/recompiler/Tessellation.cpp b/src/graphics/shader/recompiler/Tessellation.cpp index 8e2ffaa69..ee42a2345 100644 --- a/src/graphics/shader/recompiler/Tessellation.cpp +++ b/src/graphics/shader/recompiler/Tessellation.cpp @@ -25,13 +25,16 @@ struct TessellationAddress { bool operator==(const TessellationAddress&) const = default; }; -uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, - uint32_t control_points, uint32_t input_stride) { +// Reflects the LS output stride, or the HS output stride and patch constant range. +void ReflectTessellationLayout(const Decoder::Program& program, bool local, + ShaderTessellationInputInfo& info) { using namespace Decoder; using Address = TessellationAddress; using Kind = Address::Kind; using Registers = std::array; - Registers registers {}; + + const auto control_points = local ? info.input_control_points : info.output_control_points; + Registers 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}; }; @@ -85,13 +88,13 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, // A forward branch joins its target with the fallthrough path. Definitions that // differ between the paths are unknown there. std::unordered_map joins; - const auto merge = [](Registers& into, const Registers& from) { for (uint32_t index = 0; index < into.size(); index++) { if (into[index] != from[index]) into[index] = {}; } }; - uint32_t stride = 0; + + uint32_t stride = 0, patch_begin = UINT32_MAX, patch_end = 0; for (const auto& inst: program.instructions) { if (const auto join = joins.find(inst.pc); join != joins.end()) { merge(registers, join->second); @@ -119,10 +122,15 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, inst.pc); } if (address.coefficient != 0u) { - const auto expected = control_read ? input_stride : stride; + const auto expected = control_read ? info.ls_stride : stride; EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u || (expected != 0u && expected != address.coefficient)); if (!control_read) stride = address.coefficient; + } else if (control_store) { + // Control-point-independent HS outputs are patch constants. + const auto begin = address.constant + inst.offset; + patch_begin = std::min(patch_begin, begin); + patch_end = std::max(patch_end, begin + 4u * inst.data_dwords); } } // Store vdata is a source, despite occupying the decoder's dst field. @@ -145,6 +153,7 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, (rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu))) value = constant(0u); break; + case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break; case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); 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; @@ -173,7 +182,12 @@ uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local, registers.at(inst.dst.reg) = value; } EXIT_NOT_IMPLEMENTED(!joins.empty() || stride == 0u); - return stride; + (local ? info.ls_stride : info.hs_stride) = stride; + if (patch_end != 0u) { + EXIT_NOT_IMPLEMENTED(patch_begin < stride * control_points); + info.patch_base = patch_begin; + info.patch_size = patch_end - patch_begin; + } } const IR::Inst* TessellationBufferBase(const IR::Inst& inst) { @@ -184,6 +198,15 @@ const IR::Inst* TessellationBufferBase(const IR::Inst& inst) { : nullptr; } +// Whether an access predicate, possibly a conjunction of EXEC masks, implies a condition. +bool Implies(IR::Value predicate, IR::Value condition) { + predicate = predicate.Resolve(); + if (predicate == condition.Resolve()) return true; + const auto* inst = predicate.TryInstruction(); + return inst != nullptr && inst->GetOpcode() == IR::ValueOpcode::LogicalAnd && + (Implies(inst->Arg(0), condition) || Implies(inst->Arg(1), condition)); +} + IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value, IR::Value predicate, std::unordered_map& resolved) { using namespace IR; @@ -192,18 +215,28 @@ IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value 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() + return Implies(predicate, source->Arg(0)) ? ActiveAddress(block, before, source->Arg(1), predicate, resolved) : value; } - if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32) + // Pure address arithmetic that stays behind EXEC selects; mul24 lowers to IMul32 of BFEs. + const auto opcode = source->GetOpcode(); + if (opcode != ValueOpcode::IAdd32 && opcode != ValueOpcode::ISub32 && + opcode != ValueOpcode::IMul32 && opcode != ValueOpcode::BitFieldUExtract) 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); + std::array args; + bool changed = false; + for (size_t index = 0; index < source->NumArgs(); index++) { + args[index] = ActiveAddress(block, before, source->Arg(index), predicate, resolved); + changed |= args[index] != source->Arg(index).Resolve(); + } + if (changed) { + const auto flags = source->Flags(); + const auto copy = + source->NumArgs() == 2u + ? block.PrependNewInst(before, opcode, {args[0], args[1]}, flags) + : block.PrependNewInst(before, opcode, {args[0], args[1], args[2]}, flags); + value = Value(&*copy); } resolved.emplace(source, value); return value; @@ -216,11 +249,12 @@ void AnalyzeTessellationPrograms(std::span local, std::spanbegin(); it != block->end(); ++it) { auto& inst = *it; @@ -291,8 +325,13 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options : options.stage != ShaderType::TessellationEvaluation); if (address.IsImmediate() && address.U32() >= tess.hs_stride * tess.output_control_points) { - EXIT_NOT_IMPLEMENTED(!write); - kind = TessellationAttribute::PatchOutput; + const auto begin = address.U32() + memory.offset; + EXIT_NOT_IMPLEMENTED(begin < tess.patch_base || + begin + 4u * components > + tess.patch_base + tess.patch_size); + kind = write ? TessellationAttribute::PatchOutput + : TessellationAttribute::PatchInput; + patches += components; } } } else { @@ -343,11 +382,14 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options ConstantPropagationPass(program.blocks, program.wave_size); RemoveIdentities(program); EliminateDeadCode(program.blocks); - LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n", + // Patch constant stores whose EXEC selects were not stripped would land in control points. + EXIT_NOT_IMPLEMENTED(options.stage == ShaderType::TessellationControl && + tess.patch_size != 0u && patches == 0u); + LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u patches=%u\n", options.stage == ShaderType::Local ? "LS" : options.stage == ShaderType::TessellationControl ? "HS" : "TES", - reads, writes, factors); + reads, writes, factors, patches); } } // namespace Libs::Graphics::ShaderRecompiler diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index aa537360a..da487fcc5 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -104,9 +104,8 @@ struct EmitterState { std::array u32_vector_types {}; std::array i32_vector_types {}; std::array f32_vector_types {}; - std::array tess_variables {}; + std::array tess_variables {}; uint32_t tess_inner_variable = 0; - uint32_t tess_patch_base = 0; uint32_t lane_count = 1; uint32_t lane_half = 0; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp index f71359ae0..96cbba35a 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterTessellation.cpp @@ -5,6 +5,17 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace { +bool IsTessellationInput(IR::TessellationAttribute kind) { + using Attribute = IR::TessellationAttribute; + return kind == Attribute::ControlInput || kind == Attribute::EvaluationInput || + kind == Attribute::PatchInput; +} + +bool IsTessellationPatch(IR::TessellationAttribute kind) { + using Attribute = IR::TessellationAttribute; + return kind == Attribute::PatchOutput || kind == Attribute::PatchInput; +} + uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { auto& state = ctx.state; using Attribute = IR::TessellationAttribute; @@ -12,8 +23,9 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { 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 bool patch = IsTessellationPatch(kind); + const auto storage = + IsTessellationInput(kind) ? spv::StorageClassInput : spv::StorageClassOutput; const auto pointer = state.builder.AllocateId(); if (kind == Attribute::Factor) { EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); @@ -26,15 +38,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { return pointer; } auto address = ctx.Arg(inst, 1); - if (kind == Attribute::PatchOutput) { - address = - EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base)); + if (patch) { + address = EmitBinaryU32(state, spv::OpISub, address, ConstantU32(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 offset = + patch ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride)); const auto attribute = EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4)); const auto component = @@ -42,7 +52,7 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { 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) { + if (kind == Attribute::LocalOutput || patch) { state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute, component); } else { @@ -60,20 +70,12 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) { void DefineTessellationInterfaces(EmitterState& state) { using Attribute = IR::TessellationAttribute; - std::array used {}; - uint32_t patch_begin = UINT32_MAX, patch_end = 0; + std::array used {}; 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 (inst.GetOpcode() == IR::ValueOpcode::GetTessellationAttribute || + inst.GetOpcode() == IR::ValueOpcode::SetTessellationAttribute) + used.at(inst.Arg(0).U32()) = true; } } if (std::ranges::none_of(used, [](bool value) { return value; })) return; @@ -83,15 +85,16 @@ void DefineTessellationInterfaces(EmitterState& state) { }; 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; + const auto kind = static_cast(index); + const bool patch = IsTessellationPatch(kind); + const auto storage = + IsTessellationInput(kind) ? 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 if (patch) { + // HS and TES share the reflected patch layout so their interfaces match. + type = array(TypeU32Vector(state, 4), (tess.patch_size + 15u) / 16u); } else { const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput; const auto stride = local ? tess.ls_stride : tess.hs_stride; @@ -112,11 +115,10 @@ void DefineTessellationInterfaces(EmitterState& state) { 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); + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation, + patch ? (tess.hs_stride + 15u) / 16u : 0u); } - if (kind == Attribute::Factor || kind == Attribute::PatchOutput) { + if (kind == Attribute::Factor || patch) { state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch); } } diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 5092eafbf..7691b6a24 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -220,8 +220,11 @@ enum class TessellationAttribute { ControlOutput, EvaluationInput, PatchOutput, + PatchInput, Factor }; +constexpr uint32_t NumTessellationAttributes = + static_cast(TessellationAttribute::Factor) + 1u; enum class StageInputKind { VertexIndex, diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index f093ed534..d9f661d2c 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -441,7 +441,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector