mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 03:16:54 +02:00
shader: remove recompiler error-string propagation
This commit is contained in:
+164
-192
@@ -29,6 +29,19 @@ void Check(bool condition, const char *message) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void CheckFatal(F &&function, std::string_view expected, const char *message) {
|
||||
try {
|
||||
function();
|
||||
} catch (const std::runtime_error &error) {
|
||||
Check(std::string_view(error.what()).find(expected) !=
|
||||
std::string_view::npos,
|
||||
message);
|
||||
return;
|
||||
}
|
||||
Check(false, message);
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
Program program;
|
||||
Block *block = nullptr;
|
||||
@@ -75,8 +88,7 @@ struct Fixture {
|
||||
}
|
||||
|
||||
MemoryFlags AddMemory(MemoryInfo memory, uint32_t pc) {
|
||||
const auto index =
|
||||
static_cast<uint32_t>(program.memory_info.size());
|
||||
const auto index = static_cast<uint32_t>(program.memory_info.size());
|
||||
program.memory_info.push_back(memory);
|
||||
return {index, pc};
|
||||
}
|
||||
@@ -113,10 +125,8 @@ struct Fixture {
|
||||
}
|
||||
|
||||
void PlanAndTrack() {
|
||||
std::string error;
|
||||
if (!BuildSrtPlan(program, &error) || !TrackResources(program, &error)) {
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
BuildSrtPlan(program);
|
||||
TrackResources(program);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,10 +251,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
auto fixture = MakeIndirectImageFixture(false);
|
||||
fixture->PlanAndTrack();
|
||||
EliminateDeadCode(fixture->program.blocks);
|
||||
std::string validation_error;
|
||||
Check(
|
||||
ValidateProgram(fixture->program, true, &validation_error),
|
||||
"post-tracking dead-code elimination invalidated descriptor provenance");
|
||||
ValidateProgram(fixture->program, true);
|
||||
|
||||
Check(fixture->program.info.buffers.size() == 1 &&
|
||||
fixture->program.info.images.size() == 1 &&
|
||||
@@ -291,7 +298,6 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
const auto same_snapshot = [](const ResourceSnapshot &lhs,
|
||||
const ResourceSnapshot &rhs) {
|
||||
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
|
||||
@@ -300,7 +306,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
lhs.user_data == rhs.user_data &&
|
||||
lhs.indirect_images.empty() == rhs.indirect_images.empty();
|
||||
};
|
||||
Check(MaterializeResources(fixture->program, runtime, snapshot, &error) &&
|
||||
Check(MaterializeResources(fixture->program, runtime, snapshot) &&
|
||||
snapshot.images.size() == 1 &&
|
||||
std::equal(image_descriptor.begin(), image_descriptor.end(),
|
||||
snapshot.images[0].dwords.begin()),
|
||||
@@ -308,8 +314,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
|
||||
const auto prior_snapshot = snapshot;
|
||||
memory.fail_address = 0x1004u;
|
||||
Check(!MaterializeResources(fixture->program, runtime, snapshot, &error) &&
|
||||
error.find("scalar read") != std::string::npos &&
|
||||
Check(!MaterializeResources(fixture->program, runtime, snapshot) &&
|
||||
same_snapshot(snapshot, prior_snapshot),
|
||||
"rejected planning memory read mutated the snapshot");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
@@ -325,12 +330,11 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
memory.words[(0x2020u - memory.base) / 4u + 3u] =
|
||||
image_descriptor[3] ^ (1u << 28u);
|
||||
ResourceSnapshot null_snapshot;
|
||||
Check(
|
||||
MaterializeResources(fixture->program, runtime, null_snapshot, &error) &&
|
||||
null_snapshot.indirect_images.empty() &&
|
||||
std::ranges::all_of(null_snapshot.images[0].dwords,
|
||||
[](uint32_t dword) { return dword == 0u; }),
|
||||
"stale typed null image descriptors were not canonicalized");
|
||||
Check(MaterializeResources(fixture->program, runtime, null_snapshot) &&
|
||||
null_snapshot.indirect_images.empty() &&
|
||||
std::ranges::all_of(null_snapshot.images[0].dwords,
|
||||
[](uint32_t dword) { return dword == 0u; }),
|
||||
"stale typed null image descriptors were not canonicalized");
|
||||
|
||||
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
|
||||
memory.words[(0x2000u - memory.base) / 4u + dword] =
|
||||
@@ -341,13 +345,13 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
memory.words[(0x2020u - memory.base) / 4u] ^= 1u;
|
||||
memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u;
|
||||
ResourceSnapshot dynamic_snapshot;
|
||||
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot,
|
||||
&error) &&
|
||||
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot) &&
|
||||
dynamic_snapshot.images.size() == 1 &&
|
||||
dynamic_snapshot.indirect_images.size() == 1 &&
|
||||
dynamic_snapshot.indirect_images[0].descriptors.size() == 2 &&
|
||||
SpecializeResources(fixture->program, dynamic_snapshot, &error) &&
|
||||
fixture->program.info.images.size() == 2 &&
|
||||
dynamic_snapshot.indirect_images[0].descriptors.size() == 2,
|
||||
"dynamic indirect image table did not materialize");
|
||||
SpecializeResources(fixture->program, dynamic_snapshot);
|
||||
Check(fixture->program.info.images.size() == 2 &&
|
||||
fixture->program.info.images[0].indirect_root == 0 &&
|
||||
fixture->program.info.images[0].indirect_mapping_capacity != 0 &&
|
||||
fixture->program.info.images[0].indirect_resources.size() == 2 &&
|
||||
@@ -364,27 +368,21 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
memory.words[(0x2000u - memory.base) / 4u] += 0x100u;
|
||||
memory.words[(0x2020u - memory.base) / 4u] += 0x101u;
|
||||
ResourceSnapshot rebound_snapshot;
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
|
||||
&error),
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
|
||||
"stable indirect key mapping did not accept changed image addresses");
|
||||
memory.words[(0x2020u - memory.base) / 4u] =
|
||||
memory.words[(0x2000u - memory.base) / 4u];
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
|
||||
&error),
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
|
||||
"runtime indirect key mapping did not accept collapsed candidates");
|
||||
const auto collapsed_snapshot = rebound_snapshot;
|
||||
ResourceSnapshot capacity_snapshot;
|
||||
for (const uint32_t records : {1u, 3u}) {
|
||||
user_data[2] = records;
|
||||
Check(
|
||||
MaterializeResources(fixture->program, runtime, capacity_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program, capacity_snapshot,
|
||||
&error),
|
||||
MaterializeResources(fixture->program, runtime, capacity_snapshot) &&
|
||||
ValidateResourceSpecialization(fixture->program, capacity_snapshot),
|
||||
"runtime indirect key mapping rejected a fitting material-table size");
|
||||
}
|
||||
user_data[2] = 2u;
|
||||
@@ -398,9 +396,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
}
|
||||
memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u;
|
||||
Check(
|
||||
!MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
error.find("candidate topology") != std::string::npos &&
|
||||
!MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
|
||||
same_snapshot(rebound_snapshot, collapsed_snapshot),
|
||||
"larger indirect candidate topology reused or mutated a cached snapshot");
|
||||
|
||||
@@ -418,26 +414,26 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
.read_specialization_memory =
|
||||
ReadLinearTestMemory};
|
||||
Check(!MaterializeResources(memory_backed->program, memory_backed_runtime,
|
||||
snapshot, &error) &&
|
||||
error.find("constant read failed") != std::string::npos &&
|
||||
snapshot) &&
|
||||
same_snapshot(snapshot, prior_snapshot),
|
||||
"rejected indirect table descriptor read mutated the snapshot");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
|
||||
auto malformed = MakeIndirectImageFixture(true);
|
||||
Check(BuildSrtPlan(malformed->program, &error) &&
|
||||
!TrackResources(malformed->program, &error) &&
|
||||
error.find("ReadFirstLane") != std::string::npos &&
|
||||
!malformed->program.resource_tracking_complete &&
|
||||
BuildSrtPlan(malformed->program);
|
||||
CheckFatal([&] { TrackResources(malformed->program); }, "not a valid runtime value",
|
||||
"malformed indirect image pattern was accepted");
|
||||
Check(!malformed->program.resource_tracking_complete &&
|
||||
malformed->program.info.images.empty() &&
|
||||
malformed->program.descriptor_sources.empty(),
|
||||
"malformed indirect image pattern was partially accepted");
|
||||
|
||||
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
|
||||
Check(BuildSrtPlan(wrapped_immediate->program, &error) &&
|
||||
!TrackResources(wrapped_immediate->program, &error) &&
|
||||
error.find("ReadFirstLane") != std::string::npos &&
|
||||
!wrapped_immediate->program.resource_tracking_complete,
|
||||
BuildSrtPlan(wrapped_immediate->program);
|
||||
CheckFatal([&] { TrackResources(wrapped_immediate->program); },
|
||||
"not a valid runtime value",
|
||||
"wrapped scalar immediate entered the invariant image proof");
|
||||
Check(!wrapped_immediate->program.resource_tracking_complete,
|
||||
"wrapped scalar immediate entered the invariant image proof");
|
||||
}
|
||||
|
||||
@@ -494,16 +490,12 @@ void TestDenseBufferTracking() {
|
||||
second.Instruction()->Flags<uint32_t>() == 1,
|
||||
"typed handles were not assigned dense indices");
|
||||
Check(fixture.program.memory_info[load_flags.index].resource == 0 &&
|
||||
fixture.program.memory_info[store_flags.index].resource ==
|
||||
0 &&
|
||||
fixture.program.memory_info[other_flags.index].resource ==
|
||||
1,
|
||||
fixture.program.memory_info[store_flags.index].resource == 0 &&
|
||||
fixture.program.memory_info[other_flags.index].resource == 1,
|
||||
"typed memory metadata was not patched to dense indices");
|
||||
|
||||
std::string error;
|
||||
Check(!TrackResources(fixture.program, &error) &&
|
||||
error.find("already tracked") != std::string::npos,
|
||||
"resource tracking allowed a second mutation pass");
|
||||
CheckFatal([&] { TrackResources(fixture.program); }, "already tracked",
|
||||
"resource tracking allowed a second mutation pass");
|
||||
}
|
||||
|
||||
void TestScalarAndVectorBufferAlias() {
|
||||
@@ -531,8 +523,7 @@ void TestScalarAndVectorBufferAlias() {
|
||||
fixture.program.info.buffers[0].scalar,
|
||||
"typed scalar and vector uses of one descriptor were split");
|
||||
Check(fixture.program.memory_info[scalar_flags.index].resource == 0 &&
|
||||
fixture.program.memory_info[vector_flags.index].resource ==
|
||||
0,
|
||||
fixture.program.memory_info[vector_flags.index].resource == 0,
|
||||
"scalar/vector alias did not share a dense index");
|
||||
}
|
||||
|
||||
@@ -552,16 +543,13 @@ void TestRuntimeUnsignedMinDescriptor() {
|
||||
std::array<uint32_t, 1> user_data{0xffffffffu};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue value;
|
||||
std::string error;
|
||||
const auto source = fixture.program.info.buffers[0].source;
|
||||
Check(EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value,
|
||||
&error) &&
|
||||
Check(EvaluateDescriptorSource(fixture.program, source, runtime, value) &&
|
||||
value.dwords[3] == 0x100u,
|
||||
"runtime descriptor unsigned minimum did not clamp its first operand");
|
||||
user_data[0] = 0x80u;
|
||||
Check(
|
||||
EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value,
|
||||
&error) &&
|
||||
EvaluateDescriptorSource(fixture.program, source, runtime, value) &&
|
||||
value.dwords[3] == 0x80u,
|
||||
"runtime descriptor unsigned minimum did not preserve its first operand");
|
||||
}
|
||||
@@ -624,9 +612,7 @@ void TestImagesSamplersAndAliases() {
|
||||
repeated.second.Instruction()->Flags<uint32_t>() == 0,
|
||||
"unused sampler border colors prevented source interning");
|
||||
const auto sampler_source = fixture.program.info.samplers[0].source;
|
||||
Check(fixture.program.descriptor_sources[sampler_source]
|
||||
.dwords[3]
|
||||
.U32() == 0,
|
||||
Check(fixture.program.descriptor_sources[sampler_source].dwords[3].U32() == 0,
|
||||
"unused sampler border color was not canonicalized");
|
||||
Check(fixture.program.info.buffers[0].image_alias == 0,
|
||||
"buffer/image descriptor alias was not linked");
|
||||
@@ -673,9 +659,7 @@ void TestSampleAdjustSamplerScratch() {
|
||||
std::array<uint32_t, 4> user_data{4u, 1u, 2u, 0x80000abcu};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program, source, 0x1ec, runtime,
|
||||
descriptor, &error) &&
|
||||
Check(EvaluateDescriptorSource(fixture.program, source, runtime, descriptor) &&
|
||||
descriptor.dwords[3] == 0x80000abcu,
|
||||
"SampleAdjust canonicalization lost sampler border fields");
|
||||
|
||||
@@ -704,11 +688,9 @@ void TestSampleAdjustSamplerScratch() {
|
||||
rejected.Emit(ValueOpcode::ImageSampleRaw,
|
||||
{rejected_image, rejected_sampler, rejected.ImageAddress()},
|
||||
rejected.AddMemory(rejected_memory, 0x200));
|
||||
std::string rejected_error;
|
||||
Check(BuildSrtPlan(rejected.program, &rejected_error) &&
|
||||
!TrackResources(rejected.program, &rejected_error) &&
|
||||
rejected_error.find("WqmMask") != std::string::npos,
|
||||
message);
|
||||
BuildSrtPlan(rejected.program);
|
||||
CheckFatal([&] { TrackResources(rejected.program); },
|
||||
"not a valid runtime value", message);
|
||||
};
|
||||
CheckRejected(0u, 12u,
|
||||
"ordinary sampling accepted SampleAdjust reserved scratch");
|
||||
@@ -778,15 +760,13 @@ void TestDynamicStorageMipTracking() {
|
||||
descriptor.dword_count = 8;
|
||||
ResourceSnapshot snapshot;
|
||||
snapshot.images.assign(images.size(), descriptor);
|
||||
std::string error;
|
||||
Check(SpecializeResources(fixture.program, snapshot, &error) &&
|
||||
fixture.program.info.images[1].mip_count == 3 &&
|
||||
ValidateResourceSpecialization(fixture.program, snapshot, &error),
|
||||
SpecializeResources(fixture.program, snapshot);
|
||||
Check(fixture.program.info.images[1].mip_count == 3 &&
|
||||
ValidateResourceSpecialization(fixture.program, snapshot),
|
||||
"base-1 through last-3 dynamic storage range was not specialized");
|
||||
ShaderComputeInputInfo compute{};
|
||||
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(fixture.program, 0, &error),
|
||||
"dynamic storage mip bindings were not allocated");
|
||||
CollectShaderInfo(fixture.program, {.compute = &compute});
|
||||
AllocateBindings(fixture.program, 0);
|
||||
const auto *storage_binding =
|
||||
FindBinding(fixture.program.bindings, DescriptorBindingKind::Storage2D);
|
||||
Check(storage_binding != nullptr &&
|
||||
@@ -805,19 +785,19 @@ void TestDynamicStorageMipTracking() {
|
||||
DescriptorValue null_descriptor{};
|
||||
null_descriptor.dword_count = 8;
|
||||
null_snapshot.images.push_back(null_descriptor);
|
||||
Check(SpecializeResources(null_program, null_snapshot, &error) &&
|
||||
null_program.info.images[0].mip_count == 1 &&
|
||||
ValidateResourceSpecialization(null_program, null_snapshot, &error),
|
||||
SpecializeResources(null_program, null_snapshot);
|
||||
Check(null_program.info.images[0].mip_count == 1 &&
|
||||
ValidateResourceSpecialization(null_program, null_snapshot),
|
||||
"canonical null dynamic storage image did not retain one descriptor");
|
||||
|
||||
snapshot.images[1].dwords[3] =
|
||||
(snapshot.images[1].dwords[3] & ~(0xfu << 16u)) | (2u << 16u);
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
|
||||
"a changed dynamic storage mip count reused the specialization");
|
||||
snapshot.images[1].dwords[3] =
|
||||
(snapshot.images[1].dwords[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
|
||||
(4u << 12u) | (3u << 16u);
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
|
||||
"an inverted dynamic storage mip range was accepted");
|
||||
}
|
||||
|
||||
@@ -861,11 +841,9 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
.userdata = &memory};
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
std::vector<uint32_t> flat;
|
||||
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source,
|
||||
12};
|
||||
std::string error;
|
||||
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source};
|
||||
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
|
||||
descriptors, flat, {}, &error),
|
||||
descriptors, flat, {}),
|
||||
"typed runtime source evaluation failed");
|
||||
Check(descriptors.size() == 1 && descriptors[0].dwords[0] == 0xdeadbeefu &&
|
||||
flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1,
|
||||
@@ -876,16 +854,16 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
descriptors = {{{1u}, 1u}};
|
||||
flat = {2u};
|
||||
Check(!EvaluateRuntimeSources(fixture.program, std::span{&request, 1},
|
||||
runtime, descriptors, flat, {}, &error) &&
|
||||
runtime, descriptors, flat, {}) &&
|
||||
descriptors == std::vector<DescriptorValue>{{{1u}, 1u}} &&
|
||||
flat == std::vector<uint32_t>{2u},
|
||||
"runtime evaluation failure was not transactional");
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(fixture.program, 0, &error) &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) != nullptr,
|
||||
CollectShaderInfo(fixture.program, {.compute = &compute});
|
||||
AllocateBindings(fixture.program, 0);
|
||||
Check(FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) != nullptr,
|
||||
"flattened typed SRT reads did not receive a binding");
|
||||
}
|
||||
|
||||
@@ -919,18 +897,16 @@ void TestDynamicSrtReadRemainsExplicit() {
|
||||
.read_memory = ReadTestMemory,
|
||||
.userdata = &memory};
|
||||
DescriptorValue value;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program,
|
||||
fixture.program.info.buffers[0].source, 8,
|
||||
runtime, value, &error) &&
|
||||
fixture.program.info.buffers[0].source, runtime, value) &&
|
||||
value.dwords[0] == 0xabcdef01u && memory.reads == 1,
|
||||
"dynamic typed scalar descriptor source was not evaluated");
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
|
||||
AllocateBindings(fixture.program, 0, &error) &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
||||
CollectShaderInfo(fixture.program, {.compute = &compute});
|
||||
AllocateBindings(fixture.program, 0);
|
||||
Check(FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::BdaPagetable) != nullptr &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
@@ -966,12 +942,10 @@ void TestPhiValidation() {
|
||||
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(memory, 20), merge);
|
||||
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error),
|
||||
"SRT planning rejected a well-formed typed phi");
|
||||
Check(!TrackResources(fixture.program, &error) &&
|
||||
error.find("control-dependent phi") != std::string::npos &&
|
||||
!fixture.program.resource_tracking_complete &&
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal([&] { TrackResources(fixture.program); }, "not a valid runtime value",
|
||||
"control-dependent descriptor phi was accepted");
|
||||
Check(!fixture.program.resource_tracking_complete &&
|
||||
fixture.program.info.buffers.empty() &&
|
||||
fixture.program.descriptor_sources.empty(),
|
||||
"control-dependent descriptor phi was not rejected transactionally");
|
||||
@@ -994,9 +968,7 @@ void TestLoopCycleEnteredThroughRuntimeValue() {
|
||||
{carried, Value(0u), Value(0u), Value(0u)}, MemoryFlags{0, 12},
|
||||
loop);
|
||||
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error),
|
||||
"SRT planning rejected a valid loop entered through a runtime value");
|
||||
BuildSrtPlan(fixture.program);
|
||||
}
|
||||
|
||||
void TestInvariantLoopPhi() {
|
||||
@@ -1023,10 +995,8 @@ void TestInvariantLoopPhi() {
|
||||
std::array<uint32_t, 1> user_data{0x12345678u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
DescriptorValue descriptor;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program,
|
||||
fixture.program.info.buffers[0].source, 4,
|
||||
runtime, descriptor, &error) &&
|
||||
fixture.program.info.buffers[0].source, runtime, descriptor) &&
|
||||
descriptor.dwords[0] == user_data[0],
|
||||
"loop-invariant descriptor phi was not evaluated through typed SSA");
|
||||
}
|
||||
@@ -1057,11 +1027,9 @@ void TestDmaAddressMaterialization() {
|
||||
std::array<uint32_t, 2> user_data{0x2008u, 0u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot),
|
||||
"DMA shader resources did not materialize");
|
||||
Check(SpecializeResources(fixture.program, snapshot, &error),
|
||||
"DMA shader resources were not specialized");
|
||||
SpecializeResources(fixture.program, snapshot);
|
||||
}
|
||||
|
||||
void TestDynamicFlatAddressesUseDma() {
|
||||
@@ -1089,8 +1057,7 @@ void TestDynamicFlatAddressesUseDma() {
|
||||
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot),
|
||||
"exec-masked FLAT shader resources did not materialize");
|
||||
|
||||
Fixture mismatch;
|
||||
@@ -1137,15 +1104,15 @@ void TestBufferSwizzleSpecialization() {
|
||||
(1u << 24u)};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error) &&
|
||||
SpecializeResources(fixture.program, snapshot, &error) &&
|
||||
fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
|
||||
ValidateResourceSpecialization(fixture.program, snapshot, &error),
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot),
|
||||
"buffer resources did not materialize");
|
||||
SpecializeResources(fixture.program, snapshot);
|
||||
Check(fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
|
||||
ValidateResourceSpecialization(fixture.program, snapshot),
|
||||
"buffer destination selectors were not specialized");
|
||||
|
||||
snapshot.buffers[0].dwords[3] ^= 1u << 9u;
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
|
||||
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
|
||||
"buffer swizzle change did not invalidate specialization");
|
||||
}
|
||||
|
||||
@@ -1171,17 +1138,14 @@ void TestShaderInfoAndBindingLayout() {
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
compute.dispatch_thread_dimensions = true;
|
||||
std::string error;
|
||||
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error),
|
||||
"typed shader info collection failed");
|
||||
CollectShaderInfo(fixture.program, {.compute = &compute});
|
||||
Check(fixture.program.info.has_bitwise_xor &&
|
||||
!fixture.program.info.inputs.empty() &&
|
||||
fixture.program.info.inputs[0].kind ==
|
||||
StageInputKind::GlobalInvocationId,
|
||||
"typed shader values were not reflected in shader info");
|
||||
|
||||
Check(AllocateBindings(fixture.program, 0, &error),
|
||||
"typed binding allocation failed");
|
||||
AllocateBindings(fixture.program, 0);
|
||||
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
|
||||
nullptr &&
|
||||
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
|
||||
@@ -1211,53 +1175,57 @@ void TestGraphicsPushConstantLayout() {
|
||||
}
|
||||
fixture.program.shader_info_complete = true;
|
||||
};
|
||||
|
||||
std::string error;
|
||||
Fixture vertex(ShaderType::Vertex);
|
||||
AddUserData(vertex, 9);
|
||||
Check(AllocateBindings(vertex.program, 0, &error) &&
|
||||
vertex.program.bindings.push_constant_offset == 0 &&
|
||||
AllocateBindings(vertex.program, 0);
|
||||
Check(vertex.program.bindings.push_constant_offset == 0 &&
|
||||
vertex.program.bindings.push_constant_size == 9 * sizeof(uint32_t),
|
||||
"vertex shader was not placed at the start of the graphics push bank");
|
||||
|
||||
const auto pixel_offset = vertex.program.bindings.push_constant_size;
|
||||
Fixture pixel(ShaderType::Pixel);
|
||||
AddUserData(pixel, 4);
|
||||
Check(AllocateBindings(pixel.program, pixel_offset, &error) &&
|
||||
pixel.program.bindings.push_constant_offset == pixel_offset &&
|
||||
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
|
||||
FindBinding(pixel.program.bindings, DescriptorBindingKind::UserData) ==
|
||||
nullptr,
|
||||
"pixel shader did not follow the vertex data in the graphics push bank");
|
||||
AllocateBindings(pixel.program, pixel_offset);
|
||||
Check(
|
||||
pixel.program.bindings.push_constant_offset == pixel_offset &&
|
||||
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
|
||||
FindBinding(pixel.program.bindings,
|
||||
DescriptorBindingKind::UserData) == nullptr,
|
||||
"pixel shader did not follow the vertex data in the graphics push bank");
|
||||
|
||||
Fixture edge(ShaderType::Pixel);
|
||||
AddUserData(edge, 1);
|
||||
Check(AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t),
|
||||
&error) &&
|
||||
edge.program.bindings.push_constant_size == sizeof(uint32_t),
|
||||
AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t));
|
||||
Check(edge.program.bindings.push_constant_size == sizeof(uint32_t),
|
||||
"last aligned push-constant dword did not fit in the graphics bank");
|
||||
|
||||
Fixture spill(ShaderType::Pixel);
|
||||
AddUserData(spill, 32);
|
||||
Check(AllocateBindings(spill.program, pixel_offset, &error) &&
|
||||
spill.program.bindings.push_constant_size == 0 &&
|
||||
FindBinding(spill.program.bindings, DescriptorBindingKind::UserData) !=
|
||||
nullptr,
|
||||
"pixel shader overlapping the vertex push data did not spill to storage");
|
||||
AllocateBindings(spill.program, pixel_offset);
|
||||
Check(
|
||||
spill.program.bindings.push_constant_size == 0 &&
|
||||
FindBinding(spill.program.bindings,
|
||||
DescriptorBindingKind::UserData) != nullptr,
|
||||
"pixel shader overlapping the vertex push data did not spill to storage");
|
||||
|
||||
Fixture full(ShaderType::Pixel);
|
||||
AddUserData(full, 1);
|
||||
Check(AllocateBindings(full.program, NativePushConstantSize, &error) &&
|
||||
full.program.bindings.push_constant_size == 0 &&
|
||||
FindBinding(full.program.bindings, DescriptorBindingKind::UserData) !=
|
||||
nullptr,
|
||||
AllocateBindings(full.program, NativePushConstantSize);
|
||||
Check(full.program.bindings.push_constant_size == 0 &&
|
||||
FindBinding(full.program.bindings,
|
||||
DescriptorBindingKind::UserData) != nullptr,
|
||||
"full graphics push bank did not spill pixel user data to storage");
|
||||
|
||||
Fixture invalid(ShaderType::Pixel);
|
||||
AddUserData(invalid, 1);
|
||||
Check(!AllocateBindings(invalid.program,
|
||||
NativePushConstantSize + sizeof(uint32_t), &error) &&
|
||||
!invalid.program.binding_layout_complete,
|
||||
CheckFatal(
|
||||
[&] {
|
||||
AllocateBindings(invalid.program,
|
||||
NativePushConstantSize + sizeof(uint32_t));
|
||||
},
|
||||
"push-constant offset",
|
||||
"push-constant placement beyond the graphics bank was accepted");
|
||||
Check(!invalid.program.binding_layout_complete,
|
||||
"push-constant placement beyond the graphics bank was accepted");
|
||||
}
|
||||
|
||||
@@ -1273,12 +1241,11 @@ void TestResourceLimitIsTransactional() {
|
||||
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
fixture.AddMemory(memory, index * 4u));
|
||||
}
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error),
|
||||
"SRT plan failed before resource-limit test");
|
||||
Check(!TrackResources(fixture.program, &error) &&
|
||||
error.find("buffer resource limit exceeded") != std::string::npos &&
|
||||
!fixture.program.resource_tracking_complete &&
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal([&] { TrackResources(fixture.program); },
|
||||
"buffer resource limit exceeded",
|
||||
"resource-limit failure was not reported");
|
||||
Check(!fixture.program.resource_tracking_complete &&
|
||||
fixture.program.info.buffers.empty() &&
|
||||
fixture.program.descriptor_sources.empty(),
|
||||
"resource-limit failure partially mutated typed resource state");
|
||||
@@ -1293,12 +1260,11 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.Emit(ValueOpcode::StoreAddressU32,
|
||||
{address, Value(0u), Value(0u), Value(1u), Value(true)},
|
||||
fixture.AddMemory(memory, 4));
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error) &&
|
||||
!TrackResources(fixture.program, &error) &&
|
||||
error.find("address operation has invalid resource kind") !=
|
||||
std::string::npos,
|
||||
"resource tracking accepted an address opcode with buffer metadata");
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
"address operation has invalid resource kind",
|
||||
"resource tracking accepted an address opcode with buffer metadata");
|
||||
}
|
||||
{
|
||||
Fixture fixture;
|
||||
@@ -1311,12 +1277,11 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.Emit(ValueOpcode::ImageRead,
|
||||
{image, fixture.ImageAddress(), Value(true)},
|
||||
fixture.AddMemory(memory, 8));
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error) &&
|
||||
!TrackResources(fixture.program, &error) &&
|
||||
error.find("image operation has invalid resource kind") !=
|
||||
std::string::npos,
|
||||
"resource tracking accepted an image opcode with address metadata");
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
"image operation has invalid resource kind",
|
||||
"resource tracking accepted an image opcode with address metadata");
|
||||
}
|
||||
{
|
||||
Fixture fixture;
|
||||
@@ -1329,12 +1294,11 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.Emit(ValueOpcode::ImageRead,
|
||||
{image, fixture.ImageAddress(), Value(true)},
|
||||
fixture.AddMemory(memory, 12));
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(fixture.program, &error) &&
|
||||
!TrackResources(fixture.program, &error) &&
|
||||
error.find("image operation has invalid resource kind") !=
|
||||
std::string::npos,
|
||||
"resource tracking accepted a sampled read with storage metadata");
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
"image operation has invalid resource kind",
|
||||
"resource tracking accepted a sampled read with storage metadata");
|
||||
}
|
||||
{
|
||||
Fixture fixture;
|
||||
@@ -1347,12 +1311,10 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.Emit(ValueOpcode::ImageAtomicIAdd32,
|
||||
{image, fixture.ImageAddress(), Value(1u), Value(true)},
|
||||
fixture.AddMemory(memory, 16));
|
||||
std::string error;
|
||||
Check(
|
||||
BuildSrtPlan(fixture.program, &error) &&
|
||||
!TrackResources(fixture.program, &error) &&
|
||||
error.find("image operation has invalid resource kind") !=
|
||||
std::string::npos,
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
"image operation has invalid resource kind",
|
||||
"resource tracking accepted a uint atomic with float storage metadata");
|
||||
}
|
||||
}
|
||||
@@ -1399,14 +1361,24 @@ int main() {
|
||||
// target links only fmt; keep assertion failures observable without widening
|
||||
// its focused build manifest.
|
||||
namespace Common {
|
||||
int DbgExitHandler(const char *, int, std::string_view text) {
|
||||
throw std::runtime_error(std::string(text));
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) {
|
||||
throw std::runtime_error(std::string(text));
|
||||
}
|
||||
|
||||
int DbgExitIfHandler(const char *expression, const char *file, int line) {
|
||||
throw std::runtime_error(std::string("typed IR assertion: ") + expression +
|
||||
" at " + file + ':' + std::to_string(line));
|
||||
}
|
||||
|
||||
int DbgNotImplementedHandler(const char *expression, const char *file, int line) {
|
||||
throw std::runtime_error(std::string("typed IR not implemented: ") + expression +
|
||||
" at " + file + ':' + std::to_string(line));
|
||||
int DbgNotImplementedHandler(const char *expression, const char *file,
|
||||
int line) {
|
||||
throw std::runtime_error(std::string("typed IR not implemented: ") +
|
||||
expression + " at " + file + ':' +
|
||||
std::to_string(line));
|
||||
}
|
||||
|
||||
void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); }
|
||||
@@ -1415,8 +1387,8 @@ void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); }
|
||||
// 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/Program.cpp"
|
||||
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
|
||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
|
||||
|
||||
@@ -20,6 +20,19 @@ void Check(bool condition, const char *message) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void CheckFatal(F &&function, std::string_view expected, const char *message) {
|
||||
try {
|
||||
function();
|
||||
} catch (const std::runtime_error &error) {
|
||||
Check(std::string_view(error.what()).find(expected) !=
|
||||
std::string_view::npos,
|
||||
message);
|
||||
return;
|
||||
}
|
||||
Check(false, message);
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
Program program;
|
||||
|
||||
@@ -58,10 +71,7 @@ struct Fixture {
|
||||
return static_cast<uint32_t>(program.memory_info.size() - 1u);
|
||||
}
|
||||
|
||||
void Plan() {
|
||||
std::string error;
|
||||
Check(BuildSrtPlan(program, &error), error.c_str());
|
||||
}
|
||||
void Plan() { BuildSrtPlan(program); }
|
||||
};
|
||||
|
||||
struct TestMemory {
|
||||
@@ -112,8 +122,7 @@ void TestImmediateFlatteningAndGvn() {
|
||||
TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
std::string error;
|
||||
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
|
||||
Check(WalkSrt(fixture.program, runtime, flat), "flattened SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>{0xfeedbeefu} && memory_image.reads == 1,
|
||||
"flattened SRT did not evaluate its canonical read once");
|
||||
}
|
||||
@@ -130,8 +139,7 @@ void TestRawScalarComponentAlignment() {
|
||||
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
std::string error;
|
||||
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
|
||||
Check(WalkSrt(fixture.program, runtime, flat), "raw scalar SRT walk failed");
|
||||
Check(
|
||||
flat == std::vector<uint32_t>{0x12345678u} && memory_image.reads == 1,
|
||||
"raw scalar base, immediate, and offset were not aligned independently");
|
||||
@@ -141,11 +149,9 @@ void TestScalarMemoryDomainMismatchFails() {
|
||||
Fixture raw;
|
||||
const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer);
|
||||
RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), raw_memory);
|
||||
std::string error;
|
||||
Check(!BuildSrtPlan(raw.program, &error) &&
|
||||
error.find("incompatible scalar memory metadata") !=
|
||||
std::string::npos,
|
||||
"raw scalar load accepted descriptor-buffer metadata");
|
||||
CheckFatal([&] { BuildSrtPlan(raw.program); },
|
||||
"incompatible scalar memory metadata",
|
||||
"raw scalar load accepted descriptor-buffer metadata");
|
||||
|
||||
Fixture buffer;
|
||||
const auto buffer_memory = buffer.AddMemory(ResourceKind::ScalarAddress);
|
||||
@@ -154,11 +160,9 @@ void TestScalarMemoryDomainMismatchFails() {
|
||||
{Value(0x1000u), Value(0u), Value(16u), Value(0u)});
|
||||
buffer.EmitMemory(ValueOpcode::ReadConstBuffer, {resource, Value(0u)},
|
||||
buffer_memory);
|
||||
error.clear();
|
||||
Check(!BuildSrtPlan(buffer.program, &error) &&
|
||||
error.find("incompatible scalar memory metadata") !=
|
||||
std::string::npos,
|
||||
"descriptor scalar load accepted raw-address metadata");
|
||||
CheckFatal([&] { BuildSrtPlan(buffer.program); },
|
||||
"incompatible scalar memory metadata",
|
||||
"descriptor scalar load accepted raw-address metadata");
|
||||
}
|
||||
|
||||
void TestDynamicReadRemainsTyped() {
|
||||
@@ -191,8 +195,7 @@ void TestNestedSrtWalk() {
|
||||
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
std::string error;
|
||||
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
|
||||
Check(WalkSrt(fixture.program, runtime, flat), "nested SRT walk failed");
|
||||
Check(flat == std::vector<uint32_t>({0x2000u, 0xabcdef01u}),
|
||||
"nested typed SRT reads were not evaluated in dependency order");
|
||||
}
|
||||
@@ -215,10 +218,8 @@ void TestShaderBaseAndUserData() {
|
||||
SrtRuntime runtime{.user_data = user_data,
|
||||
.shader_base = 0x12345678abcdef00ull};
|
||||
DescriptorValue result;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, 0x90, runtime, result,
|
||||
&error),
|
||||
error.c_str());
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, runtime, result),
|
||||
"shader-relative descriptor evaluation failed");
|
||||
Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u &&
|
||||
result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u,
|
||||
"shader-relative typed descriptor expression evaluated incorrectly");
|
||||
@@ -242,9 +243,8 @@ void TestCarryAndBitFields() {
|
||||
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
|
||||
|
||||
DescriptorValue result;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error),
|
||||
error.c_str());
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result),
|
||||
"carry and bit-field descriptor evaluation failed");
|
||||
Check(result.dwords[0] == 1u && result.dwords[1] == 1u &&
|
||||
result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu,
|
||||
"typed carry or bit-field runtime evaluation is incorrect");
|
||||
@@ -267,17 +267,13 @@ void TestInvariantAndDivergentPhi() {
|
||||
{.dwords = {Value(&divergent)}, .dword_count = 1});
|
||||
|
||||
DescriptorValue result;
|
||||
std::string error;
|
||||
Check(
|
||||
EvaluateDescriptorSource(fixture.program, 0, 0x100, {}, result, &error) &&
|
||||
result.dwords[0] == 7u,
|
||||
"loop-invariant typed phi was rejected");
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
|
||||
result.dwords[0] == 7u,
|
||||
"loop-invariant typed phi was rejected");
|
||||
result.dword_count = 4;
|
||||
result.dwords[0] = 0xdeadbeefu;
|
||||
Check(!EvaluateDescriptorSource(fixture.program, 1, 0x104, {}, result,
|
||||
&error) &&
|
||||
result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu &&
|
||||
error.find("runtime-dependent") != std::string::npos,
|
||||
Check(!EvaluateDescriptorSource(fixture.program, 1, {}, result) &&
|
||||
result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu,
|
||||
"divergent phi did not fail transactionally");
|
||||
}
|
||||
|
||||
@@ -318,8 +314,7 @@ void TestRuntime64BitDescriptorOps() {
|
||||
{.dwords = {low, high}, .dword_count = 2});
|
||||
|
||||
DescriptorValue result;
|
||||
std::string error;
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error) &&
|
||||
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
|
||||
result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u,
|
||||
"64-bit typed descriptor arithmetic evaluation is incorrect");
|
||||
}
|
||||
@@ -339,10 +334,9 @@ void TestConstantBufferBounds() {
|
||||
TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}};
|
||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||
std::vector<uint32_t> flat;
|
||||
std::string error;
|
||||
Check(WalkSrt(fixture.program, runtime, flat, &error) &&
|
||||
Check(WalkSrt(fixture.program, runtime, flat) &&
|
||||
flat == std::vector<uint32_t>{0xa5a5a5a5u},
|
||||
error.c_str());
|
||||
"constant-buffer SRT walk failed");
|
||||
|
||||
Fixture overflow;
|
||||
const auto overflow_memory = overflow.AddMemory(ResourceKind::ScalarBuffer);
|
||||
@@ -356,9 +350,8 @@ void TestConstantBufferBounds() {
|
||||
{overflow_read, Value(0u), Value(16u), Value(0u)});
|
||||
overflow.Plan();
|
||||
flat = {0x55u};
|
||||
Check(!WalkSrt(overflow.program, runtime, flat, &error) &&
|
||||
flat == std::vector<uint32_t>{0x55u} &&
|
||||
error.find("exceeds size") != std::string::npos,
|
||||
Check(!WalkSrt(overflow.program, runtime, flat) &&
|
||||
flat == std::vector<uint32_t>{0x55u},
|
||||
"out-of-bounds constant-buffer walk was not transactional");
|
||||
}
|
||||
|
||||
@@ -420,10 +413,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() {
|
||||
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
|
||||
|
||||
const auto undef = fixture.Emit(ValueOpcode::UndefU32);
|
||||
const auto write = fixture.Emit(
|
||||
ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)});
|
||||
const auto read =
|
||||
fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)});
|
||||
const auto write =
|
||||
fixture.Emit(ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)});
|
||||
const auto read = fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)});
|
||||
entry_info.condition =
|
||||
fixture.Emit(ValueOpcode::IEqual32, {read, Value(42u)});
|
||||
fixture.Emit(ValueOpcode::Reference, {entry_info.condition});
|
||||
@@ -435,11 +427,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() {
|
||||
RemoveIdentities(fixture.program.blocks);
|
||||
EliminateDeadCode(fixture.program.blocks);
|
||||
|
||||
std::string error;
|
||||
Check(entry_info.condition == Value(true) &&
|
||||
ValidateProgram(fixture.program, true, &error),
|
||||
error.empty() ? "folded branch condition did not survive identity removal"
|
||||
: error.c_str());
|
||||
Check(entry_info.condition == Value(true),
|
||||
"folded branch condition did not survive identity removal");
|
||||
ValidateProgram(fixture.program, true);
|
||||
}
|
||||
|
||||
void TestUndefinedRuntimeValueFails() {
|
||||
@@ -450,11 +440,8 @@ void TestUndefinedRuntimeValueFails() {
|
||||
{.dwords = {undef}, .dword_count = 1});
|
||||
DescriptorValue result;
|
||||
result.dword_count = 3;
|
||||
std::string error;
|
||||
Check(!EvaluateDescriptorSource(fixture.program, 0, 0x200, {}, result,
|
||||
&error) &&
|
||||
result.dword_count == 3 &&
|
||||
error.find("undefined typed runtime value") != std::string::npos,
|
||||
Check(!EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
|
||||
result.dword_count == 3,
|
||||
"undefined typed descriptor source did not fail transactionally");
|
||||
}
|
||||
|
||||
@@ -464,6 +451,14 @@ namespace Common {
|
||||
|
||||
int DbgExitIfHandler(const char *, const char *, int) { return 1; }
|
||||
|
||||
int DbgExitHandler(const char *, int, std::string_view text) {
|
||||
throw std::runtime_error(std::string(text));
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) {
|
||||
throw std::runtime_error(std::string(text));
|
||||
}
|
||||
|
||||
void DbgExit(int) { std::abort(); }
|
||||
|
||||
} // namespace Common
|
||||
@@ -496,9 +491,9 @@ int main() {
|
||||
// Keep this focused standalone target self-contained by amalgamating its small
|
||||
// typed-IR implementation set.
|
||||
#include "../src/graphics/shader/recompiler/ir/Block.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/Program.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/Type.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/Value.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/Program.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp"
|
||||
#include "../src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
|
||||
|
||||
@@ -85,6 +85,12 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
#include <cerrno>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -907,6 +913,31 @@ void Require(const char *shader_name, const char *stage, bool value,
|
||||
}
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
template <typename Function>
|
||||
void ExpectFatal(const char *name, Function function) {
|
||||
const pid_t pid = ::fork();
|
||||
Require(name, "fatal subprocess", pid >= 0,
|
||||
"fork failed while starting fatal shader test");
|
||||
if (pid == 0) {
|
||||
function();
|
||||
::_exit(0);
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
pid_t waited = -1;
|
||||
do {
|
||||
waited = ::waitpid(pid, &status, 0);
|
||||
} while (waited < 0 && errno == EINTR);
|
||||
Require(name, "fatal subprocess", waited == pid,
|
||||
"waitpid failed while collecting fatal shader test");
|
||||
Require(name, "fatal exit",
|
||||
WIFEXITED(status) && WEXITSTATUS(status) == (321 & 0xff),
|
||||
"fatal shader path did not terminate with status 321");
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
#endif
|
||||
|
||||
void CheckLeastRecentlyUsedCacheOrdering() {
|
||||
Common::LeastRecentlyUsedCache<uint32_t, uint64_t> cache;
|
||||
const auto first = cache.Insert(1, 1);
|
||||
@@ -979,7 +1010,6 @@ struct TestCase {
|
||||
std::vector<std::pair<std::string, size_t>> decoded_counts;
|
||||
std::vector<std::pair<std::string, size_t>> ir_counts;
|
||||
u32 expected_storage_mip_descriptors = 0;
|
||||
std::string expected_compile_error;
|
||||
};
|
||||
|
||||
struct GraphicsCase {
|
||||
@@ -1187,17 +1217,7 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
options.wave_size = test.compute_info.wave_size;
|
||||
}
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
if (!ShaderRecompiler::TryRecompile(test.code, options, result, &error)) {
|
||||
if (!test.expected_compile_error.empty() &&
|
||||
error.find(test.expected_compile_error) != std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
Fail(test.name, "decode/IR", error.c_str());
|
||||
}
|
||||
Require(test.name, "decode/IR", test.expected_compile_error.empty(),
|
||||
"shader unexpectedly compiled despite the required rejection");
|
||||
auto result = ShaderRecompiler::Recompile(test.code, options);
|
||||
for (const auto &[text, expected] : test.decoded_counts) {
|
||||
const auto actual = CountText(result.decoded_dump, text);
|
||||
Require(test.name, "decoded RDNA2", actual == expected,
|
||||
@@ -1245,9 +1265,7 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
}
|
||||
result.program.bindings = {};
|
||||
result.program.binding_layout_complete = false;
|
||||
if (!ShaderRecompiler::IR::AllocateBindings(result.program, 0, &error)) {
|
||||
Fail(test.name, "binding layout", error.c_str());
|
||||
}
|
||||
ShaderRecompiler::IR::AllocateBindings(result.program, 0);
|
||||
const auto *shader_data = ShaderRecompiler::IR::FindBinding(
|
||||
result.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::UserData);
|
||||
@@ -1256,13 +1274,8 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
result.program.bindings.push_constant_size == 0 &&
|
||||
shader_data != nullptr,
|
||||
"oversized shader data did not use its storage fallback");
|
||||
std::vector<u32> storage_spirv;
|
||||
if (!ShaderRecompiler::Spirv::EmitProgram(result.program, result.resources,
|
||||
options.input_info, storage_spirv,
|
||||
&error)) {
|
||||
Fail(test.name, "SPIR-V emit", error.c_str());
|
||||
}
|
||||
result.spirv = std::move(storage_spirv);
|
||||
result.spirv = ShaderRecompiler::Spirv::EmitProgram(
|
||||
result.program, result.resources, options.input_info);
|
||||
}
|
||||
Require(test.name, "SPIR-V emit", !result.spirv.empty(),
|
||||
"recompiler returned empty SPIR-V");
|
||||
@@ -1338,12 +1351,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
|
||||
options.input_info.pixel = &pixel_info;
|
||||
options.user_data = user_data.data();
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
if (!ShaderRecompiler::TryRecompile(test.fragment_code, options, result,
|
||||
&error)) {
|
||||
Fail(test.name, "decode/IR", error.c_str());
|
||||
}
|
||||
auto result = ShaderRecompiler::Recompile(test.fragment_code, options);
|
||||
Require(test.name, "SPIR-V emit", !result.spirv.empty(),
|
||||
"recompiler returned empty SPIR-V");
|
||||
ValidateSpirv(test.name, result.spirv);
|
||||
@@ -7579,10 +7587,7 @@ public:
|
||||
const auto allocate_bindings =
|
||||
[&](ShaderRecompiler::IR::Program &program) {
|
||||
program.shader_info_complete = true;
|
||||
std::string error;
|
||||
Require(name, "binding layout",
|
||||
ShaderRecompiler::IR::AllocateBindings(program, 0, &error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::IR::AllocateBindings(program, 0);
|
||||
};
|
||||
|
||||
constexpr auto stencil_format = Prospero::BufferFormat::k8UInt;
|
||||
@@ -7616,11 +7621,7 @@ public:
|
||||
ShaderRecompiler::IR::DescriptorValue null_descriptor{};
|
||||
null_descriptor.dword_count = 8;
|
||||
null_snapshot.images.assign(3, null_descriptor);
|
||||
std::string null_error;
|
||||
Require(name, "null specialization",
|
||||
ShaderRecompiler::IR::SpecializeResources(
|
||||
null_program, null_snapshot, &null_error),
|
||||
null_error.c_str());
|
||||
ShaderRecompiler::IR::SpecializeResources(null_program, null_snapshot);
|
||||
allocate_bindings(null_program);
|
||||
ShaderStageRuntime null_runtime{
|
||||
std::make_shared<const ShaderRecompiler::IR::Program>(
|
||||
@@ -11657,10 +11658,6 @@ void CompareGraphicsWords(const GraphicsCase &test,
|
||||
|
||||
void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
||||
auto compiled = CompileCase(test);
|
||||
if (!test.expected_compile_error.empty()) {
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
return;
|
||||
}
|
||||
if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) {
|
||||
Require(test.name, "resource specialization",
|
||||
!compiled.program.info.images.empty() &&
|
||||
@@ -18506,8 +18503,6 @@ TestCase FlatStoreVariants() {
|
||||
{O::V_MOV_B32, O::FLAT_STORE_BYTE, O::FLAT_STORE_SHORT,
|
||||
O::FLAT_STORE_DWORD, O::FLAT_STORE_DWORDX2,
|
||||
O::FLAT_STORE_DWORDX3, O::FLAT_STORE_DWORDX4, O::S_ENDPGM}};
|
||||
test.expected_compile_error =
|
||||
"writable FLAT/GLOBAL addresses require GPU ownership tracking";
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -20314,9 +20309,7 @@ void CheckIndirectImageKeySwitch() {
|
||||
program.info.samplers.push_back({1u, 0x10f0u});
|
||||
program.info.sampled_pairs.push_back({0u, 0u, 0x10f0u});
|
||||
|
||||
std::string error;
|
||||
Require(name, "binding layout", AllocateBindings(program, 0, &error),
|
||||
error.c_str());
|
||||
AllocateBindings(program, 0);
|
||||
ResourceSnapshot snapshot{};
|
||||
DescriptorValue descriptor{};
|
||||
descriptor.dword_count = 8;
|
||||
@@ -20340,14 +20333,9 @@ void CheckIndirectImageKeySwitch() {
|
||||
snapshot.samplers.push_back(sampler_descriptor);
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
std::vector<u32> spirv;
|
||||
Require(name, "SPIR-V requirements",
|
||||
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, &error),
|
||||
error.c_str());
|
||||
Require(name, "SPIR-V emit",
|
||||
ShaderRecompiler::Spirv::EmitProgram(
|
||||
program, snapshot, {.compute = &compute}, spirv, &error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
|
||||
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, snapshot,
|
||||
{.compute = &compute});
|
||||
ValidateSpirv(name, spirv);
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
||||
std::string text;
|
||||
@@ -21314,7 +21302,6 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(GlobalSignedImmediateRebasesBeforeSaddr);
|
||||
AddCase(FlatSegmentIgnoresSaddrAndMasksOffsetMsb);
|
||||
AddCase(ScratchIsPrivatePerInvocation);
|
||||
AddCase(FlatStoreVariants);
|
||||
AddCase(DsReadWriteVariants);
|
||||
AddCase(DsWriteB16D16HiCapturedUsesHighHalf);
|
||||
AddCase(DsReadU16D16CapturedPreservesHighHalf);
|
||||
@@ -21433,7 +21420,7 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
compute.thread_ids_num = 1;
|
||||
compute.workgroup_register = 8;
|
||||
|
||||
const auto Compile = [&](const char *stage, const std::vector<u32> &code) {
|
||||
const auto Compile = [&](const std::vector<u32> &code) {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.wave_size = 64;
|
||||
@@ -21442,17 +21429,13 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
||||
options.user_data = user_data.data();
|
||||
options.input_info.compute = &compute;
|
||||
options.dump_ir = false;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Require("Ps5GameExampleImageClear", stage,
|
||||
ShaderRecompiler::TryRecompile(code, options, result, &error),
|
||||
error);
|
||||
auto result = ShaderRecompiler::Recompile(code, options);
|
||||
ValidateSpirv("Ps5GameExampleImageClear", result.spirv);
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto code = MakeCode();
|
||||
auto positive = Compile("exact Prospero kernel", code);
|
||||
auto positive = Compile(code);
|
||||
|
||||
compute.stage.program = std::make_shared<const ShaderRecompiler::IR::Program>(
|
||||
std::move(positive.program));
|
||||
@@ -21543,11 +21526,7 @@ void CheckEmbeddedFetchVertexOffset() {
|
||||
options.user_data = user_data.data();
|
||||
options.input_info.vertex = &vertex;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Require(name, "compile",
|
||||
ShaderRecompiler::TryRecompile(code, options, result, &error),
|
||||
error);
|
||||
auto result = ShaderRecompiler::Recompile(code, options);
|
||||
Require(name, "fetch rewrite",
|
||||
result.program.info.vertex_fetch_components[0] == 4,
|
||||
"encoded fetch sequence was not recognized and rewritten");
|
||||
@@ -23477,12 +23456,8 @@ void CheckImageSamplerSpecialization() {
|
||||
ShaderRecompiler::IR::DescriptorValue sampler_descriptor{};
|
||||
sampler_descriptor.dword_count = 4;
|
||||
mixed_sampler_snapshot.samplers.push_back(sampler_descriptor);
|
||||
std::string specialization_error;
|
||||
Require("ImageSamplerSpecialization", "mixed sampler specialization",
|
||||
ShaderRecompiler::IR::SpecializeResources(mixed_sampler_program,
|
||||
mixed_sampler_snapshot,
|
||||
&specialization_error),
|
||||
specialization_error);
|
||||
ShaderRecompiler::IR::SpecializeResources(mixed_sampler_program,
|
||||
mixed_sampler_snapshot);
|
||||
Require("ImageSamplerSpecialization", "mixed sampler variant",
|
||||
mixed_sampler_program.info.samplers.size() == 2u &&
|
||||
!mixed_sampler_program.info.samplers[0].force_point_filtering &&
|
||||
@@ -23493,25 +23468,45 @@ void CheckImageSamplerSpecialization() {
|
||||
"a shared native/bit-packed sampler was not split into point and "
|
||||
"native variants");
|
||||
|
||||
ShaderRecompiler::IR::Program packed_atomic_program;
|
||||
packed_atomic_program.resource_tracking_complete = true;
|
||||
auto packed_atomic_image = sampled_image;
|
||||
packed_atomic_image.kind =
|
||||
ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
packed_atomic_image.written = true;
|
||||
packed_atomic_image.atomic = true;
|
||||
packed_atomic_program.info.images.push_back(packed_atomic_image);
|
||||
ShaderRecompiler::IR::ResourceSnapshot packed_atomic_snapshot;
|
||||
packed_atomic_snapshot.images.push_back(packed_image_descriptor);
|
||||
std::string atomic_error;
|
||||
Require("ImageSamplerSpecialization", "packed atomic rejection",
|
||||
!ShaderRecompiler::IR::SpecializeResources(
|
||||
packed_atomic_program, packed_atomic_snapshot, &atomic_error) &&
|
||||
atomic_error.find("unsupported format 34") != std::string::npos,
|
||||
"a non-atomic PS5 packed format entered the typed atomic path");
|
||||
std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId");
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
void CheckShaderRecompilerFatalContracts() {
|
||||
ExpectFatal("WritableFlatStoreRejection", [] {
|
||||
const auto test = FlatStoreVariants();
|
||||
(void)CompileCase(test);
|
||||
});
|
||||
|
||||
ExpectFatal("PackedAtomicImageRejection", [] {
|
||||
ShaderRecompiler::IR::Program program;
|
||||
program.resource_tracking_complete = true;
|
||||
|
||||
ShaderRecompiler::IR::ImageResource image;
|
||||
image.kind = ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
image.read = true;
|
||||
image.written = true;
|
||||
image.atomic = true;
|
||||
program.info.images.push_back(image);
|
||||
|
||||
ShaderRecompiler::IR::DescriptorValue descriptor{};
|
||||
descriptor.dword_count = 8;
|
||||
descriptor.dwords[0] = 0x2000u;
|
||||
descriptor.dwords[1] =
|
||||
static_cast<uint32_t>(Prospero::BufferFormat::k11_11_10UInt) << 20u;
|
||||
descriptor.dwords[2] = 3u | (3u << 14u);
|
||||
descriptor.dwords[3] =
|
||||
DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
||||
|
||||
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
||||
snapshot.images.push_back(descriptor);
|
||||
ShaderRecompiler::IR::SpecializeResources(program, snapshot);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void CheckStorageTextureVolumeUploadLayout() {
|
||||
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
|
||||
constexpr uint32_t width = 33;
|
||||
@@ -24232,10 +24227,7 @@ void CheckEmbeddedFetchLaneSpill() {
|
||||
options.user_data = user_data.data();
|
||||
options.input_info.vertex = &vertex;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Require("EmbeddedFetchLaneSpill", "compile",
|
||||
ShaderRecompiler::TryRecompile(code, options, result, &error), error);
|
||||
auto result = ShaderRecompiler::Recompile(code, options);
|
||||
Require("EmbeddedFetchLaneSpill", "fetch rewrite",
|
||||
result.program.info.vertex_fetch_components[0] == 4,
|
||||
"lane-spilled fetch descriptor was not recognized and rewritten");
|
||||
@@ -25041,6 +25033,12 @@ int main(int argc, char **argv) {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
EnsureConfigInitialized();
|
||||
CheckLeastRecentlyUsedCacheOrdering();
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
if (argc == 2 && std::strcmp(argv[1], "--shader-fatal-only") == 0) {
|
||||
CheckShaderRecompilerFatalContracts();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {
|
||||
CheckClipControlDepthClipState();
|
||||
return 0;
|
||||
@@ -25301,6 +25299,7 @@ int main(int argc, char **argv) {
|
||||
std::fprintf(stderr, "unknown test selector: %s\n", argv[1]);
|
||||
return 2;
|
||||
}
|
||||
CheckShaderRecompilerFatalContracts();
|
||||
VulkanHarness vulkan;
|
||||
#endif
|
||||
CheckClipControlDepthClipState();
|
||||
|
||||
@@ -52,7 +52,7 @@ SrtProgram(uint64_t address) {
|
||||
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, 0x40});
|
||||
program.srt_reads.push_back({Value(&raw), 0});
|
||||
|
||||
auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource);
|
||||
auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst,
|
||||
@@ -77,17 +77,38 @@ UnbasedFlatProgram() {
|
||||
return std::make_shared<const Program>(std::move(program));
|
||||
}
|
||||
|
||||
std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program>
|
||||
UserDataBufferProgram() {
|
||||
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 std::make_shared<const Program>(std::move(program));
|
||||
}
|
||||
|
||||
void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
using namespace Libs::Graphics;
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
|
||||
ShaderStageRuntime stage;
|
||||
std::string error;
|
||||
uint32_t specialization_reads = 0;
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error,
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage,
|
||||
RejectSpecializationRead,
|
||||
&specialization_reads),
|
||||
error.c_str());
|
||||
"mapped SRT stage materialization failed");
|
||||
Check(specialization_reads == 0,
|
||||
"ordinary SRT read used the specialization reader");
|
||||
Check(stage.program == cached_program && stage.resources != nullptr,
|
||||
@@ -104,16 +125,36 @@ void TestUnbasedFlatCacheHitMaterializes() {
|
||||
auto prior_resources =
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
ShaderStageRuntime stage{prior_program, prior_resources};
|
||||
std::string error;
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
|
||||
"unbased FLAT stage materialization failed");
|
||||
Check(stage.program == cached_program && stage.resources != prior_resources,
|
||||
"unbased FLAT cache hit did not publish its BDA stage snapshot");
|
||||
}
|
||||
|
||||
void TestFailedMaterializationPreservesPriorStage() {
|
||||
using namespace Libs::Graphics;
|
||||
auto cached_program = UserDataBufferProgram();
|
||||
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
|
||||
auto prior_resources =
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
ShaderStageRuntime stage{prior_program, prior_resources};
|
||||
|
||||
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
|
||||
"missing runtime user data did not reject the cached stage");
|
||||
Check(stage.program == prior_program && stage.resources == prior_resources,
|
||||
"failed cache materialization replaced the prior stage");
|
||||
}
|
||||
|
||||
} // 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(); }
|
||||
@@ -123,6 +164,7 @@ void DbgExit(int) { std::abort(); }
|
||||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
TestFailedMaterializationPreservesPriorStage();
|
||||
std::puts("ShaderStageRuntimeTests: all cases passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -130,7 +172,7 @@ int main() {
|
||||
// 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/Program.cpp"
|
||||
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
|
||||
|
||||
+629
-1261
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user