blob: 2c11e98c5707cc30d4ce0bd900ccadbcadb09c92 [file]
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// 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 <vector>
#include "dawn/common/Constants.h"
#include "dawn/tests/unittests/validation/ValidationTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
class DynamicBindingArrayTests : public ValidationTest {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
return {wgpu::FeatureName::ChromiumExperimentalBindless};
}
// Helper similar to utils::MakeBindGroupLayout but that adds a dynamic array.
wgpu::BindGroupLayout MakeBindGroupLayout(
wgpu::DynamicBindingKind kind,
uint32_t dynamicArrayStart = 0,
std::initializer_list<utils::BindingLayoutEntryInitializationHelper> entriesInitializer =
{}) {
std::vector<wgpu::BindGroupLayoutEntry> entries;
for (const utils::BindingLayoutEntryInitializationHelper& entry : entriesInitializer) {
entries.push_back(entry);
}
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
dynamic.dynamicArray.kind = kind;
dynamic.dynamicArray.start = dynamicArrayStart;
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.nextInChain = &dynamic;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
return device.CreateBindGroupLayout(&descriptor);
}
// Helper similar to utils::MakeBindGroup but that adds a dynamic array.
wgpu::BindGroup MakeBindGroup(
const wgpu::BindGroupLayout& layout,
uint32_t dynamicArraySize,
std::initializer_list<utils::BindingInitializationHelper> entriesInitializer) {
std::vector<wgpu::BindGroupEntry> entries;
for (const utils::BindingInitializationHelper& helper : entriesInitializer) {
entries.push_back(helper.GetAsBinding());
}
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = dynamicArraySize;
wgpu::BindGroupDescriptor descriptor;
descriptor.nextInChain = &dynamic;
descriptor.layout = layout;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
return device.CreateBindGroup(&descriptor);
}
};
class DynamicBindingArrayTests_FeatureDisabled : public ValidationTest {};
// Control case where creating a dynamic binding array layout with the feature enabled is valid.
TEST_F(DynamicBindingArrayTests, LayoutSuccessWithFeatureEnabled) {
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor desc;
desc.nextInChain = &dynamic;
// No error is produced.
device.CreateBindGroupLayout(&desc);
}
// Error case where creating a dynamic binding array layout with the feature disabled is an error.
TEST_F(DynamicBindingArrayTests_FeatureDisabled, LayoutErrorWithFeatureDisabled) {
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor desc;
desc.nextInChain = &dynamic;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
// Control case where creating a dynamic binding array bind group with the feature enabled is valid.
TEST_F(DynamicBindingArrayTests, GroupSuccessWithFeatureEnabled) {
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 1;
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
// No error is produced.
device.CreateBindGroup(&desc);
}
// Error case where creating a dynamic binding array bind group with the feature disabled is an
// error.
TEST_F(DynamicBindingArrayTests_FeatureDisabled, GroupErrorWithFeatureDisabled) {
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 1;
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = utils::MakeBindGroupLayout(device, {});
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that using DynamicArrayKind::Undefined is an error.
TEST_F(DynamicBindingArrayTests, UndefinedArrayKind) {
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
wgpu::BindGroupLayoutDescriptor desc;
desc.nextInChain = &dynamic;
// Control case: SampledTexture is a valid kind.
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
device.CreateBindGroupLayout(&desc);
// Error case: Undefined is invalid.
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::Undefined;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
// Check that the start of the binding array must be less than maxBindingsPerBindGroup
TEST_F(DynamicBindingArrayTests, DynamicArrayStartLimit) {
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor desc;
desc.nextInChain = &dynamic;
// No error is produced if we are under the limit.
dynamic.dynamicArray.start = kMaxBindingsPerBindGroup - 1;
device.CreateBindGroupLayout(&desc);
// Error case if we are at the limit.
dynamic.dynamicArray.start = kMaxBindingsPerBindGroup;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
// Error case if we are above the limit.
dynamic.dynamicArray.start = kMaxBindingsPerBindGroup + 1;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
// Check that conflicts of binding number are not allowed between dynamic and static bindings.
TEST_F(DynamicBindingArrayTests, ConflictWithStaticBindings) {
wgpu::BindGroupLayoutEntry entry;
entry.binding = 0;
entry.bindingArraySize = 0;
entry.texture.sampleType = wgpu::TextureSampleType::Float;
wgpu::BindGroupLayoutDynamicBindingArray dynamic;
dynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
dynamic.dynamicArray.start = 3;
wgpu::BindGroupLayoutDescriptor desc;
desc.nextInChain = &dynamic;
desc.entryCount = 1;
desc.entries = &entry;
// Control case: the non-arrayed static binding is before the dynamic array.
entry.binding = 2;
entry.bindingArraySize = 1;
device.CreateBindGroupLayout(&desc);
// Error case: the non-arrayed static binding is after the dynamic array.
entry.binding = 3;
entry.bindingArraySize = 1;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
// Control case: the arrayed static binding is before the dynamic array.
entry.binding = 0;
entry.bindingArraySize = 3;
device.CreateBindGroupLayout(&desc);
// Error case: the arrayed static binding is after the dynamic array.
entry.binding = 0;
entry.bindingArraySize = 4;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
// Check that the layout must have a dynamic array, even if the dynamicArraySize is 0.
TEST_F(DynamicBindingArrayTests, LayoutNoDynamicArray) {
wgpu::BindGroupDynamicBindingArray dynamic;
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
// Control case: dynamicArraySize = 1 is valid with a layout with a dynamic binding array.
dynamic.dynamicArraySize = 1;
desc.layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
device.CreateBindGroup(&desc);
// Control case: dynamicArraySize = 0 is valid with a layout with a dynamic binding array.
dynamic.dynamicArraySize = 0;
device.CreateBindGroup(&desc);
// Error case: dynamicArraySize > 0 requires the layout to have a dynamic binding array.
dynamic.dynamicArraySize = 1;
desc.layout = utils::MakeBindGroupLayout(device, {});
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
// Error case: dynamicArraySize = 0 requires the layout to have a dynamic binding array.
dynamic.dynamicArraySize = 1;
desc.layout = utils::MakeBindGroupLayout(device, {});
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that the dynamic array size must be below the limit.
TEST_F(DynamicBindingArrayTests, DynamicArraySizeLimit) {
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
wgpu::BindGroupDynamicBindingArray dynamic;
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
// Control case: reaching the limit is valid.
dynamic.dynamicArraySize = kMaxDynamicBindingArraySize;
device.CreateBindGroup(&desc);
// Error case: going above the limit isn't.
dynamic.dynamicArraySize = kMaxDynamicBindingArraySize + 1;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that a dynamic array counts as one additional storage buffer when checking against limits.
TEST_F(DynamicBindingArrayTests, UsesOneStorageBufferTowardsLimit) {
const uint32_t maxStorageBuffers = deviceLimits.maxStorageBuffersPerShaderStage;
std::vector<wgpu::BindGroupLayoutEntry> storageBufferEntries(maxStorageBuffers);
for (size_t i = 0; i < storageBufferEntries.size(); i++) {
storageBufferEntries[i].buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
storageBufferEntries[i].visibility =
wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute;
storageBufferEntries[i].binding = i;
}
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
layoutDynamic.dynamicArray.start = maxStorageBuffers + 3;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
layoutDesc.entries = storageBufferEntries.data();
// Success case: exactly maxStorageBuffers are used (1 for the dynamic array, max - 1 for the
// the static bindings).
layoutDesc.entryCount = maxStorageBuffers - 1;
device.CreateBindGroupLayout(&layoutDesc);
// Error case: the dynamic binding array storage buffer make the layout go over the limit.
layoutDesc.entryCount = maxStorageBuffers;
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&layoutDesc));
}
// Check that the dynamic array size must be below the limit.
TEST_F(DynamicBindingArrayTests, DynamicArrayRequiresASize) {
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 0;
wgpu::BindGroupDescriptor desc;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
// Control case: a size, even of 0 is valid.
desc.nextInChain = &dynamic;
device.CreateBindGroup(&desc);
// Error case: an unspecified size is invalid.
desc.nextInChain = nullptr;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that specifying an entry that's neither a known static one or a dynamic one is an error.
TEST_F(DynamicBindingArrayTests, EntryNeitherStaticNorDynamic) {
// Create a layout with a static entry at 0 and a dynamic binding array starting at 2.
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
layoutDynamic.dynamicArray.start = 2;
wgpu::BindGroupLayoutEntry layoutEntry;
layoutEntry.binding = 0;
layoutEntry.texture.sampleType = wgpu::TextureSampleType::Float;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
layoutDesc.entryCount = 1;
layoutDesc.entries = &layoutEntry;
// Create the texture to put in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.size = {1, 1};
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&tDesc);
// Create a bind group on that layout with one or two static entries for 0/1.
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 10;
wgpu::BindGroupEntry entries[2];
entries[0].binding = 0;
entries[0].textureView = texture.CreateView();
entries[1].binding = 1;
entries[1].textureView = texture.CreateView();
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
desc.entries = entries;
// Control case: specifying only entry 0 which is in the layout is valid.
desc.entryCount = 1;
device.CreateBindGroup(&desc);
// Error case: specifying entry 1 which is not in the layout is an error.
desc.entryCount = 2;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that even when the bind group has a dynamic binding array, forgetting a static entry is an
// error.
TEST_F(DynamicBindingArrayTests, MissingStaticEntry) {
// Create a layout with a static entry at 0 and a dynamic binding array starting at 1.
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
layoutDynamic.dynamicArray.start = 1;
wgpu::BindGroupLayoutEntry layoutEntry;
layoutEntry.binding = 0;
layoutEntry.texture.sampleType = wgpu::TextureSampleType::Float;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
layoutDesc.entryCount = 1;
layoutDesc.entries = &layoutEntry;
// Create the texture to put in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.size = {1, 1};
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&tDesc);
// Create a bind group on that layout with or without a static entry at binding 0.
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 10;
wgpu::BindGroupEntry entry;
entry.binding = 0;
entry.textureView = texture.CreateView();
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
desc.entries = &entry;
// Control case: static entry 0 is specified.
desc.entryCount = 1;
device.CreateBindGroup(&desc);
// Error case: static entry 0 is missing, which is an error.
desc.entryCount = 0;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that dynamic entries must be in bounds of the dynamic binding array.
TEST_F(DynamicBindingArrayTests, BindingPastDynamicArray) {
// Create a layout with a static entry at 0 and a dynamic binding array starting at 1.
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
layoutDynamic.dynamicArray.start = 2;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
// Create the texture to put in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.size = {1, 1};
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&tDesc);
// Create a bind group on that layout with an entry at binding 11 or 12.
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 10;
wgpu::BindGroupEntry entry;
entry.textureView = texture.CreateView();
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
desc.entries = &entry;
desc.entryCount = 1;
// Control case: entry is exactly at the end of the dynamic binding array.
entry.binding = 11;
device.CreateBindGroup(&desc);
// Error case: entry is past the end of the dynamic binding array, which is an error.
entry.binding = 12;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that dynamic entries must not conflict.
TEST_F(DynamicBindingArrayTests, DynamicEntryConflict) {
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
// Create the texture to put in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.size = {1, 1};
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&tDesc);
// Create a bind group on that layout with one or two static entries for 0/1.
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 10;
wgpu::BindGroupEntry entries[2];
entries[0].textureView = texture.CreateView();
entries[1].textureView = texture.CreateView();
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
desc.entryCount = 2;
desc.entries = entries;
// Control case: dynamic entries have different binding indices.
entries[0].binding = 0;
entries[1].binding = 1;
device.CreateBindGroup(&desc);
// Error case: dynamic entries have the same binding index, which is an error.
entries[0].binding = 0;
entries[1].binding = 0;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that dynamic binding arrays are not allowed in combination with an external texture in the
// static bindings part.
// TODO(https://issues.chromium.org/435251399): Remove this constraint that's only a workaround
// while prototyping.
TEST_F(DynamicBindingArrayTests, NotAllowedWithExternalTextures) {
// Control case, static buffer binding + dynamic binding array is allowed.
MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, 5,
{{
0,
wgpu::ShaderStage::Fragment,
wgpu::BufferBindingType::Uniform,
}});
// Error case, static buffer binding + dynamic binding array is an error.
ASSERT_DEVICE_ERROR(MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture, 5,
{{
0,
wgpu::ShaderStage::Fragment,
&utils::kExternalTextureBindingLayout,
}}));
}
// Check that DynamicBindingKind::SampledTexture must be a texture entry.
TEST_F(DynamicBindingArrayTests, SampledTextureKindRequiresTexture) {
wgpu::BindGroupLayoutDynamicBindingArray layoutDynamic;
layoutDynamic.dynamicArray.kind = wgpu::DynamicBindingKind::SampledTexture;
wgpu::BindGroupLayoutDescriptor layoutDesc;
layoutDesc.nextInChain = &layoutDynamic;
// Create the texture to put in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.size = {1, 1};
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&tDesc);
// Create the buffer to put in the bind group.
wgpu::BufferDescriptor bDesc;
bDesc.size = 4;
bDesc.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer buffer = device.CreateBuffer(&bDesc);
// Create a bind group on that layout with one or two static entries for 0/1.
wgpu::BindGroupDynamicBindingArray dynamic;
dynamic.dynamicArraySize = 10;
wgpu::BindGroupDescriptor desc;
desc.nextInChain = &dynamic;
desc.layout = device.CreateBindGroupLayout(&layoutDesc);
desc.entryCount = 1;
// Control case: entry is a texture, which is valid.
wgpu::BindGroupEntry textureEntry;
textureEntry.binding = 0;
textureEntry.textureView = texture.CreateView();
desc.entries = &textureEntry;
device.CreateBindGroup(&desc);
// Error case: entry is a buffer, which is an error.
wgpu::BindGroupEntry bufferEntry;
bufferEntry.binding = 0;
bufferEntry.buffer = buffer;
desc.entries = &bufferEntry;
ASSERT_DEVICE_ERROR(device.CreateBindGroup(&desc));
}
// Check that the view must have only the TextureBinding usage for DynamicKind::SampledTexture.
TEST_F(DynamicBindingArrayTests, SampledTextureKindRequiresTextureBindingOnlyView) {
wgpu::BindGroupLayout layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::TextureDescriptor tDesc{
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::StorageBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::R32Uint,
};
wgpu::Texture tex = device.CreateTexture(&tDesc);
// Control case: limiting the usage to TextureBinding is valid to add to SampledTexture.
{
wgpu::TextureViewDescriptor vDesc{
.usage = wgpu::TextureUsage::TextureBinding,
};
wgpu::TextureView view = tex.CreateView(&vDesc);
MakeBindGroup(layout, 1, {{0, view}});
}
// Error case: having unrelated usages in the view is not allowed. RenderAttachment case.
{
wgpu::TextureViewDescriptor vDesc{
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment,
};
wgpu::TextureView view = tex.CreateView(&vDesc);
ASSERT_DEVICE_ERROR(MakeBindGroup(layout, 1, {{0, view}}));
}
// Error case: having unrelated usages in the view is not allowed. StorageBinding case.
{
wgpu::TextureViewDescriptor vDesc{
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding,
};
wgpu::TextureView view = tex.CreateView(&vDesc);
ASSERT_DEVICE_ERROR(MakeBindGroup(layout, 1, {{0, view}}));
}
// Error case: the defaulted texture usages don't contain TextureBinding.
{
wgpu::TextureDescriptor tDesc2 = tDesc;
tDesc2.usage = wgpu::TextureUsage::CopyDst;
wgpu::TextureView view = device.CreateTexture(&tDesc2).CreateView();
ASSERT_DEVICE_ERROR(MakeBindGroup(layout, 1, {{0, view}}));
}
}
// Check that the view must have a single aspect for DynamicKind::SampledTexture.
TEST_F(DynamicBindingArrayTests, SampledTextureKindRequiresSingleAspect) {
wgpu::BindGroupLayout layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::TextureDescriptor tDesc{
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::Depth24PlusStencil8,
};
wgpu::Texture tex = device.CreateTexture(&tDesc);
// Success case, only the depth aspect is selected.
{
wgpu::TextureViewDescriptor vDesc{
.aspect = wgpu::TextureAspect::DepthOnly,
};
wgpu::TextureView view = tex.CreateView(&vDesc);
MakeBindGroup(layout, 1, {{0, view}});
}
// Success case, only the stencil aspect is selected.
{
wgpu::TextureViewDescriptor vDesc{
.aspect = wgpu::TextureAspect::StencilOnly,
};
wgpu::TextureView view = tex.CreateView(&vDesc);
MakeBindGroup(layout, 1, {{0, view}});
}
// Error case: both aspects are selected.
{
wgpu::TextureView view = tex.CreateView();
ASSERT_DEVICE_ERROR(MakeBindGroup(layout, 1, {{0, view}}));
}
}
// Test that it is an error to call .Destroy() on a bind group without a dynamic array.
TEST_F(DynamicBindingArrayTests, DestroyDisallowedOnStaticOnlyBindGroup) {
// Create an empty dynamic bind group.
wgpu::BindGroupLayout layoutDynamic =
MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::BindGroup bgDynamic = MakeBindGroup(layoutDynamic, 0, {});
// Create a static only bind group.
wgpu::BindGroupLayout staticLayout = utils::MakeBindGroupLayout(device, {});
wgpu::BindGroup bgStatic = utils::MakeBindGroup(device, staticLayout, {});
// Success case: calling .Destroy() on a dynamic array bind group is valid.
bgDynamic.Destroy();
// Calling it multiple times is even valid!
bgDynamic.Destroy();
// Error case: calling .Destroy() on a static only bind group is an error.
ASSERT_DEVICE_ERROR(bgStatic.Destroy());
}
// Test that it is an error to call .Destroy() on an error bind group. This is a regression test for
// an issues where doing so was triggering an ASSERT.
TEST_F(DynamicBindingArrayTests, DestroyOnErrorBindGroup) {
// Create an error bind group.
wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}});
wgpu::BindGroup bg;
ASSERT_DEVICE_ERROR(bg = utils::MakeBindGroup(device, layout, {}));
ASSERT_DEVICE_ERROR(bg.Destroy());
}
// Test that using a destroyed dynamic binding array in a render pass in a submit is an error.
TEST_F(DynamicBindingArrayTests, DestroyedDynamicBindingArrayUsedInRenderPass) {
// Create an empty dynamic bind group.
wgpu::BindGroupLayout layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::BindGroup bg = MakeBindGroup(layout, 0, {});
for (bool destroy : {false, true}) {
if (destroy) {
bg.Destroy();
}
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetBindGroup(0, bg);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
if (destroy) {
ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands));
} else {
device.GetQueue().Submit(1, &commands);
}
}
}
// Test that using a destroyed dynamic binding array in a compute pass in a submit is an error.
TEST_F(DynamicBindingArrayTests, DestroyedDynamicBindingArrayUsedInComputePass) {
// Create an empty dynamic bind group.
wgpu::BindGroupLayout layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::BindGroup bg = MakeBindGroup(layout, 0, {});
for (bool destroy : {false, true}) {
if (destroy) {
bg.Destroy();
}
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
if (destroy) {
ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands));
} else {
device.GetQueue().Submit(1, &commands);
}
}
}
// Test that using a destroyed dynamic binding array in a render bundle in a submit is an error.
TEST_F(DynamicBindingArrayTests, DestroyedDynamicBindingArrayUsedInRenderBundle) {
// Create an empty dynamic bind group.
wgpu::BindGroupLayout layout = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
wgpu::BindGroup bg = MakeBindGroup(layout, 0, {});
// Create the render bundle
wgpu::TextureFormat passFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::RenderBundleEncoderDescriptor rbDesc;
rbDesc.colorFormatCount = 1;
rbDesc.colorFormats = &passFormat;
wgpu::RenderBundleEncoder rbEncoder = device.CreateRenderBundleEncoder(&rbDesc);
rbEncoder.SetBindGroup(0, bg);
wgpu::RenderBundle bundle = rbEncoder.Finish();
for (bool destroy : {false, true}) {
if (destroy) {
bg.Destroy();
}
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
auto rp = utils::CreateBasicRenderPass(device, 1, 1, wgpu::TextureFormat::RGBA8Unorm);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.ExecuteBundles(1, &bundle);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
if (destroy) {
ASSERT_DEVICE_ERROR(device.GetQueue().Submit(1, &commands));
} else {
device.GetQueue().Submit(1, &commands);
}
}
}
// Test that using the WGSL enable requires the bindless extension.
TEST_F(DynamicBindingArrayTests_FeatureDisabled, WGSLEnableNotAllowed) {
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)"));
}
// 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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@group(1) @binding(1) var b : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
_ = hasBinding<texture_2d<f32>>(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 : resource_binding;
@group(0) @binding(1) var b : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
_ = hasBinding<texture_2d<f32>>(b, 42);
}
)"));
}
// Test that a shader using only arrayLength on the dynamic binding array is compatible with any
// DynamicBindingKind.
TEST_F(DynamicBindingArrayTests, DynamicArrayKindWithoutTypeInfoValidWithAllLayouts) {
wgpu::ComputePipelineDescriptor csDesc;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = arrayLength(a);
}
)");
// Check that it is compatible with DynamicBindingKind::SampledTexture.
{
wgpu::BindGroupLayout bgl = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl);
device.CreateComputePipeline(&csDesc);
}
// TODO(https://crbug.com/435317394): Add tests with additional DynamicBindingKind.
}
// TODO(https://crbug.com/435317394): Add tests for an error being produced at shader module
// compilation time if it uses the same resource_binding with different DynamicArrayKinds.
// Test that BGL defaulting works with dynamic binding arrays.
TEST_F(DynamicBindingArrayTests, GetBGLSuccess) {
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
MakeBindGroup(pipeline.GetBindGroupLayout(0), 18, {});
}
// Test that the correct array start is defaulted.
TEST_F(DynamicBindingArrayTests, GetBGLDefaultedArrayStart) {
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(7) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a ,42);
}
)");
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
// Create the texture to use in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.size = {1, 1};
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture tex = device.CreateTexture(&tDesc);
// Success case, the array starts at 7 so a texture at binding 7 and 7 + 1 is valid.
MakeBindGroup(pipeline.GetBindGroupLayout(0), 2,
{
{7, tex.CreateView()},
{8, tex.CreateView()},
});
// Error case, the texture is before the start of the binding array.
ASSERT_DEVICE_ERROR(MakeBindGroup(pipeline.GetBindGroupLayout(0), 2,
{
{6, tex.CreateView()},
}));
// Error case, the texture is after the end of the binding array.
ASSERT_DEVICE_ERROR(MakeBindGroup(pipeline.GetBindGroupLayout(0), 2,
{
{9, tex.CreateView()},
}));
}
// Test that the correct array kind is defaulted.
TEST_F(DynamicBindingArrayTests, GetBGLDefaultedArrayKind) {
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
// Create the texture to use in the bind group.
wgpu::TextureDescriptor tDesc;
tDesc.format = wgpu::TextureFormat::RGBA8Unorm;
tDesc.size = {1, 1};
tDesc.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture tex = device.CreateTexture(&tDesc);
// Success case, the BGL will have DynamicArrayKind::SampledTexture.
MakeBindGroup(pipeline.GetBindGroupLayout(0), 1,
{
{0, tex.CreateView()},
});
// TODO(https://crbug.com/435317394): Add tests for the DynamicArrayKind. It is not possible to
// create other DynamicArrayKind than SampledTexture at the moment so we cannot test error
// cases.
}
// Test that defaulting fails if we go above maxBindingsPerBindGroup
TEST_F(DynamicBindingArrayTests, GetBGLMaxBindingsPerGroupLimit) {
// Control case, we are just below the limit.
{
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding()" + std::to_string(kMaxBindingsPerBindGroup - 1) +
R"() var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
device.CreateComputePipeline(&csDesc);
}
// Error case, we are above the limit.
{
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding()" + std::to_string(kMaxBindingsPerBindGroup) +
R"() var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
}
}
// Test that defaulting fails if we go above maxBindGroups
TEST_F(DynamicBindingArrayTests, GetBGLMaxBindGroupsLimit) {
// Control case, we are just below the limit.
{
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module =
utils::CreateShaderModule(device,
R"(
enable chromium_experimental_dynamic_binding;
@group()" + std::to_string(kMaxBindGroups - 1) +
R"() @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
device.CreateComputePipeline(&csDesc);
}
// Error case, we are above the limit.
{
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
csDesc.compute.module =
utils::CreateShaderModule(device,
R"(
enable chromium_experimental_dynamic_binding;
@group()" + std::to_string(kMaxBindGroups) +
R"() @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = hasBinding<texture_2d<f32>>(a, 42);
}
)");
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
}
}
// Test that the dynamic binding arrays start must match between stages.
TEST_F(DynamicBindingArrayTests, GetBGLArrayStartMatchesBetweenStages) {
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.layout = nullptr;
pDesc.vertex.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(38) var a : resource_binding;
@vertex fn vs() -> @builtin(position) vec4f {
_ = hasBinding<texture_2d<f32>>(a, 42);
return vec4f();
}
)");
// Success case: dynamic binding arrays match between the stages.
{
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(38) var a : resource_binding;
@fragment fn fs() -> @location(0) vec4f {
_ = hasBinding<texture_2d<f32>>(a, 42);
return vec4f();
}
)");
device.CreateRenderPipeline(&pDesc);
}
// Success case: dynamic binding arrays have different starts on different groups.
{
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(1) @binding(3) var a : resource_binding;
@fragment fn fs() -> @location(0) vec4f {
_ = hasBinding<texture_2d<f32>>(a, 42);
return vec4f();
}
)");
device.CreateRenderPipeline(&pDesc);
}
// Error case: dynamic binding arrays have different starts on the same group.
{
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(3) var a : resource_binding;
@fragment fn fs() -> @location(0) vec4f {
_ = hasBinding<texture_2d<f32>>(a, 42);
return vec4f();
}
)");
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc));
}
}
// Test that defaulting the layout when no type information is given for the dynamic binding array
// is an error.
TEST_F(DynamicBindingArrayTests, DefaultedDynamicArrayKindMustNotBeUndefined) {
wgpu::ComputePipelineDescriptor csDesc;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var a : resource_binding;
@compute @workgroup_size(1) fn main() {
_ = arrayLength(a);
}
)");
// Control case, explicitly giving a layout works
wgpu::BindGroupLayout bgl = MakeBindGroupLayout(wgpu::DynamicBindingKind::SampledTexture);
csDesc.layout = utils::MakeBasicPipelineLayout(device, &bgl);
device.CreateComputePipeline(&csDesc);
// Error case, defaulting the layout is not possible.
csDesc.layout = nullptr;
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&csDesc));
}
// Test merging of DynamicBindingKind between VS and FS (FS typed case)
TEST_F(DynamicBindingArrayTests, MergingUnknowVSDynamicArrayKindInFS) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var untyped : resource_binding;
@vertex fn vs() -> @builtin(position) vec4f {
_ = arrayLength(untyped);
return vec4f();
}
@group(0) @binding(0) var typed : resource_binding;
@fragment fn fs() -> @location(0) vec4f {
_ = hasBinding<texture_2d<f32>>(typed, 42);
return vec4f();
}
)");
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.layout = nullptr;
pDesc.vertex.module = module;
pDesc.cFragment.module = module;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);
// Check that this is a DynamicBindingKind::SampledTexture dynamic binding array.
wgpu::TextureDescriptor tDesc = {
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::RGBA8Unorm,
};
wgpu::Texture tex = device.CreateTexture(&tDesc);
MakeBindGroup(pipeline.GetBindGroupLayout(0), 1, {{0, tex.CreateView()}});
}
// Test merging of DynamicBindingKind between VS and FS (VS typed case)
TEST_F(DynamicBindingArrayTests, MergingUnknowFSDynamicArrayKindInVS) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
enable chromium_experimental_dynamic_binding;
@group(0) @binding(0) var typed : resource_binding;
@vertex fn vs() -> @builtin(position) vec4f {
_ = hasBinding<texture_2d<f32>>(typed, 42);
return vec4f();
}
@group(0) @binding(0) var untyped : resource_binding;
@fragment fn fs() -> @location(0) vec4f {
_ = arrayLength(untyped);
return vec4f();
}
)");
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.layout = nullptr;
pDesc.vertex.module = module;
pDesc.cFragment.module = module;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);
// Check that this is a DynamicBindingKind::SampledTexture dynamic binding array.
wgpu::TextureDescriptor tDesc = {
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::RGBA8Unorm,
};
wgpu::Texture tex = device.CreateTexture(&tDesc);
MakeBindGroup(pipeline.GetBindGroupLayout(0), 1, {{0, tex.CreateView()}});
}
// TODO(https://crbug.com/435317394): Add tests for the DynamicArrayKind defaulting / merging
// between stages. Tests to add are:
// - Using two incompatible kinds between stages produces an error.
// Test that pinning / unpinning is valid for a simple case. This is a control for the test that
// errors are produced when the feature is not enabled.
TEST_F(DynamicBindingArrayTests, PinUnpinTextureSuccess) {
wgpu::TextureDescriptor desc{
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::R32Float,
};
wgpu::Texture tex = device.CreateTexture(&desc);
tex.Pin(wgpu::TextureUsage::TextureBinding);
tex.Unpin();
}
// Test that calling pin/unpin is an error when the feature is not enabled.
TEST_F(DynamicBindingArrayTests_FeatureDisabled, PinUnpinTextureSuccess) {
wgpu::TextureDescriptor desc{
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::R32Float,
};
wgpu::Texture tex = device.CreateTexture(&desc);
ASSERT_DEVICE_ERROR(tex.Pin(wgpu::TextureUsage::TextureBinding));
ASSERT_DEVICE_ERROR(tex.Unpin());
}
// Test the validation of the usage parameter of Pin.
TEST_F(DynamicBindingArrayTests, PinUnpinTextureUsageConstraint) {
wgpu::TextureDescriptor desc{
.size = {1, 1},
.format = wgpu::TextureFormat::R32Float,
};
desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::StorageBinding;
wgpu::Texture testTexture = device.CreateTexture(&desc);
desc.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::Texture renderOnlyTexture = device.CreateTexture(&desc);
// Control case, pinning the sampled texture to TextureBinding is valid.
testTexture.Pin(wgpu::TextureUsage::TextureBinding);
// Error case, pinning to a usage not in the texture is invalid.
ASSERT_DEVICE_ERROR(renderOnlyTexture.Pin(wgpu::TextureUsage::TextureBinding));
// Error case, pinning to an invalid usage is invalid.
ASSERT_DEVICE_ERROR(testTexture.Pin(static_cast<wgpu::TextureUsage>(0x8000'0000)));
// Error case, pinning must be to a shader usage.
ASSERT_DEVICE_ERROR(testTexture.Pin(wgpu::TextureUsage::CopySrc));
// Error case, pinning must be to a shader usage.
// TODO(https://crbug.com/435317394): Lift this constraint and allow other shader usages.
ASSERT_DEVICE_ERROR(testTexture.Pin(wgpu::TextureUsage::StorageBinding));
}
// Test that pinning / unpinning don't need to be balanced.
TEST_F(DynamicBindingArrayTests, PinUnpinUnbalancedIsValid) {
wgpu::TextureDescriptor desc{
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::R32Float,
};
wgpu::Texture tex = device.CreateTexture(&desc);
// Pinning right after creation is valid.
tex.Unpin();
// Pinning twice is valid.
tex.Pin(wgpu::TextureUsage::TextureBinding);
// TODO(https://crbug.com/435317394): Use a different usage here when another is valid.
tex.Pin(wgpu::TextureUsage::TextureBinding);
// Unpinning twice (plus one more to make sure we are unbalanced) is valid.
tex.Unpin();
tex.Unpin();
tex.Unpin();
}
// Test that pinning is not allowed on a destroyed texture.
TEST_F(DynamicBindingArrayTests, PinDestroyedTextureInvalid) {
wgpu::TextureDescriptor desc{
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::R32Float,
};
wgpu::Texture tex = device.CreateTexture(&desc);
// Success case, pinning before Destroy() is valid.
tex.Pin(wgpu::TextureUsage::TextureBinding);
tex.Unpin();
// Error case, pinning a destroyed texture is not allowed.
tex.Destroy();
ASSERT_DEVICE_ERROR(tex.Pin(wgpu::TextureUsage::TextureBinding));
}
// TODO(https://crbug.com/435317394): This is missing the most important part of the pinned usage
// validation: at submit time (or writeTexture and friends) we must ensure that the usages don't
// conflict with the pinned usage. However adding this validation is a bit risky for the prototyping
// of bindless as it could cause perf regressions. Instead backends will ASSERT when possible that
// no barriers are emitted for the texture while it is pinned.
} // anonymous namespace
} // namespace dawn