diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 64b52e58c..00f695511 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -651,6 +651,9 @@ TranslateResult TranslateProgram(std::span code, const CompileOp IR::ResolveControlFlowIdentities(ir); IR::RemoveIdentities(ir.blocks); IR::EliminateDeadCode(ir.blocks); + IR::EliminateMaskedValues(ir); + IR::RemoveIdentities(ir.blocks); + IR::EliminateDeadCode(ir.blocks); const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size); if (read_lane_stats.rewritten_reads != 0) { LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options), diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp index d58acc0b4..c6b0e2834 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.cpp @@ -32,6 +32,10 @@ uint32_t Builder::AllocateId() { return m_next_id++; } +void Builder::RequireVersion(uint32_t version) { + m_version = std::max(m_version, version); +} + void Builder::RequireCapability(uint32_t capability) { if (m_required_capabilities.insert(capability).second) { AddCapability({capability}); diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h index 06bac8a91..d9aeb9ec7 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvBuilder.h @@ -28,6 +28,7 @@ public: KYTY_CLASS_DEFAULT_COPY(Builder); uint32_t AllocateId(); + void RequireVersion(uint32_t version); void RequireCapability(uint32_t capability); void RequireExtension(const char* name); uint32_t Import(const char* name); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index bde60fa8b..e1457dcea 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -71,7 +71,8 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t {OpSelect, TypeU32(state), bits, value, ConstantU32(state, 1), ConstantU32(state, 0)}); return bits; } - if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex) { + if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex || + kind == IR::StageInputKind::Layer) { const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); state.builder.AddFunction({OpLoad, TypeI32(state), value, variable}); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index a86bf5491..4c22dfe1f 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -50,8 +50,8 @@ enum : uint32_t { CapabilityGroupNonUniform = 61, CapabilityGroupNonUniformBallot = 64, CapabilityGroupNonUniformShuffle = 65, + CapabilityShaderLayer = 69, CapabilitySignedZeroInfNanPreserve = 4466, - CapabilityShaderViewportIndexLayerEXT = 5254, CapabilityFragmentBarycentricKHR = 5284, CapabilityComputeDerivativeGroupQuadsKHR = 5288, CapabilityPhysicalStorageBufferAddresses = 5347, diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 0ee81495b..b34c757d5 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -518,6 +518,7 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) { case IR::StageInputKind::InstanceIndex: return BuiltInInstanceIndex; case IR::StageInputKind::FragCoord: return BuiltInFragCoord; case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing; + case IR::StageInputKind::Layer: return BuiltInLayer; case IR::StageInputKind::BaryCoordSmooth: return BuiltInBaryCoordKHR; case IR::StageInputKind::BaryCoordNoPerspective: return BuiltInBaryCoordNoPerspKHR; case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId; @@ -541,6 +542,9 @@ void AddInputAnnotationsAndNames(EmitterState& state) { } for (const auto& input: state.inputs) { state.builder.AddName(input.variable_id, input.debug_name.c_str()); + if (input.kind == IR::StageInputKind::Layer) { + state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat}); + } if (input.kind == IR::StageInputKind::Parameter) { const auto flat = PixelParameterIsFlat(state, input.location); if (input.per_vertex) { @@ -710,9 +714,9 @@ void DefineModule(EmitterState& state) { if (state.cull_distance_variable != 0) { state.builder.RequireCapability(CapabilityCullDistance); } - if (state.layer_variable != 0) { - state.builder.RequireCapability(CapabilityShaderViewportIndexLayerEXT); - state.builder.RequireExtension("SPV_EXT_shader_viewport_index_layer"); + if (state.layer_variable != 0 || InputVariableForKind(state, IR::StageInputKind::Layer) != 0) { + state.builder.RequireVersion(0x00010500u); + state.builder.RequireCapability(CapabilityShaderLayer); } if (state.requirements.image_gather_extended) { state.builder.RequireCapability(CapabilityImageGatherExtended); @@ -805,6 +809,7 @@ void DefineModule(EmitterState& state) { switch (input.kind) { case IR::StageInputKind::VertexIndex: case IR::StageInputKind::InstanceIndex: + case IR::StageInputKind::Layer: ptr_type = TypePointer(state, StorageClassInput, TypeI32(state)); break; case IR::StageInputKind::WorkgroupId: diff --git a/src/graphics/shader/recompiler/frontend/translate/Control.cpp b/src/graphics/shader/recompiler/frontend/translate/Control.cpp index 1cbd6ee1d..0c3cd163f 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Control.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Control.cpp @@ -259,13 +259,20 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) { } void Translator::S_WQM_B64(const Decoder::Instruction& inst) { - const auto mask_valid = ReadMaskValid(inst.src0); + const auto source_mask = ReadMask(inst.src0); + const auto mask_valid = ReadMaskValid(inst.src0); const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ReadOperand(inst.src0, IR::Type::U64)})); + // WQM activates whole quads and cannot clear an already active lane. + const auto widened_mask = ir.LogicalOr(source_mask, ThreadBit(ExtractU64(result))); WriteOperand(DestinationOperand(inst), result); - if (inst.dst.kind == Decoder::OperandKind::Sgpr) { + if (inst.dst.kind == Decoder::OperandKind::ExecLo) { + ir.SetExec(widened_mask); + } else if (inst.dst.kind == Decoder::OperandKind::VccLo) { + ir.SetVcc(widened_mask); + } else if (inst.dst.kind == Decoder::OperandKind::Sgpr) { const auto dst = static_cast(inst.dst.reg); - ir.SetThreadBitScalarReg(dst, ThreadBit(ExtractU64(result))); + ir.SetThreadBitScalarReg(dst, widened_mask); ir.SetScalarMaskTag(dst, mask_valid); } ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}))); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index db11eae0b..b25376de1 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1137,9 +1137,13 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& builtin(IR::StageInputKind::FragCoord, 3)); } if (ps->ps_front_face) { - entry_ir.SetVectorReg(static_cast(reg), + entry_ir.SetVectorReg(static_cast(reg++), builtin(IR::StageInputKind::FrontFacing)); } + if (ps->ps_ancillary) { + entry_ir.SetVectorReg(static_cast(reg), + builtin(IR::StageInputKind::PackedAncillary)); + } } else if (options.stage == ShaderType::Vertex) { entry_ir.SetVectorReg(static_cast(5), builtin(IR::StageInputKind::VertexIndex)); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index eb23ff179..61703b4e2 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -160,6 +160,8 @@ enum class StageInputKind { InstanceIndex, FragCoord, FrontFacing, + PackedAncillary, + Layer, BaryCoordSmooth, BaryCoordNoPerspective, WorkgroupId, diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp index 21bc6ac99..223e5e954 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp @@ -1,8 +1,10 @@ #include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" +#include "graphics/shader/recompiler/ir/ShaderIR.h" #include #include #include +#include namespace Libs::Graphics::ShaderRecompiler::IR { namespace { @@ -116,6 +118,87 @@ bool FoldSelect(Inst& inst) { return false; } +bool IsSelect(ValueOpcode opcode) { + return opcode == ValueOpcode::SelectU1 || opcode == ValueOpcode::SelectU32 || + opcode == ValueOpcode::SelectF32; +} + +bool Implies(Value condition, Value required, uint32_t depth = 0) { + condition = condition.Resolve(); + if (condition == required) return true; + const auto* inst = condition.TryInstruction(); + return depth < 64u && inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd && + (Implies(inst->Arg(0), required, depth + 1u) || + Implies(inst->Arg(1), required, depth + 1u)); +} + +bool IsAcyclic(const BlockList& blocks) { + std::unordered_map completed; + const auto visit = [&](auto&& self, const Block* block) -> bool { + const auto [entry, added] = completed.emplace(block, false); + if (!added) return entry->second; + for (const auto* successor: block->ImmSuccessors()) { + if (!self(self, successor)) return false; + } + completed[block] = true; + return true; + }; + for (const auto* block: blocks) { + if (!visit(visit, block)) return false; + } + return true; +} + +class MaskedUses { +public: + MaskedUses(const Program& program, Value condition) + : program(program), condition(condition.Resolve()) {} + + bool OnlyWhenActive(const Inst& inst) { + const auto [entry, added] = completed.emplace(&inst, false); + if (!added) return entry->second; + for (const auto& use: inst.Uses()) { + const auto& user = *use.user; + const auto op = user.GetOpcode(); + if (IsSelect(op)) { + if (use.operand == 0) return false; + if (use.operand == 1 && Implies(user.Arg(0), condition)) continue; + } else if (op == ValueOpcode::SetAttribute) { + if (use.operand == 0 && Implies(user.Arg(1), condition)) continue; + return false; + } else if (!IsLaneLocal(user, use.operand)) { + return false; + } + if (!OnlyWhenActive(user)) return false; + } + completed[&inst] = true; + return true; + } + +private: + bool IsLaneLocal(const Inst& inst, size_t operand) const { + const auto op = inst.GetOpcode(); + // These opcode-table groups contain only construction, conversion and arithmetic. + if ((op >= ValueOpcode::BitCastU16F16 && op <= ValueOpcode::BitFieldSExtract) || + (op >= ValueOpcode::WqmU64 && op <= ValueOpcode::FPFract32) || + op == ValueOpcode::Identity || op == ValueOpcode::Phi || + op == ValueOpcode::MakeImageAddress) { + return true; + } + if (op != ValueOpcode::ImageSampleRaw || operand != 2) return false; + // Explicit LOD or gradients keep coordinates local; implicit derivatives read neighbors. + const auto index = inst.Flags().index; + return index < program.memory_info.size() && + (program.memory_info[index].image_sample_flags & + (Decoder::ImageSampleFlagLod | Decoder::ImageSampleFlagLevelZero | + Decoder::ImageSampleFlagDerivative)) != 0; + } + + const Program& program; + Value condition; + std::unordered_map completed; +}; + bool FoldPhi(Inst& inst) { Value same; for (size_t index = 0; index < inst.NumArgs(); index++) { @@ -192,7 +275,8 @@ bool FoldCompositeExtract(Inst& inst, ValueOpcode construct, size_t components) return false; } -void FoldInstruction(Inst& inst) { +void FoldInstruction(Block& block, Block::iterator instruction) { + auto& inst = *instruction; switch (inst.GetOpcode()) { case ValueOpcode::Phi: FoldPhi(inst); return; case ValueOpcode::SelectU1: @@ -223,6 +307,21 @@ void FoldInstruction(Inst& inst) { const auto value = Arg(inst, 0); const auto offset = Arg(inst, 1); const auto count = Arg(inst, 2); + const auto* source = value.TryInstruction(); + if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin && + source->Arg(0) == Value(static_cast(StageInputKind::PackedAncillary)) && + IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && + offset.U32() >= 16u && offset.U32() < 27u && count.U32() != 0u && + count.U32() <= 27u - offset.U32()) { + // Preserve the extraction (including sign extension) while exposing only its + // used hardware field. Other live ancillary fields remain unsupported. + const auto layer = block.PrependNewInst( + instruction, ValueOpcode::GetBuiltin, + {Value(static_cast(StageInputKind::Layer)), Value(0u)}); + inst.SetArg(0, Value(&*layer)); + inst.SetArg(1, Value(offset.U32() - 16u)); + return; + } if (!IsImmediate(value, Type::U32) || !IsImmediate(offset, Type::U32) || !IsImmediate(count, Type::U32) || offset.U32() > 32u || count.U32() > 32u - offset.U32()) { @@ -584,8 +683,22 @@ void FoldInstruction(Inst& inst) { void ConstantPropagationPass(const BlockList& blocks) { for (auto* block: blocks) { - for (auto& inst: block->Instructions()) { - FoldInstruction(inst); + for (auto inst = block->begin(); inst != block->end(); ++inst) { + FoldInstruction(*block, inst); + } + } +} + +void EliminateMaskedValues(Program& program) { + // A loop-carried value may have been produced under a previous iteration's predicate. + if (!IsAcyclic(program.blocks)) return; + for (auto block = program.blocks.rbegin(); block != program.blocks.rend(); ++block) { + auto& instructions = (*block)->Instructions(); + for (auto inst = instructions.rbegin(); inst != instructions.rend(); ++inst) { + if (IsSelect(inst->GetOpcode()) && + MaskedUses(program, inst->Arg(0)).OnlyWhenActive(*inst)) { + Replace(*inst, inst->Arg(1)); + } } } } diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h index e158c1ea1..2491b106f 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.h @@ -4,6 +4,9 @@ namespace Libs::Graphics::ShaderRecompiler::IR { +struct Program; + void ConstantPropagationPass(const BlockList& blocks); +void EliminateMaskedValues(Program& program); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 37a4bb023..8a846a26f 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -115,6 +115,13 @@ void ValidateValueReferences(const Program& program, const ShaderInfoOptions& op const auto kind = static_cast(inst.Arg(0).U32()); const auto component = inst.Arg(1).U32(); switch (kind) { + case StageInputKind::PackedAncillary: + return Fail("packed pixel ancillary input has an unsupported live use"); + case StageInputKind::Layer: + if (program.stage != ShaderType::Pixel || component != 0u) { + return Fail("typed layer input is invalid"); + } + break; case StageInputKind::VertexIndex: case StageInputKind::InstanceIndex: case StageInputKind::FrontFacing: @@ -251,6 +258,7 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { case StageInputKind::FrontFacing: AddInput(info, kind, 0, 1, "gl_FrontFacing"); break; + case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break; case StageInputKind::BaryCoordSmooth: AddInput(info, kind, 0, 3, "gl_BaryCoordKHR"); break; @@ -269,6 +277,7 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { case StageInputKind::GlobalInvocationId: AddInput(info, kind, 0, 3, "gl_GlobalInvocationID"); break; + case StageInputKind::PackedAncillary: case StageInputKind::Parameter: break; } } diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index c6f7171a1..8779772be 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -699,6 +699,7 @@ static void ShaderGetStaticInputInfoPS( ps_info.ps_pos_z = (active_inputs & 0x00000400u) != 0; ps_info.ps_pos_w = (active_inputs & 0x00000800u) != 0; ps_info.ps_front_face = (active_inputs & 0x00001000u) != 0; + ps_info.ps_ancillary = (active_inputs & 0x00002000u) != 0; ps_info.ps_sample_shading = (active_inputs & 0x00000011u) != 0; ps_info.ps_no_perspective = (sh.ps_input_ena & sh.ps_input_addr & 0x00000020u) != 0; ps_info.ps_pixel_kill_enable = sh.db_shader_control.shader_kill_enable; @@ -808,6 +809,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(static_cast(info.ps_pos_z)); key.push_back(static_cast(info.ps_pos_w)); key.push_back(static_cast(info.ps_front_face)); + key.push_back(static_cast(info.ps_ancillary)); key.push_back(static_cast(info.ps_no_perspective)); key.push_back(static_cast(info.ps_pixel_kill_enable)); key.push_back(static_cast(info.ps_depth_export_enable)); @@ -985,6 +987,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_pos_z = %s\n" "\t ps_pos_w = %s\n" "\t ps_front_face = %s\n" + "\t ps_ancillary = %s\n" "\t ps_sample_shading = %s\n" "\t ps_no_perspective = %s\n" "\t ps_pixel_kill_enable = %s\n" @@ -994,6 +997,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { info.ps_perspective_center_vgpr, info.ps_pos_x ? "true" : "false", info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false", info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false", + info.ps_ancillary ? "true" : "false", info.ps_sample_shading ? "true" : "false", info.ps_no_perspective ? "true" : "false", info.ps_pixel_kill_enable ? "true" : "false", info.ps_early_z ? "true" : "false", info.ps_execute_on_noop ? "true" : "false"); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index daf3d5738..6a72d33de 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -125,6 +125,7 @@ struct ShaderPixelInputInfo { bool ps_pos_z = false; bool ps_pos_w = false; bool ps_front_face = false; + bool ps_ancillary = false; bool ps_no_perspective = false; bool ps_pixel_kill_enable = false; bool ps_depth_export_enable = false; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 35656e54f..33ccf87bc 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1032,6 +1032,9 @@ struct GraphicsCase { std::vector pixel_interpolator_settings; bool pixel_no_perspective = false; std::vector vertices; + bool pixel_ancillary = false; + bool pixel_front_face = false; + u32 layers = 1; }; struct CompiledShader { @@ -1373,6 +1376,9 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { ? 1u : static_cast(test.pixel_interpolator_settings.size()); pixel_info.ps_no_perspective = test.pixel_no_perspective; + pixel_info.ps_ancillary = test.pixel_ancillary; + pixel_info.ps_front_face = test.pixel_front_face; + pixel_info.ps_system_input_base = 2; for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) { pixel_info.interpolator_settings[i] = i; } @@ -1457,12 +1463,13 @@ enum : u32 { OpReturn = 253, }; -std::vector MakePassthroughVertexSpirv() { +std::vector MakePassthroughVertexSpirv(bool layered) { using ShaderRecompiler::Spirv::Builder; Builder b; const auto void_type = b.Type(OpTypeVoid); const auto uint_type = b.Type(OpTypeInt, {32, 0}); + const auto int_type = b.Type(OpTypeInt, {32, 1}); const auto float_type = b.Type(OpTypeFloat, {32}); const auto vec2_type = b.Type(OpTypeVector, {float_type, 2}); const auto vec4_type = b.Type(OpTypeVector, {float_type, 4}); @@ -1489,6 +1496,20 @@ std::vector MakePassthroughVertexSpirv() { b.DefineGlobalVariable(ptr_output_vec4, StorageClassOutput); const auto per_vertex = b.DefineGlobalVariable(ptr_output_per_vertex, StorageClassOutput); + u32 instance = 0; + u32 layer = 0; + std::vector interfaces = {in_pos, in_color, per_vertex, out_color}; + if (layered) { + instance = b.DefineGlobalVariable( + b.Type(OpTypePointer, {StorageClassInput, int_type}), StorageClassInput); + layer = b.DefineGlobalVariable( + b.Type(OpTypePointer, {StorageClassOutput, int_type}), StorageClassOutput); + b.AddAnnotation({OpDecorate, instance, DecorationBuiltIn, 43}); // InstanceIndex + b.AddAnnotation({OpDecorate, layer, DecorationBuiltIn, 9}); // Layer + b.RequireVersion(0x00010500u); + b.RequireCapability(69); // ShaderLayer + interfaces.insert(interfaces.end(), {instance, layer}); + } const auto main = b.AllocateId(); const auto label = b.AllocateId(); const auto pos2 = b.AllocateId(); @@ -1500,8 +1521,7 @@ std::vector MakePassthroughVertexSpirv() { b.RequireCapability(CapabilityShader); b.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450}); - b.AddEntryPoint(ExecutionModelVertex, main, "main", - {in_pos, in_color, per_vertex, out_color}); + b.AddEntryPoint(ExecutionModelVertex, main, "main", interfaces); b.AddAnnotation({OpDecorate, in_pos, DecorationLocation, 0}); b.AddAnnotation({OpDecorate, in_color, DecorationLocation, 1}); b.AddAnnotation({OpDecorate, out_color, DecorationLocation, 0}); @@ -1518,6 +1538,11 @@ std::vector MakePassthroughVertexSpirv() { {OpAccessChain, ptr_output_vec4, position_ptr, per_vertex, const_u32_0}); b.AddFunction({OpStore, position_ptr, position}); b.AddFunction({OpStore, out_color, color4}); + if (layered) { + const auto index = b.AllocateId(); + b.AddFunction({OpLoad, int_type, index, instance}); + b.AddFunction({OpStore, layer, index}); + } b.AddFunction({OpReturn}); b.AddFunction({OpFunctionEnd}); return b.Build(); @@ -10343,13 +10368,15 @@ public: std::vector RenderFragment(const GraphicsCase &test, const CompiledShader &fragment) { - const auto vertex_spirv = TestSpv::MakePassthroughVertexSpirv(); + const auto vertex_spirv = TestSpv::MakePassthroughVertexSpirv(test.layers > 1); ValidateSpirv(test.name, vertex_spirv); Image target = - CreateImage2D(test.name, 1, 1, vk::Format::eR32G32B32A32Sfloat, + CreateImageMips(test.name, 1, 1, vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eColorAttachment, {}, 4, - vk::ImageLayout::eGeneral); + vk::ImageLayout::eGeneral, vk::ImageType::e2D, + test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, + test.layers); const std::vector default_vertices = { 0xbf800000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u, 0x40400000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, @@ -10507,7 +10534,7 @@ public: vk::RenderingInfo rendering{}; rendering.sType = vk::StructureType::eRenderingInfo; rendering.renderArea.extent = {1, 1}; - rendering.layerCount = 1; + rendering.layerCount = test.layers; rendering.colorAttachmentCount = 1; rendering.pColorAttachments = &color; cmd.beginRendering(rendering); @@ -10521,13 +10548,13 @@ public: } vk::DeviceSize offset = 0; cmd.bindVertexBuffers(0, 1, &vertex_buffer.buffer, &offset); - cmd.draw(3, 1, 0, 0); + cmd.draw(3, test.layers, 0, 0); cmd.endRendering(); EndSubmitAndFree(test.name, "graphics", cmd); target.layout = vk::ImageLayout::eGeneral; auto pixel = ReadImage(test.name, &target); - pixel.resize(4); + pixel.resize(4 * test.layers); m_device.destroyPipeline(pipeline, nullptr); m_device.destroyPipelineLayout(pipeline_layout, nullptr); @@ -11762,6 +11789,8 @@ private: Require("VulkanHarness", "dispatch", available_features12.bufferDeviceAddress == true, "bufferDeviceAddress is not supported"); + Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true, + "vertex layer output is not supported"); float priority = 1.0f; vk::DeviceQueueCreateInfo queue_info{}; @@ -11779,6 +11808,7 @@ private: vk::StructureType::ePhysicalDeviceVulkan12Features; device_features12.timelineSemaphore = true; device_features12.bufferDeviceAddress = true; + device_features12.shaderOutputLayer = true; vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{}; barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR; barycentric.pNext = &device_features12; @@ -13822,6 +13852,50 @@ TestCase ScalarWqmB64SelectsSccDomain() { O::S_ENDPGM}}; } +TestCase ScalarWqmB64PreservesPartialMasks() { + using O = ShaderOpcode; + TestCase test; + test.name = "ScalarWqmB64PreservesPartialMasks"; + auto &code = test.code; + code.push_back(EncodeSop1(0x04, 16, 126)); // Save full EXEC. + AppendVMovU32(&code, 1, 3); + AppendVMovU32(&code, 2, 9); + u32 offset = 0; + for (const u32 destination : {126u, 106u, 8u}) { + code.push_back(EncodeVopc(0xc2, Vgpr(0), 1)); // Only lane 3. + code.push_back(EncodeSop1(0x04, destination, 106)); + code.push_back(EncodeSop1(0x0a, destination, destination)); + code.push_back(EncodeSop1(0x04, 126, destination)); + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset); + code.push_back(EncodeSop1(0x04, 126, 16)); + offset += 64; + } + code.push_back(EncodeSMovB32(8, InlineU32(32))); // Raw bits 5 and 33. + code.push_back(EncodeSMovB32(9, InlineU32(2))); + code.push_back(EncodeSop1(0x0a, 10, 8)); + code.push_back(EncodeSop1(0x04, 126, 10)); + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset); + AppendEnd(&code); + test.initial.assign(256, 0xdeadbeef); + test.expected = test.initial; + for (u32 lane = 0; lane < 64; ++lane) { + for (u32 region = 0; region < 3; ++region) { + if (lane < 4) test.expected[region * 64 + lane] = 9; + } + if ((lane >= 4 && lane < 8) || (lane >= 32 && lane < 36)) { + test.expected[192 + lane] = 9; + } + } + test.opcodes = {O::S_MOV_B64, O::S_MOV_B32, O::S_WQM_B64, O::V_MOV_B32, + O::V_CMP_EQ_U32, O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, + O::S_ENDPGM}; + test.compute_info.threads_num[0] = 64; + test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1; + test.compute_info.thread_ids_num = 1; + test.has_compute_info = true; + return test; +} + TestCase ScalarMaskProvenanceOverlapAndMixedBinary() { using O = ShaderOpcode; @@ -21701,6 +21775,42 @@ GraphicsCase GraphicsInterpolationExport() { O::V_MOV_B32, O::EXP, O::S_ENDPGM}}; } +GraphicsCase GraphicsAncillaryLayer(bool front_face) { + GraphicsCase test; + test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer"; + test.pixel_ancillary = true; + test.pixel_front_face = front_face; + test.layers = 8; + const auto ancillary = Vgpr(front_face ? 3 : 2); + test.fragment_code.push_back(EncodeSop1(0x04, 8, 126)); + test.fragment_code.push_back(EncodeSop1(0x0a, 126, 126)); + AppendVop3(&test.fragment_code, 0x148, 4, ancillary, InlineU32(16), InlineU32(11)); + AppendVop3(&test.fragment_code, 0x149, 5, ancillary, InlineU32(16), InlineU32(3)); + test.fragment_code.push_back(EncodeSop1(0x04, 126, 8)); + AppendVMovU32(&test.fragment_code, 6, test.layers); + test.fragment_code.push_back(EncodeVopc(0xc1, Vgpr(4), 6)); // Dynamic layer < count. + test.fragment_code.push_back(EncodeSop1(0x04, 126, 106)); + test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4))); // float(unsigned layer) + test.fragment_code.push_back(EncodeVop1(0x05, 1, Vgpr(5))); // float(signed low 3 bits) + AppendVMovLiteral(&test.fragment_code, 2, 0); + AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u); + test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); + test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3)); + AppendEnd(&test.fragment_code); + test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_BFE_I32, + ShaderOpcode::V_CVT_F32_U32, ShaderOpcode::V_CVT_F32_I32, + ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64, + ShaderOpcode::S_WQM_B64, ShaderOpcode::V_CMP_LT_U32, + ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM}; + for (u32 layer = 0; layer < test.layers; ++layer) { + const auto signed_bits = layer < 4 ? static_cast(layer) : static_cast(layer) - 8; + test.expected_pixel.insert(test.expected_pixel.end(), + {std::bit_cast(static_cast(layer)), + std::bit_cast(static_cast(signed_bits)), 0, 0x3f800000u}); + } + return test; +} + GraphicsCase GraphicsFlatInterpolatorExport() { using O = ShaderOpcode; @@ -22000,6 +22110,7 @@ std::vector MakeCases() { AddCase(ScalarMaskHighWriteInvalidatesProvenance); AddCase(ScalarSelectB64PreservesMaskProvenance); AddCase(ScalarWqmB64SelectsSccDomain); + AddCase(ScalarWqmB64PreservesPartialMasks); AddCase(ScalarMaskProvenanceOverlapAndMixedBinary); AddCase(ScalarLiteral); AddCase(VectorMoves); @@ -22257,6 +22368,8 @@ std::vector MakeCases() { std::vector MakeGraphicsCases() { return { GraphicsInterpolationExport(), + GraphicsAncillaryLayer(false), + GraphicsAncillaryLayer(true), GraphicsFlatInterpolatorExport(), GraphicsDsAddtidScratchExport(), GraphicsDirectSgprPushConstantExport(), @@ -26535,6 +26648,7 @@ int main(int argc, char **argv) { RunCase(&vulkan, ScalarNotB64UpdatesScc()); RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance()); RunCase(&vulkan, ScalarWqmB64SelectsSccDomain()); + RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks()); RunCase(&vulkan, ScalarMaskProvenanceOverlapAndMixedBinary()); RunCase(&vulkan, ScratchIsPrivatePerInvocation()); RunCase(&vulkan, Vop1MoveRelDestination()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 2b367a921..66c83ed98 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4778,7 +4778,6 @@ void TestImageAddressOperands() { } } - void TestNewShaderRecompilerImageSampleVariants() { const uint32_t shader[] = { EncodeMimg0(0x24, 0xf), @@ -5932,6 +5931,166 @@ void TestPsInputCountRegisterDecode() { "SPI_PS_IN_CONTROL NUM_INTERP decoding failed"); } +void TestPixelAncillaryLayerInput() { + using namespace ShaderRecompiler::IR; + std::vector shader = { + EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11), + EncodeVop3Word0(0x149, 7), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 3), + EncodeExp0(0x00, 0x3), EncodeExp1(6, 7, 0, 0), EncodeSopp(0x01), + }; + HW::PixelShaderInfo regs{}; + regs.ps_regs.data_addr = reinterpret_cast(shader.data()); + ShaderMappedData mapped{}; + mapped.code_size_bytes = shader.size() * sizeof(uint32_t); + ShaderMapUserData(regs.ps_regs.data_addr, mapped); + HW::ShaderRegisters sh{}; + sh.ps_input_ena = sh.ps_input_addr = 0x3320; // Linear I/J, X/Y, front-face, ancillary. + const std::array mappings{}; + ShaderPixelInputInfo pixel{}; + (void)PrepareProgram(regs, sh, mappings, pixel); + Check(pixel.ps_system_input_base == 2 && pixel.ps_front_face && pixel.ps_ancillary, + "pixel ancillary register flags were not retained"); + const auto ancillary_key = MakeStageStaticKey(pixel); + pixel.ps_ancillary = false; + Check(ancillary_key != MakeStageStaticKey(pixel), + "pixel ancillary input is missing from the shader cache key"); + pixel.ps_ancillary = true; + auto options = MakeCompileOptions(ShaderType::Pixel); + options.input_info.pixel = &pixel; + auto result = RecompileForTest(shader, options); + Check(ProgramHasInput(result.program, StageInputKind::Layer), + "ancillary after X/Y and front-face did not provide the layer input"); + Check(!ProgramHasInput(result.program, StageInputKind::PackedAncillary), + "symbolic ancillary input survived its layer extraction"); + Check(SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 9u, 14u) && + result.spirv[1] == 0x00010500u && + SpirvContainsCapability(result.spirv, 69u) && + !SpirvContainsCapability(result.spirv, 5254u), + "fragment layer input lacks its builtin, flat decoration or capability"); + const auto source = DisassembleSpirvBinary(result.spirv); + Check(SpirvSourceHasInstructionUsing(source, "OpLoad", "%int %gl_Layer") && + SpirvInstructionOpcodeCount(result.spirv, 202u) == 1u && + SpirvInstructionOpcodeCount(result.spirv, 203u) == 1u, + "layer extraction lost its scalar integer load or signedness"); + Check(!SpirvHasDecorationValue(result.spirv, 11u, 18u), + "layer extraction unexpectedly enabled SampleId"); + CheckSpirvBinaryValidates(result.spirv); + + for (const uint32_t destination : {126u, 106u, 0u}) { + std::vector wqm = { + EncodeSop1(0x04, 8, 126), // Save the incoming EXEC predicate. + EncodeSop1(0x0a, destination, 126), + EncodeSop1(0x04, 126, destination), + EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11), + EncodeVop1(0x01, 5, 6 + 256), // Overwrite the packed ancillary register. + EncodeSop1(0x04, 126, 8), + EncodeExp0(0x00, 0x1), EncodeExp1(5, 0, 0, 0), EncodeSopp(0x01), + }; + auto wqm_result = RecompileForTest(wqm, options); + Check(ProgramHasInput(wqm_result.program, StageInputKind::Layer) && + !ProgramHasInput(wqm_result.program, StageInputKind::PackedAncillary), + "WQM retained an unreachable old ancillary register value"); + CheckSpirvBinaryValidates(wqm_result.spirv); + } + + const uint32_t unused[] = {EncodeSopp(0x01)}; + auto unused_result = RecompileForTest(unused, options); + Check(!ProgramHasInput(unused_result.program, StageInputKind::Layer) && + unused_result.spirv[1] == 0x00010300u && + !SpirvContainsCapability(unused_result.spirv, 69u), + "unused ancillary input changed the module requirements"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + shader[1] = EncodeVop3Word1(5 + 256, 128 + 8, 128 + 4); + ExpectFatal([&] { (void)RecompileForTest(shader, options); }, + "unsupported live ancillary field was silently replaced"); +#endif +} + +void TestMaskedValueDemand() { + using namespace ShaderRecompiler::IR; + enum class Use { Guarded, Conjunction, TrueArm, Unguarded, OtherGuard, Predicate, + ReadLane, ExplicitLod, ExplicitGradient, LevelZero, ImplicitLod, + Mixed, Phi, Loop }; + for (const auto use : {Use::Guarded, Use::Conjunction, Use::TrueArm, Use::Unguarded, + Use::OtherGuard, Use::Predicate, Use::ReadLane, + Use::ExplicitLod, Use::ExplicitGradient, Use::LevelZero, + Use::ImplicitLod, Use::Mixed, Use::Phi, Use::Loop}) { + Program program; + for (uint32_t i = 0; i < 4; ++i) { + program.block_storage.push_back(std::make_unique()); + program.blocks.push_back(program.block_storage.back().get()); + } + auto &entry = *program.blocks[0]; + auto &join = *program.blocks[3]; + entry.AddBranch(program.blocks[1]); + entry.AddBranch(program.blocks[2]); + program.blocks[1]->AddBranch(&join); + program.blocks[2]->AddBranch(&join); + if (use == Use::Loop) join.AddBranch(&entry); + const auto input = Value(&entry.AppendNewInst(ValueOpcode::GetUserData, + {Value(static_cast(0))})); + const auto predicate = Value(&entry.AppendNewInst(ValueOpcode::INotEqual32, + {input, Value(0u)})); + const auto other = Value(&entry.AppendNewInst(ValueOpcode::IEqual32, + {input, Value(2u)})); + auto &masked = entry.AppendNewInst(ValueOpcode::SelectU32, + {predicate, Value(10u), Value(20u)}); + Value value(&masked); + if (use == Use::Phi || use == Use::Loop) { + auto &phi = join.AppendNewInst(ValueOpcode::Phi, {}, static_cast(Type::U32)); + phi.AddPhiOperand(program.blocks[1], value); + phi.AddPhiOperand(program.blocks[2], Value(0u)); + value = Value(&phi); + } + value = Value(&join.AppendNewInst(ValueOpcode::BitCastF32U32, {value})); + value = Value(&join.AppendNewInst(ValueOpcode::FPMul32, {value, Value::F32(2.f)})); + value = Value(&join.AppendNewInst(ValueOpcode::BitCastU32F32, {value})); + if (use == Use::ReadLane) { + value = Value(&join.AppendNewInst(ValueOpcode::ReadLane, {value, Value(0u)})); + } + if (use == Use::ExplicitLod || use == Use::ExplicitGradient || + use == Use::LevelZero || use == Use::ImplicitLod) { + const auto zero = Value(0u); + auto &address = join.AppendNewInst(ValueOpcode::MakeImageAddress, + {value, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero, zero}); + auto &image = entry.AppendNewInst(ValueOpcode::GetImageResource, + {zero, zero, zero, zero, zero, zero, zero, zero}); + auto &sampler = entry.AppendNewInst(ValueOpcode::GetSamplerResource, + {zero, zero, zero, zero}); + program.memory_info.emplace_back().image_sample_flags = + use == Use::ExplicitLod ? ShaderRecompiler::Decoder::ImageSampleFlagLod : + use == Use::ExplicitGradient ? ShaderRecompiler::Decoder::ImageSampleFlagDerivative : + use == Use::LevelZero ? ShaderRecompiler::Decoder::ImageSampleFlagLevelZero : 0u; + value = Value(&join.AppendNewInst(ValueOpcode::ImageSampleRaw, + {Value(&image), Value(&sampler), Value(&address)})); + value = Value(&join.AppendNewInst(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)})); + } + auto guard = predicate; + if (use == Use::Conjunction) { + guard = Value(&join.AppendNewInst(ValueOpcode::LogicalAnd, {predicate, other})); + } else if (use == Use::OtherGuard) { + guard = other; + } else if (use == Use::Unguarded) { + guard = Value(true); + } else if (use == Use::Predicate) { + guard = Value(&join.AppendNewInst(ValueOpcode::INotEqual32, {value, Value(0u)})); + } else if (use == Use::TrueArm) { + value = Value(&join.AppendNewInst(ValueOpcode::SelectU32, {predicate, value, Value(0u)})); + guard = Value(true); + } + value = Value(&join.AppendNewInst(ValueOpcode::CompositeConstructU32x4, + {value, Value(0u), Value(0u), Value(0u)})); + join.AppendNewInst(ValueOpcode::SetAttribute, {value, guard}); + if (use == Use::Mixed) join.AppendNewInst(ValueOpcode::SetAttribute, {value, Value(true)}); + EliminateMaskedValues(program); + const bool removed = Value(&masked).Resolve() == Value(10u); + const bool safe = use == Use::Guarded || use == Use::Conjunction || use == Use::TrueArm || + use == Use::ExplicitLod || use == Use::ExplicitGradient || + use == Use::LevelZero || use == Use::Phi; + Check(removed == safe, "masked value demand crossed an unguarded or nonlocal use"); + } +} + void TestGraphicsCreateInterpolantMapping() { ShaderRegister regs[32] = {}; @@ -9170,12 +9329,13 @@ void TestNewShaderRecompilerAuxPositionExports() { SpirvHasDecorationValue(all.spirv, 11u, 9u) && SpirvContainsCapability(all.spirv, 32u) && SpirvContainsCapability(all.spirv, 33u) && - SpirvContainsCapability(all.spirv, 5254u), + SpirvContainsCapability(all.spirv, 69u), "auxiliary vertex BuiltIns or capabilities are missing"); const auto all_source = DisassembleSpirvBinary(all.spirv); - Check(Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") && + Check(all.spirv[1] == 0x00010500u && + !Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") && SpirvBuiltInStoreUsesAndConstant(all.spirv, 9u, 0x7ffu), - "layer export extension or GFX10 layer mask is missing"); + "layer export module version or GFX10 layer mask is incorrect"); const auto positions = std::count_if( all.program.info.outputs.begin(), all.program.info.outputs.end(), [](const auto &output) { @@ -12004,6 +12164,8 @@ int main() { TestNewShaderRecompilerIrLookupMissFailsExplicitly(); TestNewShaderRecompilerRejectsDppOn64BitCompares(); TestPsInputCountRegisterDecode(); + TestPixelAncillaryLayerInput(); + TestMaskedValueDemand(); TestNewShaderRecompilerUnbasedFlatUsesBda(); TestNewShaderRecompilerFlatUserPointerUsesDma(); TestNewShaderRecompilerFlatAddressDomainsUseDma();