#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_ #include "common/common.h" #include "common/stringUtils.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_format.h" #include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/ir/Block.h" #include "graphics/shader/recompiler/ir/ResourceSnapshot.h" #include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.h" #include "graphics/shader/shader.h" #include #include #include #include #include #include #include #include #include namespace Xbyak { class CodeGenerator; } namespace Libs::Graphics::ShaderRecompiler::IR { class SrtWalker; // Finite descriptor domains also bound the arithmetic used to prove their indices. constexpr uint32_t MaxIndirectDescriptorProbes = 65536u; enum class ResourceKind { None, ScalarBuffer, ScalarAddress, Buffer, IndirectBuffer, Flat, FlatLocal, Global, Scratch, Lds, Gds, Image, Sampler }; [[nodiscard]] constexpr bool IsAddressResourceKind(ResourceKind kind) { return kind == ResourceKind::ScalarAddress || kind == ResourceKind::Flat || kind == ResourceKind::FlatLocal || kind == ResourceKind::Global || kind == ResourceKind::Scratch; } struct MemoryInfo { ResourceKind kind = ResourceKind::None; uint32_t resource = 0; uint32_t sampler = 0; uint32_t offset = 0; uint32_t secondary_offset = 0; uint32_t dmask = 0; uint32_t data_dwords = 1; uint32_t data_bits = 32; uint32_t component_index = 0; uint32_t component_count = 1; uint32_t data_format = 0; uint32_t number_format = 0; uint32_t image_sample_flags = 0; Decoder::ImageDimension image_dimension = Decoder::ImageDimension::Unknown; uint32_t image_address_components = 0; bool address_is_full = false; bool data_signed = false; bool typed = false; bool formatted = false; bool image_has_mip = false; bool image_r128 = false; bool idxen = false; bool offen = false; bool coherent = false; bool planning_only = false; [[nodiscard]] bool IsCoherentBufferPairLoad(ValueOpcode opcode) const { return kind == ResourceKind::Buffer && opcode == ValueOpcode::LoadBufferU32x2 && coherent && !typed && !formatted && data_bits == 32u; } [[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const { if (typed) return false; if (data_bits == 8u) return !formatted && opcode == ValueOpcode::LoadBufferU8; if (data_bits == 16u) return !formatted && opcode == ValueOpcode::LoadBufferU16; return data_bits == 32u && (formatted ? opcode == ValueOpcode::LoadBufferU32 : opcode == ValueOpcode::ReadConstBuffer || opcode == ValueOpcode::LoadBufferU32 || opcode == ValueOpcode::LoadBufferU32x2 || opcode == ValueOpcode::LoadBufferU32x3 || opcode == ValueOpcode::LoadBufferU32x4); } bool operator==(const MemoryInfo& other) const = default; }; enum class ExportTargetKind { Unknown, Null, Position, Primitive, Parameter, Mrt, MrtZ }; struct ExportInfo { ExportTargetKind kind = ExportTargetKind::Unknown; uint32_t target = 0; uint32_t index = 0; uint32_t en = 0; bool done = false; bool compr = false; bool vm = false; bool operator==(const ExportInfo& other) const = default; }; struct BufferResource { static constexpr uint32_t NoImageAlias = UINT32_MAX; static constexpr uint32_t NoIndirectBuffer = UINT32_MAX; uint32_t source = 0; uint32_t descriptor_index = UINT32_MAX; uint32_t first_use_pc = 0; uint32_t max_byte_extent = 0; uint32_t packed_stride = 0; Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid; uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7); uint32_t image_alias = NoImageAlias; bool read = false; bool written = false; bool atomic = false; bool formatted = false; bool scalar = false; bool live = false; uint32_t indirect_root = NoIndirectBuffer; uint32_t indirect_mapping_offset = 0; uint32_t indirect_search_iterations = 0; std::vector indirect_resources; bool operator==(const BufferResource& other) const = default; }; enum class ImageMipMode { None, Dynamic }; constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu; struct ImageResource { static constexpr uint32_t NoIndirectImage = UINT32_MAX; uint32_t source = 0; uint32_t descriptor_index = UINT32_MAX; uint32_t first_use_pc = 0; ImageResourceClass resource_class = ImageResourceClass::None; Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported; Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown; ImageMipMode mip_mode = ImageMipMode::None; uint32_t mip_count = 1; Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid; uint32_t shader_swizzle = ShaderImageIdentitySwizzle; bool read = false; bool written = false; bool atomic = false; bool atomic64 = false; bool unfiltered_access_only = true; bool depth_compare = false; bool cube = false; bool r128 = false; uint32_t indirect_root = NoIndirectImage; uint32_t indirect_mapping_offset = 0; uint32_t indirect_search_iterations = 0; std::vector indirect_resources; bool operator==(const ImageResource& other) const = default; }; [[nodiscard]] constexpr Prospero::BufferFormat RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format) { if (image.resource_class != ImageResourceClass::Storage || !image.written) return Prospero::BufferFormat::kInvalid; if (format == Prospero::BufferFormat::k32SInt && (image.atomic ? !image.atomic64 : !image.read)) return Prospero::BufferFormat::k32UInt; if (format == Prospero::BufferFormat::k16SInt && !image.read && !image.atomic) return Prospero::BufferFormat::k16UInt; return Prospero::BufferFormat::kInvalid; } struct SamplerResource { static constexpr uint32_t NoIndirectSampler = UINT32_MAX; uint32_t source = 0; uint32_t first_use_pc = 0; // Native filtering/border variants share the original sampler's runtime descriptor. uint32_t snapshot_index = 0; uint32_t indirect_root = NoIndirectSampler; uint32_t indirect_mapping_offset = 0; uint32_t indirect_search_iterations = 0; std::vector indirect_resources; bool force_point_filtering = false; bool depth_compare = false; bool integer_border = false; bool gather_lod = false; bool operator==(const SamplerResource& other) const = default; }; struct SampledResourcePair { uint32_t image = 0; uint32_t sampler = 0; uint32_t first_use_pc = 0; bool operator==(const SampledResourcePair& other) const = default; }; enum class TessellationAttribute { LocalOutput, ControlInput, ControlOutput, EvaluationInput, PatchOutput, Factor }; enum class StageInputKind { VertexIndex, InvocationId, PrimitiveId, TessCoord, InstanceIndex, FragCoord, FrontFacing, PackedAncillary, Layer, SampleId, BaryCoordSmooth, BaryCoordSmoothSample, BaryCoordSmoothCentroid, BaryCoordNoPerspective, WorkgroupId, NumWorkgroups, LocalInvocationId, LocalInvocationIndex, GlobalInvocationId, Parameter, }; enum class StageOutputKind { Position, Parameter, Mrt, ObjectPrimitiveId, Depth, SampleMask, PointSize, ClipDistance, CullDistance, Layer, ViewportIndex }; struct PositionExportComponent { uint32_t clip_distance = UINT32_MAX; uint32_t cull_distance = UINT32_MAX; bool point_size = false; bool layer = false; bool viewport = false; }; inline PositionExportComponent DecodePositionExportComponent(uint32_t control, uint32_t pos_index, uint32_t component) { PositionExportComponent result; if (pos_index == 0 || component >= 4) { return result; } uint32_t slot = pos_index - 1; uint32_t vector = 3; for (uint32_t i = 0; i < 3; i++) { if ((control & (1u << (21u + i))) != 0) { if (slot == 0) { vector = i; break; } slot--; } } if (vector == 3) { return result; } if (vector == 0) { result.point_size = component == 0 && (control & (1u << 16u)) != 0; result.layer = component == 2 && (control & (1u << 18u)) != 0; result.viewport = component == 2 && (control & (1u << 19u)) != 0; return result; } const auto scalar = (vector - 1) * 4 + component; const auto lower = (1u << scalar) - 1u; const auto clip = control & 0xffu; const auto cull = (control >> 8u) & 0xffu; if ((clip & (1u << scalar)) != 0) { result.clip_distance = std::popcount(clip & lower); } if ((cull & (1u << scalar)) != 0) { result.cull_distance = std::popcount(cull & lower); } return result; } struct StageInput { StageInputKind kind = StageInputKind::VertexIndex; uint32_t location = 0; uint32_t component_count = 1; std::string debug_name; bool per_vertex = false; bool operator==(const StageInput& other) const = default; }; struct StageOutput { StageOutputKind kind = StageOutputKind::Parameter; uint32_t index = 0; uint32_t location = 0; std::string debug_name; bool operator==(const StageOutput& other) const = default; }; inline constexpr uint32_t FirstImageBinding = 1u; inline constexpr uint32_t FirstComparisonImageBinding = 22u; inline constexpr uint32_t FirstStorageImageBinding = 29u; inline constexpr uint32_t ImageBindingCount = 48u; enum class DescriptorBindingKind : uint32_t { Buffers = 0u, Samplers = FirstImageBinding + ImageBindingCount, Gds, BdaPagetable, FaultBuffer, FlattenedSrt, ShaderData, SharedMemory, Count, }; static_assert(static_cast(DescriptorBindingKind::Samplers) == 49u); static_assert(static_cast(DescriptorBindingKind::Count) == 56u); struct PushData { static constexpr uint32_t DwordCount = 32; static constexpr uint32_t MeshDrawDwordCount = 6; static constexpr uint32_t NoStart = UINT32_MAX; std::array dwords {}; [[nodiscard]] static constexpr uint32_t GraphicsDrawDwords(bool mesh, bool object_id) { return mesh ? MeshDrawDwordCount : static_cast(object_id); } [[nodiscard]] static constexpr bool CanFit(uint32_t start, uint32_t size) { return size != 0 && start <= DwordCount && size <= DwordCount - start; } [[nodiscard]] static constexpr uint32_t StartFor(uint32_t cursor, uint32_t size) { return CanFit(cursor, size) ? cursor : NoStart; } }; static_assert(sizeof(PushData) == 128); constexpr uint32_t NativePushConstantSize = sizeof(PushData); [[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) { const uint32_t group = stage == ShaderType::Pixel ? 1u : stage == ShaderType::TessellationControl ? 2u : stage == ShaderType::TessellationEvaluation ? 3u : 0u; return static_cast(kind) + group * static_cast(DescriptorBindingKind::Count); } [[nodiscard]] constexpr ImageResourceClass ImageBindingResourceClass(DescriptorBindingKind kind) { const auto value = static_cast(kind); if (value >= FirstImageBinding && value < FirstStorageImageBinding) { return ImageResourceClass::Sampled; } if (value >= FirstStorageImageBinding && value < static_cast(DescriptorBindingKind::Samplers)) { return ImageResourceClass::Storage; } return ImageResourceClass::None; } [[nodiscard]] constexpr uint32_t ImageBindingIndex(DescriptorBindingKind kind) { return static_cast(kind) - FirstImageBinding; } [[nodiscard]] constexpr std::optional DescriptorBindingForImage(const ImageResource& image) { constexpr uint32_t SampledFloatBinding = 1u; constexpr uint32_t SampledUintBinding = 8u; constexpr uint32_t SampledSintBinding = 15u; constexpr uint32_t StorageFloatBinding = FirstStorageImageBinding; constexpr uint32_t StorageUintBinding = StorageFloatBinding + 5u; constexpr uint32_t AtomicUintBinding = StorageUintBinding + 5u; uint32_t base = 0; bool sampled = false; if (image.resource_class == ImageResourceClass::Sampled) { if (image.atomic) { return std::nullopt; } sampled = true; switch (image.numeric_class) { case Prospero::TextureNumericClass::Float: base = image.depth_compare ? FirstComparisonImageBinding : SampledFloatBinding; break; case Prospero::TextureNumericClass::Uint: base = SampledUintBinding; break; case Prospero::TextureNumericClass::Sint: base = SampledSintBinding; break; case Prospero::TextureNumericClass::Unsupported: return std::nullopt; default: return std::nullopt; } if (image.depth_compare && image.numeric_class != Prospero::TextureNumericClass::Float) { return std::nullopt; } } else if (image.resource_class == ImageResourceClass::Storage) { if (image.atomic) { if (image.numeric_class != Prospero::TextureNumericClass::Uint) { return std::nullopt; } base = AtomicUintBinding + (image.atomic64 ? 5u : 0u); } else { switch (image.numeric_class) { case Prospero::TextureNumericClass::Float: base = StorageFloatBinding; break; case Prospero::TextureNumericClass::Uint: base = StorageUintBinding; break; case Prospero::TextureNumericClass::Sint: case Prospero::TextureNumericClass::Unsupported: return std::nullopt; default: return std::nullopt; } } } else { return std::nullopt; } uint32_t dimension = 0; switch (image.dimension) { case Decoder::ImageDimension::Dim1D: break; case Decoder::ImageDimension::Dim1DArray: dimension = 1u; break; case Decoder::ImageDimension::Dim2D: dimension = 2u; break; case Decoder::ImageDimension::Dim2DArray: dimension = 3u; break; case Decoder::ImageDimension::Dim2DMsaa: if (!sampled) { return std::nullopt; } dimension = 4u; break; case Decoder::ImageDimension::Dim2DMsaaArray: if (!sampled) { return std::nullopt; } dimension = 5u; break; case Decoder::ImageDimension::Dim3D: dimension = sampled ? 6u : 4u; break; case Decoder::ImageDimension::Unknown: return std::nullopt; default: return std::nullopt; } return static_cast(base + dimension); } struct Bindings { std::array(DescriptorBindingKind::Count)> descriptor_counts {}; uint64_t descriptor_mask = 0; uint32_t push_data_start_dword = PushData::NoStart; uint32_t dispatch_thread_dword = PushData::NoStart; uint32_t memory_offset_dword = 0; [[nodiscard]] uint32_t ShaderDataDwords() const { return memory_offset_dword + (descriptor_counts[static_cast(DescriptorBindingKind::Buffers)] + 3u) / 4u; } [[nodiscard]] bool UsesPushData() const { return push_data_start_dword != PushData::NoStart; } void AdvancePushData(uint32_t& cursor) const { if (UsesPushData()) { cursor = push_data_start_dword + ShaderDataDwords(); } } bool operator==(const Bindings& other) const = default; }; struct ShaderInfo { static constexpr uint32_t MaxBufferRoots = 64; static constexpr uint32_t MaxImages = 64; static constexpr uint32_t MaxSamplers = 32; std::vector buffers; std::vector images; std::vector samplers; std::vector sampled_pairs; std::vector inputs; std::vector outputs; std::vector user_data_registers; std::array vertex_fetch_components {}; int32_t vertex_offset_sgpr = -1; int32_t instance_offset_sgpr = -1; bool has_bitwise_xor = false; bool uses_dma = false; bool uses_swizzle = false; bool uses_lds = false; bool uses_gds = false; bool uses_flattened_srt = false; bool uses_dispatch_threads = false; bool bvh = false; bool subgroup_ballot = false; bool subgroup_vote = false; bool subgroup_barrier = false; bool subgroup_shuffle = false; bool subgroup_arithmetic = 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; bool buffer_u8 = false; bool buffer_u16 = false; bool shared_int64_atomics = false; bool coherent_buffers = false; bool float64 = false; bool operator==(const ShaderInfo& other) const = default; }; struct DescriptorSource { struct IndirectDescriptor { struct SelectorRead { struct CountedRows { uint32_t source = UINT32_MAX; uint32_t stride = 0; // Byte spacing between selector rows, not their length. bool operator==(const CountedRows& other) const = default; }; uint32_t source = UINT32_MAX; uint32_t stride = 0; uint32_t offset = 0; uint32_t maximum = UINT32_MAX; uint32_t bit_offset = 0; uint32_t bit_mask = UINT32_MAX; bool global = false; // Four DWORD bitmap words per record; set-bit ranks subtract from the last key. bool bitset = false; std::optional rows; std::vector> byte_ranges; bool operator==(const SelectorRead& other) const = default; }; struct PointerTable { uint32_t offset = 0; uint32_t stride = 0; uint32_t count = 1; bool indexed = false; // Parent pointers use raw MUBUF record addressing. bool operator==(const PointerTable& other) const = default; }; std::optional selector; std::optional table_maximum; std::optional pointer_table; uint32_t table_source = 0; uint32_t table_offset = 0; uint32_t table_immediate = 0; uint32_t table_stride = 0; uint32_t table_record_bytes = 0; bool table_scalar = false; bool indexed = false; bool signed_count = false; uint32_t workgroup_axis = UINT32_MAX; Value key_count; // First selector index, or first descriptor key for a direct or bitmap range. Value selector_first; Value selector_mask; std::vector sources; bool operator==(const IndirectDescriptor& other) const = default; }; std::array dwords {}; uint32_t dword_count = 0; std::optional indirect_descriptor; bool operator==(const DescriptorSource& other) const = default; }; struct SrtRead { Value value; uint32_t flat_offset = 0; bool operator==(const SrtRead& other) const = default; }; struct ResourceBlock { // Conditional successors are ordered true, false; an empty condition follows every edge. Value condition; std::vector successors; std::vector sources; std::vector srt_reads; }; // Stable shader metadata consumed by the renderer after native IR has been discarded. struct CompiledShaderInfo { ShaderType stage = ShaderType::Unknown; uint64_t shader_hash = 0; uint32_t wave_size = 64; uint32_t user_data_base = 0; uint32_t user_data_count = 64; uint32_t scratch_dwords = 0; uint32_t param_export_mask = 0; bool has_address_writes = false; ShaderInfo info; Bindings bindings; }; struct UniformFillPlan { UniformFill fill; std::array values; Value pattern_size; Value element_count; }; // Resource analysis retained by the shader cache. It owns immutable descriptor/SRT, // condition and fill values without translated blocks, plus reusable evaluation scratch. struct ResourcePlan { struct SelectorKeys { uint32_t source = UINT32_MAX; std::vector values; bool resolved = false; }; struct EvaluationContext { struct Entry { uint64_t value = 0; uint64_t generation = 0; }; std::vector values; uint64_t generation = 0; const SrtWalker* session = nullptr; Value active_mask; uint8_t read_policy = 0; }; ResourcePlan(); ~ResourcePlan(); ResourcePlan(const ResourcePlan&) = delete; ResourcePlan& operator=(const ResourcePlan&) = delete; ResourcePlan(ResourcePlan&&) noexcept; ResourcePlan& operator=(ResourcePlan&& other) noexcept; ShaderType stage = ShaderType::Unknown; uint64_t shader_hash = 0; uint32_t user_data_base = 0; uint32_t user_data_count = 64; std::list value_storage; std::vector memory_info; std::vector descriptor_sources; std::vector selector_groups; std::vector control_flow; std::unordered_map> control_phi_predecessors; std::vector srt_reads; bool requires_specialization_memory = false; std::vector> source_reads; bool capture_specialization_reads = false; bool srt_plan_complete = false; bool resource_tracking_complete = false; ShaderInfo info; UniformFillPlan uniform_fill; std::unique_ptr srt_code; // GPU-thread scratch for nested clean/EXEC memos, activity and material keys. mutable std::deque evaluation_contexts; mutable uint32_t evaluation_value_count = 0; mutable std::vector active_sources; mutable std::vector visited_blocks; mutable std::vector reachable_blocks; mutable std::vector pending_blocks; mutable std::vector material_keys; mutable std::vector selector_keys; }; struct Program: ResourcePlan { Program() = default; ~Program(); Program(const Program&) = delete; Program& operator=(const Program&) = delete; Program(Program&&) noexcept = default; Program& operator=(Program&& other) noexcept; CompiledShaderInfo TakeCompiledInfo() &&; std::vector> block_storage; BlockList blocks; uint32_t wave_size = 64; uint32_t scratch_dwords = 0; bool dispatcher_fallback = false; CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None; std::string fallback_reason; struct ScalarWrite { uint32_t pc; ScalarReg reg; Value value; }; std::vector scalar_writes; // Typed memory and export instructions reference shader-local metadata by dense index. // Decoder-only details (such as NSA register numbers) have already become IR operands. std::vector export_info; bool has_address_writes = false; bool shader_info_complete = false; Bindings bindings; }; uint32_t StorageBufferElementBits(const Program& program, const MemoryInfo& memory); std::string ProgramToString(const Program& program); bool HasShaderMemoryWrites(const Program& program); void ValidateProgram(const Program& program, bool require_ssa); bool EquivalentValue(const ResourcePlan& program, Value left, Value right); bool EquivalentSelectorDomain(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& left, const DescriptorSource::IndirectDescriptor& right); Value ResolveInvariantPhi(const ResourcePlan& program, Value value); Value ResolveActiveU32(Value value, Value active); } // namespace Libs::Graphics::ShaderRecompiler::IR #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_ */