mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
shader: respect branch reachability when refreshing scalar resources
This commit is contained in:
@@ -219,6 +219,56 @@ void TestUnbasedFlatCacheHitMaterializes() {
|
||||
"unbased FLAT plan produced unexpected descriptors");
|
||||
}
|
||||
|
||||
void TestWrittenDescriptorUsesStrictReaderOnce() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
Program program;
|
||||
program.stage = Libs::Graphics::ShaderType::Compute;
|
||||
program.srt_plan_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
auto &block = AddValueBlock(program);
|
||||
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress});
|
||||
auto &handle = block.AppendNewInst(ValueOpcode::GetAddressResource,
|
||||
{Value(0x1000u), Value(0u)});
|
||||
auto &offset = block.AppendNewInst(ValueOpcode::GetUserData,
|
||||
{Value(static_cast<ScalarReg>(0))});
|
||||
auto &read = block.AppendNewInst(ValueOpcode::LoadAddressU32,
|
||||
{Value(&handle), Value(&offset), Value(0u), Value(false)});
|
||||
read.SetFlags(MemoryFlags{.index = 0});
|
||||
DescriptorSource source;
|
||||
source.dwords = {Value(&read), Value(0u), Value(4u), Value(0u)};
|
||||
source.dword_count = 4;
|
||||
program.descriptor_sources.push_back(source);
|
||||
program.info.buffers.push_back({.source = 0, .written = true});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
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{
|
||||
.user_data = user_data,
|
||||
.read_memory = +[](void *data, uint64_t, std::span<uint32_t> words) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
words[0] = 0x8000u;
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
++reads.strict;
|
||||
if (!reads.clean || address != 0x1004u) return false;
|
||||
words[0] = 0x8000u;
|
||||
return true;
|
||||
}};
|
||||
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,
|
||||
"writable descriptor was evaluated twice or scalar EXEC suppressed its read");
|
||||
}
|
||||
|
||||
void TestFailedMaterializationRejectsStage() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto plan = UserDataBufferPlan();
|
||||
@@ -262,6 +312,7 @@ int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestIntegerRuntimeValueFollowsSrtReads();
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
TestWrittenDescriptorUsesStrictReaderOnce();
|
||||
TestFailedMaterializationRejectsStage();
|
||||
TestMixedSamplerDuplicatesTheCorrectSnapshot();
|
||||
std::puts("ResourceMaterializationTests: all cases passed");
|
||||
|
||||
@@ -1386,11 +1386,17 @@ void TestUniformizedMaterialImageKeys() {
|
||||
specialization.images[0].indirect_mapping_offset + 3u] == 4096u,
|
||||
"material mask did not limit sparse descriptor reads");
|
||||
user_data[8] = first_material;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"written buffer alias with a material key was accepted");
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{first_material, 4}) !=
|
||||
snapshot.specialization_reads.end(),
|
||||
"material key read was omitted from the renderer write-overlap check");
|
||||
user_data[8] = first_table;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"written buffer alias with an image record was accepted");
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{first_table, 32}) !=
|
||||
snapshot.specialization_reads.end(),
|
||||
"image descriptor read was omitted from the renderer write-overlap check");
|
||||
CheckFatal([&] { make_plan(Variant::WrongUpdate); }, "not a valid runtime value",
|
||||
"non-clearing material mask was accepted");
|
||||
CheckFatal([&] { make_plan(Variant::WrongEquality); }, "not a valid runtime value",
|
||||
@@ -2785,6 +2791,108 @@ void TestConditionalBufferMaterialization() {
|
||||
CheckActive();
|
||||
}
|
||||
|
||||
void TestGuardedScalarDescriptorReads() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
for (const bool shared : {false, true}) {
|
||||
for (const bool exec : {false, true}) {
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
auto *optional = fixture.AddBlock();
|
||||
auto *done = fixture.AddBlock();
|
||||
entry->AddBranch(optional);
|
||||
entry->AddBranch(done);
|
||||
optional->AddBranch(done);
|
||||
fixture.program.block_info[0].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch, .true_block = 1, .false_block = 2};
|
||||
fixture.program.block_info[1].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = 2};
|
||||
fixture.program.block_info[2].terminator.kind = CFG::TerminatorKind::Return;
|
||||
const auto control = fixture.Buffer({fixture.UserData(0), fixture.UserData(1),
|
||||
fixture.UserData(2), fixture.UserData(3)});
|
||||
MemoryInfo scalar;
|
||||
scalar.kind = ResourceKind::ScalarBuffer;
|
||||
scalar.offset = 28;
|
||||
const auto flag = fixture.Emit(ValueOpcode::ReadConstBuffer, {control, Value(0u)},
|
||||
fixture.AddMemory(scalar, 4));
|
||||
const auto lane = fixture.Emit(ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
||||
const auto varying = fixture.Emit(ValueOpcode::INotEqual32, {lane, Value(0u)});
|
||||
const auto enabled = fixture.Emit(ValueOpcode::INotEqual32, {flag, Value(0u)});
|
||||
fixture.program.block_info[0].condition =
|
||||
fixture.Emit(ValueOpcode::LogicalAnd, {varying, enabled});
|
||||
const auto root = fixture.Address(fixture.UserData(4), Value(0u));
|
||||
const auto Load = [&](Block *block) {
|
||||
fixture.block = block;
|
||||
MemoryInfo address;
|
||||
address.kind = ResourceKind::ScalarAddress;
|
||||
const auto pointer = fixture.Emit(ValueOpcode::LoadAddressU32,
|
||||
{root, Value(0u), Value(0u), Value(exec)}, fixture.AddMemory(address, 8));
|
||||
const auto table = fixture.Address(pointer, Value(0u));
|
||||
std::array<Value, 4> words;
|
||||
for (uint32_t word = 0; word < words.size(); ++word) {
|
||||
address.offset = 40 + word * 4;
|
||||
words[word] = fixture.Emit(ValueOpcode::LoadAddressU32,
|
||||
{table, Value(0u), Value(0u), Value(exec)}, fixture.AddMemory(address, 12));
|
||||
}
|
||||
scalar.offset = 0;
|
||||
fixture.Emit(ValueOpcode::ReadConstBuffer, {fixture.Buffer(words), Value(0u)},
|
||||
fixture.AddMemory(scalar, 16));
|
||||
};
|
||||
Load(optional);
|
||||
if (shared) Load(done);
|
||||
fixture.block = done;
|
||||
MemoryInfo store;
|
||||
store.kind = ResourceKind::Buffer;
|
||||
fixture.Emit(ValueOpcode::StoreBufferU32,
|
||||
{fixture.Buffer({Value(0x9000u), Value(0u), Value(4u), Value(0u)}),
|
||||
Value(0u), Value(0u), Value(0u), Value(1u), Value(true)}, fixture.AddMemory(store, 20));
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
Check(!exec || (!plan.srt_reads.empty() && !plan.control_flow[1].srt_reads.empty() &&
|
||||
(!shared || !plan.control_flow[2].srt_reads.empty())),
|
||||
"guarded shared-read fixture lost its flattened execution sites");
|
||||
struct Memory { LinearTestMemory data; uint32_t null_reads = 0; } memory;
|
||||
const auto Read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &memory = *static_cast<Memory *>(data);
|
||||
if (address == 40) ++memory.null_reads;
|
||||
return ReadLinearTestMemory(&memory.data, address, words);
|
||||
};
|
||||
const std::array<uint32_t, 5> user_data{0x1000u, 0u, 64u, 0u, 0x1040u};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = Read,
|
||||
.userdata = &memory, .read_specialization_memory = Read};
|
||||
memory.data.words[0xa8 / 4] = 0x2000u;
|
||||
memory.data.words[0xb0 / 4] = 4u;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
if (!shared) {
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && memory.null_reads == 0 &&
|
||||
std::ranges::all_of(snapshot.flattened_srt, [](auto word) { return word == 0; }),
|
||||
"disabled feature speculatively dereferenced its null BVH table");
|
||||
memory.data.words[7] = 1;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.null_reads == 1,
|
||||
"active feature accepted an unreadable BVH table");
|
||||
}
|
||||
memory.data.words[0x40 / 4] = 0x1080u;
|
||||
memory.data.watched_address = 0x10a8u;
|
||||
memory.data.watched_reads = 0;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
std::ranges::any_of(snapshot.buffers, [](const auto& descriptor) {
|
||||
return descriptor.dwords[0] == 0x2000u;
|
||||
}) &&
|
||||
(!exec || (memory.data.watched_reads == 1 &&
|
||||
std::ranges::find(snapshot.flattened_srt, 0x2000u) != snapshot.flattened_srt.end())),
|
||||
"active or shared descriptor read was omitted after refreshing the cached plan");
|
||||
if (!shared) {
|
||||
memory.data.words[7] = 0;
|
||||
memory.data.words[0x40 / 4] = 0;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) && memory.null_reads == 1 &&
|
||||
std::ranges::all_of(snapshot.flattened_srt, [](auto word) { return word == 0; }),
|
||||
"cached plan retained reads after the feature was disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestConservativeBufferReachability() {
|
||||
std::array<uint32_t, 8> user_data{
|
||||
0x1000, 16u << 16u, 1, 0x4dfac,
|
||||
@@ -2797,11 +2905,14 @@ void TestConservativeBufferReachability() {
|
||||
auto plan = ConditionalBufferPlan(use);
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
std::array<uint32_t, 4> expected{};
|
||||
if (use != ConditionalBufferUse::Writable)
|
||||
std::copy_n(user_data.begin() + 4, expected.size(), expected.begin());
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 2 &&
|
||||
std::equal(user_data.begin() + 4, user_data.end(),
|
||||
std::equal(expected.begin(), expected.end(),
|
||||
snapshot.buffers[1].dwords.begin()),
|
||||
"shared, loop-dependent, or writable-alias resource was pruned");
|
||||
"shared or loop-dependent resource was pruned, or inactive scalar branch retained");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3187,6 +3298,7 @@ int main() {
|
||||
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
|
||||
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
|
||||
Run("conditional buffer materialization", TestConditionalBufferMaterialization);
|
||||
Run("guarded scalar descriptor reads", TestGuardedScalarDescriptorReads);
|
||||
Run("conservative buffer reachability", TestConservativeBufferReachability);
|
||||
Run("conditional indirect image", TestConditionalIndirectImageMaterialization);
|
||||
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
|
||||
|
||||
@@ -1497,6 +1497,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
.shader_base = reinterpret_cast<uint64_t>(test.code.data()),
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = const_cast<std::vector<u32> *>(&test.initial),
|
||||
.read_specialization_memory = ReadTestMemory,
|
||||
};
|
||||
Require(test.name, "resource materialization",
|
||||
ShaderRecompiler::IR::MaterializeResources(
|
||||
@@ -33928,7 +33929,8 @@ void CheckResourcePlanHandoff() {
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime{.user_data = user_data,
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory = ReadTestMemory};
|
||||
Require(name, "initial descriptors",
|
||||
MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers.size() == 1 && snapshot.buffers[0].dwords[0] == 0x1000u,
|
||||
|
||||
@@ -8584,11 +8584,17 @@ void TestNestedSelectionPreservesDescriptorSources() {
|
||||
"nested descriptor selection lost its structured resource bindings");
|
||||
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
||||
const auto pc = result.program.info.buffers[i].first_use_pc;
|
||||
const uint32_t expected = pc == 8u * 4u ? table[0]
|
||||
: pc == 14u * 4u ? table[4]
|
||||
: pc == direct_arm * 4u ? user_data[8]
|
||||
: pc == join * 4u ? user_data[4] : 0u;
|
||||
Check(expected != 0u && result.resources.buffers[i].dwords[0] == expected,
|
||||
Check(pc == 8u * 4u || pc == 14u * 4u || pc == direct_arm * 4u || pc == join * 4u,
|
||||
"nested descriptor selection lost a native source location");
|
||||
const auto selected_pc = (mode == 0u ? 8u : mode == 1u ? 14u : direct_arm) * 4u;
|
||||
const auto *expected = pc == 8u * 4u ? table.data()
|
||||
: pc == 14u * 4u ? table.data() + 4
|
||||
: pc == direct_arm * 4u ? user_data.data() + 8 : user_data.data() + 4;
|
||||
const auto &descriptor = result.resources.buffers[i];
|
||||
const bool unused = pc != selected_pc && pc != join * 4u;
|
||||
Check(descriptor.dword_count == 4 &&
|
||||
(std::equal(expected, expected + 4, descriptor.dwords.begin()) ||
|
||||
(unused && std::ranges::all_of(descriptor.dwords, [](auto word) { return word == 0; }))),
|
||||
"nested descriptor selection chose another arm's descriptor");
|
||||
}
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
@@ -8696,10 +8702,10 @@ void TestNativeScalarReadDescriptorPlanning() {
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u &&
|
||||
std::ranges::equal(result.resources.flattened_srt, table),
|
||||
"scalar descriptor planning retained an unrelated native execution mask");
|
||||
const auto expected = mode == 2u ? table : std::array<uint32_t, 4>{};
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u &&
|
||||
std::ranges::equal(result.resources.flattened_srt, expected),
|
||||
"scalar descriptor planning retained an unrelated native execution mask");
|
||||
Check(std::equal(expected.begin(), expected.end(), result.resources.buffers[0].dwords.begin()),
|
||||
"scalar descriptor planning selected the wrong native resource");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
Reference in New Issue
Block a user