Raise maxStorageTexturesPerShaderStage to 8

The higher tier for this limit is available on all D3D12, all Metal,
and most Vulkan devices.

Bug: dawn:685
Change-Id: Ic2a39ad7908ea178e7aac48b7bb54b262d7039cf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/121543
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Shrek Shao <shrekshao@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
Austin Eng 2023-02-27 20:22:51 +00:00 committed by Dawn LUCI CQ
parent 4d3af66bbd
commit 148e7fab1c
6 changed files with 207 additions and 189 deletions

View File

@ -31,11 +31,11 @@ static constexpr uint32_t kMaxInterStageShaderVariables = 16u;
static constexpr uint64_t kAssumedMaxBufferSize =
0x80000000u; // Use 2 GB when the limit is unavailable
// Per stage limits
// Per stage maximum limits used to optimized Dawn internals.
static constexpr uint32_t kMaxSampledTexturesPerShaderStage = 16;
static constexpr uint32_t kMaxSamplersPerShaderStage = 16;
static constexpr uint32_t kMaxStorageBuffersPerShaderStage = 8;
static constexpr uint32_t kMaxStorageTexturesPerShaderStage = 4;
static constexpr uint32_t kMaxStorageTexturesPerShaderStage = 8;
static constexpr uint32_t kMaxUniformBuffersPerShaderStage = 12;
// Indirect command sizes

View File

@ -112,80 +112,85 @@ MaybeError ValidateBindingCounts(const CombinedLimits& limits, const BindingCoun
limits.v1.maxDynamicStorageBuffersPerPipelineLayout);
for (SingleShaderStage stage : IterateStages(kAllStages)) {
DAWN_INVALID_IF(
bindingCounts.perStage[stage].sampledTextureCount > kMaxSampledTexturesPerShaderStage,
"The number of sampled textures (%u) in the %s stage exceeds the maximum "
"per-stage limit (%u).",
bindingCounts.perStage[stage].sampledTextureCount, stage,
kMaxSampledTexturesPerShaderStage);
DAWN_INVALID_IF(bindingCounts.perStage[stage].sampledTextureCount >
limits.v1.maxSampledTexturesPerShaderStage,
"The number of sampled textures (%u) in the %s stage exceeds the maximum "
"per-stage limit (%u).",
bindingCounts.perStage[stage].sampledTextureCount, stage,
limits.v1.maxSampledTexturesPerShaderStage);
// The per-stage number of external textures is bound by the maximum sampled textures
// per stage.
DAWN_INVALID_IF(bindingCounts.perStage[stage].externalTextureCount >
kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
"The number of external textures (%u) in the %s stage exceeds the maximum "
"per-stage limit (%u).",
bindingCounts.perStage[stage].externalTextureCount, stage,
kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].externalTextureCount >
limits.v1.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
"The number of external textures (%u) in the %s stage exceeds the maximum "
"per-stage limit (%u).",
bindingCounts.perStage[stage].externalTextureCount, stage,
limits.v1.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].sampledTextureCount +
(bindingCounts.perStage[stage].externalTextureCount *
kSampledTexturesPerExternalTexture) >
kMaxSampledTexturesPerShaderStage,
limits.v1.maxSampledTexturesPerShaderStage,
"The combination of sampled textures (%u) and external textures (%u) in the %s "
"stage exceeds the maximum per-stage limit (%u).",
bindingCounts.perStage[stage].sampledTextureCount,
bindingCounts.perStage[stage].externalTextureCount, stage,
kMaxSampledTexturesPerShaderStage);
limits.v1.maxSampledTexturesPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].samplerCount > kMaxSamplersPerShaderStage,
bindingCounts.perStage[stage].samplerCount > limits.v1.maxSamplersPerShaderStage,
"The number of samplers (%u) in the %s stage exceeds the maximum per-stage limit "
"(%u).",
bindingCounts.perStage[stage].samplerCount, stage, kMaxSamplersPerShaderStage);
bindingCounts.perStage[stage].samplerCount, stage, limits.v1.maxSamplersPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].samplerCount +
(bindingCounts.perStage[stage].externalTextureCount *
kSamplersPerExternalTexture) >
kMaxSamplersPerShaderStage,
limits.v1.maxSamplersPerShaderStage,
"The combination of samplers (%u) and external textures (%u) in the %s stage "
"exceeds the maximum per-stage limit (%u).",
bindingCounts.perStage[stage].samplerCount,
bindingCounts.perStage[stage].externalTextureCount, stage, kMaxSamplersPerShaderStage);
bindingCounts.perStage[stage].externalTextureCount, stage,
limits.v1.maxSamplersPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].storageBufferCount > kMaxStorageBuffersPerShaderStage,
bindingCounts.perStage[stage].storageBufferCount >
limits.v1.maxStorageBuffersPerShaderStage,
"The number of storage buffers (%u) in the %s stage exceeds the maximum per-stage "
"limit (%u).",
bindingCounts.perStage[stage].storageBufferCount, stage,
kMaxStorageBuffersPerShaderStage);
limits.v1.maxStorageBuffersPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].storageTextureCount > kMaxStorageTexturesPerShaderStage,
bindingCounts.perStage[stage].storageTextureCount >
limits.v1.maxStorageTexturesPerShaderStage,
"The number of storage textures (%u) in the %s stage exceeds the maximum per-stage "
"limit (%u).",
bindingCounts.perStage[stage].storageTextureCount, stage,
kMaxStorageTexturesPerShaderStage);
limits.v1.maxStorageTexturesPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].uniformBufferCount > kMaxUniformBuffersPerShaderStage,
bindingCounts.perStage[stage].uniformBufferCount >
limits.v1.maxUniformBuffersPerShaderStage,
"The number of uniform buffers (%u) in the %s stage exceeds the maximum per-stage "
"limit (%u).",
bindingCounts.perStage[stage].uniformBufferCount, stage,
kMaxUniformBuffersPerShaderStage);
limits.v1.maxUniformBuffersPerShaderStage);
DAWN_INVALID_IF(
bindingCounts.perStage[stage].uniformBufferCount +
(bindingCounts.perStage[stage].externalTextureCount *
kUniformsPerExternalTexture) >
kMaxUniformBuffersPerShaderStage,
limits.v1.maxUniformBuffersPerShaderStage,
"The combination of uniform buffers (%u) and external textures (%u) in the %s "
"stage exceeds the maximum per-stage limit (%u).",
bindingCounts.perStage[stage].uniformBufferCount,
bindingCounts.perStage[stage].externalTextureCount, stage,
kMaxUniformBuffersPerShaderStage);
limits.v1.maxUniformBuffersPerShaderStage);
}
return {};

View File

@ -38,6 +38,11 @@
#define LIMITS_RESOURCE_BINDINGS(X) \
X(Maximum, maxDynamicUniformBuffersPerPipelineLayout, 8, 10) \
X(Maximum, maxDynamicStorageBuffersPerPipelineLayout, 4, 8) \
X(Maximum, maxSampledTexturesPerShaderStage, 16, 16) \
X(Maximum, maxSamplersPerShaderStage, 16, 16) \
X(Maximum, maxStorageBuffersPerShaderStage, 8, 8) \
X(Maximum, maxStorageTexturesPerShaderStage, 4, 8) \
X(Maximum, maxUniformBuffersPerShaderStage, 12, 12)
// TODO(crbug.com/dawn/685):
// These limits don't have tiers yet. Define two tiers with the same values since the macros
@ -49,11 +54,6 @@
X(Maximum, maxTextureArrayLayers, 256, 256) \
X(Maximum, maxBindGroups, 4, 4) \
X(Maximum, maxBindingsPerBindGroup, 640, 640) \
X(Maximum, maxSampledTexturesPerShaderStage, 16, 16) \
X(Maximum, maxSamplersPerShaderStage, 16, 16) \
X(Maximum, maxStorageBuffersPerShaderStage, 8, 8) \
X(Maximum, maxStorageTexturesPerShaderStage, 4, 4) \
X(Maximum, maxUniformBuffersPerShaderStage, 12, 12) \
X(Maximum, maxUniformBufferBindingSize, 65536, 65536) \
X(Alignment, minUniformBufferOffsetAlignment, 256, 256) \
X(Alignment, minStorageBufferOffsetAlignment, 256, 256) \

View File

@ -1427,150 +1427,6 @@ TEST_P(BindGroupTests, ReadonlyStorage) {
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderPass.color, 0, 0);
}
// Test that creating a large bind group, with each binding type at the max count, works and can be
// used correctly. The test loads a different value from each binding, and writes 1 to a storage
// buffer if all values are correct.
TEST_P(BindGroupTests, ReallyLargeBindGroup) {
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
std::ostringstream interface;
std::ostringstream body;
uint32_t binding = 0;
uint32_t expectedValue = 42;
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
auto CreateTextureWithRedData = [&](wgpu::TextureFormat format, uint32_t value,
wgpu::TextureUsage usage) {
wgpu::TextureDescriptor textureDesc = {};
textureDesc.usage = wgpu::TextureUsage::CopyDst | usage;
textureDesc.size = {1, 1, 1};
textureDesc.format = format;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
if (format == wgpu::TextureFormat::R8Unorm) {
ASSERT(expectedValue < 255u);
}
wgpu::Buffer textureData =
utils::CreateBufferFromData(device, wgpu::BufferUsage::CopySrc, {value});
wgpu::ImageCopyBuffer imageCopyBuffer = {};
imageCopyBuffer.buffer = textureData;
imageCopyBuffer.layout.bytesPerRow = 256;
wgpu::ImageCopyTexture imageCopyTexture = {};
imageCopyTexture.texture = texture;
wgpu::Extent3D copySize = {1, 1, 1};
commandEncoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &copySize);
return texture;
};
std::vector<wgpu::BindGroupEntry> bgEntries;
static_assert(kMaxSampledTexturesPerShaderStage == kMaxSamplersPerShaderStage,
"Please update this test");
for (uint32_t i = 0; i < kMaxSampledTexturesPerShaderStage; ++i) {
wgpu::Texture texture = CreateTextureWithRedData(
wgpu::TextureFormat::R8Unorm, expectedValue, wgpu::TextureUsage::TextureBinding);
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
interface << "@group(0) @binding(" << binding++ << ") "
<< "var tex" << i << " : texture_2d<f32>;\n";
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, device.CreateSampler(), nullptr});
interface << "@group(0) @binding(" << binding++ << ")"
<< "var samp" << i << " : sampler;\n";
body << "if (abs(textureSampleLevel(tex" << i << ", samp" << i
<< ", vec2f(0.5, 0.5), 0.0).r - " << expectedValue++ << ".0 / 255.0) > 0.0001) {\n";
body << " return;\n";
body << "}\n";
}
for (uint32_t i = 0; i < kMaxStorageTexturesPerShaderStage; ++i) {
wgpu::Texture texture = CreateTextureWithRedData(
wgpu::TextureFormat::R32Uint, expectedValue, wgpu::TextureUsage::StorageBinding);
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
interface << "@group(0) @binding(" << binding++ << ") "
<< "var image" << i << " : texture_storage_2d<r32uint, write>;\n";
body << "_ = image" << i << ";";
}
for (uint32_t i = 0; i < kMaxUniformBuffersPerShaderStage; ++i) {
wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Uniform, {expectedValue, 0, 0, 0});
bgEntries.push_back({nullptr, binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr});
interface << "struct UniformBuffer" << i << R"({
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<uniform> ubuf" << i << " : UniformBuffer" << i << ";\n";
body << "if (ubuf" << i << ".value != " << expectedValue++ << "u) {\n";
body << " return;\n";
body << "}\n";
}
// Save one storage buffer for writing the result
for (uint32_t i = 0; i < kMaxStorageBuffersPerShaderStage - 1; ++i) {
wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Storage, {expectedValue});
bgEntries.push_back({nullptr, binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr});
interface << "struct ReadOnlyStorageBuffer" << i << R"({
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<storage, read> sbuf" << i << " : ReadOnlyStorageBuffer" << i << ";\n";
body << "if (sbuf" << i << ".value != " << expectedValue++ << "u) {\n";
body << " return;\n";
body << "}\n";
}
wgpu::Buffer result = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0});
bgEntries.push_back({nullptr, binding, result, 0, sizeof(uint32_t), nullptr, nullptr});
interface << R"(struct ReadWriteStorageBuffer{
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<storage, read_write> result : ReadWriteStorageBuffer;\n";
body << "result.value = 1u;\n";
std::string shader =
interface.str() + "@compute @workgroup_size(1) fn main() {\n" + body.str() + "}\n";
wgpu::ComputePipelineDescriptor cpDesc;
cpDesc.compute.module = utils::CreateShaderModule(device, shader.c_str());
cpDesc.compute.entryPoint = "main";
wgpu::ComputePipeline cp = device.CreateComputePipeline(&cpDesc);
wgpu::BindGroupDescriptor bgDesc = {};
bgDesc.layout = cp.GetBindGroupLayout(0);
bgDesc.entryCount = static_cast<uint32_t>(bgEntries.size());
bgDesc.entries = bgEntries.data();
wgpu::BindGroup bg = device.CreateBindGroup(&bgDesc);
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
pass.SetPipeline(cp);
pass.SetBindGroup(0, bg);
pass.DispatchWorkgroups(1, 1, 1);
pass.End();
wgpu::CommandBuffer commands = commandEncoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, result, 0);
}
// This is a regression test for crbug.com/dawn/319 where creating a bind group with a
// destroyed resource would crash the backend.
TEST_P(BindGroupTests, CreateWithDestroyedResource) {

View File

@ -392,6 +392,152 @@ TEST_P(MaxLimitTests, MaxDynamicBuffers) {
EXPECT_TEXTURE_EQ(&expected, renderTarget, {0, 0}, {1, 1});
}
// Test that creating a large bind group, with each binding type at the max count, works and can be
// used correctly. The test loads a different value from each binding, and writes 1 to a storage
// buffer if all values are correct.
TEST_P(MaxLimitTests, ReallyLargeBindGroup) {
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
wgpu::Limits limits = GetSupportedLimits().limits;
std::ostringstream interface;
std::ostringstream body;
uint32_t binding = 0;
uint32_t expectedValue = 42;
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
auto CreateTextureWithRedData = [&](wgpu::TextureFormat format, uint32_t value,
wgpu::TextureUsage usage) {
wgpu::TextureDescriptor textureDesc = {};
textureDesc.usage = wgpu::TextureUsage::CopyDst | usage;
textureDesc.size = {1, 1, 1};
textureDesc.format = format;
wgpu::Texture texture = device.CreateTexture(&textureDesc);
if (format == wgpu::TextureFormat::R8Unorm) {
ASSERT(expectedValue < 255u);
}
wgpu::Buffer textureData =
utils::CreateBufferFromData(device, wgpu::BufferUsage::CopySrc, {value});
wgpu::ImageCopyBuffer imageCopyBuffer = {};
imageCopyBuffer.buffer = textureData;
imageCopyBuffer.layout.bytesPerRow = 256;
wgpu::ImageCopyTexture imageCopyTexture = {};
imageCopyTexture.texture = texture;
wgpu::Extent3D copySize = {1, 1, 1};
commandEncoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &copySize);
return texture;
};
std::vector<wgpu::BindGroupEntry> bgEntries;
for (uint32_t i = 0;
i < std::min(limits.maxSampledTexturesPerShaderStage, limits.maxSamplersPerShaderStage);
++i) {
wgpu::Texture texture = CreateTextureWithRedData(
wgpu::TextureFormat::R8Unorm, expectedValue, wgpu::TextureUsage::TextureBinding);
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
interface << "@group(0) @binding(" << binding++ << ") "
<< "var tex" << i << " : texture_2d<f32>;\n";
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, device.CreateSampler(), nullptr});
interface << "@group(0) @binding(" << binding++ << ")"
<< "var samp" << i << " : sampler;\n";
body << "if (abs(textureSampleLevel(tex" << i << ", samp" << i
<< ", vec2f(0.5, 0.5), 0.0).r - " << expectedValue++ << ".0 / 255.0) > 0.0001) {\n";
body << " return;\n";
body << "}\n";
}
for (uint32_t i = 0; i < limits.maxStorageTexturesPerShaderStage; ++i) {
wgpu::Texture texture = CreateTextureWithRedData(
wgpu::TextureFormat::R32Uint, expectedValue, wgpu::TextureUsage::StorageBinding);
bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()});
interface << "@group(0) @binding(" << binding++ << ") "
<< "var image" << i << " : texture_storage_2d<r32uint, write>;\n";
body << "_ = image" << i << ";";
}
for (uint32_t i = 0; i < limits.maxUniformBuffersPerShaderStage; ++i) {
wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Uniform, {expectedValue, 0, 0, 0});
bgEntries.push_back({nullptr, binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr});
interface << "struct UniformBuffer" << i << R"({
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<uniform> ubuf" << i << " : UniformBuffer" << i << ";\n";
body << "if (ubuf" << i << ".value != " << expectedValue++ << "u) {\n";
body << " return;\n";
body << "}\n";
}
// Save one storage buffer for writing the result
for (uint32_t i = 0; i < limits.maxStorageBuffersPerShaderStage - 1; ++i) {
wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Storage, {expectedValue});
bgEntries.push_back({nullptr, binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr});
interface << "struct ReadOnlyStorageBuffer" << i << R"({
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<storage, read> sbuf" << i << " : ReadOnlyStorageBuffer" << i << ";\n";
body << "if (sbuf" << i << ".value != " << expectedValue++ << "u) {\n";
body << " return;\n";
body << "}\n";
}
wgpu::Buffer result = utils::CreateBufferFromData<uint32_t>(
device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0});
bgEntries.push_back({nullptr, binding, result, 0, sizeof(uint32_t), nullptr, nullptr});
interface << R"(struct ReadWriteStorageBuffer{
value : u32
}
)";
interface << "@group(0) @binding(" << binding++ << ") "
<< "var<storage, read_write> result : ReadWriteStorageBuffer;\n";
body << "result.value = 1u;\n";
std::string shader =
interface.str() + "@compute @workgroup_size(1) fn main() {\n" + body.str() + "}\n";
wgpu::ComputePipelineDescriptor cpDesc;
cpDesc.compute.module = utils::CreateShaderModule(device, shader.c_str());
cpDesc.compute.entryPoint = "main";
wgpu::ComputePipeline cp = device.CreateComputePipeline(&cpDesc);
wgpu::BindGroupDescriptor bgDesc = {};
bgDesc.layout = cp.GetBindGroupLayout(0);
bgDesc.entryCount = static_cast<uint32_t>(bgEntries.size());
bgDesc.entries = bgEntries.data();
wgpu::BindGroup bg = device.CreateBindGroup(&bgDesc);
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
pass.SetPipeline(cp);
pass.SetBindGroup(0, bg);
pass.DispatchWorkgroups(1, 1, 1);
pass.End();
wgpu::CommandBuffer commands = commandEncoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(1, result, 0);
}
DAWN_INSTANTIATE_TEST(MaxLimitTests,
D3D12Backend(),
MetalBackend(),

View File

@ -1293,25 +1293,32 @@ TEST_F(BindGroupLayoutValidationTest, PerStageLimits) {
wgpu::BindGroupLayoutEntry otherEntry;
};
wgpu::Limits limits = GetSupportedLimits().limits;
std::array<TestInfo, 7> kTestInfos = {
TestInfo{kMaxSampledTexturesPerShaderStage, BGLEntryType(wgpu::TextureSampleType::Float),
TestInfo{limits.maxSampledTexturesPerShaderStage,
BGLEntryType(wgpu::TextureSampleType::Float),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxSamplersPerShaderStage, BGLEntryType(wgpu::SamplerBindingType::Filtering),
TestInfo{limits.maxSamplersPerShaderStage,
BGLEntryType(wgpu::SamplerBindingType::Filtering),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxSamplersPerShaderStage, BGLEntryType(wgpu::SamplerBindingType::Comparison),
TestInfo{limits.maxSamplersPerShaderStage,
BGLEntryType(wgpu::SamplerBindingType::Comparison),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxStorageBuffersPerShaderStage, BGLEntryType(wgpu::BufferBindingType::Storage),
TestInfo{limits.maxStorageBuffersPerShaderStage,
BGLEntryType(wgpu::BufferBindingType::Storage),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{
kMaxStorageTexturesPerShaderStage,
limits.maxStorageTexturesPerShaderStage,
BGLEntryType(wgpu::StorageTextureAccess::WriteOnly, wgpu::TextureFormat::RGBA8Unorm),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxUniformBuffersPerShaderStage, BGLEntryType(wgpu::BufferBindingType::Uniform),
TestInfo{limits.maxUniformBuffersPerShaderStage,
BGLEntryType(wgpu::BufferBindingType::Uniform),
BGLEntryType(wgpu::TextureSampleType::Float)},
// External textures use multiple bindings (3 sampled textures, 1 sampler, 1 uniform buffer)
// that count towards the per stage binding limits. The number of external textures are
// currently restricted by the maximum number of sampled textures.
TestInfo{kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
TestInfo{limits.maxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture,
BGLEntryType(&utils::kExternalTextureBindingLayout),
BGLEntryType(wgpu::BufferBindingType::Uniform)}};
@ -1388,14 +1395,16 @@ TEST_F(BindGroupLayoutValidationTest, PerStageLimitsWithExternalTexture) {
wgpu::BindGroupLayoutEntry otherEntry;
};
wgpu::Limits limits = GetSupportedLimits().limits;
std::array<TestInfo, 3> kTestInfos = {
TestInfo{kMaxSampledTexturesPerShaderStage, kSampledTexturesPerExternalTexture,
TestInfo{limits.maxSampledTexturesPerShaderStage, kSampledTexturesPerExternalTexture,
BGLEntryType(wgpu::TextureSampleType::Float),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxSamplersPerShaderStage, kSamplersPerExternalTexture,
TestInfo{limits.maxSamplersPerShaderStage, kSamplersPerExternalTexture,
BGLEntryType(wgpu::SamplerBindingType::Filtering),
BGLEntryType(wgpu::BufferBindingType::Uniform)},
TestInfo{kMaxUniformBuffersPerShaderStage, kUniformsPerExternalTexture,
TestInfo{limits.maxUniformBuffersPerShaderStage, kUniformsPerExternalTexture,
BGLEntryType(wgpu::BufferBindingType::Uniform),
BGLEntryType(wgpu::TextureSampleType::Float)},
};
@ -1480,8 +1489,10 @@ TEST_F(BindGroupLayoutValidationTest, DynamicBufferNumberLimit) {
// In this test, we use all the same shader stage. Ensure that this does not exceed the
// per-stage limit.
ASSERT(limits.maxDynamicUniformBuffersPerPipelineLayout <= kMaxUniformBuffersPerShaderStage);
ASSERT(limits.maxDynamicStorageBuffersPerPipelineLayout <= kMaxStorageBuffersPerShaderStage);
ASSERT(limits.maxDynamicUniformBuffersPerPipelineLayout <=
limits.maxUniformBuffersPerShaderStage);
ASSERT(limits.maxDynamicStorageBuffersPerPipelineLayout <=
limits.maxStorageBuffersPerShaderStage);
for (uint32_t i = 0; i < limits.maxDynamicUniformBuffersPerPipelineLayout; ++i) {
maxUniformDB.push_back(utils::BindingLayoutEntryInitializationHelper(