#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" #include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include #include #include #include namespace Libs::Graphics::ShaderRecompiler::IR { namespace { constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u); constexpr uint32_t SamplerDword3ReservedMask = 0x3ffff000u; uint32_t PossibleU32Bits(Value value) { value = value.Resolve(); if (value.IsImmediate()) { return value.GetType() == Type::U32 ? value.U32() : UINT32_MAX; } const auto* inst = value.TryInstruction(); if (inst == nullptr) { return UINT32_MAX; } switch (inst->GetOpcode()) { case ValueOpcode::BitwiseAnd32: return PossibleU32Bits(inst->Arg(0)) & PossibleU32Bits(inst->Arg(1)); case ValueOpcode::BitwiseOr32: return PossibleU32Bits(inst->Arg(0)) | PossibleU32Bits(inst->Arg(1)); case ValueOpcode::ShiftLeftLogical32: { const auto shift = inst->Arg(1).Resolve(); return shift.IsImmediate() && shift.GetType() == Type::U32 ? PossibleU32Bits(inst->Arg(0)) << (shift.U32() & 31u) : UINT32_MAX; } default: return UINT32_MAX; } } Value CanonicalizeSampleAdjustDword3(Value value) { for (;;) { value = value.Resolve(); const auto* inst = value.TryInstruction(); if (inst == nullptr || inst->GetOpcode() != ValueOpcode::BitwiseOr32) { return value; } const auto left = inst->Arg(0).Resolve(); const auto right = inst->Arg(1).Resolve(); const bool left_reserved = (PossibleU32Bits(left) & ~SamplerDword3ReservedMask) == 0; const bool right_reserved = (PossibleU32Bits(right) & ~SamplerDword3ReservedMask) == 0; if (left_reserved && right_reserved) { return Value(0u); } if (left_reserved) { value = right; } else if (right_reserved) { value = left; } else { return value; } } } const char* StageName(ShaderType stage) { switch (stage) { case ShaderType::Vertex: return "vertex"; case ShaderType::Pixel: return "pixel"; case ShaderType::Fetch: return "fetch"; case ShaderType::Compute: return "compute"; default: return "unknown"; } } uint32_t ByteExtent(const MemoryInfo& memory) { const auto bytes = std::max((memory.data_bits + 7u) / 8u, 1u); const auto count = std::max(memory.data_dwords, 1u); const auto end = static_cast(memory.offset) + static_cast(bytes) * count; return end > UINT32_MAX ? UINT32_MAX : static_cast(end); } class Tracker { public: explicit Tracker(Program& program): m_program(program), m_info(program.info) { m_info.buffers.clear(); m_info.images.clear(); m_info.samplers.clear(); m_info.sampled_pairs.clear(); m_info.uses_dma = false; m_shader_writes = HasShaderMemoryWrites(program); } void Run() { if (m_program.resource_tracking_complete) { Fail(0, "resources already tracked"); } if (!m_program.srt_plan_complete) { Fail(0, "SRT plan is not ready"); } PlanIndirectImages(); for (auto* block: m_program.blocks) { for (auto& inst: *block) { Collect(inst); } } LinkImageAliases(); for (const auto& patch: m_handle_patches) { patch.handle->SetFlags(patch.resource); } for (const auto& patch: m_memory_patches) { auto& memory = m_program.memory_info[patch.index]; memory.resource = patch.resource; if (patch.has_sampler) { memory.sampler = patch.sampler; } } for (const auto& plan: m_indirect_images) { plan.handle->SetArg(0, plan.key); for (uint32_t dword = 0; dword < 4u; dword++) { plan.handle->SetArg(dword + 1u, plan.roots[dword + 4u]); } for (uint32_t dword = 5u; dword < plan.roots.size(); dword++) { plan.handle->SetArg(dword, plan.key); } for (const auto index: plan.memory) { m_program.memory_info[index].planning_only = true; } } std::erase_if(m_program.dynamic_reads, [&](Value value) { const auto* inst = value.Resolve().TryInstruction(); return std::any_of(m_indirect_images.begin(), m_indirect_images.end(), [&](const IndirectImagePlan& plan) { return std::ranges::find(plan.reads, inst) != plan.reads.end(); }); }); m_program.descriptor_sources = std::move(m_sources); m_program.info = std::move(m_info); m_program.resource_tracking_complete = true; } private: struct HandlePatch { Inst* handle = nullptr; uint32_t resource = 0; }; struct MemoryPatch { uint32_t index = 0; uint32_t resource = 0; uint32_t sampler = 0; bool has_sampler = false; }; struct IndirectImagePlan { Inst* handle = nullptr; uint32_t source = 0; Value key; std::array roots {}; std::array memory {}; std::array reads {}; }; [[noreturn]] void Fail(uint32_t pc, const std::string& reason) const { const auto message = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}", m_program.shader_hash, StageName(m_program.stage), pc, reason); EXIT("%s", message.c_str()); std::abort(); } Value LowerDescriptorPhi(Value value) { value = value.Resolve(); const auto* phi = value.TryInstruction(); if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi || phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32 || m_program.blocks.size() != m_program.block_info.size()) { return value; } for (const auto& [original, selected]: m_descriptor_selections) { if (original == phi) { return selected; } } const auto* merge = phi->Parent(); const auto* branch = phi->PhiBlock(0); if (merge == nullptr || branch == nullptr || phi->PhiBlock(1) == nullptr || branch == phi->PhiBlock(1)) { return value; } if (branch->ImmSuccessors().size() != 2u) { if (branch->ImmPredecessors().size() != 1u) { return value; } branch = branch->ImmPredecessors()[0]; } if (branch == merge || branch->ImmSuccessors().size() != 2u) { return value; } std::array target_ids; for (uint32_t arm = 0; arm < 2; arm++) { const auto* incoming = phi->PhiBlock(arm); if (incoming == merge || (incoming != branch && (incoming->ImmPredecessors().size() != 1u || incoming->ImmPredecessors()[0] != branch || incoming->ImmSuccessors().size() != 1u || incoming->ImmSuccessors()[0] != merge))) { return value; } const auto* target = incoming == branch ? merge : incoming; const auto it = std::ranges::find(m_program.blocks, target); if (it == m_program.blocks.end()) { return value; } target_ids[arm] = m_program.block_info[it - m_program.blocks.begin()].id; } const auto branch_it = std::ranges::find(m_program.blocks, branch); if (branch_it == m_program.blocks.end()) { return value; } const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()]; const auto& term = info.terminator; if (term.kind != CFG::TerminatorKind::ConditionalBranch || !((term.true_block == target_ids[0] && term.false_block == target_ids[1]) || (term.false_block == target_ids[0] && term.true_block == target_ids[1])) || !ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) || !ValidateRuntimeValue(m_program, phi->Arg(0)) || !ValidateRuntimeValue(m_program, phi->Arg(1))) { return value; } // Retain a host expression; replacing the GPU Phi would break SSA dominance. const auto true_arg = term.true_block == target_ids[0] ? 0u : 1u; auto& selected = m_program.value_storage.emplace_back(ValueOpcode::SelectU32); selected.SetArg(0, info.condition); selected.SetArg(1, phi->Arg(true_arg)); selected.SetArg(2, phi->Arg(true_arg ^ 1u)); m_descriptor_selections.emplace_back(phi, Value(&selected)); return Value(&selected); } void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust, DescriptorSource& descriptor, uint32_t pc) { if (handle.NumArgs() != width) { Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}", ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width)); } descriptor.dword_count = width; for (uint32_t i = 0; i < width; i++) { descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i)); } if (sample_adjust) { descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]); } const auto dword0 = descriptor.dwords[0].Resolve(); if (sampler && dword0.IsImmediate() && dword0.GetType() == Type::U32 && (dword0.U32() & SamplerBorderClampMask) == 0) { // Border color and its table index are unused unless a clamp axis selects border mode. descriptor.dwords[3] = Value(0u); } } bool ValidateSource(const DescriptorSource& descriptor, uint32_t& bad_dword) const { for (uint32_t i = 0; i < descriptor.dword_count; i++) { bad_dword = i; if (descriptor.dwords[i].Resolve().GetType() != Type::U32) { return false; } if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) { return false; } } return true; } uint32_t InternSource(const DescriptorSource& descriptor) { for (uint32_t candidate = 0; candidate < m_sources.size(); candidate++) { const auto& current = m_sources[candidate]; if (current.dword_count != descriptor.dword_count || current.indirect_image != descriptor.indirect_image) { continue; } bool same = true; for (uint32_t i = 0; i < descriptor.dword_count; i++) { same = same && EquivalentValue(m_program, current.dwords[i], descriptor.dwords[i]); } if (same) { return candidate; } } m_sources.push_back(descriptor); return static_cast(m_sources.size() - 1); } static bool ImmediateU32(Value value, uint32_t& result) { value = value.Resolve(); if (!value.IsImmediate() || value.GetType() != Type::U32) { return false; } result = value.U32(); return true; } static bool UsesOnly(const Inst& value, std::span users) { return !value.Uses().empty() && std::ranges::all_of(value.Uses(), [&](const Use& use) { return std::ranges::find(users, use.user) != users.end(); }); } const MemoryInfo* ScalarReadMemory(const Inst& read, uint32_t& index) const { if (read.GetOpcode() != ValueOpcode::ReadConstBuffer || read.NumArgs() != 2u) { return nullptr; } index = read.Flags().index; if (index >= m_program.memory_info.size()) { return nullptr; } const auto& memory = m_program.memory_info[index]; return memory.kind == ResourceKind::ScalarBuffer && memory.data_bits == 32u && memory.data_dwords == 1u ? &memory : nullptr; } bool MemoryIndexBelongsTo(uint32_t index, const Inst& owner) const { for (const auto* block: m_program.blocks) { for (const auto& inst: *block) { const auto op = inst.GetOpcode(); if ((BufferAccessOf(op) == BufferAccess::None && AddressOpcodeInfoOf(op).access == AddressAccess::None && ImageOpcodeInfoOf(op).access == ImageAccess::None) || &inst == &owner) { continue; } if (inst.Flags().index == index) { return false; } } } return true; } bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source, DescriptorSource& descriptor) { if (handle.GetOpcode() != ValueOpcode::GetBufferResource) { return false; } MakeSource(handle, 4u, false, false, descriptor, pc); uint32_t bad_dword = 0; if (!ValidateSource(descriptor, bad_dword)) { return false; } source = InternSource(descriptor); return true; } bool MatchMaterialOffset(Value value, Value& selector, uint32_t& stride, uint32_t& offset) const { value = value.Resolve(); offset = 0; auto* candidate = value.TryInstruction(); if (candidate != nullptr && candidate->GetOpcode() == ValueOpcode::IAdd32 && candidate->NumArgs() == 2u) { uint32_t immediate = 0; if (ImmediateU32(candidate->Arg(0), immediate)) { value = candidate->Arg(1).Resolve(); } else if (ImmediateU32(candidate->Arg(1), immediate)) { value = candidate->Arg(0).Resolve(); } else { return false; } offset = immediate; } const auto* multiply = value.TryInstruction(); if (multiply == nullptr || multiply->GetOpcode() != ValueOpcode::IMul32 || multiply->NumArgs() != 2u) { return false; } if (ImmediateU32(multiply->Arg(0), stride)) { selector = multiply->Arg(1).Resolve(); } else if (ImmediateU32(multiply->Arg(1), stride)) { selector = multiply->Arg(0).Resolve(); } else { return false; } const auto* selector_inst = selector.TryInstruction(); return stride != 0u && selector_inst != nullptr && selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane; } bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) { if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) { return false; } std::array heap_reads {}; Inst* heap_handle = nullptr; Value heap_offset; for (uint32_t dword = 0; dword < heap_reads.size(); dword++) { heap_reads[dword] = handle.Arg(dword).Resolve().TryInstruction(); if (heap_reads[dword] == nullptr) { return false; } uint32_t memory_index = 0; const auto* memory = ScalarReadMemory(*heap_reads[dword], memory_index); if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) || !MemoryIndexBelongsTo(memory_index, *heap_reads[dword])) { return false; } auto* current_handle = heap_reads[dword]->Arg(0).Resolve().TryInstruction(); if (current_handle == nullptr || (heap_handle != nullptr && current_handle != heap_handle)) { return false; } heap_handle = current_handle; if (dword == 0u) { heap_offset = heap_reads[dword]->Arg(1).Resolve(); } else if (!EquivalentValue(m_program, heap_offset, heap_reads[dword]->Arg(1))) { return false; } plan.memory[dword] = memory_index; plan.reads[dword] = heap_reads[dword]; } const auto* shift = heap_offset.TryInstruction(); uint32_t shift_amount = 0; if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 || shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(1), shift_amount) || shift_amount != 5u) { return false; } auto* material_read = shift->Arg(0).Resolve().TryInstruction(); if (material_read == nullptr) { return false; } uint32_t material_memory_index = 0; const auto* material_memory = ScalarReadMemory(*material_read, material_memory_index); if (material_memory == nullptr || material_memory->offset != 0u || !MemoryIndexBelongsTo(material_memory_index, *material_read)) { return false; } auto* material_handle = material_read->Arg(0).Resolve().TryInstruction(); if (material_handle == nullptr) { return false; } Value selector; uint32_t selector_stride = 0; uint32_t selector_offset = 0; if (!MatchMaterialOffset(material_read->Arg(1), selector, selector_stride, selector_offset)) { return false; } const std::array material_users {shift}; std::array heap_users {}; std::copy(heap_reads.begin(), heap_reads.end(), heap_users.begin()); const std::array image_users {&handle}; if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, heap_users)) { return false; } for (const auto* read: heap_reads) { if (!UsesOnly(*read, image_users)) { return false; } } DescriptorSource material_source; DescriptorSource heap_source; uint32_t material_source_index = 0; uint32_t heap_source_index = 0; if (!MakeRuntimeBufferSource(*material_handle, pc, material_source_index, material_source) || !MakeRuntimeBufferSource(*heap_handle, pc, heap_source_index, heap_source)) { return false; } DescriptorSource image_source; image_source.dword_count = 8u; std::copy(material_source.dwords.begin(), material_source.dwords.begin() + 4u, image_source.dwords.begin()); std::copy(heap_source.dwords.begin(), heap_source.dwords.begin() + 4u, image_source.dwords.begin() + 4u); image_source.indirect_image = DescriptorSource::IndirectImage { material_source_index, heap_source_index, selector_stride, selector_offset, 0u}; plan.handle = &handle; plan.source = InternSource(image_source); plan.key = Value(material_read); plan.roots = image_source.dwords; return true; } const IndirectImagePlan* FindIndirectImage(const Inst& handle) const { const auto found = std::find_if(m_indirect_images.begin(), m_indirect_images.end(), [&](const IndirectImagePlan& plan) { return plan.handle == &handle; }); return found == m_indirect_images.end() ? nullptr : &*found; } bool IsIndirectPlanningMemory(uint32_t index) const { return std::any_of(m_indirect_images.begin(), m_indirect_images.end(), [&](const IndirectImagePlan& plan) { return std::ranges::find(plan.memory, index) != plan.memory.end(); }); } void PlanIndirectImages() { for (auto* block: m_program.blocks) { for (auto& inst: *block) { if (ImageOpcodeInfoOf(inst.GetOpcode()).access == ImageAccess::None || inst.NumArgs() == 0u) { continue; } auto* handle = inst.Arg(0).Resolve().TryInstruction(); if (handle == nullptr || FindIndirectImage(*handle) != nullptr) { continue; } IndirectImagePlan plan; if (TryMakeIndirectImage(*handle, inst.Flags().pc, plan)) { m_indirect_images.push_back(std::move(plan)); } } } } bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle, uint32_t& source, bool sampler = false, bool sample_adjust = false) { handle = value.Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != expected) { Fail(pc, fmt::format("memory operation requires {}", ValueOpcodeName(expected))); } DescriptorSource descriptor; MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc); uint32_t bad_dword = 0; if (expected == ValueOpcode::GetImageResource) { for (; bad_dword < descriptor.dword_count; bad_dword++) { const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction(); if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) { Fail(pc, fmt::format("{} dword {} is not a valid runtime value", ValueOpcodeName(expected), bad_dword)); } } bad_dword = 0; } if (!ValidateSource(descriptor, bad_dword)) { if (expected == ValueOpcode::GetBufferResource && std::all_of(descriptor.dwords.begin(), descriptor.dwords.begin() + width, [](Value word) { return word.Resolve().GetType() == Type::U32; })) { return false; } Fail(pc, fmt::format("{} dword {} is not a valid runtime value", ValueOpcodeName(expected), bad_dword)); } source = InternSource(descriptor); return true; } void ValidateAddressHandle(Value value, uint32_t pc) const { const auto* handle = value.Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) { Fail(pc, "address operation requires GetAddressResource"); } if (handle->NumArgs() != 2) { Fail(pc, "GetAddressResource must have two address dwords"); } } uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) { for (uint32_t i = 0; i < m_info.buffers.size(); i++) { if (m_info.buffers[i].source == source) { Merge(m_info.buffers[i], memory, op, pc); return i; } } if (m_info.buffers.size() >= ShaderInfo::MaxBuffers) { return UINT32_MAX; } BufferResource resource; resource.source = source; resource.first_use_pc = pc; Merge(resource, memory, op, pc); m_info.buffers.push_back(resource); return static_cast(m_info.buffers.size() - 1); } static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) { const auto access = BufferAccessOf(op); const bool atomic = access == BufferAccess::Atomic; const bool write = access == BufferAccess::Write || atomic; resource.first_use_pc = std::min(resource.first_use_pc, pc); resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory)); resource.read = resource.read || !write || atomic; resource.written = resource.written || write; resource.atomic = resource.atomic || atomic; resource.formatted = resource.formatted || memory.formatted; resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer || memory.kind == ResourceKind::ScalarBuffer; } uint32_t AddImage(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) { const auto resource_class = ImageOpcodeInfoOf(op).resource_class; const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None; const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0; for (uint32_t i = 0; i < m_info.images.size(); i++) { auto& image = m_info.images[i]; if (image.source == source && image.resource_class == resource_class && image.dimension == memory.image_dimension && image.mip_mode == mip && image.depth_compare == depth && image.r128 == memory.image_r128) { Merge(image, op, pc); return i; } } if (m_info.images.size() >= ShaderInfo::MaxImages) { return UINT32_MAX; } ImageResource image; image.source = source; image.first_use_pc = pc; image.resource_class = resource_class; image.dimension = memory.image_dimension; image.mip_mode = mip; image.depth_compare = depth; image.r128 = memory.image_r128; Merge(image, op, pc); m_info.images.push_back(image); return static_cast(m_info.images.size() - 1); } static void Merge(ImageResource& image, ValueOpcode op, uint32_t pc) { const auto access = ImageOpcodeInfoOf(op).access; const bool atomic = access == ImageAccess::Atomic; const bool write = access == ImageAccess::Write || atomic; image.first_use_pc = std::min(image.first_use_pc, pc); image.read = image.read || !write || atomic; image.written = image.written || write; image.atomic = image.atomic || atomic; } uint32_t AddSampler(uint32_t source, uint32_t pc) { for (uint32_t i = 0; i < m_info.samplers.size(); i++) { if (m_info.samplers[i].source == source) { m_info.samplers[i].first_use_pc = std::min(m_info.samplers[i].first_use_pc, pc); return i; } } if (m_info.samplers.size() >= ShaderInfo::MaxSamplers) { return UINT32_MAX; } m_info.samplers.push_back({source, pc}); return static_cast(m_info.samplers.size() - 1); } void AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc) { for (auto& pair: m_info.sampled_pairs) { if (pair.image == image && pair.sampler == sampler) { pair.first_use_pc = std::min(pair.first_use_pc, pc); return; } } if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) { Fail(pc, "sampled image/sampler pair limit exceeded"); } m_info.sampled_pairs.push_back({image, sampler, pc}); } void AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc) { for (const auto& patch: m_handle_patches) { if (patch.handle == handle) { if (patch.resource != resource) { Fail(pc, fmt::format("{} is reused with incompatible resource classes", ValueOpcodeName(handle->GetOpcode()))); } return; } } m_handle_patches.push_back({handle, resource}); } void AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler, uint32_t pc) { for (auto& patch: m_memory_patches) { if (patch.index != index) { continue; } if (patch.resource != resource || (has_sampler && patch.has_sampler && patch.sampler != sampler)) { Fail(pc, "memory metadata is reused with incompatible resources"); } if (has_sampler) { patch.sampler = sampler; patch.has_sampler = true; } return; } m_memory_patches.push_back({index, resource, sampler, has_sampler}); } void Collect(Inst& inst) { const auto op = inst.GetOpcode(); const auto buffer = BufferAccessOf(op); const auto address_info = AddressOpcodeInfoOf(op); const auto image_info = ImageOpcodeInfoOf(op); if (buffer == BufferAccess::None && address_info.access == AddressAccess::None && image_info.access == ImageAccess::None) { return; } const auto flags = inst.Flags(); if (flags.index >= m_program.memory_info.size()) { Fail(flags.pc, fmt::format("memory metadata index {} is out of range", flags.index)); } if (inst.NumArgs() == 0) { Fail(flags.pc, "memory operation has no resource handle"); } const auto& memory = m_program.memory_info[flags.index]; if (memory.planning_only || IsIndirectPlanningMemory(flags.index)) { return; } Inst* handle = nullptr; uint32_t source = 0; uint32_t resource = 0; if (buffer != BufferAccess::None) { if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source)) { if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed || (op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) { Fail(flags.pc, "buffer descriptor is not a valid runtime value; GPU-selected access " "requires a raw DWORD x2/x4 load"); } m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer; m_info.uses_dma = true; return; } resource = AddBuffer(source, memory, op, flags.pc); if (resource == UINT32_MAX) { Fail(flags.pc, "buffer resource limit exceeded"); } AddHandlePatch(handle, resource, flags.pc); AddMemoryPatch(flags.index, resource, 0, false, flags.pc); return; } if (address_info.access != AddressAccess::None) { if (!IsAddressResourceKind(memory.kind)) { Fail(flags.pc, "address operation has invalid resource kind"); } if (memory.kind == ResourceKind::Scratch) { handle = inst.Arg(0).Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetScratchResource || handle->NumArgs() != 0) { Fail(flags.pc, "scratch operation requires GetScratchResource"); } if (m_program.scratch_dwords == 0) { Fail(flags.pc, "scratch operation requires a nonzero AGC per-thread size"); } return; } ValidateAddressHandle(inst.Arg(0), flags.pc); m_info.uses_dma = true; return; } if (memory.kind != ResourceKind::Image || image_info.resource_class == ImageResourceClass::None) { Fail(flags.pc, "image operation has invalid resource kind"); } handle = inst.Arg(0).Resolve().TryInstruction(); const auto* indirect = handle != nullptr ? FindIndirectImage(*handle) : nullptr; if (indirect != nullptr) { source = indirect->source; } else { GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source); } resource = AddImage(source, memory, op, flags.pc); if (resource == UINT32_MAX) { Fail(flags.pc, "image resource limit exceeded"); } AddHandlePatch(handle, resource, flags.pc); uint32_t sampler = 0; if (image_info.needs_sampler) { if (inst.NumArgs() < 2) { Fail(flags.pc, "sampled image operation has no sampler handle"); } Inst* sampler_handle = nullptr; uint32_t sampler_source = 0; const bool sample_adjust = (memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0; GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, sampler_handle, sampler_source, true, sample_adjust); sampler = AddSampler(sampler_source, flags.pc); if (sampler == UINT32_MAX) { Fail(flags.pc, "sampler resource limit exceeded"); } AddHandlePatch(sampler_handle, sampler, flags.pc); AddSampledPair(resource, sampler, flags.pc); } AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc); } const DescriptorSource* Source(uint32_t source) const { return source < m_sources.size() ? &m_sources[source] : nullptr; } void LinkImageAliases() { for (auto& buffer: m_info.buffers) { const auto* buffer_source = Source(buffer.source); if (buffer_source == nullptr || buffer_source->dword_count != 4) { continue; } for (uint32_t image = 0; image < m_info.images.size(); image++) { const auto* image_source = Source(m_info.images[image].source); if (image_source == nullptr || image_source->dword_count != 8 || image_source->indirect_image.has_value()) { continue; } bool alias = true; for (uint32_t dword = 0; dword < 4; dword++) { alias = alias && EquivalentValue(m_program, buffer_source->dwords[dword], image_source->dwords[dword]); } if (alias) { buffer.image_alias = image; break; } } } } Program& m_program; ShaderInfo m_info; std::vector m_sources; std::vector m_handle_patches; std::vector m_memory_patches; std::vector m_indirect_images; std::vector> m_descriptor_selections; bool m_shader_writes = false; }; } // namespace void TrackResources(Program& program) { Tracker(program).Run(); } } // namespace Libs::Graphics::ShaderRecompiler::IR