#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, uint32_t *) { ++*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 TestFailedMaterializationPreservesPriorStage() { using namespace Libs::Graphics::ShaderRecompiler::IR; auto plan = UserDataBufferPlan(); ResourceSnapshot snapshot; snapshot.user_data.push_back(0xfeedbeefu); ResourceSpecialization specialization; specialization.buffers.push_back({.packed_stride = 7}); Check(!MaterializeResources(plan, {}, snapshot, specialization), "missing runtime user data did not reject the cached stage"); Check(snapshot.user_data == std::vector{0xfeedbeefu} && specialization.buffers.size() == 1 && specialization.buffers[0].packed_stride == 7, "failed cache materialization changed its destinations"); } 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(); TestFailedMaterializationPreservesPriorStage(); 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"