shader: eliminate retained SRT lookup aliases
This commit is contained in:
parent
e1ac893a0c
commit
dbe5353f3a
|
|
@ -393,8 +393,10 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
|
|||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
|
|
|
|||
|
|
@ -598,7 +598,6 @@ struct ResourcePlan {
|
|||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<ResourceBlock> control_flow;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<uint8_t> clean_flat_slots;
|
||||
bool requires_specialization_memory = false;
|
||||
bool capture_specialization_reads = false;
|
||||
bool srt_plan_complete = false;
|
||||
|
|
|
|||
|
|
@ -173,32 +173,28 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
|||
return &program.descriptor_sources[source];
|
||||
}
|
||||
|
||||
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
|
||||
std::span<uint8_t> slots, Value extra = {}) {
|
||||
void MarkCleanReads(const std::unordered_set<const Inst*>& planned_reads,
|
||||
const DescriptorSource* source, Value extra = {}) {
|
||||
if (source == nullptr && extra.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<Value> pending;
|
||||
std::vector<Value> pending;
|
||||
if (source != nullptr) {
|
||||
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
|
||||
}
|
||||
if (!extra.IsEmpty()) pending.push_back(extra);
|
||||
std::vector<const Inst*> visited;
|
||||
while (!pending.empty()) {
|
||||
auto value = pending.back().Resolve();
|
||||
auto* inst = pending.back().Resolve().TryInstruction();
|
||||
pending.pop_back();
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) {
|
||||
continue;
|
||||
}
|
||||
visited.push_back(inst);
|
||||
if (inst->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto slot = inst->Arg(1).Resolve();
|
||||
if (slot.IsImmediate() && slot.GetType() == Type::U32 && slot.U32() < slots.size()) {
|
||||
slots[slot.U32()] = 1u;
|
||||
pending.push_back(program.srt_reads[slot.U32()].value);
|
||||
}
|
||||
continue;
|
||||
if (planned_reads.contains(inst)) {
|
||||
auto flags = inst->Flags<SrtReadFlags>();
|
||||
flags.clean = 1u;
|
||||
inst->SetFlags(flags);
|
||||
}
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); arg++) {
|
||||
pending.push_back(inst->Arg(arg));
|
||||
|
|
@ -945,6 +941,12 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
|||
if (source == nullptr) {
|
||||
return value;
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto slot = source->Arg(1).Resolve();
|
||||
EXIT_IF(!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= program.srt_reads.size());
|
||||
return Clone(program.srt_reads[slot.U32()].value);
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::ReadFirstLane &&
|
||||
ValidateRuntimeValue(program, source->Arg(0))) {
|
||||
// Uniform values need no new EXEC context; preserve their shared evaluation memo.
|
||||
|
|
@ -965,6 +967,11 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
|||
}
|
||||
auto& target =
|
||||
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
|
||||
if (source->GetOpcode() == ValueOpcode::LoadAddressU32 ||
|
||||
source->GetOpcode() == ValueOpcode::ReadConstBuffer ||
|
||||
source->GetOpcode() == ValueOpcode::LoadBufferU32) {
|
||||
target.SetFlags(SrtReadFlags {.index = source->Flags<MemoryFlags>().index});
|
||||
}
|
||||
cloned.emplace(source, &target);
|
||||
if (source->GetOpcode() == ValueOpcode::Phi) {
|
||||
for (size_t index = 0; index < source->NumArgs(); index++) {
|
||||
|
|
@ -978,12 +985,15 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
|||
return Value(&target);
|
||||
};
|
||||
|
||||
bool capture_indirect_reads = false;
|
||||
plan.descriptor_sources.reserve(program.descriptor_sources.size());
|
||||
for (const auto& source: program.descriptor_sources) {
|
||||
auto& target = plan.descriptor_sources.emplace_back();
|
||||
target.dword_count = source.dword_count;
|
||||
target.indirect_descriptor = source.indirect_descriptor;
|
||||
if (target.indirect_descriptor.has_value()) {
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads = true;
|
||||
target.indirect_descriptor->key_count = Clone(target.indirect_descriptor->key_count);
|
||||
target.indirect_descriptor->selector_first =
|
||||
Clone(target.indirect_descriptor->selector_first);
|
||||
|
|
@ -1034,34 +1044,39 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
|||
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
|
||||
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
|
||||
}
|
||||
plan.clean_flat_slots.resize(plan.srt_reads.size());
|
||||
bool capture_indirect_reads = false;
|
||||
for (const auto& source: plan.descriptor_sources) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads = true;
|
||||
if (indirect.selector)
|
||||
MarkCleanFlatSlots(plan, Source(plan, indirect.selector->source), plan.clean_flat_slots);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_mask);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_first);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.key_count);
|
||||
if (indirect.sources.empty()) {
|
||||
MarkCleanFlatSlots(plan, Source(plan, indirect.table_source), plan.clean_flat_slots);
|
||||
} else {
|
||||
for (const auto candidate: indirect.sources) {
|
||||
MarkCleanFlatSlots(plan, Source(plan, candidate), plan.clean_flat_slots);
|
||||
}
|
||||
}
|
||||
}
|
||||
plan.capture_specialization_reads |= capture_indirect_reads || !plan.control_flow.empty();
|
||||
if (plan.capture_specialization_reads) {
|
||||
// Every former flat alias now targets its retained raw read. Mark those reads
|
||||
// on the normalized graph, so discarded Phi/EXEC dependencies stay discarded.
|
||||
std::unordered_set<const Inst*> planned_reads;
|
||||
for (const auto& read: plan.srt_reads) {
|
||||
auto* inst = read.value.ResolveInstruction();
|
||||
EXIT_IF(inst == nullptr || (inst->GetOpcode() != ValueOpcode::LoadAddressU32 &&
|
||||
inst->GetOpcode() != ValueOpcode::ReadConstBuffer));
|
||||
planned_reads.insert(inst);
|
||||
}
|
||||
for (const auto& source: plan.descriptor_sources) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
if (indirect.selector)
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.selector->source));
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_mask);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_first);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.key_count);
|
||||
if (indirect.sources.empty()) {
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.table_source));
|
||||
} else {
|
||||
for (const auto candidate: indirect.sources) {
|
||||
MarkCleanReads(planned_reads, Source(plan, candidate));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean writable addresses prove that shader writes cannot overlap captured reads.
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
if (buffer.written) MarkCleanFlatSlots(plan, Source(plan, buffer.source), plan.clean_flat_slots);
|
||||
if (buffer.written) MarkCleanReads(planned_reads, Source(plan, buffer.source));
|
||||
}
|
||||
for (const auto& image: plan.info.images) {
|
||||
if (image.written) MarkCleanFlatSlots(plan, Source(plan, image.source), plan.clean_flat_slots);
|
||||
if (image.written) MarkCleanReads(planned_reads, Source(plan, image.source));
|
||||
}
|
||||
}
|
||||
if (capture_indirect_reads) plan.resource_tracking_complete &= !program.has_address_writes;
|
||||
|
|
@ -1086,7 +1101,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
|||
observed.read_memory = CaptureOrdinaryRead;
|
||||
}
|
||||
SrtWalker clean(program, CleanRuntime(observed));
|
||||
SrtWalker walker(program, observed, program.clean_flat_slots,
|
||||
SrtWalker walker(program, observed,
|
||||
capture_reads || program.requires_specialization_memory ? &clean : nullptr);
|
||||
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -324,10 +324,9 @@ private:
|
|||
} // namespace
|
||||
|
||||
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots, SrtWalker* clean_evaluator,
|
||||
Value active_mask)
|
||||
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
|
||||
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()),
|
||||
SrtWalker* clean_evaluator, Value active_mask)
|
||||
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
|
||||
m_active_mask(active_mask.Resolve()),
|
||||
m_context(AcquireContext(program)) {}
|
||||
|
||||
SrtWalker::~SrtWalker() { --m_program.evaluation_depth; }
|
||||
|
|
@ -455,7 +454,7 @@ bool SrtWalker::EvaluateExtract(const Inst& inst, uint64_t& result) {
|
|||
}
|
||||
|
||||
bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
const auto flags = inst.Flags<SrtReadFlags>();
|
||||
if (flags.index >= m_program.memory_info.size()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -557,9 +556,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
|||
return EvaluateWide(inst.Arg(0), result);
|
||||
}
|
||||
const auto clean_runtime = CleanRuntime(m_runtime);
|
||||
SrtWalker clean_active(m_program, clean_runtime, {}, nullptr, inst.Arg(1));
|
||||
SrtWalker active(m_program, m_runtime, m_clean_flat_slots, &clean_active,
|
||||
inst.Arg(1));
|
||||
SrtWalker clean_active(m_program, clean_runtime, nullptr, inst.Arg(1));
|
||||
SrtWalker active(m_program, m_runtime, &clean_active, inst.Arg(1));
|
||||
return active.EvaluateWide(inst.Arg(0), result);
|
||||
}
|
||||
case ValueOpcode::BitCastU32F32:
|
||||
|
|
@ -573,25 +571,14 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
|||
result = static_cast<uint32_t>(a) |
|
||||
(static_cast<uint64_t>(static_cast<uint32_t>(b)) << 32u);
|
||||
return true;
|
||||
case ValueOpcode::ReadConst: {
|
||||
const auto slot = inst.Arg(1).Resolve();
|
||||
if (!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= m_program.srt_reads.size()) {
|
||||
return false;
|
||||
}
|
||||
if (slot.U32() < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) {
|
||||
return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value,
|
||||
result);
|
||||
}
|
||||
return EvaluateWide(m_program.srt_reads[slot.U32()].value, result);
|
||||
}
|
||||
case ValueOpcode::LoadAddressU32:
|
||||
case ValueOpcode::ReadConstBuffer:
|
||||
case ValueOpcode::LoadBufferU32:
|
||||
if (IsRawRead(m_program, inst)) {
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadBufferU32 && m_clean_evaluator != nullptr &&
|
||||
m_clean_evaluator->m_active_mask == m_active_mask) {
|
||||
if (m_clean_evaluator != nullptr &&
|
||||
(inst.Flags<SrtReadFlags>().clean != 0u ||
|
||||
(inst.GetOpcode() == ValueOpcode::LoadBufferU32 &&
|
||||
m_clean_evaluator->m_active_mask == m_active_mask))) {
|
||||
return m_clean_evaluator->EvaluateWide(Value(const_cast<Inst*>(&inst)), result);
|
||||
}
|
||||
return EvaluateRawRead(inst, result);
|
||||
|
|
@ -934,8 +921,8 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
|||
const auto refresh = [&](uint32_t slot) {
|
||||
if (slot >= m_program.srt_reads.size()) return false;
|
||||
const auto& read = m_program.srt_reads[slot];
|
||||
const bool clean = read.flat_offset < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[read.flat_offset] != 0u;
|
||||
const auto* inst = read.value.ResolveInstruction();
|
||||
const bool clean = inst != nullptr && inst->Flags<SrtReadFlags>().clean != 0u;
|
||||
if (clean && (m_clean_evaluator == nullptr || m_runtime.read_specialization_memory == nullptr))
|
||||
return false;
|
||||
auto& evaluator = clean ? *m_clean_evaluator : *this;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ struct SrtRuntime {
|
|||
std::span<const uint32_t> workgroup_counts;
|
||||
};
|
||||
|
||||
// Retained reads no longer need the guest instruction PC. A clean read evaluates
|
||||
// its address and bounds through the strict reader as well as the final DWORD.
|
||||
struct SrtReadFlags {
|
||||
uint32_t index = 0;
|
||||
uint32_t clean = 0;
|
||||
};
|
||||
|
||||
enum class RuntimeValueType { Any, Integer };
|
||||
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
|
|
@ -31,8 +38,7 @@ SrtRuntime CleanRuntime(SrtRuntime runtime);
|
|||
class SrtWalker {
|
||||
public:
|
||||
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, SrtWalker* clean_evaluator = nullptr,
|
||||
Value active_mask = {});
|
||||
SrtWalker* clean_evaluator = nullptr, Value active_mask = {});
|
||||
~SrtWalker();
|
||||
SrtWalker(const SrtWalker&) = delete;
|
||||
SrtWalker& operator=(const SrtWalker&) = delete;
|
||||
|
|
@ -54,7 +60,6 @@ private:
|
|||
|
||||
const ResourcePlan& m_program;
|
||||
SrtRuntime m_runtime;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
SrtWalker* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
ResourcePlan::EvaluationContext& m_context;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) {
|
|||
return *result;
|
||||
}
|
||||
|
||||
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) {
|
||||
Libs::Graphics::ShaderRecompiler::IR::Program SrtProgram(uint64_t address) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
Program program;
|
||||
program.stage = Libs::Graphics::ShaderType::Compute;
|
||||
|
|
@ -62,7 +62,7 @@ Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) {
|
|||
source.dwords[1] = Value(0u);
|
||||
source.dword_count = 2;
|
||||
program.descriptor_sources.push_back(source);
|
||||
return ExtractResourcePlan(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UnbasedFlatPlan() {
|
||||
|
|
@ -152,7 +152,7 @@ Libs::Graphics::ShaderRecompiler::IR::Program MixedSamplerProgram() {
|
|||
void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto plan = SrtPlan(reinterpret_cast<uint64_t>(&dword));
|
||||
auto plan = ExtractResourcePlan(SrtProgram(reinterpret_cast<uint64_t>(&dword)));
|
||||
uint32_t specialization_reads = 0;
|
||||
const SrtRuntime runtime{.userdata = &specialization_reads,
|
||||
.read_specialization_memory =
|
||||
|
|
@ -170,7 +170,7 @@ void TestMappedSrtUsesDirectReaderByDefault() {
|
|||
|
||||
void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto plan = SrtPlan(0x10000);
|
||||
auto plan = SrtProgram(0x10000);
|
||||
const auto root = plan.descriptor_sources.front().dwords[0];
|
||||
Check(ValidateRuntimeValue(plan, root, RuntimeValueType::Integer),
|
||||
"integer SRT read was rejected");
|
||||
|
|
@ -217,6 +217,98 @@ void TestIntegerRuntimeValueFollowsSrtReads() {
|
|||
"cyclic SRT read-first-lane dependency was accepted");
|
||||
}
|
||||
|
||||
void TestSrtAliasesRetainReadPolicy() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto program = SrtProgram(0x1000u);
|
||||
auto &block = *program.blocks[0];
|
||||
const auto append_read = [&](uint32_t address) {
|
||||
auto &handle = block.AppendNewInst(ValueOpcode::GetAddressResource,
|
||||
{Value(address), Value(0u)});
|
||||
auto &read = block.AppendNewInst(ValueOpcode::LoadAddressU32,
|
||||
{Value(&handle), Value(0u), Value(0u), Value(true)});
|
||||
// Different reads deliberately share MemoryInfo but have different guest PCs.
|
||||
read.SetFlags(MemoryFlags{.index = 0, .pc = address});
|
||||
const auto slot = static_cast<uint32_t>(program.srt_reads.size());
|
||||
program.srt_reads.push_back({Value(&read), slot});
|
||||
auto &srt = block.AppendNewInst(ValueOpcode::GetSrtResource);
|
||||
return Value(&block.AppendNewInst(ValueOpcode::ReadConst,
|
||||
{Value(&srt), Value(slot)}));
|
||||
};
|
||||
const auto ordinary = append_read(0x2000u);
|
||||
const auto pointer = append_read(0x3000u);
|
||||
program.srt_reads[0].value.Instruction()->Arg(0).Instruction()->SetArg(0, pointer);
|
||||
auto &mask = block.AppendNewInst(ValueOpcode::INotEqual32, {ordinary, Value(0u)});
|
||||
auto &first = block.AppendNewInst(ValueOpcode::ReadFirstLane,
|
||||
{Value(9u), Value(&mask)});
|
||||
program.descriptor_sources.push_back({.dwords = {Value(&first), Value(0u)},
|
||||
.dword_count = 2});
|
||||
DescriptorSource indirect;
|
||||
indirect.indirect_descriptor.emplace().sources = {0, 1};
|
||||
program.descriptor_sources.push_back(indirect);
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
for (const auto &inst : plan.value_storage) {
|
||||
Check(inst.GetOpcode() != ValueOpcode::ReadConst &&
|
||||
inst.GetOpcode() != ValueOpcode::GetSrtResource,
|
||||
"retained plan still contains flattened-read aliases");
|
||||
}
|
||||
Check(plan.descriptor_sources[0].dwords[0] == plan.srt_reads[0].value &&
|
||||
plan.srt_reads[0].value.Instruction()->Flags<SrtReadFlags>().clean == 1u &&
|
||||
plan.srt_reads[1].value.Instruction()->Flags<SrtReadFlags>().clean == 0u &&
|
||||
plan.srt_reads[2].value.Instruction()->Flags<SrtReadFlags>().clean == 1u,
|
||||
"alias normalization lost read identity or retained discarded EXEC provenance");
|
||||
struct Reads { uint32_t strict = 0; uint32_t ordinary = 0; bool dirty = false; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
if (address == 0x2000u) words[0] = 0x2222u;
|
||||
else if (address == 0x3000u) words[0] = 0x8000u;
|
||||
else if (address == 0x8000u) words[0] = 0xeeeeu;
|
||||
else return false;
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address,
|
||||
std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
++reads.strict;
|
||||
if (reads.dirty) return false;
|
||||
if (address == 0x3000u) words[0] = 0x1000u;
|
||||
else if (address == 0x1000u) words[0] = 0x1111u;
|
||||
else return false;
|
||||
return true;
|
||||
}};
|
||||
std::vector<uint32_t> flat;
|
||||
DescriptorValue descriptor;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(runtime));
|
||||
SrtWalker walker(plan, runtime, &clean);
|
||||
Check(walker.RefreshFlatBuffer(flat) &&
|
||||
flat == std::vector<uint32_t>{0x1111u, 0x2222u, 0x1000u} &&
|
||||
walker.EvaluateDescriptor(0, descriptor) && descriptor.dwords[0] == 0x1111u &&
|
||||
reads.strict == 2 && reads.ordinary == 1,
|
||||
"normalized aliases changed nested strict reads or repeated a shared read");
|
||||
}
|
||||
Check(!SrtWalker(plan, runtime).RefreshFlatBuffer(flat),
|
||||
"strict flat read accepted a missing clean evaluator");
|
||||
auto no_reader = runtime;
|
||||
no_reader.read_specialization_memory = nullptr;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(no_reader));
|
||||
Check(!SrtWalker(plan, no_reader, &clean).RefreshFlatBuffer(flat),
|
||||
"strict flat read accepted a missing strict reader");
|
||||
}
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, descriptor) &&
|
||||
descriptor.dwords[0] == 0xeeeeu && reads.strict == 2 && reads.ordinary == 3,
|
||||
"direct descriptor evaluation without a clean evaluator changed read domains");
|
||||
reads.dirty = true;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(runtime));
|
||||
Check(!SrtWalker(plan, runtime, &clean).RefreshFlatBuffer(flat) &&
|
||||
reads.strict == 3 && reads.ordinary == 3,
|
||||
"dirty strict pointer fell back to an ordinary read");
|
||||
}
|
||||
}
|
||||
|
||||
void TestUniformVectorDescriptorRead() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
Program program;
|
||||
|
|
@ -285,16 +377,24 @@ void TestUniformVectorDescriptorRead() {
|
|||
}
|
||||
count.SetArg(4, Value(false));
|
||||
auto &inactive = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&count), Value(false)});
|
||||
program.descriptor_sources.push_back({.dwords = {Value(&inactive)}, .dword_count = 1});
|
||||
reads.strict = 0;
|
||||
uint32_t result = 99;
|
||||
Check(SrtWalker(program, runtime).Evaluate(Value(&inactive), result) &&
|
||||
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
||||
"literal false EXEC read vector memory");
|
||||
{
|
||||
const auto plan = ExtractResourcePlan(program);
|
||||
Check(SrtWalker(plan, runtime).Evaluate(plan.descriptor_sources.back().dwords[0], result) &&
|
||||
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
||||
"literal false EXEC read vector memory");
|
||||
}
|
||||
count.SetArg(4, Value(true));
|
||||
handle.SetArg(3, Value(0x204u));
|
||||
Check(SrtWalker(program, runtime).Evaluate(Value(&count), result) &&
|
||||
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
||||
"invalid vector buffer format read memory");
|
||||
program.descriptor_sources.back().dwords[0] = Value(&count);
|
||||
{
|
||||
const auto plan = ExtractResourcePlan(program);
|
||||
Check(SrtWalker(plan, runtime).Evaluate(plan.descriptor_sources.back().dwords[0], result) &&
|
||||
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
||||
"invalid vector buffer format read memory");
|
||||
}
|
||||
handle.SetArg(3, Value(0x16204u));
|
||||
count.SetArg(1, Value(&lane));
|
||||
Check(!ValidateRuntimeValue(program, Value(&count)),
|
||||
|
|
@ -567,6 +667,7 @@ void DbgExit(int) { std::abort(); }
|
|||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestIntegerRuntimeValueFollowsSrtReads();
|
||||
TestSrtAliasesRetainReadPolicy();
|
||||
TestUniformVectorDescriptorRead();
|
||||
TestExactReciprocalDescriptorArithmetic();
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
|
|
|
|||
|
|
@ -1758,17 +1758,18 @@ void TestRuntimeUnsignedMinDescriptor() {
|
|||
{descriptor, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(memory, 0x330));
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
|
||||
std::array<uint32_t, 1> user_data{0xffffffffu};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue value;
|
||||
const auto source = fixture.program.info.buffers[0].source;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, value) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, value) &&
|
||||
value.dwords[3] == 0x100u,
|
||||
"runtime descriptor unsigned minimum did not clamp its first operand");
|
||||
user_data[0] = 0x80u;
|
||||
Check(
|
||||
SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, value) &&
|
||||
SrtWalker(plan, runtime).EvaluateDescriptor(source, value) &&
|
||||
value.dwords[3] == 0x80u,
|
||||
"runtime descriptor unsigned minimum did not preserve its first operand");
|
||||
}
|
||||
|
|
@ -1875,6 +1876,7 @@ void TestSampleAdjustSamplerScratch() {
|
|||
{image, shift_sampler, fixture.ImageAddress()},
|
||||
fixture.AddMemory(memory, 0x200));
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
|
||||
const auto source = fixture.program.info.samplers[0].source;
|
||||
const auto stored = fixture.program.descriptor_sources[source]
|
||||
|
|
@ -1886,12 +1888,12 @@ void TestSampleAdjustSamplerScratch() {
|
|||
std::array<uint32_t, 4> user_data{4u, 1u, 2u, 0x80000abcu};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, descriptor) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, descriptor) &&
|
||||
descriptor.dwords[3] == 0x80000abcu,
|
||||
"SampleAdjust canonicalization lost sampler border fields");
|
||||
const auto shift_source = fixture.program.info.samplers[
|
||||
shift_sampler.Instruction()->Flags<uint32_t>()].source;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(shift_source, descriptor) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(shift_source, descriptor) &&
|
||||
std::ranges::equal(std::span(descriptor.dwords).first(4),
|
||||
std::array{0x36u, 0xfff000u, 0x02500000u, 0u}),
|
||||
"SampleAdjust shift-only scratch was not reduced to its zero border payload");
|
||||
|
|
@ -2239,6 +2241,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
|||
SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue descriptor;
|
||||
std::vector<uint32_t> flat;
|
||||
const uint32_t request = fixture.program.info.buffers[0].source;
|
||||
|
|
@ -2246,27 +2249,25 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
|||
SrtWalker walker(plan, runtime);
|
||||
return walker.EvaluateDescriptor(request, descriptor) && walker.RefreshFlatBuffer(flat);
|
||||
};
|
||||
Check(refresh(fixture.program), "typed runtime source evaluation failed");
|
||||
Check(refresh(plan), "typed runtime source evaluation failed");
|
||||
Check(descriptor.dwords[0] == 0xdeadbeefu &&
|
||||
flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1,
|
||||
"descriptor and flat SRT evaluation did not share one memoized read");
|
||||
|
||||
memory.reads = 0;
|
||||
memory.words[1] = 0x12345678u;
|
||||
Check(refresh(fixture.program) && descriptor.dwords[0] == 0x12345678u &&
|
||||
Check(refresh(plan) && descriptor.dwords[0] == 0x12345678u &&
|
||||
flat == std::vector<uint32_t>{0x12345678u} && memory.reads == 1,
|
||||
"repeated runtime evaluation reused stale scalar memory");
|
||||
|
||||
memory.reads = 0;
|
||||
memory.fail_after = 0;
|
||||
Check(!refresh(fixture.program), "unavailable scalar memory was accepted");
|
||||
Check(!refresh(plan), "unavailable scalar memory was accepted");
|
||||
memory.fail_after = UINT32_MAX;
|
||||
Check(refresh(fixture.program) && descriptor.dwords[0] == 0x12345678u && memory.reads == 1,
|
||||
Check(refresh(plan) && descriptor.dwords[0] == 0x12345678u && memory.reads == 1,
|
||||
"failed runtime evaluation left a value marked as visiting");
|
||||
|
||||
auto detached = ExtractResourcePlan(fixture.program);
|
||||
Check(refresh(detached), "detached resource plan did not evaluate");
|
||||
auto moved = std::move(detached);
|
||||
auto moved = std::move(plan);
|
||||
memory.reads = 0;
|
||||
memory.words[1] = 0x87654321u;
|
||||
Check(refresh(moved) && descriptor.dwords[0] == 0x87654321u && memory.reads == 1,
|
||||
|
|
@ -2296,6 +2297,7 @@ void TestDynamicSrtReadRemainsExplicit() {
|
|||
{descriptor, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(buffer, 8));
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
|
||||
Check(fixture.program.srt_reads.empty() &&
|
||||
read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 &&
|
||||
|
|
@ -2309,7 +2311,7 @@ void TestDynamicSrtReadRemainsExplicit() {
|
|||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
DescriptorValue value;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, value) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, value) &&
|
||||
value.dwords[0] == 0xabcdef01u && memory.reads == 1,
|
||||
"dynamic typed scalar descriptor source was not evaluated");
|
||||
|
||||
|
|
@ -2475,7 +2477,7 @@ void TestConditionalSamplerPhi() {
|
|||
memory.fail_address = 0x1000 + (first == 448 / 4 ? 480u : 448u);
|
||||
DescriptorValue selected;
|
||||
SrtWalker clean(plan, CleanRuntime(runtime));
|
||||
Check(SrtWalker(plan, runtime, {}, &clean).EvaluateDescriptor(source, selected),
|
||||
Check(SrtWalker(plan, runtime, &clean).EvaluateDescriptor(source, selected),
|
||||
"conditional sampler did not survive detached plan lifetime");
|
||||
for (uint32_t word = 0; word < 4; ++word) {
|
||||
Check(selected.dwords[word] == memory.words[first + word],
|
||||
|
|
@ -2487,13 +2489,13 @@ void TestConditionalSamplerPhi() {
|
|||
no_clean_reader.read_specialization_memory = nullptr;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(no_clean_reader));
|
||||
Check(!SrtWalker(plan, no_clean_reader, {}, &clean).EvaluateDescriptor(source, selected),
|
||||
Check(!SrtWalker(plan, no_clean_reader, &clean).EvaluateDescriptor(source, selected),
|
||||
"conditional sampler used unchecked memory for its predicate");
|
||||
}
|
||||
memory.fail_address = 0x2000 + 196;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(runtime));
|
||||
Check(!SrtWalker(plan, runtime, {}, &clean).EvaluateDescriptor(source, selected),
|
||||
Check(!SrtWalker(plan, runtime, &clean).EvaluateDescriptor(source, selected),
|
||||
"conditional sampler ignored unavailable coherent predicate memory");
|
||||
}
|
||||
}
|
||||
|
|
@ -2544,6 +2546,7 @@ void TestFiniteImagePhiCycle() {
|
|||
fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()},
|
||||
fixture.AddMemory(memory, 4));
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_descriptor;
|
||||
Check(finite && finite->sources.size() == 2,
|
||||
|
|
@ -2558,7 +2561,7 @@ void TestFiniteImagePhiCycle() {
|
|||
"finite image key did not preserve the loop-carried selection");
|
||||
std::array<uint32_t, 16> data;
|
||||
for (uint32_t i = 0; i < data.size(); ++i) data[i] = 100u + i;
|
||||
SrtWalker walker(fixture.program, SrtRuntime{.user_data = data});
|
||||
SrtWalker walker(plan, SrtRuntime{.user_data = data});
|
||||
for (uint32_t candidate = 0; candidate < 2; ++candidate) {
|
||||
DescriptorValue descriptor;
|
||||
Check(walker.EvaluateDescriptor(finite->sources[candidate], descriptor),
|
||||
|
|
@ -2658,10 +2661,11 @@ void TestLoopCycleEnteredThroughRuntimeValue() {
|
|||
ConstantPropagationPass(fixture.program.blocks);
|
||||
RemoveIdentities(fixture.program.blocks);
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
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(
|
||||
SrtWalker(plan, {.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");
|
||||
|
|
@ -2687,11 +2691,12 @@ void TestInvariantLoopPhi() {
|
|||
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(memory, 4), loop);
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
|
||||
std::array<uint32_t, 1> user_data{0x12345678u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, descriptor) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, descriptor) &&
|
||||
descriptor.dwords[0] == user_data[0],
|
||||
"loop-invariant descriptor phi was not evaluated through typed SSA");
|
||||
}
|
||||
|
|
@ -2799,10 +2804,11 @@ void TestBoundedRelativeRegisterWrites() {
|
|||
continue;
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
const std::array<uint32_t, 3> user_data{0x1000u, 0u, 72u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(
|
||||
fixture.program.info.buffers[0].source, descriptor) &&
|
||||
descriptor.dwords[2] == 72u,
|
||||
"impossible relative writes left a false descriptor dependency");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#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/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
|
@ -131,7 +132,7 @@ void TestImmediateFlatteningAndGvn() {
|
|||
TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed");
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>{0xfeedbeefu} && memory_image.reads == 1,
|
||||
"flattened SRT did not evaluate its canonical read once");
|
||||
}
|
||||
|
|
@ -147,7 +148,7 @@ void TestRawScalarComponentAlignment() {
|
|||
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed");
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed");
|
||||
Check(
|
||||
flat == std::vector<uint32_t>{0x12345678u} && memory_image.reads == 1,
|
||||
"raw scalar base, immediate, and offset were not aligned independently");
|
||||
|
|
@ -202,7 +203,7 @@ void TestNestedSrtWalk() {
|
|||
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "nested SRT walk failed");
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "nested SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>({0x2000u, 0xabcdef01u}),
|
||||
"nested typed SRT reads were not evaluated in dependency order");
|
||||
}
|
||||
|
|
@ -225,7 +226,7 @@ void TestShaderBaseAndUserData() {
|
|||
SrtRuntime runtime{.user_data = user_data,
|
||||
.shader_base = 0x12345678abcdef00ull};
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result),
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).EvaluateDescriptor(0, result),
|
||||
"shader-relative descriptor evaluation failed");
|
||||
Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u &&
|
||||
result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u,
|
||||
|
|
@ -250,7 +251,7 @@ void TestCarryAndBitFields() {
|
|||
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
|
||||
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result),
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
|
||||
"carry and bit-field descriptor evaluation failed");
|
||||
Check(result.dwords[0] == 1u && result.dwords[1] == 1u &&
|
||||
result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu,
|
||||
|
|
@ -273,11 +274,12 @@ void TestInvariantAndDivergentPhi() {
|
|||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {Value(&divergent)}, .dword_count = 1});
|
||||
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result) &&
|
||||
Check(SrtWalker(plan, {}).EvaluateDescriptor(0, result) &&
|
||||
result.dwords[0] == 7u,
|
||||
"loop-invariant typed phi was rejected");
|
||||
Check(!SrtWalker(fixture.program, {}).EvaluateDescriptor(1, result),
|
||||
Check(!SrtWalker(plan, {}).EvaluateDescriptor(1, result),
|
||||
"divergent phi was accepted");
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +376,7 @@ void TestRuntime64BitDescriptorOps() {
|
|||
{.dwords = {low, high}, .dword_count = 2});
|
||||
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result) &&
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result) &&
|
||||
result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u,
|
||||
"64-bit typed descriptor arithmetic evaluation is incorrect");
|
||||
}
|
||||
|
|
@ -447,12 +449,13 @@ void TestUniformFirstLaneSamplerLod() {
|
|||
std::array<uint32_t, 7> user_data{};
|
||||
user_data[6] = 3u;
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
|
||||
result.dwords[0] == 0x00300300u,
|
||||
"uniform sampler LOD evaluated incorrectly");
|
||||
user_data[6] = 20u;
|
||||
Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result) &&
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
|
||||
result.dwords[0] == 0x00ffffffu,
|
||||
"uniform sampler LOD clamp evaluated incorrectly");
|
||||
}
|
||||
|
|
@ -487,11 +490,12 @@ void TestFloatComparisonDescriptorInputs() {
|
|||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {low, high}, .dword_count = 2});
|
||||
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
for (size_t index = 0; index < inputs.size(); index++) {
|
||||
const auto &input = inputs[index];
|
||||
const std::array user_data{input.bits};
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(fixture.program, {.user_data = user_data})
|
||||
Check(SrtWalker(plan, {.user_data = user_data})
|
||||
.EvaluateDescriptor(0, result) &&
|
||||
result.dwords[0] == (flush && index < 4 ? 1 : input.less_equal) &&
|
||||
result.dwords[1] == (flush && index < 4 ? 1 : input.greater_equal),
|
||||
|
|
@ -566,7 +570,7 @@ void TestConstantBufferBounds() {
|
|||
TestMemory memory_image{{{0x3000u, 0x12345678u}, {0x300cu, 0xa5a5a5a5u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) == test.valid,
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat) == test.valid,
|
||||
"constant-buffer walk misaligned or wrapped its offset components");
|
||||
Check(test.valid ? flat == std::vector<uint32_t>{test.expected} : memory_image.reads == 0,
|
||||
"constant-buffer walk read the wrong word or accessed an out-of-bounds address");
|
||||
|
|
@ -690,7 +694,7 @@ void TestUndefinedRuntimeValueFails() {
|
|||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {undef}, .dword_count = 1});
|
||||
DescriptorValue result;
|
||||
Check(!SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result),
|
||||
Check(!SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
|
||||
"undefined typed descriptor source was accepted");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#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/ResourceMaterialization.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"
|
||||
|
|
@ -10564,14 +10565,16 @@ void TestMergedShaderUserDataSnapshot() {
|
|||
const auto &program = translated.program;
|
||||
Check(program.info.buffers.size() == 2 && program.srt_reads.size() == 4,
|
||||
"merged shader lost front user SGPRs or the back-stage SRT load");
|
||||
const auto plan = IR::ExtractResourcePlan(program);
|
||||
for (const auto *params : {&first, &second}) {
|
||||
const auto user_data = std::span(params->user_data).first(params->user_data_count);
|
||||
const IR::SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadHostTestMemory};
|
||||
IR::SrtWalker walker(plan, runtime);
|
||||
IR::DescriptorValue front_descriptor, back_descriptor;
|
||||
const auto &table = params == &first ? first_table : second_table;
|
||||
Check(IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) &&
|
||||
IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) &&
|
||||
Check(walker.EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) &&
|
||||
walker.EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) &&
|
||||
std::equal(user_data.begin() + 8, user_data.end(),
|
||||
front_descriptor.dwords.begin()) &&
|
||||
std::equal(table.begin(), table.end(), back_descriptor.dwords.begin()),
|
||||
|
|
@ -13743,7 +13746,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
|||
ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base,
|
||||
nullptr, nullptr};
|
||||
ShaderRecompiler::IR::DescriptorValue carry_value;
|
||||
Check(ShaderRecompiler::IR::SrtWalker(carry_ir, carry_runtime).EvaluateDescriptor(carry_source_index, carry_value) &&
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(carry_ir), carry_runtime).EvaluateDescriptor(carry_source_index, carry_value) &&
|
||||
carry_value.dwords[0] == static_cast<uint32_t>(expected_pc) &&
|
||||
carry_value.dwords[1] == static_cast<uint32_t>(expected_pc >> 32u),
|
||||
"S_GETPC shader-base or add/addc carry evaluation was incorrect");
|
||||
|
|
@ -13813,7 +13816,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
|||
TypedDescriptorSource(inline_sampler_ir,
|
||||
inline_sampler_ir.info.samplers[0].source) !=
|
||||
nullptr &&
|
||||
ShaderRecompiler::IR::SrtWalker(inline_sampler_ir, runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) &&
|
||||
ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) &&
|
||||
sampler.dwords[0] == 0 && sampler.dwords[1] == 0x00fff000u &&
|
||||
sampler.dwords[2] == 0x09500000u && sampler.dwords[3] == 0,
|
||||
"real inline sampler construction was unresolved or evaluated "
|
||||
|
|
@ -13849,7 +13852,7 @@ void TestSrtWalkerRealSmemTranslation() {
|
|||
std::vector<uint32_t> flat;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat);
|
||||
Check(walked, "SRT walk failed");
|
||||
Check(flat.size() == table.size() &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
|
|
@ -13878,7 +13881,7 @@ void TestSrtWalkerVccBaseTranslation() {
|
|||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(flat.size() == table.size() &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
"typed SSA lost an SMEM base copied through VCC");
|
||||
|
|
@ -13906,14 +13909,15 @@ void TestSrtWalkerRealSBufferTranslation() {
|
|||
std::vector<uint32_t> flat;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||
const auto plan = ShaderRecompiler::IR::ExtractResourcePlan(ir);
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat);
|
||||
Check(walked, "SRT walk failed");
|
||||
Check(flat.size() == 4 &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
"real S_BUFFER_LOAD walk did not align offset components independently");
|
||||
|
||||
user_data[10] = 3 * sizeof(uint32_t);
|
||||
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat);
|
||||
Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds");
|
||||
CheckFlattenedReadSlots(
|
||||
ir, 4, "real S_BUFFER_LOAD patch used the wrong flat offsets");
|
||||
|
|
@ -13928,7 +13932,7 @@ void TestSrtWalkerRealSBufferTranslation() {
|
|||
static_cast<uint32_t>(std::size(negative_shader)),
|
||||
negative_ir);
|
||||
user_data[10] = sizeof(table);
|
||||
Check(!ShaderRecompiler::IR::SrtWalker(negative_ir, runtime).RefreshFlatBuffer(flat),
|
||||
Check(!ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime).RefreshFlatBuffer(flat),
|
||||
"real S_BUFFER_LOAD walk accepted a negative immediate");
|
||||
}
|
||||
|
||||
|
|
@ -13967,7 +13971,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() {
|
|||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
user_data, 0, ReadSrtHostRangeDword, &range};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(flat.size() == table.size() &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
"overlapping scalar-memory load did not capture its sources before "
|
||||
|
|
|
|||
Loading…
Reference in New Issue