shader: collect usage once and emit descriptor slots directly

This commit is contained in:
nmzik
2026-10-09 02:17:38 +04:00
parent b35cdc39b7
commit e1ac893a0c
28 changed files with 1068 additions and 1408 deletions
+40 -107
View File
@@ -1,6 +1,5 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
@@ -2009,12 +2008,8 @@ void TestFmaskLoadSpecialization() {
"inactive FMASK load did not preserve the execution mask");
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program);
const auto kind = DescriptorBindingForImage(fixture.program.info.images[0]);
Check(kind.has_value() &&
FindBinding(fixture.program.bindings, *kind)->resources ==
std::vector<uint32_t>{0},
"FMASK allocated an ordinary image descriptor");
Check(DescriptorBindingForImage(fixture.program.info.images[0]).has_value(),
"FMASK removal lost the ordinary image class");
user_data[8] = 0;
user_data[1] = static_cast<uint32_t>(Prospero::BufferFormat::k8UInt) << 20u;
ResourceSpecialization rebound;
@@ -2152,14 +2147,9 @@ void TestDynamicStorageMipTracking() {
"base-1 through last-3 dynamic storage range was not specialized");
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
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 =
FindBinding(fixture.program.bindings, *storage_kind);
Check(storage_binding != nullptr &&
storage_binding->resources == std::vector<uint32_t>({0, 1, 1, 1}),
"dynamic storage mip descriptors were not expanded consecutively");
Check(DescriptorBindingForImage(images[0]) == DescriptorBindingForImage(images[1]) &&
images[0].mip_count == 1 && images[1].mip_mode == ImageMipMode::Dynamic,
"dynamic storage mip metadata did not retain one homogeneous image class");
Fixture null_fixture;
const auto null_handle = null_fixture.Image(
@@ -2284,10 +2274,8 @@ void TestSrtFlatteningAndRuntimeMemoization() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
"flattened typed SRT reads did not receive a binding");
Check(fixture.program.info.uses_flattened_srt,
"flattened typed SRT reads were not collected");
}
void TestDynamicSrtReadRemainsExplicit() {
@@ -2327,20 +2315,9 @@ void TestDynamicSrtReadRemainsExplicit() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::BdaPagetable) != nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FaultBuffer) != nullptr,
"dynamic scalar read received the wrong resource bindings");
Check(fixture.program.bindings.memory_offset_dword ==
fixture.program.bindings.user_data_registers.size() &&
fixture.program.bindings.memory_offset_count == 1u &&
fixture.program.bindings.ShaderDataDwords() ==
fixture.program.bindings.memory_offset_dword + 1u,
"unified memory-offset layout is inconsistent");
Check(!fixture.program.info.uses_flattened_srt && fixture.program.info.uses_dma &&
fixture.program.info.live_buffers == 1u,
"dynamic scalar read received the wrong resource usage");
}
void TestWritableDescriptorPhi() {
@@ -3240,7 +3217,7 @@ void TestConditionalIndirectImageMaterialization() {
"taken indirect image branch did not require its descriptor table");
}
void TestShaderInfoAndBindingLayout() {
void TestShaderInfoCollection() {
Fixture fixture;
const auto handle = fixture.Buffer(
{fixture.UserData(3), fixture.UserData(4), Value(64u), Value(0u)}, 4);
@@ -3270,15 +3247,9 @@ void TestShaderInfoAndBindingLayout() {
StageInputKind::GlobalInvocationId,
"typed shader values were not reflected in shader info");
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
nullptr &&
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::ShaderData) == nullptr &&
fixture.program.bindings.UsesPushData(),
"typed resources were not assigned native bindings");
Check(fixture.program.info.live_buffers == 1u &&
fixture.program.info.uses_gds && !fixture.program.info.uses_lds,
"typed resources were not collected in shader info");
Check(NativeBinding(ShaderType::Compute, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Buffers) &&
NativeBinding(ShaderType::Vertex, DescriptorBindingKind::Buffers) ==
@@ -3287,9 +3258,8 @@ void TestShaderInfoAndBindingLayout() {
static_cast<uint32_t>(DescriptorBindingKind::Count) +
static_cast<uint32_t>(DescriptorBindingKind::Buffers),
"fixed stage binding ranges are inconsistent");
Check(fixture.program.bindings.user_data_registers ==
std::vector<uint32_t>({3u, 4u}),
"binding layout did not collect live typed user-data values");
Check(fixture.program.info.user_data_registers == std::vector<uint32_t>({3u, 4u}),
"shader info did not collect live typed user-data values");
}
void TestImageBindingAbi() {
@@ -3414,60 +3384,27 @@ void TestImageBindingAbi() {
Check(Invalid(image), "float atomic image received a descriptor binding");
}
void TestGraphicsPushConstantLayout() {
const auto AddUserData = [](Fixture &fixture, uint32_t count) {
for (uint32_t index = 0; index < count; index++) {
fixture.Emit(ValueOpcode::ReferenceU32, {fixture.UserData(index)});
}
fixture.program.shader_info_complete = true;
};
uint32_t cursor = 0;
Fixture pixel(ShaderType::Pixel);
AddUserData(pixel, 4);
AllocateBindings(pixel.program, cursor);
Check(
pixel.program.bindings.UsesPushData() &&
pixel.program.bindings.push_data_start_dword == 0 &&
FindBinding(pixel.program.bindings,
DescriptorBindingKind::ShaderData) == nullptr,
"pixel shader did not start the shared push-data block");
pixel.program.bindings.AdvancePushData(cursor);
void TestLiveShaderDataCollection() {
Fixture fixture;
fixture.UserData(7);
fixture.Emit(ValueOpcode::ReferenceU32, {fixture.UserData(5)});
fixture.Emit(ValueOpcode::ReferenceU32, {fixture.UserData(2)});
fixture.Emit(ValueOpcode::ReferenceU32, {fixture.UserData(5)});
fixture.Emit(ValueOpcode::GetDispatchThreadExtent, {Value(1u)});
fixture.PlanAndTrack();
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
Check(fixture.program.info.user_data_registers == std::vector<uint32_t>({2u, 5u}) &&
!fixture.program.info.uses_dispatch_threads,
"shader data collection retained unused values or duplicated registers");
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, 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, 20);
AllocateBindings(spill.program, cursor);
Check(
!spill.program.bindings.UsesPushData() &&
spill.program.bindings.push_data_start_dword == PushData::NoStart &&
FindBinding(spill.program.bindings,
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 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");
const auto extent = fixture.Emit(ValueOpcode::GetDispatchThreadExtent, {Value(0u)});
fixture.Emit(ValueOpcode::ReferenceU32, {extent});
fixture.program.shader_info_complete = false;
CollectShaderInfo(fixture.program, {.compute = &compute});
Check(fixture.program.info.uses_dispatch_threads &&
fixture.program.info.user_data_registers == std::vector<uint32_t>({2u, 5u}),
"shader data recollection lost a live dispatch extent or retained stale registers");
}
void TestResourceLimitIsTransactional() {
@@ -3489,12 +3426,8 @@ void TestResourceLimitIsTransactional() {
"compute shader did not retain all 64 distinct buffers");
ShaderComputeInputInfo compute{};
CollectShaderInfo(accepted.program, {.compute = &compute});
AllocateBindings(accepted.program);
const auto *binding = FindBinding(accepted.program.bindings,
DescriptorBindingKind::Buffers);
Check(binding != nullptr && binding->resources.size() == 64u &&
accepted.program.bindings.memory_offset_count == 64u,
"compute shader binding layout truncated the 64 buffers");
Check(accepted.program.info.live_buffers == UINT64_MAX,
"shader info truncated the 64 live buffers");
Fixture fixture;
MemoryInfo memory;
@@ -3596,9 +3529,9 @@ int main() {
Run("guarded scalar descriptor reads", TestGuardedScalarDescriptorReads);
Run("conservative buffer reachability", TestConservativeBufferReachability);
Run("conditional indirect image", TestConditionalIndirectImageMaterialization);
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
Run("shader info collection", TestShaderInfoCollection);
Run("image binding ABI", TestImageBindingAbi);
Run("graphics push constants", TestGraphicsPushConstantLayout);
Run("live shader data collection", TestLiveShaderDataCollection);
Run("resource limit", TestResourceLimitIsTransactional);
Run("malformed memory kinds", TestMalformedMemoryKindsRejected);
} catch (const std::exception &exception) {
+129 -196
View File
@@ -41,7 +41,7 @@
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
#include "graphics/shader/rectListShader.h"
#include "graphics/shader/shader.h"
#include "graphics/shader/shaderCompiler.h"
@@ -456,11 +456,12 @@ struct RenderExecutorTestAccess {
: program.stage == ShaderType::Pixel
? vk::ShaderStageFlagBits::eFragment
: vk::ShaderStageFlagBits::eCompute;
for (const auto &binding : program.bindings.descriptors) {
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
const auto index = std::countr_zero(mask);
const auto kind = static_cast<ShaderRecompiler::IR::DescriptorBindingKind>(index);
layout_bindings.push_back(
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding),
shader_stage});
{ShaderRecompiler::IR::NativeBinding(program.stage, kind),
NativeDescriptorType(kind), program.bindings.descriptor_counts[index], shader_stage});
}
}
vk::DescriptorSetLayoutCreateInfo descriptor_info{};
@@ -1676,15 +1677,11 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
image != result.program.info.images.end() &&
image->mip_count == test.expected_mip_descriptors,
"runtime descriptor range did not specialize the mip count");
const auto resource =
static_cast<u32>(image - result.program.info.images.begin());
const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(*image);
const auto *binding = kind ? ShaderRecompiler::IR::FindBinding(
result.program.bindings, *kind) : nullptr;
Require(test.name, "dynamic mip binding",
binding != nullptr &&
std::ranges::count(binding->resources, resource) ==
test.expected_mip_descriptors,
kind && image->descriptor_index != UINT32_MAX &&
image->descriptor_index + image->mip_count <=
result.program.bindings.descriptor_counts[static_cast<size_t>(*kind)],
"dynamic image did not receive one descriptor per mip");
}
if (test.expand_shader_data_storage) {
@@ -1697,49 +1694,45 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
block.AppendNewInst(ShaderRecompiler::IR::ValueOpcode::ReferenceU32,
{ShaderRecompiler::IR::Value(&user_data)});
}
result.program.bindings = {};
result.program.binding_layout_complete = false;
ShaderRecompiler::IR::AllocateBindings(result.program);
const auto *shader_data = ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::ShaderData);
result.program.shader_info_complete = false;
ShaderRecompiler::IR::CollectShaderInfo(result.program, options.input_info);
result.spirv = ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info);
const auto shader_data = result.program.bindings.descriptor_counts[
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::ShaderData)];
Require(test.name, "binding layout",
result.program.bindings.user_data_registers.size() == 33 &&
result.program.info.user_data_registers.size() == 33 &&
!result.program.bindings.UsesPushData() &&
shader_data != nullptr,
shader_data != 0,
"oversized shader data did not use its storage fallback");
result.spirv = ShaderRecompiler::Spirv::EmitProgram(result.program,
options.input_info);
}
Require(test.name, "SPIR-V emit", !result.spirv.empty(),
"recompiler returned empty SPIR-V");
ValidateSpirv(test.name, result.spirv);
CheckSpirvText(test, result.spirv);
const auto *buffer_binding = ShaderRecompiler::IR::FindBinding(
result.program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
if (test.expected_buffer_resources) {
std::vector<u32> resources;
for (u32 i = 0; i < result.program.info.buffers.size(); ++i) {
if (result.program.info.buffers[i].descriptor_index != UINT32_MAX) resources.push_back(i);
}
Require(test.name, "live buffer bindings",
buffer_binding != nullptr &&
buffer_binding->resources == *test.expected_buffer_resources,
resources == *test.expected_buffer_resources,
"dead buffer changed the surviving descriptor order");
}
std::vector<u32> packed_user_data;
for (const auto reg : result.program.bindings.user_data_registers) {
for (const auto reg : result.program.info.user_data_registers) {
packed_user_data.push_back(
resources.user_data[reg - result.program.user_data_base]);
}
packed_user_data.resize(result.program.bindings.ShaderDataDwords());
const auto buffer_count = result.program.bindings.memory_offset_count;
for (u32 i = 0; i < buffer_count; i++) {
u32 offset = 0;
const auto resource = buffer_binding->resources[i];
if (resource < test.storage_buffer_offsets.size()) {
offset = test.storage_buffer_offsets[resource];
}
for (u32 resource = 0; resource < result.program.info.buffers.size(); ++resource) {
const auto index = result.program.info.buffers[resource].descriptor_index;
if (index == UINT32_MAX) continue;
const auto offset = resource < test.storage_buffer_offsets.size()
? test.storage_buffer_offsets[resource] : 0u;
Require(test.name, "shader data", offset < 256,
"storage buffer offset is not representable");
packed_user_data[result.program.bindings.memory_offset_dword + i / 4u] |=
offset << ((i % 4u) * 8u);
packed_user_data[result.program.bindings.memory_offset_dword + index / 4u] |=
offset << ((index % 4u) * 8u);
}
return {std::move(result.spirv), std::move(result.program),
std::move(resources), std::move(packed_user_data)};
@@ -1835,7 +1828,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
"recompiler returned empty SPIR-V");
ValidateSpirv(test.name, result.spirv);
std::vector<u32> packed_user_data;
for (const auto reg : result.program.bindings.user_data_registers) {
for (const auto reg : result.program.info.user_data_registers) {
packed_user_data.push_back(
resources.user_data[reg - result.program.user_data_base]);
}
@@ -2142,12 +2135,12 @@ public:
ShaderRecompiler::IR::CompiledShaderInfo vertex_program{};
vertex_program.stage = ShaderType::Vertex;
vertex_program.bindings.push_data_start_dword = 2;
vertex_program.bindings.user_data_registers = {0, 1};
vertex_program.info.user_data_registers = {0, 1};
vertex_program.bindings.memory_offset_dword = 2;
ShaderRecompiler::IR::CompiledShaderInfo pixel_program{};
pixel_program.stage = ShaderType::Pixel;
pixel_program.bindings.push_data_start_dword = 0;
pixel_program.bindings.user_data_registers = {0, 1};
pixel_program.info.user_data_registers = {0, 1};
pixel_program.bindings.memory_offset_dword = 2;
ShaderRecompiler::IR::ResourceSnapshot vertex_snapshot;
vertex_snapshot.user_data = {0x11111111u, 0x22222222u};
@@ -11970,19 +11963,23 @@ public:
block_words + 0x10000 / sizeof(uint32_t) - 4),
"compressed atlas aliases reloaded stale CPU bytes over GPU-written blocks");
const auto allocate_bindings =
[&](ShaderRecompiler::IR::Program &program) {
program.shader_info_complete = true;
ShaderRecompiler::IR::AllocateBindings(program);
};
const auto set_image_bindings = [](ShaderRecompiler::IR::Program &program) {
for (auto &image : program.info.images) {
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(image);
const auto index = static_cast<size_t>(kind);
image.descriptor_index = program.bindings.descriptor_counts[index];
program.bindings.descriptor_counts[index] += image.mip_count;
program.bindings.descriptor_mask |= uint64_t{1} << index;
}
};
const auto make_buffer_program =
[](ShaderType stage, ShaderRecompiler::IR::BufferResource resource) {
ShaderRecompiler::IR::CompiledShaderInfo program{};
program.stage = stage;
resource.descriptor_index = 0;
program.info.buffers.push_back(resource);
program.bindings.descriptors.push_back(
{ShaderRecompiler::IR::DescriptorBindingKind::Buffers, {0}});
program.bindings.memory_offset_count = 1;
program.bindings.descriptor_counts[0] = 1;
program.bindings.descriptor_mask = 1;
program.bindings.push_data_start_dword = 0;
return program;
};
@@ -12283,7 +12280,7 @@ public:
}
ShaderRecompiler::IR::ApplyResourceSpecialization(
null_program, null_specialization);
allocate_bindings(null_program);
set_image_bindings(null_program);
ShaderRecompiler::IR::CompiledShaderInfo null_info{};
null_info.stage = null_program.stage;
null_info.info = std::move(null_program.info);
@@ -12378,7 +12375,7 @@ public:
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
storage_resource.written = true;
storage_program.info.images.push_back(storage_resource);
allocate_bindings(storage_program);
set_image_bindings(storage_program);
ShaderRecompiler::IR::DescriptorValue storage_descriptor{};
std::copy(std::begin(storage.fields), std::end(storage.fields),
storage_descriptor.dwords.begin());
@@ -12914,7 +12911,7 @@ public:
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
sampled_resource.read = true;
sampled_program.info.images.push_back(sampled_resource);
allocate_bindings(sampled_program);
set_image_bindings(sampled_program);
ShaderRecompiler::IR::ResourceSnapshot sampled_snapshot{};
sampled_snapshot.images.push_back(storage_descriptor);
ShaderRecompiler::IR::CompiledShaderInfo sampled_info{};
@@ -12996,7 +12993,7 @@ public:
vertex_sampled_program.stage = ShaderType::Vertex;
vertex_sampled_program.resource_tracking_complete = true;
vertex_sampled_program.info.images.push_back(sampled_resource);
allocate_bindings(vertex_sampled_program);
set_image_bindings(vertex_sampled_program);
ShaderRecompiler::IR::CompiledShaderInfo vertex_sampled_info{};
vertex_sampled_info.stage = vertex_sampled_program.stage;
vertex_sampled_info.info = std::move(vertex_sampled_program.info);
@@ -13008,7 +13005,7 @@ public:
pixel_storage_program.stage = ShaderType::Pixel;
pixel_storage_program.resource_tracking_complete = true;
pixel_storage_program.info.images.push_back(storage_resource);
allocate_bindings(pixel_storage_program);
set_image_bindings(pixel_storage_program);
ShaderRecompiler::IR::CompiledShaderInfo pixel_storage_info{};
pixel_storage_info.stage = pixel_storage_program.stage;
pixel_storage_info.info = std::move(pixel_storage_program.info);
@@ -13084,7 +13081,7 @@ public:
split_ir.stage = ShaderType::Vertex;
split_ir.resource_tracking_complete = true;
split_ir.info.images = {storage_resource, sampled_resource};
allocate_bindings(split_ir);
set_image_bindings(split_ir);
ShaderRecompiler::IR::CompiledShaderInfo split_program{};
split_program.stage = split_ir.stage;
split_program.info = std::move(split_ir.info);
@@ -13177,7 +13174,7 @@ public:
depth_views_ir.stage = ShaderType::Pixel;
depth_views_ir.resource_tracking_complete = true;
depth_views_ir.info.images = {sampled_resource, sampled_resource};
allocate_bindings(depth_views_ir);
set_image_bindings(depth_views_ir);
ShaderRecompiler::IR::CompiledShaderInfo depth_views_program{};
depth_views_program.stage = depth_views_ir.stage;
depth_views_program.info = std::move(depth_views_ir.info);
@@ -15261,29 +15258,20 @@ public:
std::copy_n(test.compute_info.dispatch_threads_num, 3,
shader_data.begin() + layout.dispatch_thread_dword);
}
auto Binding = [&](Kind kind) {
return ShaderRecompiler::IR::FindBinding(layout, kind);
auto Count = [&](Kind kind) {
return layout.descriptor_counts[static_cast<size_t>(kind)];
};
auto Native = [](Kind kind) {
return ShaderRecompiler::IR::NativeBinding(ShaderType::Compute, kind);
};
for (const auto &binding : layout.descriptors) {
const auto resource_class =
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) {
continue;
}
const auto &image =
compiled.program.info.images.at(binding.resources.front());
for (const auto &image : compiled.program.info.images) {
bool supported =
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
if (image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
supported = supported ||
image.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
image.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim1DArray;
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray;
}
Require(test.name, "dispatch", supported,
"unsupported image dimension needs a matching Vulkan test view");
@@ -15311,11 +15299,10 @@ public:
item.stageFlags = vk::ShaderStageFlagBits::eCompute;
layout_bindings.push_back(item);
};
for (const auto &binding : layout.descriptors) {
add_layout_binding(ShaderRecompiler::IR::NativeBinding(
ShaderType::Compute, binding.kind),
NativeDescriptorType(binding.kind),
NativeDescriptorCount(binding));
for (auto mask = layout.descriptor_mask; mask != 0; mask &= mask - 1) {
const auto index = std::countr_zero(mask);
const auto kind = static_cast<Kind>(index);
add_layout_binding(Native(kind), NativeDescriptorType(kind), layout.descriptor_counts[index]);
}
vk::DescriptorSetLayoutCreateInfo layout_info{};
@@ -15376,9 +15363,9 @@ public:
}
pool_sizes.push_back({type, count});
};
for (const auto &binding : layout.descriptors) {
add_pool_size(NativeDescriptorType(binding.kind),
NativeDescriptorCount(binding));
for (auto mask = layout.descriptor_mask; mask != 0; mask &= mask - 1) {
const auto index = std::countr_zero(mask);
add_pool_size(NativeDescriptorType(static_cast<Kind>(index)), layout.descriptor_counts[index]);
}
vk::DescriptorPoolCreateInfo pool_info{};
pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
@@ -15403,11 +15390,8 @@ public:
std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> sampled_infos;
std::array<std::vector<vk::DescriptorImageInfo>, ShaderRecompiler::IR::ImageBindingCount> image_infos;
std::vector<vk::ImageView> sampled_mip_views;
std::vector<vk::DescriptorImageInfo> storage_infos;
std::vector<vk::DescriptorImageInfo> storage_uint_infos;
std::vector<vk::DescriptorImageInfo> storage_atomic_infos;
std::vector<vk::DescriptorImageInfo> sampler_infos;
Buffer flattened_buffer;
Buffer user_data_buffer;
@@ -15419,7 +15403,7 @@ public:
vk::DescriptorBufferInfo bda_pagetable_info{};
vk::DescriptorBufferInfo fault_buffer_info{};
if (Binding(Kind::SharedMemory) != nullptr) {
if (Count(Kind::SharedMemory) != 0) {
const auto bytes = uint64_t{test.compute_info.lds_size_dwords} * sizeof(u32) *
test.dispatch_x * test.dispatch_y * test.dispatch_z;
shared_buffer = CreateHostBuffer(test.name, bytes, vk::BufferUsageFlagBits::eStorageBuffer, {});
@@ -15433,9 +15417,9 @@ public:
writes.push_back(write);
}
const bool uses_bda = Binding(Kind::BdaPagetable) != nullptr;
const bool uses_bda = Count(Kind::BdaPagetable) != 0;
Require(test.name, "dispatch",
uses_bda == (Binding(Kind::FaultBuffer) != nullptr),
uses_bda == (Count(Kind::FaultBuffer) != 0),
"BDA page table and fault buffer must be bound together");
if (uses_bda) {
EnsureBdaBuffers(test.name);
@@ -15458,16 +15442,16 @@ public:
}
}
const auto *buffers = Binding(Kind::Buffers);
if (buffers != nullptr) {
buffer_infos.resize(buffers->resources.size());
for (u32 i = 0; i < buffer_infos.size(); i++) {
auto &info = buffer_infos[i];
if (Count(Kind::Buffers) != 0) {
buffer_infos.resize(Count(Kind::Buffers));
for (u32 resource = 0; resource < compiled.program.info.buffers.size(); ++resource) {
const auto index = compiled.program.info.buffers[resource].descriptor_index;
if (index == UINT32_MAX) continue;
auto &info = buffer_infos[index];
info.buffer = buffer.buffer;
info.offset = 0;
info.range = buffer.size;
if (test.storage_buffer_range_bytes != 0) {
const auto resource = buffers->resources[i];
const auto offset = resource < test.storage_buffer_offsets.size()
? test.storage_buffer_offsets[resource]
: 0u;
@@ -15486,8 +15470,7 @@ public:
write.pBufferInfo = buffer_infos.data();
writes.push_back(write);
}
if (const auto *flattened = Binding(Kind::FlattenedSrt);
flattened != nullptr) {
if (Count(Kind::FlattenedSrt) != 0) {
flattened_buffer =
CreateStorageBuffer(test.name, compiled.resources.flattened_srt,
compiled.resources.flattened_srt.size());
@@ -15501,7 +15484,7 @@ public:
write.pBufferInfo = &flattened_info;
writes.push_back(write);
}
if (const auto *user = Binding(Kind::ShaderData); user != nullptr) {
if (Count(Kind::ShaderData) != 0) {
user_data_buffer =
CreateStorageBuffer(test.name, shader_data, shader_data.size());
user_data_info = {user_data_buffer.buffer, 0, user_data_buffer.size};
@@ -15514,7 +15497,7 @@ public:
write.pBufferInfo = &user_data_info;
writes.push_back(write);
}
if (const auto *gds = Binding(Kind::Gds); gds != nullptr) {
if (Count(Kind::Gds) != 0) {
Require(test.name, "dispatch", gds_buffer != nullptr,
"GDS descriptor requested but no GDS buffer was provided");
gds_info = {gds_buffer->buffer, 0, gds_buffer->size};
@@ -15527,51 +15510,27 @@ public:
write.pBufferInfo = &gds_info;
writes.push_back(write);
}
std::vector<const ShaderRecompiler::IR::DescriptorBinding *> sampled_bindings;
const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr;
const ShaderRecompiler::IR::DescriptorBinding *storage_uint = nullptr;
const ShaderRecompiler::IR::DescriptorBinding *storage_atomic = nullptr;
for (const auto &binding : layout.descriptors) {
const auto resource_class =
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) {
continue;
}
const auto &image =
compiled.program.info.images.at(binding.resources.front());
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
sampled_bindings.push_back(&binding);
} else if (image.atomic) {
storage_atomic = &binding;
} else if (image.numeric_class == Prospero::TextureNumericClass::Float) {
storage = &binding;
} else {
storage_uint = &binding;
}
}
size_t sampled_count = 0;
for (const auto *binding : sampled_bindings) sampled_count += binding->resources.size();
sampled_infos.reserve(sampled_count);
Require(test.name, "dispatch", sampled_resources.empty() ||
sampled_resources.size() == compiled.program.info.images.size(),
"sampled resources must match the logical images");
for (const auto *sampled : sampled_bindings) {
Require(test.name, "dispatch", sampled_image != nullptr || !sampled_resources.empty(),
"sampled image descriptor requested but no sampled image was "
"provided");
const auto first_info = sampled_infos.size();
sampled_infos.resize(first_info + sampled->resources.size());
std::vector<u32> mip_indices(compiled.program.info.images.size());
for (u32 slot = 0; slot < sampled->resources.size(); slot++) {
auto &info = sampled_infos[first_info + slot];
const auto resource = sampled->resources[slot];
const auto *source_image = sampled_resources.empty() ? sampled_image
: &sampled_resources[resource];
for (u32 resource = 0; resource < compiled.program.info.images.size(); ++resource) {
const auto &image = compiled.program.info.images[resource];
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(image);
auto &infos = image_infos[ShaderRecompiler::IR::ImageBindingIndex(kind)];
infos.resize(Count(kind));
const bool sampled = image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
const auto *source_image = sampled
? (sampled_resources.empty() ? sampled_image : &sampled_resources[resource])
: (image.numeric_class == Prospero::TextureNumericClass::Float
? storage_image : storage_image_uint);
Require(test.name, "dispatch", source_image != nullptr,
"image descriptor requested but no matching image was provided");
for (u32 mip_index = 0; mip_index < image.mip_count; ++mip_index) {
auto &info = infos[image.descriptor_index + mip_index];
info.imageView = source_image->view;
info.imageLayout = source_image->layout;
if (compiled.program.info.images[resource].mip_mode ==
ShaderRecompiler::IR::ImageMipMode::Dynamic) {
const auto mip = test.sampled_image_view_base_mip + mip_indices[resource]++;
if (sampled && image.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic) {
const auto mip = test.sampled_image_view_base_mip + mip_index;
Require(test.name, "dispatch", mip < source_image->mip_levels,
"sampled mip descriptor exceeds the supplied image");
vk::ImageViewCreateInfo view{};
@@ -15590,52 +15549,27 @@ public:
sampled_mip_views.push_back(info.imageView);
}
}
}
for (u32 group = 0; group < image_infos.size(); ++group) {
const auto &infos = image_infos[group];
if (infos.empty()) continue;
const auto kind = static_cast<Kind>(ShaderRecompiler::IR::FirstImageBinding + group);
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = Native(sampled->kind);
write.descriptorCount = static_cast<u32>(sampled->resources.size());
write.descriptorType = vk::DescriptorType::eSampledImage;
write.pImageInfo = sampled_infos.data() + first_info;
write.dstBinding = Native(kind);
write.descriptorCount = static_cast<u32>(infos.size());
write.descriptorType = NativeDescriptorType(kind);
write.pImageInfo = infos.data();
writes.push_back(write);
}
const auto BindStorage =
[&](const ShaderRecompiler::IR::DescriptorBinding *binding,
const Image *image, std::vector<vk::DescriptorImageInfo> *infos) {
if (binding == nullptr) {
return;
}
Require(
test.name, "dispatch", image != nullptr,
"storage image descriptor requested but no matching image was "
"provided");
infos->resize(binding->resources.size());
for (auto &info : *infos) {
info.imageView = image->view;
info.imageLayout = image->layout;
}
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = Native(binding->kind);
write.descriptorCount = static_cast<u32>(infos->size());
write.descriptorType = vk::DescriptorType::eStorageImage;
write.pImageInfo = infos->data();
writes.push_back(write);
};
BindStorage(storage, storage_image, &storage_infos);
BindStorage(storage_uint, storage_image_uint, &storage_uint_infos);
BindStorage(storage_atomic, storage_image_uint, &storage_atomic_infos);
const auto *samplers = Binding(Kind::Samplers);
if (samplers != nullptr) {
if (Count(Kind::Samplers) != 0) {
Require(test.name, "dispatch", sampler != nullptr,
"sampler descriptor requested but no sampler was provided");
sampler_infos.resize(samplers->resources.size());
sampler_infos.resize(Count(Kind::Samplers));
for (auto &info : sampler_infos) {
info.sampler = sampler;
}
vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = descriptor_set;
write.dstBinding = Native(Kind::Samplers);
write.descriptorCount = static_cast<u32>(sampler_infos.size());
@@ -15711,7 +15645,7 @@ public:
}
cmd.dispatch(test.dispatch_x, test.dispatch_y, test.dispatch_z);
if (buffers != nullptr) {
if (Count(Kind::Buffers) != 0) {
vk::BufferMemoryBarrier barrier{};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask =
@@ -18886,8 +18820,7 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
}
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
auto Has = [&](Kind kind) {
return ShaderRecompiler::IR::FindBinding(compiled.program.bindings, kind) !=
nullptr;
return compiled.program.bindings.descriptor_counts[static_cast<size_t>(kind)] != 0;
};
const auto dwords = std::max<size_t>(
{test.initial.size(), test.expected.size(), static_cast<size_t>(1)});
@@ -18900,9 +18833,8 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
vk::Sampler sampler = nullptr;
bool needs_sampled_image = false;
bool needs_storage_image = false;
for (const auto &binding : compiled.program.bindings.descriptors) {
const auto resource_class =
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
for (const auto &image : compiled.program.info.images) {
const auto resource_class = image.resource_class;
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
needs_sampled_image = true;
} else if (resource_class ==
@@ -34642,16 +34574,16 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
program.info.buffers[0].indirect_search_iterations = 0;
program.info.buffers[0].indirect_resources.clear();
}
program.binding_layout_complete = false;
AllocateBindings(program);
const auto *buffers = FindBinding(program.bindings, DescriptorBindingKind::Buffers);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
Require(test.name, "candidate bindings",
buffers != nullptr && buffers->resources ==
(null_buffers == 2u ? std::vector<u32>{0u} : std::vector<u32>{0u, 1u}),
program.bindings.descriptor_counts[0] == (null_buffers == 2u ? 1u : 2u) &&
program.info.buffers[0].descriptor_index == 0u &&
(null_buffers == 2u || program.info.buffers[1].descriptor_index == 1u),
"indirect store lost a buffer binding");
compiled.packed_user_data.resize(program.bindings.ShaderDataDwords());
compiled.packed_user_data[program.bindings.memory_offset_dword] = 12u | (108u << 8u);
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv(test.name, compiled.spirv);
if (null_buffers == 2u) {
test.required_spirv.clear();
@@ -34760,8 +34692,9 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
program.info.samplers.push_back({1u, 0x10f0u});
program.info.sampled_pairs.push_back({0u, 0u, 0x10f0u});
AllocateBindings(program);
ShaderComputeInputInfo compute{};
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
{.compute = &compute});
ValidateSpirv(name, spirv);
@@ -34789,8 +34722,8 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
"the sample operand lacks its nonuniform decoration");
program.info.images[0].indirect_search_iterations = 0;
program.binding_layout_complete = false;
AllocateBindings(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv("DenseIndirectImageTable", spirv);
Require(name, "dense image map disassembly", tools.Disassemble(spirv, &text),
@@ -34810,8 +34743,8 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
program.info.images.push_back(candidate);
}
program.info.images[0] = root;
program.binding_layout_complete = false;
AllocateBindings(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv(name, spirv);
Require(name, "large image array disassembly", tools.Disassemble(spirv, &text),
@@ -34824,8 +34757,8 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
program.info.images[root.indirect_resources[103]].dimension =
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
program.info.images[root.indirect_resources[103]].cube = true;
program.binding_layout_complete = false;
AllocateBindings(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv(name, spirv);
Require(name, "mixed contiguous image runs", tools.Disassemble(spirv, &text) &&
@@ -34850,8 +34783,8 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
? ShaderRecompiler::Decoder::ImageDimension::Dim2D
: ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
}
program.binding_layout_complete = false;
AllocateBindings(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
ValidateSpirv(name, spirv);
Require(name, "mixed SPIR-V disassembly", tools.Disassemble(spirv, &text),
@@ -34914,14 +34847,13 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
{Value(&output), Value(0u), Value(&byte_offset), Value(0u), Value(&sample_x), Value(true)});
store.SetFlags(MemoryFlags{1u, 0u});
program.info.buffers.push_back({.packed_stride = 1, .written = true});
program.binding_layout_complete = false;
AllocateBindings(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute});
TestCase test;
test.name = "IndirectImageContiguousRuns";
CompiledShader compiled;
compiled.program = std::move(program);
compiled.packed_user_data.resize(compiled.program.bindings.ShaderDataDwords());
constexpr std::array ordinals{0u, 1u, 2u, 3u, 4u, 5u, 2u, 0u};
compiled.resources.flattened_srt.push_back(ordinals.size());
compiled.resources.flattened_srt.insert(compiled.resources.flattened_srt.end(),
@@ -34944,6 +34876,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
compute.threads_num[1] = compute.threads_num[2] = 1u;
compiled.program.wave_size = wave_size;
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &compute});
compiled.packed_user_data.resize(compiled.program.bindings.ShaderDataDwords());
ValidateSpirv(test.name, compiled.spirv);
const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u;
Require(test.name, "native array run samples", tools.Disassemble(compiled.spirv, &text) &&
@@ -39491,8 +39424,8 @@ void CheckResourcePlanHandoff() {
ValidateSpirv(name, compiled.spirv);
const auto &bindings = compiled.program.bindings;
Require(name, "GPU data requirements",
bindings.user_data_registers.empty() && bindings.memory_offset_count == 1 &&
(FindBinding(bindings, DescriptorBindingKind::FlattenedSrt) != nullptr) ==
compiled.program.info.user_data_registers.empty() && bindings.descriptor_counts[0] == 1 &&
(bindings.descriptor_counts[static_cast<size_t>(DescriptorBindingKind::FlattenedSrt)] != 0) ==
numeric_read,
"descriptor-only shader retained SRT uploads or live scalar data was removed");
+121 -124
View File
@@ -1294,10 +1294,14 @@ void TestNormalizedImageContracts() {
"Vulkan image-view compatibility classes diverged from production");
}
void TestSpirvRequirementsAnalysis() {
void TestShaderFeatureCollection() {
using namespace ShaderRecompiler::IR;
ShaderPixelInputInfo pixel_info{};
ShaderComputeInputInfo compute_info{};
ShaderVertexInputInfo vertex_info{};
Program program;
program.resource_tracking_complete = true;
program.stage = ShaderType::Pixel;
program.block_storage.push_back(std::make_unique<Block>());
auto *block = program.block_storage.back().get();
@@ -1318,8 +1322,8 @@ void TestSpirvRequirementsAnalysis() {
block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)});
export_value.SetFlags(ExportFlags{.index = 0});
const auto requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
CollectShaderInfo(program, {.pixel = &pixel_info});
const auto& requirements = program.info;
Check(requirements.subgroup_ballot && requirements.subgroup_shuffle &&
requirements.subgroup_local_invocation_id &&
requirements.compute_derivatives &&
@@ -1328,8 +1332,9 @@ void TestSpirvRequirementsAnalysis() {
"consolidated SPIR-V requirements missed an IR dependency");
program.stage = ShaderType::Compute;
const auto compute_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.compute = &compute_info});
const auto& compute_requirements = program.info;
Check(!compute_requirements.function_lds &&
!compute_requirements.pixel_valid_mask,
"stage-specific SPIR-V requirements leaked into compute");
@@ -1339,19 +1344,22 @@ void TestSpirvRequirementsAnalysis() {
[&] {
program.stage = ShaderType::Pixel;
shared.SetFlags(MemoryFlags{.index = 1});
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.pixel = &pixel_info});
},
"invalid shared-memory requirement metadata did not terminate analysis");
ExpectFatal(
[&] {
program.stage = ShaderType::Pixel;
export_value.SetFlags(ExportFlags{.index = 1});
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(program);
program.shader_info_complete = false;
CollectShaderInfo(program, {.pixel = &pixel_info});
},
"invalid export requirement metadata did not terminate analysis");
#endif
Program add_tid;
add_tid.resource_tracking_complete = true;
add_tid.stage = ShaderType::Compute;
add_tid.info.buffers.resize(1);
add_tid.info.buffers[0].packed_stride = 1u << 20u;
@@ -1367,8 +1375,8 @@ void TestSpirvRequirementsAnalysis() {
ValueOpcode::LoadBufferU32,
{Value(&buffer), Value(0u), Value(0u), Value(0u), Value(true)});
load.SetFlags(MemoryFlags{.index = 0});
const auto add_tid_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
CollectShaderInfo(add_tid, {.compute = &compute_info});
const auto& add_tid_requirements = add_tid.info;
Check(add_tid_requirements.subgroup_local_invocation_id &&
!add_tid_requirements.subgroup_ballot &&
!add_tid_requirements.subgroup_shuffle,
@@ -1378,20 +1386,24 @@ void TestSpirvRequirementsAnalysis() {
ExpectFatal(
[&] {
add_tid.memory_info[0].resource = 1;
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
add_tid.shader_info_complete = false;
CollectShaderInfo(add_tid, {.compute = &compute_info});
},
"invalid buffer requirement metadata did not terminate analysis");
ExpectFatal(
[&] {
add_tid.stage = ShaderType::Vertex;
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(add_tid);
add_tid.shader_info_complete = false;
CollectShaderInfo(add_tid, {.vertex = &vertex_info});
},
"graphics buffer ADD_TID did not terminate requirements analysis");
#endif
Program empty;
const auto empty_requirements =
ShaderRecompiler::Spirv::Emitter::AnalyzeProgramRequirements(empty);
empty.stage = ShaderType::Compute;
empty.resource_tracking_complete = true;
CollectShaderInfo(empty, {.compute = &compute_info});
const auto& empty_requirements = empty.info;
Check(!empty_requirements.subgroup_ballot &&
!empty_requirements.subgroup_shuffle &&
!empty_requirements.subgroup_local_invocation_id &&
@@ -5209,25 +5221,13 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
raw_options.user_data = raw_user_data;
auto raw = RecompileForTest(raw_shader, raw_options);
const auto *address_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable);
const auto *fault_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer);
const auto& raw_counts = raw.program.bindings.descriptor_counts;
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
Check(raw.program.info.uses_dma && raw.program.info.buffers.empty() &&
address_binding != nullptr && fault_binding != nullptr &&
address_binding->resources.empty() &&
fault_binding->resources.empty() &&
ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Buffers) ==
nullptr &&
ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
nullptr &&
raw.program.bindings.memory_offset_count == 0u,
raw_counts[static_cast<size_t>(BindingKind::BdaPagetable)] == 1 &&
raw_counts[static_cast<size_t>(BindingKind::FaultBuffer)] == 1 &&
raw_counts[static_cast<size_t>(BindingKind::Buffers)] == 0 &&
raw_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] == 0,
"raw scalar load did not use only the DMA domain");
Check(count_live_memory_ops(
raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32,
@@ -5255,23 +5255,14 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
buffer_options.user_data = buffer_user_data;
auto buffer = RecompileForTest(buffer_shader, buffer_options);
const auto *buffer_binding = ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
const auto& buffer_counts = buffer.program.bindings.descriptor_counts;
Check(buffer.program.info.buffers.size() == 1u &&
buffer.program.info.buffers[0].scalar &&
buffer.program.info.buffers[0].descriptor_index == 0 &&
!buffer.program.info.uses_dma &&
buffer_binding != nullptr &&
buffer_binding->resources == std::vector<uint32_t>{0u} &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) ==
nullptr &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
nullptr &&
buffer.program.bindings.memory_offset_count == 1u,
buffer_counts[static_cast<size_t>(BindingKind::Buffers)] == 1 &&
buffer_counts[static_cast<size_t>(BindingKind::BdaPagetable)] == 0 &&
buffer_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] == 0,
"descriptor scalar load did not use only the buffer domain");
Check(count_live_memory_ops(
buffer.program, ShaderRecompiler::IR::ValueOpcode::ReadConstBuffer,
@@ -6066,8 +6057,7 @@ void TestNewShaderRecompilerImageLoad2DMsaa() {
const auto binding_kind = ShaderRecompiler::IR::DescriptorBindingForImage(
result.program.info.images[0]);
Check(binding_kind.has_value() &&
ShaderRecompiler::IR::FindBinding(result.program.bindings,
*binding_kind) != nullptr,
result.program.bindings.descriptor_counts[static_cast<size_t>(*binding_kind)] != 0,
"2D-MSAA image did not receive a multisampled descriptor binding");
Check(SpirvContainsTypeImage(result.spirv, 1, 0, 1, 1),
"SPIR-V binary does not contain a multisampled 2D image type");
@@ -7232,14 +7222,8 @@ void TestNewShaderRecompilerUnbasedFlatUsesBda() {
auto result = RecompileForTest(shader, options);
Check(result.program.info.uses_dma &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) !=
nullptr &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer) !=
nullptr,
result.program.bindings.descriptor_counts[static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable)] != 0 &&
result.program.bindings.descriptor_counts[static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer)] != 0,
"unbased FLAT did not compile through BDA");
CheckSpirvBinaryValidates(result.spirv);
}
@@ -7978,6 +7962,8 @@ void TestNewShaderRecompilerStructuredU64Phi() {
use.Emit(IR::ValueOpcode::CompositeExtractU64,
{IR::Value(&phi), IR::Value(1u)});
program.shader_info_complete = false;
ShaderRecompiler::IR::CollectShaderInfo(program, options.input_info);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
const auto before = MeasureSpirv(result.spirv);
@@ -9841,6 +9827,8 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
IR::IREmitter use(prologue_program.blocks[1]);
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
prologue_program.shader_info_complete = false;
IR::CollectShaderInfo(prologue_program, options.input_info);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(prologue_program,
options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -9866,6 +9854,8 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
const auto sum = use.Emit(IR::ValueOpcode::IAdd32, {high, IR::Value(7u)});
use.Emit(IR::ValueOpcode::IAdd32, {sum, middle});
program.shader_info_complete = false;
IR::CollectShaderInfo(program, options.input_info);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
options.input_info);
CheckSpirvBinaryValidates(spirv);
@@ -9902,6 +9892,8 @@ void TestNewShaderRecompilerPlanningOnlyLoads() {
read.Instruction()->SetFlags(IR::MemoryFlags{.index = 1});
ir.Emit(IR::ValueOpcode::ReferenceU32, {load});
ir.Emit(IR::ValueOpcode::ReferenceU32, {read});
program.shader_info_complete = false;
IR::CollectShaderInfo(program, options.input_info);
const auto spirv = Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
Check(spirv == result.spirv,
@@ -10035,6 +10027,8 @@ void TestNewShaderRecompilerNativeU64Translation() {
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {base, IR::Value(count)});
}
program.shader_info_complete = false;
ShaderRecompiler::IR::CollectShaderInfo(program, options.input_info);
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
const auto source = DisassembleSpirvBinary(spirv);
@@ -10072,6 +10066,8 @@ void TestNewShaderRecompilerNativeU64Translation() {
use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)});
spirv.clear();
program.shader_info_complete = false;
ShaderRecompiler::IR::CollectShaderInfo(program, options.input_info);
spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
const auto before = MeasureSpirv(result.spirv);
@@ -10432,13 +10428,13 @@ void TestMeshExportStorage() {
CheckSpirvBinaryValidates(result.spirv);
const auto &layout = result.program.bindings;
Check(layout.UsesPushData() == (push_data_start == PushData::MeshDrawDwordCount) &&
layout.memory_offset_count == 1,
layout.descriptor_counts[static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::Buffers)] == 1,
"mesh shader did not retain its user-data placement and buffer offset");
const auto reg = std::ranges::find(layout.user_data_registers, 13u);
Check(reg != layout.user_data_registers.end(), "mesh shader lost user s13");
const auto reg = std::ranges::find(result.program.info.user_data_registers, 13u);
Check(reg != result.program.info.user_data_registers.end(), "mesh shader lost user s13");
const auto source = DisassembleSpirvBinary(result.spirv);
for (const auto dword :
{static_cast<uint32_t>(reg - layout.user_data_registers.begin()),
{static_cast<uint32_t>(reg - result.program.info.user_data_registers.begin()),
layout.memory_offset_dword}) {
const auto operand =
std::string(layout.UsesPushData() ? "vsharp" : "shader_data") +
@@ -13317,72 +13313,55 @@ void TestNewShaderRecompilerNativeBindingPlan() {
options.user_data = user_data;
auto result = RecompileForTest(shader, options);
const auto *buffers = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::Buffers);
const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr;
const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr;
const ShaderRecompiler::IR::DescriptorBinding *atomic_storage = nullptr;
const auto& counts = result.program.bindings.descriptor_counts;
std::optional<BindingKind> sampled;
std::optional<BindingKind> storage;
std::optional<BindingKind> atomic_storage;
for (const auto &image : result.program.info.images) {
const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(image);
if (!kind.has_value()) {
continue;
}
const auto *binding =
ShaderRecompiler::IR::FindBinding(result.program.bindings, *kind);
if (image.resource_class ==
ShaderRecompiler::IR::ImageResourceClass::Sampled) {
sampled = binding;
Check(kind.has_value() && image.descriptor_index == 0,
"native image did not receive its descriptor slot");
if (image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
sampled = kind;
} else if (image.atomic) {
atomic_storage = binding;
atomic_storage = kind;
} else {
storage = binding;
storage = kind;
}
}
const auto *samplers = ShaderRecompiler::IR::FindBinding(
result.program.bindings, BindingKind::Samplers);
Check(buffers != nullptr && buffers->resources.size() == 2,
"native binding plan did not allocate scalar/vector buffers");
Check(sampled != nullptr && sampled->resources.size() == 1,
"native binding plan did not allocate the sampled image");
Check(storage != nullptr && storage->resources.size() == 1,
"native binding plan did not allocate the float storage image");
Check(atomic_storage != nullptr && atomic_storage->resources.size() == 1,
"native binding plan did not allocate the atomic storage image");
Check(samplers != nullptr && samplers->resources.size() == 1,
"native binding plan did not allocate the sampler");
Check(counts[static_cast<size_t>(BindingKind::Buffers)] == 2 &&
result.program.info.buffers[0].descriptor_index == 0 &&
result.program.info.buffers[1].descriptor_index == 1,
"native emission did not allocate scalar/vector buffer slots");
Check(sampled && counts[static_cast<size_t>(*sampled)] == 1,
"native emission did not allocate the sampled image");
Check(storage && counts[static_cast<size_t>(*storage)] == 1,
"native emission did not allocate the float storage image");
Check(atomic_storage && counts[static_cast<size_t>(*atomic_storage)] == 1,
"native emission did not allocate the atomic storage image");
Check(counts[static_cast<size_t>(BindingKind::Samplers)] == 1,
"native emission did not allocate the sampler");
Check(SpirvContainsOpcode(result.spirv, 86),
"SPIR-V binary does not combine separate image/sampler descriptors");
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, buffers->kind)),
"SPIR-V lacks storage-buffer Binding decoration");
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, sampled->kind)),
"SPIR-V lacks sampled-image Binding decoration");
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(
result.program.stage, samplers->kind)),
"SPIR-V lacks sampler Binding decoration");
for (const auto kind : {BindingKind::Buffers, *sampled, BindingKind::Samplers}) {
Check(SpirvHasDecorationValue(result.spirv, 33u,
ShaderRecompiler::IR::NativeBinding(result.program.stage, kind)),
"SPIR-V lacks a resource Binding decoration");
}
Check(SpirvDecorationValueCount(result.spirv, 34u, 0u) ==
result.program.bindings.descriptors.size(),
static_cast<size_t>(std::popcount(result.program.bindings.descriptor_mask)),
"SPIR-V resources do not all use descriptor set zero");
CheckSpirvBinaryValidates(result.spirv);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
ExpectFatal(
[&] {
auto malformed = RecompileForTest(shader, options);
auto malformed_buffers = std::ranges::find_if(
malformed.program.bindings.descriptors, [](const auto &binding) {
return binding.kind == BindingKind::Buffers;
});
malformed_buffers->resources.clear();
(void)ShaderRecompiler::Spirv::EmitProgram(malformed.program,
options.input_info);
},
"malformed native binding topology did not terminate SPIR-V emission");
const auto bindings = result.program.bindings;
result.program.bindings = {};
result.program.info.buffers[0].descriptor_index = UINT32_MAX;
Check(ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info) == result.spirv &&
result.program.bindings == bindings &&
result.program.info.buffers[0].descriptor_index == 0,
"repeated emission did not rebuild descriptor slots deterministically");
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
ShaderRecompiler::IR::Inst *buffer_handle = nullptr;
for (auto *block : result.program.blocks) {
const auto found = std::ranges::find_if(*block, [](const auto &inst) {
@@ -14499,12 +14478,32 @@ void TestGraphicsPushConstantPlacement() {
RecompileForTest(shader, options, nullptr, nullptr, PushDataStart);
Check(placed.program.bindings.UsesPushData() &&
placed.program.bindings.push_data_start_dword == PushDataStart &&
ShaderRecompiler::IR::FindBinding(placed.program.bindings,
BindingKind::ShaderData) == nullptr,
placed.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 0,
"pixel shader did not use its assigned shared push-data position");
Check(SpirvHasMemberDecorationValue(placed.spirv, OffsetDecoration, 0),
"SPIR-V push-data block did not use the canonical zero base");
CheckSpirvBinaryValidates(placed.spirv);
auto exact = RecompileForTest(shader, options, nullptr, nullptr, 28);
Check(exact.program.bindings.UsesPushData() &&
exact.program.bindings.push_data_start_dword == 28 &&
exact.program.bindings.ShaderDataDwords() == 4,
"exact-fit shader data did not use the remaining push space");
uint32_t cursor = 28;
exact.program.bindings.AdvancePushData(cursor);
Check(cursor == 32, "exact-fit shader data advanced the cursor incorrectly");
CheckSpirvBinaryValidates(exact.spirv);
auto spill = RecompileForTest(shader, options, nullptr, nullptr, 29);
auto full = RecompileForTest(shader, options, nullptr, nullptr, 32);
Check(!spill.program.bindings.UsesPushData() &&
spill.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 1 &&
spill.program.bindings == full.program.bindings && spill.spirv == full.spirv,
"insufficient push space did not produce canonical storage-backed data");
cursor = 29;
spill.program.bindings.AdvancePushData(cursor);
Check(cursor == 29, "spilled shader data consumed push space");
CheckSpirvBinaryValidates(spill.spirv);
}
void TestNewShaderRecompilerUnsupportedMemoryDecode() {
@@ -14689,7 +14688,7 @@ void TestCompilerStageInputOwnership() {
#endif
}
void TestSpirvEmissionOwnsRequirements() {
void TestSpirvRepeatedEmission() {
using namespace ShaderRecompiler;
const uint32_t shader[] = {EncodeSopp(0x01)};
ShaderComputeInputInfo compute{};
@@ -14705,10 +14704,12 @@ void TestSpirvEmissionOwnsRequirements() {
const auto first = emitter.Emit(IR::ValueOpcode::ReadFirstLane,
{value, IR::Value(true)});
emitter.Emit(IR::ValueOpcode::IAdd32, {first, lane});
compiled.program.shader_info_complete = false;
IR::CollectShaderInfo(compiled.program, options.input_info);
const auto binary = Spirv::EmitProgram(compiled.program, options.input_info);
CheckSpirvBinaryValidates(binary);
Check((DisassembleSpirvBinary(binary).find("BuiltIn SubgroupLocalInvocationId") != std::string::npos),
"emission reused requirements from an earlier IR version");
"collection missed the subgroup requirement of the added IR");
Check(Spirv::EmitProgram(compiled.program, options.input_info) == binary,
"repeated emission reused IDs from the previous module");
@@ -14781,9 +14782,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
const auto dead_gds_result =
compile("dead-gds", dead_gds,
{.words = 64, .instructions = 20, .labels = 4, .branches = 3});
Check(ShaderRecompiler::IR::FindBinding(
dead_gds_result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Gds) == nullptr,
Check(dead_gds_result.program.bindings.descriptor_counts[static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::Gds)] == 0,
"dead GDS load retained a descriptor binding");
const uint32_t structured_phi[] = {
@@ -14924,9 +14923,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
wide_gds_metrics.workgroup_variables == 0u &&
wide_gds_metrics.array_lengths == 1u,
"native wide GDS fixture did not share one entry-dominating length");
Check(ShaderRecompiler::IR::FindBinding(
wide_gds_result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr,
Check(wide_gds_result.program.bindings.descriptor_counts[static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::Gds)] != 0,
"live native wide GDS access did not allocate its descriptor binding");
const auto count_shared = [](const auto &result,
ShaderRecompiler::IR::ValueOpcode opcode) {
@@ -15020,11 +15017,11 @@ int main() {
TestShaderBufferResourceSize();
TestNativeShaderResourceDependencies();
TestNormalizedImageContracts();
TestSpirvRequirementsAnalysis();
TestShaderFeatureCollection();
TestTypedSpirvSerialization();
TestDeferredSpirvPhiPatching();
TestCompilerStageInputOwnership();
TestSpirvEmissionOwnsRequirements();
TestSpirvRepeatedEmission();
TestRepeatedExportsHaveOneInterface();
TestNewShaderRecompilerSpirvSizeBaselines();
TestDemandDrivenSpirvDeclarations();