[dawn][native] Reflect dynamic arrays from shader and validate against layout
Bug: 435317394
Change-Id: I5de3b6772f6623a572bbd877576960625674442b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/258697
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 4573220..13ad3f8 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -420,6 +420,18 @@
DAWN_UNREACHABLE();
}
+ResultOrError<wgpu::DynamicBindingKind> FromArrayResourceType(
+ tint::inspector::RuntimeBindingArrayInfo::ResourceType type) {
+ switch (type) {
+ case tint::inspector::RuntimeBindingArrayInfo::ResourceType::kSampledTexture:
+ return wgpu::DynamicBindingKind::SampledTexture;
+ case tint::inspector::RuntimeBindingArrayInfo::ResourceType::kNone:
+ return DAWN_VALIDATION_ERROR(
+ "Attempted to convert 'None' array resource type from Tint.");
+ }
+ DAWN_UNREACHABLE();
+}
+
// Validation errors, if any, are stored within outputParseResult instead of get returned as
// ErrorData.
MaybeError ParseWGSL(std::unique_ptr<tint::Source::File> file,
@@ -716,6 +728,30 @@
});
}
+MaybeError ValidateCompatibilityOfDynamicBindingArrayWithLayout(
+ DeviceBase* device,
+ const BindGroupLayoutInternalBase* layout,
+ const GroupDynamicBindingArrayInfo& shaderDynamicArray) {
+ DAWN_INVALID_IF(!layout->HasDynamicArray(), "%s doesn't contain a dynamic binding array.",
+ layout);
+
+ DAWN_INVALID_IF(layout->GetAPIDynamicArrayStart() != shaderDynamicArray.start,
+ "@binding for the dynamic array in the shader (%u) doesn't match the start "
+ "(%u) defined in %s.",
+ shaderDynamicArray.start, layout->GetAPIDynamicArrayStart(), layout);
+
+ // If the dynamic binding array is never accessed with any type in the shader, it is valid to
+ // use with any DynamicArrayKind.
+ if (shaderDynamicArray.kind != wgpu::DynamicBindingKind::Undefined) {
+ DAWN_INVALID_IF(shaderDynamicArray.kind != layout->GetDynamicArrayKind(),
+ "Shader dynamic binding array is used with types (of kind %s) incompatible "
+ "with %s's kind of dynamic binding array (%s).",
+ shaderDynamicArray.kind, layout, layout->GetDynamicArrayKind());
+ }
+
+ return {};
+}
+
MaybeError ValidateCompatibilityWithBindGroupLayout(DeviceBase* device,
BindGroupIndex group,
const EntryPointMetadata& entryPoint,
@@ -730,6 +766,15 @@
group, bindingId, layout);
}
+ // Check that the dynamic binding array, if any in the shader, matches the BindGroupLayout.
+ if (entryPoint.dynamicBindingArrays.contains(group)) {
+ DAWN_TRY_CONTEXT(
+ ValidateCompatibilityOfDynamicBindingArrayWithLayout(
+ device, layout, entryPoint.dynamicBindingArrays.at(group)),
+ "validating that the entry-point's dynamic binding array for @group(%u) matches %s",
+ group, layout);
+ }
+
return {};
}
@@ -1175,6 +1220,51 @@
resource.binding, resource.bind_group);
}
+ // Dynamic binding array reflection
+ for (const tint::inspector::RuntimeBindingArrayInfo& array :
+ inspector->GetRuntimeBindingArrayInfo(entryPoint.name)) {
+ BindGroupIndex group(array.group);
+ if (DelayedInvalidIf(group >= kMaxBindGroupsTyped,
+ "The entry-point uses a binding with a group decoration (%u) "
+ "that exceeds maxBindGroups (%u) - 1.",
+ group, kMaxBindGroups)) {
+ continue;
+ }
+
+ BindingNumber binding(array.binding);
+ if (DelayedInvalidIf(
+ binding >= kMaxBindingsPerBindGroupTyped,
+ "Binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u) - 1.",
+ uint32_t(binding), kMaxBindingsPerBindGroup)) {
+ continue;
+ }
+
+ // Check that all the uses of the dynamic binding array have compatible DynamicArrayKind.
+ wgpu::DynamicBindingKind kind = wgpu::DynamicBindingKind::Undefined;
+ for (const auto& type : array.type_info) {
+ wgpu::DynamicBindingKind kindForType;
+ DAWN_TRY_ASSIGN(kindForType, FromArrayResourceType(type.type));
+
+ // This is the first kind that we compute, just store it.
+ if (kind == wgpu::DynamicBindingKind::Undefined) {
+ kind = kindForType;
+ continue;
+ }
+
+ DAWN_INVALID_IF(kindForType != kind,
+ "Dynamic binding array for @group(%u) used with two incompatible kinds "
+ "of types %s vs. %s",
+ group, kind, kindForType);
+ }
+
+ DAWN_INVALID_IF(metadata->dynamicBindingArrays.contains(group),
+ "Duplicate dynamic binding array for group: %u.", group);
+ metadata->dynamicBindingArrays[group] = {{
+ .start = binding,
+ .kind = kind,
+ }};
+ }
+
// Sampler binding point placeholder for non-sampler texture usage. Make it
// ToTint(EntryPointMetadata::nonSamplerBindingPoint), so that we have
// FromTint(tintNonSamplerBindingPoint) == EntryPointMetadata::nonSamplerBindingPoint, and we
@@ -1511,7 +1601,11 @@
for (BindGroupIndex group : ~layout->GetBindGroupLayoutsMask()) {
DAWN_INVALID_IF(entryPoint.bindings[group].size() > 0,
"The entry-point uses bindings in group %u but %s doesn't have a "
- "BindGroupLayout for this index",
+ "BindGroupLayout for this index.",
+ group, layout);
+ DAWN_INVALID_IF(entryPoint.dynamicBindingArrays.contains(group),
+ "The entry-point uses a dynamic binding array in group %u but %s doesn't "
+ "have a BindGroupLayout for this index.",
group, layout);
}
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index 53ee98b..a70f61a 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -202,6 +202,15 @@
using BindingGroupInfoMap = absl::flat_hash_map<BindingNumber, ShaderBindingInfo>;
using BindingInfoArray = ityp::array<BindGroupIndex, BindingGroupInfoMap, kMaxBindGroups>;
+// Shader metadata that's the equivalent for the dynamic binding arrays in the BGLs.
+#define GROUP_DYNAMIC_BINDING_ARRAY_INFO_MEMBERS(X) \
+ X(BindingNumber, start) \
+ X(wgpu::DynamicBindingKind, kind)
+DAWN_SERIALIZABLE(struct, GroupDynamicBindingArrayInfo, GROUP_DYNAMIC_BINDING_ARRAY_INFO_MEMBERS){};
+#undef GROUP_DYNAMIC_BINDING_ARRAY_INFO_MEMBERS
+
+using DynamicBindingArrayInfo = absl::flat_hash_map<BindGroupIndex, GroupDynamicBindingArrayInfo>;
+
// Define types for the shader reflection data structures in detail namespaces to prevent messing
// up dawn::native namespace. These types can be exposed within EntryPointMetadata if needed.
namespace detail {
@@ -273,6 +282,9 @@
X(std::vector<std::string>, infringedLimitErrors) \
/* bindings[G][B] is the reflection data for the binding defined with @group(G) @binding(B)*/ \
X(BindingInfoArray, bindings) \
+ /* dynamicBindingArray[G] is the reflection data for the dynamic binding array of @group(G)*/ \
+ /* if one is present in the shader module */ \
+ X(DynamicBindingArrayInfo, dynamicBindingArrays) \
/* Contains the reflection information of all sampler and non-sampler texture (storage */ \
/* texture not included) usage in the entry point. For non-sampler usage, */ \
/* nonSamplerBindingPoint is used for sampler slot. */ \
diff --git a/src/dawn/tests/unittests/validation/DynamicBindingArrayValidationTests.cpp b/src/dawn/tests/unittests/validation/DynamicBindingArrayValidationTests.cpp
index fb8c439..0f84540 100644
--- a/src/dawn/tests/unittests/validation/DynamicBindingArrayValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/DynamicBindingArrayValidationTests.cpp
@@ -618,5 +618,256 @@
}
}
+// Test that a shader using a dynamic binding array requires a layout with one.
+TEST_F(DynamicBindingArrayTests, ShaderRequiresLayoutWithDynamicArray) {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+
+ // Success case, the layout has a dynamic binding array.
+ wgpu::BindGroupLayout bglDynamic =
+ MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bglDynamic);
+ device.CreateComputePipeline(&csDesc);
+
+ // Error case, the layout doesn't have a dynamic binding array.
+ wgpu::BindGroupLayout bglStatic = utils::MakeBindGroupLayout(device, {});
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bglStatic);
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+
+ // Error case, the layout doesn't have a dynamic binding array (even if there is a similar
+ // looking binding).
+ wgpu::BindGroupLayout bglStaticWithTexture = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}});
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bglStaticWithTexture);
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+}
+
+// Test that it is valid to have a layout specifying a dynamic binding array with a shader that
+// doesn't have one.
+TEST_F(DynamicBindingArrayTests, ShaderNoDynamicArrayWithLayoutThatHasOne) {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ @compute @workgroup_size(1) fn main() {
+ }
+ )");
+ wgpu::BindGroupLayout bglDynamic =
+ MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bglDynamic);
+ device.CreateComputePipeline(&csDesc);
+}
+
+// Test that the dynamic array start must match between shader and layout.
+TEST_F(DynamicBindingArrayTests, ShaderAndLayoutDynamicArrayStartMatches) {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding(1) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+
+ // Success case, start of the array matches.
+ wgpu::BindGroupLayout bgl1 = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, 1);
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl1);
+ device.CreateComputePipeline(&csDesc);
+
+ // Error case, layout start is before the shader's start.
+ wgpu::BindGroupLayout bgl0 = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, 0);
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl0);
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+
+ // Error case, layout start is after the shader's start.
+ wgpu::BindGroupLayout bgl2 = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, 2);
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl2);
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+}
+
+// Test that the @binding decoration of the dynamic array must be less than maxBindingsPerBindGroup.
+TEST_F(DynamicBindingArrayTests, ShaderArrayStartLessThanMaxBindingsPerBindGroup) {
+ wgpu::BindGroupLayout bgl =
+ MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, kMaxBindingsPerBindGroup - 1);
+
+ // Control case, we are just below the limit.
+ {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module =
+ utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding()" + std::to_string(kMaxBindingsPerBindGroup - 1) +
+ R"() var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl);
+ device.CreateComputePipeline(&csDesc);
+ }
+
+ // Error case, we are above the limit.
+ {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module =
+ utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding()" + std::to_string(kMaxBindingsPerBindGroup) +
+ R"() var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl);
+ // Two errors happen because we cannot create a layout that matches, but check that the
+ // shader's validation about maxBindingsPerBindGroup is the one that's reported.
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc),
+ testing::HasSubstr("maxBindingsPerBindGroup"));
+ }
+}
+
+// Test that the @group decoration of the dynamic array must be less than maxBindGroups.
+TEST_F(DynamicBindingArrayTests, ShaderArrayAtMaxBindGroups) {
+ std::array<wgpu::BindGroupLayout, kMaxBindGroups> bgls;
+ bgls[bgls.size() - 1] = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
+
+ wgpu::PipelineLayoutDescriptor plDesc;
+ plDesc.bindGroupLayoutCount = bgls.size();
+ plDesc.bindGroupLayouts = bgls.data();
+ wgpu::PipelineLayout pl = device.CreatePipelineLayout(&plDesc);
+
+ // Control case, we are just below the limit.
+ {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module =
+ utils::CreateShaderModule(device,
+ R"(
+ enable chromium_experimental_dynamic_binding;
+ @group()" + std::to_string(kMaxBindGroups - 1) +
+ R"() @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ csDesc.layout = pl;
+ device.CreateComputePipeline(&csDesc);
+ }
+
+ // Error case, we are above the limit.
+ {
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module =
+ utils::CreateShaderModule(device,
+ R"(
+ enable chromium_experimental_dynamic_binding;
+ @group()" + std::to_string(kMaxBindGroups) +
+ R"() @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ csDesc.layout = pl;
+ // Two errors happen because we cannot create a layout that matches, but check that the
+ // shader's validation about maxBindingsPerBindGroup is the one that's reported.
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc),
+ testing::HasSubstr("maxBindGroups"));
+ }
+}
+
+// Test that the group for the dynamic binding array must be in the PipelineLayout.
+TEST_F(DynamicBindingArrayTests, ShaderBindingArrayMustHaveGroupInPipelineLayout) {
+ std::array<wgpu::BindGroupLayout, 3> bgls = {
+ nullptr, MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture), nullptr};
+
+ wgpu::PipelineLayoutDescriptor plDesc;
+ plDesc.bindGroupLayoutCount = bgls.size();
+ plDesc.bindGroupLayouts = bgls.data();
+
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.layout = device.CreatePipelineLayout(&plDesc);
+
+ // Control case, the group is in the pipeline layout.
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(1) @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ device.CreateComputePipeline(&csDesc);
+
+ // Error case, the group is not in the layout (@group(0) case)
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+
+ // Error case, the group is not in the layout (@group(2) case)
+ csDesc.compute.module = utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(2) @binding(0) var a : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ }
+ )");
+ ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
+}
+
+// Test that a shader cannot have two dynamic binding arrays on the same group.
+TEST_F(DynamicBindingArrayTests, ShaderTwoDynamicArraysSameGroupIsAnError) {
+ // Control case, the two dynamic binding arrays are on different groups.
+ utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding(0) var a : binding_array<texture_2d<f32>>;
+ @group(1) @binding(1) var b : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ _ = b[42];
+ }
+ )");
+
+ // Error case, the two dynamic binding arrays are on the same group.
+ ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
+ enable chromium_experimental_dynamic_binding;
+ @group(0) @binding(0) var a : binding_array<texture_2d<f32>>;
+ @group(0) @binding(1) var b : binding_array<texture_2d<f32>>;
+
+ @compute @workgroup_size(1) fn main() {
+ _ = a[42];
+ _ = b[42];
+ }
+ )"));
+}
+
+// TODO(https://crbug.com/435317394): Add tests for the DynamicArrayKind. It is not possible to do
+// it at the moment because we cannot reflect DynamicArrayKind::Undefined (would require referencing
+// but not indexing the array) or any value that's not DynamicArrayKind::SampledTexture (no support
+// in Dawn or Tint for other cases). Tests to add after that are:
+// - The kind in the layout must matche the deduced kind for the shader.
+// - Case with a runtime but typed binding_array.
+// - Case with an untyped binding_array.
+// - A shader only referencing but not indexing an untyped binding_array is valid to use with any
+// DynamicArrayKind in the layout.
+// - An error is produced at shader module compilation time if it uses the same binding_array with
+// different DynamicArrayKinds.
+
} // anonymous namespace
} // namespace dawn