#include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include #include #include namespace { void Check(bool value, const char *text) { if (!value) { std::fprintf(stderr, "ResourceMaterializationTests: failed: %s\n", text); std::abort(); } } bool RejectSpecializationRead(void *userdata, uint64_t, std::span) { ++*static_cast(userdata); return false; } Libs::Graphics::ShaderRecompiler::IR::Block & AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) { using namespace Libs::Graphics::ShaderRecompiler::IR; auto block = std::make_unique(); auto *result = block.get(); program.blocks.push_back(result); program.block_info.push_back({.id = 0}); program.block_storage.push_back(std::move(block)); return *result; } Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) { 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 &value_block = AddValueBlock(program); MemoryInfo memory; memory.kind = ResourceKind::ScalarAddress; memory.planning_only = true; program.memory_info.push_back(memory); const auto low = Value(static_cast(address)); const auto high = Value(static_cast(address >> 32u)); auto &handle = value_block.AppendNewInst(ValueOpcode::GetAddressResource, {low, high}); auto &raw = value_block.AppendNewInst( ValueOpcode::LoadAddressU32, {Value(&handle), Value(0u), Value(0u), Value(true)}); raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40}); program.srt_reads.push_back({Value(&raw), 0}); auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource); auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst, {Value(&srt), Value(0u)}); DescriptorSource source; source.dwords[0] = Value(&flat); source.dwords[1] = Value(0u); source.dword_count = 2; program.descriptor_sources.push_back(source); return ExtractResourcePlan(program); } Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UnbasedFlatPlan() { using namespace Libs::Graphics::ShaderRecompiler::IR; Program program; program.stage = Libs::Graphics::ShaderType::Compute; program.srt_plan_complete = true; program.resource_tracking_complete = true; AddValueBlock(program); program.info.uses_dma = true; return ExtractResourcePlan(program); } Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UserDataBufferPlan() { 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 &value_block = AddValueBlock(program); auto &user_data = value_block.AppendNewInst( ValueOpcode::GetUserData, {Value(static_cast(0))}); DescriptorSource source; source.dwords[0] = Value(&user_data); source.dwords[1] = Value(0u); source.dwords[2] = Value(0u); source.dwords[3] = Value(0u); source.dword_count = 4; program.descriptor_sources.push_back(source); program.info.buffers.push_back({.source = 0}); return ExtractResourcePlan(program); } Libs::Graphics::ShaderRecompiler::IR::ResourcePlan MixedSamplerPlan() { using namespace Libs::Graphics::ShaderRecompiler::IR; Program program; program.stage = Libs::Graphics::ShaderType::Compute; program.srt_plan_complete = true; program.resource_tracking_complete = true; AddValueBlock(program); const auto AddSource = [&program](uint32_t dword_count, uint32_t first) { DescriptorSource source; source.dword_count = dword_count; source.dwords[0] = Value(first); for (uint32_t i = 1; i < dword_count; i++) { source.dwords[i] = Value(0u); } program.descriptor_sources.push_back(source); return static_cast(program.descriptor_sources.size() - 1u); }; const auto image0 = AddSource(8, 0); const auto image1 = AddSource(8, 0); const auto sampler0 = AddSource(4, 0x11111111u); const auto sampler1 = AddSource(4, 0x22222222u); program.info.images.push_back( {.source = image0, .resource_class = ImageResourceClass::Sampled, .numeric_class = Libs::Graphics::Prospero::TextureNumericClass::Float, .dimension = Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D}); program.info.images.push_back( {.source = image1, .resource_class = ImageResourceClass::Sampled, .numeric_class = Libs::Graphics::Prospero::TextureNumericClass::Float, .dimension = Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D, .conversion_format = Libs::Graphics::Prospero::BufferFormat::k8_8_8_8UNorm}); program.info.samplers.push_back({.source = sampler0}); program.info.samplers.push_back({.source = sampler1}); program.info.sampled_pairs.push_back({.image = 0, .sampler = 0}); program.info.sampled_pairs.push_back({.image = 0, .sampler = 1}); program.info.sampled_pairs.push_back({.image = 1, .sampler = 1}); return ExtractResourcePlan(program); } void TestMappedSrtUsesDirectReaderByDefault() { using namespace Libs::Graphics::ShaderRecompiler::IR; const uint32_t dword = 0x12345678; auto plan = SrtPlan(reinterpret_cast(&dword)); uint32_t specialization_reads = 0; const SrtRuntime runtime{.userdata = &specialization_reads, .read_specialization_memory = RejectSpecializationRead}; ResourceSnapshot snapshot; ResourceSpecialization specialization; Check(MaterializeResources(plan, runtime, snapshot, specialization), "mapped SRT stage materialization failed"); Check(specialization_reads == 0, "ordinary SRT read used the specialization reader"); Check(snapshot.flattened_srt.size() == 1 && snapshot.flattened_srt[0] == dword, "cache rematerialization did not use the direct reader by default"); } void TestIntegerRuntimeValueFollowsSrtReads() { using namespace Libs::Graphics::ShaderRecompiler::IR; auto plan = SrtPlan(0x10000); const auto root = plan.descriptor_sources.front().dwords[0]; Check(ValidateRuntimeValue(plan, root, RuntimeValueType::Integer), "integer SRT read was rejected"); Block values; auto &comparison = values.AppendNewInst(ValueOpcode::FPOrdLessThanEqual32, {Value::F32(1.f), Value::F32(0.f)}); auto &selection = values.AppendNewInst( ValueOpcode::SelectU32, {Value(&comparison), Value(1u), Value(0u)}); plan.srt_reads[0].value = Value(&selection); Check(ValidateRuntimeValue(plan, root), "ordinary SRT validation rejected a floating-point dependency"); Check(!ValidateRuntimeValue(plan, root, RuntimeValueType::Integer), "integer SRT validation missed a hidden floating-point dependency"); auto &first = values.AppendNewInst(ValueOpcode::ReadFirstLane, {root, Value(true)}); Check(!ValidateRuntimeValue(plan, Value(&first), RuntimeValueType::Integer), "read-first-lane lost integer-only SRT validation"); auto &active = values.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&selection), Value(&comparison)}); Check(!ValidateRuntimeValue(plan, Value(&active), RuntimeValueType::Integer), "floating-point execution mask was accepted as integer-only"); auto &lane = values.AppendNewInst( ValueOpcode::GetBuiltin, {Value(static_cast(StageInputKind::LocalInvocationId)), Value(0u)}); auto &mask = values.AppendNewInst(ValueOpcode::INotEqual32, {Value(&lane), Value(0u)}); selection.SetArg(0, Value(&mask)); active.SetArg(1, Value(&mask)); Check(ValidateRuntimeValue(plan, Value(&active), RuntimeValueType::Integer), "nonuniform integer execution mask was rejected"); auto &float_value = values.AppendNewInst(ValueOpcode::BitCastU32F32, {Value::F32(1.f)}); selection.SetArg(2, Value(&float_value)); Check(!ValidateRuntimeValue(plan, Value(&active), RuntimeValueType::Integer), "floating-point inactive arm was accepted as integer-only"); plan.srt_reads[0].value = Value(&first); Check(!ValidateRuntimeValue(plan, root, RuntimeValueType::Integer), "cyclic SRT read-first-lane dependency was accepted"); } void TestUnbasedFlatCacheHitMaterializes() { using namespace Libs::Graphics::ShaderRecompiler::IR; auto plan = UnbasedFlatPlan(); ResourceSnapshot snapshot; ResourceSpecialization specialization; Check(MaterializeResources(plan, {}, snapshot, specialization), "unbased FLAT stage materialization failed"); Check(snapshot.buffers.empty() && snapshot.images.empty(), "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(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 user_data{4u}; const SrtRuntime runtime{ .user_data = user_data, .read_memory = +[](void *data, uint64_t, std::span words) { ++static_cast(data)->ordinary; words[0] = 0x8000u; return true; }, .userdata = &reads, .read_specialization_memory = +[](void *data, uint64_t address, std::span words) { auto &reads = *static_cast(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(); ResourceSnapshot snapshot; ResourceSpecialization specialization; Check(!MaterializeResources(plan, {}, snapshot, specialization), "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( Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u); source.dwords[3] = Value(Libs::Graphics::DstSel(4, 5, 6, 7) | (static_cast(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(DescriptorSource::IndirectImage{}).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 words{0x100u, 0x200u, 0x200u}; std::array counts{}; uint32_t ordinary = 0; } reads; const SrtRuntime runtime{ .read_memory = +[](void *data, uint64_t, std::span) { ++static_cast(data)->ordinary; return false; }, .userdata = &reads, .read_specialization_memory = +[](void *data, uint64_t address, std::span words) { if (words.size() != 1 || address < 0x1000u || address >= 0x100cu || (address & 3u) != 0) return false; auto &reads = *static_cast(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 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{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{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(); ResourceSnapshot snapshot; ResourceSpecialization specialization; Check(MaterializeResources(plan, {}, snapshot, specialization), "mixed sampler materialization failed"); Check(snapshot.samplers.size() == 3, "mixed sampler materialization appended unrelated samplers"); Check(snapshot.samplers[2] == snapshot.samplers[1] && snapshot.samplers[2] != snapshot.samplers[0], "point sampler variant duplicated the wrong runtime descriptor"); } } // namespace namespace Common { int DbgExitHandler(const char *, int, std::string_view) { std::abort(); } int DbgExitHandler(const char *, int, fmt::text_style, std::string_view) { std::abort(); } int DbgExitIfHandler(const char *, const char *, int) { return 1; } void DbgExit(int) { std::abort(); } } // namespace Common int main() { TestMappedSrtUsesDirectReaderByDefault(); TestIntegerRuntimeValueFollowsSrtReads(); TestUnbasedFlatCacheHitMaterializes(); TestWrittenDescriptorUsesStrictReaderOnce(); TestFailedMaterializationRejectsStage(); TestFiniteImageRefreshReusesScalarReads(); TestMixedSamplerDuplicatesTheCorrectSnapshot(); std::puts("ResourceMaterializationTests: all cases passed"); return 0; } // Keep this focused standalone target self-contained by amalgamating its small // typed-IR implementation set. #include "graphics/shader/recompiler/ir/Block.cpp" #include "graphics/shader/recompiler/ir/Program.cpp" #include "graphics/shader/recompiler/ir/Type.cpp" #include "graphics/shader/recompiler/ir/Value.cpp" #include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"