[dawn][native] Add BGLEntryArraySize and validation in BGL
This adds the new chained struct that will be used to specify the
arraySize of BGL. It is gated both behind UnsafeAPI and a new killswitch
toggle. Validation is added for this structure in the BGL as well as
tests.
Bug: 393558555
Change-Id: I2c73d31f297d75889738e18fdff15ec448901860
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/236414
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Brandon Jones <bajones@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 6aedbf9..5ce3cc2 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -495,6 +495,15 @@
]
},
+ "bind group layout entry array size": {
+ "category": "structure",
+ "chained": "in",
+ "chain roots": ["bind group layout entry"],
+ "members": [
+ {"name": "array size", "type": "uint32_t", "default": 0}
+ ]
+ },
+
"bind group layout entry": {
"category": "structure",
"extensible": "in",
@@ -3773,6 +3782,7 @@
{"value": 10, "name": "surface color management", "tags": ["upstream", "emscripten"]},
{"value": 11, "name": "request adapter WebXR options", "tags": ["upstream", "emscripten"]},
{"value": 12, "name": "adapter properties subgroups"},
+ {"value": 13, "name": "bind group layout entry array size"},
{"value": 0, "name": "texture binding view dimension descriptor", "tags": ["compat"]},
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index abcb102..04960e4 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -37,6 +37,7 @@
#include "dawn/common/Enumerator.h"
#include "dawn/common/MatchVariant.h"
+#include "dawn/common/Range.h"
#include "dawn/native/ChainUtils.h"
#include "dawn/native/Device.h"
#include "dawn/native/Error.h"
@@ -94,6 +95,11 @@
bool allowInternalBinding) {
DAWN_TRY(ValidateShaderStage(entry->visibility));
+ uint32_t arraySize = 1;
+ if (auto* arraySizeInfo = entry.Get<BindGroupLayoutEntryArraySize>()) {
+ arraySize = arraySizeInfo->arraySize;
+ }
+
int bindingMemberCount = 0;
if (entry->buffer.type != wgpu::BufferBindingType::BindingNotUsed) {
@@ -117,11 +123,21 @@
"(note that read-only storage buffer bindings are allowed).",
entry->visibility, wgpu::ShaderStage::Vertex);
}
+
+ // TODO(393558555): Support arraySize != 1 for non-dynamic buffers.
+ DAWN_INVALID_IF(arraySize != 1,
+ "arraySize (%u) != 1 for a buffer binding is not implemented yet.",
+ arraySize);
}
if (entry->sampler.type != wgpu::SamplerBindingType::BindingNotUsed) {
bindingMemberCount++;
DAWN_TRY(ValidateSamplerBindingType(entry->sampler.type));
+
+ // TODO(393558555): Support arraySize != 1 for samplers.
+ DAWN_INVALID_IF(arraySize != 1,
+ "arraySize (%u) != 1 for a sampler binding is not implemented yet.",
+ arraySize);
}
if (entry->texture.sampleType != wgpu::TextureSampleType::BindingNotUsed) {
@@ -193,6 +209,11 @@
default:
DAWN_UNREACHABLE();
}
+
+ // TODO(393558555): Support arraySize != 1 for storage textures.
+ DAWN_INVALID_IF(arraySize != 1,
+ "arraySize (%u) != 1 for a storage texture binding is not implemented yet.",
+ arraySize);
}
if (auto* staticSamplerBindingLayout = entry.Get<StaticSamplerBindingLayout>()) {
@@ -203,6 +224,10 @@
wgpu::FeatureName::StaticSamplers);
DAWN_TRY(device->ValidateObject(staticSamplerBindingLayout->sampler));
+ DAWN_INVALID_IF(
+ arraySize != 1,
+ "BindGroupLayoutEntry arraySize (%u) is greater than 1 for a static sampler entry.",
+ arraySize);
if (staticSamplerBindingLayout->sampledTextureBinding == WGPU_LIMIT_U32_UNDEFINED) {
DAWN_INVALID_IF(staticSamplerBindingLayout->sampler->IsYCbCr(),
@@ -212,6 +237,10 @@
if (entry.Get<ExternalTextureBindingLayout>()) {
bindingMemberCount++;
+ DAWN_INVALID_IF(
+ arraySize != 1,
+ "BindGroupLayoutEntry arraySize (%u) is greater than 1 for an external texture entry.",
+ arraySize);
}
DAWN_INVALID_IF(bindingMemberCount == 0,
@@ -222,6 +251,13 @@
"BindGroupLayoutEntry had more than one of buffer, sampler, texture, "
"storageTexture, or externalTexture set");
+ if (auto* arraySizeInfo = entry.Get<BindGroupLayoutEntryArraySize>()) {
+ DAWN_INVALID_IF(arraySizeInfo->arraySize != 1 &&
+ entry->texture.sampleType == wgpu::TextureSampleType::BindingNotUsed,
+ "Entry that is not a sampled texture has an arraySize (%u) that is not 1.",
+ arraySizeInfo->arraySize);
+ }
+
return {};
}
@@ -378,21 +414,48 @@
for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
UnpackedPtr<BindGroupLayoutEntry> entry;
DAWN_TRY_ASSIGN(entry, ValidateAndUnpack(&descriptor->entries[i]));
- BindingNumber bindingNumber = BindingNumber(entry->binding);
- DAWN_INVALID_IF(bindingNumber >= kMaxBindingsPerBindGroupTyped,
- "Binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
- uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
- DAWN_INVALID_IF(bindingMap.count(bindingNumber) != 0,
- "On entries[%u]: binding index (%u) was specified by a previous entry.", i,
- entry->binding);
+ BindingNumber bindingNumber = BindingNumber(entry->binding);
+ DAWN_INVALID_IF(
+ bindingNumber >= kMaxBindingsPerBindGroupTyped,
+ "On entries[%u]: binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
+ i, uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
+
+ BindingNumber arraySize{1};
+ if (auto* arraySizeInfo = entry.Get<BindGroupLayoutEntryArraySize>()) {
+ arraySize = BindingNumber(arraySizeInfo->arraySize);
+
+ DAWN_INVALID_IF(
+ device->IsToggleEnabled(Toggle::DisableBindGroupLayoutEntryArraySize),
+ "On entries[%u]: chaining of BindGroupLayoutEntryArraySize is disabled.", i);
+ DAWN_INVALID_IF(!device->IsToggleEnabled(Toggle::AllowUnsafeAPIs),
+ "On entries[%u]: chaining of BindGroupLayoutEntryArraySize is unsafe "
+ "while it is being implemented.",
+ i);
+
+ DAWN_INVALID_IF(arraySize == BindingNumber(0), "On entries[%u]: arraySize is 0.", i);
+ DAWN_INVALID_IF(arraySize > kMaxBindingsPerBindGroupTyped - bindingNumber,
+ "On entries[%u]: binding (%u) + arraySize (%u) is %u which is larger "
+ "than maxBindingsPerBindGroup (%u).",
+ i, arraySize, bindingNumber,
+ uint32_t(arraySize) + uint32_t(bindingNumber),
+ kMaxBindingsPerBindGroupTyped);
+ }
+
+ // Check that the same binding is not set twice. bindingNumber + arraySize cannot overflow
+ // as they are both smaller than kMaxBindingsPerBindGroupTyped.
+ static_assert(kMaxBindingsPerBindGroup < std::numeric_limits<uint32_t>::max() / 2);
+ for (BindingNumber usedBinding : Range(bindingNumber, bindingNumber + arraySize)) {
+ DAWN_INVALID_IF(bindingMap.count(usedBinding) != 0,
+ "On entries[%u]: binding index (%u) was specified by a previous entry.",
+ i, entry->binding);
+ bindingMap.insert({usedBinding, i});
+ }
DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding),
"validating entries[%u]", i);
IncrementBindingCounts(&bindingCounts, entry);
-
- bindingMap.insert({bindingNumber, i});
}
// Perform a second validation pass for static samplers. This is done after initial validation
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index ca1c442..3f04c74 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -54,22 +54,27 @@
void IncrementBindingCounts(BindingCounts* bindingCounts,
const UnpackedPtr<BindGroupLayoutEntry>& entry) {
- bindingCounts->totalCount += 1;
+ uint32_t arraySize = 1;
+ if (const auto* arraySizeInfo = entry.Get<BindGroupLayoutEntryArraySize>()) {
+ arraySize = arraySizeInfo->arraySize;
+ }
+
+ bindingCounts->totalCount += arraySize;
uint32_t PerStageBindingCounts::*perStageBindingCountMember = nullptr;
if (entry->buffer.type != wgpu::BufferBindingType::BindingNotUsed) {
- ++bindingCounts->bufferCount;
+ bindingCounts->bufferCount += arraySize;
const BufferBindingLayout& buffer = entry->buffer;
if (buffer.minBindingSize == 0) {
- ++bindingCounts->unverifiedBufferCount;
+ bindingCounts->unverifiedBufferCount += arraySize;
}
switch (buffer.type) {
case wgpu::BufferBindingType::Uniform:
if (buffer.hasDynamicOffset) {
- ++bindingCounts->dynamicUniformBufferCount;
+ bindingCounts->dynamicUniformBufferCount += arraySize;
}
perStageBindingCountMember = &PerStageBindingCounts::uniformBufferCount;
break;
@@ -79,7 +84,7 @@
case kInternalReadOnlyStorageBufferBinding:
case wgpu::BufferBindingType::ReadOnlyStorage:
if (buffer.hasDynamicOffset) {
- ++bindingCounts->dynamicStorageBufferCount;
+ bindingCounts->dynamicStorageBufferCount += arraySize;
}
perStageBindingCountMember = &PerStageBindingCounts::storageBufferCount;
break;
@@ -110,7 +115,7 @@
DAWN_ASSERT(perStageBindingCountMember != nullptr);
for (SingleShaderStage stage : IterateStages(entry->visibility)) {
- ++(bindingCounts->perStage[stage].*perStageBindingCountMember);
+ (bindingCounts->perStage[stage].*perStageBindingCountMember) += arraySize;
}
}
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index 715b2d5..d85eb16 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -153,6 +153,10 @@
{"disable_sample_variables",
"Disables gl_SampleMask and related functionality which is unsupported on some platforms.",
"https://crbug.com/dawn/673", ToggleStage::Device}},
+ {Toggle::DisableBindGroupLayoutEntryArraySize,
+ {"disable_bind_group_layout_entry_array_size",
+ "Disable uses of wgpu::BindGroupLayoutEntryArraySize.",
+ "https://issues.chromium.org/393558555", ToggleStage::Device}},
{Toggle::UseD3D12SmallShaderVisibleHeapForTesting,
{"use_d3d12_small_shader_visible_heap",
"Enable use of a small D3D12 shader visible heap, instead of using a large one by default. "
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index a7781b0..0d86dce 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -61,6 +61,7 @@
DisableBaseInstance,
DisableIndexedDrawBuffers,
DisableSampleVariables,
+ DisableBindGroupLayoutEntryArraySize,
UseD3D12SmallShaderVisibleHeapForTesting,
UseDXC,
DisableRobustness,
diff --git a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
index ed8340b..75a4e76 100644
--- a/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -25,6 +25,7 @@
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#include <limits>
#include <tuple>
#include <utility>
#include <vector>
@@ -1797,6 +1798,386 @@
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
+// Control case for a valid use of wgpu::BindGroupLayoutEntryArraySize
+TEST_F(BindGroupLayoutValidationTest, ArraySizeSuccess) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+ arraySize.arraySize = 1;
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+ device.CreateBindGroupLayout(&desc);
+}
+
+class BindGroupLayoutArraySizeDisabledValidationTest : public BindGroupValidationTest {
+ protected:
+ std::vector<const char*> GetEnabledToggles() override {
+ return {"disable_bind_group_layout_entry_array_size"};
+ }
+};
+
+// Check that using a wgpu::BindGroupLayoutEntryArraySize is disallowed by that toggle.
+TEST_F(BindGroupLayoutArraySizeDisabledValidationTest, ArraySizeDisabled) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+ arraySize.arraySize = 1;
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that using arraySize != 1 is only allowed for sampled textures.
+TEST_F(BindGroupLayoutValidationTest, ArraySizeAllowedBindingTypes) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize1;
+ arraySize1.arraySize = 1;
+
+ wgpu::BindGroupLayoutEntryArraySize arraySize2;
+ arraySize2.arraySize = 2;
+
+ // Sampled texture
+ {
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize 1
+ entry.nextInChain = &arraySize1;
+ device.CreateBindGroupLayout(&desc);
+ // Second success case, arraySize 2
+ entry.nextInChain = &arraySize2;
+ device.CreateBindGroupLayout(&desc);
+ }
+
+ // Uniform buffer
+ {
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.buffer.type = wgpu::BufferBindingType::Uniform;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize 1
+ entry.nextInChain = &arraySize1;
+ device.CreateBindGroupLayout(&desc);
+ // Error case, arraySize 2
+ entry.nextInChain = &arraySize2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+
+ // Storage buffer
+ {
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.buffer.type = wgpu::BufferBindingType::Uniform;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize 1
+ entry.nextInChain = &arraySize1;
+ device.CreateBindGroupLayout(&desc);
+ // Error case, arraySize 2
+ entry.nextInChain = &arraySize2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+
+ // Storage texture
+ {
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.storageTexture.format = wgpu::TextureFormat::R32Uint;
+ entry.storageTexture.access = wgpu::StorageTextureAccess::ReadOnly;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize 1
+ entry.nextInChain = &arraySize1;
+ device.CreateBindGroupLayout(&desc);
+ // Error case, arraySize 2
+ entry.nextInChain = &arraySize2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+
+ // Sampler
+ {
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.sampler.type = wgpu::SamplerBindingType::Filtering;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize 1
+ entry.nextInChain = &arraySize1;
+ device.CreateBindGroupLayout(&desc);
+ // Error case, arraySize 2
+ entry.nextInChain = &arraySize2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+}
+
+// Check that arraySize = 0 is not allowed.
+TEST_F(BindGroupLayoutValidationTest, ArraySizeZero) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize is 1
+ arraySize.arraySize = 1;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error case, arraySize is 0
+ arraySize.arraySize = 0;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that binding + arraySize must fit in maxBindingsPerBindGroup
+TEST_F(BindGroupLayoutValidationTest, ArrayEndPastMaxBindingsPerBindGroup) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = kMaxBindingsPerBindGroup - 1;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+
+ // Success case, arraySize is 1 so the last used binding is maxBindingsPerBindGroup
+ arraySize.arraySize = 1;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error case, arraySize is 2 so we go past maxBindingsPerBindGroup
+ arraySize.arraySize = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that arraySize + binding overflowing is caught by some validation.
+TEST_F(BindGroupLayoutValidationTest, ArraySizePlusBindingOverflow) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+ arraySize.arraySize = std::numeric_limits<uint32_t>::max();
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 3;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Check that arraySize is taken into account when looking for duplicate bindings.
+TEST_F(BindGroupLayoutValidationTest, ArraySizeDuplicateBindings) {
+ const uint32_t kPlaceholderBinding = 42; // will be replaced before each check.
+
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+ arraySize.arraySize = 3;
+
+ // Check single entry, then an array that overlaps it.
+ {
+ wgpu::BindGroupLayoutEntry entries[2] = {
+ {
+ .binding = kPlaceholderBinding,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ {
+ .nextInChain = &arraySize,
+ .binding = 1,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ };
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 2;
+ desc.entries = entries;
+
+ // Success cases, 0 and 4 are not in [1, 4)
+ entries[0].binding = 0;
+ device.CreateBindGroupLayout(&desc);
+ entries[0].binding = 4;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error cases, 1, 2, 3 are in [1, 4)
+ entries[0].binding = 1;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 3;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+
+ // Check an array, then a single entry that goes in it.
+ {
+ wgpu::BindGroupLayoutEntry entries[2] = {
+ {
+ .nextInChain = &arraySize,
+ .binding = 1,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ {
+ .binding = kPlaceholderBinding,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ };
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 2;
+ desc.entries = entries;
+
+ // Success cases, 0 and 4 are not in [1, 4)
+ entries[1].binding = 0;
+ device.CreateBindGroupLayout(&desc);
+ entries[1].binding = 4;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error cases, 1, 2, 3 are in [1, 4)
+ entries[1].binding = 1;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[1].binding = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[1].binding = 3;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+
+ // Check two arrays that overlap
+ {
+ wgpu::BindGroupLayoutEntry entries[2] = {
+ {
+ .nextInChain = &arraySize,
+ .binding = kPlaceholderBinding,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ {
+ .nextInChain = &arraySize,
+ .binding = 3,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ };
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 2;
+ desc.entries = entries;
+
+ // Success cases, 0 and 6 make the arrays not overlap
+ entries[0].binding = 0;
+ device.CreateBindGroupLayout(&desc);
+ entries[0].binding = 6;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error cases, 1, 2, 3, 4, 5 make the arrays overlap
+ entries[0].binding = 1;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 3;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 4;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ entries[0].binding = 5;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+ }
+}
+
+// Check that arraySize counts towards the binding limits.
+TEST_F(BindGroupLayoutValidationTest, ArraySizeCountsTowardsLimit) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+
+ wgpu::BindGroupLayoutEntry entries[2] = {
+ {
+ .nextInChain = &arraySize,
+ .binding = 1,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ {
+ .binding = 0,
+ .visibility = wgpu::ShaderStage::Fragment,
+ .texture = {.sampleType = wgpu::TextureSampleType::Float},
+ },
+ };
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 2;
+ desc.entries = entries;
+
+ wgpu::Limits limits = GetSupportedLimits();
+
+ // Success case: we are just at the limit with the arraySize.
+ arraySize.arraySize = limits.maxSampledTexturesPerShaderStage - 1;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error case: we are just above the limit with the arraySize.
+ arraySize.arraySize = limits.maxSampledTexturesPerShaderStage;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
+// Test that only arraySize = 1 is allowed for external textures
+TEST_F(BindGroupLayoutValidationTest, ExternalTextureWithArraySize) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+
+ wgpu::ExternalTextureBindingLayout externalTextureLayout = {};
+ externalTextureLayout.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutEntry binding = {};
+ binding.binding = 0;
+ binding.nextInChain = &externalTextureLayout;
+
+ wgpu::BindGroupLayoutDescriptor desc = {};
+ desc.entryCount = 1;
+ desc.entries = &binding;
+
+ // Success case, arraySize is 1.
+ arraySize.arraySize = 1;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error case, arraySize is not 1.
+ arraySize.arraySize = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
class BindGroupLayoutWithStaticSamplersValidationTest : public BindGroupLayoutValidationTest {
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
return {wgpu::FeatureName::StaticSamplers};
@@ -2006,6 +2387,31 @@
utils::MakeBindGroup(device, layout, {{0, device.CreateSampler(&samplerDesc)}}));
}
+// Test that only arraySize = 1 is allowed for static samplers
+TEST_F(BindGroupLayoutWithStaticSamplersValidationTest, StaticSamplerWithArraySize) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+
+ wgpu::StaticSamplerBindingLayout staticSamplerBinding = {};
+ staticSamplerBinding.sampler = device.CreateSampler();
+ staticSamplerBinding.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutEntry binding = {};
+ binding.binding = 0;
+ binding.nextInChain = &staticSamplerBinding;
+
+ wgpu::BindGroupLayoutDescriptor desc = {};
+ desc.entryCount = 1;
+ desc.entries = &binding;
+
+ // Success case, arraySize is 1.
+ arraySize.arraySize = 1;
+ device.CreateBindGroupLayout(&desc);
+
+ // Error case, arraySize is not 1.
+ arraySize.arraySize = 2;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
constexpr uint32_t kBindingSize = 8;
class SetBindGroupValidationTest : public ValidationTest {
diff --git a/src/dawn/tests/unittests/validation/UnsafeAPIValidationTests.cpp b/src/dawn/tests/unittests/validation/UnsafeAPIValidationTests.cpp
index de7ee23..b42ad63 100644
--- a/src/dawn/tests/unittests/validation/UnsafeAPIValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/UnsafeAPIValidationTests.cpp
@@ -64,6 +64,23 @@
)"));
}
+// Check that using a wgpu::BindGroupLayoutEntryArraySize is an unsafe API.
+TEST_F(UnsafeAPIValidationTest, BindGroupLayoutEntryArraySize) {
+ wgpu::BindGroupLayoutEntryArraySize arraySize;
+ arraySize.arraySize = 1;
+
+ wgpu::BindGroupLayoutEntry entry;
+ entry.binding = 0;
+ entry.visibility = wgpu::ShaderStage::Fragment;
+ entry.texture.sampleType = wgpu::TextureSampleType::Float;
+ entry.nextInChain = &arraySize;
+
+ wgpu::BindGroupLayoutDescriptor desc;
+ desc.entryCount = 1;
+ desc.entries = &entry;
+ ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
+}
+
class TimestampQueryUnsafeAPIValidationTest : public ValidationTest {
protected:
std::vector<const char*> GetDisabledToggles() override { return {"allow_unsafe_apis"}; }
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index 4ac723a..3d3969e 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -54,6 +54,7 @@
"java/android/dawn/BindGroupLayout.kt",
"java/android/dawn/BindGroupLayoutDescriptor.kt",
"java/android/dawn/BindGroupLayoutEntry.kt",
+ "java/android/dawn/BindGroupLayoutEntryArraySize.kt",
"java/android/dawn/BlendComponent.kt",
"java/android/dawn/BlendFactor.kt",
"java/android/dawn/BlendOperation.kt",