#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h" #include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/shader.h" #include #include namespace Libs::Graphics::ShaderRecompiler::Spirv { using ShaderError::Fail; namespace { bool ValidateNativeProgram(const IR::Program& program, std::string* error) { using Kind = IR::DescriptorBindingKind; constexpr auto KindCount = static_cast(Kind::Count); std::array, KindCount> expected; std::array present {}; const auto Dense = [](size_t size) { std::vector values(size); for (uint32_t i = 0; i < values.size(); i++) { values[i] = i; } return values; }; auto Expect = [&](Kind kind, std::vector resources = {}) { const auto index = static_cast(kind); present[index] = true; expected[index] = std::move(resources); }; if (!program.info.buffers.empty()) { Expect(Kind::Buffers, Dense(program.info.buffers.size())); } for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto kind = IR::DescriptorBindingForImage(program.info.images[i]); if (!kind.has_value()) { return Fail(error, "native shader plan has an invalid image class"); } present[static_cast(*kind)] = true; const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage; const auto count = dynamic ? program.info.images[i].mip_count : 1u; if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { return Fail(error, "native shader plan has an invalid image mip descriptor count"); } expected[static_cast(*kind)].insert(expected[static_cast(*kind)].end(), count, i); } if (!program.info.samplers.empty()) { Expect(Kind::Samplers, Dense(program.info.samplers.size())); } bool uses_gds = false; for (const auto* block: program.blocks) { for (const auto& inst: *block) { if (IR::SharedAccessOf(inst.GetOpcode()) == IR::SharedAccess::None) { continue; } const auto index = inst.Flags().index; if (index >= program.memory_info.size()) { return Fail(error, "shared operation has invalid memory metadata"); } const auto kind = program.memory_info[index].kind; if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) { return Fail(error, "shared operation has invalid resource kind"); } uses_gds |= kind == IR::ResourceKind::Gds; } } if (uses_gds) { Expect(Kind::Gds); } if (program.info.uses_dma) { Expect(Kind::BdaPagetable); Expect(Kind::FaultBuffer); } const bool uses_flattened_runtime = !program.srt_reads.empty() || std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) { return image.indirect_mapping_capacity != 0u; }); if (uses_flattened_runtime) { Expect(Kind::FlattenedSrt); } if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) { Expect(Kind::UserData); } std::array seen {}; for (const auto& binding: program.bindings.descriptors) { const auto kind = static_cast(binding.kind); if (kind >= KindCount || seen[kind] || !present[kind] || binding.resources != expected[kind]) { return Fail(error, "native descriptor groups do not match shader topology"); } seen[kind] = true; } for (size_t i = 0; i < KindCount; i++) { if (present[i] != seen[i]) { return Fail(error, "native shader plan is missing a required descriptor group"); } } const auto has_user_storage = present[static_cast(Kind::UserData)]; if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 || program.bindings.push_constant_offset + program.bindings.push_constant_size > IR::NativePushConstantSize || program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() || program.bindings.memory_offset_count != program.info.buffers.size() || (!has_user_storage && program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) || (has_user_storage && program.bindings.push_constant_size != 0) || !std::is_sorted(program.bindings.user_data_registers.begin(), program.bindings.user_data_registers.end()) || std::adjacent_find(program.bindings.user_data_registers.begin(), program.bindings.user_data_registers.end()) != program.bindings.user_data_registers.end()) { return Fail(error, "native user-data layout is inconsistent"); } const auto planning_only_handle = [&](const IR::Inst& handle) { return !handle.Uses().empty() && std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) { const auto op = use.user->GetOpcode(); if (op != IR::ValueOpcode::LoadAddressU32 && op != IR::ValueOpcode::ReadConstBuffer) { return false; } const auto index = use.user->Flags().index; return index < program.memory_info.size() && program.memory_info[index].planning_only; }); }; for (const auto* block: program.blocks) { for (const auto& inst: *block) { const auto dense = inst.Flags(); switch (inst.GetOpcode()) { case IR::ValueOpcode::GetBufferResource: if (planning_only_handle(inst)) { break; } if (dense >= program.info.buffers.size()) { return Fail(error, "typed buffer handle has an invalid dense resource"); } break; case IR::ValueOpcode::GetAddressResource: if (planning_only_handle(inst)) { break; } if (inst.NumArgs() != 2 || !program.info.uses_dma) { return Fail(error, "typed address handle has invalid DMA metadata"); } break; case IR::ValueOpcode::GetScratchResource: if (inst.NumArgs() != 0 || program.scratch_dwords == 0) { return Fail(error, "typed scratch handle has invalid shader metadata"); } break; case IR::ValueOpcode::GetImageResource: if (dense >= program.info.images.size()) { return Fail(error, "typed image handle has an invalid dense resource"); } break; case IR::ValueOpcode::GetSamplerResource: if (dense >= program.info.samplers.size()) { return Fail(error, "typed sampler handle has an invalid dense resource"); } break; case IR::ValueOpcode::ReadConst: { const auto slot = inst.Arg(1).Resolve(); if (!slot.IsImmediate() || slot.GetType() != IR::Type::U32 || slot.U32() >= program.srt_reads.size()) { return Fail(error, "flattened SRT read has an invalid dense slot"); } break; } default: break; } } } return true; } } // namespace bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { program.spirv_requirements.reset(); IR::SpirvRequirements requirements {}; const auto MarkBallot = [&] { requirements.subgroup_ballot = true; }; for (const auto* block: program.blocks) { for (const auto& inst: *block) { const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access; if (address_access != IR::AddressAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { return Fail(error, "address operation has invalid memory metadata"); } if (program.memory_info[memory_index].kind == IR::ResourceKind::Scratch) { if (program.scratch_dwords == 0) { return Fail(error, "scratch operation has no per-thread storage"); } requirements.function_scratch = true; } else if (address_access == IR::AddressAccess::Write) { return Fail(error, "writable FLAT/GLOBAL addresses require GPU ownership tracking"); } } if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { return Fail(error, "buffer operation has invalid memory metadata"); } const auto& memory = program.memory_info[memory_index]; if (memory.kind == IR::ResourceKind::Buffer) { if (memory.resource >= program.info.buffers.size()) { return Fail(error, "buffer operation has invalid resource metadata"); } if ((program.info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) { if (program.stage != ShaderType::Compute) { return Fail(error, "buffer ADD_TID is only valid for compute shaders"); } requirements.subgroup_local_invocation_id = true; } } } const auto shared_access = IR::SharedAccessOf(inst.GetOpcode()); if (shared_access != IR::SharedAccess::None) { const auto index = inst.Flags().index; if (index >= program.memory_info.size()) { return Fail(error, "shared operation has invalid memory metadata"); } const auto kind = program.memory_info[index].kind; if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) { return Fail(error, "shared operation has invalid resource kind"); } if (program.stage != ShaderType::Compute && kind == IR::ResourceKind::Lds) { requirements.function_lds = true; } if (shared_access == IR::SharedAccess::Append || shared_access == IR::SharedAccess::Consume) { MarkBallot(); requirements.subgroup_shuffle = true; requirements.subgroup_local_invocation_id = true; } } switch (inst.GetOpcode()) { case IR::ValueOpcode::Ballot: MarkBallot(); break; case IR::ValueOpcode::DppMoveU32: case IR::ValueOpcode::ReadFirstLane: case IR::ValueOpcode::ReadLane: { MarkBallot(); 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::WriteLane: { MarkBallot(); requirements.subgroup_local_invocation_id = true; break; } case IR::ValueOpcode::Permlane16U32: { MarkBallot(); requirements.subgroup_shuffle = true; requirements.subgroup_local_invocation_id = true; break; } case IR::ValueOpcode::SwizzleU32: { MarkBallot(); requirements.subgroup_shuffle = true; requirements.subgroup_local_invocation_id = 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 (index >= program.export_info.size()) { return Fail(error, "attribute export has invalid metadata"); } if (program.stage == ShaderType::Pixel && program.export_info[index].vm) { requirements.pixel_valid_mask = true; } break; } default: break; } } } program.spirv_requirements.emplace(requirements); return true; } bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources, ShaderStageInputInfo input_info, std::vector& spirv, std::string* error) { using namespace Emitter; if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex && program.stage != ShaderType::Pixel) { SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders"); return false; } if (!program.srt_plan_complete || !program.resource_tracking_complete || !program.shader_info_complete || !program.binding_layout_complete || !program.spirv_requirements.has_value()) { SetError(error, "SPIR-V emitter requires a fully planned native shader program"); return false; } if (!IR::ValidateResourceSnapshot(program, resources, error)) { return false; } if (!IR::ValidateResourceSpecialization(program, resources, error)) { return false; } if (!ValidateNativeProgram(program, error)) { return false; } if (!IR::ValidateProgram(program, true, error)) { return false; } EmitterState state(program, resources, input_info); state.stage = program.stage; state.wave_size = program.wave_size; state.inputs.reserve(program.info.inputs.size()); state.outputs.reserve(program.info.outputs.size()); state.interface_variables.reserve(program.info.inputs.size() + program.info.outputs.size()); CopyProgramInputsAndOutputs(state, program); AllocateInputVariables(state); AllocateOutputVariables(state); DefineModule(state); if (!EmitProgram(state, program, error)) { return false; } auto binary = state.builder.Build(); if (binary.empty()) { SetError(error, "SPIR-V builder returned an empty module"); return false; } spirv = std::move(binary); return true; } } // namespace Libs::Graphics::ShaderRecompiler::Spirv