mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 21:06:55 +02:00
shader: resolve descriptors before resource planning
This commit is contained in:
@@ -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 <algorithm>
|
||||
@@ -624,8 +623,6 @@ TranslateResult TranslateProgram(std::span<const uint32_t> 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<IR::MemoryFlags>().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) {
|
||||
|
||||
@@ -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<ExportInfo> export_info;
|
||||
std::vector<Value> dynamic_reads;
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
|
||||
@@ -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 <algorithm>
|
||||
@@ -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<MemoryFlags>();
|
||||
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<Position> positions {{use->id, use_pc}};
|
||||
std::vector<bool> 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<MemoryFlags>();
|
||||
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<MemoryFlags>().pc);
|
||||
for (uint32_t word = 0; word < width; ++word)
|
||||
CollectScalarRead(source.dwords[word], inst->Flags<MemoryFlags>().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<MemoryFlags>();
|
||||
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<MemoryFlags>().pc);
|
||||
}
|
||||
}
|
||||
for (auto* read: m_scalar_reads) {
|
||||
const auto flags = read->Flags<MemoryFlags>();
|
||||
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<MemoryFlags>().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<uint64_t>()));
|
||||
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<MemoryFlags>();
|
||||
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<Program::ScalarWrite> m_scalar_writes;
|
||||
std::vector<ResolvedHandle> m_resolved_handles;
|
||||
std::vector<const Inst*> m_srt_visiting;
|
||||
std::vector<const Inst*> m_srt_visited;
|
||||
std::vector<Inst*> m_scalar_reads;
|
||||
ShaderInfo m_info;
|
||||
std::vector<DescriptorSource> m_sources;
|
||||
std::vector<HandlePatch> m_handle_patches;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <unordered_set>
|
||||
|
||||
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<const Inst*> 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<MemoryFlags>();
|
||||
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<MemoryFlags>().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<uint32_t>(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<uint64_t>()));
|
||||
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<MemoryFlags>().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<Inst*> m_visiting;
|
||||
std::vector<Inst*> m_visited;
|
||||
std::vector<Patch> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<uint32_t, 3> 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<uint32_t, 1> 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<uint32_t>(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"
|
||||
|
||||
@@ -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 <cstdlib>
|
||||
@@ -71,7 +72,7 @@ struct Fixture {
|
||||
return static_cast<uint32_t>(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<Value, 4> 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<ScalarReg>(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<Value>{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<ScalarReg>(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"
|
||||
|
||||
+64
-18
@@ -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<uint32_t, 4> table{0x5000u, 0u, 4u, 3u << 28u};
|
||||
const auto address = reinterpret_cast<uint64_t>(table.data());
|
||||
std::array<uint32_t, 10> user_data{};
|
||||
user_data[0] = static_cast<uint32_t>(address);
|
||||
user_data[1] = static_cast<uint32_t>(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<uint32_t, 4>{};
|
||||
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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t, 4> table = {0x11111111u, 0x22222222u, 0x33333333u,
|
||||
@@ -12761,7 +12806,7 @@ void TestSrtWalkerRealSBufferTranslation() {
|
||||
};
|
||||
ShaderRecompiler::IR::Program ir;
|
||||
BuildTypedPlan(shader, static_cast<uint32_t>(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<uint32_t, 5> table = {0x11111111u, 0x22222222u, 0x33333333u,
|
||||
@@ -12816,7 +12861,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() {
|
||||
EncodeSopp(0x01)});
|
||||
ShaderRecompiler::IR::Program ir;
|
||||
BuildTypedPlan(shader.data(), static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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();
|
||||
|
||||
Reference in New Issue
Block a user