mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
2090 lines
86 KiB
C++
2090 lines
86 KiB
C++
#include "graphics/guest_gpu/gpu_defs.h"
|
|
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
|
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
|
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
|
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
|
|
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
|
|
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
|
#include "graphics/shader/shaderBindings.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
namespace {
|
|
|
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
|
using Libs::Graphics::ShaderComputeInputInfo;
|
|
using Libs::Graphics::ShaderPixelInputInfo;
|
|
using Libs::Graphics::ShaderTextureResource;
|
|
using Libs::Graphics::ShaderType;
|
|
using Libs::Graphics::ShaderVertexInputInfo;
|
|
namespace Prospero = Libs::Graphics::Prospero;
|
|
namespace Decoder = Libs::Graphics::ShaderRecompiler::Decoder;
|
|
|
|
void Check(bool condition, const char *message) {
|
|
if (!condition) {
|
|
throw std::runtime_error(message);
|
|
}
|
|
}
|
|
|
|
Operand Sgpr(uint32_t reg) {
|
|
Operand operand;
|
|
operand.kind = OperandKind::Register;
|
|
operand.reg.file = RegisterFile::Scalar;
|
|
operand.reg.index = reg;
|
|
return operand;
|
|
}
|
|
|
|
Operand Imm(uint32_t value) {
|
|
Operand operand;
|
|
operand.kind = OperandKind::ImmediateU32;
|
|
operand.imm = value;
|
|
return operand;
|
|
}
|
|
|
|
Instruction Move(uint32_t pc, uint32_t dst, uint32_t src) {
|
|
Instruction inst;
|
|
inst.pc = pc;
|
|
inst.op = Opcode::MoveU32;
|
|
inst.dst = Sgpr(dst);
|
|
inst.src[0] = Sgpr(src);
|
|
inst.src_count = 1;
|
|
return inst;
|
|
}
|
|
|
|
Instruction MoveImmediate(uint32_t pc, uint32_t dst, uint32_t value) {
|
|
auto inst = Move(pc, dst, 0);
|
|
inst.src[0] = Imm(value);
|
|
return inst;
|
|
}
|
|
|
|
Instruction BufferUse(uint32_t pc, uint32_t base,
|
|
Opcode op = Opcode::BufferLoadDword) {
|
|
Instruction inst;
|
|
inst.pc = pc;
|
|
inst.op = op;
|
|
inst.memory.kind = ResourceKind::Buffer;
|
|
inst.memory.resource = base / 4;
|
|
return inst;
|
|
}
|
|
|
|
Instruction ImageUse(uint32_t pc, Opcode op, ResourceKind kind,
|
|
Decoder::ImageDimension dimension, uint32_t resource = 0,
|
|
uint32_t sampler = 2) {
|
|
Instruction inst;
|
|
inst.pc = pc;
|
|
inst.op = op;
|
|
inst.memory.kind = kind;
|
|
inst.memory.resource = resource;
|
|
inst.memory.sampler = sampler;
|
|
inst.memory.image_dimension = dimension;
|
|
return inst;
|
|
}
|
|
|
|
Instruction ScalarLoad(uint32_t pc, uint32_t dst, uint32_t base,
|
|
uint32_t offset) {
|
|
Instruction inst;
|
|
inst.pc = pc;
|
|
inst.op = Opcode::SLoadDword;
|
|
inst.dst = Sgpr(dst);
|
|
inst.memory.kind = ResourceKind::ScalarBuffer;
|
|
inst.memory.resource = base;
|
|
inst.memory.offset = offset;
|
|
return inst;
|
|
}
|
|
|
|
Instruction ScalarBufferLoad(uint32_t pc, uint32_t dst, uint32_t descriptor,
|
|
uint32_t offset) {
|
|
auto inst = ScalarLoad(pc, dst, descriptor * 4, offset);
|
|
inst.op = Opcode::SBufferLoadDword;
|
|
inst.memory.resource = descriptor;
|
|
return inst;
|
|
}
|
|
|
|
void MakeScalarMemoryGroup(std::vector<Instruction> *instructions) {
|
|
const auto count = static_cast<uint32_t>(instructions->size());
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
(*instructions)[i].memory.component_index = i;
|
|
(*instructions)[i].memory.component_count = count;
|
|
}
|
|
}
|
|
|
|
Instruction Export(uint32_t pc, ExportTargetKind kind, uint32_t index = 0,
|
|
uint32_t en = 0xf) {
|
|
Instruction inst;
|
|
inst.pc = pc;
|
|
inst.op = Opcode::Export;
|
|
inst.export_info.kind = kind;
|
|
inst.export_info.index = index;
|
|
inst.export_info.en = en;
|
|
return inst;
|
|
}
|
|
|
|
struct TestMemory {
|
|
uint64_t base = 0x1000;
|
|
std::array<uint32_t, 8> words{};
|
|
uint32_t reads = 0;
|
|
uint32_t fail_after = UINT32_MAX;
|
|
};
|
|
|
|
bool ReadTestMemory(void *userdata, uint64_t address, uint32_t *value) {
|
|
auto *memory = static_cast<TestMemory *>(userdata);
|
|
if (memory == nullptr || value == nullptr || address < memory->base ||
|
|
address - memory->base >= memory->words.size() * sizeof(uint32_t) ||
|
|
memory->reads >= memory->fail_after) {
|
|
return false;
|
|
}
|
|
const auto index =
|
|
static_cast<size_t>((address - memory->base) / sizeof(uint32_t));
|
|
*value = memory->words[index];
|
|
memory->reads++;
|
|
return true;
|
|
}
|
|
|
|
const StageInput *FindInput(const ShaderInfo &info, StageInputKind kind,
|
|
uint32_t location = 0) {
|
|
for (const auto &input : info.inputs) {
|
|
if (input.kind == kind && input.location == location) {
|
|
return &input;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const StageOutput *FindOutput(const ShaderInfo &info, StageOutputKind kind,
|
|
uint32_t index = 0) {
|
|
for (const auto &output : info.outputs) {
|
|
if (output.kind == kind && output.index == index) {
|
|
return &output;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Prepare(Program &program) {
|
|
std::string error;
|
|
if (!BuildScalarProvenance(program, &error) ||
|
|
!BuildSrtPlan(program, &error) || !PatchSrtReads(program, &error) ||
|
|
!TrackResources(program, &error)) {
|
|
throw std::runtime_error(error);
|
|
}
|
|
}
|
|
|
|
void TestDenseBufferPatching() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto first = BufferUse(4, 0);
|
|
first.memory.offset = 4;
|
|
first.memory.formatted = true;
|
|
auto write = BufferUse(8, 0, Opcode::BufferStoreDword);
|
|
write.memory.offset = 12;
|
|
auto atomic = BufferUse(10, 0, Opcode::AtomicAddU32);
|
|
auto &insts = program.blocks[0].instructions;
|
|
insts = {first, atomic, write, Move(12, 4, 20),
|
|
Move(16, 5, 21), Move(20, 6, 22), Move(24, 7, 23), BufferUse(28, 4)};
|
|
|
|
Prepare(program);
|
|
Check(program.info.buffers.size() == 2,
|
|
"buffer sources were not densely deduplicated");
|
|
const auto &resource = program.info.buffers[0];
|
|
Check(resource.read && resource.written && resource.atomic &&
|
|
resource.formatted && resource.max_byte_extent == 16 &&
|
|
resource.first_use_pc == 4,
|
|
"buffer access facts were not merged");
|
|
Check(insts[0].memory.resource == 0 && insts[1].memory.resource == 0 &&
|
|
insts[2].memory.resource == 0 && insts[7].memory.resource == 1,
|
|
"buffer operands were not patched to dense indices");
|
|
Check(insts[0].memory.resource_source == ScalarProvenance::Undefined &&
|
|
insts[7].memory.resource_source == ScalarProvenance::Undefined,
|
|
"patched instructions retained duplicate descriptor source handles");
|
|
std::string error;
|
|
Check(!TrackResources(program, &error) &&
|
|
error.find("already tracked") != std::string::npos,
|
|
"resource tracking was not guarded against a second patch pass");
|
|
}
|
|
|
|
void TestScalarAndVectorBufferAlias() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
Instruction scalar;
|
|
scalar.pc = 4;
|
|
scalar.op = Opcode::SBufferLoadDword;
|
|
scalar.dst = Sgpr(20);
|
|
scalar.src[0] = Imm(0);
|
|
scalar.src_count = 1;
|
|
scalar.memory.kind = ResourceKind::ScalarBuffer;
|
|
scalar.memory.resource = 2;
|
|
program.blocks[0].instructions = {scalar, BufferUse(8, 8)};
|
|
|
|
Prepare(program);
|
|
Check(program.info.buffers.size() == 1 && program.info.buffers[0].scalar,
|
|
"scalar and vector uses of one descriptor were split");
|
|
Check(program.blocks[0].instructions[0].memory.resource == 0 &&
|
|
program.blocks[0].instructions[1].memory.resource == 0,
|
|
"scalar/vector alias did not share one dense index");
|
|
}
|
|
|
|
void TestBufferImageAliasIsLinkedDuringTracking() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {BufferUse(4, 0), BufferUse(8, 8),
|
|
ImageUse(12, Opcode::ImageLoad,
|
|
ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D)};
|
|
|
|
Prepare(program);
|
|
Check(program.info.buffers.size() == 2 && program.info.images.size() == 1 &&
|
|
program.info.buffers[0].image_alias == 0 &&
|
|
program.info.buffers[1].image_alias == BufferResource::NoImageAlias,
|
|
"buffer/image descriptor provenance aliases were not linked during "
|
|
"tracking");
|
|
}
|
|
|
|
void TestImagesAndSamplers() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto sample0 = ImageUse(4, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D);
|
|
auto sample1 = ImageUse(8, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D);
|
|
auto volume = ImageUse(12, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim3D);
|
|
auto compare = ImageUse(16, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D);
|
|
compare.memory.image_sample_flags = Decoder::ImageSampleFlagCompare;
|
|
auto storage = ImageUse(20, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D);
|
|
auto storage_mip = storage;
|
|
storage_mip.pc = 24;
|
|
storage_mip.memory.image_has_mip = true;
|
|
auto atomic =
|
|
ImageUse(28, Opcode::AtomicAddU32, ResourceKind::StorageImageUint,
|
|
Decoder::ImageDimension::Dim2D);
|
|
program.blocks[0].instructions = {sample0, sample1, volume, compare,
|
|
storage, storage_mip, atomic};
|
|
|
|
Prepare(program);
|
|
Check(program.info.images.size() == 6 && program.info.samplers.size() == 1 &&
|
|
program.info.sampled_pairs.size() == 3,
|
|
"image view classes or samplers were deduplicated incorrectly");
|
|
const auto &insts = program.blocks[0].instructions;
|
|
Check(insts[0].memory.resource == 0 && insts[1].memory.resource == 0 &&
|
|
insts[2].memory.resource == 1 && insts[3].memory.resource == 2 &&
|
|
insts[4].memory.resource == 3 && insts[5].memory.resource == 4 &&
|
|
insts[6].memory.resource == 5 && insts[0].memory.sampler == 0 &&
|
|
insts[2].memory.sampler == 0,
|
|
"image/sampler operands were not patched to dense list indices");
|
|
Check(program.info.images[0].read && !program.info.images[0].written &&
|
|
program.info.images[2].depth_compare &&
|
|
program.info.images[3].written &&
|
|
program.info.images[4].mip_mode == ImageMipMode::DynamicStorage &&
|
|
program.info.images[5].atomic &&
|
|
program.info.sampled_pairs[0].sampler ==
|
|
program.info.sampled_pairs[1].sampler,
|
|
"image access facts were wrong");
|
|
}
|
|
|
|
void TestDynamicPhiResource() {
|
|
Program program;
|
|
program.blocks.resize(4);
|
|
program.blocks[0].successors = {1, 2};
|
|
program.blocks[1].predecessors = {0};
|
|
program.blocks[1].successors = {3};
|
|
program.blocks[2].predecessors = {0};
|
|
program.blocks[2].successors = {3};
|
|
program.blocks[3].predecessors = {1, 2};
|
|
Instruction left;
|
|
left.op = Opcode::MoveU32;
|
|
left.dst = Sgpr(0);
|
|
left.src[0] = Imm(1);
|
|
left.src_count = 1;
|
|
Instruction right = left;
|
|
right.pc = 4;
|
|
right.src[0] = Imm(2);
|
|
program.blocks[1].instructions = {left};
|
|
program.blocks[2].instructions = {right};
|
|
program.blocks[3].instructions = {BufferUse(8, 0)};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
const auto original_source =
|
|
program.blocks[3].instructions[0].memory.resource_source;
|
|
Check(
|
|
!TrackResources(program, &error) &&
|
|
error.find("unsupported GPU selection") != std::string::npos &&
|
|
program.blocks[3].instructions[0].memory.resource_source ==
|
|
original_source &&
|
|
!program.resource_tracking_complete,
|
|
"control-flow descriptor was patched without an executable GPU selector");
|
|
}
|
|
|
|
void TestTrackingRequiresCompletedSrtPlan() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.shader_hash = 0x8899;
|
|
program.blocks.resize(4);
|
|
program.blocks[0].successors = {1, 2};
|
|
program.blocks[1].predecessors = {0};
|
|
program.blocks[1].successors = {3};
|
|
program.blocks[2].predecessors = {0};
|
|
program.blocks[2].successors = {3};
|
|
program.blocks[3].predecessors = {1, 2};
|
|
program.blocks[1].instructions = {MoveImmediate(0, 0, 1)};
|
|
program.blocks[2].instructions = {MoveImmediate(4, 0, 2)};
|
|
program.blocks[3].instructions = {BufferUse(8, 0)};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error), error.c_str());
|
|
Check(!program.srt_plan_complete,
|
|
"provenance unexpectedly marked the SRT plan complete");
|
|
const auto original_source =
|
|
program.blocks[3].instructions[0].memory.resource_source;
|
|
BufferResource existing_info;
|
|
existing_info.source = 777;
|
|
program.info.buffers.push_back(existing_info);
|
|
Check(!TrackResources(program, &error) &&
|
|
error.find("SRT plan is not ready") != std::string::npos &&
|
|
program.blocks[3].instructions[0].memory.resource == 0 &&
|
|
program.blocks[3].instructions[0].memory.resource_source ==
|
|
original_source &&
|
|
program.info.buffers.size() == 1 &&
|
|
program.info.buffers[0].source == 777 &&
|
|
!program.resource_tracking_complete,
|
|
"tracking bypassed the SRT-plan readiness invariant or partially "
|
|
"patched the program");
|
|
}
|
|
|
|
void TestCyclicResourceIsRejected() {
|
|
Program program;
|
|
program.blocks.resize(2);
|
|
program.blocks[0].successors = {1};
|
|
program.blocks[1].predecessors = {0, 1};
|
|
program.blocks[1].successors = {1};
|
|
Instruction increment;
|
|
increment.op = Opcode::IAddU32;
|
|
increment.dst = Sgpr(0);
|
|
increment.src[0] = Sgpr(0);
|
|
increment.src[1] = Imm(1);
|
|
increment.src_count = 2;
|
|
program.blocks[1].instructions = {BufferUse(4, 0), increment};
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
Check(!TrackResources(program, &error) &&
|
|
error.find("unsupported GPU selection") != std::string::npos &&
|
|
!program.resource_tracking_complete,
|
|
"cyclic descriptor was patched without a bindless/direct path");
|
|
}
|
|
|
|
void TestUnknownSourceFailsWithoutPatching() {
|
|
Program program;
|
|
program.stage = ShaderType::Pixel;
|
|
program.shader_hash = 0x12345678;
|
|
program.blocks.resize(1);
|
|
auto valid = BufferUse(4, 0);
|
|
Instruction unsupported;
|
|
unsupported.op = Opcode::SelectU32;
|
|
unsupported.dst = Sgpr(4);
|
|
unsupported.src[0] = Sgpr(20);
|
|
unsupported.src_count = 1;
|
|
program.blocks[0].instructions = {valid, unsupported, BufferUse(0x44, 4)};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
BufferResource existing_info;
|
|
existing_info.source = 777;
|
|
program.info.buffers.push_back(existing_info);
|
|
Check(!TrackResources(program, &error),
|
|
"unknown descriptor unexpectedly tracked");
|
|
Check(error.find("hash=0x0000000012345678") != std::string::npos &&
|
|
error.find("stage=pixel") != std::string::npos &&
|
|
error.find("pc=0x00000044") != std::string::npos &&
|
|
error.find("unknown value") != std::string::npos,
|
|
"unknown descriptor error lost shader context");
|
|
Check(!program.resource_tracking_complete &&
|
|
program.info.buffers.size() == 1 &&
|
|
program.info.buffers[0].source == 777 &&
|
|
program.blocks[0].instructions[0].memory.resource == 0 &&
|
|
program.blocks[0].instructions[0].memory.resource_source !=
|
|
ScalarProvenance::Undefined &&
|
|
program.blocks[0].instructions[2].memory.resource == 1 &&
|
|
program.blocks[0].instructions[2].memory.resource_source !=
|
|
ScalarProvenance::Undefined,
|
|
"failed tracking partially patched the program");
|
|
}
|
|
|
|
void TestResourceLimitFailsTransactionally() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i <= ShaderInfo::MaxBuffers; i++) {
|
|
insts.push_back(MoveImmediate(i * 8, 0, i));
|
|
insts.push_back(BufferUse(i * 8 + 4, 0));
|
|
}
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
const auto first_source = insts[1].memory.resource_source;
|
|
Check(!TrackResources(program, &error) &&
|
|
error.find("buffer resource limit exceeded") != std::string::npos &&
|
|
insts[1].memory.resource_source == first_source &&
|
|
program.info.buffers.empty() && !program.resource_tracking_complete,
|
|
"resource limit failure partially patched the program");
|
|
}
|
|
|
|
void TestComputeShaderInfoCollection() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
Instruction add_tid;
|
|
add_tid.op = Opcode::DsReadAddtidB32;
|
|
program.blocks[0].instructions = {add_tid, BufferUse(4, 0)};
|
|
Prepare(program);
|
|
const auto buffers = program.info.buffers;
|
|
const auto images = program.info.images;
|
|
const auto samplers = program.info.samplers;
|
|
const auto pairs = program.info.sampled_pairs;
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.group_id[1] = true;
|
|
compute.dispatch_thread_dimensions = true;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.compute = &compute}, &error),
|
|
error.c_str());
|
|
Check(program.shader_info_complete && program.info.inputs.size() == 3 &&
|
|
FindInput(program.info, StageInputKind::WorkgroupId) != nullptr &&
|
|
FindInput(program.info, StageInputKind::LocalInvocationId) ==
|
|
nullptr &&
|
|
FindInput(program.info, StageInputKind::LocalInvocationIndex) !=
|
|
nullptr &&
|
|
FindInput(program.info, StageInputKind::GlobalInvocationId) !=
|
|
nullptr &&
|
|
program.info.buffers == buffers && program.info.images == images &&
|
|
program.info.samplers == samplers &&
|
|
program.info.sampled_pairs == pairs,
|
|
"compute addtid input discovery or resource preservation was wrong");
|
|
|
|
Program metadata_program;
|
|
metadata_program.stage = ShaderType::Compute;
|
|
metadata_program.blocks.resize(1);
|
|
Instruction xor_address;
|
|
xor_address.op = Opcode::BitwiseXor3U32;
|
|
metadata_program.blocks[0].instructions = {xor_address};
|
|
Prepare(metadata_program);
|
|
compute = {};
|
|
compute.thread_ids_num = 2;
|
|
Check(CollectShaderInfo(metadata_program, {.compute = &compute}, &error),
|
|
error.c_str());
|
|
Check(FindInput(metadata_program.info, StageInputKind::LocalInvocationId) !=
|
|
nullptr &&
|
|
FindInput(metadata_program.info,
|
|
StageInputKind::LocalInvocationIndex) != nullptr &&
|
|
metadata_program.info.has_bitwise_xor,
|
|
"compute thread metadata or XOR-address heuristic was not collected");
|
|
}
|
|
|
|
void TestVertexShaderInfoCollection() {
|
|
Program program;
|
|
program.stage = ShaderType::Vertex;
|
|
program.blocks.resize(1);
|
|
Instruction attr0;
|
|
attr0.op = Opcode::LoadInputF32;
|
|
attr0.input_info.attr = 0;
|
|
attr0.input_info.chan = 2;
|
|
Instruction attr2 = attr0;
|
|
attr2.input_info.attr = 2;
|
|
attr2.input_info.chan = 0;
|
|
program.blocks[0].instructions = {attr0, attr2,
|
|
Export(8, ExportTargetKind::Position),
|
|
Export(12, ExportTargetKind::Parameter, 2)};
|
|
Prepare(program);
|
|
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.resources_num = 3;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.vertex = &vertex}, &error), error.c_str());
|
|
const auto *input0 = FindInput(program.info, StageInputKind::Parameter, 0);
|
|
const auto *input2 = FindInput(program.info, StageInputKind::Parameter, 2);
|
|
Check(program.info.inputs.size() == 4 && input0 != nullptr &&
|
|
input0->component_count == 3 && input2 != nullptr &&
|
|
input2->component_count == 1 &&
|
|
FindInput(program.info, StageInputKind::VertexIndex) != nullptr &&
|
|
FindInput(program.info, StageInputKind::InstanceIndex) != nullptr &&
|
|
FindOutput(program.info, StageOutputKind::Position) != nullptr &&
|
|
FindOutput(program.info, StageOutputKind::Parameter, 2) != nullptr,
|
|
"vertex inputs or exports were not collected from lowered IR");
|
|
|
|
const auto info = program.info;
|
|
const auto resources_complete = program.resource_tracking_complete;
|
|
const auto srt_complete = program.srt_plan_complete;
|
|
const auto patching_complete = program.srt_patching_complete;
|
|
Check(!CollectShaderInfo(program, {.vertex = &vertex}, &error) &&
|
|
error.find("already collected") != std::string::npos &&
|
|
program.info == info && program.shader_info_complete &&
|
|
program.resource_tracking_complete == resources_complete &&
|
|
program.srt_plan_complete == srt_complete &&
|
|
program.srt_patching_complete == patching_complete,
|
|
"repeated shader info collection mutated the immutable interface");
|
|
}
|
|
|
|
void TestPixelShaderInfoCollection() {
|
|
Program program;
|
|
program.stage = ShaderType::Pixel;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {Export(4, ExportTargetKind::Mrt, 3),
|
|
Export(8, ExportTargetKind::MrtZ, 0, 0x4),
|
|
Export(12, ExportTargetKind::Mrt, 3),
|
|
Export(16, ExportTargetKind::Mrt, 7, 0)};
|
|
Prepare(program);
|
|
|
|
ShaderPixelInputInfo pixel;
|
|
pixel.ps_pos_x = true;
|
|
pixel.ps_front_face = true;
|
|
pixel.input_num = 2;
|
|
pixel.ps_depth_export_enable = true;
|
|
pixel.ps_sample_mask_export_enable = true;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.pixel = &pixel}, &error), error.c_str());
|
|
Check(program.info.inputs.size() == 4 &&
|
|
FindInput(program.info, StageInputKind::FragCoord) != nullptr &&
|
|
FindInput(program.info, StageInputKind::FrontFacing) != nullptr &&
|
|
FindInput(program.info, StageInputKind::Parameter, 0) != nullptr &&
|
|
FindInput(program.info, StageInputKind::Parameter, 1) != nullptr &&
|
|
program.info.outputs.size() == 2 &&
|
|
FindOutput(program.info, StageOutputKind::Mrt, 3) != nullptr &&
|
|
FindOutput(program.info, StageOutputKind::Mrt, 7) == nullptr &&
|
|
FindOutput(program.info, StageOutputKind::Depth) == nullptr &&
|
|
FindOutput(program.info, StageOutputKind::SampleMask) != nullptr,
|
|
"pixel inputs, disabled exports, or sample-mask-only MRTZ collection "
|
|
"was wrong");
|
|
|
|
Program depth_program;
|
|
depth_program.stage = ShaderType::Pixel;
|
|
depth_program.blocks.resize(1);
|
|
depth_program.blocks[0].instructions = {
|
|
Export(4, ExportTargetKind::MrtZ, 0, 0x1)};
|
|
Prepare(depth_program);
|
|
Check(CollectShaderInfo(depth_program, {.pixel = &pixel}, &error),
|
|
error.c_str());
|
|
Check(depth_program.info.outputs.size() == 1 &&
|
|
FindOutput(depth_program.info, StageOutputKind::Depth) != nullptr &&
|
|
FindOutput(depth_program.info, StageOutputKind::SampleMask) ==
|
|
nullptr,
|
|
"depth-only MRTZ export incorrectly enabled sample-mask output");
|
|
}
|
|
|
|
void TestShaderInfoCollectionIsTransactional() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
StageInput sentinel;
|
|
sentinel.kind = StageInputKind::Parameter;
|
|
sentinel.location = 9;
|
|
program.info.inputs.push_back(sentinel);
|
|
const auto info = program.info;
|
|
std::string error;
|
|
Check(!CollectShaderInfo(program, {}, &error) &&
|
|
error.find("not tracked") != std::string::npos &&
|
|
program.info == info && !program.shader_info_complete,
|
|
"pre-track shader info collection mutated program state");
|
|
|
|
Prepare(program);
|
|
const auto tracked_info = program.info;
|
|
const auto srt_complete = program.srt_plan_complete;
|
|
const auto patching_complete = program.srt_patching_complete;
|
|
Check(!CollectShaderInfo(program, {}, &error) &&
|
|
error.find("requires compute metadata") != std::string::npos &&
|
|
program.info == tracked_info && !program.shader_info_complete &&
|
|
program.resource_tracking_complete &&
|
|
program.srt_plan_complete == srt_complete &&
|
|
program.srt_patching_complete == patching_complete,
|
|
"missing stage metadata committed incomplete shader info");
|
|
program.stage = ShaderType::Unknown;
|
|
Check(!CollectShaderInfo(program, {}, &error) &&
|
|
error.find("unsupported shader stage") != std::string::npos &&
|
|
program.info == tracked_info && !program.shader_info_complete &&
|
|
program.resource_tracking_complete &&
|
|
program.srt_plan_complete == srt_complete &&
|
|
program.srt_patching_complete == patching_complete,
|
|
"unsupported-stage shader info collection was not transactional");
|
|
}
|
|
|
|
void TestShaderInfoMetadataValidation() {
|
|
std::string error;
|
|
|
|
Program vertex_program;
|
|
vertex_program.stage = ShaderType::Vertex;
|
|
vertex_program.blocks.resize(1);
|
|
Instruction bad_input;
|
|
bad_input.op = Opcode::LoadInputF32;
|
|
bad_input.input_info.attr = 0;
|
|
bad_input.input_info.chan = 4;
|
|
vertex_program.blocks[0].instructions = {bad_input};
|
|
Prepare(vertex_program);
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.resources_num = 1;
|
|
const auto vertex_info = vertex_program.info;
|
|
Check(!CollectShaderInfo(vertex_program, {.vertex = &vertex}, &error) &&
|
|
error.find("vertex input reference") != std::string::npos &&
|
|
vertex_program.info == vertex_info &&
|
|
!vertex_program.shader_info_complete,
|
|
"out-of-range vertex channel produced immutable malformed info");
|
|
vertex.resources_num = -1;
|
|
Check(!CollectShaderInfo(vertex_program, {.vertex = &vertex}, &error) &&
|
|
error.find("vertex resource count") != std::string::npos &&
|
|
vertex_program.info == vertex_info &&
|
|
!vertex_program.shader_info_complete,
|
|
"negative vertex resource count produced immutable malformed info");
|
|
|
|
Program pixel_program;
|
|
pixel_program.stage = ShaderType::Pixel;
|
|
pixel_program.blocks.resize(1);
|
|
Prepare(pixel_program);
|
|
ShaderPixelInputInfo pixel;
|
|
pixel.input_num = 33;
|
|
const auto pixel_info = pixel_program.info;
|
|
Check(!CollectShaderInfo(pixel_program, {.pixel = &pixel}, &error) &&
|
|
error.find("pixel input count") != std::string::npos &&
|
|
pixel_program.info == pixel_info &&
|
|
!pixel_program.shader_info_complete,
|
|
"out-of-range pixel input count produced immutable malformed info");
|
|
|
|
Program compute_program;
|
|
compute_program.stage = ShaderType::Compute;
|
|
compute_program.blocks.resize(1);
|
|
Prepare(compute_program);
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 4;
|
|
const auto compute_info = compute_program.info;
|
|
Check(!CollectShaderInfo(compute_program, {.compute = &compute}, &error) &&
|
|
error.find("thread ID count") != std::string::npos &&
|
|
compute_program.info == compute_info &&
|
|
!compute_program.shader_info_complete,
|
|
"out-of-range compute metadata produced immutable malformed info");
|
|
}
|
|
|
|
void TestTrackingRequiresSrtPatching() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {BufferUse(4, 0)};
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error),
|
|
error.c_str());
|
|
const auto source = program.blocks[0].instructions[0].memory.resource_source;
|
|
Check(!TrackResources(program, &error) &&
|
|
error.find("SRT reads were not patched") != std::string::npos &&
|
|
program.blocks[0].instructions[0].memory.resource_source ==
|
|
source &&
|
|
!program.resource_tracking_complete,
|
|
"resource tracking bypassed SRT patch completion");
|
|
Check(PatchSrtReads(program, &error) && TrackResources(program, &error),
|
|
error.c_str());
|
|
}
|
|
|
|
void TestDynamicSrtReadRemainsExplicit() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
auto load = ScalarLoad(i * 4, i, 16, i * 4);
|
|
load.src[0] = Sgpr(20);
|
|
load.src_count = 1;
|
|
insts.push_back(load);
|
|
}
|
|
insts.push_back(ImageUse(0x40, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D));
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
Check(program.srt.reads.empty() && program.srt.dynamic_reads.size() == 8 &&
|
|
program.srt_patching_complete,
|
|
"dynamic SRT offsets were incorrectly assigned fixed flat slots");
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
Check(insts[i].op == Opcode::SLoadDword &&
|
|
insts[i].memory.kind == ResourceKind::ScalarBuffer,
|
|
"dynamic SRT read was rewritten as an immediate flat load");
|
|
}
|
|
Check(TrackResources(program, &error), error.c_str());
|
|
}
|
|
|
|
void TestSrtPatchingFailureIsTransactional() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
insts.push_back(BufferUse(0x20, 0));
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && program.srt.reads.size() == 4,
|
|
error.c_str());
|
|
insts[0].scalar_value = ScalarProvenance::Undefined;
|
|
const auto instructions = insts;
|
|
const auto provenance = program.provenance;
|
|
const auto srt = program.srt;
|
|
Check(!PatchSrtReads(program, &error) &&
|
|
error.find("no scalar-load producer") != std::string::npos &&
|
|
insts == instructions && program.provenance == provenance &&
|
|
program.srt == srt && !program.srt_patching_complete,
|
|
"failed SRT patching partially changed the program");
|
|
}
|
|
|
|
void TestScalarMemoryGroupsSnapshotOperands() {
|
|
const auto CheckGroup = [](bool buffer) {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(buffer ? ScalarBufferLoad(4, 16 + i, 4, i * 4)
|
|
: ScalarLoad(4, 16 + i, 16, i * 4));
|
|
}
|
|
MakeScalarMemoryGroup(&insts);
|
|
insts.push_back(BufferUse(8, 16));
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && program.srt.reads.size() == 4 &&
|
|
PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
Check(insts[i].op == Opcode::LoadSrtDword && insts[i].src[0].imm == i,
|
|
"overlapping scalar-memory group did not snapshot its operands");
|
|
}
|
|
};
|
|
CheckGroup(false);
|
|
CheckGroup(true);
|
|
|
|
const auto CheckOffsetOverlap = [](bool buffer) {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
auto load = buffer ? ScalarBufferLoad(4, 20 + i, 4, i * 4)
|
|
: ScalarLoad(4, 20 + i, 16, i * 4);
|
|
load.src[0] = Sgpr(20);
|
|
load.src_count = 1;
|
|
insts.push_back(load);
|
|
}
|
|
MakeScalarMemoryGroup(&insts);
|
|
insts.push_back(BufferUse(8, 20));
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) &&
|
|
program.srt.dynamic_reads.size() == 4 &&
|
|
PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
const auto value = insts[i].scalar_value;
|
|
const auto &node = program.provenance.values[value];
|
|
const auto offset_arg = buffer ? 4u : 2u;
|
|
Check(
|
|
program.provenance.values[node.args[offset_arg]].op ==
|
|
ScalarValueOp::UserData &&
|
|
program.provenance.values[node.args[offset_arg]].imm == 20 &&
|
|
insts[i].op ==
|
|
(buffer ? Opcode::SBufferLoadDword : Opcode::SLoadDword),
|
|
"overlapping scalar-memory offset was read after a component write");
|
|
}
|
|
};
|
|
CheckOffsetOverlap(false);
|
|
CheckOffsetOverlap(true);
|
|
}
|
|
|
|
void TestSrtPatchingHandlesGvnAndMoveForwarding() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t copy = 0; copy < 2; copy++) {
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(ScalarLoad(copy * 0x20 + i * 4, copy * 4 + i, 16, i * 4));
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(Move(0x40 + i * 4, 8 + i, i));
|
|
}
|
|
insts.push_back(BufferUse(0x60, 4));
|
|
insts.push_back(BufferUse(0x64, 8));
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && program.srt.reads.size() == 4 &&
|
|
PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
Check(insts[i].op == Opcode::LoadSrtDword && insts[i].src[0].imm == i % 4,
|
|
"all producers of a GVN'd SRT read were not patched");
|
|
}
|
|
for (uint32_t i = 8; i < 12; i++) {
|
|
Check(insts[i].op == Opcode::MoveU32,
|
|
"move forwarding was incorrectly rewritten as an SRT load");
|
|
}
|
|
}
|
|
|
|
void TestSrtPatchingHandlesCfgProducers() {
|
|
Program program;
|
|
program.blocks.resize(4);
|
|
program.blocks[0].successors = {1, 2};
|
|
program.blocks[1].predecessors = {0};
|
|
program.blocks[1].successors = {3};
|
|
program.blocks[2].predecessors = {0};
|
|
program.blocks[2].successors = {3};
|
|
program.blocks[3].predecessors = {1, 2};
|
|
for (uint32_t block = 1; block <= 2; block++) {
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
program.blocks[block].instructions.push_back(
|
|
ScalarLoad(block * 0x20 + i * 4, i, 16, i * 4));
|
|
}
|
|
}
|
|
program.blocks[3].instructions = {BufferUse(0x60, 0)};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && program.srt.reads.size() == 4 &&
|
|
PatchSrtReads(program, &error),
|
|
error.c_str());
|
|
for (uint32_t block = 1; block <= 2; block++) {
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
const auto &inst = program.blocks[block].instructions[i];
|
|
Check(inst.op == Opcode::LoadSrtDword && inst.src[0].imm == i,
|
|
"CFG-equivalent SRT producer was not patched");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestSrtPatchPlanValidation() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
insts.push_back(BufferUse(0x20, 0));
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error),
|
|
error.c_str());
|
|
program.srt.reads = {program.srt.reads[0], program.srt.reads[0]};
|
|
const auto instructions = insts;
|
|
Check(!PatchSrtReads(program, &error) &&
|
|
error.find("dense value-to-offset bijection") !=
|
|
std::string::npos &&
|
|
insts == instructions && !program.srt_patching_complete,
|
|
"duplicate SRT flat slots were accepted or partially patched");
|
|
|
|
Program dynamic;
|
|
dynamic.blocks.resize(1);
|
|
dynamic.blocks[0].instructions = {BufferUse(4, 0)};
|
|
Check(BuildScalarProvenance(dynamic, &error) && BuildSrtPlan(dynamic, &error),
|
|
error.c_str());
|
|
dynamic.srt.dynamic_sources = {ScalarProvenance::Undefined};
|
|
Check(!PatchSrtReads(dynamic, &error) &&
|
|
error.find("invalid dynamic descriptor source") !=
|
|
std::string::npos &&
|
|
!dynamic.srt_patching_complete,
|
|
"undefined dynamic descriptor source was accepted");
|
|
}
|
|
|
|
void TestMaterializationSharesReadConstEvaluation() {
|
|
Program program;
|
|
program.stage = ShaderType::Pixel;
|
|
program.shader_hash = 0x10203040;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(MoveImmediate(0x20 + i * 4, 8 + i, 0xa0 + i));
|
|
}
|
|
insts.push_back(ImageUse(0x40, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D));
|
|
insts.push_back(ImageUse(0x44, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D));
|
|
Prepare(program);
|
|
Check(program.info.images.size() == 2 && program.info.samplers.size() == 1,
|
|
"materialization test did not preserve sampled/storage view topology");
|
|
Check(program.srt.reads.size() == 8 && program.srt_patching_complete,
|
|
"immediate descriptor reads did not produce a compact SRT patch plan");
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
Check(insts[i].op == Opcode::LoadSrtDword && insts[i].src_count == 1 &&
|
|
insts[i].src[0].kind == OperandKind::ImmediateU32 &&
|
|
insts[i].src[0].imm == i &&
|
|
insts[i].memory.kind == ResourceKind::None,
|
|
"immediate SRT read was not patched to its dense flat-buffer slot");
|
|
}
|
|
std::string error;
|
|
Check(!PatchSrtReads(program, &error) &&
|
|
error.find("already patched") != std::string::npos,
|
|
"repeated SRT patching was accepted");
|
|
|
|
TestMemory memory;
|
|
for (uint32_t i = 0; i < memory.words.size(); i++) {
|
|
memory.words[i] = 0x100 + i;
|
|
}
|
|
memory.words[3] |= Prospero::GpuEnumValue(Prospero::ImageType::kColor2D)
|
|
<< 28u;
|
|
std::array<uint32_t, 32> user_data{};
|
|
user_data[16] = static_cast<uint32_t>(memory.base);
|
|
user_data[17] = static_cast<uint32_t>(memory.base >> 32u);
|
|
SrtRuntime runtime{user_data, 0, ReadTestMemory, &memory};
|
|
ResourceSnapshot snapshot;
|
|
error.clear();
|
|
Check(MaterializeResources(program, runtime, snapshot, &error),
|
|
error.c_str());
|
|
Check(snapshot.buffers.empty() && snapshot.images.size() == 2 &&
|
|
snapshot.samplers.size() == 1 &&
|
|
snapshot.images[0] == snapshot.images[1],
|
|
"dense runtime snapshot did not preserve resource order or aliases");
|
|
Check(snapshot.flattened_srt.size() == 8 &&
|
|
std::equal(snapshot.flattened_srt.begin(),
|
|
snapshot.flattened_srt.end(), memory.words.begin()) &&
|
|
snapshot.user_data ==
|
|
std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
|
"runtime snapshot omitted flattened SRT or current user data");
|
|
Check(memory.reads == 8, "aliased image views repeated ReadConst evaluation "
|
|
"instead of sharing it");
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
Check(snapshot.images[0].dwords[i] == memory.words[i],
|
|
"materialized image descriptor contains the wrong dword");
|
|
}
|
|
}
|
|
|
|
void TestInvalidImagesMaterializeAsNull() {
|
|
Program sampled;
|
|
sampled.stage = ShaderType::Pixel;
|
|
sampled.user_data_count = 8;
|
|
sampled.blocks.resize(1);
|
|
sampled.blocks[0].instructions = {ImageUse(0x40, Opcode::ImageLoad,
|
|
ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D)};
|
|
Prepare(sampled);
|
|
Check(sampled.info.images.size() == 1 && sampled.info.samplers.empty(),
|
|
"sampled-image normalization test has unexpected resource topology");
|
|
|
|
constexpr std::array<uint32_t, 8> stale = {0x00000004, 0x00000004, 0xc0061060,
|
|
0x06000514, 0x20010000, 0xa4580290,
|
|
0x00000004, 0x00000001};
|
|
std::string error;
|
|
for (uint32_t type = 0; type < 8; type++) {
|
|
auto descriptor = stale;
|
|
descriptor[3] = (descriptor[3] & 0x0fffffffu) | (type << 28u);
|
|
ResourceSnapshot snapshot;
|
|
Check(MaterializeResources(sampled, {descriptor}, snapshot, &error) &&
|
|
snapshot.images.size() == 1 &&
|
|
std::all_of(snapshot.images[0].dwords.begin(),
|
|
snapshot.images[0].dwords.end(),
|
|
[](uint32_t word) { return word == 0; }) &&
|
|
ValidateResourceSpecialization(sampled, snapshot, &error),
|
|
error.c_str());
|
|
}
|
|
|
|
constexpr std::array<uint32_t, 8> packet = {
|
|
0xc0071058, 0xe80eeeb8, 0x00000000, 0xffffffff,
|
|
0xffffffff, 0x00000001, 0x00000000, 0x00000113};
|
|
ResourceSnapshot packet_snapshot;
|
|
Check(MaterializeResources(sampled, {packet}, packet_snapshot, &error) &&
|
|
std::all_of(packet_snapshot.images[0].dwords.begin(),
|
|
packet_snapshot.images[0].dwords.end(),
|
|
[](uint32_t word) { return word == 0; }),
|
|
"invalid MSAA image words were not normalized to null");
|
|
|
|
struct MsaaCase {
|
|
uint32_t base_level;
|
|
uint32_t fragments;
|
|
uint32_t max_mip;
|
|
bool valid;
|
|
};
|
|
constexpr std::array msaa_cases = {
|
|
MsaaCase{0, 1, 1, true}, MsaaCase{0, 2, 2, true},
|
|
MsaaCase{0, 3, 3, true}, MsaaCase{1, 1, 1, false},
|
|
MsaaCase{0, 2, 1, false}, MsaaCase{0, 0, 0, false},
|
|
MsaaCase{0, 4, 4, false},
|
|
};
|
|
constexpr std::array msaa_types = {
|
|
Prospero::ImageType::kColor2DMsaa,
|
|
Prospero::ImageType::kColor2DMsaaArray,
|
|
};
|
|
for (const auto type : msaa_types) {
|
|
for (const auto& test : msaa_cases) {
|
|
std::array<uint32_t, 8> msaa{};
|
|
msaa[0] = 1;
|
|
msaa[1] = 36u << 20u;
|
|
msaa[3] = (Prospero::GpuEnumValue(type) << 28u) |
|
|
(test.base_level << 12u) | (test.fragments << 16u);
|
|
msaa[5] = test.max_mip << 4u;
|
|
ResourceSnapshot msaa_snapshot;
|
|
const auto materialized =
|
|
MaterializeResources(sampled, {msaa}, msaa_snapshot, &error);
|
|
const auto preserved =
|
|
materialized &&
|
|
std::equal(msaa.begin(), msaa.end(),
|
|
msaa_snapshot.images[0].dwords.begin());
|
|
const auto is_null =
|
|
materialized &&
|
|
std::all_of(msaa_snapshot.images[0].dwords.begin(),
|
|
msaa_snapshot.images[0].dwords.end(),
|
|
[](uint32_t word) { return word == 0; });
|
|
Check(materialized && (test.valid ? preserved : is_null),
|
|
"MSAA image descriptor validity mismatch");
|
|
}
|
|
}
|
|
|
|
auto valid = stale;
|
|
valid[3] = (valid[3] & 0x0fffffffu) |
|
|
(Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) << 28u);
|
|
ResourceSnapshot valid_snapshot;
|
|
Check(MaterializeResources(sampled, {valid}, valid_snapshot, &error) &&
|
|
std::equal(valid.begin(), valid.end(),
|
|
valid_snapshot.images[0].dwords.begin()),
|
|
"valid sampled image descriptor was normalized");
|
|
|
|
Program storage;
|
|
storage.stage = ShaderType::Compute;
|
|
storage.user_data_count = 8;
|
|
storage.blocks.resize(1);
|
|
storage.blocks[0].instructions = {ImageUse(0x40, Opcode::ImageStore,
|
|
ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D)};
|
|
Prepare(storage);
|
|
ResourceSnapshot storage_snapshot;
|
|
Check(MaterializeResources(storage, {stale}, storage_snapshot, &error) &&
|
|
std::all_of(storage_snapshot.images[0].dwords.begin(),
|
|
storage_snapshot.images[0].dwords.end(),
|
|
[](uint32_t word) { return word == 0; }),
|
|
"invalid storage image descriptor was not normalized to null");
|
|
}
|
|
|
|
void TestInvalidBuffersMaterializeAsNull() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.user_data_count = 4;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {BufferUse(0x40, 0)};
|
|
Prepare(program);
|
|
|
|
constexpr std::array<uint32_t, 4> valid = {
|
|
0x0000100c, 0x00100000, 0x00000001, 0x0004dfac};
|
|
auto invalid = valid;
|
|
invalid[3] |= 1u << 30u;
|
|
std::string error;
|
|
ResourceSnapshot snapshot;
|
|
Check(MaterializeResources(program, {invalid}, snapshot, &error) &&
|
|
std::all_of(snapshot.buffers[0].dwords.begin(),
|
|
snapshot.buffers[0].dwords.begin() + 4,
|
|
[](uint32_t word) { return word == 0; }),
|
|
"invalid buffer descriptor was not normalized to null");
|
|
Check(MaterializeResources(program, {valid}, snapshot, &error) &&
|
|
std::equal(valid.begin(), valid.end(),
|
|
snapshot.buffers[0].dwords.begin()),
|
|
"valid buffer descriptor was normalized");
|
|
}
|
|
|
|
void TestMaterializationFailureIsTransactional() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
insts.push_back(ImageUse(0x40, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D));
|
|
Prepare(program);
|
|
|
|
TestMemory memory;
|
|
memory.fail_after = 3;
|
|
std::array<uint32_t, 32> user_data{};
|
|
user_data[16] = static_cast<uint32_t>(memory.base);
|
|
user_data[17] = static_cast<uint32_t>(memory.base >> 32u);
|
|
SrtRuntime runtime{user_data, 0, ReadTestMemory, &memory};
|
|
ResourceSnapshot snapshot;
|
|
DescriptorValue sentinel;
|
|
sentinel.dword_count = 1;
|
|
sentinel.dwords[0] = 777;
|
|
snapshot.images.push_back(sentinel);
|
|
std::string error;
|
|
Check(!MaterializeResources(program, runtime, snapshot, &error) &&
|
|
error.find("failed at") != std::string::npos &&
|
|
snapshot.images.size() == 1 &&
|
|
snapshot.images[0].dwords[0] == 777 &&
|
|
snapshot.images[0].dword_count == 1,
|
|
"failed runtime materialization partially replaced the prior snapshot");
|
|
}
|
|
|
|
void TestResourceSpecializationIsTypedAndTransactional() {
|
|
Program null_program;
|
|
null_program.stage = ShaderType::Compute;
|
|
null_program.blocks.resize(1);
|
|
null_program.blocks[0].instructions = {
|
|
ImageUse(0x10, Opcode::ImageLoad, ResourceKind::ImageUint,
|
|
Decoder::ImageDimension::Dim3D)};
|
|
Prepare(null_program);
|
|
ResourceSnapshot null_snapshot;
|
|
null_snapshot.images.resize(1);
|
|
null_snapshot.images[0].dword_count = 8;
|
|
null_snapshot.images[0].dwords[1] = 0x12345600u;
|
|
null_snapshot.images[0].dwords[2] = 0x89abcdefu;
|
|
null_snapshot.images[0].dwords[3] = 0x01234567u;
|
|
std::string error;
|
|
Check(
|
|
SpecializeResources(null_program, null_snapshot, &error) &&
|
|
ValidateResourceSpecialization(null_program, null_snapshot, &error) &&
|
|
null_program.info.images[0].kind == ResourceKind::Image &&
|
|
null_program.info.images[0].dimension ==
|
|
Decoder::ImageDimension::Dim2D &&
|
|
null_program.blocks[0].instructions[0].memory.image_dimension ==
|
|
Decoder::ImageDimension::Dim2D,
|
|
"zero-base image descriptor did not use the canonical null-image shape");
|
|
|
|
Program null_atomic;
|
|
null_atomic.stage = ShaderType::Compute;
|
|
null_atomic.blocks.resize(1);
|
|
null_atomic.blocks[0].instructions = {
|
|
ImageUse(0x14, Opcode::AtomicAddU32, ResourceKind::StorageImageUint,
|
|
Decoder::ImageDimension::Dim1D)};
|
|
Prepare(null_atomic);
|
|
Check(SpecializeResources(null_atomic, null_snapshot, &error) &&
|
|
ValidateResourceSpecialization(null_atomic, null_snapshot, &error) &&
|
|
null_atomic.info.images[0].kind ==
|
|
ResourceKind::StorageImageUint &&
|
|
null_atomic.info.images[0].dimension ==
|
|
Decoder::ImageDimension::Dim2D,
|
|
"null image atomic did not preserve its required integer storage type");
|
|
|
|
Program array_view;
|
|
array_view.stage = ShaderType::Compute;
|
|
array_view.blocks.resize(1);
|
|
array_view.blocks[0].instructions = {
|
|
ImageUse(0x18, Opcode::ImageLoad, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim1D)};
|
|
Prepare(array_view);
|
|
ResourceSnapshot array_snapshot;
|
|
array_snapshot.images.resize(1);
|
|
array_snapshot.images[0].dword_count = 8;
|
|
array_snapshot.images[0].dwords[0] = 0x1000;
|
|
array_snapshot.images[0].dwords[3] =
|
|
Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray) << 28u;
|
|
Check(SpecializeResources(array_view, array_snapshot, &error) &&
|
|
array_view.info.images[0].dimension ==
|
|
Decoder::ImageDimension::Dim1D &&
|
|
array_view.blocks[0].instructions[0].memory.image_dimension ==
|
|
Decoder::ImageDimension::Dim1D,
|
|
"non-array MIMG view did not narrow a 1D-array descriptor");
|
|
auto null_after_1d = array_snapshot;
|
|
null_after_1d.images[0].dwords.fill(0);
|
|
Check(!ValidateResourceSpecialization(array_view, null_after_1d, &error) &&
|
|
error.find("canonical null specialization") != std::string::npos,
|
|
"non-null 1D specialization accepted a canonical 2D null descriptor");
|
|
|
|
Program cross_family_array;
|
|
cross_family_array.stage = ShaderType::Compute;
|
|
cross_family_array.blocks.resize(1);
|
|
cross_family_array.blocks[0].instructions = {
|
|
ImageUse(0x1c, Opcode::ImageLoad, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2DArray)};
|
|
Prepare(cross_family_array);
|
|
Check(SpecializeResources(cross_family_array, array_snapshot, &error) &&
|
|
cross_family_array.info.images[0].dimension ==
|
|
Decoder::ImageDimension::Dim1DArray &&
|
|
cross_family_array.blocks[0]
|
|
.instructions[0]
|
|
.memory.image_dimension ==
|
|
Decoder::ImageDimension::Dim1DArray,
|
|
"array MIMG intent did not produce a 1D-array view");
|
|
|
|
Program cross_family_2d_array;
|
|
cross_family_2d_array.stage = ShaderType::Compute;
|
|
cross_family_2d_array.blocks.resize(1);
|
|
cross_family_2d_array.blocks[0].instructions = {
|
|
ImageUse(0x20, Opcode::ImageLoad, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim1DArray)};
|
|
Prepare(cross_family_2d_array);
|
|
auto array_2d_snapshot = array_snapshot;
|
|
array_2d_snapshot.images[0].dwords[3] =
|
|
Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) << 28u;
|
|
Check(SpecializeResources(cross_family_2d_array, array_2d_snapshot, &error) &&
|
|
cross_family_2d_array.info.images[0].dimension ==
|
|
Decoder::ImageDimension::Dim2DArray,
|
|
"array MIMG intent did not produce a 2D-array view");
|
|
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {ImageUse(0x20, Opcode::ImageStore,
|
|
ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim3D)};
|
|
Prepare(program);
|
|
|
|
ResourceSnapshot snapshot;
|
|
snapshot.images.resize(1);
|
|
snapshot.images[0].dword_count = 7;
|
|
const auto info = program.info;
|
|
const auto memory = program.blocks[0].instructions[0].memory;
|
|
error.clear();
|
|
Check(!ValidateResourceSnapshot(program, snapshot, &error) &&
|
|
error.find("image descriptor 0 has 7 dwords") != std::string::npos,
|
|
"malformed resource snapshot was accepted");
|
|
Check(!SpecializeResources(program, snapshot, &error) &&
|
|
program.info == info &&
|
|
program.blocks[0].instructions[0].memory == memory,
|
|
"failed resource specialization partially mutated the program");
|
|
|
|
snapshot.images[0].dword_count = 8;
|
|
snapshot.images[0].dwords[0] = 0x1000;
|
|
snapshot.images[0].dwords[1] =
|
|
Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) << 20u;
|
|
snapshot.images[0].dwords[3] =
|
|
(Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) << 28u) | 0x3acu;
|
|
Check(SpecializeResources(program, snapshot, &error), error.c_str());
|
|
const auto &image = program.info.images[0];
|
|
const auto &inst = program.blocks[0].instructions[0];
|
|
Check(image.kind == ResourceKind::StorageImageUint &&
|
|
image.dimension == Decoder::ImageDimension::Dim2D &&
|
|
image.storage_swizzle == 0x3acu &&
|
|
inst.memory.kind == ResourceKind::StorageImageUint &&
|
|
inst.memory.image_dimension == Decoder::ImageDimension::Dim2D,
|
|
"runtime descriptor shape and integer format did not specialize dense "
|
|
"IR");
|
|
auto null_after_uint = snapshot;
|
|
null_after_uint.images[0].dwords.fill(0);
|
|
Check(!ValidateResourceSpecialization(program, null_after_uint, &error) &&
|
|
error.find("canonical null specialization") != std::string::npos,
|
|
"non-null integer specialization accepted a non-integer null descriptor");
|
|
|
|
auto stale_swizzle = snapshot;
|
|
stale_swizzle.images[0].dwords[3] =
|
|
(stale_swizzle.images[0].dwords[3] & ~0xfffu) |
|
|
StorageImageIdentitySwizzle;
|
|
Check(!ValidateResourceSpecialization(program, stale_swizzle, &error) &&
|
|
error.find("changed swizzle") != std::string::npos,
|
|
"storage image cache validation ignored a SPIR-V-baked swizzle");
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
const auto *binding =
|
|
FindBinding(program.bindings, DescriptorBindingKind::StorageUint2D);
|
|
Check(binding != nullptr &&
|
|
binding->resources == std::vector<uint32_t>({0}) &&
|
|
FindBinding(program.bindings, DescriptorBindingKind::Storage3D) ==
|
|
nullptr,
|
|
"specialized image topology did not reach the exact native binding "
|
|
"group");
|
|
}
|
|
|
|
void TestRuntimeSpecializationCoversBakedBufferAndAddressFields() {
|
|
Program buffer_program;
|
|
buffer_program.stage = ShaderType::Compute;
|
|
buffer_program.blocks.resize(1);
|
|
buffer_program.blocks[0].instructions = {BufferUse(0, 0)};
|
|
Prepare(buffer_program);
|
|
|
|
ResourceSnapshot buffer_snapshot;
|
|
buffer_snapshot.buffers.resize(1);
|
|
buffer_snapshot.buffers[0].dword_count = 4;
|
|
buffer_snapshot.buffers[0].dwords[1] = (16u << 16u) | (1u << 31u);
|
|
buffer_snapshot.buffers[0].dwords[3] =
|
|
(Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) << 12u) |
|
|
(2u << 21u) | (1u << 23u);
|
|
std::string error;
|
|
Check(SpecializeResources(buffer_program, buffer_snapshot, &error) &&
|
|
ValidateResourceSpecialization(buffer_program, buffer_snapshot,
|
|
&error),
|
|
error.c_str());
|
|
Check(buffer_program.info.buffers[0].packed_stride ==
|
|
(16u | (1u << 14u) | (2u << 16u) | (1u << 20u)) &&
|
|
buffer_program.info.buffers[0].descriptor_format ==
|
|
Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt),
|
|
"buffer specialization omitted SPIR-V-baked descriptor fields");
|
|
auto stale_buffer = buffer_snapshot;
|
|
stale_buffer.buffers[0].dwords[1] ^= 4u << 16u;
|
|
Check(!ValidateResourceSpecialization(buffer_program, stale_buffer, &error) &&
|
|
error.find("buffer descriptor 0") != std::string::npos,
|
|
"stale buffer stride reused incompatible specialized SPIR-V");
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(CollectShaderInfo(buffer_program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(buffer_program, {}, &error),
|
|
error.c_str());
|
|
Check(buffer_program.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({0, 1, 2, 3}),
|
|
"buffer fixture did not allocate its descriptor user-data roots");
|
|
buffer_snapshot.user_data.resize(4);
|
|
Check(ValidateResourceSpecialization(buffer_program, buffer_snapshot, &error),
|
|
error.c_str());
|
|
auto truncated_user_data = buffer_snapshot;
|
|
truncated_user_data.user_data.resize(3);
|
|
Check(
|
|
!ValidateResourceSnapshot(buffer_program, truncated_user_data, &error) &&
|
|
error.find("user SGPR 3") != std::string::npos,
|
|
"truncated cache snapshot user-data window was accepted");
|
|
|
|
Program based_program;
|
|
based_program.stage = ShaderType::Compute;
|
|
based_program.blocks.resize(1);
|
|
auto raw = ScalarLoad(8, 0, 16, 0);
|
|
raw.memory.offset = static_cast<uint32_t>(-4);
|
|
raw.src[0] = Sgpr(20);
|
|
raw.src_count = 1;
|
|
based_program.blocks[0].instructions = {MoveImmediate(0, 16, 0x1000),
|
|
MoveImmediate(4, 17, 0), raw};
|
|
Prepare(based_program);
|
|
ResourceSnapshot based_snapshot;
|
|
Check(MaterializeResources(based_program, {}, based_snapshot, &error) &&
|
|
SpecializeResources(based_program, based_snapshot, &error),
|
|
error.c_str());
|
|
auto relocated = based_snapshot;
|
|
relocated.addresses[0].guest_base += 0x1000;
|
|
relocated.addresses[0].binding_base += 0x1000;
|
|
Check(ValidateResourceSpecialization(based_program, relocated, &error),
|
|
"relocated based address with identical relative bias changed "
|
|
"specialization");
|
|
relocated.addresses[0].binding_base++;
|
|
Check(!ValidateResourceSpecialization(based_program, relocated, &error) &&
|
|
error.find("address resource 0") != std::string::npos,
|
|
"stale based-address bias reused incompatible specialized SPIR-V");
|
|
|
|
Program flat_program;
|
|
flat_program.stage = ShaderType::Compute;
|
|
flat_program.blocks.resize(1);
|
|
Instruction flat;
|
|
flat.op = Opcode::FlatLoadDword;
|
|
flat.memory.kind = ResourceKind::Flat;
|
|
flat_program.blocks[0].instructions = {flat};
|
|
Prepare(flat_program);
|
|
ResourceSnapshot flat_snapshot;
|
|
flat_snapshot.addresses = {{0x11u, 0x10u}};
|
|
flat_snapshot.user_data = {0xdeadbeefu};
|
|
const auto prior_flat_snapshot = flat_snapshot;
|
|
Check(
|
|
!MaterializeResources(flat_program, {}, flat_snapshot, &error) &&
|
|
error.find("requires runtime guest-address translation") !=
|
|
std::string::npos &&
|
|
flat_snapshot.addresses == prior_flat_snapshot.addresses &&
|
|
flat_snapshot.user_data == prior_flat_snapshot.user_data,
|
|
"unbased flat memory without a translator did not fail transactionally");
|
|
SrtRuntime flat_runtime;
|
|
flat_runtime.flat_memory_base = 0x100000000ull;
|
|
Check(
|
|
MaterializeResources(flat_program, flat_runtime, flat_snapshot, &error) &&
|
|
SpecializeResources(flat_program, flat_snapshot, &error),
|
|
error.c_str());
|
|
auto stale_flat = flat_snapshot;
|
|
stale_flat.addresses[0].guest_base += 0x1000;
|
|
stale_flat.addresses[0].binding_base += 0x1000;
|
|
Check(!ValidateResourceSpecialization(flat_program, stale_flat, &error),
|
|
"unbased flat memory reused an absolute base baked into SPIR-V");
|
|
}
|
|
|
|
void TestTrackedProgramIsImmutable() {
|
|
Program program;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
insts.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
insts.push_back(MoveImmediate(0x20 + i * 4, 8 + i, 0xa0 + i));
|
|
insts.push_back(MoveImmediate(0x30 + i * 4, 12 + i, 0xb0 + i));
|
|
}
|
|
insts.push_back(ImageUse(0x50, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D));
|
|
insts.push_back(ImageUse(0x54, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D));
|
|
insts.push_back(BufferUse(0x58, 12));
|
|
Prepare(program);
|
|
Check(program.info.buffers.size() == 1 && program.info.images.size() == 2 &&
|
|
program.info.samplers.size() == 1 &&
|
|
program.info.sampled_pairs.size() == 1,
|
|
"post-track immutability fixture lacks complete resource topology");
|
|
|
|
const auto provenance = program.provenance;
|
|
const auto srt = program.srt;
|
|
const auto info = program.info;
|
|
const auto srt_complete = program.srt_plan_complete;
|
|
const auto tracking_complete = program.resource_tracking_complete;
|
|
const auto patching_complete = program.srt_patching_complete;
|
|
std::vector<MemoryInfo> memory;
|
|
memory.reserve(insts.size());
|
|
for (const auto &inst : insts) {
|
|
memory.push_back(inst.memory);
|
|
}
|
|
|
|
const auto CheckUnchanged = [&]() {
|
|
Check(program.provenance == provenance && program.srt == srt &&
|
|
program.info == info &&
|
|
program.srt_plan_complete == srt_complete &&
|
|
program.srt_patching_complete == patching_complete &&
|
|
program.resource_tracking_complete == tracking_complete &&
|
|
insts.size() == memory.size(),
|
|
"rejected post-track pass mutated immutable program state");
|
|
for (uint32_t i = 0; i < memory.size(); i++) {
|
|
Check(
|
|
insts[i].memory == memory[i],
|
|
"rejected post-track pass mutated a dense operand or source handle");
|
|
}
|
|
};
|
|
|
|
std::string error;
|
|
Check(!BuildScalarProvenance(program, &error) &&
|
|
error.find("after resource tracking") != std::string::npos,
|
|
"post-track provenance rebuild was accepted");
|
|
CheckUnchanged();
|
|
Check(!BuildSrtPlan(program, &error) &&
|
|
error.find("after resource tracking") != std::string::npos,
|
|
"post-track SRT rebuild was accepted");
|
|
CheckUnchanged();
|
|
}
|
|
|
|
void TestNativeBindingLayout() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
insts.push_back(BufferUse(4, 48));
|
|
insts.push_back(ImageUse(8, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2D, 0, 2));
|
|
insts.push_back(ImageUse(12, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim2DArray, 0, 2));
|
|
insts.push_back(ImageUse(16, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim3D, 4));
|
|
insts.push_back(ImageUse(20, Opcode::ImageStore,
|
|
ResourceKind::StorageImageUint,
|
|
Decoder::ImageDimension::Dim2DArray, 6));
|
|
Prepare(program);
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {.descriptor_set = 3}, &error),
|
|
error.c_str());
|
|
const auto *buffers =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Buffers);
|
|
const auto *sampled2d =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Sampled2D);
|
|
const auto *sampled_array =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Sampled2DArray);
|
|
const auto *storage3d =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Storage3D);
|
|
const auto *storage_uint_array =
|
|
FindBinding(program.bindings, DescriptorBindingKind::StorageUint2DArray);
|
|
const auto *samplers =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Samplers);
|
|
const auto *shader_data =
|
|
FindBinding(program.bindings, DescriptorBindingKind::UserData);
|
|
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
|
Check(
|
|
program.bindings.descriptor_set == 3 && buffers != nullptr &&
|
|
sampled2d != nullptr && sampled_array != nullptr &&
|
|
storage3d != nullptr && storage_uint_array != nullptr &&
|
|
samplers != nullptr && buffers->binding == 0 &&
|
|
sampled2d->binding == 1 && sampled_array->binding == 2 &&
|
|
storage3d->binding == 3 && storage_uint_array->binding == 4 &&
|
|
samplers->binding == 5 &&
|
|
buffers->resources == std::vector<uint32_t>{0} &&
|
|
sampled2d->resources == std::vector<uint32_t>{0} &&
|
|
sampled_array->resources == std::vector<uint32_t>{1} &&
|
|
storage3d->resources == std::vector<uint32_t>{2} &&
|
|
storage_uint_array->resources == std::vector<uint32_t>{3} &&
|
|
samplers->resources == std::vector<uint32_t>{0} &&
|
|
!program.bindings.user_data_registers.empty() &&
|
|
program.bindings.buffer_offset_dword ==
|
|
program.bindings.user_data_registers.size() &&
|
|
program.bindings.buffer_offset_count == 1 &&
|
|
((program.bindings.push_constant_size ==
|
|
shader_data_dwords * sizeof(uint32_t) &&
|
|
shader_data == nullptr) ||
|
|
(program.bindings.push_constant_size == 0 &&
|
|
shader_data != nullptr && shader_data->binding == 6)) &&
|
|
FindBinding(program.bindings, DescriptorBindingKind::FlattenedSrt) ==
|
|
nullptr &&
|
|
program.binding_layout_complete,
|
|
"native binding allocator did not preserve dense typed resource groups");
|
|
}
|
|
|
|
void TestNativeBindingLayoutOneDimensionalImages() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
insts.push_back(ImageUse(8, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim1D, 0, 2));
|
|
insts.push_back(ImageUse(12, Opcode::ImageSample, ResourceKind::Image,
|
|
Decoder::ImageDimension::Dim1DArray, 0, 2));
|
|
insts.push_back(ImageUse(16, Opcode::ImageStore, ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim1D, 4));
|
|
insts.push_back(ImageUse(20, Opcode::ImageStore,
|
|
ResourceKind::StorageImageUint,
|
|
Decoder::ImageDimension::Dim1DArray, 6));
|
|
Prepare(program);
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
const auto *sampled =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Sampled1D);
|
|
const auto *sampled_array =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Sampled1DArray);
|
|
const auto *storage =
|
|
FindBinding(program.bindings, DescriptorBindingKind::Storage1D);
|
|
const auto *storage_uint_array =
|
|
FindBinding(program.bindings, DescriptorBindingKind::StorageUint1DArray);
|
|
Check(sampled != nullptr && sampled_array != nullptr && storage != nullptr &&
|
|
storage_uint_array != nullptr &&
|
|
sampled->resources == std::vector<uint32_t>{0} &&
|
|
sampled_array->resources == std::vector<uint32_t>{1} &&
|
|
storage->resources == std::vector<uint32_t>{2} &&
|
|
storage_uint_array->resources == std::vector<uint32_t>{3},
|
|
"binding allocator did not preserve first-class 1D image groups");
|
|
}
|
|
|
|
void TestNativeBindingLayoutSrtAndUserDataOverflow() {
|
|
Program srt;
|
|
srt.stage = ShaderType::Compute;
|
|
srt.blocks.resize(1);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
srt.blocks[0].instructions.push_back(ScalarLoad(i * 4, i, 16, i * 4));
|
|
}
|
|
srt.blocks[0].instructions.push_back(BufferUse(0x20, 0));
|
|
Prepare(srt);
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
Check(CollectShaderInfo(srt, {.compute = &compute}, &error) &&
|
|
AllocateBindings(srt, {}, &error),
|
|
error.c_str());
|
|
const auto *flat =
|
|
FindBinding(srt.bindings, DescriptorBindingKind::FlattenedSrt);
|
|
Check(flat != nullptr && flat->binding == 1 && flat->resources.empty(),
|
|
"flattened SRT did not receive one native backend binding");
|
|
|
|
Program overflow;
|
|
overflow.stage = ShaderType::Compute;
|
|
overflow.blocks.resize(1);
|
|
for (uint32_t i = 0; i < 33; i++) {
|
|
Instruction direct;
|
|
direct.pc = i * 4;
|
|
direct.op = Opcode::MoveU32;
|
|
direct.dst.kind = OperandKind::Register;
|
|
direct.dst.reg = {RegisterFile::Vector, i};
|
|
direct.src[0] = Sgpr(i);
|
|
direct.src_count = 1;
|
|
overflow.blocks[0].instructions.push_back(direct);
|
|
}
|
|
Check(BuildScalarProvenance(overflow, &error) &&
|
|
BuildSrtPlan(overflow, &error) && PatchSrtReads(overflow, &error) &&
|
|
TrackResources(overflow, &error) &&
|
|
CollectShaderInfo(overflow, {.compute = &compute}, &error) &&
|
|
AllocateBindings(overflow, {}, &error),
|
|
error.c_str());
|
|
const auto *user_data =
|
|
FindBinding(overflow.bindings, DescriptorBindingKind::UserData);
|
|
Check(overflow.bindings.user_data_registers.size() == 33 &&
|
|
overflow.bindings.user_data_registers.front() == 0 &&
|
|
overflow.bindings.user_data_registers.back() == 32 &&
|
|
overflow.bindings.push_constant_size == 0 && user_data != nullptr &&
|
|
user_data->binding == 0,
|
|
"oversized sparse user data was not moved to a descriptor binding");
|
|
}
|
|
|
|
void TestNativeBindingLayoutGds() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
Instruction append;
|
|
append.op = Opcode::DsAppend;
|
|
append.memory.kind = ResourceKind::Gds;
|
|
program.blocks[0].instructions.push_back(append);
|
|
|
|
ShaderComputeInputInfo compute;
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
|
TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
const auto *gds = FindBinding(program.bindings, DescriptorBindingKind::Gds);
|
|
Check(gds != nullptr && gds->binding == 0 && gds->resources.empty(),
|
|
"GDS append/consume did not allocate one native storage binding");
|
|
|
|
Program lds;
|
|
lds.stage = ShaderType::Compute;
|
|
lds.blocks.resize(1);
|
|
append.memory.kind = ResourceKind::Lds;
|
|
lds.blocks[0].instructions.push_back(append);
|
|
Check(BuildScalarProvenance(lds, &error) && BuildSrtPlan(lds, &error) &&
|
|
PatchSrtReads(lds, &error) && TrackResources(lds, &error) &&
|
|
CollectShaderInfo(lds, {.compute = &compute}, &error) &&
|
|
AllocateBindings(lds, {}, &error),
|
|
error.c_str());
|
|
Check(FindBinding(lds.bindings, DescriptorBindingKind::Gds) == nullptr,
|
|
"LDS append/consume incorrectly allocated a GDS binding");
|
|
}
|
|
|
|
void TestNativeBindingLayoutIsTransactional() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
std::string error;
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
|
TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error),
|
|
error.c_str());
|
|
BindingLayout sentinel;
|
|
sentinel.descriptor_set = 99;
|
|
program.bindings = sentinel;
|
|
Check(!AllocateBindings(program, {.push_constant_offset = 129}, &error) &&
|
|
error.find("Vulkan minimum") != std::string::npos &&
|
|
program.bindings == sentinel && !program.binding_layout_complete,
|
|
"failed native binding allocation partially changed the program");
|
|
Check(!AllocateBindings(program, {.push_constant_offset = 2}, &error) &&
|
|
error.find("dword aligned") != std::string::npos &&
|
|
program.bindings == sentinel && !program.binding_layout_complete,
|
|
"misaligned push-constant allocation partially changed the program");
|
|
Check(AllocateBindings(program, {}, &error), error.c_str());
|
|
const auto layout = program.bindings;
|
|
Check(!AllocateBindings(program, {}, &error) &&
|
|
error.find("already allocated") != std::string::npos &&
|
|
program.bindings == layout,
|
|
"repeated native binding allocation was accepted or mutated layout");
|
|
|
|
Program tail;
|
|
tail.stage = ShaderType::Compute;
|
|
tail.blocks.resize(1);
|
|
Instruction direct;
|
|
direct.op = Opcode::MoveU32;
|
|
direct.dst.kind = OperandKind::Register;
|
|
direct.dst.reg = {RegisterFile::Vector, 0};
|
|
direct.src[0] = Sgpr(0);
|
|
direct.src_count = 1;
|
|
tail.blocks[0].instructions = {direct};
|
|
Check(BuildScalarProvenance(tail, &error) && BuildSrtPlan(tail, &error) &&
|
|
PatchSrtReads(tail, &error) && TrackResources(tail, &error) &&
|
|
CollectShaderInfo(tail, {.compute = &compute}, &error) &&
|
|
AllocateBindings(tail, {.push_constant_offset = 124}, &error) &&
|
|
tail.bindings.push_constant_size == 4 &&
|
|
FindBinding(tail.bindings, DescriptorBindingKind::UserData) ==
|
|
nullptr,
|
|
"valid final dword of the guaranteed push-constant range was rejected");
|
|
}
|
|
|
|
void TestNativeBindingLayoutTracksReachingUserData() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto &insts = program.blocks[0].instructions;
|
|
insts.push_back(Move(0, 100, 4));
|
|
Instruction forwarded;
|
|
forwarded.pc = 4;
|
|
forwarded.op = Opcode::MoveU32;
|
|
forwarded.dst.kind = OperandKind::Register;
|
|
forwarded.dst.reg = {RegisterFile::Vector, 0};
|
|
forwarded.src[0] = Sgpr(100);
|
|
forwarded.src_count = 1;
|
|
insts.push_back(forwarded);
|
|
auto sparse = forwarded;
|
|
sparse.pc = 8;
|
|
sparse.dst.reg.index = 1;
|
|
sparse.src[0] = Sgpr(20);
|
|
insts.push_back(sparse);
|
|
insts.push_back(MoveImmediate(12, 101, 7));
|
|
auto constant = forwarded;
|
|
constant.pc = 16;
|
|
constant.dst.reg.index = 2;
|
|
constant.src[0] = Sgpr(101);
|
|
insts.push_back(constant);
|
|
|
|
std::string error;
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
|
TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error),
|
|
error.c_str());
|
|
auto fallback = program;
|
|
Check(AllocateBindings(program, {}, &error) &&
|
|
program.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({4, 20}) &&
|
|
program.bindings.push_constant_size == 8 &&
|
|
FindBinding(program.bindings, DescriptorBindingKind::UserData) ==
|
|
nullptr,
|
|
"native user-data map included a temporary or missed sparse roots");
|
|
Check(AllocateBindings(fallback, {.max_push_dwords = 1}, &error) &&
|
|
fallback.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({4, 20}) &&
|
|
fallback.bindings.push_constant_size == 0 &&
|
|
FindBinding(fallback.bindings, DescriptorBindingKind::UserData) !=
|
|
nullptr,
|
|
"max push-dword limit did not move exact sparse user data to storage");
|
|
}
|
|
|
|
void TestNativeBindingLayoutRejectsUnknownShapeAndBadProvenance() {
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
|
|
Program image;
|
|
image.stage = ShaderType::Compute;
|
|
image.blocks.resize(1);
|
|
image.blocks[0].instructions = {ImageUse(4, Opcode::ImageStore,
|
|
ResourceKind::StorageImage,
|
|
Decoder::ImageDimension::Dim2D)};
|
|
Prepare(image);
|
|
Check(CollectShaderInfo(image, {.compute = &compute}, &error), error.c_str());
|
|
image.info.images[0].dimension = Decoder::ImageDimension::Unknown;
|
|
image.bindings.descriptor_set = 77;
|
|
const auto image_layout = image.bindings;
|
|
Check(!AllocateBindings(image, {}, &error) &&
|
|
error.find("invalid image binding class") != std::string::npos &&
|
|
image.bindings == image_layout && !image.binding_layout_complete,
|
|
"unknown image shape was defaulted or partially allocated");
|
|
|
|
Program provenance;
|
|
provenance.stage = ShaderType::Compute;
|
|
provenance.blocks.resize(1);
|
|
Instruction direct;
|
|
direct.op = Opcode::MoveU32;
|
|
direct.dst.kind = OperandKind::Register;
|
|
direct.dst.reg = {RegisterFile::Vector, 0};
|
|
direct.src[0] = Sgpr(4);
|
|
direct.src_count = 1;
|
|
provenance.blocks[0].instructions = {direct};
|
|
Check(BuildScalarProvenance(provenance, &error) &&
|
|
BuildSrtPlan(provenance, &error) &&
|
|
PatchSrtReads(provenance, &error) &&
|
|
TrackResources(provenance, &error) &&
|
|
CollectShaderInfo(provenance, {.compute = &compute}, &error),
|
|
error.c_str());
|
|
provenance.blocks[0].instructions[0].scalar_sources[0] =
|
|
static_cast<uint32_t>(provenance.provenance.values.size() + 1);
|
|
provenance.bindings.descriptor_set = 88;
|
|
const auto provenance_layout = provenance.bindings;
|
|
Check(!AllocateBindings(provenance, {}, &error) &&
|
|
error.find("invalid scalar provenance reference") !=
|
|
std::string::npos &&
|
|
provenance.bindings == provenance_layout &&
|
|
!provenance.binding_layout_complete,
|
|
"malformed per-use provenance was ignored or partially allocated");
|
|
}
|
|
|
|
void TestNativeBindingLayoutDynamicSrtDoesNotUseFlatBinding() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
auto load = ScalarLoad(i * 4, i, 16, i * 4);
|
|
load.src[0] = Sgpr(20);
|
|
load.src_count = 1;
|
|
program.blocks[0].instructions.push_back(load);
|
|
}
|
|
program.blocks[0].instructions.push_back(BufferUse(0x20, 0));
|
|
Prepare(program);
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
Check(program.srt.reads.empty() && program.srt.dynamic_reads.size() == 4 &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
Check(program.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({16, 17, 20}) &&
|
|
FindBinding(program.bindings,
|
|
DescriptorBindingKind::FlattenedSrt) == nullptr,
|
|
"dynamic-only SRT reads were flattened or lost reaching user data");
|
|
}
|
|
|
|
void TestNativeBindingLayoutTracksRawScalarMemoryBase() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto load = ScalarLoad(0, 0, 16, 0);
|
|
load.src[0] = Sgpr(20);
|
|
load.src_count = 1;
|
|
Instruction consume;
|
|
consume.pc = 4;
|
|
consume.op = Opcode::MoveU32;
|
|
consume.dst.kind = OperandKind::Register;
|
|
consume.dst.reg = {RegisterFile::Vector, 0};
|
|
consume.src[0] = Sgpr(0);
|
|
consume.src_count = 1;
|
|
program.blocks[0].instructions = {load, consume};
|
|
|
|
std::string error;
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && program.srt.reads.empty() &&
|
|
program.srt.dynamic_reads.empty() &&
|
|
PatchSrtReads(program, &error) && TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
const auto *address_memory =
|
|
FindBinding(program.bindings, DescriptorBindingKind::AddressMemory);
|
|
Check(program.blocks[0].instructions[0].op == Opcode::SLoadDword &&
|
|
program.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({16, 17, 20}) &&
|
|
FindBinding(program.bindings,
|
|
DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
|
address_memory != nullptr &&
|
|
address_memory->resources == std::vector<uint32_t>({0}) &&
|
|
program.info.addresses.size() == 1 &&
|
|
program.info.addresses[0].kind == ResourceKind::ScalarBuffer,
|
|
"ordinary raw scalar memory omitted its implicit base or offset roots");
|
|
}
|
|
|
|
void TestRawScalarMemoryTracksReachingBaseIdentity() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
auto first = ScalarLoad(8, 0, 16, 0);
|
|
first.memory.offset = static_cast<uint32_t>(-4);
|
|
first.src[0] = Sgpr(20);
|
|
first.src_count = 1;
|
|
auto second = ScalarLoad(20, 1, 16, 0);
|
|
second.src[0] = Sgpr(20);
|
|
second.src_count = 1;
|
|
program.blocks[0].instructions = {
|
|
MoveImmediate(0, 16, 0x1000), MoveImmediate(4, 17, 0), first,
|
|
MoveImmediate(12, 16, 0x2000), MoveImmediate(16, 17, 0), second};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
|
TrackResources(program, &error),
|
|
error.c_str());
|
|
Check(program.info.addresses.size() == 2 &&
|
|
program.info.addresses[0].source !=
|
|
program.info.addresses[1].source &&
|
|
program.blocks[0].instructions[2].memory.resource == 0 &&
|
|
program.blocks[0].instructions[5].memory.resource == 1,
|
|
"raw scalar loads with redefined SBASE collapsed to one address "
|
|
"resource");
|
|
|
|
std::array<uint32_t, 64> user_data{};
|
|
user_data[20] = 4;
|
|
ResourceSnapshot snapshot;
|
|
Check(MaterializeResources(program, {user_data}, snapshot, &error),
|
|
error.c_str());
|
|
Check(snapshot.addresses.size() == 2 &&
|
|
snapshot.addresses[0].guest_base == 0x1000 &&
|
|
snapshot.addresses[0].binding_base == 0x0ffc &&
|
|
snapshot.addresses[1].guest_base == 0x2000 &&
|
|
snapshot.addresses[1].binding_base == 0x2000,
|
|
"runtime snapshot lost per-use raw scalar base values");
|
|
}
|
|
|
|
void TestNativeBindingLayoutUsesExplicitFlatMemory() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.blocks.resize(1);
|
|
Instruction load;
|
|
load.op = Opcode::FlatLoadDword;
|
|
load.memory.kind = ResourceKind::Flat;
|
|
program.blocks[0].instructions = {load};
|
|
Prepare(program);
|
|
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
std::string error;
|
|
Check(CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
const auto *flat =
|
|
FindBinding(program.bindings, DescriptorBindingKind::AddressMemory);
|
|
Check(
|
|
flat != nullptr && flat->resources == std::vector<uint32_t>({0}) &&
|
|
program.info.addresses.size() == 1 &&
|
|
program.info.addresses[0].kind == ResourceKind::Flat &&
|
|
FindBinding(program.bindings, DescriptorBindingKind::Buffers) ==
|
|
nullptr,
|
|
"flat address space was aliased to an ordinary buffer descriptor group");
|
|
|
|
ResourceSnapshot snapshot;
|
|
SrtRuntime runtime;
|
|
runtime.flat_memory_base = 0x1234567887654000ull;
|
|
Check(MaterializeResources(program, runtime, snapshot, &error) &&
|
|
snapshot.addresses.size() == 1 &&
|
|
snapshot.addresses[0].guest_base == 0x1234567887654000ull &&
|
|
snapshot.addresses[0].binding_base == 0x1234567887654000ull,
|
|
"unbased flat virtual address space lost its runtime binding base");
|
|
}
|
|
|
|
void TestNativeBindingLayoutHonorsUserDataCount() {
|
|
Program program;
|
|
program.stage = ShaderType::Compute;
|
|
program.user_data_count = 8;
|
|
program.blocks.resize(1);
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
Instruction direct;
|
|
direct.pc = i * 4;
|
|
direct.op = Opcode::MoveU32;
|
|
direct.dst.kind = OperandKind::Register;
|
|
direct.dst.reg = {RegisterFile::Vector, i};
|
|
direct.src[0] = Sgpr(7 + i);
|
|
direct.src_count = 1;
|
|
program.blocks[0].instructions.push_back(direct);
|
|
}
|
|
std::string error;
|
|
ShaderComputeInputInfo compute;
|
|
compute.thread_ids_num = 1;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
program.provenance
|
|
.values[program.blocks[0].instructions[0].scalar_sources[0]]
|
|
.op == ScalarValueOp::UserData &&
|
|
program.blocks[0].instructions[1].scalar_sources[0] ==
|
|
ScalarProvenance::Unknown &&
|
|
BuildSrtPlan(program, &error) && PatchSrtReads(program, &error) &&
|
|
TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.compute = &compute}, &error) &&
|
|
AllocateBindings(program, {}, &error) &&
|
|
program.bindings.user_data_registers == std::vector<uint32_t>({7}),
|
|
"stage user-data count did not bound provenance roots");
|
|
|
|
Program invalid;
|
|
invalid.user_data_count = 65;
|
|
invalid.blocks.resize(1);
|
|
Check(!BuildScalarProvenance(invalid, &error) &&
|
|
error.find("exceeds 64") != std::string::npos,
|
|
"out-of-range stage user-data count was accepted");
|
|
}
|
|
|
|
void TestNativeBindingLayoutHonorsUserDataBase() {
|
|
Program program;
|
|
program.stage = ShaderType::Vertex;
|
|
program.user_data_base = 8;
|
|
program.user_data_count = 8;
|
|
program.blocks.resize(1);
|
|
program.blocks[0].instructions = {Move(0, 20, 7), Move(4, 21, 8),
|
|
Move(8, 22, 15), Move(12, 23, 16),
|
|
BufferUse(16, 8)};
|
|
|
|
std::string error;
|
|
Check(BuildScalarProvenance(program, &error) &&
|
|
program.blocks[0].instructions[0].scalar_sources[0] ==
|
|
ScalarProvenance::Unknown &&
|
|
program.provenance
|
|
.values[program.blocks[0].instructions[1].scalar_sources[0]]
|
|
.op == ScalarValueOp::UserData &&
|
|
program.blocks[0].instructions[3].scalar_sources[0] ==
|
|
ScalarProvenance::Unknown &&
|
|
BuildSrtPlan(program, &error),
|
|
error.c_str());
|
|
|
|
const std::array<uint32_t, 8> user_data = {
|
|
0x11111111, 0x22222222, 0x33333333, 0x44444444, 0, 0, 0, 0xaaaaaaaa};
|
|
DescriptorValue descriptor;
|
|
const auto source =
|
|
program.blocks[0].instructions.back().memory.resource_source;
|
|
Check(EvaluateDescriptorSource(program, source, 16, {user_data}, descriptor,
|
|
&error) &&
|
|
descriptor.dwords[0] == user_data[0] &&
|
|
descriptor.dwords[3] == user_data[3],
|
|
"vertex user-data base was not translated to runtime-local indices");
|
|
|
|
ShaderVertexInputInfo vertex;
|
|
Check(PatchSrtReads(program, &error) && TrackResources(program, &error) &&
|
|
CollectShaderInfo(program, {.vertex = &vertex}, &error) &&
|
|
AllocateBindings(program, {}, &error),
|
|
error.c_str());
|
|
Check(
|
|
program.bindings.user_data_registers ==
|
|
std::vector<uint32_t>({8, 9, 10, 11, 15}),
|
|
"native binding plan did not retain physical shifted user-SGPR indices");
|
|
}
|
|
|
|
void TestTextureNullDescriptorUsesAddressBits() {
|
|
ShaderTextureResource texture;
|
|
const uint32_t captured[8] = {0x00000000, 0xc3800000, 0x0059c09f, 0x91b00fac,
|
|
0x00000000, 0x00700000, 0x00000000, 0x00000000};
|
|
std::copy(std::begin(captured), std::end(captured),
|
|
std::begin(texture.fields));
|
|
Check(
|
|
texture.IsNull(),
|
|
"zero-address image with populated metadata was not classified as null");
|
|
texture.fields[0] = 1;
|
|
Check(!texture.IsNull(), "nonzero image base address was classified as null");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
const char *current = "startup";
|
|
#define RUN(test) \
|
|
current = #test; \
|
|
test()
|
|
try {
|
|
RUN(TestDenseBufferPatching);
|
|
RUN(TestScalarAndVectorBufferAlias);
|
|
RUN(TestBufferImageAliasIsLinkedDuringTracking);
|
|
RUN(TestImagesAndSamplers);
|
|
RUN(TestDynamicPhiResource);
|
|
RUN(TestTrackingRequiresCompletedSrtPlan);
|
|
RUN(TestCyclicResourceIsRejected);
|
|
RUN(TestUnknownSourceFailsWithoutPatching);
|
|
RUN(TestResourceLimitFailsTransactionally);
|
|
RUN(TestComputeShaderInfoCollection);
|
|
RUN(TestVertexShaderInfoCollection);
|
|
RUN(TestPixelShaderInfoCollection);
|
|
RUN(TestShaderInfoCollectionIsTransactional);
|
|
RUN(TestShaderInfoMetadataValidation);
|
|
RUN(TestTrackingRequiresSrtPatching);
|
|
RUN(TestDynamicSrtReadRemainsExplicit);
|
|
RUN(TestSrtPatchingFailureIsTransactional);
|
|
RUN(TestScalarMemoryGroupsSnapshotOperands);
|
|
RUN(TestSrtPatchingHandlesGvnAndMoveForwarding);
|
|
RUN(TestSrtPatchingHandlesCfgProducers);
|
|
RUN(TestSrtPatchPlanValidation);
|
|
RUN(TestMaterializationSharesReadConstEvaluation);
|
|
RUN(TestInvalidImagesMaterializeAsNull);
|
|
RUN(TestInvalidBuffersMaterializeAsNull);
|
|
RUN(TestMaterializationFailureIsTransactional);
|
|
RUN(TestResourceSpecializationIsTypedAndTransactional);
|
|
RUN(TestRuntimeSpecializationCoversBakedBufferAndAddressFields);
|
|
RUN(TestTrackedProgramIsImmutable);
|
|
RUN(TestNativeBindingLayout);
|
|
RUN(TestNativeBindingLayoutOneDimensionalImages);
|
|
RUN(TestNativeBindingLayoutSrtAndUserDataOverflow);
|
|
RUN(TestNativeBindingLayoutGds);
|
|
RUN(TestNativeBindingLayoutIsTransactional);
|
|
RUN(TestNativeBindingLayoutTracksReachingUserData);
|
|
RUN(TestNativeBindingLayoutRejectsUnknownShapeAndBadProvenance);
|
|
RUN(TestNativeBindingLayoutDynamicSrtDoesNotUseFlatBinding);
|
|
RUN(TestNativeBindingLayoutTracksRawScalarMemoryBase);
|
|
RUN(TestRawScalarMemoryTracksReachingBaseIdentity);
|
|
RUN(TestNativeBindingLayoutUsesExplicitFlatMemory);
|
|
RUN(TestNativeBindingLayoutHonorsUserDataCount);
|
|
RUN(TestNativeBindingLayoutHonorsUserDataBase);
|
|
RUN(TestTextureNullDescriptorUsesAddressBits);
|
|
std::cout << "ResourceTrackingTests: all cases passed\n";
|
|
return 0;
|
|
} catch (const std::exception &e) {
|
|
std::cerr << "ResourceTrackingTests: " << current << " failed: " << e.what()
|
|
<< '\n';
|
|
return 1;
|
|
}
|
|
#undef RUN
|
|
}
|