#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_INTERNAL_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_INTERNAL_H_ #include "common/common.h" #include "common/stringUtils.h" #include "graphics/shader/recompiler/BufferFormat.h" #include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/BindingLayout.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { struct InputBinding : IR::StageInput { uint32_t variable_id = 0; }; struct OutputBinding : IR::StageOutput { uint32_t variable_id = 0; uint32_t mesh_data_variable = 0; }; using ImageDimension = Decoder::ImageDimension; struct ImageDimensionInfo { ImageDimension dimension; uint32_t spirv_dimension; uint32_t coordinate_components; uint32_t spatial_components; uint32_t arrayed; uint32_t multisampled; }; constexpr std::array ImageDimensions {{ {ImageDimension::Dim1D, spv::Dim1D, 1, 1, 0, 0}, {ImageDimension::Dim1DArray, spv::Dim1D, 2, 1, 1, 0}, {ImageDimension::Dim2D, spv::Dim2D, 2, 2, 0, 0}, {ImageDimension::Dim2DArray, spv::Dim2D, 3, 2, 1, 0}, {ImageDimension::Dim3D, spv::Dim3D, 3, 3, 0, 0}, {ImageDimension::Dim2DMsaa, spv::Dim2D, 2, 2, 0, 1}, {ImageDimension::Dim2DMsaaArray, spv::Dim2D, 3, 2, 1, 1}, }}; const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension); struct SpirvRequirements { 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 function_scratch = false; bool pixel_valid_mask = false; bool buffer_int64_atomics = false; }; SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program); struct EmitterState { EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_) : builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u), program(program_), input_info(input_info_), requirements(AnalyzeProgramRequirements(program_)) {} Builder builder; const IR::Program& program; ShaderStageInputInfo input_info; std::array tess_variables {}; uint32_t tess_inner_variable = 0; uint32_t tess_patch_base = 0; const SpirvRequirements requirements; uint32_t lane_count = 1; uint32_t lane_half = 0; uint32_t storage_buffer_variable = 0; uint32_t storage_buffer_u64_variable = 0; std::array memory_byte_offsets {}; uint32_t bda_pagetable_variable = 0; uint32_t fault_buffer_variable = 0; uint32_t bda_pointer_function = 0; uint32_t gds_variable = 0; uint32_t gds_length = 0; uint32_t push_constant_variable = 0; uint32_t shader_data_storage_variable = 0; uint32_t flattened_srt_variable = 0; uint32_t lds_variable = 0; std::array scratch_variable {}; std::array image_variables {}; uint32_t sampler_variable = 0; uint32_t main_func = 0; uint32_t mesh_guest_func = 0; uint32_t mesh_allocation = 0; uint32_t mesh_primitive_data = 0; uint32_t mesh_primitives = 0; uint32_t mesh_cull = 0; uint32_t entry_label = 0; uint32_t current_label = 0; const IR::Block* current_block = nullptr; uint32_t pixel_valid_mask_variable = 0; uint32_t subgroup_local_invocation_id_variable = 0; uint32_t per_vertex_variable = 0; uint32_t point_size_variable = 0; uint32_t clip_distance_variable = 0; uint32_t invalid_position_clip_distance = UINT32_MAX; uint32_t cull_distance_variable = 0; uint32_t layer_variable = 0; uint32_t viewport_index_variable = 0; uint32_t depth_variable = 0; uint32_t sample_mask_variable = 0; std::vector inputs; std::vector outputs; std::vector interface_variables; std::unordered_map labels; }; uint32_t TypeVoid(EmitterState& state); uint32_t TypeBool(EmitterState& state); uint32_t TypeBoolVector(EmitterState& state, uint32_t components); uint32_t TypeU32(EmitterState& state); uint32_t TypeU64(EmitterState& state); uint32_t TypeScalarU64(EmitterState& state); uint32_t TypeU32Pair(EmitterState& state); uint32_t TypeI32(EmitterState& state); uint32_t TypeI32Pair(EmitterState& state); uint32_t TypeF32(EmitterState& state); uint32_t TypeU32Vector(EmitterState& state, uint32_t components); uint32_t TypeU32Composite(EmitterState& state, uint32_t components); uint32_t TypeI32Vector(EmitterState& state, uint32_t components); uint32_t TypeF32Vector(EmitterState& state, uint32_t components); uint32_t TypePointer(EmitterState& state, spv::StorageClass storage_class, uint32_t pointee); uint32_t TypeFunction(EmitterState& state); uint32_t TypeStorageBufferPointer(EmitterState& state); uint32_t TypeStorageBufferElementPointer(EmitterState& state); uint32_t TypeStorageBufferU64Pointer(EmitterState& state); uint32_t TypeStorageBufferU64ElementPointer(EmitterState& state); uint32_t TypePhysicalU32Pointer(EmitterState& state); uint32_t TypePushConstantElementPointer(EmitterState& state); uint32_t TypeU32ArrayPointer(EmitterState& state, spv::StorageClass storage_class, uint32_t dwords); uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_class); inline void EmitLabel(EmitterState& state, uint32_t label) { state.current_label = label; state.builder.AddFunction(spv::OpLabel, label); } uint32_t TypeId(EmitterState& state, IR::Type type); // Shared instruction construction; typed aliases add no forwarding functions. template uint32_t EmitNative(EmitterState& state, Args... args) { const auto result = state.builder.AllocateId(); state.builder.AddFunction(opcode, TypeId(state, type), result, args...); return result; } uint32_t GlslStd450(EmitterState& state); template uint32_t EmitGlsl(EmitterState& state, Args... args) { return EmitNative(state, GlslStd450(state), opcode, args...); } inline uint32_t Unary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t value) { const auto result = state.builder.AllocateId(); state.builder.AddFunction(opcode, type, result, value); return result; } inline uint32_t Binary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t lhs, uint32_t rhs) { const auto result = state.builder.AllocateId(); state.builder.AddFunction(opcode, type, result, lhs, rhs); return result; } inline uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value, uint32_t false_value) { const auto result = state.builder.AllocateId(); state.builder.AddFunction(spv::OpSelect, type, result, condition, true_value, false_value); return result; } struct ValueEmitContext { explicit ValueEmitContext(EmitterState& state_): state(state_) {} uint32_t Def(IR::Value value); uint32_t Arg(const IR::Inst& inst, size_t index); uint32_t HalfArg(const IR::Inst& inst, size_t index, uint32_t half); uint32_t Ballot(IR::Value predicate); uint32_t FirstLane(uint32_t ballot); uint32_t Shuffle(const IR::Inst& inst, size_t index, uint32_t lane); uint32_t Result(const IR::Inst& inst); uint32_t Define(const IR::Inst& inst, uint32_t value); uint32_t ResourceIndex(IR::Value value, IR::ValueOpcode opcode); const IR::Inst* ImageAddress(IR::Value value); const IR::MemoryInfo& Memory(const IR::Inst& inst) const; const IR::ExportInfo& Export(const IR::Inst& inst) const; uint32_t Label(const IR::Block* block) const; [[noreturn]] void Fail(const char* reason) const; [[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const; EmitterState& state; std::unordered_map definitions; const std::unordered_map* dispatcher_spills = nullptr; std::unordered_map> dispatcher_block_loads; uint32_t scratch_u32_variable = 0; ValueEmitContext* other_half = nullptr; uint32_t half = 0; }; enum class VertexInputScalarKind { Float, Sint, Uint }; constexpr uint32_t NoImageComponent = 0xffffffffu; struct DppTargetLane { uint32_t lane = 0; uint32_t valid = 0; }; struct ImageSampleLayout { uint32_t offset = NoImageComponent; uint32_t dref = NoImageComponent; uint32_t bias = NoImageComponent; uint32_t coord = 0; uint32_t lod = NoImageComponent; uint32_t grad_x = NoImageComponent; uint32_t grad_y = NoImageComponent; }; struct F32Class { uint32_t bits = 0; uint32_t nan = 0; uint32_t zero = 0; }; uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr); bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr); bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr); VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location); uint32_t VertexParameterComponentCount(const InputBinding& input); uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind); uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp); uint32_t ConstantU32(EmitterState& state, uint32_t value); uint32_t EmitSubgroupLocalInvocationId(EmitterState& state); [[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state, IR::DescriptorBindingKind kind, uint32_t resource, const char* reason); uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind, uint32_t resource); uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type, uint32_t variable_id, uint32_t array_index, IR::DescriptorBindingKind kind, uint32_t resource, const char* variable_name); uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass numeric_class); uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class, uint32_t components); uint32_t ImageType(EmitterState& state, const IR::ImageResource& image); uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension); uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource); uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler); uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler); uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource); void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord, uint32_t texel); spv::ExecutionModel ExecutionModelForStage(ShaderType stage); uint32_t ConstantU32(EmitterState& state, uint32_t value); uint32_t ConstantI32(EmitterState& state, int32_t value); uint32_t ConstantF32(EmitterState& state, uint32_t bits); uint32_t ConstantF32Value(EmitterState& state, float value); uint32_t ConstantBool(EmitterState& state, bool value); 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); uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0); uint32_t MeshPrimitivePointer(EmitterState& state); DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control, uint32_t lane_bits); DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount, bool left); DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subid, uint32_t amount); DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row); DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags); uint32_t EmitSubgroupLocalInvocationId(EmitterState& state); uint32_t InputVariableForKind(const EmitterState& state, IR::StageInputKind kind); const InputBinding* InputBindingForParameter(const EmitterState& state, uint32_t location); uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding& input, uint32_t component); uint32_t EmitInputComponentU32(EmitterState& state, IR::StageInputKind kind, uint32_t component); uint32_t EmitLocalInvocationIndex(EmitterState& state); uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t ballot, uint32_t lane); uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane); inline constexpr auto EmitAddU32 = EmitNative; uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs); uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index); uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem); Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem); void EmitMemoryOffsets(EmitterState& state); uint32_t LdsDwordCount(const EmitterState& state); struct MemoryResourceAccess { IR::ResourceKind kind = IR::ResourceKind::None; uint32_t object_pointer = 0; uint32_t length = 0; uint32_t index_offset = 0; uint32_t byte_offset = 0; bool add_index_offset = false; }; MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem); MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state, const IR::MemoryInfo& mem, uint32_t variable, uint32_t pointer_type); uint32_t EmitMemoryElementIndex(EmitterState& state, const MemoryResourceAccess& access, uint32_t raw_index); uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access, uint32_t index); uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access, uint32_t index); uint32_t EmitStorageBufferElementPointer(EmitterState& state, const MemoryResourceAccess& access, uint32_t index, uint32_t pointer_type); uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value); inline constexpr auto EmitTBufferSelectF32 = EmitNative; bool IsSignedFormatComponent(Format::ComponentType type); uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits); uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info, uint32_t component, uint32_t raw); void EmitDeviceAtomicMemoryBarrier(EmitterState& state); uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source, bool max_value); uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control); inline constexpr auto EmitSelectValueU32 = EmitNative; uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask); uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift); inline constexpr auto EmitOrU32 = EmitNative; uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value, uint32_t constant); uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value); uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32); uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value); uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value); inline constexpr auto EmitBitcastF32ToU32 = EmitNative; inline constexpr auto EmitBitcastU32ToF32 = EmitNative; inline constexpr auto EmitAndU32 = EmitNative; inline constexpr auto EmitLogicalAndBool = EmitNative; inline constexpr auto EmitLogicalOrBool = EmitNative; inline constexpr auto EmitLogicalNotBool = EmitNative; F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits); F32Class EmitClassifyF32(EmitterState& state, uint32_t value); uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit, uint32_t class_match); uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask); uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value); inline constexpr auto EmitTruncF32Value = EmitGlsl; uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value); uint32_t EmitTrigCycleF32(EmitterState& state, uint32_t src, bool preserve_signed_zero); inline constexpr auto EmitFNegateValue = EmitNative; inline constexpr auto EmitFAbsValue = EmitGlsl; uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits); void EmitProgram(EmitterState& state); void DefineGetBdaPointer(EmitterState& state); // These templates accept local lambdas from several emitter translation units. template void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) { const auto then_label = state.builder.AllocateId(); const auto merge_label = state.builder.AllocateId(); state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone); state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label); EmitLabel(state, then_label); fn(); state.builder.AddFunction(spv::OpBranch, merge_label); EmitLabel(state, merge_label); } template uint32_t EmitValueOrDefaultIfCondition(EmitterState& state, uint32_t condition, uint32_t type, uint32_t default_value, Fn&& fn) { const auto then_label = state.builder.AllocateId(); const auto header = state.current_label; const auto merge_label = state.builder.AllocateId(); state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone); state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label); EmitLabel(state, then_label); const auto then_value = fn(); const auto then_exit = state.current_label; state.builder.AddFunction(spv::OpBranch, merge_label); EmitLabel(state, merge_label); const auto value = state.builder.AllocateId(); state.builder.AddFunction(spv::OpPhi, type, value, then_value, then_exit, default_value, header); return value; } template uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) { return EmitValueOrDefaultIfCondition(state, condition, TypeU32(state), ConstantU32(state, 0), std::forward(fn)); } template uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) { const auto scope = kind == IR::ResourceKind::Lds ? spv::ScopeWorkgroup : spv::ScopeDevice; const auto memory = [&] { switch (kind) { case IR::ResourceKind::Lds: return spv::MemorySemanticsWorkgroupMemoryMask; case IR::ResourceKind::Image: return spv::MemorySemanticsImageMemoryMask; default: return spv::MemorySemanticsUniformMemoryMask; } }(); const auto preheader = state.builder.AllocateId(); const auto header = state.builder.AllocateId(); const auto cont = state.builder.AllocateId(); const auto merge = state.builder.AllocateId(); const auto initial = state.builder.AllocateId(); const auto observed = state.builder.AllocateId(); const auto exchanged = state.builder.AllocateId(); state.builder.AddFunction(spv::OpBranch, preheader); EmitLabel(state, preheader); state.builder.AddFunction(spv::OpAtomicLoad, TypeU32(state), initial, pointer, ConstantU32(state, scope), ConstantU32(state, spv::MemorySemanticsMaskNone)); state.builder.AddFunction(spv::OpBranch, header); EmitLabel(state, header); state.builder.AddFunction(spv::OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont); const auto next = desired(observed); state.builder.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), exchanged, pointer, ConstantU32(state, scope), ConstantU32(state, spv::MemorySemanticsMaskNone), ConstantU32(state, spv::MemorySemanticsMaskNone), next, observed); const auto success = state.builder.AllocateId(); state.builder.AddFunction(spv::OpIEqual, TypeBool(state), success, exchanged, observed); state.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone); state.builder.AddFunction(spv::OpBranchConditional, success, merge, cont); EmitLabel(state, cont); state.builder.AddFunction(spv::OpBranch, header); EmitLabel(state, merge); state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(state, scope), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory)); return observed; } } // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_INTERNAL_H_ */