mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
shader: skip resources in untaken uniform branches
Co-authored-by: GodsWill <GodsWill@users.noreply.github.com> Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
This commit is contained in:
@@ -470,6 +470,13 @@ struct SrtRead {
|
||||
bool operator==(const SrtRead& other) const = default;
|
||||
};
|
||||
|
||||
struct ResourceBlock {
|
||||
// Conditional successors are ordered true, false; an empty condition follows every edge.
|
||||
Value condition;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> sources;
|
||||
};
|
||||
|
||||
// Stable shader metadata consumed by the renderer after native IR has been discarded.
|
||||
struct CompiledShaderInfo {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
@@ -483,8 +490,8 @@ struct CompiledShaderInfo {
|
||||
BindingLayout bindings;
|
||||
};
|
||||
|
||||
// Immutable runtime resource analysis retained by the shader cache. It owns only the native
|
||||
// value graph reachable from descriptors/SRT reads, rather than the translated shader CFG.
|
||||
// Immutable runtime resource analysis retained by the shader cache. It owns the descriptor/SRT
|
||||
// and uniform condition values, plus resource-use edges, without retaining translated blocks.
|
||||
struct ResourcePlan {
|
||||
ResourcePlan() = default;
|
||||
~ResourcePlan();
|
||||
@@ -501,6 +508,7 @@ struct ResourcePlan {
|
||||
std::list<Inst> value_storage;
|
||||
std::vector<MemoryInfo> memory_info;
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<ResourceBlock> control_flow;
|
||||
std::vector<uint32_t> materialization_sources;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<uint8_t> clean_flat_slots;
|
||||
|
||||
@@ -293,8 +293,9 @@ static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& r
|
||||
}
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint8_t> active_sources;
|
||||
if (!EvaluateRuntimeSources(program, program.materialization_sources, runtime, values,
|
||||
flattened_srt, program.clean_flat_slots)) {
|
||||
flattened_srt, program.clean_flat_slots, active_sources)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -308,6 +309,10 @@ static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& r
|
||||
const auto& image = program.info.images[image_index];
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (!active_sources[image.source]) {
|
||||
next.images[image_index].dword_count = 8u;
|
||||
continue;
|
||||
}
|
||||
const std::array requests {source->indirect_image->material_source,
|
||||
source->indirect_image->heap_source};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
@@ -604,6 +609,110 @@ bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan&
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IntegerCondition(const ResourcePlan& program, Value condition) {
|
||||
if (!ValidateRuntimeValue(program, condition)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<Value> pending {condition};
|
||||
std::unordered_set<const Inst*> visited;
|
||||
while (!pending.empty()) {
|
||||
const auto value = pending.back().Resolve();
|
||||
pending.pop_back();
|
||||
// Host floating-point evaluation does not model the shader's rounding/denormal modes.
|
||||
if (TypesOverlap(value.GetType(), Type::F16 | Type::F32 | Type::F32x2)) {
|
||||
return false;
|
||||
}
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst != nullptr && visited.insert(inst).second) {
|
||||
if (inst->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto& read = program.srt_reads[inst->Arg(1).Resolve().U32()];
|
||||
if (!ValidateRuntimeValue(program, read.value)) {
|
||||
return false;
|
||||
}
|
||||
pending.push_back(read.value);
|
||||
}
|
||||
for (size_t i = 0; i < inst->NumArgs(); i++) {
|
||||
pending.push_back(inst->Arg(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program) {
|
||||
if (program.blocks.size() != program.block_info.size()) {
|
||||
return {};
|
||||
}
|
||||
std::unordered_map<uint32_t, uint32_t> indices;
|
||||
for (uint32_t i = 0; i < program.block_info.size(); i++) {
|
||||
if (!indices.emplace(program.block_info[i].id, i).second) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
std::vector<ResourceBlock> blocks(program.blocks.size());
|
||||
for (uint32_t i = 0; i < blocks.size(); i++) {
|
||||
auto& block = blocks[i];
|
||||
const auto& info = program.block_info[i];
|
||||
const auto& terminator = info.terminator;
|
||||
std::vector<uint32_t> successors;
|
||||
switch (terminator.kind) {
|
||||
case CFG::TerminatorKind::Branch: successors.push_back(terminator.true_block); break;
|
||||
case CFG::TerminatorKind::ConditionalBranch:
|
||||
successors = {terminator.true_block, terminator.false_block};
|
||||
if (IntegerCondition(program, info.condition)) {
|
||||
block.condition = info.condition;
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch:
|
||||
successors = terminator.indirect_targets;
|
||||
break;
|
||||
case CFG::TerminatorKind::Return: break;
|
||||
default: return {};
|
||||
}
|
||||
for (const auto successor: successors) {
|
||||
const auto found = indices.find(successor);
|
||||
if (found == indices.end()) {
|
||||
return {};
|
||||
}
|
||||
block.successors.push_back(found->second);
|
||||
}
|
||||
for (const auto& inst: *program.blocks[i]) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto buffer = BufferAccessOf(op);
|
||||
const auto image = ImageOpcodeInfoOf(op);
|
||||
// Any shader write may alias a scalar predicate read, including on a later loop visit.
|
||||
if (buffer == BufferAccess::Write || buffer == BufferAccess::Atomic ||
|
||||
image.access == ImageAccess::Write || image.access == ImageAccess::Atomic ||
|
||||
AddressOpcodeInfoOf(op).access == AddressAccess::Write) {
|
||||
return {};
|
||||
}
|
||||
if (buffer == BufferAccess::None && image.access == ImageAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
|
||||
if (memory.planning_only) {
|
||||
continue;
|
||||
}
|
||||
if (buffer != BufferAccess::None) {
|
||||
block.sources.push_back(program.info.buffers.at(memory.resource).source);
|
||||
} else {
|
||||
block.sources.push_back(program.info.images.at(memory.resource).source);
|
||||
if (image.needs_sampler) {
|
||||
block.sources.push_back(program.info.samplers.at(memory.sampler).source);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::ranges::sort(block.sources);
|
||||
block.sources.erase(std::unique(block.sources.begin(), block.sources.end()),
|
||||
block.sources.end());
|
||||
}
|
||||
if (std::ranges::none_of(
|
||||
blocks, [](const ResourceBlock& block) { return !block.condition.IsEmpty(); })) {
|
||||
return {};
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
ResourcePlan plan;
|
||||
plan.stage = program.stage;
|
||||
@@ -659,6 +768,10 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
for (const auto& read: program.srt_reads) {
|
||||
plan.srt_reads.push_back({Clone(read.value), read.flat_offset});
|
||||
}
|
||||
plan.control_flow = ResourceControlFlow(program);
|
||||
for (auto& block: plan.control_flow) {
|
||||
block.condition = Clone(block.condition);
|
||||
}
|
||||
plan.materialization_sources.reserve(plan.info.buffers.size() + plan.info.images.size() +
|
||||
plan.info.samplers.size());
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
|
||||
@@ -472,10 +472,11 @@ private:
|
||||
}
|
||||
m_visiting.push_back(inst);
|
||||
uint64_t out = 0;
|
||||
if (!EvaluateInst(*inst, out)) {
|
||||
const bool evaluated = EvaluateInst(*inst, out);
|
||||
m_visiting.pop_back();
|
||||
if (!evaluated) {
|
||||
return false;
|
||||
}
|
||||
m_visiting.pop_back();
|
||||
m_cache.emplace(inst, out);
|
||||
result = out;
|
||||
return true;
|
||||
@@ -938,7 +939,8 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
||||
bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
std::span<const uint8_t> clean_flat_slots,
|
||||
std::vector<uint8_t>& active_sources) {
|
||||
if (!program.srt_plan_complete) {
|
||||
return false;
|
||||
}
|
||||
@@ -948,8 +950,38 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
|
||||
}
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
Evaluator clean_evaluator(program, clean_runtime);
|
||||
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
|
||||
Evaluator clean_evaluator(program, clean_runtime);
|
||||
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
|
||||
std::vector<uint8_t> active(program.descriptor_sources.size(), 1u);
|
||||
if (evaluate_flat && !program.control_flow.empty()) {
|
||||
for (const auto& block: program.control_flow) {
|
||||
for (const auto source: block.sources) {
|
||||
active.at(source) = 0u;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> visited(program.control_flow.size());
|
||||
std::vector<uint32_t> pending {0};
|
||||
while (!pending.empty()) {
|
||||
const auto index = pending.back();
|
||||
pending.pop_back();
|
||||
if (visited.at(index)) {
|
||||
continue;
|
||||
}
|
||||
visited[index] = 1u;
|
||||
const auto& block = program.control_flow[index];
|
||||
for (const auto source: block.sources) {
|
||||
active[source] = 1u;
|
||||
}
|
||||
uint32_t condition = 0;
|
||||
// A missing clean reader must never fall through to the evaluator's raw-memory path.
|
||||
if (!block.condition.IsEmpty() && runtime.read_specialization_memory != nullptr &&
|
||||
clean_evaluator.Evaluate(block.condition, condition)) {
|
||||
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
|
||||
} else {
|
||||
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<DescriptorValue> evaluated;
|
||||
evaluated.reserve(sources.size());
|
||||
for (const auto source_index: sources) {
|
||||
@@ -959,7 +991,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
|
||||
}
|
||||
DescriptorValue value;
|
||||
value.dword_count = source->dword_count;
|
||||
for (uint32_t index = 0; index < source->dword_count; index++) {
|
||||
for (uint32_t index = 0; active[source_index] && index < source->dword_count; index++) {
|
||||
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) {
|
||||
return false;
|
||||
}
|
||||
@@ -980,6 +1012,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
|
||||
}
|
||||
}
|
||||
results = std::move(evaluated);
|
||||
active_sources = std::move(active);
|
||||
if (evaluate_flat) {
|
||||
flat = std::move(flattened);
|
||||
}
|
||||
@@ -1014,21 +1047,23 @@ bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
|
||||
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results) {
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, ignored, false, {});
|
||||
std::vector<uint8_t> active;
|
||||
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, ignored, false, {},
|
||||
active);
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat,
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::vector<uint8_t>& active_sources) {
|
||||
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true,
|
||||
clean_flat_slots);
|
||||
clean_flat_slots, active_sources);
|
||||
}
|
||||
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::vector<uint32_t>& flat) {
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat) {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {});
|
||||
std::vector<uint8_t> active;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, active);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -32,11 +32,12 @@ bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
|
||||
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
|
||||
|
||||
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
|
||||
// On failure neither destination is changed.
|
||||
// Evaluates potentially reachable descriptor sources and the flattened immediate SRT with one
|
||||
// memoized scalar walk. Inactive descriptors are zero; on failure no destination is changed.
|
||||
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots);
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::vector<uint8_t>& active_sources);
|
||||
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::vector<uint32_t>& flat);
|
||||
|
||||
@@ -900,9 +900,10 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
.userdata = &memory};
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
std::vector<uint32_t> flat;
|
||||
std::vector<uint8_t> active_sources;
|
||||
const uint32_t request = fixture.program.info.buffers[0].source;
|
||||
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
|
||||
descriptors, flat, {}),
|
||||
descriptors, flat, {}, active_sources),
|
||||
"typed runtime source evaluation failed");
|
||||
Check(descriptors.size() == 1 && descriptors[0].dwords[0] == 0xdeadbeefu &&
|
||||
flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1,
|
||||
@@ -912,10 +913,12 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
memory.fail_after = 0;
|
||||
descriptors = {{{1u}, 1u}};
|
||||
flat = {2u};
|
||||
active_sources = {3u};
|
||||
Check(!EvaluateRuntimeSources(fixture.program, std::span{&request, 1},
|
||||
runtime, descriptors, flat, {}) &&
|
||||
runtime, descriptors, flat, {}, active_sources) &&
|
||||
descriptors == std::vector<DescriptorValue>{{{1u}, 1u}} &&
|
||||
flat == std::vector<uint32_t>{2u},
|
||||
flat == std::vector<uint32_t>{2u} &&
|
||||
active_sources == std::vector<uint8_t>{3u},
|
||||
"runtime evaluation failure was not transactional");
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
@@ -1188,6 +1191,183 @@ void TestBufferSwizzleSpecialization() {
|
||||
"buffer swizzle change did not select a new specialization key");
|
||||
}
|
||||
|
||||
enum class ConditionalBufferUse { Optional, Shared, Loop, Writable };
|
||||
|
||||
ResourcePlan ConditionalBufferPlan(ConditionalBufferUse use) {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
auto *optional = fixture.AddBlock();
|
||||
auto *done = fixture.AddBlock();
|
||||
auto *condition_block = entry;
|
||||
uint32_t condition_index = 0;
|
||||
fixture.program.block_info[0].id = 11;
|
||||
fixture.program.block_info[1].id = 27;
|
||||
fixture.program.block_info[2].id = 42;
|
||||
if (use == ConditionalBufferUse::Loop) {
|
||||
condition_block = fixture.AddBlock();
|
||||
condition_index = 3;
|
||||
fixture.program.block_info[3].id = 55;
|
||||
fixture.program.block_info[0].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = 55};
|
||||
entry->AddBranch(condition_block);
|
||||
}
|
||||
condition_block->AddBranch(optional);
|
||||
condition_block->AddBranch(done);
|
||||
optional->AddBranch(use == ConditionalBufferUse::Loop ? condition_block : done);
|
||||
fixture.program.block_info[condition_index].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = 27, .false_block = 42};
|
||||
fixture.program.block_info[1].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch,
|
||||
.true_block = use == ConditionalBufferUse::Loop ? 55u : 42u};
|
||||
|
||||
const auto control = fixture.Buffer(
|
||||
{fixture.UserData(0), fixture.UserData(1), fixture.UserData(2),
|
||||
fixture.UserData(3)}, 4);
|
||||
MemoryInfo scalar;
|
||||
scalar.kind = ResourceKind::ScalarBuffer;
|
||||
auto flag = fixture.Emit(ValueOpcode::ReadConstBuffer,
|
||||
{control, Value(0u)}, fixture.AddMemory(scalar, 4));
|
||||
if (use == ConditionalBufferUse::Loop) {
|
||||
auto &phi = condition_block->AppendNewInst(ValueOpcode::Phi, {},
|
||||
static_cast<uint64_t>(Type::U32));
|
||||
phi.AddPhiOperand(entry, flag);
|
||||
phi.AddPhiOperand(optional, Value(1u));
|
||||
flag = Value(&phi);
|
||||
}
|
||||
fixture.program.block_info[condition_index].condition =
|
||||
fixture.Emit(ValueOpcode::INotEqual32, {flag, Value(0u)}, 0, condition_block);
|
||||
|
||||
const auto payload = fixture.Buffer(
|
||||
{fixture.UserData(4), fixture.UserData(5), fixture.UserData(6),
|
||||
fixture.UserData(7)}, 8);
|
||||
MemoryInfo vector;
|
||||
vector.kind = ResourceKind::Buffer;
|
||||
const auto load = [&](Block *block) {
|
||||
fixture.Emit(ValueOpcode::LoadBufferU32,
|
||||
{payload, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(vector, 8), block);
|
||||
};
|
||||
load(optional);
|
||||
if (use == ConditionalBufferUse::Shared) {
|
||||
load(done);
|
||||
}
|
||||
if (use == ConditionalBufferUse::Writable) {
|
||||
fixture.Emit(ValueOpcode::StoreBufferU32,
|
||||
{control, Value(0u), Value(0u), Value(0u), Value(1u),
|
||||
Value(true)}, fixture.AddMemory(vector, 12));
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
return ExtractResourcePlan(fixture.program);
|
||||
}
|
||||
|
||||
void TestConditionalBufferMaterialization() {
|
||||
auto plan = ConditionalBufferPlan(ConditionalBufferUse::Optional);
|
||||
// GTA III leaves packet words in s[12:15] when its scalar control word is zero.
|
||||
std::array<uint32_t, 8> user_data{
|
||||
0x1000, 16u << 16u, 1, 0x4dfac,
|
||||
0xc0107600, 0x8c, 0x97730000, 0x100020};
|
||||
TestMemory memory;
|
||||
SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
snapshot.buffers[1].dword_count == 4 &&
|
||||
snapshot.buffers[1].dwords == std::array<uint32_t, 8>{},
|
||||
"untaken scalar branch materialized stale buffer words");
|
||||
Check(snapshot.user_data == std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
||||
"resource reachability changed native shader user data");
|
||||
|
||||
runtime.user_data = std::span(user_data).first(4);
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"untaken branch evaluated its unavailable descriptor");
|
||||
const auto prior = snapshot;
|
||||
memory.words[0] = 1;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
SameResourceSnapshot(snapshot, prior),
|
||||
"taken branch accepted an unavailable descriptor or changed the snapshot");
|
||||
|
||||
runtime.user_data = user_data;
|
||||
const auto CheckActive = [&] {
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
std::equal(user_data.begin() + 4, user_data.end(),
|
||||
snapshot.buffers[1].dwords.begin()),
|
||||
"potentially executed buffer descriptor was discarded");
|
||||
};
|
||||
CheckActive();
|
||||
memory.words[0] = 0;
|
||||
memory.fail_after = memory.reads;
|
||||
CheckActive();
|
||||
runtime.read_specialization_memory = nullptr;
|
||||
CheckActive();
|
||||
}
|
||||
|
||||
void TestConservativeBufferReachability() {
|
||||
std::array<uint32_t, 8> user_data{
|
||||
0x1000, 16u << 16u, 1, 0x4dfac,
|
||||
0x2000, 16u << 16u, 1, 0x4dfac};
|
||||
TestMemory memory;
|
||||
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
for (const auto use : {ConditionalBufferUse::Shared, ConditionalBufferUse::Loop,
|
||||
ConditionalBufferUse::Writable}) {
|
||||
auto plan = ConditionalBufferPlan(use);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
std::equal(user_data.begin() + 4, user_data.end(),
|
||||
snapshot.buffers[1].dwords.begin()),
|
||||
"shared, loop-dependent, or writable-alias resource was pruned");
|
||||
}
|
||||
}
|
||||
|
||||
void TestConditionalIndirectImageMaterialization() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
auto fixture = MakeIndirectImageFixture(false);
|
||||
auto *body = fixture->block;
|
||||
auto *entry = fixture->AddBlock();
|
||||
auto *done = fixture->AddBlock();
|
||||
entry->AddBranch(body);
|
||||
entry->AddBranch(done);
|
||||
body->AddBranch(done);
|
||||
const auto flag = fixture->Emit(ValueOpcode::GetUserData,
|
||||
{Value(static_cast<ScalarReg>(8))}, 0, entry);
|
||||
fixture->program.block_info[1].condition = fixture->Emit(
|
||||
ValueOpcode::INotEqual32, {flag, Value(0u)}, 0, entry);
|
||||
fixture->program.block_info[1].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = 0, .false_block = 2};
|
||||
fixture->program.block_info[0].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = 2};
|
||||
std::swap(fixture->program.blocks[0], fixture->program.blocks[1]);
|
||||
std::swap(fixture->program.block_info[0], fixture->program.block_info[1]);
|
||||
fixture->PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture->program);
|
||||
std::array<uint32_t, 9> user_data{
|
||||
0x1000, 224u << 16u, 2, 0, 0x2000, 16u << 16u, 4, 0, 0};
|
||||
uint32_t reads = 0;
|
||||
const SrtRuntime runtime{
|
||||
.user_data = user_data, .userdata = &reads,
|
||||
.read_specialization_memory = [](void *data, uint64_t, uint32_t *) {
|
||||
++*static_cast<uint32_t *>(data);
|
||||
return false;
|
||||
}};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
reads == 0 && snapshot.images.size() == 1 &&
|
||||
snapshot.images[0].dwords == std::array<uint32_t, 8>{},
|
||||
"untaken indirect image branch probed its descriptor table");
|
||||
user_data[8] = 1;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && reads != 0,
|
||||
"taken indirect image branch did not require its descriptor table");
|
||||
}
|
||||
|
||||
void TestShaderInfoAndBindingLayout() {
|
||||
Fixture fixture;
|
||||
const auto handle = fixture.Buffer(
|
||||
@@ -1484,6 +1664,9 @@ int main() {
|
||||
Run("DMA address materialization", TestDmaAddressMaterialization);
|
||||
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
|
||||
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
|
||||
Run("conditional buffer materialization", TestConditionalBufferMaterialization);
|
||||
Run("conservative buffer reachability", TestConservativeBufferReachability);
|
||||
Run("conditional indirect image", TestConditionalIndirectImageMaterialization);
|
||||
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
|
||||
Run("image binding ABI", TestImageBindingAbi);
|
||||
Run("graphics push constants", TestGraphicsPushConstantLayout);
|
||||
|
||||
Reference in New Issue
Block a user