diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index a34bc9874..59327498a 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -15,7 +15,6 @@ #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" #include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h" -#include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include "graphics/shader/recompiler/ir/passes/SsaRewrite.h" #include @@ -624,8 +623,6 @@ TranslateResult TranslateProgram(std::span code, const CompileOp IR::EliminateDeadCode(ir.blocks); } LowerTessellationMemory(ir, options); - IR::BuildSrtPlan(ir); - IR::EliminateDeadCode(ir.blocks); IR::TrackResources(ir, decoded, native_cfg); IR::EliminateDeadCode(ir.blocks); TranslateResult result; @@ -652,16 +649,6 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o for (auto* block: ir.blocks) { for (auto& inst: *block) { const auto op = inst.GetOpcode(); - if (op == IR::ValueOpcode::ReferenceU32) { - const auto* read = inst.Arg(0).Resolve().TryInstruction(); - if (read != nullptr && - (read->GetOpcode() == IR::ValueOpcode::LoadAddressU32 || - read->GetOpcode() == IR::ValueOpcode::ReadConstBuffer) && - ir.memory_info[read->Flags().index].planning_only) { - inst.Invalidate(); - } - continue; - } uint32_t first = 0; if (op == IR::ValueOpcode::GetBufferResource) { if (std::ranges::any_of(inst.Uses(), [&](const IR::Use& use) { diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 88cd6d8e0..65dd66328 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -588,7 +588,6 @@ struct Program: ResourcePlan { // 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; - std::vector dynamic_reads; bool shader_info_complete = false; BindingLayout bindings; bool binding_layout_complete = false; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index 5b2dee23d..cb976372f 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -2,6 +2,7 @@ #include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" +#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include @@ -98,9 +99,8 @@ public: if (m_program.resource_tracking_complete) { Fail(0, "resources already tracked"); } - if (!m_program.srt_plan_complete) { - Fail(0, "SRT plan is not ready"); - } + PlanScalarReads(); + EliminateDeadCode(m_program.blocks); PlanIndirectImages(); for (auto* block: m_program.blocks) { for (auto& inst: *block) { @@ -130,13 +130,6 @@ public: 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; @@ -155,6 +148,13 @@ private: bool has_sampler = false; }; + struct ResolvedHandle { + const Inst* handle; + uint32_t pc; + DescriptorSource source; + Value planning_handle; + }; + struct IndirectImagePlan { Inst* handle = nullptr; uint32_t source = 0; @@ -191,6 +191,8 @@ private: if (arg != nullptr) pending.push_back(arg); } } else if (inst->GetOpcode() == ValueOpcode::ReadConst || + inst->GetOpcode() == ValueOpcode::LoadAddressU32 || + inst->GetOpcode() == ValueOpcode::ReadConstBuffer || inst->GetOpcode() == ValueOpcode::GetUserData) { candidates.push_back(inst); } @@ -206,7 +208,9 @@ private: RegIndex(candidate->Arg(0).ScalarRegister()) != reg) continue; } else { - if (candidate->GetOpcode() != ValueOpcode::ReadConst) continue; + if (candidate->GetOpcode() != ValueOpcode::ReadConst && + candidate->GetOpcode() != ValueOpcode::LoadAddressU32 && + candidate->GetOpcode() != ValueOpcode::ReadConstBuffer) continue; const auto flags = candidate->Flags(); if (flags.pc != pc || flags.index >= m_program.memory_info.size()) continue; if (native == m_decoded.instructions.end() || native->pc != pc || @@ -228,11 +232,18 @@ private: std::vector positions {{use->id, use_pc}}; std::vector reached(m_native_cfg.blocks.size()); Value selected; + uint32_t selected_pc = UINT32_MAX; const auto select = [&](uint32_t pc) { const auto source = source_at(pc); - if (source.IsEmpty() || (!selected.IsEmpty() && !EquivalentValue(m_program, selected, source))) - return false; + if (source.IsEmpty()) return false; + if (!selected.IsEmpty()) { + const auto op = source.TryInstruction()->GetOpcode(); + if ((op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) && + selected_pc != pc) return false; + if (!EquivalentValue(m_program, selected, source)) return false; + } selected = source; + selected_pc = pc; return true; }; while (!positions.empty()) { @@ -335,6 +346,13 @@ private: void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust, uint32_t base_reg, DescriptorSource& descriptor, uint32_t pc) { + const auto resolved = std::ranges::find_if(m_resolved_handles, [&](const auto& entry) { + return entry.handle == &handle && entry.pc == pc; + }); + if (resolved != m_resolved_handles.end()) { + descriptor = resolved->source; + return; + } if (handle.NumArgs() != width) { Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}", ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width)); @@ -354,6 +372,174 @@ private: // Border color and its table index are unused unless a clamp axis selects border mode. descriptor.dwords[3] = Value(0u); } + m_resolved_handles.push_back({&handle, pc, descriptor, {}}); + } + + uint32_t ScalarReadBase(const Inst& read) const { + const auto flags = read.Flags(); + if (m_program.memory_info[flags.index].kind == ResourceKind::ScalarBuffer) + return m_program.memory_info[flags.index].resource * 4u; + const auto native = std::ranges::lower_bound(m_decoded.instructions, flags.pc, {}, + &Decoder::Instruction::pc); + return native != m_decoded.instructions.end() && native->pc == flags.pc && + native->src0.kind == Decoder::OperandKind::Sgpr + ? native->src0.reg : UINT32_MAX; + } + + void CollectScalarRead(Value value, uint32_t use_pc) { + value = value.Resolve(); + if (value.IsImmediate()) return; + auto* inst = value.TryInstruction(); + if (inst == nullptr) Fail(use_pc, "invalid typed planning value"); + const auto cycle = std::ranges::find(m_srt_visiting, inst); + if (cycle != m_srt_visiting.end()) { + if (std::any_of(cycle, m_srt_visiting.end(), [](const Inst* value) { + return value->GetOpcode() == ValueOpcode::Phi; + })) return; + Fail(use_pc, "cyclic typed planning value without a phi"); + } + if (std::ranges::find(m_srt_visited, inst) != m_srt_visited.end()) return; + m_srt_visiting.push_back(inst); + uint32_t memory_index = 0; + const auto* memory = ScalarReadMemory(*inst, memory_index); + DescriptorSource source; + if (memory != nullptr) { + const auto* handle = inst->Arg(0).Resolve().TryInstruction(); + const auto width = memory->kind == ResourceKind::ScalarBuffer ? 4u : 2u; + if (handle == nullptr || handle->GetOpcode() != + (width == 4u ? ValueOpcode::GetBufferResource : ValueOpcode::GetAddressResource)) + Fail(use_pc, "scalar read has an invalid resource handle"); + MakeSource(*handle, width, false, false, ScalarReadBase(*inst), source, + inst->Flags().pc); + for (uint32_t word = 0; word < width; ++word) + CollectScalarRead(source.dwords[word], inst->Flags().pc); + for (size_t arg = 1; arg < inst->NumArgs(); ++arg) + CollectScalarRead(inst->Arg(arg), use_pc); + } else { + for (size_t arg = 0; arg < inst->NumArgs(); ++arg) + CollectScalarRead(inst->Arg(arg), use_pc); + } + m_srt_visiting.pop_back(); + m_srt_visited.push_back(inst); + if (memory == nullptr) return; + const auto offset = inst->Arg(1).Resolve(); + if (!offset.IsImmediate() || offset.GetType() != Type::U32) return; + m_scalar_reads.push_back(inst); + } + + void PlanScalarReads() { + m_program.srt_plan_complete = false; + m_program.srt_reads.clear(); + for (auto* block: m_program.blocks) { + for (auto& inst: *block) { + const auto op = inst.GetOpcode(); + const auto image = ImageOpcodeInfoOf(op); + if (BufferAccessOf(op) == BufferAccess::None && + AddressOpcodeInfoOf(op).access == AddressAccess::None && + image.access == ImageAccess::None) continue; + const auto flags = inst.Flags(); + if (flags.index >= m_program.memory_info.size()) + Fail(flags.pc, "memory metadata index is out of range"); + if (inst.NumArgs() < (image.needs_sampler ? 2u : 1u)) + Fail(flags.pc, "memory operation has no resource handle"); + const auto& memory = m_program.memory_info[flags.index]; + if ((op == ValueOpcode::LoadAddressU32 && memory.kind == ResourceKind::ScalarBuffer) || + (op == ValueOpcode::ReadConstBuffer && memory.kind == ResourceKind::ScalarAddress)) + Fail(flags.pc, "scalar read has incompatible scalar memory metadata"); + for (uint32_t arg = 0; arg < (image.needs_sampler ? 2u : 1u); ++arg) { + const auto* handle = inst.Arg(arg).Resolve().TryInstruction(); + if (handle == nullptr) continue; + const auto kind = handle->GetOpcode(); + const bool sampler = kind == ValueOpcode::GetSamplerResource; + const uint32_t width = kind == ValueOpcode::GetImageResource ? 8u + : kind == ValueOpcode::GetAddressResource ? 2u + : kind == ValueOpcode::GetBufferResource || sampler ? 4u : 0u; + if (width == 0u) continue; + const auto base = width == 2u + ? (memory.kind == ResourceKind::ScalarAddress ? ScalarReadBase(inst) : UINT32_MAX) + : (sampler ? memory.sampler : memory.resource) * 4u; + DescriptorSource source; + MakeSource(*handle, width, sampler, + sampler && (memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0, + base, source, flags.pc); + for (uint32_t word = 0; word < width; ++word) + CollectScalarRead(source.dwords[word], flags.pc); + } + } + } + for (auto* block: m_program.blocks) { + for (auto& inst: *block) { + uint32_t index = 0; + if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && + ScalarReadMemory(inst, index) != nullptr && inst.Arg(1).Resolve().IsImmediate() && + ValidateRuntimeValue(m_program, Value(&inst))) + CollectScalarRead(Value(&inst), inst.Flags().pc); + } + } + for (auto* read: m_scalar_reads) { + const auto flags = read->Flags(); + auto& memory = m_program.memory_info[flags.index]; + memory.planning_only = true; + const auto* handle = read->Arg(0).Resolve().TryInstruction(); + auto resolved = std::ranges::find_if(m_resolved_handles, [&](const auto& entry) { + return entry.handle == handle && entry.pc == flags.pc; + }); + EXIT_IF(resolved == m_resolved_handles.end()); + uint32_t slot = 0; + for (; slot < m_program.srt_reads.size(); ++slot) { + const auto* other = m_program.srt_reads[slot].value.Resolve().TryInstruction(); + if (other->GetOpcode() != read->GetOpcode() || + m_program.memory_info[other->Flags().index] != memory) continue; + const auto* other_handle = other->Arg(0).Resolve().TryInstruction(); + bool same = true; + for (uint32_t word = 0; word < resolved->source.dword_count; ++word) + same &= EquivalentValue(m_program, resolved->source.dwords[word], other_handle->Arg(word)); + for (size_t arg = 1; arg < read->NumArgs(); ++arg) + same &= EquivalentValue(m_program, read->Arg(arg), other->Arg(arg)); + if (same) break; + } + const bool keep = slot == m_program.srt_reads.size(); + if (keep) m_program.srt_reads.push_back({Value(read), slot}); + auto* block = read->Parent(); + auto& list = block->Instructions(); + const auto where = std::ranges::find_if(list, [&](const Inst& inst) { + return &inst == read; + }); + const auto resource = Value(&*block->PrependNewInst(where, ValueOpcode::GetSrtResource)); + const auto flat = Value(&*block->PrependNewInst(where, ValueOpcode::ReadConst, + {resource, Value(slot)}, read->Flags())); + const auto uses = read->Uses(); + for (const auto& use: uses) use.user->SetArg(use.operand, flat); + for (auto& entry: m_resolved_handles) { + for (uint32_t word = 0; word < entry.source.dword_count; ++word) + if (entry.source.dwords[word].Resolve() == Value(read)) + entry.source.dwords[word] = flat; + } + for (auto& info: m_program.block_info) { + if (info.condition.Resolve() == Value(read)) info.condition = flat; + if (info.indirect_target.Resolve() == Value(read)) info.indirect_target = flat; + } + if (keep) { + if (resolved->planning_handle.IsEmpty()) { + bool unchanged = true; + for (uint32_t word = 0; word < resolved->source.dword_count; ++word) + unchanged &= handle->Arg(word).Resolve() == resolved->source.dwords[word].Resolve(); + resolved->planning_handle = read->Arg(0); + if (!unchanged) { + auto& retained = m_program.value_storage.emplace_back(handle->GetOpcode()); + for (uint32_t word = 0; word < resolved->source.dword_count; ++word) + retained.SetArg(word, resolved->source.dwords[word]); + resolved->planning_handle = Value(&retained); + } + } + read->SetArg(0, resolved->planning_handle); + read->SetParent(nullptr); + m_program.value_storage.splice(m_program.value_storage.end(), list, where); + } else { + list.erase(where); + } + } + m_program.srt_plan_complete = true; } bool ValidateSource(const DescriptorSource& descriptor, uint32_t& bad_dword) const { @@ -457,14 +643,19 @@ private: return true; } - bool MakeRuntimeTableSource(const Inst& handle, uint32_t pc, - DescriptorSource& descriptor) { - const auto width = handle.GetOpcode() == ValueOpcode::GetBufferResource ? 4u - : handle.GetOpcode() == ValueOpcode::GetAddressResource ? 2u : 0u; + bool MakeRuntimeTableSource(const Inst& read, DescriptorSource& descriptor) { + const auto* handle = read.Arg(0).Resolve().TryInstruction(); + if (handle == nullptr) return false; + const auto width = handle->GetOpcode() == ValueOpcode::GetBufferResource ? 4u + : handle->GetOpcode() == ValueOpcode::GetAddressResource ? 2u : 0u; if (width == 0u) { return false; } - MakeSource(handle, width, false, false, UINT32_MAX, descriptor, pc); + const auto flags = read.Flags(); + const auto kind = m_program.memory_info[flags.index].kind; + const auto base = kind == ResourceKind::ScalarAddress || kind == ResourceKind::ScalarBuffer + ? ScalarReadBase(read) : UINT32_MAX; + MakeSource(*handle, width, false, false, base, descriptor, flags.pc); uint32_t bad_dword = 0; return ValidateSource(descriptor, bad_dword); } @@ -852,7 +1043,7 @@ private: const auto* material_handle = read->Arg(0).Resolve().TryInstruction(); if (material_handle == nullptr || material_handle->GetOpcode() != ValueOpcode::GetAddressResource || - !MakeRuntimeTableSource(*material_handle, pc, material_source)) return false; + !MakeRuntimeTableSource(*read, material_source)) return false; AffineOffset offset; if (!MatchAffineOffset(read->Arg(1), active, offset) || offset.index.IsEmpty() || offset.stride == 0u || offset.offset + memory.offset > UINT32_MAX || @@ -974,7 +1165,7 @@ private: } DescriptorSource table_source; - if (!MakeRuntimeTableSource(*table_handle, pc, table_source)) { + if (!MakeRuntimeTableSource(*plan.reads[0], table_source)) { return false; } DescriptorSource material_source; @@ -1018,7 +1209,7 @@ private: const auto* material_handle = material_read->Arg(0).Resolve().TryInstruction(); if (material_handle == nullptr || material_handle->GetOpcode() != ValueOpcode::GetBufferResource || - !MakeRuntimeTableSource(*material_handle, pc, material_source)) { + !MakeRuntimeTableSource(*material_read, material_source)) { return false; } indirect.material_source = InternSource(material_source); @@ -1378,6 +1569,10 @@ private: const Decoder::Program& m_decoded; const CFG::Graph& m_native_cfg; std::vector m_scalar_writes; + std::vector m_resolved_handles; + std::vector m_srt_visiting; + std::vector m_srt_visited; + std::vector m_scalar_reads; ShaderInfo m_info; std::vector m_sources; std::vector m_handle_patches; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h index a3fb5b4a7..03e530235 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h @@ -5,8 +5,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { -// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in -// descriptor_sources, then writes dense indices to handle flags and MemoryInfo. +// Resolves native descriptor sources, plans their scalar reads, and assigns dense resource bindings. void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 41d5c88ee..953051551 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include namespace Libs::Graphics::ShaderRecompiler::IR { @@ -23,21 +22,6 @@ namespace { constexpr uint64_t AddressMask = 0x0000ffffffffffffull; -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"; - } -} - -std::string Diagnostic(const ResourcePlan& program, uint32_t pc, const std::string& message) { - return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash, - StageName(program.stage), pc, message); -} - bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) { if (base > AddressMask) { return false; @@ -313,152 +297,6 @@ private: std::unordered_set m_validated_dependencies; }; -class PlanBuilder { -public: - explicit PlanBuilder(Program& program): m_program(program) {} - - void Run() { - m_program.srt_reads.clear(); - m_program.dynamic_reads.clear(); - for (auto* block: m_program.blocks) { - for (auto& inst: *block) { - const auto op = inst.GetOpcode(); - if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) { - const auto flags = inst.Flags(); - if (flags.index < m_program.memory_info.size()) { - const auto kind = m_program.memory_info[flags.index].kind; - const bool crosswired = (op == ValueOpcode::LoadAddressU32 && - kind == ResourceKind::ScalarBuffer) || - (op == ValueOpcode::ReadConstBuffer && - kind == ResourceKind::ScalarAddress); - if (crosswired) { - Fail(flags.pc, - fmt::format("{} has incompatible scalar memory metadata", - ValueOpcodeName(op))); - } - } - } - if (IsDescriptorHandle(inst.GetOpcode())) { - for (size_t index = 0; index < inst.NumArgs(); index++) { - Collect(inst.Arg(index), 0); - } - } - } - } - for (auto* block: m_program.blocks) { - for (auto& inst: *block) { - if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_program, inst) && - inst.Arg(1).Resolve().IsImmediate() && - ValidateRuntimeValue(m_program, Value(&inst))) { - Collect(Value(&inst), inst.Flags().pc); - } - } - } - PatchReads(); - } - -private: - struct Patch { - Inst* inst = nullptr; - uint32_t slot = 0; - bool keep = false; - }; - - [[noreturn]] void Fail(uint32_t pc, const std::string& message) const { - const auto diagnostic = Diagnostic(m_program, pc, message); - EXIT("shader SRT planning failed: %s", diagnostic.c_str()); - std::abort(); - } - - void Collect(Value value, uint32_t use_pc) { - value = value.Resolve(); - if (value.IsImmediate()) { - return; - } - auto* inst = value.TryInstruction(); - if (inst == nullptr) { - Fail(use_pc, "invalid typed planning value"); - } - const auto cycle = std::ranges::find(m_visiting, inst); - if (cycle != m_visiting.end()) { - const auto contains_phi = std::any_of(cycle, m_visiting.end(), [](const Inst* value) { - return value->GetOpcode() == ValueOpcode::Phi; - }); - if (contains_phi) { - return; - } - Fail(use_pc, fmt::format("cyclic typed planning value {} without a phi", - ValueOpcodeName(inst->GetOpcode()))); - } - if (std::ranges::find(m_visited, inst) != m_visited.end()) { - return; - } - m_visiting.push_back(inst); - for (size_t index = 0; index < inst->NumArgs(); index++) { - Collect(inst->Arg(index), use_pc); - } - m_visiting.pop_back(); - m_visited.push_back(inst); - if (!IsRawRead(m_program, *inst)) { - return; - } - const auto offset = inst->Arg(1).Resolve(); - if (!offset.IsImmediate() || offset.GetType() != Type::U32) { - if (std::ranges::find(m_program.dynamic_reads, value) == - m_program.dynamic_reads.end()) { - m_program.dynamic_reads.push_back(value); - } - return; - } - for (uint32_t slot = 0; slot < m_program.srt_reads.size(); slot++) { - if (EquivalentValue(m_program, value, m_program.srt_reads[slot].value)) { - m_patches.push_back({inst, slot, false}); - return; - } - } - const auto slot = static_cast(m_program.srt_reads.size()); - m_program.srt_reads.push_back({value, slot}); - m_patches.push_back({inst, slot, true}); - } - - void PatchReads() { - for (const auto& patch: m_patches) { - auto* block = patch.inst->Parent(); - auto& list = block->Instructions(); - auto where = - std::ranges::find_if(list, [&](const Inst& inst) { return &inst == patch.inst; }); - const auto resource = - Value(&*block->PrependNewInst(where, ValueOpcode::GetSrtResource)); - const auto flat = Value(&*block->PrependNewInst(where, ValueOpcode::ReadConst, - {resource, Value(patch.slot)}, - patch.inst->Flags())); - const auto uses = patch.inst->Uses(); - for (const auto& use: uses) { - use.user->SetArg(use.operand, flat); - } - for (auto& info: m_program.block_info) { - if (info.condition.Resolve() == Value(patch.inst)) { - info.condition = flat; - } - if (info.indirect_target.Resolve() == Value(patch.inst)) { - info.indirect_target = flat; - } - } - if (patch.keep) { - const auto memory = patch.inst->Flags().index; - if (memory < m_program.memory_info.size()) { - m_program.memory_info[memory].planning_only = true; - } - block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(patch.inst)}); - } - } - } - - Program& m_program; - std::vector m_visiting; - std::vector m_visited; - std::vector m_patches; -}; } // namespace @@ -1068,13 +906,5 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValue return RuntimeValidator(program, type).Run(value); } -void BuildSrtPlan(Program& program) { - if (program.resource_tracking_complete) { - EXIT("shader SRT planning failed: cannot rebuild SRT after resource tracking"); - } - program.srt_plan_complete = false; - PlanBuilder(program).Run(); - program.srt_plan_complete = true; -} } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index 4b9ead833..9e160f41b 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -21,9 +21,6 @@ struct SrtRuntime { enum class RuntimeValueType { Any, Integer }; -// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots; -// dynamic offsets remain explicit and are never assigned a fake slot. -void BuildSrtPlan(Program& program); bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type = RuntimeValueType::Any); // Uses the strict reader for values that affect shader specialization. diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index c37d47793..811c44848 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -1,6 +1,7 @@ #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/BindingLayout.h" +#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" @@ -126,7 +127,27 @@ struct Fixture { } void PlanAndTrack() { - BuildSrtPlan(program); + for (size_t index = 0; index < program.block_info.size(); ++index) { + const auto condition = program.block_info[index].condition; + if (!condition.IsEmpty()) + Emit(ValueOpcode::Reference, {condition}, 0, program.blocks[index]); + } + for (auto *target : program.blocks) { + for (auto &inst : *target) { + if (inst.HasUses() || inst.MayHaveSideEffects() || + (BufferAccessOf(inst.GetOpcode()) == BufferAccess::None && + AddressOpcodeInfoOf(inst.GetOpcode()).access == AddressAccess::None && + ImageOpcodeInfoOf(inst.GetOpcode()).access == ImageAccess::None)) + continue; + auto value = Value(&inst); + if (value.GetType() == Type::U32x4) + value = Emit(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}, 0, target); + if (value.GetType() == Type::U8) + value = Emit(ValueOpcode::ConvertU32U8, {value}, 0, target); + Check(value.GetType() == Type::U32, "unhandled memory result type in fixture"); + Emit(ValueOpcode::ReferenceU32, {value}, 0, target); + } + } TrackResources(program, {}, {}); } }; @@ -277,7 +298,9 @@ void TestInvariantIndirectImageMaterialization() { Check(fixture->program.info.buffers.size() == 1 && fixture->program.info.images.size() == 1 && - fixture->program.dynamic_reads.size() == 1, + std::ranges::any_of(*fixture->block, [](const Inst &inst) { + return inst.GetOpcode() == ValueOpcode::ReadConstBuffer; + }), "indirect image key was not retained as a scalar-buffer read"); const auto source = fixture->program.info.images[0].source; Check(source < fixture->program.descriptor_sources.size() && @@ -519,8 +542,8 @@ void TestInvariantIndirectImageMaterialization() { "a same-shape refresh discarded the runtime output storage"); auto malformed = MakeIndirectImageFixture(true); - BuildSrtPlan(malformed->program); - CheckFatal([&] { TrackResources(malformed->program, {}, {}); }, "not a valid runtime value", + + CheckFatal([&] { malformed->PlanAndTrack(); }, "not a valid runtime value", "malformed indirect image pattern was accepted"); Check(!malformed->program.resource_tracking_complete && malformed->program.info.images.empty() && @@ -528,8 +551,8 @@ void TestInvariantIndirectImageMaterialization() { "malformed indirect image pattern was partially accepted"); auto wrapped_immediate = MakeIndirectImageFixture(false, 4u); - BuildSrtPlan(wrapped_immediate->program); - CheckFatal([&] { TrackResources(wrapped_immediate->program, {}, {}); }, + + CheckFatal([&] { wrapped_immediate->PlanAndTrack(); }, "not a valid runtime value", "wrapped scalar immediate entered the invariant image proof"); Check(!wrapped_immediate->program.resource_tracking_complete, @@ -1405,7 +1428,7 @@ void TestDenseBufferTracking() { fixture.program.memory_info[other_flags.index].resource == 1, "typed memory metadata was not patched to dense indices"); - CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "already tracked", + CheckFatal([&] { fixture.PlanAndTrack(); }, "already tracked", "resource tracking allowed a second mutation pass"); } @@ -1601,8 +1624,8 @@ void TestSampleAdjustSamplerScratch() { rejected.Emit(ValueOpcode::ImageSampleRaw, {rejected_image, rejected_sampler, rejected.ImageAddress()}, rejected.AddMemory(rejected_memory, 0x200)); - BuildSrtPlan(rejected.program); - CheckFatal([&] { TrackResources(rejected.program, {}, {}); }, + + CheckFatal([&] { rejected.PlanAndTrack(); }, "not a valid runtime value", message); }; CheckRejected(0u, 12u, @@ -1936,7 +1959,8 @@ void TestDynamicSrtReadRemainsExplicit() { fixture.PlanAndTrack(); Check(fixture.program.srt_reads.empty() && - fixture.program.dynamic_reads.size() == 1 && + read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 && + !fixture.program.memory_info[0].planning_only && fixture.program.info.uses_dma, "dynamic scalar read was incorrectly flattened or lost"); std::array user_data{0x1000u, 0u, 4u}; @@ -1990,8 +2014,7 @@ void TestPhiValidation() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, 20), merge); - BuildSrtPlan(fixture.program); - CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "not a valid runtime value", + CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value", "control-dependent descriptor phi was accepted"); Check(!fixture.program.resource_tracking_complete && fixture.program.info.buffers.empty() && @@ -2092,7 +2115,9 @@ ResourcePlan ConditionalSamplerPlan(bool diamond, bool reverse, bool reverse_phi {result, Value(0u)})}); } fixture.PlanAndTrack(); - Check(fixture.program.value_storage.size() == 4, + Check(std::ranges::count_if(fixture.program.value_storage, [](const Inst &inst) { + return inst.GetOpcode() == ValueOpcode::SelectU32; + }) == 4, "repeated sampler uses retained duplicate planning selections"); Check(sampler_words[0].ResolveInstruction()->GetOpcode() == ValueOpcode::Phi, "host descriptor selection changed the GPU Phi"); @@ -2168,11 +2193,24 @@ void TestLoopCycleEnteredThroughRuntimeValue() { {Value(&phi), Value(0xffffffffu)}, 0, loop); phi.AddPhiOperand(entry, initial); phi.AddPhiOperand(loop, carried); - fixture.Emit(ValueOpcode::GetBufferResource, + const auto handle = fixture.Emit(ValueOpcode::GetBufferResource, {carried, Value(0u), Value(0u), Value(0u)}, MemoryFlags{0, 12}, loop); - - BuildSrtPlan(fixture.program); + MemoryInfo memory; + memory.kind = ResourceKind::Buffer; + fixture.Emit(ValueOpcode::LoadBufferU32, + {handle, Value(0u), Value(0u), Value(0u), Value(true)}, + fixture.AddMemory(memory, 16), loop); + ConstantPropagationPass(fixture.program.blocks); + RemoveIdentities(fixture.program.blocks); + fixture.PlanAndTrack(); + const std::array user_data{0x4000u}; + DescriptorValue descriptor; + Check(fixture.program.info.buffers.size() == 1u && + SrtWalker(fixture.program, {.user_data = user_data}).EvaluateDescriptor( + fixture.program.info.buffers[0].source, descriptor) && + descriptor.dwords[0] == user_data[0], + "runtime-rooted invariant loop lost its buffer source"); } void TestInvariantLoopPhi() { @@ -2516,14 +2554,15 @@ void TestShaderInfoAndBindingLayout() { fixture.Emit(ValueOpcode::LoadBufferU32, {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(buffer, 4)); - fixture.Emit( + const auto invocation = fixture.Emit( ValueOpcode::GetBuiltin, {Value(static_cast(StageInputKind::GlobalInvocationId)), Value(2u)}); - fixture.Emit(ValueOpcode::BitwiseXor32, {Value(1u), Value(2u)}); + const auto value = fixture.Emit(ValueOpcode::BitwiseXor32, + {invocation, Value(2u)}); MemoryInfo gds; gds.kind = ResourceKind::Gds; - fixture.Emit(ValueOpcode::WriteSharedU32, {Value(0u), Value(1u), Value(true)}, + fixture.Emit(ValueOpcode::WriteSharedU32, {Value(0u), value, Value(true)}, fixture.AddMemory(gds, 8)); fixture.PlanAndTrack(); @@ -2767,8 +2806,8 @@ void TestResourceLimitIsTransactional() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, index * 4u)); } - BuildSrtPlan(fixture.program); - CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, + + CheckFatal([&] { fixture.PlanAndTrack(); }, "buffer resource limit exceeded", "resource-limit failure was not reported"); Check(!fixture.program.resource_tracking_complete && @@ -2786,9 +2825,9 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::StoreAddressU32, {address, Value(0u), Value(0u), Value(1u), Value(true)}, fixture.AddMemory(memory, 4)); - BuildSrtPlan(fixture.program); + CheckFatal( - [&] { TrackResources(fixture.program, {}, {}); }, + [&] { fixture.PlanAndTrack(); }, "address operation has invalid resource kind", "resource tracking accepted an address opcode with buffer metadata"); } @@ -2803,9 +2842,9 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::ImageRead, {image, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 8)); - BuildSrtPlan(fixture.program); + CheckFatal( - [&] { TrackResources(fixture.program, {}, {}); }, + [&] { fixture.PlanAndTrack(); }, "image operation has invalid resource kind", "resource tracking accepted an image opcode with address metadata"); } @@ -2896,3 +2935,4 @@ void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); } #include "graphics/shader/recompiler/ir/Value.cpp" #include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp" +#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp" diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index a52fb7d1f..f24c3b377 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -2,6 +2,7 @@ #include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h" +#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include @@ -71,7 +72,7 @@ struct Fixture { return static_cast(program.memory_info.size() - 1u); } - void Plan() { BuildSrtPlan(program); } + void Plan() { TrackResources(program, {}, {}); } }; struct TestMemory { @@ -102,6 +103,16 @@ Value RawRead(Fixture &fixture, Value address, Value offset, uint32_t memory, 0x80, block); } +void LoadBuffer(Fixture &fixture, std::array words) { + const auto handle = fixture.Emit(ValueOpcode::GetBufferResource, + {words[0], words[1], words[2], words[3]}); + const auto loaded = fixture.EmitMemory( + ValueOpcode::LoadBufferU32, + {handle, Value(0u), Value(0u), Value(0u), Value(true)}, + fixture.AddMemory(ResourceKind::Buffer)); + fixture.Emit(ValueOpcode::ReferenceU32, {loaded}); +} + void TestImmediateFlatteningAndGvn() { Fixture fixture; const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 0x20); @@ -109,14 +120,11 @@ void TestImmediateFlatteningAndGvn() { fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory); const auto second = RawRead( fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory); - fixture.Emit(ValueOpcode::GetBufferResource, - {first, second, Value(16u), Value(0u)}); + LoadBuffer(fixture, {first, second, Value(16u), Value(0u)}); fixture.Plan(); Check(fixture.program.srt_reads.size() == 1, "equivalent typed scalar reads were not coalesced"); - Check(fixture.program.dynamic_reads.empty(), - "immediate scalar read was classified as dynamic"); Check(fixture.program.memory_info[memory].planning_only, "flattened raw read was not kept as a planning-only root"); @@ -133,8 +141,7 @@ void TestRawScalarComponentAlignment() { const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 2); const auto read = RawRead( fixture, Address(fixture, Value(0x1003u), Value(0u)), Value(2u), memory); - fixture.Emit(ValueOpcode::GetBufferResource, - {read, Value(0u), Value(16u), Value(0u)}); + LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)}); fixture.Plan(); TestMemory memory_image{{{0x1000u, 0x12345678u}}}; @@ -150,7 +157,7 @@ void TestScalarMemoryDomainMismatchFails() { Fixture raw; const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer); RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), raw_memory); - CheckFatal([&] { BuildSrtPlan(raw.program); }, + CheckFatal([&] { TrackResources(raw.program, {}, {}); }, "incompatible scalar memory metadata", "raw scalar load accepted descriptor-buffer metadata"); @@ -161,7 +168,7 @@ void TestScalarMemoryDomainMismatchFails() { {Value(0x1000u), Value(0u), Value(16u), Value(0u)}); buffer.EmitMemory(ValueOpcode::ReadConstBuffer, {resource, Value(0u)}, buffer_memory); - CheckFatal([&] { BuildSrtPlan(buffer.program); }, + CheckFatal([&] { TrackResources(buffer.program, {}, {}); }, "incompatible scalar memory metadata", "descriptor scalar load accepted raw-address metadata"); } @@ -173,12 +180,12 @@ void TestDynamicReadRemainsTyped() { {Value(static_cast(2))}); const auto read = RawRead( fixture, Address(fixture, Value(0x1000u), Value(0u)), offset, memory); - fixture.Emit(ValueOpcode::GetBufferResource, - {read, Value(0u), Value(16u), Value(0u)}); + LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)}); fixture.Plan(); Check(fixture.program.srt_reads.empty() && - fixture.program.dynamic_reads == std::vector{read}, + read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 && + !fixture.program.memory_info[memory].planning_only, "dynamic scalar read received a fake flattened slot"); } @@ -189,8 +196,7 @@ void TestNestedSrtWalk() { fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory); const auto value = RawRead(fixture, Address(fixture, pointer, Value(0u)), Value(0u), memory); - fixture.Emit(ValueOpcode::GetBufferResource, - {value, Value(0u), Value(16u), Value(0u)}); + LoadBuffer(fixture, {value, Value(0u), Value(16u), Value(0u)}); fixture.Plan(); TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}}; @@ -211,7 +217,7 @@ void TestShaderBaseAndUserData() { const auto user = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast(2))}); const auto sum = fixture.Emit(ValueOpcode::IAdd32, {user, Value(4u)}); - fixture.Plan(); + fixture.program.descriptor_sources.push_back( {.dwords = {low, high, sum}, .dword_count = 3}); @@ -239,7 +245,7 @@ void TestCarryAndBitFields() { {Value(0u), Value(0x89abcdefu), Value(0u), Value(32u)}); const auto sign = fixture.Emit(ValueOpcode::BitFieldSExtract, {Value(0x000000f0u), Value(4u), Value(4u)}); - fixture.Plan(); + fixture.program.descriptor_sources.push_back( {.dwords = {low, high, inserted, sign}, .dword_count = 4}); @@ -261,7 +267,7 @@ void TestInvariantAndDivergentPhi() { divergent.SetFlags(Type::U32); divergent.AddPhiOperand(&fixture.BlockAt(0), Value(7u)); divergent.AddPhiOperand(&fixture.BlockAt(1), Value(9u)); - fixture.Plan(); + fixture.program.descriptor_sources.push_back( {.dwords = {Value(&invariant)}, .dword_count = 1}); fixture.program.descriptor_sources.push_back( @@ -285,6 +291,7 @@ void TestControlDependentStandaloneLoadStaysTyped() { const auto read = RawRead(fixture, Address(fixture, Value(&base), Value(0u), 2), Value(0u), memory, 2); + fixture.Emit(ValueOpcode::ReferenceU32, {read}, 0, 2); fixture.Plan(); Check(fixture.program.srt_reads.empty() && read.ResolveInstruction()->GetOpcode() == @@ -307,7 +314,7 @@ void TestRuntime64BitDescriptorOps() { fixture.Emit(ValueOpcode::CompositeExtractU64, {combined, Value(0u)}); const auto high = fixture.Emit(ValueOpcode::CompositeExtractU64, {combined, Value(1u)}); - fixture.Plan(); + fixture.program.descriptor_sources.push_back( {.dwords = {low, high}, .dword_count = 2}); @@ -374,7 +381,6 @@ void TestUniformFirstLaneSamplerLod() { {packed, active}); const auto divergent = fixture.Emit( ValueOpcode::ReadFirstLane, {stale, active}); - fixture.Plan(); Check(ValidateRuntimeValue(fixture.program, first), "uniform sampler LOD construction was rejected"); @@ -425,7 +431,7 @@ void TestFloatComparisonDescriptorInputs() { {greater_equal, Value(1u), Value(0u)}); fixture.program.descriptor_sources.push_back( {.dwords = {low, high}, .dword_count = 2}); - fixture.Plan(); + for (size_t index = 0; index < inputs.size(); index++) { const auto &input = inputs[index]; const std::array user_data{input.bits}; @@ -489,8 +495,7 @@ void TestConstantBufferBounds() { {Value(0x3000u), Value(0u), Value(16u), Value(0u)}); const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer, {buffer, Value(12u)}, memory); - fixture.Emit(ValueOpcode::GetBufferResource, - {read, Value(0u), Value(16u), Value(0u)}); + LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)}); fixture.Plan(); TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}}; @@ -508,8 +513,7 @@ void TestConstantBufferBounds() { const auto overflow_read = overflow.EmitMemory(ValueOpcode::ReadConstBuffer, {overflow_buffer, Value(16u)}, overflow_memory); - overflow.Emit(ValueOpcode::GetBufferResource, - {overflow_read, Value(0u), Value(16u), Value(0u)}); + LoadBuffer(overflow, {overflow_read, Value(0u), Value(16u), Value(0u)}); overflow.Plan(); Check(!SrtWalker(overflow.program, runtime).RefreshFlatBuffer(flat), "out-of-bounds constant-buffer walk was accepted"); @@ -595,7 +599,7 @@ void TestControlFlowValueSurvivesReadLaneFolding() { void TestUndefinedRuntimeValueFails() { Fixture fixture; const auto undef = fixture.Emit(ValueOpcode::UndefU32); - fixture.Plan(); + fixture.program.descriptor_sources.push_back( {.dwords = {undef}, .dword_count = 1}); DescriptorValue result; @@ -655,6 +659,7 @@ int main() { #include "../src/graphics/shader/recompiler/ir/Program.cpp" #include "../src/graphics/shader/recompiler/ir/Type.cpp" #include "../src/graphics/shader/recompiler/ir/Value.cpp" +#include "../src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp" #include "../src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp" #include "../src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp" #include "../src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp" diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 5b766985c..745c85d8c 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -8601,6 +8601,49 @@ void TestSharedExitPreservesNativeDescriptorSources() { } } +void TestNativeScalarReadDescriptorPlanning() { + constexpr uint32_t nested = 10, read = 14, join = 18, end = 20; + const uint32_t shader[] = { + EncodeSopc(0x07, 9, 128), + EncodeSopp(0x04, end - 1 - 1), + EncodeSopc(0x07, 8, 130), + EncodeSopp(0x04, read - 3 - 1), + EncodeSop1(0x24, 0, 126), EncodeSop1(0x04, 2, 126), + EncodeSopc(0x07, 8, 129), + EncodeSopp(0x04, nested - 7 - 1), + EncodeVop1(0x01, 0, 128), + EncodeSopp(0x02, join - 9 - 1), + EncodeSop1(0x04, 0, 126), EncodeSop1(0x04, 2, 126), + EncodeVop1(0x01, 0, 129), + EncodeSopp(0x02, join - 13 - 1), + EncodeSmem0(0x0a, 16, 0), 125u << 25u, + EncodeMubuf0(0x0c, 0, false), EncodeMubuf1(0, 4, 1), + EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + }; + const std::array table{0x5000u, 0u, 4u, 3u << 28u}; + const auto address = reinterpret_cast(table.data()); + std::array user_data{}; + user_data[0] = static_cast(address); + user_data[1] = static_cast(address >> 32u); + user_data[2] = sizeof(table); + user_data[3] = 3u << 28u; + user_data[9] = 1u; + for (const uint32_t mode : {2u, 0u, 1u}) { + user_data[8] = mode; + auto options = MakeCompileOptions(ShaderType::Pixel); + options.user_data = user_data; + const auto result = RecompileForTest(shader, options, ReadHostTestMemory); + Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u && + std::ranges::equal(result.resources.flattened_srt, table), + "scalar descriptor planning retained an unrelated native execution mask"); + const auto expected = mode == 2u ? table : std::array{}; + Check(std::equal(expected.begin(), expected.end(), result.resources.buffers[0].dwords.begin()), + "scalar descriptor planning selected the wrong native resource"); + CheckSpirvBinaryValidates(result.spirv); + } +} + void TestNativeGuardedSamplerSource() { constexpr uint32_t nested = 12, sample = 18, join = 20, end = 22; const uint32_t shader[] = { @@ -12348,7 +12391,7 @@ void BuildTypedPlan(const uint32_t *code, uint32_t words, ShaderRecompiler::IR::RemoveIdentities(ir.blocks); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); } - ShaderRecompiler::IR::BuildSrtPlan(ir); + ShaderRecompiler::IR::TrackResources(ir, decoded, cfg); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); } @@ -12445,15 +12488,20 @@ void TestTypedDescriptorRealWideMoveTranslation() { EncodeMubuf1(0, 0, 1), // buffer_store_dword via copied s[0:3] EncodeSopp(0x01), }; - ShaderRecompiler::IR::Program ir; - BuildTypedPlan(shader, static_cast(std::size(shader)), ir); + ShaderRecompiler::Decoder::Program decoded; + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); + const auto native = ShaderRecompiler::CFG::BuildGraph(decoded); + ShaderComputeInputInfo compute; + auto ir = ShaderRecompiler::Frontend::TranslateProgram( + decoded, native, {.stage = ShaderType::Compute, .wave_size = 64u, + .input_info = {.compute = &compute}}); for (uint32_t reg = 0; reg < 4; ++reg) { Check(std::ranges::any_of(ir.scalar_writes, [&](const auto &write) { return write.pc == (reg / 2u) * 4u && ShaderRecompiler::IR::RegIndex(write.reg) == reg; }), "wide scalar move lost a native descriptor component write"); } - ShaderRecompiler::IR::TrackResources(ir, {}, {}); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); Check(ir.info.buffers.size() == 1, "real wide-move shader did not track one buffer use"); const auto *source = TypedDescriptorSource(ir, ir.info.buffers[0].source); @@ -12627,8 +12675,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { ShaderRecompiler::IR::Program load_ir; BuildTypedPlan(load_shader, static_cast(std::size(load_shader)), load_ir); - Check(load_ir.srt_reads.size() == 8 && - load_ir.dynamic_reads.empty(), + Check(load_ir.srt_reads.size() == 8, "real scalar loads did not build eight flattened reads"); uint32_t address_reads = 0; uint32_t buffer_reads = 0; @@ -12643,7 +12690,6 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { } Check(address_reads == 4 && buffer_reads == 4, "real scalar loads used the wrong raw typed operations"); - ShaderRecompiler::IR::TrackResources(load_ir, {}, {}); Check(load_ir.info.buffers.size() == 2, "real scalar-load descriptor sources were not attached"); for (const auto &buffer : load_ir.info.buffers) { @@ -12674,7 +12720,6 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { BuildTypedPlan(inline_sampler_shader, static_cast(std::size(inline_sampler_shader)), inline_sampler_ir); - ShaderRecompiler::IR::TrackResources(inline_sampler_ir, {}, {}); ShaderRecompiler::IR::DescriptorValue sampler; ShaderRecompiler::IR::SrtRuntime runtime; Check(inline_sampler_ir.info.samplers.size() == 1 && @@ -12705,7 +12750,7 @@ void TestSrtWalkerRealSmemTranslation() { }; ShaderRecompiler::IR::Program ir; BuildTypedPlan(shader, static_cast(std::size(shader)), ir); - Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), + Check(ir.srt_reads.size() == 4, "real SMEM translation did not build four compact SRT reads"); const std::array table = {0x11111111u, 0x22222222u, 0x33333333u, @@ -12761,7 +12806,7 @@ void TestSrtWalkerRealSBufferTranslation() { }; ShaderRecompiler::IR::Program ir; BuildTypedPlan(shader, static_cast(std::size(shader)), ir); - Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), + Check(ir.srt_reads.size() == 4, "real S_BUFFER_LOAD translation did not build four compact reads"); const std::array table = {0x11111111u, 0x22222222u, 0x33333333u, @@ -12816,7 +12861,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() { EncodeSopp(0x01)}); ShaderRecompiler::IR::Program ir; BuildTypedPlan(shader.data(), static_cast(shader.size()), ir); - Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), + Check(ir.srt_reads.size() == 4, opcode == 0x02 ? "overlapping S_LOAD operands were evaluated after a " "component write" : "overlapping S_BUFFER_LOAD operands were evaluated " @@ -12861,11 +12906,10 @@ void TestScalarMemoryLoadCrossesIntoVcc() { }; ShaderRecompiler::IR::Program ir; BuildTypedPlan(shader, static_cast(std::size(shader)), ir); - Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), + Check(ir.srt_reads.size() == 4, "wide SMEM destination crossing into VCC lost scalar provenance"); CheckFlattenedReadSlots( ir, 4, "wide SMEM destination crossing into VCC used wrong flat offsets"); - ShaderRecompiler::IR::TrackResources(ir, {}, {}); Check(ir.info.buffers.size() == 1, "wide SMEM destination crossing into VCC lost its buffer use"); const auto *source = @@ -12927,7 +12971,6 @@ void TestResourceTrackingRealDensePatching() { }; ShaderRecompiler::IR::Program ir; BuildTypedPlan(shader, static_cast(std::size(shader)), ir); - ShaderRecompiler::IR::TrackResources(ir, {}, {}); Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 && ir.info.samplers.size() == 1, "real resource tracking produced the wrong dense list sizes"); @@ -12994,7 +13037,6 @@ void TestDirectTranslationResetsAnalysisState() { ShaderRecompiler::IR::Program ir; BuildTypedPlan(first_shader, static_cast(std::size(first_shader)), ir); - ShaderRecompiler::IR::TrackResources(ir, {}, {}); ShaderComputeInputInfo compute; ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute}); Check(ir.resource_tracking_complete && ir.shader_info_complete && @@ -13003,10 +13045,13 @@ void TestDirectTranslationResetsAnalysisState() { ir.shader_hash = 0xdeadbeef; const uint32_t second_shader[] = {EncodeSopp(0x01)}; - BuildTypedPlan(second_shader, static_cast(std::size(second_shader)), - ir); + ShaderRecompiler::Decoder::Program decoded; + ShaderRecompiler::Decoder::DecodeProgram(std::span{second_shader}, decoded); + ir = ShaderRecompiler::Frontend::TranslateProgram( + decoded, ShaderRecompiler::CFG::BuildGraph(decoded), + {.stage = ShaderType::Compute, .input_info = {.compute = &compute}}); Check(!ir.resource_tracking_complete && !ir.shader_info_complete && - ir.srt_plan_complete && + !ir.srt_plan_complete && ir.srt_reads.empty() && ir.shader_hash == 0 && ir.info.buffers.empty() && ir.info.images.empty() && ir.info.samplers.empty() && ir.info.sampled_pairs.empty() && @@ -13872,6 +13917,7 @@ int main() { TestSharedReturnPreservesDescriptorDominance(); TestSharedExitPreservesNativeDescriptorSources(); TestNestedSelectionPreservesDescriptorSources(); + TestNativeScalarReadDescriptorPlanning(); TestNativeGuardedSamplerSource(); TestNativeDescriptorProvenanceRejectsGpuSelection(); TestCfgSiblingSharedExit();