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:
nmzik
2026-09-05 09:38:50 +04:00
parent b92f0a5458
commit 59902d1fd8
5 changed files with 362 additions and 22 deletions
+10 -2
View File
@@ -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);