shader: synchronize GPU-produced direct writable descriptors

Co-authored-by: TheCruZ <thecruzwtf@gmail.com>
This commit is contained in:
nmzik 2026-10-04 01:49:30 +04:00
parent e4aca7a60b
commit ec3e48cdde
3 changed files with 111 additions and 10 deletions

View File

@ -975,12 +975,14 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
}
}
plan.capture_specialization_reads = capture_indirect_reads || !plan.control_flow.empty();
// Writable descriptor addresses must come from clean backing for the alias proof.
for (const auto& buffer: plan.info.buffers) {
if (buffer.written) MarkCleanFlatSlots(plan, Source(plan, buffer.source), plan.clean_flat_slots);
}
for (const auto& image: plan.info.images) {
if (image.written) MarkCleanFlatSlots(plan, Source(plan, image.source), plan.clean_flat_slots);
if (plan.capture_specialization_reads) {
// Clean writable addresses prove that shader writes cannot overlap captured reads.
for (const auto& buffer: plan.info.buffers) {
if (buffer.written) MarkCleanFlatSlots(plan, Source(plan, buffer.source), plan.clean_flat_slots);
}
for (const auto& image: plan.info.images) {
if (image.written) MarkCleanFlatSlots(plan, Source(plan, image.source), plan.clean_flat_slots);
}
}
if (capture_indirect_reads) plan.resource_tracking_complete &= !program.has_address_writes;
return plan;
@ -1004,7 +1006,8 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
observed.read_memory = CaptureOrdinaryRead;
}
SrtWalker clean(program, CleanRuntime(observed));
SrtWalker walker(program, observed, program.clean_flat_slots, &clean);
SrtWalker walker(program, observed, program.clean_flat_slots,
capture_reads || program.requires_specialization_memory ? &clean : nullptr);
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
return false;
}
@ -1026,7 +1029,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return false;
}
if (active.empty() || active[source]) {
return (written ? clean : walker).EvaluateDescriptor(source, value);
return (capture_reads && written ? clean : walker).EvaluateDescriptor(source, value);
}
value = {};
value.dword_count = program.descriptor_sources[source].dword_count;

View File

@ -227,6 +227,7 @@ void TestWrittenDescriptorUsesStrictReaderOnce() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.user_data_count = 1;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &block = AddValueBlock(program);
@ -243,7 +244,32 @@ 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.
auto &condition = block.AppendNewInst(ValueOpcode::IEqual32,
{Value(&offset), Value(4u)});
auto &store_block = AddValueBlock(program);
AddValueBlock(program);
program.block_info[0].condition = Value(&condition);
program.block_info[0].terminator.kind =
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
program.block_info[0].terminator.true_block = 1;
program.block_info[0].terminator.false_block = 2;
program.block_info[1].id = 1;
program.block_info[1].terminator.kind =
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
program.block_info[2].id = 2;
program.block_info[2].terminator.kind =
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
program.memory_info.push_back({.kind = ResourceKind::Buffer, .resource = 0});
auto &output = store_block.AppendNewInst(ValueOpcode::GetBufferResource,
{source.dwords[0], source.dwords[1], source.dwords[2], source.dwords[3]});
store_block.AppendNewInst(ValueOpcode::StoreBufferU32,
{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");
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{
@ -269,7 +295,9 @@ void TestWrittenDescriptorUsesStrictReaderOnce() {
reads.clean = true;
reads.strict = 0;
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
reads.ordinary == 0 && reads.strict == 1 && snapshot.buffers[0].dwords[0] == 0x8000u,
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");
}
@ -313,7 +341,7 @@ void TestFiniteImageRefreshReusesScalarReads() {
DescriptorSource root;
root.dword_count = 8;
root.dwords.fill(Value(0u));
root.indirect_image.emplace(DescriptorSource::IndirectImage{}).sources = {0, 1, 2, 1};
root.indirect_descriptor.emplace(DescriptorSource::IndirectDescriptor{}).sources = {0, 1, 2, 1};
program.descriptor_sources.push_back(root);
program.info.images.push_back({
.source = 3,

View File

@ -13174,6 +13174,75 @@ void TestComputeImageFill() {
Run(mutation);
}
void TestGpuProducedWritableDescriptor() {
using namespace ShaderRecompiler::IR;
// Reduced from 994af59d50bedca8: the GPU-built BVH header supplies a relative
// output address and record count. CMPX changes EXEC without clobbering VCC.
const uint32_t shader[] = {
0xf4041a81u, 0xfa000028u, // s_load_dwordx2 vcc, s[2:3], 0x28
0xf4000281u, 0xfa000074u, // s_load_dword s10, s[2:3], 0x74
0xbe8b03ffu, 0x00016204u, // s_mov_b32 s11, 0x16204
0xbf8cc07fu, // s_waitcnt lgkmcnt(0)
0x8008026au, // s_add_u32 s8, vcc_lo, s2
EncodeVopc(0xd5, 129, 0), // v_cmpx_ne_u32 1, v0
0x8209036bu, // s_addc_u32 s9, vcc_hi, s3
0xbe891d97u, // s_bitset1_b32 s9, 23 (stride = 128)
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 0),
EncodeSopp(0x01),
};
const std::array<uint32_t, 4> user_data{0u, 0u, 0x00315000u, 3u};
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data;
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
auto plan = ExtractResourcePlan(translated.program);
Check(plan.info.buffers.size() == 1 && plan.info.buffers[0].written &&
!plan.capture_specialization_reads && plan.srt_reads.size() == 3,
"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;
} 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;
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;
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.
}};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
const auto check = [&](uint32_t low, uint32_t high, uint32_t count, uint32_t reads_expected) {
Check(MaterializeResources(plan, runtime, snapshot, specialization),
"GPU-produced direct writable descriptor was rejected");
Check(snapshot.buffers.size() == 1 &&
snapshot.buffers[0].dwords[0] == low &&
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,
"GPU-produced descriptor lost current scalar data or repeated its reads");
};
check(0x003176c0u, 0x00800003u, 151u, 3u);
const auto previous_specialization = specialization;
reads.header = {0xffd00000u, 0u, 302u}; // Exercise the low-address carry on refresh.
check(0x00015000u, 0x00800004u, 302u, 6u);
Check(specialization == previous_specialization,
"GPU-updated address or record count created a shader permutation");
}
void TestTypedDescriptorRealCarryAndScalarLoads() {
const uint32_t carry_shader[] = {
EncodeSop1(0x1f, 0, 0), // s_getpc_b64 s[0:1]
@ -14590,6 +14659,7 @@ int main() {
TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled();
TestTypedDescriptorRealWideMoveTranslation();
TestComputeImageFill();
TestGpuProducedWritableDescriptor();
TestTypedDescriptorRealCarryAndScalarLoads();
TestSrtWalkerRealSmemTranslation();
TestSrtWalkerVccBaseTranslation();