From 7e42513faf761a8c5a45f18dc94e5570b78eeba3 Mon Sep 17 00:00:00 2001 From: Senaxx Date: Wed, 19 Aug 2026 00:50:13 +0200 Subject: [PATCH] perf(shader): cache analysis (#259) perf(shader): cache SPIR-V requirement analysis Co-authored-by: nmzik --- .../renderer/pipeline/shaderSubgroup.cpp | 2 +- .../shader/recompiler/ShaderRecompiler.cpp | 1 + .../recompiler/backend/spirv/SpirvEmitter.cpp | 127 ++++++++++---- .../recompiler/backend/spirv/SpirvEmitter.h | 2 +- .../backend/spirv/spirvEmitterAnalysis.cpp | 155 ------------------ .../backend/spirv/spirvEmitterInternal.h | 14 -- src/graphics/shader/recompiler/ir/ShaderIR.h | 14 ++ tests/shaderCfgTests.cpp | 85 +++++++++- 8 files changed, 193 insertions(+), 207 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/shaderSubgroup.cpp b/src/graphics/host_gpu/renderer/pipeline/shaderSubgroup.cpp index 5836081bf..cb8f3177d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaderSubgroup.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaderSubgroup.cpp @@ -51,7 +51,7 @@ ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabili guest_wave_size <= capabilities.max_subgroup_size) { return {ShaderSubgroupMode::Controlled, guest_wave_size}; } - if (!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(program)) { + if (!ShaderRecompiler::Spirv::GetProgramRequirements(program).requires_exact_subgroup) { return {ShaderSubgroupMode::FlattenedMasks, 0}; } return {}; diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 1a1e0e3b5..c005c6bdb 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -942,6 +942,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, if (!IR::AllocateBindings(ir, layout_options, error)) { return false; } + ir.spirv_requirements = Spirv::GetProgramRequirements(ir); std::string ir_dump; if (options.dump_ir) { ir_dump = MakeIrDump(cfg, ir); diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index 5abac1d1f..d3d9816c3 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -277,38 +277,108 @@ void CollectValueRegisterMirrors(const IR::ValueProgram& program, } } -bool ValueProgramHasOpcode(const IR::ValueProgram& program, IR::ValueOpcode opcode) { - return std::ranges::any_of(program.blocks, [opcode](const IR::Block* block) { - return std::ranges::any_of( - *block, [opcode](const IR::Inst& inst) { return inst.GetOpcode() == opcode; }); - }); -} - -bool ProgramRequiresExactSubgroupSize(const IR::Program& program) { - if (program.values == nullptr) { - return false; +IR::SpirvRequirements GetProgramRequirements(const IR::Program& program) { + if (program.spirv_requirements.has_value()) { + return *program.spirv_requirements; } + IR::SpirvRequirements requirements; + if (program.values == nullptr) { + return requirements; + } + const auto MarkExactSubgroup = [&] { + requirements.requires_exact_subgroup = true; + requirements.subgroup_ballot = true; + }; for (const auto* block: program.values->blocks) { for (const auto& inst: *block) { switch (inst.GetOpcode()) { - case IR::ValueOpcode::Ballot: + case IR::ValueOpcode::Ballot: MarkExactSubgroup(); break; case IR::ValueOpcode::DppMoveU32: + case IR::ValueOpcode::ReadFirstLane: + case IR::ValueOpcode::ReadLane: { + MarkExactSubgroup(); + requirements.subgroup_shuffle = true; + if (inst.GetOpcode() == IR::ValueOpcode::DppMoveU32) { + requirements.subgroup_local_invocation_id = true; + } + break; + } case IR::ValueOpcode::DppUpdateU32: case IR::ValueOpcode::WqmMask: - case IR::ValueOpcode::ReadFirstLane: - case IR::ValueOpcode::ReadLane: - case IR::ValueOpcode::WriteLane: + case IR::ValueOpcode::WriteLane: { + MarkExactSubgroup(); + requirements.subgroup_local_invocation_id = true; + break; + } case IR::ValueOpcode::Permlane16U32: - case IR::ValueOpcode::SwizzleU32: - case IR::ValueOpcode::DataAppend: - case IR::ValueOpcode::DataConsume: case IR::ValueOpcode::GdsDataAppend: - case IR::ValueOpcode::GdsDataConsume: return true; + case IR::ValueOpcode::GdsDataConsume: { + MarkExactSubgroup(); + requirements.subgroup_shuffle = true; + requirements.subgroup_local_invocation_id = true; + break; + } + case IR::ValueOpcode::SwizzleU32: { + MarkExactSubgroup(); + requirements.subgroup_shuffle = true; + requirements.subgroup_local_invocation_id = true; + requirements.function_lds = program.stage != ShaderType::Compute; + break; + } + case IR::ValueOpcode::DataAppend: + case IR::ValueOpcode::DataConsume: { + MarkExactSubgroup(); + requirements.subgroup_shuffle = true; + requirements.subgroup_local_invocation_id = true; + [[fallthrough]]; + } + case IR::ValueOpcode::LoadSharedU8: + case IR::ValueOpcode::LoadSharedU16: + case IR::ValueOpcode::LoadSharedU32: + case IR::ValueOpcode::WriteSharedU8: + case IR::ValueOpcode::WriteSharedU16: + case IR::ValueOpcode::WriteSharedU32: + case IR::ValueOpcode::SharedAtomicFMin32: + case IR::ValueOpcode::SharedAtomicFMax32: + case IR::ValueOpcode::SharedAtomicSwap32: + case IR::ValueOpcode::SharedAtomicIAdd32: + case IR::ValueOpcode::SharedAtomicISub32: + case IR::ValueOpcode::SharedAtomicSMin32: + case IR::ValueOpcode::SharedAtomicUMin32: + case IR::ValueOpcode::SharedAtomicSMax32: + case IR::ValueOpcode::SharedAtomicUMax32: + case IR::ValueOpcode::SharedAtomicAnd32: + case IR::ValueOpcode::SharedAtomicOr32: + case IR::ValueOpcode::SharedAtomicXor32: { + const auto index = inst.Flags().index; + if (program.stage != ShaderType::Compute && + index < program.values->memory_info.size() && + program.values->memory_info[index].kind == IR::ResourceKind::Lds) { + requirements.function_lds = true; + } + break; + } + case IR::ValueOpcode::LaneId: + requirements.subgroup_local_invocation_id = true; + break; + case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break; + case IR::ValueOpcode::ImageGatherRaw: + requirements.image_gather_extended = true; + break; + case IR::ValueOpcode::SetAttribute: { + const auto index = inst.Flags().index; + if (program.stage == ShaderType::Pixel && + index < program.values->export_info.size() && + program.values->export_info[index].vm) { + requirements.pixel_valid_mask = true; + } + break; + } default: break; } } } - return false; + return requirements; } bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources, @@ -344,11 +414,12 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc return false; } EmitterState state(program, resources, input_info); + const auto requirements = GetProgramRequirements(program); state.stage = program.stage; state.wave_size = program.wave_size; state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation; state.exact_subgroup_operations = - !state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program); + !state.per_invocation_masks && requirements.requires_exact_subgroup; state.registers.reserve(InitialEmitterVectorReserve); state.inputs.reserve(InitialEmitterVectorReserve); state.outputs.reserve(InitialEmitterVectorReserve); @@ -356,15 +427,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc state.reachable_blocks.reserve(InitialEmitterVectorReserve); CollectValueRegisterMirrors(value_program, state.registers); CopyProgramInputsAndOutputs(state, program); - state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program) || - (!state.per_invocation_masks && - ValueProgramHasOpcode(value_program, IR::ValueOpcode::Ballot)); - state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program); - state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program); - state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program); - state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program); - state.needs_function_lds = ProgramNeedsFunctionLds(program); - state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program); + state.needs_subgroup_ballot = requirements.subgroup_ballot; + state.needs_subgroup_shuffle = requirements.subgroup_shuffle; + state.needs_subgroup_local_invocation_id = requirements.subgroup_local_invocation_id; + state.needs_compute_derivatives = requirements.compute_derivatives; + state.needs_image_gather_extended = requirements.image_gather_extended; + state.needs_function_lds = requirements.function_lds; + state.needs_pixel_valid_mask = requirements.pixel_valid_mask; AllocateInputVariables(state); AllocateOutputVariables(state); AllocateDescriptorVariables(state); diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h index 476bce9fa..cf5195bd5 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h @@ -9,7 +9,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv { -bool ProgramRequiresExactSubgroupSize(const IR::Program& program); +IR::SpirvRequirements GetProgramRequirements(const IR::Program& program); bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources, ShaderStageInputInfo input_info, std::vector& spirv, std::string* error); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index 08b504c5f..c02d69249 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -88,161 +88,6 @@ uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo return 0; } -bool ProgramNeedsComputeDerivatives(const IR::Program& program) { - if (program.values == nullptr) { - return false; - } - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - if (inst.GetOpcode() == IR::ValueOpcode::ImageQueryLod) { - return true; - } - } - } - return false; -} - -bool ProgramNeedsImageGatherExtended(const IR::Program& program) { - if (program.values == nullptr) { - return false; - } - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - if (inst.GetOpcode() == IR::ValueOpcode::ImageGatherRaw) { - return true; - } - } - } - return false; -} - -bool ProgramNeedsFunctionLds(const IR::Program& program) { - if (program.stage == ShaderType::Compute || program.values == nullptr) { - return false; - } - - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - switch (inst.GetOpcode()) { - case IR::ValueOpcode::SwizzleU32: return true; - case IR::ValueOpcode::LoadSharedU8: - case IR::ValueOpcode::LoadSharedU16: - case IR::ValueOpcode::LoadSharedU32: - case IR::ValueOpcode::WriteSharedU8: - case IR::ValueOpcode::WriteSharedU16: - case IR::ValueOpcode::WriteSharedU32: - case IR::ValueOpcode::SharedAtomicFMin32: - case IR::ValueOpcode::SharedAtomicFMax32: - case IR::ValueOpcode::SharedAtomicSwap32: - case IR::ValueOpcode::SharedAtomicIAdd32: - case IR::ValueOpcode::SharedAtomicISub32: - case IR::ValueOpcode::SharedAtomicSMin32: - case IR::ValueOpcode::SharedAtomicUMin32: - case IR::ValueOpcode::SharedAtomicSMax32: - case IR::ValueOpcode::SharedAtomicUMax32: - case IR::ValueOpcode::SharedAtomicAnd32: - case IR::ValueOpcode::SharedAtomicOr32: - case IR::ValueOpcode::SharedAtomicXor32: - case IR::ValueOpcode::DataAppend: - case IR::ValueOpcode::DataConsume: { - const auto index = inst.Flags().index; - if (index < program.values->memory_info.size() && - program.values->memory_info[index].kind == IR::ResourceKind::Lds) { - return true; - } - break; - } - default: break; - } - } - } - return false; -} - -bool ProgramNeedsPixelValidMask(const IR::Program& program) { - if (program.stage != ShaderType::Pixel || program.values == nullptr) { - return false; - } - - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - if (inst.GetOpcode() != IR::ValueOpcode::SetAttribute) { - continue; - } - const auto index = inst.Flags().index; - if (index < program.values->export_info.size() && - program.values->export_info[index].vm) { - return true; - } - } - } - return false; -} - -bool ProgramNeedsSubgroupBallot(const IR::Program& program) { - if (program.values == nullptr) { - return false; - } - if (program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation) { - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - if (inst.GetOpcode() == IR::ValueOpcode::WqmMask) { - return true; - } - } - } - } - return ProgramRequiresExactSubgroupSize(program); -} - -bool ProgramNeedsSubgroupShuffle(const IR::Program& program) { - if (program.values == nullptr) { - return false; - } - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - switch (inst.GetOpcode()) { - case IR::ValueOpcode::DppMoveU32: - case IR::ValueOpcode::ReadFirstLane: - case IR::ValueOpcode::ReadLane: - case IR::ValueOpcode::Permlane16U32: - case IR::ValueOpcode::DataAppend: - case IR::ValueOpcode::DataConsume: - case IR::ValueOpcode::GdsDataAppend: - case IR::ValueOpcode::GdsDataConsume: - case IR::ValueOpcode::SwizzleU32: return true; - default: break; - } - } - } - return false; -} - -bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) { - if (program.values == nullptr) { - return false; - } - for (const auto* block: program.values->blocks) { - for (const auto& inst: *block) { - switch (inst.GetOpcode()) { - case IR::ValueOpcode::DppMoveU32: - case IR::ValueOpcode::DppUpdateU32: - case IR::ValueOpcode::WqmMask: - case IR::ValueOpcode::LaneId: - case IR::ValueOpcode::WriteLane: - case IR::ValueOpcode::Permlane16U32: - case IR::ValueOpcode::DataAppend: - case IR::ValueOpcode::DataConsume: - case IR::ValueOpcode::GdsDataAppend: - case IR::ValueOpcode::GdsDataConsume: - case IR::ValueOpcode::SwizzleU32: return true; - default: break; - } - } - } - return false; -} - uint32_t PointerForRegister(const EmitterState& state, IR::Register reg) { for (const auto& binding: state.registers) { if (binding.reg == reg) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 3e58faac6..7c8ccbaa1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -631,20 +631,6 @@ void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp); -bool ProgramNeedsComputeDerivatives(const IR::Program& program); - -bool ProgramNeedsImageGatherExtended(const IR::Program& program); - -bool ProgramNeedsFunctionLds(const IR::Program& program); - -bool ProgramNeedsPixelValidMask(const IR::Program& program); - -bool ProgramNeedsSubgroupBallot(const IR::Program& program); - -bool ProgramNeedsSubgroupShuffle(const IR::Program& program); - -bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program); - uint32_t PointerForRegister(const EmitterState& state, IR::Register reg); uint32_t ConstantU32(EmitterState& state, uint32_t value); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 16c869e90..1b34237d1 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -351,6 +352,17 @@ struct ShaderInfo { bool operator==(const ShaderInfo& other) const = default; }; +struct SpirvRequirements { + bool requires_exact_subgroup = false; + bool subgroup_ballot = false; + bool subgroup_shuffle = false; + bool subgroup_local_invocation_id = false; + bool compute_derivatives = false; + bool image_gather_extended = false; + bool function_lds = false; + bool pixel_valid_mask = false; +}; + struct Program { ShaderType stage = ShaderType::Unknown; ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave; @@ -369,6 +381,8 @@ struct Program { bool shader_info_complete = false; BindingLayout bindings; bool binding_layout_complete = false; + + std::optional spirv_requirements; }; bool LowerProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, ShaderType stage, diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index b57be4633..36077f261 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -668,6 +668,69 @@ void TestNormalizedImageContracts() { "Vulkan image-view compatibility classes diverged from production"); } +void TestSpirvRequirementsAnalysis() { + using namespace ShaderRecompiler::IR; + + Program program; + program.stage = ShaderType::Pixel; + program.values = std::make_shared(); + program.values->block_storage.push_back(std::make_unique()); + auto *block = program.values->block_storage.back().get(); + program.values->blocks.push_back(block); + program.values->memory_info.push_back({.kind = ResourceKind::Lds}); + program.values->export_info.push_back({.vm = true}); + + block->AppendNewInst(ValueOpcode::DppMoveU32, {Value(0u), Value(true)}); + block->AppendNewInst(ValueOpcode::ImageQueryLod, + {Value(0u), Value(0u), Value(0u)}); + block->AppendNewInst(ValueOpcode::ImageGatherRaw, + {Value(0u), Value(0u), Value(0u)}); + auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32, + {Value(0u), Value(0u), Value(true)}); + shared.SetFlags(MemoryFlags{.index = 0}); + auto &export_value = + block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)}); + export_value.SetFlags(ExportFlags{.index = 0}); + + const auto requirements = + ShaderRecompiler::Spirv::GetProgramRequirements(program); + Check(requirements.requires_exact_subgroup && requirements.subgroup_ballot && + requirements.subgroup_shuffle && + requirements.subgroup_local_invocation_id && + requirements.compute_derivatives && + requirements.image_gather_extended && requirements.function_lds && + requirements.pixel_valid_mask, + "consolidated SPIR-V requirements missed an IR dependency"); + + program.stage = ShaderType::Compute; + const auto compute_requirements = + ShaderRecompiler::Spirv::GetProgramRequirements(program); + Check(!compute_requirements.function_lds && + !compute_requirements.pixel_valid_mask, + "stage-specific SPIR-V requirements leaked into compute"); + + program.stage = ShaderType::Pixel; + shared.SetFlags(MemoryFlags{.index = 1}); + export_value.SetFlags(ExportFlags{.index = 1}); + const auto invalid_metadata = + ShaderRecompiler::Spirv::GetProgramRequirements(program); + Check(!invalid_metadata.function_lds && !invalid_metadata.pixel_valid_mask, + "invalid SPIR-V requirement metadata was accepted"); + + Program empty; + const auto empty_requirements = + ShaderRecompiler::Spirv::GetProgramRequirements(empty); + Check(!empty_requirements.requires_exact_subgroup && + !empty_requirements.subgroup_ballot && + !empty_requirements.subgroup_shuffle && + !empty_requirements.subgroup_local_invocation_id && + !empty_requirements.compute_derivatives && + !empty_requirements.image_gather_extended && + !empty_requirements.function_lds && + !empty_requirements.pixel_valid_mask, + "empty IR unexpectedly requested SPIR-V features"); +} + void TestNativeSubgroupPolicy() { const auto SetTypedValues = [](ShaderRecompiler::IR::Program &program, ShaderRecompiler::IR::ValueOpcode opcode = @@ -686,6 +749,9 @@ void TestNativeSubgroupPolicy() { block->AppendNewInst(opcode, {Value(&resource), Value(0u), Value(true), Value(1u), Value(0u)}); } + program.spirv_requirements.reset(); + program.spirv_requirements = + ShaderRecompiler::Spirv::GetProgramRequirements(program); }; GraphicContext context; @@ -717,7 +783,8 @@ void TestNativeSubgroupPolicy() { ShaderRecompiler::IR::Program cross_lane = safe; SetTypedValues(cross_lane, ShaderRecompiler::IR::ValueOpcode::ReadLane); - Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(cross_lane) && + Check(ShaderRecompiler::Spirv::GetProgramRequirements(cross_lane) + .requires_exact_subgroup && ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context}, vk::ShaderStageFlagBits::eVertex, cross_lane) @@ -734,7 +801,8 @@ void TestNativeSubgroupPolicy() { ShaderRecompiler::IR::Program zero_exec = safe; zero_exec.lane_mask_mode = ShaderLaneMaskMode::NativeWave; SetTypedValues(zero_exec); - Check(!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(zero_exec) && + Check(!ShaderRecompiler::Spirv::GetProgramRequirements(zero_exec) + .requires_exact_subgroup && ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context}, vk::ShaderStageFlagBits::eCompute, zero_exec) @@ -744,18 +812,20 @@ void TestNativeSubgroupPolicy() { ShaderRecompiler::IR::Program selective_exec = safe; SetTypedValues(selective_exec, ShaderRecompiler::IR::ValueOpcode::Ballot); - Check( - ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(selective_exec), - "selective EXEC write was classified mask-free"); + Check(ShaderRecompiler::Spirv::GetProgramRequirements(selective_exec) + .requires_exact_subgroup, + "selective EXEC write was classified mask-free"); ShaderRecompiler::IR::Program carry = safe; SetTypedValues(carry, ShaderRecompiler::IR::ValueOpcode::Ballot); - Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(carry), + Check(ShaderRecompiler::Spirv::GetProgramRequirements(carry) + .requires_exact_subgroup, "lane-varying VCC carry producer was classified mask-free"); ShaderRecompiler::IR::Program vcc_branch = safe; SetTypedValues(vcc_branch, ShaderRecompiler::IR::ValueOpcode::Ballot); - Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(vcc_branch), + Check(ShaderRecompiler::Spirv::GetProgramRequirements(vcc_branch) + .requires_exact_subgroup, "whole-wave VCC branch was classified mask-free"); ShaderRecompiler::IR::Program ds_partial = safe; @@ -9387,6 +9457,7 @@ int main() { TestResourceDescriptorClassification(); TestNativeShaderResourceDependencies(); TestNormalizedImageContracts(); + TestSpirvRequirementsAnalysis(); TestNativeSubgroupPolicy(); TestNewShaderRecompilerSMovB32(); TestNewShaderRecompilerCapturedVop1SdwaByteConvert();