shader: rework resource specialization and optimize pipeline caching

This commit is contained in:
nmzik
2026-09-03 02:58:11 +02:00
parent c48a8bd6ae
commit 98200fed19
39 changed files with 2281 additions and 2235 deletions
+184 -122
View File
@@ -8,6 +8,7 @@
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include <array>
#include <bit>
#include <cstring>
#include <iostream>
#include <limits>
@@ -29,6 +30,14 @@ void Check(bool condition, const char *message) {
}
}
bool SameResourceSnapshot(const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data;
}
template <typename F>
void CheckFatal(F &&function, std::string_view expected, const char *message) {
try {
@@ -250,6 +259,7 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
void TestInvariantIndirectImageMaterialization() {
auto fixture = MakeIndirectImageFixture(false);
fixture->PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture->program);
EliminateDeadCode(fixture->program.blocks);
ValidateProgram(fixture->program, true);
@@ -298,24 +308,20 @@ void TestInvariantIndirectImageMaterialization() {
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
const auto same_snapshot = [](const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data &&
lhs.indirect_images.empty() == rhs.indirect_images.empty();
};
Check(MaterializeResources(fixture->program, runtime, snapshot) &&
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 1 &&
std::equal(image_descriptor.begin(), image_descriptor.end(),
snapshot.images[0].dwords.begin()),
"invariant indirect image table did not materialize");
const auto prior_snapshot = snapshot;
const auto prior_specialization = specialization;
memory.fail_address = 0x1004u;
Check(!MaterializeResources(fixture->program, runtime, snapshot) &&
same_snapshot(snapshot, prior_snapshot),
Check(!MaterializeResources(resource_plan, runtime, snapshot,
specialization) &&
SameResourceSnapshot(snapshot, prior_snapshot) &&
specialization == prior_specialization,
"rejected planning memory read mutated the snapshot");
memory.fail_address = UINT64_MAX;
@@ -330,8 +336,9 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2020u - memory.base) / 4u + 3u] =
image_descriptor[3] ^ (1u << 28u);
ResourceSnapshot null_snapshot;
Check(MaterializeResources(fixture->program, runtime, null_snapshot) &&
null_snapshot.indirect_images.empty() &&
ResourceSpecialization null_specialization;
Check(MaterializeResources(resource_plan, runtime, null_snapshot,
null_specialization) &&
std::ranges::all_of(null_snapshot.images[0].dwords,
[](uint32_t dword) { return dword == 0u; }),
"stale typed null image descriptors were not canonicalized");
@@ -345,19 +352,25 @@ 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) &&
dynamic_snapshot.images.size() == 1 &&
dynamic_snapshot.indirect_images.size() == 1 &&
dynamic_snapshot.indirect_images[0].descriptors.size() == 2,
ResourceSpecialization dynamic_specialization;
Check(MaterializeResources(resource_plan, runtime, dynamic_snapshot,
dynamic_specialization) &&
dynamic_snapshot.images.size() == 2 &&
dynamic_specialization.images.size() == 2,
"dynamic indirect image table did not materialize");
SpecializeResources(fixture->program, dynamic_snapshot);
ApplyResourceSpecialization(fixture->program, dynamic_specialization);
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_search_iterations != 0 &&
fixture->program.info.images[0].indirect_resources.size() == 2 &&
dynamic_snapshot.images.size() == 2 &&
dynamic_snapshot.indirect_images.empty(),
dynamic_snapshot.images.size() == 2,
"dynamic indirect image table was not specialized transactionally");
const auto &mapping = dynamic_specialization.images[0];
const auto key_count = dynamic_snapshot.flattened_srt[mapping.indirect_mapping_offset];
Check(mapping.indirect_search_iterations == std::bit_width(key_count) &&
mapping.indirect_mapping_offset + 1u + key_count * 2u ==
dynamic_snapshot.flattened_srt.size(),
"indirect image mapping retained worst-case padding");
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
memory.words[(0x2000u - memory.base) / 4u + dword] =
@@ -368,22 +381,25 @@ 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) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
ResourceSpecialization rebound_specialization;
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization == dynamic_specialization,
"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) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
"runtime indirect key mapping did not accept collapsed candidates");
const auto collapsed_snapshot = rebound_snapshot;
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization != dynamic_specialization,
"collapsed indirect candidates did not select a new specialization");
const auto collapsed_specialization = rebound_specialization;
ResourceSnapshot capacity_snapshot;
ResourceSpecialization capacity_specialization;
for (const uint32_t records : {1u, 3u}) {
user_data[2] = records;
Check(
MaterializeResources(fixture->program, runtime, capacity_snapshot) &&
ValidateResourceSpecialization(fixture->program, capacity_snapshot),
"runtime indirect key mapping rejected a fitting material-table size");
Check(MaterializeResources(resource_plan, runtime, capacity_snapshot,
capacity_specialization),
"runtime indirect key mapping rejected a valid material-table size");
}
user_data[2] = 2u;
memory.words[(0x2020u - memory.base) / 4u] =
@@ -395,13 +411,14 @@ void TestInvariantIndirectImageMaterialization() {
image_descriptor[dword];
}
memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u;
Check(
!MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
same_snapshot(rebound_snapshot, collapsed_snapshot),
"larger indirect candidate topology reused or mutated a cached snapshot");
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization != collapsed_specialization,
"larger indirect candidate topology reused the old specialization");
auto memory_backed = MakeIndirectImageFixture(false, 0u, true);
memory_backed->PlanAndTrack();
auto memory_backed_plan = ExtractResourcePlan(memory_backed->program);
EliminateDeadCode(memory_backed->program.blocks);
std::array<uint32_t, 11> memory_backed_user_data{0x1000u, 224u << 16u, 2u, 0u,
0x2000u, 16u << 16u, 4u, 0u,
@@ -413,9 +430,12 @@ void TestInvariantIndirectImageMaterialization() {
.userdata = &memory,
.read_specialization_memory =
ReadLinearTestMemory};
Check(!MaterializeResources(memory_backed->program, memory_backed_runtime,
snapshot) &&
same_snapshot(snapshot, prior_snapshot),
const auto memory_backed_prior_snapshot = snapshot;
const auto memory_backed_prior_specialization = specialization;
Check(!MaterializeResources(memory_backed_plan, memory_backed_runtime,
snapshot, specialization) &&
SameResourceSnapshot(snapshot, memory_backed_prior_snapshot) &&
specialization == memory_backed_prior_specialization,
"rejected indirect table descriptor read mutated the snapshot");
memory.fail_address = UINT64_MAX;
@@ -728,6 +748,7 @@ void TestDynamicStorageMipTracking() {
const auto mip2 = AddStore(12, true, Value(2u));
const auto dynamic = AddStore(16, true, fixture.UserData(8));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
const auto &images = fixture.program.info.images;
Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None &&
@@ -758,15 +779,23 @@ void TestDynamicStorageMipTracking() {
<< 28u);
descriptor.dwords[5] = 3u << 4u;
descriptor.dword_count = 8;
std::array<uint32_t, 9> user_data{};
std::copy(descriptor.dwords.begin(), descriptor.dwords.end(),
user_data.begin());
user_data[8] = 2u;
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
snapshot.images.assign(images.size(), descriptor);
SpecializeResources(fixture.program, snapshot);
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"dynamic storage resources did not materialize");
ApplyResourceSpecialization(fixture.program, specialization);
Check(fixture.program.info.images[1].mip_count == 3 &&
ValidateResourceSpecialization(fixture.program, snapshot),
snapshot.images.size() == fixture.program.info.images.size(),
"base-1 through last-3 dynamic storage range was not specialized");
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
const auto storage_kind = DescriptorBindingForImage(images[0]);
Check(storage_kind.has_value(), "storage image has no descriptor binding");
const auto *storage_binding =
@@ -775,32 +804,58 @@ void TestDynamicStorageMipTracking() {
storage_binding->resources == std::vector<uint32_t>({0, 1, 1, 1}),
"dynamic storage mip descriptors were not expanded consecutively");
Program null_program;
null_program.resource_tracking_complete = true;
ImageResource null_image;
null_image.resource_class = ImageResourceClass::Storage;
null_image.dimension = Decoder::ImageDimension::Dim2D;
null_image.mip_mode = ImageMipMode::DynamicStorage;
null_image.written = true;
null_program.info.images.push_back(null_image);
Fixture null_fixture;
const auto null_handle = null_fixture.Image(
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)});
const auto null_address = null_fixture.Emit(
ValueOpcode::MakeImageAddress,
{Value(0u), Value(0u), null_fixture.UserData(0), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)});
MemoryInfo null_memory;
null_memory.kind = ResourceKind::Image;
null_memory.image_dimension = Decoder::ImageDimension::Dim2D;
null_memory.image_address_components = 3u;
null_memory.image_has_mip = true;
const auto null_data = null_fixture.Emit(
ValueOpcode::CompositeConstructU32x4,
{Value(1u), Value(2u), Value(3u), Value(4u)});
null_fixture.Emit(ValueOpcode::ImageWrite,
{null_handle, null_address, null_data, Value(true)},
null_fixture.AddMemory(null_memory, 4));
null_fixture.PlanAndTrack();
auto null_plan = ExtractResourcePlan(null_fixture.program);
ResourceSnapshot null_snapshot;
DescriptorValue null_descriptor{};
null_descriptor.dword_count = 8;
null_snapshot.images.push_back(null_descriptor);
SpecializeResources(null_program, null_snapshot);
Check(null_program.info.images[0].mip_count == 1 &&
ValidateResourceSpecialization(null_program, null_snapshot),
ResourceSpecialization null_specialization;
const std::array<uint32_t, 1> null_user_data{0u};
Check(MaterializeResources(null_plan, {.user_data = null_user_data},
null_snapshot, null_specialization),
"canonical null dynamic storage image did not materialize");
ApplyResourceSpecialization(null_fixture.program, null_specialization);
Check(null_fixture.program.info.images[0].mip_count == 1 &&
null_snapshot.images.size() == 1,
"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),
"a changed dynamic storage mip count reused the specialization");
snapshot.images[1].dwords[3] =
(snapshot.images[1].dwords[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
auto changed_user_data = user_data;
changed_user_data[3] =
(changed_user_data[3] & ~(0xfu << 16u)) | (2u << 16u);
ResourceSnapshot changed_snapshot;
ResourceSpecialization changed_specialization;
Check(MaterializeResources(resource_plan, {.user_data = changed_user_data},
changed_snapshot, changed_specialization) &&
changed_specialization != specialization,
"a changed dynamic storage mip count reused the specialization key");
changed_user_data[3] =
(changed_user_data[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
(4u << 12u) | (3u << 16u);
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"an inverted dynamic storage mip range was accepted");
const auto valid_snapshot = changed_snapshot;
const auto valid_specialization = changed_specialization;
Check(!MaterializeResources(resource_plan, {.user_data = changed_user_data},
changed_snapshot, changed_specialization) &&
SameResourceSnapshot(changed_snapshot, valid_snapshot) &&
changed_specialization == valid_specialization,
"an inverted dynamic storage mip range was accepted or mutated output");
}
void TestSrtFlatteningAndRuntimeMemoization() {
@@ -843,7 +898,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
.userdata = &memory};
std::vector<DescriptorValue> descriptors;
std::vector<uint32_t> flat;
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source};
const uint32_t request = fixture.program.info.buffers[0].source;
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
descriptors, flat, {}),
"typed runtime source evaluation failed");
@@ -863,7 +918,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
"flattened typed SRT reads did not receive a binding");
@@ -906,7 +961,7 @@ void TestDynamicSrtReadRemainsExplicit() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
@@ -1023,15 +1078,18 @@ void TestDmaAddressMaterialization() {
{unbased, Value(0u), Value(0u), Value(9u), Value(true)},
fixture.AddMemory(flat, 8));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
Check(fixture.program.info.uses_dma,
"typed address operations did not enable DMA");
std::array<uint32_t, 2> user_data{0x2008u, 0u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"DMA shader resources did not materialize");
SpecializeResources(fixture.program, snapshot);
ApplyResourceSpecialization(fixture.program, specialization);
}
void TestDynamicFlatAddressesUseDma() {
@@ -1053,13 +1111,16 @@ void TestDynamicFlatAddressesUseDma() {
fixture.Emit(ValueOpcode::LoadAddressU8, {address, low, high, active},
fixture.AddMemory(flat, 0xa4));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
Check(fixture.program.info.uses_dma,
"exec-masked FLAT address did not enable DMA");
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"exec-masked FLAT shader resources did not materialize");
Fixture mismatch;
@@ -1095,6 +1156,7 @@ void TestBufferSwizzleSpecialization() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, 4));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
constexpr auto swizzle = Libs::Graphics::DstSel(4, 5, 0, 1);
std::array<uint32_t, 4> user_data{
@@ -1106,16 +1168,22 @@ void TestBufferSwizzleSpecialization() {
(1u << 24u)};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"buffer resources did not materialize");
SpecializeResources(fixture.program, snapshot);
ApplyResourceSpecialization(fixture.program, specialization);
Check(fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
ValidateResourceSpecialization(fixture.program, snapshot),
specialization.buffers[0].descriptor_swizzle == swizzle,
"buffer destination selectors were not specialized");
snapshot.buffers[0].dwords[3] ^= 1u << 9u;
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"buffer swizzle change did not invalidate specialization");
user_data[3] ^= 1u << 9u;
ResourceSnapshot changed_snapshot;
ResourceSpecialization changed_specialization;
Check(MaterializeResources(resource_plan, runtime, changed_snapshot,
changed_specialization) &&
changed_specialization != specialization,
"buffer swizzle change did not select a new specialization key");
}
void TestShaderInfoAndBindingLayout() {
@@ -1147,15 +1215,14 @@ void TestShaderInfoAndBindingLayout() {
StageInputKind::GlobalInvocationId,
"typed shader values were not reflected in shader info");
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
nullptr &&
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::UserData) == nullptr &&
fixture.program.bindings.push_constant_size ==
fixture.program.bindings.ShaderDataDwords() * sizeof(uint32_t),
DescriptorBindingKind::ShaderData) == nullptr &&
fixture.program.bindings.UsesPushData(),
"typed resources were not assigned native bindings");
Check(NativeBinding(ShaderType::Compute, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Buffers) &&
@@ -1180,7 +1247,7 @@ void TestImageBindingAbi() {
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 39u &&
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 40u &&
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 41u &&
static_cast<uint32_t>(DescriptorBindingKind::UserData) == 42u &&
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 42u &&
static_cast<uint32_t>(DescriptorBindingKind::Count) == 43u,
"native descriptor binding anchors changed");
@@ -1284,58 +1351,53 @@ void TestGraphicsPushConstantLayout() {
}
fixture.program.shader_info_complete = true;
};
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
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;
uint32_t cursor = 0;
Fixture pixel(ShaderType::Pixel);
AddUserData(pixel, 4);
AllocateBindings(pixel.program, pixel_offset);
AllocateBindings(pixel.program, cursor);
Check(
pixel.program.bindings.push_constant_offset == pixel_offset &&
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
pixel.program.bindings.UsesPushData() &&
pixel.program.bindings.push_data_start_dword == 0 &&
FindBinding(pixel.program.bindings,
DescriptorBindingKind::UserData) == nullptr,
"pixel shader did not follow the vertex data in the graphics push bank");
DescriptorBindingKind::ShaderData) == nullptr,
"pixel shader did not start the shared push-data block");
pixel.program.bindings.AdvancePushData(cursor);
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
AllocateBindings(vertex.program, cursor);
Check(vertex.program.bindings.UsesPushData() &&
vertex.program.bindings.push_data_start_dword == 4,
"vertex shader did not follow pixel data in the shared push-data block");
vertex.program.bindings.AdvancePushData(cursor);
Check(cursor == 13, "graphics push-data cursor advanced incorrectly");
Fixture edge(ShaderType::Pixel);
AddUserData(edge, 1);
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");
AddUserData(edge, NativePushConstantSize / sizeof(uint32_t));
AllocateBindings(edge.program);
Check(edge.program.bindings.UsesPushData() &&
FindBinding(edge.program.bindings,
DescriptorBindingKind::ShaderData) == nullptr,
"the full shared push-data block did not fit");
Fixture spill(ShaderType::Pixel);
AddUserData(spill, 32);
AllocateBindings(spill.program, pixel_offset);
AddUserData(spill, 20);
AllocateBindings(spill.program, cursor);
Check(
spill.program.bindings.push_constant_size == 0 &&
!spill.program.bindings.UsesPushData() &&
spill.program.bindings.push_data_start_dword == PushData::NoStart &&
FindBinding(spill.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
"pixel shader overlapping the vertex push data did not spill to storage");
DescriptorBindingKind::ShaderData) != nullptr,
"a stage that exceeded the remaining shared push data did not spill to storage");
const auto spill_layout = spill.program.bindings;
spill.program.bindings.AdvancePushData(cursor);
Check(cursor == 13, "a spilled stage consumed shared push-data space");
Fixture full(ShaderType::Pixel);
AddUserData(full, 1);
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);
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");
Fixture repeated_spill(ShaderType::Pixel);
AddUserData(repeated_spill, 20);
AllocateBindings(repeated_spill.program, 20);
Check(repeated_spill.program.bindings == spill_layout,
"storage fallback retained an irrelevant attempted push-data position");
}
void TestResourceLimitIsTransactional() {