588 lines
27 KiB
C++
588 lines
27 KiB
C++
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
|
|
|
#include <bit>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#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::Program MixedSamplerProgram() {
|
|
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);
|
|
|
|
const auto AddSource = [&program](uint32_t dword_count) {
|
|
DescriptorSource source;
|
|
source.dword_count = dword_count;
|
|
for (uint32_t i = 0; 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);
|
|
const auto image1 = AddSource(8);
|
|
const auto sampler0 = AddSource(4);
|
|
const auto sampler1 = AddSource(4);
|
|
program.descriptor_sources[image1].dwords[0] = Value(1u);
|
|
program.descriptor_sources[image1].dwords[1] = Value(static_cast<uint32_t>(
|
|
Libs::Graphics::Prospero::BufferFormat::k11_11_10UInt) << 20u);
|
|
program.descriptor_sources[image1].dwords[3] = Value(static_cast<uint32_t>(
|
|
Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
|
for (uint32_t index = 0; index < 2; ++index) {
|
|
auto &value = block.AppendNewInst(
|
|
ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(index))});
|
|
program.descriptor_sources[sampler0 + index].dwords[0] = Value(&value);
|
|
}
|
|
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});
|
|
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 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 TestUniformVectorDescriptorRead() {
|
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
|
Program program;
|
|
program.stage = Libs::Graphics::ShaderType::Compute;
|
|
program.srt_plan_complete = program.resource_tracking_complete = true;
|
|
auto &block = AddValueBlock(program);
|
|
auto &handle = block.AppendNewInst(ValueOpcode::GetBufferResource,
|
|
{Value(0x1000u), Value(4u << 16u), Value(1u), Value(0x16204u)});
|
|
program.memory_info.push_back({.kind = ResourceKind::Buffer});
|
|
auto &count = block.AppendNewInst(ValueOpcode::LoadBufferU32,
|
|
{Value(&handle), Value(0u), Value(0u), Value(0u), Value(true)});
|
|
count.SetFlags(MemoryFlags{.index = 0});
|
|
// The captured indirect kernel shares one read across sibling scalar lane reads,
|
|
// enclosed by a different EXEC mask. Its resource plan must share that read too.
|
|
auto &lane = block.AppendNewInst(ValueOpcode::GetBuiltin,
|
|
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
|
auto &active = block.AppendNewInst(ValueOpcode::ULessThan32, {Value(&lane), Value(32u)});
|
|
auto &first = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&count), Value(true)});
|
|
auto &next = block.AppendNewInst(ValueOpcode::IAdd32, {Value(&count), Value(1u)});
|
|
auto &second = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&next), Value(true)});
|
|
auto &sum = block.AppendNewInst(ValueOpcode::IAdd32, {Value(&first), Value(&second)});
|
|
auto &small = block.AppendNewInst(ValueOpcode::ULessThanEqual32, {Value(&count), Value(72u)});
|
|
auto &selected = block.AppendNewInst(ValueOpcode::SelectU32, {Value(&small), Value(&sum), Value(&count)});
|
|
auto &masked = block.AppendNewInst(ValueOpcode::SelectU32, {Value(&active), Value(&selected), Value(0u)});
|
|
auto &records = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&masked), Value(&active)});
|
|
DescriptorSource source;
|
|
source.dword_count = 4;
|
|
source.dwords = {Value(0x2000u), Value(4u << 16u), Value(&records), Value(0x16204u)};
|
|
program.descriptor_sources.push_back(source);
|
|
program.info.buffers.push_back({.source = 0, .written = true});
|
|
Check(ValidateRuntimeValue(program, Value(&count)), "uniform DWORD count was rejected");
|
|
struct Reads { uint32_t value = 72; uint32_t strict = 0; uint32_t ordinary = 0; bool clean = true; } reads;
|
|
const SrtRuntime runtime{
|
|
.read_memory = [](void *data, uint64_t, std::span<uint32_t> words) {
|
|
++static_cast<Reads *>(data)->ordinary;
|
|
words[0] = 999;
|
|
return true;
|
|
},
|
|
.userdata = &reads,
|
|
.read_specialization_memory = [](void *data, uint64_t address, std::span<uint32_t> words) {
|
|
auto &reads = *static_cast<Reads *>(data);
|
|
++reads.strict;
|
|
if (!reads.clean || address != 0x1000u || words.size() != 1) return false;
|
|
words[0] = reads.value;
|
|
return true;
|
|
}};
|
|
ResourceSnapshot snapshot;
|
|
ResourceSpecialization specialization;
|
|
for (const bool written : {false, true}) {
|
|
program.info.buffers[0].written = written;
|
|
auto plan = ExtractResourcePlan(program);
|
|
Check(plan.control_flow.empty() && plan.requires_specialization_memory &&
|
|
plan.capture_specialization_reads,
|
|
"vector descriptor read depended on incidental control-flow capture");
|
|
reads = {};
|
|
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
|
snapshot.buffers[0].dwords[2] == 145 && reads.strict == 1 && reads.ordinary == 0 &&
|
|
snapshot.specialization_reads ==
|
|
std::vector<std::pair<uint64_t, uint64_t>>{{0x1000u, 4u}},
|
|
"vector descriptor input was not read and captured exactly once");
|
|
reads.clean = false;
|
|
reads.strict = 0;
|
|
Check(!MaterializeResources(plan, runtime, snapshot, specialization) &&
|
|
reads.strict == 1 && reads.ordinary == 0,
|
|
"dirty vector descriptor input fell back to an ordinary memory read");
|
|
}
|
|
count.SetArg(4, Value(false));
|
|
auto &inactive = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&count), Value(false)});
|
|
reads.strict = 0;
|
|
uint32_t result = 99;
|
|
Check(SrtWalker(program, runtime).Evaluate(Value(&inactive), result) &&
|
|
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
|
"literal false EXEC read vector memory");
|
|
count.SetArg(4, Value(true));
|
|
handle.SetArg(3, Value(0x204u));
|
|
Check(SrtWalker(program, runtime).Evaluate(Value(&count), result) &&
|
|
result == 0 && reads.strict == 0 && reads.ordinary == 0,
|
|
"invalid vector buffer format read memory");
|
|
handle.SetArg(3, Value(0x16204u));
|
|
count.SetArg(1, Value(&lane));
|
|
Check(!ValidateRuntimeValue(program, Value(&count)),
|
|
"varying vector address was treated as a uniform descriptor read");
|
|
}
|
|
|
|
void TestExactReciprocalDescriptorArithmetic() {
|
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
|
Program program;
|
|
auto &block = AddValueBlock(program);
|
|
for (const float divisor : {64.f, 128.f, 256.f, 512.f,
|
|
std::numeric_limits<float>::min(),
|
|
std::bit_cast<float>(253u << 23u)}) {
|
|
auto &reciprocal = block.AppendNewInst(ValueOpcode::FPRecipIFlag32, {Value::F32(divisor)});
|
|
uint32_t result = 0;
|
|
Check(SrtWalker(program, {}).Evaluate(Value(&reciprocal), result) &&
|
|
result == std::bit_cast<uint32_t>(1.f / divisor),
|
|
"power-of-two reciprocal was not exact");
|
|
}
|
|
for (const float divisor : {0.f, 3.f, -64.f, std::numeric_limits<float>::infinity(),
|
|
std::numeric_limits<float>::denorm_min(),
|
|
std::bit_cast<float>(254u << 23u)}) {
|
|
auto &reciprocal = block.AppendNewInst(ValueOpcode::FPRecipIFlag32, {Value::F32(divisor)});
|
|
uint32_t result = 0;
|
|
Check(!SrtWalker(program, {}).Evaluate(Value(&reciprocal), result),
|
|
"unsupported reciprocal rounding or exceptional input 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.user_data_count = 1;
|
|
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<ScalarReg>(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});
|
|
// A host-evaluable branch captures resource reads and needs the writable
|
|
// descriptor's clean provenance for the renderer's disjointness proof.
|
|
auto &condition = block.AppendNewInst(ValueOpcode::IEqual32,
|
|
{Value(&offset), Value(4u)});
|
|
auto &store_block = AddValueBlock(program);
|
|
AddValueBlock(program);
|
|
program.block_info[0].condition = Value(&condition);
|
|
program.block_info[0].terminator.kind =
|
|
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
|
program.block_info[0].terminator.true_block = 1;
|
|
program.block_info[0].terminator.false_block = 2;
|
|
program.block_info[1].id = 1;
|
|
program.block_info[1].terminator.kind =
|
|
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
|
|
program.block_info[2].id = 2;
|
|
program.block_info[2].terminator.kind =
|
|
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
|
|
program.memory_info.push_back({.kind = ResourceKind::Buffer, .resource = 0});
|
|
auto &output = store_block.AppendNewInst(ValueOpcode::GetBufferResource,
|
|
{source.dwords[0], source.dwords[1], source.dwords[2], source.dwords[3]});
|
|
store_block.AppendNewInst(ValueOpcode::StoreBufferU32,
|
|
{Value(&output), Value(0u), Value(0u), Value(0u), Value(1u), Value(true)})
|
|
.SetFlags(MemoryFlags{.index = 1});
|
|
auto plan = ExtractResourcePlan(program);
|
|
Check(plan.capture_specialization_reads,
|
|
"conditional writable descriptor lost its alias proof");
|
|
struct Reads { uint32_t ordinary = 0; uint32_t strict = 0; bool clean = false; } reads;
|
|
const std::array<uint32_t, 1> user_data{4u};
|
|
const SrtRuntime runtime{
|
|
.user_data = user_data,
|
|
.read_memory = +[](void *data, uint64_t, std::span<uint32_t> words) {
|
|
++static_cast<Reads *>(data)->ordinary;
|
|
words[0] = 0x8000u;
|
|
return true;
|
|
},
|
|
.userdata = &reads,
|
|
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
|
auto &reads = *static_cast<Reads *>(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 &&
|
|
snapshot.specialization_reads ==
|
|
std::vector<std::pair<uint64_t, uint64_t>>{{0x1004u, 4u}},
|
|
"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<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_descriptor.emplace(DescriptorSource::IndirectDescriptor{}).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 TestMixedSamplerVariantsShareRuntimeDescriptor() {
|
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
|
auto program = MixedSamplerProgram();
|
|
auto plan = ExtractResourcePlan(program);
|
|
std::array<uint32_t, 2> user_data{0x11111111u, 0x22222222u};
|
|
const SrtRuntime runtime{.user_data = user_data};
|
|
ResourceSnapshot snapshot;
|
|
ResourceSpecialization specialization;
|
|
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
|
"mixed sampler materialization failed");
|
|
ApplyResourceSpecialization(program, specialization);
|
|
const auto &samplers = program.info.samplers;
|
|
Check(snapshot.samplers.size() == 2 && samplers.size() == 3 &&
|
|
samplers[0].snapshot_index == 0 && samplers[1].snapshot_index == 1 &&
|
|
samplers[2].snapshot_index == 1 &&
|
|
samplers[0].source == plan.info.samplers[0].source &&
|
|
samplers[1].source == plan.info.samplers[1].source &&
|
|
samplers[2].source == samplers[1].source &&
|
|
!samplers[1].force_point_filtering && samplers[2].force_point_filtering &&
|
|
program.info.sampled_pairs[2].sampler == 2,
|
|
"native sampler variants lost their source identity or binding order");
|
|
const auto capacity = snapshot.samplers.capacity();
|
|
user_data[1] = 0x33333333u;
|
|
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
|
snapshot.samplers.size() == 2 && snapshot.samplers.capacity() == capacity &&
|
|
snapshot.samplers[samplers[0].snapshot_index].dwords[0] == user_data[0] &&
|
|
snapshot.samplers[samplers[1].snapshot_index].dwords[0] == user_data[1] &&
|
|
snapshot.samplers[samplers[2].snapshot_index].dwords[0] == user_data[1],
|
|
"sampler variants retained stale or duplicated descriptors after refresh");
|
|
}
|
|
|
|
} // 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();
|
|
TestUniformVectorDescriptorRead();
|
|
TestExactReciprocalDescriptorArithmetic();
|
|
TestUnbasedFlatCacheHitMaterializes();
|
|
TestWrittenDescriptorUsesStrictReaderOnce();
|
|
TestFailedMaterializationRejectsStage();
|
|
TestFiniteImageRefreshReusesScalarReads();
|
|
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
|
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"
|