shader: resolve descriptors before resource planning

This commit is contained in:
nmzik
2026-09-28 04:53:14 +04:00
parent 06f73f07d8
commit 720a0b5bbf
9 changed files with 376 additions and 278 deletions
+65 -25
View File
@@ -1,6 +1,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
@@ -126,7 +127,27 @@ struct Fixture {
}
void PlanAndTrack() {
BuildSrtPlan(program);
for (size_t index = 0; index < program.block_info.size(); ++index) {
const auto condition = program.block_info[index].condition;
if (!condition.IsEmpty())
Emit(ValueOpcode::Reference, {condition}, 0, program.blocks[index]);
}
for (auto *target : program.blocks) {
for (auto &inst : *target) {
if (inst.HasUses() || inst.MayHaveSideEffects() ||
(BufferAccessOf(inst.GetOpcode()) == BufferAccess::None &&
AddressOpcodeInfoOf(inst.GetOpcode()).access == AddressAccess::None &&
ImageOpcodeInfoOf(inst.GetOpcode()).access == ImageAccess::None))
continue;
auto value = Value(&inst);
if (value.GetType() == Type::U32x4)
value = Emit(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}, 0, target);
if (value.GetType() == Type::U8)
value = Emit(ValueOpcode::ConvertU32U8, {value}, 0, target);
Check(value.GetType() == Type::U32, "unhandled memory result type in fixture");
Emit(ValueOpcode::ReferenceU32, {value}, 0, target);
}
}
TrackResources(program, {}, {});
}
};
@@ -277,7 +298,9 @@ void TestInvariantIndirectImageMaterialization() {
Check(fixture->program.info.buffers.size() == 1 &&
fixture->program.info.images.size() == 1 &&
fixture->program.dynamic_reads.size() == 1,
std::ranges::any_of(*fixture->block, [](const Inst &inst) {
return inst.GetOpcode() == ValueOpcode::ReadConstBuffer;
}),
"indirect image key was not retained as a scalar-buffer read");
const auto source = fixture->program.info.images[0].source;
Check(source < fixture->program.descriptor_sources.size() &&
@@ -519,8 +542,8 @@ void TestInvariantIndirectImageMaterialization() {
"a same-shape refresh discarded the runtime output storage");
auto malformed = MakeIndirectImageFixture(true);
BuildSrtPlan(malformed->program);
CheckFatal([&] { TrackResources(malformed->program, {}, {}); }, "not a valid runtime value",
CheckFatal([&] { malformed->PlanAndTrack(); }, "not a valid runtime value",
"malformed indirect image pattern was accepted");
Check(!malformed->program.resource_tracking_complete &&
malformed->program.info.images.empty() &&
@@ -528,8 +551,8 @@ void TestInvariantIndirectImageMaterialization() {
"malformed indirect image pattern was partially accepted");
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
BuildSrtPlan(wrapped_immediate->program);
CheckFatal([&] { TrackResources(wrapped_immediate->program, {}, {}); },
CheckFatal([&] { wrapped_immediate->PlanAndTrack(); },
"not a valid runtime value",
"wrapped scalar immediate entered the invariant image proof");
Check(!wrapped_immediate->program.resource_tracking_complete,
@@ -1405,7 +1428,7 @@ void TestDenseBufferTracking() {
fixture.program.memory_info[other_flags.index].resource == 1,
"typed memory metadata was not patched to dense indices");
CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "already tracked",
CheckFatal([&] { fixture.PlanAndTrack(); }, "already tracked",
"resource tracking allowed a second mutation pass");
}
@@ -1601,8 +1624,8 @@ void TestSampleAdjustSamplerScratch() {
rejected.Emit(ValueOpcode::ImageSampleRaw,
{rejected_image, rejected_sampler, rejected.ImageAddress()},
rejected.AddMemory(rejected_memory, 0x200));
BuildSrtPlan(rejected.program);
CheckFatal([&] { TrackResources(rejected.program, {}, {}); },
CheckFatal([&] { rejected.PlanAndTrack(); },
"not a valid runtime value", message);
};
CheckRejected(0u, 12u,
@@ -1936,7 +1959,8 @@ void TestDynamicSrtReadRemainsExplicit() {
fixture.PlanAndTrack();
Check(fixture.program.srt_reads.empty() &&
fixture.program.dynamic_reads.size() == 1 &&
read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 &&
!fixture.program.memory_info[0].planning_only &&
fixture.program.info.uses_dma,
"dynamic scalar read was incorrectly flattened or lost");
std::array<uint32_t, 3> user_data{0x1000u, 0u, 4u};
@@ -1990,8 +2014,7 @@ void TestPhiValidation() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, 20), merge);
BuildSrtPlan(fixture.program);
CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "not a valid runtime value",
CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value",
"control-dependent descriptor phi was accepted");
Check(!fixture.program.resource_tracking_complete &&
fixture.program.info.buffers.empty() &&
@@ -2092,7 +2115,9 @@ ResourcePlan ConditionalSamplerPlan(bool diamond, bool reverse, bool reverse_phi
{result, Value(0u)})});
}
fixture.PlanAndTrack();
Check(fixture.program.value_storage.size() == 4,
Check(std::ranges::count_if(fixture.program.value_storage, [](const Inst &inst) {
return inst.GetOpcode() == ValueOpcode::SelectU32;
}) == 4,
"repeated sampler uses retained duplicate planning selections");
Check(sampler_words[0].ResolveInstruction()->GetOpcode() == ValueOpcode::Phi,
"host descriptor selection changed the GPU Phi");
@@ -2168,11 +2193,24 @@ void TestLoopCycleEnteredThroughRuntimeValue() {
{Value(&phi), Value(0xffffffffu)}, 0, loop);
phi.AddPhiOperand(entry, initial);
phi.AddPhiOperand(loop, carried);
fixture.Emit(ValueOpcode::GetBufferResource,
const auto handle = fixture.Emit(ValueOpcode::GetBufferResource,
{carried, Value(0u), Value(0u), Value(0u)}, MemoryFlags{0, 12},
loop);
BuildSrtPlan(fixture.program);
MemoryInfo memory;
memory.kind = ResourceKind::Buffer;
fixture.Emit(ValueOpcode::LoadBufferU32,
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, 16), loop);
ConstantPropagationPass(fixture.program.blocks);
RemoveIdentities(fixture.program.blocks);
fixture.PlanAndTrack();
const std::array<uint32_t, 1> user_data{0x4000u};
DescriptorValue descriptor;
Check(fixture.program.info.buffers.size() == 1u &&
SrtWalker(fixture.program, {.user_data = user_data}).EvaluateDescriptor(
fixture.program.info.buffers[0].source, descriptor) &&
descriptor.dwords[0] == user_data[0],
"runtime-rooted invariant loop lost its buffer source");
}
void TestInvariantLoopPhi() {
@@ -2516,14 +2554,15 @@ void TestShaderInfoAndBindingLayout() {
fixture.Emit(ValueOpcode::LoadBufferU32,
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(buffer, 4));
fixture.Emit(
const auto invocation = fixture.Emit(
ValueOpcode::GetBuiltin,
{Value(static_cast<uint32_t>(StageInputKind::GlobalInvocationId)),
Value(2u)});
fixture.Emit(ValueOpcode::BitwiseXor32, {Value(1u), Value(2u)});
const auto value = fixture.Emit(ValueOpcode::BitwiseXor32,
{invocation, Value(2u)});
MemoryInfo gds;
gds.kind = ResourceKind::Gds;
fixture.Emit(ValueOpcode::WriteSharedU32, {Value(0u), Value(1u), Value(true)},
fixture.Emit(ValueOpcode::WriteSharedU32, {Value(0u), value, Value(true)},
fixture.AddMemory(gds, 8));
fixture.PlanAndTrack();
@@ -2767,8 +2806,8 @@ void TestResourceLimitIsTransactional() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, index * 4u));
}
BuildSrtPlan(fixture.program);
CheckFatal([&] { TrackResources(fixture.program, {}, {}); },
CheckFatal([&] { fixture.PlanAndTrack(); },
"buffer resource limit exceeded",
"resource-limit failure was not reported");
Check(!fixture.program.resource_tracking_complete &&
@@ -2786,9 +2825,9 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::StoreAddressU32,
{address, Value(0u), Value(0u), Value(1u), Value(true)},
fixture.AddMemory(memory, 4));
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program, {}, {}); },
[&] { fixture.PlanAndTrack(); },
"address operation has invalid resource kind",
"resource tracking accepted an address opcode with buffer metadata");
}
@@ -2803,9 +2842,9 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::ImageRead,
{image, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(memory, 8));
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program, {}, {}); },
[&] { fixture.PlanAndTrack(); },
"image operation has invalid resource kind",
"resource tracking accepted an image opcode with address metadata");
}
@@ -2896,3 +2935,4 @@ void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); }
#include "graphics/shader/recompiler/ir/Value.cpp"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp"
+30 -25
View File
@@ -2,6 +2,7 @@
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include <cstdlib>
@@ -71,7 +72,7 @@ struct Fixture {
return static_cast<uint32_t>(program.memory_info.size() - 1u);
}
void Plan() { BuildSrtPlan(program); }
void Plan() { TrackResources(program, {}, {}); }
};
struct TestMemory {
@@ -102,6 +103,16 @@ Value RawRead(Fixture &fixture, Value address, Value offset, uint32_t memory,
0x80, block);
}
void LoadBuffer(Fixture &fixture, std::array<Value, 4> words) {
const auto handle = fixture.Emit(ValueOpcode::GetBufferResource,
{words[0], words[1], words[2], words[3]});
const auto loaded = fixture.EmitMemory(
ValueOpcode::LoadBufferU32,
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(ResourceKind::Buffer));
fixture.Emit(ValueOpcode::ReferenceU32, {loaded});
}
void TestImmediateFlatteningAndGvn() {
Fixture fixture;
const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 0x20);
@@ -109,14 +120,11 @@ void TestImmediateFlatteningAndGvn() {
fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory);
const auto second = RawRead(
fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory);
fixture.Emit(ValueOpcode::GetBufferResource,
{first, second, Value(16u), Value(0u)});
LoadBuffer(fixture, {first, second, Value(16u), Value(0u)});
fixture.Plan();
Check(fixture.program.srt_reads.size() == 1,
"equivalent typed scalar reads were not coalesced");
Check(fixture.program.dynamic_reads.empty(),
"immediate scalar read was classified as dynamic");
Check(fixture.program.memory_info[memory].planning_only,
"flattened raw read was not kept as a planning-only root");
@@ -133,8 +141,7 @@ void TestRawScalarComponentAlignment() {
const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress, 2);
const auto read = RawRead(
fixture, Address(fixture, Value(0x1003u), Value(0u)), Value(2u), memory);
fixture.Emit(ValueOpcode::GetBufferResource,
{read, Value(0u), Value(16u), Value(0u)});
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
fixture.Plan();
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
@@ -150,7 +157,7 @@ void TestScalarMemoryDomainMismatchFails() {
Fixture raw;
const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer);
RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), raw_memory);
CheckFatal([&] { BuildSrtPlan(raw.program); },
CheckFatal([&] { TrackResources(raw.program, {}, {}); },
"incompatible scalar memory metadata",
"raw scalar load accepted descriptor-buffer metadata");
@@ -161,7 +168,7 @@ void TestScalarMemoryDomainMismatchFails() {
{Value(0x1000u), Value(0u), Value(16u), Value(0u)});
buffer.EmitMemory(ValueOpcode::ReadConstBuffer, {resource, Value(0u)},
buffer_memory);
CheckFatal([&] { BuildSrtPlan(buffer.program); },
CheckFatal([&] { TrackResources(buffer.program, {}, {}); },
"incompatible scalar memory metadata",
"descriptor scalar load accepted raw-address metadata");
}
@@ -173,12 +180,12 @@ void TestDynamicReadRemainsTyped() {
{Value(static_cast<ScalarReg>(2))});
const auto read = RawRead(
fixture, Address(fixture, Value(0x1000u), Value(0u)), offset, memory);
fixture.Emit(ValueOpcode::GetBufferResource,
{read, Value(0u), Value(16u), Value(0u)});
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
fixture.Plan();
Check(fixture.program.srt_reads.empty() &&
fixture.program.dynamic_reads == std::vector<Value>{read},
read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 &&
!fixture.program.memory_info[memory].planning_only,
"dynamic scalar read received a fake flattened slot");
}
@@ -189,8 +196,7 @@ void TestNestedSrtWalk() {
fixture, Address(fixture, Value(0x1000u), Value(0u)), Value(0u), memory);
const auto value =
RawRead(fixture, Address(fixture, pointer, Value(0u)), Value(0u), memory);
fixture.Emit(ValueOpcode::GetBufferResource,
{value, Value(0u), Value(16u), Value(0u)});
LoadBuffer(fixture, {value, Value(0u), Value(16u), Value(0u)});
fixture.Plan();
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
@@ -211,7 +217,7 @@ void TestShaderBaseAndUserData() {
const auto user = fixture.Emit(ValueOpcode::GetUserData,
{Value(static_cast<ScalarReg>(2))});
const auto sum = fixture.Emit(ValueOpcode::IAdd32, {user, Value(4u)});
fixture.Plan();
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high, sum}, .dword_count = 3});
@@ -239,7 +245,7 @@ void TestCarryAndBitFields() {
{Value(0u), Value(0x89abcdefu), Value(0u), Value(32u)});
const auto sign = fixture.Emit(ValueOpcode::BitFieldSExtract,
{Value(0x000000f0u), Value(4u), Value(4u)});
fixture.Plan();
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
@@ -261,7 +267,7 @@ void TestInvariantAndDivergentPhi() {
divergent.SetFlags(Type::U32);
divergent.AddPhiOperand(&fixture.BlockAt(0), Value(7u));
divergent.AddPhiOperand(&fixture.BlockAt(1), Value(9u));
fixture.Plan();
fixture.program.descriptor_sources.push_back(
{.dwords = {Value(&invariant)}, .dword_count = 1});
fixture.program.descriptor_sources.push_back(
@@ -285,6 +291,7 @@ void TestControlDependentStandaloneLoadStaysTyped() {
const auto read =
RawRead(fixture, Address(fixture, Value(&base), Value(0u), 2), Value(0u),
memory, 2);
fixture.Emit(ValueOpcode::ReferenceU32, {read}, 0, 2);
fixture.Plan();
Check(fixture.program.srt_reads.empty() &&
read.ResolveInstruction()->GetOpcode() ==
@@ -307,7 +314,7 @@ void TestRuntime64BitDescriptorOps() {
fixture.Emit(ValueOpcode::CompositeExtractU64, {combined, Value(0u)});
const auto high =
fixture.Emit(ValueOpcode::CompositeExtractU64, {combined, Value(1u)});
fixture.Plan();
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high}, .dword_count = 2});
@@ -374,7 +381,6 @@ void TestUniformFirstLaneSamplerLod() {
{packed, active});
const auto divergent = fixture.Emit(
ValueOpcode::ReadFirstLane, {stale, active});
fixture.Plan();
Check(ValidateRuntimeValue(fixture.program, first),
"uniform sampler LOD construction was rejected");
@@ -425,7 +431,7 @@ void TestFloatComparisonDescriptorInputs() {
{greater_equal, Value(1u), Value(0u)});
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high}, .dword_count = 2});
fixture.Plan();
for (size_t index = 0; index < inputs.size(); index++) {
const auto &input = inputs[index];
const std::array user_data{input.bits};
@@ -489,8 +495,7 @@ void TestConstantBufferBounds() {
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer,
{buffer, Value(12u)}, memory);
fixture.Emit(ValueOpcode::GetBufferResource,
{read, Value(0u), Value(16u), Value(0u)});
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
fixture.Plan();
TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}};
@@ -508,8 +513,7 @@ void TestConstantBufferBounds() {
const auto overflow_read =
overflow.EmitMemory(ValueOpcode::ReadConstBuffer,
{overflow_buffer, Value(16u)}, overflow_memory);
overflow.Emit(ValueOpcode::GetBufferResource,
{overflow_read, Value(0u), Value(16u), Value(0u)});
LoadBuffer(overflow, {overflow_read, Value(0u), Value(16u), Value(0u)});
overflow.Plan();
Check(!SrtWalker(overflow.program, runtime).RefreshFlatBuffer(flat),
"out-of-bounds constant-buffer walk was accepted");
@@ -595,7 +599,7 @@ void TestControlFlowValueSurvivesReadLaneFolding() {
void TestUndefinedRuntimeValueFails() {
Fixture fixture;
const auto undef = fixture.Emit(ValueOpcode::UndefU32);
fixture.Plan();
fixture.program.descriptor_sources.push_back(
{.dwords = {undef}, .dword_count = 1});
DescriptorValue result;
@@ -655,6 +659,7 @@ int main() {
#include "../src/graphics/shader/recompiler/ir/Program.cpp"
#include "../src/graphics/shader/recompiler/ir/Type.cpp"
#include "../src/graphics/shader/recompiler/ir/Value.cpp"
#include "../src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
#include "../src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
#include "../src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp"
#include "../src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
+64 -18
View File
@@ -8601,6 +8601,49 @@ void TestSharedExitPreservesNativeDescriptorSources() {
}
}
void TestNativeScalarReadDescriptorPlanning() {
constexpr uint32_t nested = 10, read = 14, join = 18, end = 20;
const uint32_t shader[] = {
EncodeSopc(0x07, 9, 128),
EncodeSopp(0x04, end - 1 - 1),
EncodeSopc(0x07, 8, 130),
EncodeSopp(0x04, read - 3 - 1),
EncodeSop1(0x24, 0, 126), EncodeSop1(0x04, 2, 126),
EncodeSopc(0x07, 8, 129),
EncodeSopp(0x04, nested - 7 - 1),
EncodeVop1(0x01, 0, 128),
EncodeSopp(0x02, join - 9 - 1),
EncodeSop1(0x04, 0, 126), EncodeSop1(0x04, 2, 126),
EncodeVop1(0x01, 0, 129),
EncodeSopp(0x02, join - 13 - 1),
EncodeSmem0(0x0a, 16, 0), 125u << 25u,
EncodeMubuf0(0x0c, 0, false), EncodeMubuf1(0, 4, 1),
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
EncodeSopp(0x01),
};
const std::array<uint32_t, 4> table{0x5000u, 0u, 4u, 3u << 28u};
const auto address = reinterpret_cast<uint64_t>(table.data());
std::array<uint32_t, 10> user_data{};
user_data[0] = static_cast<uint32_t>(address);
user_data[1] = static_cast<uint32_t>(address >> 32u);
user_data[2] = sizeof(table);
user_data[3] = 3u << 28u;
user_data[9] = 1u;
for (const uint32_t mode : {2u, 0u, 1u}) {
user_data[8] = mode;
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(std::equal(expected.begin(), expected.end(), result.resources.buffers[0].dwords.begin()),
"scalar descriptor planning selected the wrong native resource");
CheckSpirvBinaryValidates(result.spirv);
}
}
void TestNativeGuardedSamplerSource() {
constexpr uint32_t nested = 12, sample = 18, join = 20, end = 22;
const uint32_t shader[] = {
@@ -12348,7 +12391,7 @@ void BuildTypedPlan(const uint32_t *code, uint32_t words,
ShaderRecompiler::IR::RemoveIdentities(ir.blocks);
ShaderRecompiler::IR::EliminateDeadCode(ir.blocks);
}
ShaderRecompiler::IR::BuildSrtPlan(ir);
ShaderRecompiler::IR::TrackResources(ir, decoded, cfg);
ShaderRecompiler::IR::EliminateDeadCode(ir.blocks);
}
@@ -12445,15 +12488,20 @@ void TestTypedDescriptorRealWideMoveTranslation() {
EncodeMubuf1(0, 0, 1), // buffer_store_dword via copied s[0:3]
EncodeSopp(0x01),
};
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
ShaderRecompiler::Decoder::Program decoded;
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
const auto native = ShaderRecompiler::CFG::BuildGraph(decoded);
ShaderComputeInputInfo compute;
auto ir = ShaderRecompiler::Frontend::TranslateProgram(
decoded, native, {.stage = ShaderType::Compute, .wave_size = 64u,
.input_info = {.compute = &compute}});
for (uint32_t reg = 0; reg < 4; ++reg) {
Check(std::ranges::any_of(ir.scalar_writes, [&](const auto &write) {
return write.pc == (reg / 2u) * 4u &&
ShaderRecompiler::IR::RegIndex(write.reg) == reg;
}), "wide scalar move lost a native descriptor component write");
}
ShaderRecompiler::IR::TrackResources(ir, {}, {});
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
Check(ir.info.buffers.size() == 1,
"real wide-move shader did not track one buffer use");
const auto *source = TypedDescriptorSource(ir, ir.info.buffers[0].source);
@@ -12627,8 +12675,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
ShaderRecompiler::IR::Program load_ir;
BuildTypedPlan(load_shader, static_cast<uint32_t>(std::size(load_shader)),
load_ir);
Check(load_ir.srt_reads.size() == 8 &&
load_ir.dynamic_reads.empty(),
Check(load_ir.srt_reads.size() == 8,
"real scalar loads did not build eight flattened reads");
uint32_t address_reads = 0;
uint32_t buffer_reads = 0;
@@ -12643,7 +12690,6 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
}
Check(address_reads == 4 && buffer_reads == 4,
"real scalar loads used the wrong raw typed operations");
ShaderRecompiler::IR::TrackResources(load_ir, {}, {});
Check(load_ir.info.buffers.size() == 2,
"real scalar-load descriptor sources were not attached");
for (const auto &buffer : load_ir.info.buffers) {
@@ -12674,7 +12720,6 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
BuildTypedPlan(inline_sampler_shader,
static_cast<uint32_t>(std::size(inline_sampler_shader)),
inline_sampler_ir);
ShaderRecompiler::IR::TrackResources(inline_sampler_ir, {}, {});
ShaderRecompiler::IR::DescriptorValue sampler;
ShaderRecompiler::IR::SrtRuntime runtime;
Check(inline_sampler_ir.info.samplers.size() == 1 &&
@@ -12705,7 +12750,7 @@ void TestSrtWalkerRealSmemTranslation() {
};
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(),
Check(ir.srt_reads.size() == 4,
"real SMEM translation did not build four compact SRT reads");
const std::array<uint32_t, 4> table = {0x11111111u, 0x22222222u, 0x33333333u,
@@ -12761,7 +12806,7 @@ void TestSrtWalkerRealSBufferTranslation() {
};
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(),
Check(ir.srt_reads.size() == 4,
"real S_BUFFER_LOAD translation did not build four compact reads");
const std::array<uint32_t, 5> table = {0x11111111u, 0x22222222u, 0x33333333u,
@@ -12816,7 +12861,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() {
EncodeSopp(0x01)});
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader.data(), static_cast<uint32_t>(shader.size()), ir);
Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(),
Check(ir.srt_reads.size() == 4,
opcode == 0x02 ? "overlapping S_LOAD operands were evaluated after a "
"component write"
: "overlapping S_BUFFER_LOAD operands were evaluated "
@@ -12861,11 +12906,10 @@ void TestScalarMemoryLoadCrossesIntoVcc() {
};
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(),
Check(ir.srt_reads.size() == 4,
"wide SMEM destination crossing into VCC lost scalar provenance");
CheckFlattenedReadSlots(
ir, 4, "wide SMEM destination crossing into VCC used wrong flat offsets");
ShaderRecompiler::IR::TrackResources(ir, {}, {});
Check(ir.info.buffers.size() == 1,
"wide SMEM destination crossing into VCC lost its buffer use");
const auto *source =
@@ -12927,7 +12971,6 @@ void TestResourceTrackingRealDensePatching() {
};
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
ShaderRecompiler::IR::TrackResources(ir, {}, {});
Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 &&
ir.info.samplers.size() == 1,
"real resource tracking produced the wrong dense list sizes");
@@ -12994,7 +13037,6 @@ void TestDirectTranslationResetsAnalysisState() {
ShaderRecompiler::IR::Program ir;
BuildTypedPlan(first_shader, static_cast<uint32_t>(std::size(first_shader)),
ir);
ShaderRecompiler::IR::TrackResources(ir, {}, {});
ShaderComputeInputInfo compute;
ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute});
Check(ir.resource_tracking_complete && ir.shader_info_complete &&
@@ -13003,10 +13045,13 @@ void TestDirectTranslationResetsAnalysisState() {
ir.shader_hash = 0xdeadbeef;
const uint32_t second_shader[] = {EncodeSopp(0x01)};
BuildTypedPlan(second_shader, static_cast<uint32_t>(std::size(second_shader)),
ir);
ShaderRecompiler::Decoder::Program decoded;
ShaderRecompiler::Decoder::DecodeProgram(std::span{second_shader}, decoded);
ir = ShaderRecompiler::Frontend::TranslateProgram(
decoded, ShaderRecompiler::CFG::BuildGraph(decoded),
{.stage = ShaderType::Compute, .input_info = {.compute = &compute}});
Check(!ir.resource_tracking_complete && !ir.shader_info_complete &&
ir.srt_plan_complete &&
!ir.srt_plan_complete &&
ir.srt_reads.empty() && ir.shader_hash == 0 &&
ir.info.buffers.empty() && ir.info.images.empty() &&
ir.info.samplers.empty() && ir.info.sampled_pairs.empty() &&
@@ -13872,6 +13917,7 @@ int main() {
TestSharedReturnPreservesDescriptorDominance();
TestSharedExitPreservesNativeDescriptorSources();
TestNestedSelectionPreservesDescriptorSources();
TestNativeScalarReadDescriptorPlanning();
TestNativeGuardedSamplerSource();
TestNativeDescriptorProvenanceRejectsGpuSelection();
TestCfgSiblingSharedExit();