shader: capture only memory-dependent resource predicates

This commit is contained in:
nmzik
2026-10-09 15:04:01 +02:00
parent 488c0acae9
commit 3b1a94707a
4 changed files with 36 additions and 23 deletions
@@ -1007,10 +1007,15 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
// A proven uniform factor can decide a branch even when its other lanes are unknown.
// Keep only that Boolean structure, never the varying shader dependency graph.
Value unknown;
bool capture_control_reads = false;
std::function<Value(Value)> ClonePredicate = [&](Value value) -> Value {
value = value.Resolve();
if (value.IsEmpty()) return {};
if (ValidateRuntimeValue(program, value, RuntimeValueType::Integer)) return Clone(value);
if (ValidateRuntimeValue(program, value, RuntimeValueType::Integer)) {
capture_control_reads |=
!ValidateRuntimeValue(program, value, RuntimeValueType::ImmutableInteger);
return Clone(value);
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) return {};
const auto op = inst->GetOpcode();
@@ -1040,7 +1045,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
}
plan.capture_specialization_reads |= capture_indirect_reads || !plan.control_flow.empty();
plan.capture_specialization_reads |= capture_indirect_reads || capture_control_reads;
if (plan.capture_specialization_reads) {
// Every former flat alias now targets its retained raw read. Mark those reads
// on the normalized graph, so discarded Phi/EXEC dependencies stay discarded.
@@ -966,7 +966,8 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
}
uint32_t condition = 0;
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
if (!block.condition.IsEmpty() && m_runtime.read_specialization_memory != nullptr &&
if (!block.condition.IsEmpty() &&
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
predicate.Evaluate(block.condition, condition)) {
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
} else {
+23 -17
View File
@@ -435,7 +435,7 @@ void TestUnbasedFlatCacheHitMaterializes() {
"unbased FLAT plan produced unexpected descriptors");
}
void TestWrittenDescriptorUsesStrictReaderOnce() {
void TestWrittenDescriptorPredicateReads(bool memory_condition) {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
@@ -456,10 +456,10 @@ void TestWrittenDescriptorUsesStrictReaderOnce() {
source.dword_count = 4;
program.descriptor_sources.push_back(source);
program.info.buffers.push_back({.source = 0, .written = true});
// A host-evaluable branch captures resource reads and needs the writable
// descriptor's clean provenance for the renderer's disjointness proof.
// Only memory-derived host decisions need clean, disjoint descriptor reads.
auto &condition = block.AppendNewInst(ValueOpcode::IEqual32,
{Value(&offset), Value(4u)});
{memory_condition ? Value(&read) : Value(&offset),
Value(memory_condition ? 0x8000u : 4u)});
auto &store_block = AddValueBlock(program);
AddValueBlock(program);
program.blocks[0]->condition = Value(&condition);
@@ -480,8 +480,8 @@ void TestWrittenDescriptorUsesStrictReaderOnce() {
{Value(&output), Value(0u), Value(0u), Value(0u), Value(1u), Value(true)})
.SetFlags(MemoryFlags{.index = 1});
auto plan = ExtractResourcePlan(program);
Check(plan.capture_specialization_reads,
"conditional writable descriptor lost its alias proof");
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;
const std::array<uint32_t, 1> user_data{4u};
const SrtRuntime runtime{
@@ -501,16 +501,21 @@ void TestWrittenDescriptorUsesStrictReaderOnce() {
}};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(!MaterializeResources(plan, runtime, snapshot, specialization) &&
reads.ordinary == 0 && reads.strict == 1,
"GPU-dirty dynamic writable descriptor bypassed strict provenance");
reads.clean = true;
reads.strict = 0;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
reads.ordinary == 0 && reads.strict == 1 && snapshot.buffers[0].dwords[0] == 0x8000u &&
snapshot.specialization_reads ==
std::vector<std::pair<uint64_t, uint64_t>>{{0x1004u, 4u}},
"writable descriptor was evaluated twice or scalar EXEC suppressed its read");
for (const bool clean : {false, true}) {
reads = {.clean = clean};
// A failed predicate explores both edges and the writable descriptor retries its clean read.
const bool expected = !memory_condition || clean;
Check(MaterializeResources(plan, runtime, snapshot, specialization) == expected &&
reads.ordinary == (memory_condition ? 0u : 1u) &&
reads.strict == (memory_condition ? (clean ? 1u : 2u) : 0u),
"writable descriptor changed reader policy or bypassed a dirty memory predicate");
if (!expected) continue;
Check(snapshot.buffers[0].dwords[0] == 0x8000u &&
snapshot.specialization_reads == (memory_condition
? std::vector<std::pair<uint64_t, uint64_t>>{{0x1004u, 4u}}
: std::vector<std::pair<uint64_t, uint64_t>>{}),
"writable descriptor lost its address or captured an immutable predicate");
}
}
void TestFailedMaterializationRejectsStage() {
@@ -671,7 +676,8 @@ int main() {
TestUniformVectorDescriptorRead();
TestExactReciprocalDescriptorArithmetic();
TestUnbasedFlatCacheHitMaterializes();
TestWrittenDescriptorUsesStrictReaderOnce();
for (const bool memory_condition : {false, true})
TestWrittenDescriptorPredicateReads(memory_condition);
TestFailedMaterializationRejectsStage();
TestFiniteImageRefreshReusesScalarReads();
TestMixedSamplerVariantsShareRuntimeDescriptor();
+4 -3
View File
@@ -13779,7 +13779,7 @@ void TestUniformSelectedDescriptorLoadAddress() {
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
auto plan = ExtractResourcePlan(translated.program);
Check(plan.srt_reads.size() == 4u && plan.info.buffers.size() == 1u &&
plan.info.buffers[0].written,
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) {
const auto flag = *static_cast<uint32_t *>(data);
@@ -13792,13 +13792,14 @@ void TestUniformSelectedDescriptorLoadAddress() {
return true;
};
const SrtRuntime runtime{.user_data = user_data, .read_memory = read,
.userdata = &user_data[0], .read_specialization_memory = read};
.userdata = &user_data[0]};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
for (const uint32_t flag : {0u, 1u, 0u}) {
user_data[0] = flag;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.buffers[0].dwords[0] == 0x3000u + flag * 0x1000u,
snapshot.buffers[0].dwords[0] == 0x3000u + flag * 0x1000u &&
snapshot.specialization_reads.empty(),
"conditional descriptor load read the untaken pointer");
}
}