mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 12:26:51 +01:00
shader: walk every resource plan with the x86 SRT walker
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "SrtWalkerTestMemory.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
@@ -7,12 +8,6 @@
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <csignal>
|
||||
#include <sys/mman.h>
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void Check(bool value, const char *text) {
|
||||
@@ -38,6 +33,24 @@ AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
// Lists one extra source per block and walks the plan: each source's activity is its block's reachability.
|
||||
std::vector<uint8_t> ReachedBlocks(Libs::Graphics::ShaderRecompiler::IR::ResourcePlan &plan,
|
||||
const Libs::Graphics::ShaderRecompiler::IR::SrtRuntime &runtime) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
const auto first = static_cast<uint32_t>(plan.descriptor_sources.size());
|
||||
for (uint32_t block = 0; block < plan.control_flow.size(); ++block) {
|
||||
plan.descriptor_sources.emplace_back();
|
||||
plan.control_flow[block].sources.push_back(first + block);
|
||||
}
|
||||
CompileSrtWalker(plan);
|
||||
std::vector<uint32_t> flat;
|
||||
Check(WalkSrt(plan, runtime, flat, nullptr), "reachability walk failed");
|
||||
std::vector<uint8_t> reached;
|
||||
for (uint32_t block = 0; block < plan.control_flow.size(); ++block)
|
||||
reached.push_back(WalkedActive(plan, first + block));
|
||||
return reached;
|
||||
}
|
||||
|
||||
Libs::Graphics::ShaderRecompiler::IR::Program SrtProgram(uint64_t address) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
Program program;
|
||||
@@ -155,7 +168,7 @@ Libs::Graphics::ShaderRecompiler::IR::Program MixedSamplerProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
void TestWalkerLoadsBypassSpecializationReader() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto plan = ExtractResourcePlan(SrtProgram(reinterpret_cast<uint64_t>(&dword)));
|
||||
@@ -168,10 +181,10 @@ void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"mapped SRT stage materialization failed");
|
||||
Check(specialization_reads == 0,
|
||||
"ordinary SRT read used the specialization reader");
|
||||
"walker load used the specialization reader");
|
||||
Check(snapshot.flattened_srt.size() == 1 &&
|
||||
snapshot.flattened_srt[0] == dword,
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
"walker did not load the mapped SRT DWORD");
|
||||
}
|
||||
|
||||
void TestNativeNestedSrtRefresh() {
|
||||
@@ -215,17 +228,6 @@ void TestNativeNestedSrtRefresh() {
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
{
|
||||
SrtWalker walker(plan, runtime);
|
||||
Check(walker.RefreshFlatBuffer(snapshot.flattened_srt),
|
||||
"native SRT refresh failed");
|
||||
const auto loaded = first[1];
|
||||
first[1] ^= 0xffffffffu;
|
||||
uint32_t value = 0;
|
||||
Check(walker.Evaluate(plan.srt_reads[0].value, value) && value == loaded,
|
||||
"descriptor evaluation reloaded a value already copied by the native walker");
|
||||
first[1] = loaded;
|
||||
}
|
||||
for (const auto *data : {&first, &second}) {
|
||||
table = pointer_words(data->data());
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
@@ -233,32 +235,20 @@ void TestNativeNestedSrtRefresh() {
|
||||
std::vector<uint32_t>{(*data)[1], table[0], table[1], (*data)[2]},
|
||||
"native SRT refresh lost pointer masking, alignment, or current values");
|
||||
}
|
||||
#if defined(__linux__)
|
||||
struct sigaction action {}, previous {};
|
||||
action.sa_sigaction = +[](int, siginfo_t *, void *context) {
|
||||
auto ®isters = static_cast<ucontext_t *>(context)->uc_mcontext.gregs;
|
||||
const auto length = SrtWalkerLoadLength(static_cast<uint64_t>(registers[REG_RIP]));
|
||||
if (length == 0) std::abort();
|
||||
registers[REG_RAX] = 0;
|
||||
registers[REG_RIP] += length;
|
||||
};
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
auto *page = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Check(page != MAP_FAILED && sigaction(SIGSEGV, &action, &previous) == 0 &&
|
||||
MaterializeResources(plan, {.user_data = pointer_words(page)}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt == std::vector<uint32_t>(4, 0u),
|
||||
"faulting walker loads of a table and a null pointer did not read zero");
|
||||
sigaction(SIGSEGV, &previous, nullptr);
|
||||
munmap(page, 4096);
|
||||
#endif
|
||||
{
|
||||
// Unmapped tables and the null pointer they yield read as zero.
|
||||
FakeGuestMemory unmapped;
|
||||
const std::array<uint32_t, 2> unmapped_table{0x1000u, 0u};
|
||||
Check(MaterializeResources(plan, {.user_data = unmapped_table}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt == std::vector<uint32_t>(4, 0u),
|
||||
"faulting walker loads of a table and a null pointer did not read zero");
|
||||
}
|
||||
Check(!MaterializeResources(plan, {.user_data = std::span(user_data).first(1)},
|
||||
snapshot, specialization),
|
||||
"native SRT walk accepted truncated user data");
|
||||
const std::array<uint32_t, 2> fake_user_data{0x1003u, 0xabcd0000u};
|
||||
uint32_t reads = 0;
|
||||
const SrtRuntime callback_runtime{
|
||||
.user_data = fake_user_data,
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
++*static_cast<uint32_t *>(data);
|
||||
switch (address) {
|
||||
case 0x1000u: words[0] = 0x2003u; break;
|
||||
@@ -268,12 +258,11 @@ void TestNativeNestedSrtRefresh() {
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads};
|
||||
Check(MaterializeResources(plan, callback_runtime, snapshot, specialization) && reads == 4 &&
|
||||
}, &reads);
|
||||
Check(MaterializeResources(plan, {.user_data = fake_user_data}, snapshot, specialization) && reads == 4 &&
|
||||
snapshot.flattened_srt ==
|
||||
std::vector<uint32_t>{0x1234u, 0x2003u, 0xabcd0000u, 0x5678u},
|
||||
"native-eligible SRT bypassed its callback or repeated nested pointer reads");
|
||||
"native SRT walk repeated nested pointer reads");
|
||||
leaf->SetArg(0, root);
|
||||
leaf->SetArg(1, Value(0u));
|
||||
program.srt_reads.resize(1);
|
||||
@@ -290,17 +279,6 @@ void TestNativeNestedSrtRefresh() {
|
||||
Check(!MaterializeResources(bounded, {.user_data = base}, snapshot, specialization),
|
||||
"native SRT accepted address underflow or 48-bit overflow");
|
||||
}
|
||||
high.Instruction()->SetArg(0, Value(ScalarReg(6)));
|
||||
Check(ExtractResourcePlan(program).srt_code == nullptr,
|
||||
"native SRT accepted nonadjacent user-data pointer words");
|
||||
high.Instruction()->SetArg(0, Value(ScalarReg(5)));
|
||||
program.memory_info[0].offset = 0;
|
||||
program.srt_reads.push_back({pointer_low, 1});
|
||||
program.srt_reads.push_back({pointer_high, 2});
|
||||
leaf->SetArg(0, emit(ValueOpcode::GetAddressResource, {pointer_low, pointer_high}));
|
||||
pointer_high.Instruction()->SetArg(1, Value(8u));
|
||||
Check(ExtractResourcePlan(program).srt_code == nullptr,
|
||||
"native SRT accepted nonadjacent nested pointer words");
|
||||
}
|
||||
|
||||
void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||
@@ -352,7 +330,7 @@ void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||
"cyclic SRT read-first-lane dependency was accepted");
|
||||
}
|
||||
|
||||
void TestSrtAliasesRetainReadPolicy() {
|
||||
void TestSrtAliasesShareReads() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto program = SrtProgram(0x1000u);
|
||||
auto &block = *program.blocks[0];
|
||||
@@ -386,64 +364,27 @@ void TestSrtAliasesRetainReadPolicy() {
|
||||
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");
|
||||
Check(plan.srt_code == nullptr,
|
||||
"captured strict reads compiled an unused x86 walker");
|
||||
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) {
|
||||
Check(plan.descriptor_sources[0].dwords[0] == plan.srt_reads[0].value,
|
||||
"alias normalization lost read identity");
|
||||
struct Reads { uint32_t count = 0; bool dirty = false; } reads;
|
||||
FakeGuestMemory fake(+[](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;
|
||||
++reads.count;
|
||||
if (address == 0x1000u) words[0] = 0x1111u;
|
||||
else if (address == 0x2000u) words[0] = 0x2222u;
|
||||
else if (address == 0x3000u && !reads.dirty) words[0] = 0x1000u;
|
||||
else return false;
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
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");
|
||||
}
|
||||
Check(WalkSrt(plan, {}, flat, nullptr) && flat == std::vector<uint32_t>{0x1111u, 0x2222u, 0x1000u} &&
|
||||
reads.count == 3,
|
||||
"normalized aliases changed nested reads or repeated a shared read");
|
||||
// An unreadable pointer reads as zero, and so does the null table it yields.
|
||||
reads = {.dirty = true};
|
||||
Check(WalkSrt(plan, {}, flat, nullptr) && flat == std::vector<uint32_t>{0u, 0x2222u, 0u} &&
|
||||
reads.count == 3,
|
||||
"unreadable pointer did not read as zero");
|
||||
}
|
||||
|
||||
void TestUniformVectorDescriptorRead() {
|
||||
@@ -477,59 +418,66 @@ void TestUniformVectorDescriptorRead() {
|
||||
program.descriptor_sources.push_back(source);
|
||||
program.info.buffers.push_back({.source = 0, .written = true});
|
||||
Check(ValidateRuntimeValue(program, Value(&count)), "uniform DWORD count was rejected");
|
||||
struct Reads { uint32_t value = 72; uint32_t strict = 0; uint32_t ordinary = 0; bool clean = true; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = [](void *data, uint64_t, std::span<uint32_t> words) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
words[0] = 999;
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = [](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
struct Reads { uint32_t value = 72; uint32_t count = 0; bool readable = true; } reads;
|
||||
FakeGuestMemory fake([](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
++reads.strict;
|
||||
if (!reads.clean || address != 0x1000u || words.size() != 1) return false;
|
||||
++reads.count;
|
||||
if (!reads.readable || address != 0x1000u || words.size() != 1) return false;
|
||||
words[0] = reads.value;
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
const SrtRuntime runtime;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const bool written : {false, true}) {
|
||||
program.info.buffers[0].written = written;
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(plan.control_flow.empty() && plan.requires_specialization_memory &&
|
||||
plan.capture_specialization_reads,
|
||||
Check(plan.control_flow.empty() && plan.capture_specialization_reads,
|
||||
"vector descriptor read depended on incidental control-flow capture");
|
||||
reads = {};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers[0].dwords[2] == 145 && reads.strict == 1 && reads.ordinary == 0 &&
|
||||
snapshot.buffers[0].dwords[2] == 145 && reads.count == 1 &&
|
||||
snapshot.specialization_reads ==
|
||||
std::vector<std::pair<uint64_t, uint64_t>>{{0x1000u, 4u}},
|
||||
"vector descriptor input was not read and captured exactly once");
|
||||
reads.clean = false;
|
||||
reads.strict = 0;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
reads.strict == 1 && reads.ordinary == 0,
|
||||
"dirty vector descriptor input fell back to an ordinary memory read");
|
||||
// An unreadable input reads as zero, so the count selects 0 + 1.
|
||||
reads = {.readable = false};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers[0].dwords[2] == 1 && reads.count == 1,
|
||||
"unreadable vector descriptor input did not read as zero");
|
||||
}
|
||||
// A read guarded by its EXEC mask depends on that mask, so it cannot reuse the unmasked read.
|
||||
auto &guarded = block.AppendNewInst(ValueOpcode::LoadBufferU32,
|
||||
{Value(&handle), Value(0u), Value(0u), Value(0u), Value(&active)});
|
||||
guarded.SetFlags(MemoryFlags{.index = 0});
|
||||
auto &guarded_lane = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&guarded), Value(&active)});
|
||||
program.descriptor_sources.push_back({.dwords = {Value(&guarded_lane)}, .dword_count = 1});
|
||||
{
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
uint32_t value = 0;
|
||||
reads = {};
|
||||
Check(EvaluateSrtValue(plan, runtime, plan.descriptor_sources.back().dwords[0], value) &&
|
||||
value == 72u && reads.count == 1,
|
||||
"EXEC-guarded vector read lost its mask");
|
||||
}
|
||||
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;
|
||||
reads.count = 0;
|
||||
uint32_t result = 99;
|
||||
{
|
||||
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,
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(EvaluateSrtValue(plan, runtime, plan.descriptor_sources.back().dwords[0], result) &&
|
||||
result == 0 && reads.count == 0,
|
||||
"literal false EXEC read vector memory");
|
||||
}
|
||||
count.SetArg(4, Value(true));
|
||||
handle.SetArg(3, Value(0x204u));
|
||||
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,
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(EvaluateSrtValue(plan, runtime, plan.descriptor_sources.back().dwords[0], result) &&
|
||||
result == 0 && reads.count == 0,
|
||||
"invalid vector buffer format read memory");
|
||||
}
|
||||
handle.SetArg(3, Value(0x16204u));
|
||||
@@ -547,7 +495,7 @@ void TestExactReciprocalDescriptorArithmetic() {
|
||||
std::bit_cast<float>(253u << 23u)}) {
|
||||
auto &reciprocal = block.AppendNewInst(ValueOpcode::FPRecipIFlag32, {Value::F32(divisor)});
|
||||
uint32_t result = 0;
|
||||
Check(SrtWalker(program, {}).Evaluate(Value(&reciprocal), result) &&
|
||||
Check(EvaluateSrtValue(program, {}, Value(&reciprocal), result) &&
|
||||
result == std::bit_cast<uint32_t>(1.f / divisor),
|
||||
"power-of-two reciprocal was not exact");
|
||||
}
|
||||
@@ -556,7 +504,7 @@ void TestExactReciprocalDescriptorArithmetic() {
|
||||
std::bit_cast<float>(254u << 23u)}) {
|
||||
auto &reciprocal = block.AppendNewInst(ValueOpcode::FPRecipIFlag32, {Value::F32(divisor)});
|
||||
uint32_t result = 0;
|
||||
Check(!SrtWalker(program, {}).Evaluate(Value(&reciprocal), result),
|
||||
Check(!EvaluateSrtValue(program, {}, Value(&reciprocal), result),
|
||||
"unsupported reciprocal rounding or exceptional input was accepted");
|
||||
}
|
||||
}
|
||||
@@ -593,7 +541,7 @@ void TestWrittenDescriptorPredicateReads(bool memory_condition) {
|
||||
source.dword_count = 4;
|
||||
program.descriptor_sources.push_back(source);
|
||||
program.info.buffers.push_back({.source = 0, .written = true});
|
||||
// Only memory-derived host decisions need clean, disjoint descriptor reads.
|
||||
// Only memory-derived host decisions capture their descriptor reads.
|
||||
auto &condition = block.AppendNewInst(ValueOpcode::IEqual32,
|
||||
{memory_condition ? Value(&read) : Value(&offset),
|
||||
Value(memory_condition ? 0x8000u : 4u)});
|
||||
@@ -619,35 +567,25 @@ void TestWrittenDescriptorPredicateReads(bool memory_condition) {
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(plan.capture_specialization_reads == memory_condition && !plan.control_flow.empty(),
|
||||
"conditional writable descriptor used the wrong memory dependency policy");
|
||||
struct Reads { uint32_t ordinary = 0; uint32_t strict = 0; bool clean = false; } reads;
|
||||
struct Reads { uint32_t count = 0; bool readable = false; } reads;
|
||||
const std::array<uint32_t, 1> user_data{4u};
|
||||
const SrtRuntime runtime{
|
||||
.user_data = user_data,
|
||||
.read_memory = +[](void *data, uint64_t, std::span<uint32_t> words) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
words[0] = 0x8000u;
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
++reads.strict;
|
||||
if (!reads.clean || address != 0x1004u) return false;
|
||||
++reads.count;
|
||||
if (!reads.readable || address != 0x1004u) return false;
|
||||
words[0] = 0x8000u;
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const bool clean : {false, true}) {
|
||||
reads = {.clean = clean};
|
||||
// A failed predicate explores both edges; its dirty read also rejects the writable descriptor.
|
||||
const bool expected = !memory_condition || clean;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) == expected &&
|
||||
reads.ordinary == (memory_condition ? 0u : 1u) &&
|
||||
reads.strict == (memory_condition ? 1u : 0u),
|
||||
"writable descriptor changed reader policy or bypassed a dirty memory predicate");
|
||||
if (!expected) continue;
|
||||
Check(snapshot.buffers[0].dwords[0] == 0x8000u &&
|
||||
for (const bool readable : {false, true}) {
|
||||
reads = {.readable = readable};
|
||||
// The predicate and the descriptor share one read. An unreadable one is zero, which
|
||||
// makes a memory predicate skip the store block and leave its descriptor inactive.
|
||||
Check(MaterializeResources(plan, {.user_data = user_data}, snapshot, specialization) &&
|
||||
reads.count == 1,
|
||||
"writable descriptor repeated or skipped its shared read");
|
||||
Check(snapshot.buffers[0].dwords[0] == (readable ? 0x8000u : 0u) &&
|
||||
snapshot.specialization_reads == (memory_condition
|
||||
? std::vector<std::pair<uint64_t, uint64_t>>{{0x1004u, 4u}}
|
||||
: std::vector<std::pair<uint64_t, uint64_t>>{}),
|
||||
@@ -704,16 +642,13 @@ void TestSharedControlPredicates() {
|
||||
"shared predicates expanded into trees or retained varying dependencies");
|
||||
for (const uint32_t flag : {0u, 1u}) {
|
||||
const std::array<uint32_t, 1> user_data{flag};
|
||||
SrtWalker walker(plan, {.user_data = user_data});
|
||||
uint32_t result = 99;
|
||||
const bool conjunction = operation == ValueOpcode::LogicalAnd;
|
||||
const bool known = conjunction ? flag == 0 : flag != 0;
|
||||
Check(walker.Evaluate(plan.control_flow[0].condition, result) == known &&
|
||||
Check(EvaluateSrtValue(plan, {.user_data = user_data}, plan.control_flow[0].condition, result) == known &&
|
||||
(!known || result == uint32_t{conjunction}),
|
||||
"shared predicate changed its uniform factor or cached runtime values");
|
||||
std::vector<uint32_t> flat;
|
||||
Check(walker.RefreshFlatBuffer(flat) &&
|
||||
plan.visited_blocks == (known
|
||||
Check(ReachedBlocks(plan, {.user_data = user_data}) == (known
|
||||
? (conjunction ? std::vector<uint8_t>{1, 1, 0, 1}
|
||||
: std::vector<uint8_t>{1, 0, 1, 0})
|
||||
: std::vector<uint8_t>{1, 1, 1, 1}),
|
||||
@@ -772,34 +707,27 @@ void TestNestedExecPredicatesReuseReads() {
|
||||
{buffer, Value(0u), Value(0u), Value(0u), Value(true)})
|
||||
.SetFlags(MemoryFlags{.index = 1});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
struct Reads { uint32_t value = 0; bool clean = true; uint32_t strict = 0, ordinary = 0; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
return false;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
struct Reads { uint32_t value = 0; bool readable = true; uint32_t count = 0; } reads;
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
++reads.strict;
|
||||
Check(reads.strict <= depth + 1,
|
||||
"shared EXEC predicates repeated scalar reads beyond their distinct masks");
|
||||
if (!reads.clean || address != 0x1000u || words.size() != 1) return false;
|
||||
++reads.count;
|
||||
if (!reads.readable || address != 0x1000u || words.size() != 1) return false;
|
||||
words[0] = reads.value;
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const auto state : {Reads{64u, true}, Reads{0u, true}, Reads{0u, false}, Reads{64u, true}}) {
|
||||
for (const auto state : {Reads{64u, true}, Reads{0u, true}, Reads{64u, false}, Reads{64u, true}}) {
|
||||
reads = state;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
Check(MaterializeResources(plan, {}, snapshot, specialization),
|
||||
"nested EXEC predicates rejected a valid resource snapshot");
|
||||
const bool inactive = reads.clean && reads.value == 0;
|
||||
// An unreadable count reads as zero.
|
||||
const bool inactive = !reads.readable || reads.value == 0;
|
||||
Check(snapshot.buffers[0].dwords[0] == (inactive ? 0u : 0x8000u),
|
||||
"nested EXEC predicates lost an unknown branch or retained stale runtime facts");
|
||||
Check(reads.strict > 0 && reads.ordinary == 0 &&
|
||||
snapshot.specialization_reads.size() <= depth + 1,
|
||||
"shared EXEC predicates repeated scalar reads beyond their distinct masks");
|
||||
Check(reads.count == 1 && snapshot.specialization_reads ==
|
||||
std::vector<std::pair<uint64_t, uint64_t>>{{0x1000u, 4u}},
|
||||
"EXEC contexts repeated a mask-independent scalar read");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,16 +773,8 @@ void TestFiniteImageRefreshReusesScalarReads() {
|
||||
struct Reads {
|
||||
std::array<uint32_t, 3> words{0x100u, 0x200u, 0x200u};
|
||||
std::array<uint32_t, 3> counts{};
|
||||
uint32_t ordinary = 0;
|
||||
} reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
return false;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address,
|
||||
std::span<uint32_t> words) {
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
if (words.size() != 1 || address < 0x1000u || address >= 0x100cu ||
|
||||
(address & 3u) != 0) return false;
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
@@ -862,7 +782,8 @@ void TestFiniteImageRefreshReusesScalarReads() {
|
||||
++reads.counts[index];
|
||||
words[0] = reads.words[index];
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
const SrtRuntime runtime;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto capacities = [&] {
|
||||
@@ -881,15 +802,15 @@ void TestFiniteImageRefreshReusesScalarReads() {
|
||||
};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"finite image materialization failed");
|
||||
Check(reads.ordinary == 0 && reads.counts == std::array<uint32_t, 3>{1, 1, 1} &&
|
||||
Check(reads.counts == std::array<uint32_t, 3>{1, 1, 1} &&
|
||||
snapshot.specialization_reads.size() == 3,
|
||||
"finite image candidates repeated scalar reads or bypassed clean provenance");
|
||||
"finite image candidates repeated or skipped scalar reads");
|
||||
check_mapping({0, 1, 1, 1});
|
||||
const auto warm_capacities = capacities();
|
||||
reads.words = {0x300u, 0x300u, 0x400u};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"finite image refresh failed after descriptor changes");
|
||||
Check(reads.ordinary == 0 && reads.counts == std::array<uint32_t, 3>{2, 2, 2} &&
|
||||
Check(reads.counts == std::array<uint32_t, 3>{2, 2, 2} &&
|
||||
snapshot.specialization_reads.size() == 3 &&
|
||||
snapshot.images[0].dwords[0] == 0x300u && snapshot.images[1].dwords[0] == 0x400u,
|
||||
"finite image refresh retained old scalar values or repeated reads");
|
||||
@@ -992,36 +913,35 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask, bool count
|
||||
emit(use, ValueOpcode::LoadBufferU32, {buffer, Value(0u), Value(0u), Value(0u), Value(true)})
|
||||
.Instruction()->SetFlags(MemoryFlags{.index = buffer_index});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
struct Reads { uint32_t count = 0; bool clean = true; uint32_t descriptors = 0; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
struct Reads { uint32_t count = 0; bool readable = true; uint32_t descriptors = 0; } reads;
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
constexpr std::array<uint32_t, 4> descriptor{0x8000u, 1u << 16u, 16u,
|
||||
(3u << 28u) | 0x4dfacu};
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
if (address == 0x1000u && reads.readable) {
|
||||
words[0] = reads.count;
|
||||
return true;
|
||||
}
|
||||
if (address < 0x2000u || address >= 0x2010u || address % 4u || words.size() != 1)
|
||||
return false;
|
||||
++static_cast<Reads *>(data)->descriptors;
|
||||
++reads.descriptors;
|
||||
words[0] = descriptor[(address - 0x2000u) / 4u];
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
const auto &reads = *static_cast<Reads *>(data);
|
||||
if (!reads.clean || address != 0x1000u || words.size() != 1) return false;
|
||||
words[0] = reads.count;
|
||||
return true;
|
||||
}};
|
||||
}, &reads);
|
||||
const SrtRuntime runtime;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const auto state : {Reads{0u, true}, Reads{4u, true}, Reads{0u, false}, Reads{0u, true}}) {
|
||||
for (const auto state : {Reads{0u, true}, Reads{4u, true}, Reads{4u, false}, Reads{0u, true}}) {
|
||||
reads = state;
|
||||
if (!counted_loop) {
|
||||
uint32_t condition = 99;
|
||||
Check(!SrtWalker(plan, runtime).Evaluate(plan.control_flow[3].condition, condition),
|
||||
Check(!EvaluateSrtValue(plan, runtime, plan.control_flow[3].condition, condition),
|
||||
"direct merge evaluation used missing or previous runtime reachability");
|
||||
}
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"partial traversal proof rejected a valid resource snapshot");
|
||||
const bool inactive = !independent_mask && reads.clean && reads.count == 0u;
|
||||
// An unreadable count reads as zero.
|
||||
const bool inactive = !independent_mask && (!reads.readable || reads.count == 0u);
|
||||
Check(snapshot.buffers[0].dwords[0] == (inactive ? 0u : 0x8000u),
|
||||
"empty traversal kept its descriptor or an uncertain traversal lost it");
|
||||
Check(specialization.buffers[0].zero_stride_oob == inactive,
|
||||
@@ -1029,7 +949,7 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask, bool count
|
||||
Check(reads.descriptors == (inactive ? 0u : 4u),
|
||||
"inactive traversal read its descriptor or an uncertain traversal skipped it");
|
||||
if (inactive) {
|
||||
Check(!snapshot.specialization_reads.empty(), "empty traversal lost its clean scalar read");
|
||||
Check(!snapshot.specialization_reads.empty(), "empty traversal lost its captured scalar read");
|
||||
for (const auto &read : snapshot.specialization_reads)
|
||||
Check(read == std::pair<uint64_t, uint64_t>{0x1000u, 4u},
|
||||
"empty traversal proof captured an unrelated memory range");
|
||||
@@ -1075,16 +995,89 @@ void TestCyclicControlPredicatesRemainUnknown() {
|
||||
branch(left, a, right, exit);
|
||||
branch(right, b, left, exit);
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
SrtWalker walker(plan, {});
|
||||
uint32_t value = 99;
|
||||
Check(!walker.Evaluate(plan.control_flow[1].condition, value) &&
|
||||
!walker.Evaluate(plan.control_flow[2].condition, value),
|
||||
Check(!EvaluateSrtValue(plan, {}, plan.control_flow[1].condition, value) &&
|
||||
!EvaluateSrtValue(plan, {}, plan.control_flow[2].condition, value),
|
||||
"cyclic varying control acquired an unsupported uniform value");
|
||||
std::vector<uint32_t> flat;
|
||||
Check(walker.RefreshFlatBuffer(flat) && plan.visited_blocks == std::vector<uint8_t>({1, 1, 1, 1}),
|
||||
Check(ReachedBlocks(plan, {}) == std::vector<uint8_t>({1, 1, 1, 1}),
|
||||
"cyclic control lost a possible branch during reachability");
|
||||
}
|
||||
|
||||
void TestBackEdgeReachesPhiPredecessor() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
using Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
||||
Program program;
|
||||
program.stage = Libs::Graphics::ShaderType::Compute;
|
||||
program.user_data_count = 3;
|
||||
program.srt_plan_complete = program.resource_tracking_complete = true;
|
||||
for (uint32_t i = 0; i < 4; ++i) AddValueBlock(program);
|
||||
auto &entry = *program.blocks[0];
|
||||
auto &header = *program.blocks[1];
|
||||
auto &body = *program.blocks[2];
|
||||
auto &exit = *program.blocks[3];
|
||||
const auto branch = [](Block &block, Block &target, Value condition = {}, Block *other = nullptr) {
|
||||
block.terminator.kind = other ? TerminatorKind::ConditionalBranch : TerminatorKind::Branch;
|
||||
block.terminator.true_block = ⌖
|
||||
block.terminator.false_block = other;
|
||||
block.condition = condition;
|
||||
block.AddBranch(&target);
|
||||
if (other) block.AddBranch(other);
|
||||
};
|
||||
// header: p = Phi(entry: 0, body: 1); table[1] == p enters body, which returns to header. The
|
||||
// first pass evaluates p and loads table[1] before body is reached; the walk must revisit both,
|
||||
// capture the load once and reach exit, which loads table[0].
|
||||
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress});
|
||||
const auto table = Value(&entry.AppendNewInst(ValueOpcode::GetAddressResource,
|
||||
{Value(&entry.AppendNewInst(ValueOpcode::GetUserData, {Value(ScalarReg(0))})),
|
||||
Value(&entry.AppendNewInst(ValueOpcode::GetUserData, {Value(ScalarReg(1))}))}));
|
||||
const auto load = [&](Block &block, uint32_t offset) -> Inst & {
|
||||
auto &read = block.AppendNewInst(ValueOpcode::LoadAddressU32, {table, Value(offset), Value(0u), Value(true)});
|
||||
read.SetFlags(MemoryFlags{.index = 0});
|
||||
return read;
|
||||
};
|
||||
auto &p = header.AppendNewInst(ValueOpcode::Phi);
|
||||
p.SetFlags(Type::U32);
|
||||
p.AddPhiOperand(&entry, Value(0u));
|
||||
p.AddPhiOperand(&body, Value(1u));
|
||||
branch(entry, header);
|
||||
branch(header, body, Value(&header.AppendNewInst(ValueOpcode::IEqual32, {Value(&load(header, 4u)), Value(&p)})),
|
||||
&exit);
|
||||
branch(body, header);
|
||||
exit.terminator.kind = TerminatorKind::Return;
|
||||
program.srt_reads.push_back({Value(&load(exit, 0u)), 0});
|
||||
exit.AppendNewInst(ValueOpcode::ReadConst,
|
||||
{Value(&exit.AppendNewInst(ValueOpcode::GetSrtResource)), Value(0u)});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(plan.control_flow.size() == 4 && plan.control_phi_predecessors.size() == 1,
|
||||
"back-edge plan lost its control flow or Phi");
|
||||
plan.capture_specialization_reads = true;
|
||||
const std::array<uint32_t, 2> user_data{0x1000u, 0u};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
{
|
||||
// The table is unmapped: its loads read zero instead of faulting the walk.
|
||||
FakeGuestMemory unmapped;
|
||||
Check(MaterializeResources(plan, {.user_data = user_data}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt == std::vector<uint32_t>{0u},
|
||||
"faulting load in a reachable block did not read zero");
|
||||
}
|
||||
for (uint32_t selected : {0u, 1u}) {
|
||||
FakeGuestMemory fake(+[](void *userdata, uint64_t address, std::span<uint32_t> words) {
|
||||
words[0] = address == 0x1000u ? 0x5678u : *static_cast<uint32_t *>(userdata);
|
||||
return address == 0x1000u || address == 0x1004u;
|
||||
}, &selected);
|
||||
Check(MaterializeResources(plan, {.user_data = user_data}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt == std::vector<uint32_t>{0x5678u},
|
||||
"back edge to an evaluated Phi left its exit unreachable");
|
||||
Check(snapshot.specialization_reads ==
|
||||
std::vector<std::pair<uint64_t, uint64_t>>{{0x1004u, 4u}, {0x1000u, 4u}},
|
||||
"Phi restart kept the loads of the abandoned pass");
|
||||
Check(ReachedBlocks(plan, {.user_data = user_data}) ==
|
||||
(selected == 0u ? std::vector<uint8_t>{1, 1, 1, 1} : std::vector<uint8_t>{1, 1, 0, 1}),
|
||||
"Phi restart followed a rejected edge or missed a reachable one");
|
||||
}
|
||||
}
|
||||
|
||||
void TestEmptyScalarBufferSpecialization() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
struct Fixture {
|
||||
@@ -1144,8 +1137,9 @@ void TestEmptyScalarBufferSpecialization() {
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
std::array<uint32_t, 4> descriptor{0x1000u, 8u << 16u, 0u, 3u << 28u};
|
||||
uint32_t reads = 0u;
|
||||
const SrtRuntime runtime{.user_data = descriptor, .read_memory = RejectSpecializationRead,
|
||||
.userdata = &reads, .read_specialization_memory = RejectSpecializationRead};
|
||||
const SrtRuntime runtime{.user_data = descriptor, .userdata = &reads,
|
||||
.read_specialization_memory = RejectSpecializationRead};
|
||||
FakeGuestMemory fake(RejectSpecializationRead, &reads);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization, empty, populated;
|
||||
for (const uint32_t records : {0u, 1u, 0x20000000u, 0u}) {
|
||||
@@ -1377,10 +1371,10 @@ void DbgExit(int) { std::abort(); }
|
||||
} // namespace Common
|
||||
|
||||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestWalkerLoadsBypassSpecializationReader();
|
||||
TestNativeNestedSrtRefresh();
|
||||
TestIntegerRuntimeValueFollowsSrtReads();
|
||||
TestSrtAliasesRetainReadPolicy();
|
||||
TestSrtAliasesShareReads();
|
||||
TestUniformVectorDescriptorRead();
|
||||
TestExactReciprocalDescriptorArithmetic();
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
@@ -1394,6 +1388,7 @@ int main() {
|
||||
TestEmptyTraversalSkipsUnmappedDescriptor(independent_mask);
|
||||
TestEmptyTraversalSkipsUnmappedDescriptor(false, true);
|
||||
TestCyclicControlPredicatesRemainUnknown();
|
||||
TestBackEdgeReachesPhiPredecessor();
|
||||
TestEmptyScalarBufferSpecialization();
|
||||
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
||||
TestSignedStorageSpecialization();
|
||||
|
||||
+152
-154
@@ -5,7 +5,7 @@
|
||||
#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 "SrtWalkerTestMemory.h"
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
@@ -212,6 +212,12 @@ bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span<uint32_t>
|
||||
return true;
|
||||
}
|
||||
|
||||
// Serves walker loads from the memory a test runtime currently points at.
|
||||
bool ReadRuntimeMemory(void *userdata, uint64_t address, std::span<uint32_t> values) {
|
||||
const auto &runtime = *static_cast<const SrtRuntime *>(userdata);
|
||||
return runtime.read_specialization_memory(runtime.userdata, address, values);
|
||||
}
|
||||
|
||||
std::unique_ptr<Fixture>
|
||||
MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t table_offset = 0,
|
||||
uint32_t table_shader_offset = 0, bool nested = false,
|
||||
@@ -424,9 +430,8 @@ void TestInvariantIndirectImageMaterialization(bool nested) {
|
||||
const auto handle = std::ranges::find_if(*wrap->block, [](const Inst &inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::GetImageResource;
|
||||
});
|
||||
SrtWalker walker(wrap->program, runtime);
|
||||
uint32_t offset = 0;
|
||||
Check(walker.Evaluate(handle->Arg(0), offset) && offset == expected,
|
||||
Check(EvaluateSrtValue(wrap->program, runtime, handle->Arg(0), offset) && offset == expected,
|
||||
"GPU descriptor offset projection lost U32 wrap or scalar immediate carry");
|
||||
}
|
||||
auto malformed = MakeIndirectImageFixture(true);
|
||||
@@ -624,6 +629,7 @@ void TestBoundedImageViewEligibility() {
|
||||
plan.descriptor_sources.push_back(source);
|
||||
}
|
||||
plan.descriptor_sources[image_source].indirect_descriptor->sources = std::move(selected);
|
||||
CompileSrtWalker(plan);
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"explicitly selected incompatible descriptor was silently normalized to null");
|
||||
}
|
||||
@@ -873,9 +879,8 @@ void TestWaterfallImageTable() {
|
||||
std::array{UINT32_MAX, 0x5204u, UINT32_MAX}, std::array{0u, 0u, UINT32_MAX}}) {
|
||||
data[4] = index;
|
||||
data[3] = format;
|
||||
SrtWalker walker(fixture->program, runtime);
|
||||
uint32_t offset = 0;
|
||||
Check(walker.Evaluate(handle->Arg(0), offset) && offset == expected,
|
||||
Check(EvaluateSrtValue(fixture->program, runtime, handle->Arg(0), offset) && offset == expected,
|
||||
"VMEM table projection lost record bounds, format disable or multiplication carry");
|
||||
}
|
||||
auto disabled = make(true, false);
|
||||
@@ -1113,7 +1118,7 @@ void TestNestedPointerImageTable() {
|
||||
memory.fail_address = 0x1800u;
|
||||
*at(table) = 0u;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"unreadable null pointer row bypassed the strict pointee reader");
|
||||
"unreadable null pointer row bypassed the specialization reader");
|
||||
*at(table) = objects;
|
||||
|
||||
auto capped = make(4u);
|
||||
@@ -1381,7 +1386,7 @@ void TestIndexedPointerImageTable() {
|
||||
*at(table) = 0u;
|
||||
*at(table + 4u) = 0u;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"explicit null pointer bypassed the strict child reader");
|
||||
"explicit null pointer bypassed the specialization reader");
|
||||
auto reversed = make(Case::ReverseSigned);
|
||||
reversed->PlanAndTrack();
|
||||
for (const auto variant : {Case::MissingParent, Case::WrongParent, Case::MissingChild,
|
||||
@@ -1398,8 +1403,9 @@ void TestIndexedPointerImageTable() {
|
||||
const auto counted_plan = ExtractResourcePlan(counted->program);
|
||||
constexpr uint32_t count_address = 0x1800u;
|
||||
std::array<uint32_t, 6> counted_data{table, 1u | (stride << 16u), 2u, 0x5204u, count_address, 1u};
|
||||
const SrtRuntime counted_runtime{.user_data = counted_data, .read_memory = ReadLinearTestMemory, .userdata = &memory,
|
||||
const SrtRuntime counted_runtime{.user_data = counted_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
*at(table) = objects;
|
||||
*at(table + 4u) = 0xbeef0001u;
|
||||
*at(count_address) = *at(count_address + 4u) = 2u;
|
||||
@@ -1431,9 +1437,11 @@ void TestIndexedPointerImageTable() {
|
||||
Check(!MaterializeResources(counted_plan, counted_runtime, snapshot, specialization) && memory.watched_reads == 0u,
|
||||
"runtime parent count exceeding V# records was clamped or read candidates");
|
||||
*at(count_address) = 2u;
|
||||
// An unreadable parent count reads as zero: an empty domain without candidate reads.
|
||||
memory.fail_address = high + count_address;
|
||||
Check(!MaterializeResources(counted_plan, counted_runtime, snapshot, specialization) && memory.watched_reads == 0u,
|
||||
"unreadable parent count was ignored before candidate reads");
|
||||
Check(MaterializeResources(counted_plan, counted_runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 1u && memory.watched_reads == 0u,
|
||||
"unreadable parent count did not read as an empty domain");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
for (const auto variant : {Case::MissingCount, Case::DifferentCount, Case::UnprovenMask}) {
|
||||
auto unproved = make(variant);
|
||||
@@ -1780,9 +1788,9 @@ void TestGuardedDirectImageTable() {
|
||||
};
|
||||
fill_table(memory, table);
|
||||
std::array<uint32_t, 2> user_data{0x1000u, 0u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory,
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadRuntimeMemory, &runtime);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
auto packed = make_plan(Guard::Plain, 0u, true);
|
||||
@@ -2352,10 +2360,9 @@ void TestBoundedComputeImageLoop() {
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
}
|
||||
std::array<uint32_t, 3> user_data{0x1800u, 0u, 2u};
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory,
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const auto post : {PostTest::Signed, PostTest::Unsigned}) {
|
||||
@@ -2668,8 +2675,8 @@ void TestDescendingImageRange() {
|
||||
std::array<uint32_t, 4> user_data {0x1000u, 0u, 0u, UINT32_MAX};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime {.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
const SrtRuntime runtime {.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
for (const auto count : {0u, 2u, 0u}) {
|
||||
user_data[2] = count;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.images.size() == 2u,
|
||||
@@ -3176,7 +3183,7 @@ void TestUniformizedMaterialImageKeys() {
|
||||
make_plan(Variant::FirstLane);
|
||||
make_plan(Variant::FirstLaneDirect);
|
||||
plan = make_plan(Variant::FirstLaneAndNot);
|
||||
Check(plan.requires_specialization_memory &&
|
||||
Check(plan.capture_specialization_reads &&
|
||||
plan.descriptor_sources[plan.info.images[0].source]
|
||||
.indirect_descriptor->selector_mask.Resolve().TryInstruction() != nullptr,
|
||||
"uniformized image mask did not survive extraction");
|
||||
@@ -3207,10 +3214,8 @@ void TestUniformizedMaterialImageKeys() {
|
||||
user_data[9] = 16u << 16u;
|
||||
user_data[10] = 1u;
|
||||
memory.fail_address = base + 0xc00u + 3u * 0x90u;
|
||||
const SrtRuntime runtime{
|
||||
.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
@@ -3388,7 +3393,7 @@ void TestIndexedMaterialImageKeys() {
|
||||
if (shared) {
|
||||
owner_key.ResolveInstruction()->ReplaceUsesWith(Value(12994u));
|
||||
uint32_t offset = 0u;
|
||||
Check(SrtWalker(f.program, {}).Evaluate(shared_image.ResolveInstruction()->Arg(0), offset) &&
|
||||
Check(EvaluateSrtValue(f.program, {}, shared_image.ResolveInstruction()->Arg(0), offset) &&
|
||||
offset == 12994u * 32u,
|
||||
"inherited indexed selector changed GPU byte offsets into raw material keys");
|
||||
}
|
||||
@@ -3811,10 +3816,9 @@ void TestUniformScalarBufferImage() {
|
||||
memory.words.begin() + (0x1010u + index * 48u) / 4u);
|
||||
memory.words[(0x1010u + index * 48u) / 4u] += index;
|
||||
}
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory,
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materializes = [&](uint32_t address) {
|
||||
@@ -3827,9 +3831,10 @@ void TestUniformScalarBufferImage() {
|
||||
memory.words[0x44u / 4u] = 0u;
|
||||
memory.words[0x1010u / 4u] = 0x30u;
|
||||
Check(materializes(0x30u), "changed scalar table memory reused the previous image descriptor");
|
||||
// An unreadable key reads as zero and selects the first record instead of the second.
|
||||
memory.words[0x44u / 4u] = 1u;
|
||||
memory.fail_address = 0x1044u;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"unavailable nested image table was accepted");
|
||||
Check(materializes(0x30u), "unavailable nested image key did not read as zero");
|
||||
}
|
||||
|
||||
void TestCompactScalarImageTable() {
|
||||
@@ -4258,8 +4263,8 @@ void TestScalarBufferSelectorDomain() {
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
}
|
||||
std::array<uint32_t, 6> user_data {0x1000u, 0u, 180u, 0u, 0x2000u, 0u};
|
||||
const SrtRuntime runtime {.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
const SrtRuntime runtime {.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
memory.watched_address = 0x1000u + 144u;
|
||||
@@ -4314,8 +4319,8 @@ void TestScalarBufferSelectorDomain() {
|
||||
user_data[1] = 2u << 16u;
|
||||
user_data[2] = UINT32_MAX;
|
||||
Check(MaterializeResources(plan,
|
||||
{.user_data = user_data, .read_memory = read_sparse, .userdata = &sparse,
|
||||
.read_specialization_memory = read_sparse}, snapshot, specialization) &&
|
||||
{.user_data = user_data, .userdata = &sparse, .read_specialization_memory = read_sparse},
|
||||
snapshot, specialization) &&
|
||||
sparse.offsets == std::vector<uint64_t>{144u, 0x80000090u} &&
|
||||
snapshot.images.size() == 2u,
|
||||
"large V# bounds extended the native U32 domain or invented an OOB zero key");
|
||||
@@ -4644,7 +4649,8 @@ void TestCountedRowImageSelector() {
|
||||
at(MaterialBase + 256u) = 0x200u;
|
||||
at(MaterialBase + 260u) = static_cast<uint32_t>(Libs::Graphics::Prospero::BufferFormat::k8SScaled) << 20u;
|
||||
at(MaterialBase + 268u) = at(MaterialBase + 196u);
|
||||
std::array<uint32_t, 12> user_data{KeyBase, 4u << 16u, 64u, 0x14004u,
|
||||
// The walker needs every user-data DWORD the plan reads, including the zero s12-s14.
|
||||
std::array<uint32_t, 15> user_data{KeyBase, 4u << 16u, 64u, 0x14004u,
|
||||
CountBase, 4u << 16u, 4u, 0x14004u, MaterialBase, 396u << 16u, 4u, 0x14004u};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
@@ -5002,8 +5008,9 @@ void TestSharedAppendSelectorBounds() {
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
}
|
||||
std::array<uint32_t, 9> user_data {0x1000u, 160u << 16u, 8u, 0u, 0x2000u, 0u, 0x1800u, 0u, 0u};
|
||||
const SrtRuntime runtime {.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime {.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadRuntimeMemory, &runtime);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
memory.fail_address = memory.watched_address = 0x1090u;
|
||||
@@ -5085,9 +5092,8 @@ void TestSharedAppendSelectorBounds() {
|
||||
user_data[2] = 65536u;
|
||||
user_data[4] = 0x50000000u;
|
||||
user_data[6] = 0x40000000u;
|
||||
Check(MaterializeResources(plan,
|
||||
{.user_data = user_data, .read_memory = read_boundary, .userdata = &boundary,
|
||||
.read_specialization_memory = read_boundary}, snapshot, specialization) &&
|
||||
runtime = {.user_data = user_data, .userdata = &boundary, .read_specialization_memory = read_boundary};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
boundary.keys_read == 65536u && snapshot.images.size() == 1u &&
|
||||
snapshot.images[0].dwords[0] == 0x101u,
|
||||
"shared append materialization rejected its exact nonwrapping probe limit");
|
||||
@@ -5103,9 +5109,7 @@ void TestSharedAppendSelectorBounds() {
|
||||
for (const auto& test : overlaps) {
|
||||
boundary.counts = test.counts;
|
||||
boundary.keys_read = 0u;
|
||||
Check(MaterializeResources(overlap_plan,
|
||||
{.user_data = user_data, .read_memory = read_boundary, .userdata = &boundary,
|
||||
.read_specialization_memory = read_boundary}, snapshot, specialization) &&
|
||||
Check(MaterializeResources(overlap_plan, runtime, snapshot, specialization) &&
|
||||
boundary.keys_read == test.expected[layout] && snapshot.images.size() == 1u &&
|
||||
snapshot.images[0].dwords[0] == (test.expected[layout] != 0u ? 0x101u : 0u),
|
||||
"shared append bounds lost overlap, included a touching list or retained stale keys");
|
||||
@@ -5119,7 +5123,6 @@ void TestComputeBufferFill() {
|
||||
bool conditional = false;
|
||||
bool shifted = false;
|
||||
bool extra_store = false;
|
||||
bool clean = false;
|
||||
bool branch = false;
|
||||
};
|
||||
const auto Run = [](Options options) {
|
||||
@@ -5188,17 +5191,13 @@ void TestComputeBufferFill() {
|
||||
words[0] = memory.words[0];
|
||||
return true;
|
||||
};
|
||||
Check(MaterializeResources(
|
||||
plan,
|
||||
{.user_data = userdata,
|
||||
.read_memory = Read,
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory = options.clean ? Read : nullptr},
|
||||
snapshot, specialization),
|
||||
"fill fixture did not materialize");
|
||||
const auto materialize = [&](SrtMemoryReader reader) {
|
||||
FakeGuestMemory fake(reader, &memory);
|
||||
return MaterializeResources(plan, {.user_data = userdata}, snapshot, specialization);
|
||||
};
|
||||
Check(materialize(Read), "fill fixture did not materialize");
|
||||
const bool expected = !options.conditional && !options.shifted &&
|
||||
!options.extra_store && !options.branch &&
|
||||
(!options.scalar || options.clean);
|
||||
!options.extra_store && !options.branch;
|
||||
Check((snapshot.uniform_fill.words != 0) == expected,
|
||||
"fill proof accepted an unsafe store or missed the real GTA3 clear");
|
||||
if (expected) {
|
||||
@@ -5207,21 +5206,19 @@ void TestComputeBufferFill() {
|
||||
snapshot.uniform_fill.value ==
|
||||
(options.scalar ? 0x40404040u : 0u),
|
||||
"fill proof lost address coverage or the actual stored scalar");
|
||||
if (options.scalar && options.clean) {
|
||||
Check(MaterializeResources(plan,
|
||||
{.user_data = userdata, .read_memory = Read, .userdata = &memory},
|
||||
snapshot, specialization) && snapshot.uniform_fill.words == 0,
|
||||
"an unavailable clean value retained a previous uniform fill");
|
||||
if (options.scalar) {
|
||||
Check(materialize(nullptr) && snapshot.uniform_fill.words == 1 &&
|
||||
snapshot.uniform_fill.value == 0u,
|
||||
"an unreadable fill value retained a previous uniform fill");
|
||||
}
|
||||
}
|
||||
};
|
||||
Run({});
|
||||
Run({.scalar = true, .clean = true});
|
||||
Run({.scalar = true});
|
||||
Run({.conditional = true, .clean = true});
|
||||
Run({.shifted = true, .clean = true});
|
||||
Run({.extra_store = true, .clean = true});
|
||||
Run({.clean = true, .branch = true});
|
||||
Run({.conditional = true});
|
||||
Run({.shifted = true});
|
||||
Run({.extra_store = true});
|
||||
Run({.branch = true});
|
||||
}
|
||||
|
||||
void TestDenseBufferTracking() {
|
||||
@@ -5332,12 +5329,12 @@ void TestRuntimeUnsignedMinDescriptor() {
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue value;
|
||||
const auto source = fixture.program.info.buffers[0].source;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, value) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, source, value) &&
|
||||
value.dwords[3] == 0x100u,
|
||||
"runtime descriptor unsigned minimum did not clamp its first operand");
|
||||
user_data[0] = 0x80u;
|
||||
Check(
|
||||
SrtWalker(plan, runtime).EvaluateDescriptor(source, value) &&
|
||||
EvaluateSrtDescriptor(plan, runtime, source, value) &&
|
||||
value.dwords[3] == 0x80u,
|
||||
"runtime descriptor unsigned minimum did not preserve its first operand");
|
||||
}
|
||||
@@ -5488,12 +5485,12 @@ void TestSampleAdjustSamplerScratch() {
|
||||
std::array<uint32_t, 4> user_data{4u, 1u, 2u, 0x80000abcu};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, descriptor) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, 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(plan, runtime).EvaluateDescriptor(shift_source, descriptor) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, 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");
|
||||
@@ -5601,11 +5598,11 @@ void TestFmaskLoadSpecialization() {
|
||||
"FMASK load did not lower to a value vector");
|
||||
result = vector->Arg(0);
|
||||
uint32_t value = 0;
|
||||
Check(SrtWalker(fixture.program, {.user_data = user_data}).Evaluate(result, value) &&
|
||||
Check(EvaluateSrtValue(fixture.program, {.user_data = user_data}, result, value) &&
|
||||
value == 0x76543210u,
|
||||
"FMASK load did not return the native sample-to-fragment mapping");
|
||||
user_data[8] = 1;
|
||||
Check(SrtWalker(fixture.program, {.user_data = user_data}).Evaluate(result, value) &&
|
||||
Check(EvaluateSrtValue(fixture.program, {.user_data = user_data}, result, value) &&
|
||||
value == 0u,
|
||||
"inactive FMASK load did not preserve the execution mask");
|
||||
ShaderComputeInputInfo compute{};
|
||||
@@ -5849,16 +5846,14 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
std::array<uint32_t, 2> user_data{0x1000u, 0u};
|
||||
TestMemory memory;
|
||||
memory.words[1] = 0xdeadbeefu;
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue descriptor;
|
||||
std::vector<uint32_t> flat;
|
||||
const uint32_t request = fixture.program.info.buffers[0].source;
|
||||
const auto refresh = [&](const ResourcePlan& plan) {
|
||||
SrtWalker walker(plan, runtime);
|
||||
return walker.EvaluateDescriptor(request, descriptor) && walker.RefreshFlatBuffer(flat);
|
||||
return WalkSrt(plan, runtime, flat, nullptr) && WalkedDescriptor(plan, request, descriptor);
|
||||
};
|
||||
Check(refresh(plan), "typed runtime source evaluation failed");
|
||||
Check(descriptor.dwords[0] == 0xdeadbeefu &&
|
||||
@@ -5873,10 +5868,11 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
|
||||
memory.reads = 0;
|
||||
memory.fail_after = 0;
|
||||
Check(!refresh(plan), "unavailable scalar memory was accepted");
|
||||
Check(refresh(plan) && descriptor.dwords[0] == 0u && flat == std::vector<uint32_t>{0u},
|
||||
"unavailable scalar memory did not read as zero");
|
||||
memory.fail_after = UINT32_MAX;
|
||||
Check(refresh(plan) && descriptor.dwords[0] == 0x12345678u && memory.reads == 1,
|
||||
"failed runtime evaluation left a value marked as visiting");
|
||||
"a zero read left a stale value in the next walk");
|
||||
|
||||
auto moved = std::move(plan);
|
||||
memory.reads = 0;
|
||||
@@ -5924,18 +5920,17 @@ void TestScalarDataReadControlFlow() {
|
||||
{address, Value(0u), Value(0u), Value(true)}, fixture.AddMemory(scalar, 4), use);
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
Check(plan.srt_reads.size() == 1 && !fixture.program.info.uses_dma &&
|
||||
bool(plan.srt_code) == (comparison == ValueOpcode::Void),
|
||||
"scalar data lost flattening or conditional reads entered the unconditional JIT");
|
||||
Check(plan.srt_reads.size() == 1 && !fixture.program.info.uses_dma && plan.srt_code,
|
||||
"scalar data lost flattening or its walker");
|
||||
uint32_t reads = 0;
|
||||
std::array<uint32_t, 2> user_data {};
|
||||
SrtRuntime runtime {.user_data = user_data,
|
||||
.read_memory = +[](void* data, uint64_t address, std::span<uint32_t> words) {
|
||||
SrtRuntime runtime {.user_data = user_data};
|
||||
FakeGuestMemory fake(+[](void* data, uint64_t address, std::span<uint32_t> words) {
|
||||
++*static_cast<uint32_t*>(data);
|
||||
if (address == 0) return false;
|
||||
words[0] = 42;
|
||||
return true;
|
||||
}, .userdata = &reads};
|
||||
}, &reads);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const uint64_t pointer : {uint64_t{0}, uint64_t{0x1000}, uint64_t{1} << 32u}) {
|
||||
@@ -5977,11 +5972,10 @@ void TestDynamicSrtReadRemainsExplicit() {
|
||||
std::array<uint32_t, 3> user_data{0x1000u, 0u, 4u};
|
||||
TestMemory memory;
|
||||
memory.words[1] = 0xabcdef01u;
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
DescriptorValue value;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, value) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, fixture.program.info.buffers[0].source, value) &&
|
||||
value.dwords[0] == 0xabcdef01u && memory.reads == 1,
|
||||
"dynamic typed scalar descriptor source was not evaluated");
|
||||
|
||||
@@ -6091,8 +6085,9 @@ void TestBoundedFormattedBufferStore() {
|
||||
memory.words[word + 3u] = static_cast<uint32_t>(Prospero::BufferFormat::k32UInt) << 12u | 4u;
|
||||
}
|
||||
std::array<uint32_t, 2> user_data{static_cast<uint32_t>(memory.base), 0u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
@@ -6316,8 +6311,9 @@ void TestBoundedBufferRecords(uint32_t d32_components) {
|
||||
}
|
||||
std::array<uint32_t, 6> user_data{uint32_t(memory.base) + table_offset, 8u << 16u,
|
||||
4u * 192u / 8u, 0x5204u, uint32_t(memory.base), 0u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
@@ -6498,8 +6494,8 @@ void TestGuardedWaterfallBufferRecords() {
|
||||
std::copy(poison.begin(), poison.end(), memory.words.begin() + (table_offset + 480u) / 4u);
|
||||
std::array<uint32_t, 8> user_data{uint32_t(memory.base) + table_offset, 488u << 16u,
|
||||
2u, 0x5204u, 0x9000u, 4u << 16u, 32u, 0x5204u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const auto guard : {Guard::Exact, Guard::Reversed, Guard::Greater, Guard::GreaterEqual,
|
||||
@@ -6617,8 +6613,8 @@ void TestGpuSelectedBufferRecords() {
|
||||
// SMEM aligns the base and dynamic offset independently; its V# format is ignored.
|
||||
std::array<uint32_t, 8> user_data{uint32_t(memory.base) + table_offset + 3u, 0u, 0u, 0u,
|
||||
uint32_t(memory.fail_address), 12u << 16u, 129600u, 0x5204u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
@@ -6709,8 +6705,8 @@ void TestWorkgroupSelectedBufferRecords() {
|
||||
std::array<uint32_t, 8> user_data{uint32_t(memory.base) + table, 192u << 16u, 2u, 0x5204u,
|
||||
uint32_t(memory.base) + selector, 12u << 16u, MaxIndirectDescriptorProbes, 0x5204u};
|
||||
memory.watched_address = memory.base;
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory,
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory,
|
||||
.indirect_args_address = memory.base};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
@@ -6938,8 +6934,9 @@ void TestGpuSelectedFormattedRecordStore() {
|
||||
memory.fail_address = memory.base + 0x2000u;
|
||||
std::array<uint32_t, 8> user_data{uint32_t(memory.base) + table_offset, 592u << 16u, 6u, 0x5204u,
|
||||
uint32_t(memory.fail_address), 4u << 16u, 64u, 0x5204u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadRuntimeMemory, &runtime);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
@@ -7127,9 +7124,8 @@ void TestGpuSelectedFormattedRecordStore() {
|
||||
std::array<uint32_t, 8> prefix_data{uint32_t(prefix_memory.base) + table_offset - 48u, 0u, 0u, 0u,
|
||||
uint32_t(prefix_memory.base) + selector_offset, 8u << 16u, 12u, 0x5204u};
|
||||
std::array counts{2u, 1u, 1u};
|
||||
SrtRuntime prefix_runtime{.user_data = prefix_data, .read_memory = read_prefix,
|
||||
.userdata = &prefix_memory, .read_specialization_memory = read_prefix,
|
||||
.workgroup_counts = counts};
|
||||
runtime = {.user_data = prefix_data, .userdata = &prefix_memory,
|
||||
.read_specialization_memory = read_prefix, .workgroup_counts = counts};
|
||||
prefix_memory.watched_address = prefix_memory.base + selector_offset + 4u;
|
||||
for (const auto prefix : {Prefix::Forward, Prefix::Reversed}) {
|
||||
const auto selected = make(16u, 16u, false, true, prefix);
|
||||
@@ -7143,7 +7139,7 @@ void TestGpuSelectedFormattedRecordStore() {
|
||||
const auto refresh = [&] {
|
||||
prefix_memory.watched_reads = 0u;
|
||||
prefix_memory.read_attempts = 0u;
|
||||
return MaterializeResources(selected_plan, prefix_runtime, snapshot, specialization);
|
||||
return MaterializeResources(selected_plan, runtime, snapshot, specialization);
|
||||
};
|
||||
for (const uint32_t first : {0u, 2u, 4u, 0u}) {
|
||||
prefix_memory.words[first_offset / 4u] = first;
|
||||
@@ -7155,9 +7151,12 @@ void TestGpuSelectedFormattedRecordStore() {
|
||||
Check(std::ranges::find(snapshot.specialization_reads, read) != snapshot.specialization_reads.end(),
|
||||
"runtime first or selected prefix escaped specialization read capture");
|
||||
}
|
||||
// An unreadable runtime first reads as zero.
|
||||
prefix_memory.words[first_offset / 4u] = 4u;
|
||||
prefix_memory.fail_address = prefix_memory.base + first_offset;
|
||||
Check(!refresh() && prefix_memory.read_attempts == 1u,
|
||||
"unreadable runtime first reached the selector payload");
|
||||
Check(refresh() && snapshot.buffers.size() == selected_plan.info.buffers.size() + 1u &&
|
||||
snapshot.buffers[wide_root].dwords[0] == 0x100000u,
|
||||
"unreadable runtime first did not read as zero");
|
||||
prefix_memory.fail_address = UINT64_MAX;
|
||||
prefix_memory.words[first_offset / 4u] = 6u;
|
||||
Check(!refresh(), "an actually selected invalid numeric descriptor was silently omitted");
|
||||
@@ -7257,8 +7256,8 @@ void TestFiniteBufferSelection() {
|
||||
}
|
||||
memory.fail_address = memory.base + 32u + 5u * 16u;
|
||||
const std::array<uint32_t, 2> user_data{static_cast<uint32_t>(memory.base), 0u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.buffers.size() == 5u,
|
||||
@@ -7339,8 +7338,8 @@ void TestPackedScalarBufferSelection() {
|
||||
// No descriptor exists after key 18; broad enumeration would read this address.
|
||||
memory.fail_address = memory.base + 0x170u;
|
||||
const std::array<uint32_t, 2> user_data{static_cast<uint32_t>(memory.base), 0u};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto materialize = [&] { return MaterializeResources(plan, runtime, snapshot, specialization); };
|
||||
@@ -7497,38 +7496,27 @@ void TestConditionalSamplerPhi() {
|
||||
for (uint32_t word = 448 / 4; word < (480 + 16) / 4; ++word) {
|
||||
memory.words[word] = 0x400u + word;
|
||||
}
|
||||
const SrtRuntime runtime{.read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory =
|
||||
ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
// Sampler selection compares a signed value against zero.
|
||||
for (const auto flag : {-1, 0, 1, INT32_MIN, INT32_MAX}) {
|
||||
memory.words[(0x1000 + 196) / 4] = std::bit_cast<uint32_t>(flag);
|
||||
const uint32_t first = (flag < 0) != reverse ? 480 / 4 : 448 / 4;
|
||||
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(EvaluateSrtDescriptor(plan, {}, 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],
|
||||
"conditional sampler chose the wrong incoming descriptor");
|
||||
}
|
||||
}
|
||||
DescriptorValue selected;
|
||||
auto no_clean_reader = runtime;
|
||||
no_clean_reader.read_specialization_memory = nullptr;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(no_clean_reader));
|
||||
Check(!SrtWalker(plan, no_clean_reader, &clean).EvaluateDescriptor(source, selected),
|
||||
"conditional sampler used unchecked memory for its predicate");
|
||||
}
|
||||
// An unreadable negative predicate reads as zero and selects the nonnegative edge.
|
||||
memory.words[(0x1000 + 196) / 4] = UINT32_MAX;
|
||||
memory.fail_address = 0x2000 + 196;
|
||||
{
|
||||
SrtWalker clean(plan, CleanRuntime(runtime));
|
||||
Check(!SrtWalker(plan, runtime, &clean).EvaluateDescriptor(source, selected),
|
||||
"conditional sampler ignored unavailable coherent predicate memory");
|
||||
}
|
||||
DescriptorValue selected;
|
||||
Check(EvaluateSrtDescriptor(plan, {}, source, selected) &&
|
||||
selected.dwords[0] == memory.words[reverse ? 480 / 4 : 448 / 4],
|
||||
"conditional sampler did not read unavailable predicate memory as zero");
|
||||
}
|
||||
}
|
||||
CheckFatal([&] { ConditionalSamplerPlan(diamond, false, false, true); },
|
||||
@@ -7592,10 +7580,9 @@ 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(plan, SrtRuntime{.user_data = data});
|
||||
for (uint32_t candidate = 0; candidate < 2; ++candidate) {
|
||||
DescriptorValue descriptor;
|
||||
Check(walker.EvaluateDescriptor(finite->sources[candidate], descriptor),
|
||||
Check(EvaluateSrtDescriptor(plan, {.user_data = data}, finite->sources[candidate], descriptor),
|
||||
"finite image candidate is not host-readable");
|
||||
for (uint32_t word = 0; word < 8; ++word)
|
||||
Check(descriptor.dwords[word] == data[candidate * 8 + word],
|
||||
@@ -7666,8 +7653,8 @@ void TestPackedDescriptorWaterfall() {
|
||||
memory.words[word + 1u] = 0x00fff000u;
|
||||
memory.words[word + 2u] = (row & 1u) ? 0x0a500000u : 0u;
|
||||
}
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
@@ -7795,8 +7782,9 @@ void TestFiniteImageWaterfall() {
|
||||
(uint32_t(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
}
|
||||
std::array<uint32_t, 4> user_data{uint32_t(memory.base) + 256u, 8u << 16u, 60u, 0x5204u};
|
||||
SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadLinearTestMemory};
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
FakeGuestMemory fake(ReadLinearTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization, populated;
|
||||
for (const uint32_t count : {0u, 60u, 0u, 60u}) {
|
||||
@@ -8048,7 +8036,7 @@ void TestLoopCycleEnteredThroughRuntimeValue() {
|
||||
const std::array<uint32_t, 1> user_data{0x4000u};
|
||||
DescriptorValue descriptor;
|
||||
Check(fixture.program.info.buffers.size() == 1u &&
|
||||
SrtWalker(plan, {.user_data = user_data}).EvaluateDescriptor(
|
||||
EvaluateSrtDescriptor(plan, {.user_data = user_data},
|
||||
fixture.program.info.buffers[0].source, descriptor) &&
|
||||
descriptor.dwords[0] == user_data[0],
|
||||
"runtime-rooted invariant loop lost its buffer source");
|
||||
@@ -8079,7 +8067,7 @@ void TestInvariantLoopPhi() {
|
||||
std::array<uint32_t, 1> user_data{0x12345678u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, descriptor) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, fixture.program.info.buffers[0].source, descriptor) &&
|
||||
descriptor.dwords[0] == user_data[0],
|
||||
"loop-invariant descriptor phi was not evaluated through typed SSA");
|
||||
}
|
||||
@@ -8196,8 +8184,7 @@ void TestBoundedRelativeRegisterWrites() {
|
||||
const std::array<uint32_t, 3> user_data{0x1000u, 0u, 72u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(
|
||||
fixture.program.info.buffers[0].source, descriptor) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, fixture.program.info.buffers[0].source, descriptor) &&
|
||||
descriptor.dwords[2] == 72u,
|
||||
"impossible relative writes left a false descriptor dependency");
|
||||
}
|
||||
@@ -8412,6 +8399,7 @@ void TestConditionalBufferMaterialization() {
|
||||
TestMemory memory;
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
@@ -8422,12 +8410,11 @@ void TestConditionalBufferMaterialization() {
|
||||
Check(snapshot.user_data == std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
||||
"resource reachability changed native shader user data");
|
||||
|
||||
// The walker needs every user-data DWORD it may read, even for an untaken branch.
|
||||
runtime.user_data = std::span(user_data).first(4);
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"untaken branch evaluated its unavailable descriptor");
|
||||
memory.words[0] = 1;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"taken branch accepted an unavailable descriptor");
|
||||
"truncated user data was accepted");
|
||||
memory.words[0] = 1;
|
||||
|
||||
runtime.user_data = user_data;
|
||||
const auto CheckActive = [&] {
|
||||
@@ -8438,9 +8425,13 @@ void TestConditionalBufferMaterialization() {
|
||||
"potentially executed buffer descriptor was discarded");
|
||||
};
|
||||
CheckActive();
|
||||
memory.words[0] = 0;
|
||||
// An unreadable control word reads as zero and skips the branch.
|
||||
memory.fail_after = memory.reads;
|
||||
CheckActive();
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 && snapshot.buffers[1].dwords == std::array<uint32_t, 8>{},
|
||||
"unreadable scalar control word did not read as zero");
|
||||
// Walker loads do not depend on the specialization reader.
|
||||
memory.fail_after = UINT32_MAX;
|
||||
runtime.read_specialization_memory = nullptr;
|
||||
CheckActive();
|
||||
}
|
||||
@@ -8515,8 +8506,9 @@ void TestGuardedScalarDescriptorReads() {
|
||||
return ReadLinearTestMemory(&memory.data, address, words);
|
||||
};
|
||||
const std::array<uint32_t, 5> user_data{0x1000u, 0u, 64u, 0u, 0x1040u};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = Read,
|
||||
.userdata = &memory, .read_specialization_memory = Read};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = Read};
|
||||
FakeGuestMemory fake(Read, &memory);
|
||||
memory.data.words[0xa8 / 4] = 0x2000u;
|
||||
memory.data.words[0xb0 / 4] = 4u;
|
||||
ResourceSnapshot snapshot;
|
||||
@@ -8526,8 +8518,9 @@ void TestGuardedScalarDescriptorReads() {
|
||||
std::ranges::all_of(snapshot.flattened_srt, [](auto word) { return word == 0; }),
|
||||
"disabled feature speculatively dereferenced its null BVH table");
|
||||
memory.data.words[7] = 1;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.null_reads == 1,
|
||||
"active feature accepted an unreadable BVH table");
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && memory.null_reads == 1 &&
|
||||
std::ranges::all_of(snapshot.flattened_srt, [](auto word) { return word == 0; }),
|
||||
"active feature did not read its unreadable null BVH table as zero");
|
||||
}
|
||||
memory.data.words[0x40 / 4] = 0x1080u;
|
||||
memory.data.watched_address = 0x10a8u;
|
||||
@@ -8557,19 +8550,24 @@ void TestConservativeBufferReachability() {
|
||||
TestMemory memory;
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
for (const auto use : {ConditionalBufferUse::Shared, ConditionalBufferUse::Loop,
|
||||
ConditionalBufferUse::Writable}) {
|
||||
auto plan = ConditionalBufferPlan(use);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
std::array<uint32_t, 4> expected{};
|
||||
if (use != ConditionalBufferUse::Writable)
|
||||
std::copy_n(user_data.begin() + 4, expected.size(), expected.begin());
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
std::equal(expected.begin(), expected.end(),
|
||||
snapshot.buffers[1].dwords.begin()),
|
||||
"shared or loop-dependent resource was pruned, or inactive scalar branch retained");
|
||||
// With a zero control word, the loop body's only other predecessor is itself.
|
||||
for (const uint32_t flag : {0u, 1u}) {
|
||||
memory.words[0] = flag;
|
||||
std::array<uint32_t, 4> expected{};
|
||||
if (use == ConditionalBufferUse::Shared || flag != 0u)
|
||||
std::copy_n(user_data.begin() + 4, expected.size(), expected.begin());
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
std::equal(expected.begin(), expected.end(),
|
||||
snapshot.buffers[1].dwords.begin()),
|
||||
"shared or entered loop resource was pruned, or an untaken branch or loop retained");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#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 "SrtWalkerTestMemory.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
@@ -95,6 +95,16 @@ bool ReadMemory(void *userdata, uint64_t address, std::span<uint32_t> values) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Evaluates the DWORDs of a descriptor source that no resource consumes.
|
||||
bool EvaluateSource(ResourcePlan &plan, const SrtRuntime &runtime, uint32_t source,
|
||||
DescriptorValue &result) {
|
||||
const auto &words = plan.descriptor_sources[source];
|
||||
result = {.dword_count = words.dword_count};
|
||||
for (uint32_t word = 0; word < words.dword_count; ++word)
|
||||
if (!EvaluateSrtValue(plan, runtime, words.dwords[word], result.dwords[word])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Value Address(Fixture &fixture, Value low, Value high, uint32_t block = 0) {
|
||||
return fixture.Emit(ValueOpcode::GetAddressResource, {low, high}, 0, block);
|
||||
}
|
||||
@@ -132,9 +142,9 @@ void TestImmediateFlatteningAndGvn() {
|
||||
"flattened raw read was not kept as a planning-only root");
|
||||
|
||||
TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
FakeGuestMemory fake(ReadMemory, &memory_image);
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed");
|
||||
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "flattened SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>{0xfeedbeefu} && memory_image.reads == 1,
|
||||
"flattened SRT did not evaluate its canonical read once");
|
||||
}
|
||||
@@ -148,9 +158,9 @@ void TestRawScalarComponentAlignment() {
|
||||
fixture.Plan();
|
||||
|
||||
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
FakeGuestMemory fake(ReadMemory, &memory_image);
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed");
|
||||
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "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");
|
||||
@@ -203,9 +213,9 @@ void TestNestedSrtWalk() {
|
||||
fixture.Plan();
|
||||
|
||||
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
FakeGuestMemory fake(ReadMemory, &memory_image);
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "nested SRT walk failed");
|
||||
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "nested SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>({0x2000u, 0xabcdef01u}),
|
||||
"nested typed SRT reads were not evaluated in dependency order");
|
||||
}
|
||||
@@ -227,8 +237,9 @@ void TestShaderBaseAndUserData() {
|
||||
const std::array user_data{0x20u};
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.shader_base = 0x12345678abcdef00ull};
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).EvaluateDescriptor(0, result),
|
||||
Check(EvaluateSource(plan, runtime, 0, result),
|
||||
"shader-relative descriptor evaluation failed");
|
||||
Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u &&
|
||||
result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u,
|
||||
@@ -249,8 +260,9 @@ void TestCarryAndBitFields() {
|
||||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
|
||||
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
|
||||
Check(EvaluateSource(plan, {}, 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,12 +285,12 @@ void TestInvariantAndDivergentPhi() {
|
||||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {Value(&divergent)}, .dword_count = 1});
|
||||
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(plan, {}).EvaluateDescriptor(0, result) &&
|
||||
Check(EvaluateSource(plan, {}, 0, result) &&
|
||||
result.dwords[0] == 7u,
|
||||
"loop-invariant typed phi was rejected");
|
||||
Check(!SrtWalker(plan, {}).EvaluateDescriptor(1, result),
|
||||
Check(!EvaluateSource(plan, {}, 1, result),
|
||||
"divergent phi was accepted");
|
||||
}
|
||||
|
||||
@@ -294,7 +306,7 @@ void TestOperandMutationLifecycle() {
|
||||
source->ReplaceUsesWith(Value(0xaau));
|
||||
uint32_t result = 0;
|
||||
Check(source->UseCount() == 0 &&
|
||||
SrtWalker(fixture.program, {}).Evaluate(inserted, result) && result == 0x3au,
|
||||
EvaluateSrtValue(fixture.program, {}, inserted, result) && result == 0x3au,
|
||||
"inline operand replacement changed the evaluated value");
|
||||
|
||||
for (const auto opcode : {ValueOpcode::GetImageResource, ValueOpcode::MakeImageAddress}) {
|
||||
@@ -333,7 +345,7 @@ void TestOperandMutationLifecycle() {
|
||||
const auto sum = fixture.Emit(ValueOpcode::IAdd32, {Value(&phi), Value(5u)}, 0, 5);
|
||||
phi.ReplaceUsesWith(Value(6u));
|
||||
Check(source->UseCount() == 0 && phi.NumPhiBlocks() == 0 && phi.NumArgs() == 1 &&
|
||||
SrtWalker(fixture.program, {}).Evaluate(sum, result) && result == 11,
|
||||
EvaluateSrtValue(fixture.program, {}, sum, result) && result == 11,
|
||||
"Phi replacement retained incoming uses or changed the consumer value");
|
||||
}
|
||||
|
||||
@@ -374,8 +386,9 @@ void TestRuntime64BitDescriptorOps() {
|
||||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {low, high}, .dword_count = 2});
|
||||
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result) &&
|
||||
Check(EvaluateSource(plan, {}, 0, result) &&
|
||||
result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u,
|
||||
"64-bit typed descriptor arithmetic evaluation is incorrect");
|
||||
}
|
||||
@@ -448,13 +461,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);
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
|
||||
Check(EvaluateSource(plan, runtime, 0, result) &&
|
||||
result.dwords[0] == 0x00300300u,
|
||||
"uniform sampler LOD evaluated incorrectly");
|
||||
user_data[6] = 20u;
|
||||
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
|
||||
Check(EvaluateSource(plan, runtime, 0, result) &&
|
||||
result.dwords[0] == 0x00ffffffu,
|
||||
"uniform sampler LOD clamp evaluated incorrectly");
|
||||
}
|
||||
@@ -489,13 +502,12 @@ void TestFloatComparisonDescriptorInputs() {
|
||||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {low, high}, .dword_count = 2});
|
||||
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
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(plan, {.user_data = user_data})
|
||||
.EvaluateDescriptor(0, result) &&
|
||||
Check(EvaluateSource(plan, {.user_data = user_data}, 0, result) &&
|
||||
result.dwords[0] == (flush && index < 4 ? 1 : input.less_equal) &&
|
||||
result.dwords[1] == (flush && index < 4 ? 1 : input.greater_equal),
|
||||
"descriptor comparison disagrees with native FP32 input mode");
|
||||
@@ -551,7 +563,6 @@ void TestConstantBufferBounds() {
|
||||
uint32_t immediate;
|
||||
uint32_t stride;
|
||||
uint32_t records;
|
||||
bool valid;
|
||||
uint32_t expected;
|
||||
uint32_t reads;
|
||||
};
|
||||
@@ -565,17 +576,17 @@ void TestConstantBufferBounds() {
|
||||
++probe.attempts;
|
||||
return ReadMemory(&probe.memory, address, values);
|
||||
};
|
||||
// PS5 scalar-buffer loads return zero per out-of-range DWORD; an unreadable
|
||||
// in-range address remains a failed memory read.
|
||||
for (const auto &test : {Case{12u, 0u, 0u, 16u, true, 0xa5a5a5a5u, 1u},
|
||||
Case{3u, 1u, 0u, 16u, true, 0x12345678u, 1u},
|
||||
Case{0xfffffffcu, 4u, 0u, 16u, true, 0u, 0u},
|
||||
Case{16u, 0u, 0u, 16u, true, 0u, 0u},
|
||||
Case{12u, 0u, 0u, 14u, true, 0u, 0u},
|
||||
Case{0u, 0u, 0u, 0u, true, 0u, 0u},
|
||||
Case{8u, 0u, 0u, 16u, false, 0u, 1u},
|
||||
Case{12u, 0u, 48u, 32768u, true, 0xa5a5a5a5u, 1u},
|
||||
Case{0x6339c0u, 16u, 48u, 32768u, true, 0u, 0u}}) {
|
||||
// PS5 scalar-buffer loads return zero per out-of-range DWORD without an access;
|
||||
// an unreadable in-range address is accessed and reads as zero too.
|
||||
for (const auto &test : {Case{12u, 0u, 0u, 16u, 0xa5a5a5a5u, 1u},
|
||||
Case{3u, 1u, 0u, 16u, 0x12345678u, 1u},
|
||||
Case{0xfffffffcu, 4u, 0u, 16u, 0u, 0u},
|
||||
Case{16u, 0u, 0u, 16u, 0u, 0u},
|
||||
Case{12u, 0u, 0u, 14u, 0u, 0u},
|
||||
Case{0u, 0u, 0u, 0u, 0u, 0u},
|
||||
Case{8u, 0u, 0u, 16u, 0u, 1u},
|
||||
Case{12u, 0u, 48u, 32768u, 0xa5a5a5a5u, 1u},
|
||||
Case{0x6339c0u, 16u, 48u, 32768u, 0u, 0u}}) {
|
||||
Fixture fixture;
|
||||
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer, test.immediate);
|
||||
const auto buffer =
|
||||
@@ -588,12 +599,11 @@ void TestConstantBufferBounds() {
|
||||
fixture.Plan();
|
||||
|
||||
MemoryProbe memory_image;
|
||||
SrtRuntime runtime{.read_memory = read_memory, .userdata = &memory_image};
|
||||
FakeGuestMemory fake(read_memory, &memory_image);
|
||||
std::vector<uint32_t> flat;
|
||||
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat) == test.valid,
|
||||
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr),
|
||||
"constant-buffer walk misaligned or wrapped its offset components");
|
||||
Check((!test.valid || flat == std::vector<uint32_t>{test.expected}) &&
|
||||
memory_image.attempts == test.reads,
|
||||
Check(flat == std::vector<uint32_t>{test.expected} && memory_image.attempts == test.reads,
|
||||
"constant-buffer walk read the wrong word or accessed an out-of-bounds address");
|
||||
}
|
||||
}
|
||||
@@ -831,8 +841,9 @@ void TestUndefinedRuntimeValueFails() {
|
||||
|
||||
fixture.program.descriptor_sources.push_back(
|
||||
{.dwords = {undef}, .dword_count = 1});
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
DescriptorValue result;
|
||||
Check(!SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
|
||||
Check(!EvaluateSource(plan, {}, 0, result),
|
||||
"undefined typed descriptor source was accepted");
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "libs/errno.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
#include "SrtWalkerTestMemory.h"
|
||||
|
||||
#if __has_include("graphics/host_gpu/renderer/renderTargetBarriers.h")
|
||||
#error "legacy render-target barrier API must remain deleted"
|
||||
@@ -1724,15 +1725,17 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(test.code.data()),
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = const_cast<std::vector<u32> *>(&test.initial),
|
||||
.read_specialization_memory = ReadTestMemory,
|
||||
.workgroup_counts = workgroup_counts,
|
||||
};
|
||||
Require(test.name, "resource materialization",
|
||||
ShaderRecompiler::IR::MaterializeResources(
|
||||
resource_plan, runtime, resources, specialization),
|
||||
"translated resources could not be materialized");
|
||||
{
|
||||
FakeGuestMemory fake(ReadTestMemory, runtime.userdata);
|
||||
Require(test.name, "resource materialization",
|
||||
ShaderRecompiler::IR::MaterializeResources(
|
||||
resource_plan, runtime, resources, specialization),
|
||||
"translated resources could not be materialized");
|
||||
}
|
||||
auto result = ShaderRecompiler::CompileProgram(
|
||||
std::move(translated), options, specialization);
|
||||
for (const auto &[text, expected] : test.decoded_counts) {
|
||||
@@ -11235,7 +11238,7 @@ public:
|
||||
};
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = user_data, .shader_base = reinterpret_cast<uint64_t>(caller.data()),
|
||||
.read_memory = read, .read_specialization_memory = read};
|
||||
.read_specialization_memory = read};
|
||||
auto source = ShaderRecompiler::PrepareShaderSource(caller, options);
|
||||
auto translated = ShaderRecompiler::TranslateProgram(
|
||||
ShaderRecompiler::RefreshShaderSource(source, runtime), options);
|
||||
@@ -17158,12 +17161,7 @@ public:
|
||||
stencil_compute.stage.resources = &stencil_snapshot;
|
||||
Require(name, "stencil fill materialization",
|
||||
ShaderRecompiler::IR::MaterializeResources(stencil_plan,
|
||||
{.user_data = stencil_userdata, .userdata = &stencil_byte,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
if (address != reinterpret_cast<uint64_t>(data) || words.size() != 1u) return false;
|
||||
words[0] = *static_cast<uint32_t *>(data);
|
||||
return true;
|
||||
}}, stencil_snapshot, stencil_specialization),
|
||||
{.user_data = stencil_userdata}, stencil_snapshot, stencil_specialization),
|
||||
"captured stencil shader could not resolve its clear byte");
|
||||
ShaderRecompiler::IR::ApplyResourceSpecialization(stencil_translated.program,
|
||||
stencil_specialization);
|
||||
@@ -39523,25 +39521,25 @@ void CheckRuntimeBufferRecords(VulkanHarness &vulkan) {
|
||||
options.input_info.compute = &test.compute_info;
|
||||
auto translated = ShaderRecompiler::TranslateProgram(test.code, options);
|
||||
auto plan = ExtractResourcePlan(translated.program);
|
||||
struct CountMemory { u32 value = 72; u32 reads = 0; bool clean = true; } memory;
|
||||
SrtRuntime runtime{.user_data = test.user_data, .userdata = &memory};
|
||||
struct CountMemory { u32 value = 72; u32 reads = 0; bool readable = true; } memory;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Require(test.name, "strict reader required",
|
||||
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"vector descriptor input bypassed the clean-memory reader");
|
||||
runtime.read_specialization_memory = [](void *data, uint64_t address, std::span<u32> words) {
|
||||
auto &memory = *static_cast<CountMemory *>(data);
|
||||
++memory.reads;
|
||||
if (!memory.clean || address != count_address || words.size() != 1) return false;
|
||||
words[0] = memory.value;
|
||||
return true;
|
||||
const auto materialize = [&] {
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<u32> words) {
|
||||
auto &memory = *static_cast<CountMemory *>(data);
|
||||
++memory.reads;
|
||||
if (!memory.readable || address != count_address) return false;
|
||||
words[0] = memory.value;
|
||||
return true;
|
||||
}, &memory);
|
||||
return MaterializeResources(plan, {.user_data = test.user_data}, snapshot, specialization);
|
||||
};
|
||||
memory.clean = false;
|
||||
Require(test.name, "dirty input rejected",
|
||||
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"GPU-dirty count was accepted for host descriptor evaluation");
|
||||
memory.clean = true;
|
||||
// An unreadable count reads as zero, which leaves no records.
|
||||
memory.readable = false;
|
||||
Require(test.name, "unreadable input",
|
||||
materialize() && memory.reads == 1 && snapshot.buffers[2].dwords[2] == 0,
|
||||
"unreadable count did not read as zero");
|
||||
memory.readable = true;
|
||||
ResourceSpecialization initial_specialization;
|
||||
// Reuse the same resource plan and compiled shader as the count and coverage change.
|
||||
for (const u32 count : {0u, 72u, 512u, 8192u, 262144u}) {
|
||||
@@ -39550,7 +39548,7 @@ void CheckRuntimeBufferRecords(VulkanHarness &vulkan) {
|
||||
const u32 chunk = count < 512 ? 64 : count < 8192 ? 128 : count < 262144 ? 256 : 512;
|
||||
const u32 records = (count + chunk - 1) / chunk;
|
||||
Require(test.name, "cached descriptor refresh",
|
||||
MaterializeResources(plan, runtime, snapshot, specialization) && memory.reads == 1 &&
|
||||
materialize() && memory.reads == 1 &&
|
||||
snapshot.buffers[2].dwords[2] == records &&
|
||||
snapshot.specialization_reads ==
|
||||
std::vector<std::pair<uint64_t, uint64_t>>{{count_address, 4}},
|
||||
@@ -44598,7 +44596,6 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
compute.thread_ids_num = 1;
|
||||
compute.workgroup_register = 8;
|
||||
u32 scalar_clear = 0x40404040u;
|
||||
bool clean_scalar = false;
|
||||
|
||||
const auto Compile = [&](const std::vector<u32> &code) {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
@@ -44616,13 +44613,6 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
||||
.userdata = &scalar_clear,
|
||||
.read_specialization_memory = clean_scalar
|
||||
? +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
if (address != reinterpret_cast<uint64_t>(data) || words.size() != 1u) return false;
|
||||
words[0] = *static_cast<uint32_t *>(data);
|
||||
return true;
|
||||
} : nullptr,
|
||||
};
|
||||
Require("Ps5GameExampleImageClear", "resource materialization",
|
||||
ShaderRecompiler::IR::MaterializeResources(
|
||||
@@ -44780,7 +44770,7 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
scalar_code.push_back(EncodeMubuf0(0x04u, 0, true, false));
|
||||
scalar_code.push_back(EncodeMubuf1(1, 0, 0));
|
||||
AppendEnd(&scalar_code);
|
||||
clean_scalar = true;
|
||||
// The scalar input is a walker load, so it needs no specialization memory reader.
|
||||
auto loaded = Compile(scalar_code);
|
||||
runtime_resources = loaded;
|
||||
Require("Ps5GameExampleImageClear", "scalar-loaded fill",
|
||||
@@ -44788,12 +44778,6 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
packed_clear, size) &&
|
||||
packed_clear == scalar_clear && descriptor.Base48() == 0x200000u,
|
||||
"GTA3's scalar input was confused with its destination");
|
||||
clean_scalar = false;
|
||||
runtime_resources = Compile(scalar_code);
|
||||
Require("Ps5GameExampleImageClear", "unavailable clean scalar",
|
||||
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
||||
packed_clear, size),
|
||||
"a scalar without a clean reader was replaced by a fill");
|
||||
std::printf("[host] %-32s ok\n", "Ps5GameExampleImageClear");
|
||||
}
|
||||
|
||||
@@ -47260,8 +47244,9 @@ void CheckImageSamplerSpecialization() {
|
||||
auto plan = ExtractResourcePlan(translated.program);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = ReadTestMemory,
|
||||
.userdata = &memory, .read_specialization_memory = ReadTestMemory};
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
Require(name, "bounded native material plan",
|
||||
plan.capture_specialization_reads && !translated.program.has_address_writes &&
|
||||
MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
@@ -47351,10 +47336,8 @@ void CheckResourcePlanHandoff() {
|
||||
auto plan = ExtractResourcePlan(translated.program);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
FakeGuestMemory fake(ReadTestMemory, &memory);
|
||||
Require(name, "initial descriptors",
|
||||
MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 1 && snapshot.buffers[0].dwords[0] == 0x1000u,
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#ifndef KYTY_TESTS_SRTWALKERTESTMEMORY_H_
|
||||
#define KYTY_TESTS_SRTWALKERTESTMEMORY_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include <xbyak/xbyak.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <ucontext.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// Serves SRT walker loads from fake guest addresses: a walker load that faults completes with
|
||||
// the test reader's DWORD, or with zero like the emulator when the reader fails or is null.
|
||||
class FakeGuestMemory {
|
||||
public:
|
||||
explicit FakeGuestMemory(Libs::Graphics::ShaderRecompiler::IR::SrtMemoryReader reader = nullptr,
|
||||
void *userdata = nullptr) {
|
||||
Current() = {reader, userdata};
|
||||
#if defined(__linux__)
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = Handle;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGSEGV, &action, &m_previous);
|
||||
#elif defined(_WIN32)
|
||||
m_handler = AddVectoredExceptionHandler(1, Handle);
|
||||
#endif
|
||||
}
|
||||
~FakeGuestMemory() {
|
||||
#if defined(__linux__)
|
||||
sigaction(SIGSEGV, &m_previous, nullptr);
|
||||
#elif defined(_WIN32)
|
||||
RemoveVectoredExceptionHandler(m_handler);
|
||||
#endif
|
||||
Current() = {};
|
||||
}
|
||||
FakeGuestMemory(const FakeGuestMemory &) = delete;
|
||||
FakeGuestMemory &operator=(const FakeGuestMemory &) = delete;
|
||||
|
||||
private:
|
||||
using Reader = std::pair<Libs::Graphics::ShaderRecompiler::IR::SrtMemoryReader, void *>;
|
||||
static Reader &Current() {
|
||||
static thread_local Reader reader;
|
||||
return reader;
|
||||
}
|
||||
static uint32_t Read(uint64_t address) {
|
||||
uint32_t word = 0;
|
||||
const auto [reader, userdata] = Current();
|
||||
return reader != nullptr && reader(userdata, address, {&word, 1}) ? word : 0u;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
static void Handle(int, siginfo_t *, void *context) {
|
||||
auto ®isters = static_cast<ucontext_t *>(context)->uc_mcontext.gregs;
|
||||
const auto length =
|
||||
Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(registers[REG_RIP]);
|
||||
if (length == 0) std::abort();
|
||||
registers[REG_RAX] = Read(registers[REG_R10]);
|
||||
registers[REG_RIP] += length;
|
||||
}
|
||||
struct sigaction m_previous {};
|
||||
#elif defined(_WIN32)
|
||||
static LONG CALLBACK Handle(EXCEPTION_POINTERS *info) {
|
||||
auto &context = *info->ContextRecord;
|
||||
const auto length = Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(context.Rip);
|
||||
if (info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || length == 0)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
context.Rax = Read(context.R10);
|
||||
context.Rip += length;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
void *m_handler = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Evaluates one value through a walker compiled for it, keeping the plan's resource walker.
|
||||
inline bool EvaluateSrtValue(Libs::Graphics::ShaderRecompiler::IR::ResourcePlan &plan,
|
||||
const Libs::Graphics::ShaderRecompiler::IR::SrtRuntime &runtime,
|
||||
Libs::Graphics::ShaderRecompiler::IR::Value value, uint32_t &result) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
const auto *inst = value.Resolve().TryInstruction();
|
||||
const auto output = inst != nullptr ? inst->WalkOutput() : UINT32_MAX;
|
||||
auto code = std::move(plan.srt_code);
|
||||
auto scratch = std::move(plan.srt_scratch);
|
||||
const auto user_data = plan.srt_user_data;
|
||||
const auto captures = plan.srt_captures;
|
||||
CompileSrtWalker(plan, {&value, 1});
|
||||
std::vector<uint32_t> flat;
|
||||
const bool known = WalkSrt(plan, runtime, flat, nullptr) && WalkedValue(plan, value, result);
|
||||
plan.srt_code = std::move(code);
|
||||
plan.srt_scratch = std::move(scratch);
|
||||
plan.srt_user_data = user_data;
|
||||
plan.srt_captures = captures;
|
||||
if (inst != nullptr) inst->SetWalkOutput(output);
|
||||
return known;
|
||||
}
|
||||
|
||||
// Walks the plan's resources and reads back one descriptor.
|
||||
inline bool EvaluateSrtDescriptor(const Libs::Graphics::ShaderRecompiler::IR::ResourcePlan &plan,
|
||||
const Libs::Graphics::ShaderRecompiler::IR::SrtRuntime &runtime,
|
||||
uint32_t source,
|
||||
Libs::Graphics::ShaderRecompiler::IR::DescriptorValue &result) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
std::vector<uint32_t> flat;
|
||||
return WalkSrt(plan, runtime, flat, nullptr) && WalkedDescriptor(plan, source, result);
|
||||
}
|
||||
|
||||
#endif // KYTY_TESTS_SRTWALKERTESTMEMORY_H_
|
||||
+103
-143
@@ -24,13 +24,13 @@
|
||||
#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"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "graphics/shader/shaderCompiler.h"
|
||||
#include "libs/agc.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
#include "xxhash.h"
|
||||
#include "SrtWalkerTestMemory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -135,8 +135,7 @@ struct TestCompileResult {
|
||||
TestCompileResult RecompileForTest(
|
||||
std::span<const uint32_t> code,
|
||||
const ShaderRecompiler::CompileOptions &options,
|
||||
ShaderRecompiler::IR::SrtMemoryReader read_memory = nullptr,
|
||||
void *read_memory_data = nullptr, uint32_t push_data_start_dword = 0) {
|
||||
uint32_t push_data_start_dword = 0) {
|
||||
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
||||
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
@@ -144,8 +143,6 @@ TestCompileResult RecompileForTest(
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
||||
.read_memory = read_memory,
|
||||
.userdata = read_memory_data,
|
||||
.read_specialization_memory = ReadHostTestMemory,
|
||||
};
|
||||
Check(ShaderRecompiler::IR::MaterializeResources(
|
||||
@@ -1588,11 +1585,6 @@ void SetImageTestFormat(std::array<uint32_t, 64> *data, uint32_t srsrc,
|
||||
(*data)[format_dword] = static_cast<uint32_t>(format) << 20u;
|
||||
}
|
||||
|
||||
bool ReadZeroTestMemory(void *, uint64_t, std::span<uint32_t> values) {
|
||||
std::ranges::fill(values, 0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr uint32_t EncodeVop2(uint32_t opcode, uint32_t dst, uint32_t src0,
|
||||
uint32_t src1) {
|
||||
return ((opcode & 0x3fu) << 25u) | ((dst & 0xffu) << 17u) |
|
||||
@@ -5408,7 +5400,8 @@ void TestNewShaderRecompilerMemoryFamilyTranslation() {
|
||||
options.dump_ir = true;
|
||||
options.user_data = user_data;
|
||||
|
||||
auto result = RecompileForTest(shader, options, ReadZeroTestMemory);
|
||||
FakeGuestMemory zero_memory;
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check((result.decoded_dump.find("s_load_dword") != std::string::npos),
|
||||
"new decoder did not decode SMEM dword load");
|
||||
Check((result.decoded_dump.find("s_buffer_load_dword") != std::string::npos),
|
||||
@@ -9223,7 +9216,7 @@ void TestSharedReturnPreservesDescriptorDominance() {
|
||||
user_data[29] = static_cast<uint32_t>(address >> 32u);
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check(!result.program.dispatcher_fallback &&
|
||||
result.resources.buffers.size() == 1u &&
|
||||
result.resources.buffers[0].dwords[0] == 0x2000u,
|
||||
@@ -9283,7 +9276,7 @@ void TestNestedSelectionPreservesDescriptorSources() {
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.user_data_base = 8;
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 4u,
|
||||
"nested descriptor selection lost its structured resource bindings");
|
||||
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
||||
@@ -9359,7 +9352,7 @@ void TestSharedExitPreservesNativeDescriptorSources() {
|
||||
user_data[2] = arm;
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 2u,
|
||||
"shared-exit descriptor selection lost its structured resource bindings");
|
||||
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
||||
@@ -9405,7 +9398,7 @@ void TestNativeScalarReadDescriptorPlanning() {
|
||||
user_data[8] = mode;
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
const auto expected = mode == 2u ? table : std::array<uint32_t, 4>{};
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u &&
|
||||
std::ranges::equal(result.resources.flattened_srt, expected),
|
||||
@@ -9507,7 +9500,7 @@ void TestNativeGuardedSamplerSource(bool inline_constants) {
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data_base = 8;
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(code, options, ReadHostTestMemory);
|
||||
const auto result = RecompileForTest(code, options);
|
||||
const auto expected = mode == 2u ? sampler : std::array<uint32_t, 4>{};
|
||||
Check(!result.program.dispatcher_fallback && result.resources.samplers.size() == 1u &&
|
||||
result.resources.samplers[0].dword_count == expected.size() &&
|
||||
@@ -9615,8 +9608,7 @@ void TestBoundedMaterialBufferStores(bool scalar_key) {
|
||||
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
.user_data = user_data, .read_memory = ReadHostTestMemory,
|
||||
.read_specialization_memory = ReadHostTestMemory};
|
||||
.user_data = user_data, .read_specialization_memory = ReadHostTestMemory};
|
||||
const auto materialize = [&] {
|
||||
return ShaderRecompiler::IR::MaterializeResources(plan, runtime, snapshot, specialization);
|
||||
};
|
||||
@@ -9701,7 +9693,7 @@ void TestNativeDescriptorProvenanceKeepsGpuSelection() {
|
||||
options.input_info.compute = &input_info;
|
||||
options.user_data = user_data;
|
||||
for (const auto &shader : {selected, partial, loop}) {
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
Check(result.program.info.uses_dma && result.program.info.buffers.size() == 1u &&
|
||||
result.program.info.buffers[0].written,
|
||||
"GPU-selected scalar descriptor was materialized as a host binding");
|
||||
@@ -10864,7 +10856,7 @@ void TestMemoryFedScalarLeafCall() {
|
||||
struct Memory {
|
||||
std::array<uint32_t, 128> words{};
|
||||
uint64_t end = base + 0x10c;
|
||||
bool clean = true;
|
||||
bool readable = true;
|
||||
} memory;
|
||||
std::copy_n(std::array{0xe0040060u, 0x0010000fu, 8u, 0x5204u}.begin(),
|
||||
4, memory.words.begin());
|
||||
@@ -10875,13 +10867,14 @@ void TestMemoryFedScalarLeafCall() {
|
||||
memory.words[66] = EncodeSop1(0x21, 125, 14);
|
||||
const auto read = [](void *data, uint64_t address, std::span<uint32_t> out) {
|
||||
const auto& m = *static_cast<Memory*>(data);
|
||||
if (!m.clean || address < base || address > m.end || out.size_bytes() > m.end - address)
|
||||
if (!m.readable || address < base || address > m.end || out.size_bytes() > m.end - address)
|
||||
return false;
|
||||
std::memcpy(out.data(), reinterpret_cast<const uint8_t*>(m.words.data()) + address - base,
|
||||
out.size_bytes());
|
||||
return true;
|
||||
};
|
||||
IR::SrtRuntime runtime{.read_memory = read, .userdata = &memory, .read_specialization_memory = read};
|
||||
FakeGuestMemory fake(read, &memory);
|
||||
IR::SrtRuntime runtime{.userdata = &memory, .read_specialization_memory = read};
|
||||
const auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
const uint32_t gpu_callable[] = {EncodeVop1(0x02, 4, 256),
|
||||
EncodeSmem0(0x09, 14, 0), 4u << 25u, EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
|
||||
@@ -10965,14 +10958,14 @@ void TestMemoryFedScalarLeafCall() {
|
||||
Check(source.revision == 3 && source.reads.back().second == 8,
|
||||
"shorter exact-boundary leaf was not decoded after a failed old-span read");
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
memory.clean = false;
|
||||
memory.readable = false;
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, runtime); },
|
||||
"unreadable call dependencies reused stale linked code");
|
||||
memory.clean = true;
|
||||
auto ordinary = runtime;
|
||||
ordinary.read_specialization_memory = nullptr;
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, ordinary); },
|
||||
"call target query accepted an ordinary non-strict memory reader");
|
||||
memory.readable = true;
|
||||
auto no_reader = runtime;
|
||||
no_reader.read_specialization_memory = nullptr;
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, no_reader); },
|
||||
"leaf code was linked without a specialization memory reader");
|
||||
std::vector<uint32_t> escaping(std::begin(shader), std::end(shader) - 1);
|
||||
escaping.insert(escaping.end(), {EncodeVop1(0x01, 1, 14),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0), EncodeSopp(0x01)});
|
||||
@@ -10988,9 +10981,10 @@ void TestMemoryFedScalarLeafCall() {
|
||||
EncodeSMovB32(15, 129), EncodeSop2(0x0f, 14, 128, 128),
|
||||
EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
|
||||
auto pair_source = PrepareShaderSource(high_writer, options);
|
||||
IR::SrtWalker pair_walker(pair_source.call_targets, runtime);
|
||||
std::vector<uint32_t> flat;
|
||||
uint32_t high = 1;
|
||||
Check(pair_walker.Evaluate(pair_source.calls.front().target[1], high) && high == 0,
|
||||
Check(IR::WalkSrt(pair_source.call_targets, runtime, flat, nullptr) &&
|
||||
IR::WalkedValue(pair_source.call_targets, pair_source.calls.front().target[1], high) && high == 0,
|
||||
"scalar pair producer used a stale preceding high-half definition");
|
||||
const uint32_t joined[] = {
|
||||
EncodeSopp(0x04, 2), EncodeSMovB32(14, 129), EncodeSopp(0x02, 1),
|
||||
@@ -11136,7 +11130,7 @@ void TestMeshExportStorage() {
|
||||
options.back_code = split ? std::span{back} : std::span<const uint32_t>{};
|
||||
const auto result = RecompileForTest(
|
||||
split ? std::span{front} : std::span<const uint32_t>{monolithic},
|
||||
options, nullptr, nullptr, push_data_start);
|
||||
options, push_data_start);
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
const auto &layout = result.program.bindings;
|
||||
Check(layout.UsesPushData() == (push_data_start == PushData::MeshDrawDwordCount) &&
|
||||
@@ -11284,13 +11278,11 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
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);
|
||||
const IR::SrtRuntime runtime{.user_data = user_data};
|
||||
IR::DescriptorValue front_descriptor, back_descriptor;
|
||||
const auto &table = params == &first ? first_table : second_table;
|
||||
Check(walker.EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) &&
|
||||
walker.EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) &&
|
||||
Check(EvaluateSrtDescriptor(plan, runtime, program.info.buffers[0].source, front_descriptor) &&
|
||||
EvaluateSrtDescriptor(plan, runtime, 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()),
|
||||
@@ -13824,7 +13816,7 @@ void TestAutomaticObjectPrimitiveId() {
|
||||
PushData::GraphicsDrawDwords(true, false) == PushData::MeshDrawDwordCount,
|
||||
"graphics draw data reserved an incorrect prefix");
|
||||
for (const uint32_t start : {prefix, PushData::DwordCount}) {
|
||||
const auto result = RecompileForTest(shader, options, nullptr, nullptr, start);
|
||||
const auto result = RecompileForTest(shader, options, start);
|
||||
Check(result.program.bindings.UsesPushData() == (start == prefix),
|
||||
"object ID changed shader-data push/spill selection");
|
||||
Check(std::ranges::any_of(result.program.info.outputs, [](const StageOutput& output) {
|
||||
@@ -13841,7 +13833,7 @@ void TestAutomaticObjectPrimitiveId() {
|
||||
}
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
pixel.target_shader_mask = 0xf0000000u;
|
||||
ExpectFatal([&] { (void)RecompileForTest(shader, options, nullptr, nullptr, prefix); },
|
||||
ExpectFatal([&] { (void)RecompileForTest(shader, options, prefix); },
|
||||
"automatic RT7 silently overlapped an explicit MRT7 export");
|
||||
#endif
|
||||
}
|
||||
@@ -14489,33 +14481,6 @@ void CheckFlattenedReadSlots(const ShaderRecompiler::IR::Program &program,
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadSrtHostDword(void *, uint64_t address, std::span<uint32_t> values) {
|
||||
if (address == 0 || values.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
struct SrtHostRange {
|
||||
const uint32_t *data;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
bool ReadSrtHostRangeDword(void *userdata, uint64_t address, std::span<uint32_t> values) {
|
||||
const auto *range = static_cast<const SrtHostRange *>(userdata);
|
||||
if (range == nullptr || range->data == nullptr || range->count == 0 || values.empty()) {
|
||||
return false;
|
||||
}
|
||||
const auto base = reinterpret_cast<uint64_t>(range->data);
|
||||
const auto size = range->count * sizeof(uint32_t);
|
||||
if (address < base || values.size_bytes() > size || address - base > size - values.size_bytes()) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
void TestTypedDescriptorRealWideMoveTranslation() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSop1(0x04, 0, 20), // s_mov_b64 s[0:1], s[20:21]
|
||||
@@ -14581,7 +14546,7 @@ void TestComputeImageFill() {
|
||||
options.input_info.compute = &compute;
|
||||
options.user_data = userdata;
|
||||
enum class Mutation { None, Offset, WrongAxis, VaryingValue, Predicate, ExtraStore, A16 };
|
||||
const auto Run = [&](Mutation mutation, bool clean = true) {
|
||||
const auto Run = [&](Mutation mutation) {
|
||||
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
|
||||
auto &program = translated.program;
|
||||
for (auto *block : program.blocks) {
|
||||
@@ -14618,13 +14583,11 @@ void TestComputeImageFill() {
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan,
|
||||
{.user_data = userdata, .read_memory = ReadHostTestMemory,
|
||||
.read_specialization_memory = clean ? ReadHostTestMemory : nullptr},
|
||||
snapshot, specialization),
|
||||
// The fill value is a walker load, so it needs no specialization memory reader.
|
||||
Check(MaterializeResources(plan, {.user_data = userdata}, snapshot, specialization),
|
||||
"captured stencil clear did not materialize");
|
||||
const auto &fill = snapshot.uniform_fill;
|
||||
const bool expected = mutation == Mutation::None && clean;
|
||||
const bool expected = mutation == Mutation::None;
|
||||
Check((fill.kind == UniformFillKind::Image) == expected,
|
||||
"image fill proof accepted an unsafe store or missed the captured clear");
|
||||
if (expected) {
|
||||
@@ -14634,7 +14597,6 @@ void TestComputeImageFill() {
|
||||
}
|
||||
};
|
||||
Run(Mutation::None);
|
||||
Run(Mutation::None, false);
|
||||
for (const auto mutation : {Mutation::Offset, Mutation::WrongAxis, Mutation::VaryingValue,
|
||||
Mutation::Predicate, Mutation::ExtraStore, Mutation::A16})
|
||||
Run(mutation);
|
||||
@@ -14835,27 +14797,22 @@ void TestGpuProducedWritableDescriptor() {
|
||||
"native GPU-built writable descriptor acquired an unnecessary alias proof");
|
||||
struct Reads {
|
||||
std::array<uint32_t, 3> header{0x26c0u, 0u, 151u};
|
||||
uint32_t ordinary = 0;
|
||||
uint32_t strict = 0;
|
||||
uint32_t count = 0;
|
||||
} reads;
|
||||
const SrtRuntime runtime{
|
||||
.user_data = user_data,
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
if (words.size() != 1) return false;
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
const uint32_t index = address == 0x300315028ull ? 0u
|
||||
: address == 0x30031502cull ? 1u
|
||||
: address == 0x300315074ull ? 2u : 3u;
|
||||
if (index == 3u) return false;
|
||||
++reads.ordinary;
|
||||
uint32_t index = 0;
|
||||
switch (address) {
|
||||
case 0x300315028ull: index = 0u; break;
|
||||
case 0x30031502cull: index = 1u; break;
|
||||
case 0x300315074ull: index = 2u; break;
|
||||
default: return false;
|
||||
}
|
||||
++reads.count;
|
||||
words[0] = reads.header[index];
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
|
||||
++static_cast<Reads *>(data)->strict;
|
||||
return false; // Current GPU bytes are available only through the ordinary reader.
|
||||
}};
|
||||
}, &reads);
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto check = [&](uint32_t low, uint32_t high, uint32_t count, uint32_t reads_expected) {
|
||||
@@ -14866,8 +14823,7 @@ void TestGpuProducedWritableDescriptor() {
|
||||
snapshot.buffers[0].dwords[1] == high &&
|
||||
snapshot.buffers[0].dwords[2] == count &&
|
||||
snapshot.buffers[0].dwords[3] == 0x16204u &&
|
||||
snapshot.specialization_reads.empty() && reads.strict == 0 &&
|
||||
reads.ordinary == reads_expected,
|
||||
snapshot.specialization_reads.empty() && reads.count == reads_expected,
|
||||
"GPU-produced descriptor lost current scalar data or repeated its reads");
|
||||
};
|
||||
check(0x003176c0u, 0x00800003u, 151u, 3u);
|
||||
@@ -14929,25 +14885,29 @@ void TestUniformSelectedWritableDescriptor() {
|
||||
auto plan = ExtractResourcePlan(translated.program);
|
||||
Check(plan.info.buffers.size() == 1 && plan.info.buffers[0].written,
|
||||
"uniform address selection lost its writable buffer");
|
||||
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
const bool alternate = *static_cast<uint32_t *>(data) == 0;
|
||||
if (words.size() != 1) return false;
|
||||
// An unavailable read would complete as zero, so the reader counts them.
|
||||
struct Memory { const uint32_t *flag; uint32_t unavailable = 0; } memory{&user_data[2]};
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &memory = *static_cast<Memory *>(data);
|
||||
const bool alternate = *memory.flag == 0;
|
||||
bool available = false;
|
||||
switch (address) {
|
||||
case 0x1040: words[0] = 8u; return alternate;
|
||||
case 0x1044: words[0] = 1u; return alternate;
|
||||
case 0x1080: words[0] = 3u; return !alternate;
|
||||
case 0x109c: words[0] = 2u; return !alternate;
|
||||
case 0x1084: words[0] = 10u; return true;
|
||||
default: return false;
|
||||
case 0x1040: words[0] = 8u; available = alternate; break;
|
||||
case 0x1044: words[0] = 1u; available = alternate; break;
|
||||
case 0x1080: words[0] = 3u; available = !alternate; break;
|
||||
case 0x109c: words[0] = 2u; available = !alternate; break;
|
||||
case 0x1084: words[0] = 10u; available = true; break;
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = read,
|
||||
.userdata = &user_data[2], .read_specialization_memory = read};
|
||||
memory.unavailable += !available;
|
||||
return available;
|
||||
}, &memory);
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const uint32_t flag : {1u, 0u, 1u}) {
|
||||
user_data[2] = flag;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && memory.unavailable == 0,
|
||||
"uniform selection evaluated an unavailable untaken scalar source");
|
||||
const auto &descriptor = snapshot.buffers[0].dwords;
|
||||
Check(descriptor[0] == (flag ? 0xe0u : 4u) &&
|
||||
@@ -14977,18 +14937,16 @@ void TestUniformSelectedDescriptorLoadAddress() {
|
||||
Check(plan.srt_reads.size() == 4u && plan.info.buffers.size() == 1u &&
|
||||
plan.info.buffers[0].written && !plan.capture_specialization_reads,
|
||||
"conditional scalar address duplicated or detached descriptor reads");
|
||||
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
const auto flag = *static_cast<uint32_t *>(data);
|
||||
const uint64_t base = flag ? 0x2000u : 0x1000u;
|
||||
const std::array<uint32_t, 4> descriptor{0x3000u + flag * 0x1000u,
|
||||
4u << 16u, 16u, 0x16204u};
|
||||
if (words.size() != 1u || address < base || address >= base + 16u ||
|
||||
(address & 3u) != 0u) return false;
|
||||
if (address < base || address >= base + 16u || (address & 3u) != 0u) return false;
|
||||
words[0] = descriptor[(address - base) / 4u];
|
||||
return true;
|
||||
};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = read,
|
||||
.userdata = &user_data[0]};
|
||||
}, &user_data[0]);
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
for (const uint32_t flag : {0u, 1u, 0u}) {
|
||||
@@ -15058,7 +15016,7 @@ void TestBoundedScalarMaterialImageKeys() {
|
||||
uint32_t stride = 160;
|
||||
uint32_t records = 409;
|
||||
uint64_t unavailable = 0;
|
||||
bool clean = true;
|
||||
bool readable = true;
|
||||
bool mixed = false;
|
||||
bool same_key = false;
|
||||
uint32_t key_reads = 0;
|
||||
@@ -15066,7 +15024,7 @@ void TestBoundedScalarMaterialImageKeys() {
|
||||
} memory;
|
||||
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &m = *static_cast<Memory *>(data);
|
||||
if (!m.clean || (m.unavailable >= address &&
|
||||
if (!m.readable || (m.unavailable >= address &&
|
||||
m.unavailable - address < words.size_bytes())) return false;
|
||||
if (words.size() == 1 && address >= 0x10a0u && address < 0x10c0u) {
|
||||
const uint32_t descriptors[] = {0x2000u, m.stride << 16u, m.records, 0x5204u,
|
||||
@@ -15101,6 +15059,7 @@ void TestBoundedScalarMaterialImageKeys() {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
FakeGuestMemory fake(read, &memory);
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = read};
|
||||
ResourceSnapshot snapshot;
|
||||
@@ -15138,9 +15097,11 @@ void TestBoundedScalarMaterialImageKeys() {
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
memory.key_reads == 0 && memory.images == 0 && snapshot.images.size() == 2,
|
||||
"empty scalar material loop retained image probes");
|
||||
memory = {.clean = false};
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"dirty scalar material proof was accepted");
|
||||
// Unreadable descriptor and count reads are zero, which leaves the loop empty.
|
||||
memory = {.readable = false};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
memory.key_reads == 0 && memory.images == 0 && snapshot.images.size() == 2,
|
||||
"unreadable scalar material count did not read as zero");
|
||||
for (const uint64_t unavailable : {0x2094u, 0x211cu}) {
|
||||
memory = {.unavailable = unavailable};
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
@@ -15218,12 +15179,14 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
||||
std::array<uint32_t, 64> carry_user_data{};
|
||||
const uint64_t shader_base = 0x00000012fffffffbull;
|
||||
const uint64_t expected_pc = shader_base + 5u;
|
||||
ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base,
|
||||
nullptr, nullptr};
|
||||
ShaderRecompiler::IR::DescriptorValue 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),
|
||||
const ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base};
|
||||
auto carry_plan = ShaderRecompiler::IR::ExtractResourcePlan(carry_ir);
|
||||
const auto &carry_words = carry_plan.descriptor_sources[carry_source_index].dwords;
|
||||
uint32_t carry_low = 0, carry_high = 0;
|
||||
Check(EvaluateSrtValue(carry_plan, carry_runtime, carry_words[0], carry_low) &&
|
||||
EvaluateSrtValue(carry_plan, carry_runtime, carry_words[1], carry_high) &&
|
||||
carry_low == static_cast<uint32_t>(expected_pc) &&
|
||||
carry_high == static_cast<uint32_t>(expected_pc >> 32u),
|
||||
"S_GETPC shader-base or add/addc carry evaluation was incorrect");
|
||||
|
||||
const uint32_t load_shader[] = {
|
||||
@@ -15285,19 +15248,21 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
||||
BuildTypedPlan(inline_sampler_shader,
|
||||
static_cast<uint32_t>(std::size(inline_sampler_shader)),
|
||||
inline_sampler_ir);
|
||||
// The walk also reads the sampled image's user-data descriptor.
|
||||
auto user_data = ImageTestUserData();
|
||||
ShaderRecompiler::IR::DescriptorValue sampler;
|
||||
ShaderRecompiler::IR::SrtRuntime runtime;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
|
||||
Check(inline_sampler_ir.info.samplers.size() == 1 &&
|
||||
TypedDescriptorSource(inline_sampler_ir,
|
||||
inline_sampler_ir.info.samplers[0].source) !=
|
||||
nullptr &&
|
||||
ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) &&
|
||||
EvaluateSrtDescriptor(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime,
|
||||
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 "
|
||||
"incorrectly");
|
||||
|
||||
auto user_data = ImageTestUserData();
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
auto result = RecompileForTest(inline_sampler_shader, options);
|
||||
@@ -15325,9 +15290,8 @@ void TestSrtWalkerRealSmemTranslation() {
|
||||
user_data[8] = static_cast<uint32_t>(address);
|
||||
user_data[9] = static_cast<uint32_t>(address >> 32u);
|
||||
std::vector<uint32_t> flat;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat);
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
|
||||
const auto walked = ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr);
|
||||
Check(walked, "SRT walk failed");
|
||||
Check(flat.size() == table.size() &&
|
||||
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||
@@ -15353,10 +15317,9 @@ void TestSrtWalkerVccBaseTranslation() {
|
||||
const auto address = reinterpret_cast<uint64_t>(table.data());
|
||||
user_data[27] = static_cast<uint32_t>(address);
|
||||
user_data[28] = static_cast<uint32_t>(address >> 32u);
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr), "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");
|
||||
@@ -15382,17 +15345,16 @@ void TestSrtWalkerRealSBufferTranslation() {
|
||||
user_data[9] = static_cast<uint32_t>(address >> 32u);
|
||||
user_data[10] = sizeof(table);
|
||||
std::vector<uint32_t> flat;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
|
||||
nullptr};
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
|
||||
const auto plan = ShaderRecompiler::IR::ExtractResourcePlan(ir);
|
||||
const auto walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat);
|
||||
const auto walked = ShaderRecompiler::IR::WalkSrt(plan, runtime, flat, nullptr);
|
||||
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(plan, runtime).RefreshFlatBuffer(flat);
|
||||
const auto bounds_walked = ShaderRecompiler::IR::WalkSrt(plan, runtime, flat, nullptr);
|
||||
Check(bounds_walked && flat == std::vector<uint32_t>{table[0], table[1], table[2], 0u},
|
||||
"real S_BUFFER_LOAD walk did not zero only the out-of-bounds DWORD");
|
||||
CheckFlattenedReadSlots(
|
||||
@@ -15408,7 +15370,7 @@ void TestSrtWalkerRealSBufferTranslation() {
|
||||
static_cast<uint32_t>(std::size(negative_shader)),
|
||||
negative_ir);
|
||||
user_data[10] = sizeof(table);
|
||||
Check(!ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime).RefreshFlatBuffer(flat),
|
||||
Check(!ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime, flat, nullptr),
|
||||
"real S_BUFFER_LOAD walk accepted a negative immediate");
|
||||
}
|
||||
|
||||
@@ -15443,11 +15405,9 @@ void TestScalarMemorySourcesCapturedBeforeWrites() {
|
||||
if (opcode == 0x0a) {
|
||||
user_data[base + 2u] = sizeof(table);
|
||||
}
|
||||
SrtHostRange range{table.data(), table.size()};
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{
|
||||
user_data, 0, ReadSrtHostRangeDword, &range};
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
|
||||
std::vector<uint32_t> flat;
|
||||
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
|
||||
Check(ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr), "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 "
|
||||
@@ -16007,7 +15967,7 @@ void TestGraphicsPushConstantPlacement() {
|
||||
|
||||
constexpr uint32_t PushDataStart = 7;
|
||||
auto placed =
|
||||
RecompileForTest(shader, options, nullptr, nullptr, PushDataStart);
|
||||
RecompileForTest(shader, options, PushDataStart);
|
||||
Check(placed.program.bindings.UsesPushData() &&
|
||||
placed.program.bindings.push_data_start_dword == PushDataStart &&
|
||||
placed.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 0,
|
||||
@@ -16016,7 +15976,7 @@ void TestGraphicsPushConstantPlacement() {
|
||||
"SPIR-V push-data block did not use the canonical zero base");
|
||||
CheckSpirvBinaryValidates(placed.spirv);
|
||||
|
||||
auto exact = RecompileForTest(shader, options, nullptr, nullptr, 28);
|
||||
auto exact = RecompileForTest(shader, options, 28);
|
||||
Check(exact.program.bindings.UsesPushData() &&
|
||||
exact.program.bindings.push_data_start_dword == 28 &&
|
||||
exact.program.bindings.ShaderDataDwords() == 4,
|
||||
@@ -16026,8 +15986,8 @@ void TestGraphicsPushConstantPlacement() {
|
||||
Check(cursor == 32, "exact-fit shader data advanced the cursor incorrectly");
|
||||
CheckSpirvBinaryValidates(exact.spirv);
|
||||
|
||||
auto spill = RecompileForTest(shader, options, nullptr, nullptr, 29);
|
||||
auto full = RecompileForTest(shader, options, nullptr, nullptr, 32);
|
||||
auto spill = RecompileForTest(shader, options, 29);
|
||||
auto full = RecompileForTest(shader, options, 32);
|
||||
Check(!spill.program.bindings.UsesPushData() &&
|
||||
spill.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 1 &&
|
||||
spill.program.bindings == full.program.bindings && spill.spirv == full.spirv,
|
||||
|
||||
Reference in New Issue
Block a user