#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 namespace Libs::Graphics::ShaderRecompiler::IR { 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 SupportsIndirectBufferLoad(ValueOpcode opcode) const { return !formatted && !typed && data_bits == 32u && (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; uint32_t source = 0; 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 operator==(const BufferResource& other) const = default; }; enum class ImageMipMode { None, DynamicStorage }; constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu; struct ImageResource { static constexpr uint32_t NoIndirectImage = UINT32_MAX; uint32_t source = 0; 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 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; }; struct SamplerResource { uint32_t source = 0; uint32_t first_use_pc = 0; bool force_point_filtering = false; bool depth_compare = false; bool integer_border = 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, BaryCoordSmoothCentroid, BaryCoordNoPerspective, WorkgroupId, LocalInvocationId, LocalInvocationIndex, GlobalInvocationId, Parameter, }; enum class StageOutputKind { Position, Parameter, Mrt, 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 = 43u; enum class DescriptorBindingKind : uint32_t { Buffers = 0u, Samplers = FirstImageBinding + ImageBindingCount, Gds, BdaPagetable, FaultBuffer, FlattenedSrt, ShaderData, Count, }; static_assert(static_cast(DescriptorBindingKind::Samplers) == 44u); static_assert(static_cast(DescriptorBindingKind::Count) == 50u); 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 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; } 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 DescriptorBinding { DescriptorBindingKind kind = DescriptorBindingKind::Buffers; std::vector resources; bool operator==(const DescriptorBinding& other) const = default; }; struct BindingLayout { uint32_t push_data_start_dword = PushData::NoStart; uint32_t memory_offset_dword = 0; uint32_t memory_offset_count = 0; std::vector user_data_registers; std::vector descriptors; [[nodiscard]] uint32_t ShaderDataDwords() const { return memory_offset_dword + (memory_offset_count + 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 BindingLayout& other) const = default; }; struct ShaderInfo { static constexpr uint32_t MaxBuffers = 64; static constexpr uint32_t MaxImages = 64; static constexpr uint32_t MaxSamplers = 32; static constexpr uint32_t MaxSampledPairs = 64; std::vector buffers; std::vector images; std::vector samplers; std::vector sampled_pairs; std::vector inputs; std::vector outputs; 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 operator==(const ShaderInfo& other) const = default; }; struct BlockInfo { uint32_t id = 0; uint32_t start_pc = 0; uint32_t end_pc = 0; CFG::Terminator terminator; Value condition; Value indirect_target; }; struct DescriptorSource { struct IndirectImage { uint32_t material_source = UINT32_MAX; uint32_t table_source = 0; uint32_t selector_stride = 0; uint32_t selector_offset = 0; uint32_t table_offset = 0; Value key_count; Value selector_mask; std::vector sources; bool operator==(const IndirectImage& other) const = default; }; std::array dwords {}; uint32_t dword_count = 0; std::optional indirect_image; 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; BindingLayout bindings; }; struct UniformFillPlan { UniformFill fill; std::array values; }; // 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 EvaluationContext { struct Entry { uint64_t value = 0; uint64_t generation = 0; }; std::vector values; uint64_t generation = 0; }; ResourcePlan() = default; ~ResourcePlan(); ResourcePlan(const ResourcePlan&) = delete; ResourcePlan& operator=(const ResourcePlan&) = delete; ResourcePlan(ResourcePlan&&) noexcept = default; 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 control_flow; std::vector srt_reads; std::vector clean_flat_slots; bool requires_specialization_memory = false; bool capture_specialization_reads = false; bool srt_plan_complete = false; bool resource_tracking_complete = false; ShaderInfo info; UniformFillPlan uniform_fill; // 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 uint32_t evaluation_depth = 0; mutable std::vector active_sources; mutable std::vector visited_blocks; mutable std::vector pending_blocks; mutable std::vector material_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; std::vector block_info; struct ScalarWrite { uint32_t pc; ScalarReg reg; }; 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; BindingLayout bindings; bool binding_layout_complete = false; }; std::string ProgramToString(const Program& program); bool HasShaderMemoryWrites(const Program& program); void ValidateProgram(const Program& program, bool require_ssa); void ResolveControlFlowIdentities(Program& program); bool EquivalentValue(const ResourcePlan& program, Value left, Value right); Value ResolveInvariantPhi(const ResourcePlan& program, Value value); } // namespace Libs::Graphics::ShaderRecompiler::IR #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_ */