278 lines
11 KiB
C++
278 lines
11 KiB
C++
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
|
|
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<uint32_t>) {
|
|
++*static_cast<uint32_t *>(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<Block>();
|
|
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<uint32_t>(address));
|
|
const auto high = Value(static_cast<uint32_t>(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<ScalarReg>(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<uint32_t>(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<uint64_t>(&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<uint32_t>(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 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 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();
|
|
TestFailedMaterializationRejectsStage();
|
|
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"
|