mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
shader: capture only memory-dependent resource predicates
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user