mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 04:46:56 +02:00
Lower finite GPU-selected image descriptors through shared indirect bindings
This commit is contained in:
@@ -278,6 +278,101 @@ void TestFailedMaterializationRejectsStage() {
|
||||
"missing runtime user data did not reject the cached stage");
|
||||
}
|
||||
|
||||
void TestFiniteImageRefreshReusesScalarReads() {
|
||||
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 &srt = block.AppendNewInst(ValueOpcode::GetSrtResource);
|
||||
for (uint32_t index = 0; index < 3; ++index) {
|
||||
auto &read = block.AppendNewInst(ValueOpcode::LoadAddressU32,
|
||||
{Value(&handle), Value(index * 4u), Value(0u), Value(true)});
|
||||
read.SetFlags(MemoryFlags{.index = 0});
|
||||
program.srt_reads.push_back({Value(&read), index});
|
||||
auto &flat = block.AppendNewInst(ValueOpcode::ReadConst,
|
||||
{Value(&srt), Value(index)});
|
||||
DescriptorSource source;
|
||||
source.dword_count = 8;
|
||||
source.dwords.fill(Value(0u));
|
||||
source.dwords[0] = Value(&flat);
|
||||
source.dwords[1] = Value(static_cast<uint32_t>(
|
||||
Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u);
|
||||
source.dwords[3] = Value(Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u));
|
||||
program.descriptor_sources.push_back(source);
|
||||
}
|
||||
DescriptorSource root;
|
||||
root.dword_count = 8;
|
||||
root.dwords.fill(Value(0u));
|
||||
root.indirect_image.emplace().sources = {0, 1, 2, 1};
|
||||
program.descriptor_sources.push_back(root);
|
||||
program.info.images.push_back({
|
||||
.source = 3,
|
||||
.resource_class = ImageResourceClass::Sampled,
|
||||
.numeric_class = Libs::Graphics::Prospero::TextureNumericClass::Float,
|
||||
.dimension = Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
struct Reads {
|
||||
std::array<uint32_t, 3> words{0x100u, 0x200u, 0x200u};
|
||||
std::array<uint32_t, 3> counts{};
|
||||
uint32_t ordinary = 0;
|
||||
} reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
|
||||
++static_cast<Reads *>(data)->ordinary;
|
||||
return false;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address,
|
||||
std::span<uint32_t> words) {
|
||||
if (words.size() != 1 || address < 0x1000u || address >= 0x100cu ||
|
||||
(address & 3u) != 0) return false;
|
||||
auto &reads = *static_cast<Reads *>(data);
|
||||
const auto index = (address - 0x1000u) / 4u;
|
||||
++reads.counts[index];
|
||||
words[0] = reads.words[index];
|
||||
return true;
|
||||
}};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const auto capacities = [&] {
|
||||
return std::array{snapshot.images.capacity(), snapshot.flattened_srt.capacity(),
|
||||
snapshot.specialization_reads.capacity(), specialization.images.capacity()};
|
||||
};
|
||||
const auto check_mapping = [&](std::array<uint32_t, 4> ordinals) {
|
||||
const auto offset = specialization.images[0].indirect_mapping_offset;
|
||||
Check(snapshot.images.size() == 2 && snapshot.flattened_srt[offset] == 4,
|
||||
"finite image candidates were not deduplicated");
|
||||
for (uint32_t key = 0; key < ordinals.size(); ++key) {
|
||||
Check(snapshot.flattened_srt[offset + 1u + key * 2u] == key &&
|
||||
snapshot.flattened_srt[offset + 2u + key * 2u] == ordinals[key],
|
||||
"finite image selector mapping is stale or incorrect");
|
||||
}
|
||||
};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"finite image materialization failed");
|
||||
Check(reads.ordinary == 0 && reads.counts == std::array<uint32_t, 3>{1, 1, 1} &&
|
||||
snapshot.specialization_reads.size() == 3,
|
||||
"finite image candidates repeated scalar reads or bypassed clean provenance");
|
||||
check_mapping({0, 1, 1, 1});
|
||||
const auto warm_capacities = capacities();
|
||||
reads.words = {0x300u, 0x300u, 0x400u};
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"finite image refresh failed after descriptor changes");
|
||||
Check(reads.ordinary == 0 && reads.counts == std::array<uint32_t, 3>{2, 2, 2} &&
|
||||
snapshot.specialization_reads.size() == 3 &&
|
||||
snapshot.images[0].dwords[0] == 0x300u && snapshot.images[1].dwords[0] == 0x400u,
|
||||
"finite image refresh retained old scalar values or repeated reads");
|
||||
check_mapping({0, 0, 1, 0});
|
||||
Check(capacities() == warm_capacities,
|
||||
"finite image refresh grew reusable resource storage after warmup");
|
||||
}
|
||||
|
||||
void TestMixedSamplerDuplicatesTheCorrectSnapshot() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto plan = MixedSamplerPlan();
|
||||
@@ -314,6 +409,7 @@ int main() {
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
TestWrittenDescriptorUsesStrictReaderOnce();
|
||||
TestFailedMaterializationRejectsStage();
|
||||
TestFiniteImageRefreshReusesScalarReads();
|
||||
TestMixedSamplerDuplicatesTheCorrectSnapshot();
|
||||
std::puts("ResourceMaterializationTests: all cases passed");
|
||||
return 0;
|
||||
|
||||
@@ -2488,6 +2488,132 @@ void TestConditionalSamplerPhi() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestFiniteImagePhiCycle() {
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
auto *header = fixture.AddBlock();
|
||||
auto *reload = fixture.AddBlock();
|
||||
auto *merge = fixture.AddBlock();
|
||||
entry->AddBranch(header);
|
||||
header->AddBranch(reload);
|
||||
header->AddBranch(merge);
|
||||
reload->AddBranch(merge);
|
||||
merge->AddBranch(header);
|
||||
std::array<Value, 8> words;
|
||||
for (uint32_t word = 0; word < words.size(); ++word) {
|
||||
auto &loop = header->AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32));
|
||||
auto &choice = merge->AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32));
|
||||
loop.AddPhiOperand(entry, fixture.UserData(word));
|
||||
loop.AddPhiOperand(merge, Value(&choice));
|
||||
// Incoming order is independent for each descriptor DWORD.
|
||||
if (word & 1u) {
|
||||
choice.AddPhiOperand(reload, fixture.UserData(word + 8));
|
||||
choice.AddPhiOperand(header, Value(&loop));
|
||||
} else {
|
||||
choice.AddPhiOperand(header, Value(&loop));
|
||||
choice.AddPhiOperand(reload, fixture.UserData(word + 8));
|
||||
}
|
||||
words[word] = Value(&choice);
|
||||
}
|
||||
fixture.block = merge;
|
||||
const auto image = fixture.Image(words);
|
||||
const auto sampler = fixture.Sampler({Value(0u), Value(0u), Value(0u), Value(0u)});
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()},
|
||||
fixture.AddMemory(memory, 4));
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_image;
|
||||
Check(finite && finite->sources.size() == 2,
|
||||
"cyclic image selection did not retain two complete descriptor candidates");
|
||||
const auto *key = image.Instruction()->Arg(0).Instruction();
|
||||
Check(key->GetOpcode() == ValueOpcode::Phi && key->Parent() == merge,
|
||||
"finite image key lost its original merge block");
|
||||
const auto *loop = key->Arg(0).Instruction();
|
||||
Check(loop->GetOpcode() == ValueOpcode::Phi && loop->Parent() == header &&
|
||||
loop->Arg(1).Resolve() == Value(const_cast<Inst *>(key)) &&
|
||||
loop->Arg(0).U32() == 0 && key->Arg(1).U32() == 1,
|
||||
"finite image key did not preserve the loop-carried selection");
|
||||
std::array<uint32_t, 16> data;
|
||||
for (uint32_t i = 0; i < data.size(); ++i) data[i] = 100u + i;
|
||||
SrtWalker walker(fixture.program, SrtRuntime{.user_data = data});
|
||||
for (uint32_t candidate = 0; candidate < 2; ++candidate) {
|
||||
DescriptorValue descriptor;
|
||||
Check(walker.EvaluateDescriptor(finite->sources[candidate], descriptor),
|
||||
"finite image candidate is not host-readable");
|
||||
for (uint32_t word = 0; word < 8; ++word)
|
||||
Check(descriptor.dwords[word] == data[candidate * 8 + word],
|
||||
"finite image selection mixed descriptor DWORDs across predecessors");
|
||||
}
|
||||
}
|
||||
|
||||
void TestFiniteImageBitScanSentinel() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
for (const bool nonzero : {false, true}) {
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
auto *dispatch = fixture.AddBlock();
|
||||
auto *load = fixture.AddBlock();
|
||||
auto *merge = fixture.AddBlock();
|
||||
auto *exit = fixture.AddBlock();
|
||||
entry->AddBranch(dispatch);
|
||||
entry->AddBranch(exit);
|
||||
dispatch->AddBranch(load);
|
||||
dispatch->AddBranch(merge);
|
||||
load->AddBranch(merge);
|
||||
const auto mask = fixture.Emit(ValueOpcode::GetBuiltin,
|
||||
{Value(uint32_t(StageInputKind::LocalInvocationIndex)), Value(0u)});
|
||||
const auto condition = fixture.Emit(nonzero ? ValueOpcode::INotEqual32 : ValueOpcode::IEqual32,
|
||||
{mask, Value(0u)});
|
||||
auto &guard = fixture.program.block_info[0];
|
||||
guard.condition = condition;
|
||||
guard.terminator.kind = CFG::TerminatorKind::ConditionalBranch;
|
||||
guard.terminator.true_block = 1;
|
||||
guard.terminator.false_block = 4;
|
||||
fixture.block = dispatch;
|
||||
const auto first = fixture.Emit(ValueOpcode::FindILsb32, {mask});
|
||||
const auto minimum = fixture.Emit(ValueOpcode::UMin32, {first, Value(32u)});
|
||||
const auto selector = fixture.Emit(ValueOpcode::ShiftLeftLogical32, {minimum, Value(2u)});
|
||||
fixture.Emit(ValueOpcode::ReferenceU32, {selector});
|
||||
auto &table = fixture.program.block_info[1];
|
||||
table.indirect_target = selector;
|
||||
table.terminator.kind = CFG::TerminatorKind::IndirectBranch;
|
||||
table.terminator.indirect_selector_code = 0;
|
||||
table.terminator.indirect_selector_values = {0u, 128u};
|
||||
table.terminator.indirect_selector_targets = {2u, 3u};
|
||||
std::array<Value, 8> words;
|
||||
for (uint32_t word = 0; word < words.size(); ++word) {
|
||||
auto &phi = merge->AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32));
|
||||
phi.AddPhiOperand(dispatch, mask);
|
||||
phi.AddPhiOperand(load, Value(100u + word));
|
||||
words[word] = Value(&phi);
|
||||
}
|
||||
fixture.block = merge;
|
||||
const auto image = fixture.Image(words);
|
||||
const auto sampler = fixture.Sampler({Value(0u), Value(0u), Value(0u), Value(0u)});
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()},
|
||||
fixture.AddMemory(memory, 4));
|
||||
if (!nonzero) {
|
||||
CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value",
|
||||
"zero bit scan silently accepted a GPU-valued descriptor sentinel");
|
||||
Check(fixture.program.descriptor_sources.empty() && image.Instruction()->Arg(0) == words[0],
|
||||
"failed finite descriptor analysis partially mutated the resource plan");
|
||||
continue;
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
const auto &finite = fixture.program.descriptor_sources[source].indirect_image;
|
||||
Check(finite && finite->sources.size() == 1 &&
|
||||
image.Instruction()->Arg(0).Instruction()->NumPhiBlocks() == 2,
|
||||
"nonzero bit scan retained its impossible sentinel or removed a Phi edge");
|
||||
}
|
||||
}
|
||||
|
||||
void TestLoopCycleEnteredThroughRuntimeValue() {
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
@@ -3292,6 +3418,8 @@ int main() {
|
||||
Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit);
|
||||
Run("phi validation", TestPhiValidation);
|
||||
Run("conditional sampler phi", TestConditionalSamplerPhi);
|
||||
Run("finite image phi cycle", TestFiniteImagePhiCycle);
|
||||
Run("finite image bit scan sentinel", TestFiniteImageBitScanSentinel);
|
||||
Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue);
|
||||
Run("invariant loop phi", TestInvariantLoopPhi);
|
||||
Run("DMA address materialization", TestDmaAddressMaterialization);
|
||||
|
||||
Reference in New Issue
Block a user