blob: a96a58d411b76f80f69d66b10768e81d04d0c599 [file]
// Copyright 2019 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 <cstdint>
#include <string>
#include <vector>
#include "src/dawn/common/Math.h"
#include "src/dawn/tests/DawnTest.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/dawn/utils/WGPUHelpers.h"
#include "src/utils/assert.h"
namespace dawn {
namespace {
class OpArrayLengthTest : public DawnTest {
protected:
void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
dawn::utils::ComboLimits& required) override {
// Just copy all the limits, though all we really care about is
// maxStorageBuffersInFragmentStage
// maxStorageBuffersInVertexStage
supported.UnlinkedCopyTo(&required);
}
void SetUp() override {
DawnTest::SetUp();
// Create buffers of various size to check the length() implementation
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = 4;
bufferDesc.usage = wgpu::BufferUsage::Storage;
mStorageBuffer4 = device.CreateBuffer(&bufferDesc);
bufferDesc.size = 256;
mStorageBuffer256 = device.CreateBuffer(&bufferDesc);
bufferDesc.size = 512 + 256;
mStorageBuffer512 = device.CreateBuffer(&bufferDesc);
// Common shader code to use these buffers in shaders, assuming they are in bindgroup index
// 0.
mShaderInterface = R"(
struct DataBuffer {
data : array<f32>
}
// The length should be 1 because the buffer is 4-byte long.
@group(0) @binding(0) var<storage, read> buffer1 : DataBuffer;
// The length should be 64 because the buffer is 256 bytes long.
@group(0) @binding(1) var<storage, read> buffer2 : DataBuffer;
// The length should be (512 - 16*4) / 8 = 56 because the buffer is 512 bytes long
// and the structure is 8 bytes big.
struct Buffer3Data {
a : f32,
b : i32,
}
struct Buffer3 {
@size(64) garbage : mat4x4<f32>,
data : array<Buffer3Data>,
}
@group(0) @binding(2) var<storage, read> buffer3 : Buffer3;
)";
// See comments in the shader for an explanation of these values
mExpectedLengths = {1, 64, 56};
}
wgpu::BindGroupLayout MakeBindGroupLayout(wgpu::ShaderStage stages) {
// Put them all in a bind group for tests to bind them easily.
return utils::MakeBindGroupLayout(device,
{{0, stages, wgpu::BufferBindingType::ReadOnlyStorage},
{1, stages, wgpu::BufferBindingType::ReadOnlyStorage},
{2, stages, wgpu::BufferBindingType::ReadOnlyStorage}});
}
wgpu::BindGroup MakeBindGroup(wgpu::BindGroupLayout bindGroupLayout) {
return utils::MakeBindGroup(device, bindGroupLayout,
{
{0, mStorageBuffer4, 0, 4},
{1, mStorageBuffer256, 0, wgpu::kWholeSize},
{2, mStorageBuffer512, 256, wgpu::kWholeSize},
});
}
wgpu::Buffer mStorageBuffer4;
wgpu::Buffer mStorageBuffer256;
wgpu::Buffer mStorageBuffer512;
std::string mShaderInterface;
std::array<uint32_t, 3> mExpectedLengths;
};
// Test OpArrayLength in the compute stage
TEST_P(OpArrayLengthTest, Compute) {
// Create a buffer to hold the result sizes and create a bindgroup for it.
wgpu::BufferDescriptor bufferDesc;
bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
bufferDesc.size = sizeof(uint32_t) * mExpectedLengths.size();
wgpu::Buffer resultBuffer = device.CreateBuffer(&bufferDesc);
wgpu::BindGroupLayout resultLayout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}});
wgpu::BindGroup resultBindGroup =
utils::MakeBindGroup(device, resultLayout, {{0, resultBuffer, 0, wgpu::kWholeSize}});
// Create the compute pipeline that stores the length()s in the result buffer.
wgpu::BindGroupLayout bindGroupLayout = MakeBindGroupLayout(wgpu::ShaderStage::Compute);
wgpu::BindGroupLayout bgls[] = {bindGroupLayout, resultLayout};
wgpu::PipelineLayoutDescriptor plDesc;
plDesc.bindGroupLayoutCount = 2;
plDesc.bindGroupLayouts = bgls;
wgpu::PipelineLayout pl = device.CreatePipelineLayout(&plDesc);
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.layout = pl;
pipelineDesc.compute.module = utils::CreateShaderModule(device, (R"(
struct ResultBuffer {
data : array<u32, 3>
}
@group(1) @binding(0) var<storage, read_write> result : ResultBuffer;
)" + mShaderInterface + R"(
@compute @workgroup_size(1) fn main() {
result.data[0] = arrayLength(&buffer1.data);
result.data[1] = arrayLength(&buffer2.data);
result.data[2] = arrayLength(&buffer3.data);
})")
.c_str());
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc);
wgpu::BindGroup bindGroup = MakeBindGroup(bindGroupLayout);
// Run a single instance of the compute shader
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.SetBindGroup(1, resultBindGroup);
pass.DispatchWorkgroups(1);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_RANGE_EQ(mExpectedLengths.data(), resultBuffer, 0, 3);
}
// Test OpArrayLength in the fragment stage
TEST_P(OpArrayLengthTest, Fragment) {
// TODO(crbug.com/408042465): investigate this failure on Pixel 6 OpenGLES
DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM() &&
HasToggleEnabled("gl_use_array_length_from_uniform"));
DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInFragmentStage < 3);
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
// Create the pipeline that computes the length of the buffers and writes it to the only render
// pass pixel.
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
@vertex fn main() -> @builtin(position) vec4f {
return vec4f(0.0, 0.0, 0.0, 1.0);
})");
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, (mShaderInterface + R"(
@fragment fn main() -> @location(0) vec4f {
var fragColor : vec4f;
fragColor.r = f32(arrayLength(&buffer1.data)) / 255.0;
fragColor.g = f32(arrayLength(&buffer2.data)) / 255.0;
fragColor.b = f32(arrayLength(&buffer3.data)) / 255.0;
fragColor.a = 0.0;
return fragColor;
})")
.c_str());
wgpu::BindGroupLayout bindGroupLayout = MakeBindGroupLayout(wgpu::ShaderStage::Fragment);
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = vsModule;
descriptor.cFragment.module = fsModule;
descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
descriptor.cTargets[0].format = renderPass.colorFormat;
descriptor.layout = utils::MakeBasicPipelineLayout(device, &bindGroupLayout);
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
wgpu::BindGroup bindGroup = MakeBindGroup(bindGroupLayout);
// "Draw" the lengths to the texture.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(1);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
utils::RGBA8 expectedColor =
utils::RGBA8(mExpectedLengths[0], mExpectedLengths[1], mExpectedLengths[2], 0);
EXPECT_PIXEL_RGBA8_EQ(expectedColor, renderPass.color, 0, 0);
}
// Test OpArrayLength in the vertex stage
TEST_P(OpArrayLengthTest, Vertex) {
DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInVertexStage < 3);
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
// Create the pipeline that computes the length of the buffers and writes it to the only render
// pass pixel.
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, (mShaderInterface + R"(
struct VertexOut {
@location(0) color : vec4f,
@builtin(position) position : vec4f,
}
@vertex fn main() -> VertexOut {
var output : VertexOut;
output.color.r = f32(arrayLength(&buffer1.data)) / 255.0;
output.color.g = f32(arrayLength(&buffer2.data)) / 255.0;
output.color.b = f32(arrayLength(&buffer3.data)) / 255.0;
output.color.a = 0.0;
output.position = vec4f(0.0, 0.0, 0.0, 1.0);
return output;
})")
.c_str());
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
@fragment
fn main(@location(0) color : vec4f) -> @location(0) vec4f {
return color;
})");
wgpu::BindGroupLayout bindGroupLayout = MakeBindGroupLayout(wgpu::ShaderStage::Vertex);
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = vsModule;
descriptor.cFragment.module = fsModule;
descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
descriptor.cTargets[0].format = renderPass.colorFormat;
descriptor.layout = utils::MakeBasicPipelineLayout(device, &bindGroupLayout);
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
wgpu::BindGroup bindGroup = MakeBindGroup(bindGroupLayout);
// "Draw" the lengths to the texture.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(1);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
utils::RGBA8 expectedColor =
utils::RGBA8(mExpectedLengths[0], mExpectedLengths[1], mExpectedLengths[2], 0);
EXPECT_PIXEL_RGBA8_EQ(expectedColor, renderPass.color, 0, 0);
}
DAWN_INSTANTIATE_TEST(OpArrayLengthTest,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
OpenGLESBackend({"gl_use_array_length_from_uniform"}),
VulkanBackend(),
WebGPUBackend());
// Regression test for stage-visibility filtering in
// GenerateArrayLengthFromuniformData (ShaderModuleGL.cpp). A storage
// buffer in the layout that is *not* visible to the stage being
// compiled should not be given an entry in Tint's
// bindpoint_to_size_index nor in the remapper_data. Otherwise,
// ArrayLengthFromUniform loads the wrong UBO slot for arrayLength(),
// defeating Robustness clamping.
class OpArrayLengthVisibilityCollisionTest : public DawnTest {
protected:
void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
dawn::utils::ComboLimits& required) override {
supported.UnlinkedCopyTo(&required);
}
};
TEST_P(OpArrayLengthVisibilityCollisionTest, ComputeWithFragmentOnlyBufferInLayout) {
DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInFragmentStage < 1);
// Buffer A: large, bound at full size to a FRAGMENT-only storage slot.
constexpr uint32_t kBufASize = 1u << 20; // 1 MiB → arrayLength<u32> = 262144
wgpu::BufferDescriptor descA;
descA.size = kBufASize;
descA.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer bufA = device.CreateBuffer(&descA);
// Buffer B: large allocation, but only a 64-byte sub-range will be bound to
// the COMPUTE-visible storage slot. With the collision the shader receives
// A's bound size as B's arrayLength, so the Robustness clamp on B[idx]
// permits writes far past the 64-byte bound range.
constexpr uint32_t kBufBSize = 16384; // 16 KiB underlying allocation
constexpr uint32_t kBufBBound = 64; // 64 bytes bound → arrayLength = 16
constexpr uint32_t kOobIndex = 1000; // Target B[1000] (byte 4000) – OOB
constexpr uint32_t kOobMarker = 0xDEAD4A11u;
std::vector<uint32_t> initialData(kBufBSize / 4, 0u);
wgpu::Buffer bufB = utils::CreateBufferFromData(
device, initialData.data(), initialData.size() * sizeof(uint32_t),
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
// PipelineLayoutGL assigns ssboIndex by iterating groups in order, so:
// group 0 (A, FRAGMENT-only) → ssboIndex 0
// group 1 (B, COMPUTE) → ssboIndex 1 → glIndex_B = 1
// Choose A's WGSL @binding == glIndex_B (= 1) so that A's pre-remap key
// {0,1} equals B's post-remap key {0, glIndex_B}.
wgpu::BindGroupLayout bglA = utils::MakeBindGroupLayout(
device, {{1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}});
wgpu::BindGroupLayout bglB = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}});
std::string shaderSource = R"(
@group(1) @binding(0) var<storage, read_write> B : array<u32>;
@compute @workgroup_size(1) fn main() {
B[0] = arrayLength(&B);
// Robustness wraps this as B[min(idx, arrayLength(&B)-1)].
// With the bug arrayLength(&B) is huge, so the write lands at
// index 1000 – past the 64-byte bound range.
B[)" + std::to_string(kOobIndex) +
R"(u] = )" + std::to_string(kOobMarker) + R"(u;
})";
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.layout = utils::MakePipelineLayout(device, {bglA, bglB});
pipelineDesc.compute.module = utils::CreateShaderModule(device, shaderSource);
pipelineDesc.compute.entryPoint = "main";
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc);
wgpu::BindGroup bgA = utils::MakeBindGroup(device, bglA, {{1, bufA, 0, kBufASize}});
wgpu::BindGroup bgB = utils::MakeBindGroup(device, bglB, {{0, bufB, 0, kBufBBound}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bgA);
pass.SetBindGroup(1, bgB);
pass.DispatchWorkgroups(1);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(kBufBBound / 4, bufB, 0);
EXPECT_BUFFER_U32_EQ(0u, bufB, kOobIndex * sizeof(uint32_t));
}
DAWN_INSTANTIATE_TEST(OpArrayLengthVisibilityCollisionTest,
OpenGLESBackend(),
OpenGLESBackend({"gl_use_array_length_from_uniform"}),
VulkanBackend());
enum class TieredLimits {
No,
Yes,
};
std::ostream& operator<<(std::ostream& o, TieredLimits tieredLimits) {
switch (tieredLimits) {
case TieredLimits::No:
o << "NoTieredLimits";
break;
case TieredLimits::Yes:
o << "TieredLimits";
break;
}
return o;
}
DAWN_TEST_PARAM_STRUCT(MaxArrayLengthTestParams, TieredLimits);
class MaxArrayLengthTest : public DawnTestWithParams<MaxArrayLengthTestParams> {
protected:
bool GetRequireUseTieredLimits() override {
return GetParam().mTieredLimits == TieredLimits::Yes;
}
void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
dawn::utils::ComboLimits& required) override {
supported.UnlinkedCopyTo(&required);
}
void SetUp() override {
DawnTestWithParams<MaxArrayLengthTestParams>::SetUp();
// Will fail with 'VK_ERROR_OUT_OF_DEVICE_MEMORY' due to the maxStorageBufferBindingSize
// portion of the test
DAWN_SUPPRESS_TEST_IF(IsCompatibilityMode() || IsSwiftshader() || IsANGLESwiftShader() ||
IsOpenGLES());
// Warp runs into memory issues.
DAWN_SUPPRESS_TEST_IF(IsWARP());
// TODO(crbug.com/473894293): [Capture] buffer mapping: investigate.
DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled());
// x86 has issues with OpArrayLength.
DAWN_SUPPRESS_TEST_IF(IsX86());
// TODO(crbug.com/485946556): Dawn native will report the wrong arrayLength for Apple
// hardware.
DAWN_SUPPRESS_TEST_IF(IsApple() && !GetRequireUseTieredLimits());
auto maxBindingSizeSize = AlignDown(GetSupportedLimits().maxStorageBufferBindingSize, 4);
// Create buffers of various sizes to check the length() implementation
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = maxBindingSizeSize;
bufferDesc.usage = wgpu::BufferUsage::Storage;
mStorageBufferMax = device.CreateBuffer(&bufferDesc);
mExpectedLength = static_cast<uint32_t>(maxBindingSizeSize / 4);
}
wgpu::Buffer mStorageBufferMax;
uint32_t mExpectedLength;
};
// Test OpArrayLength in the compute stage
TEST_P(MaxArrayLengthTest, Compute) {
// Create a buffer to hold the result sizes and create a bindgroup for it.
wgpu::BufferDescriptor bufferDesc;
bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
bufferDesc.size = sizeof(uint32_t);
wgpu::Buffer resultBuffer = device.CreateBuffer(&bufferDesc);
wgpu::BindGroupLayout resultLayout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}});
wgpu::BindGroup resultBindGroup =
utils::MakeBindGroup(device, resultLayout, {{0, resultBuffer, 0, wgpu::kWholeSize}});
// Create the compute pipeline that stores the length()s in the result buffer.
wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}});
wgpu::BindGroupLayout bgls[] = {bindGroupLayout, resultLayout};
wgpu::PipelineLayoutDescriptor plDesc;
plDesc.bindGroupLayoutCount = 2;
plDesc.bindGroupLayouts = bgls;
wgpu::PipelineLayout pl = device.CreatePipelineLayout(&plDesc);
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.layout = pl;
pipelineDesc.compute.module = utils::CreateShaderModule(device, R"(
@group(1) @binding(0) var<storage, read_write> result : u32;
struct Buffer {
data : array<u32>,
}
@group(0) @binding(0) var<storage, read> buffer1 : Buffer;
@compute @workgroup_size(1) fn main() {
result = arrayLength(&buffer1.data);
})");
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc);
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bindGroupLayout,
{{0, mStorageBufferMax, 0, wgpu::kWholeSize}});
// Run a single instance of the compute shader
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.SetBindGroup(1, resultBindGroup);
pass.DispatchWorkgroups(1);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_BUFFER_U32_EQ(mExpectedLength, resultBuffer, 0);
}
DAWN_INSTANTIATE_TEST_P(MaxArrayLengthTest,
{D3D11Backend(), D3D12Backend(), MetalBackend(), OpenGLBackend(),
OpenGLESBackend(), OpenGLESBackend({"gl_use_array_length_from_uniform"}),
VulkanBackend(), WebGPUBackend()},
{TieredLimits::No, TieredLimits::Yes});
class GLArrayLengthOverflowTest : public DawnTest {
protected:
void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
dawn::utils::ComboLimits& required) override {
supported.UnlinkedCopyTo(&required);
}
};
// Test that using more than 96 ShaderStage::None bind group entries
// (which don't count against Dawn's validation limit) don't cause GL
// errors and failed buffer transfers.
TEST_P(GLArrayLengthOverflowTest, VisibilityNoneOverflowsArrayLengthBuffer) {
DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxBindGroups < 4);
constexpr uint32_t kLargeSize = 512u;
constexpr uint32_t kSmallSize = 256u;
constexpr uint32_t kPadPerGroup = 33;
wgpu::BufferDescriptor bd;
bd.size = kLargeSize;
bd.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
wgpu::Buffer largeBuf = device.CreateBuffer(&bd);
// Get the arrayLength() of the passed-in storage buffer, and store it into
// the first element of the array.
wgpu::ComputePipelineDescriptor primeDesc;
primeDesc.compute.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var<storage, read_write> a : array<u32>;
@compute @workgroup_size(1) fn main() {
a[0] = arrayLength(&a);
})");
wgpu::ComputePipeline primePipeline = device.CreateComputePipeline(&primeDesc);
wgpu::BindGroupLayout primeBGL = primePipeline.GetBindGroupLayout(0);
wgpu::BindGroup primeBG = utils::MakeBindGroup(device, primeBGL, {{0, largeBuf}});
{
wgpu::CommandEncoder enc = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = enc.BeginComputePass();
pass.SetPipeline(primePipeline);
pass.SetBindGroup(0, primeBG);
pass.DispatchWorkgroups(1);
pass.End();
wgpu::CommandBuffer cb = enc.Finish();
queue.Submit(1, &cb);
}
bd.size = kSmallSize;
bd.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
wgpu::Buffer smallBuf = device.CreateBuffer(&bd);
bd.size = 4;
bd.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer tinyBuffer = device.CreateBuffer(&bd);
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}});
std::vector<wgpu::BindGroupLayoutEntry> padEntries(kPadPerGroup);
for (uint32_t i = 0; i < kPadPerGroup; i++) {
padEntries[i].binding = i;
padEntries[i].visibility = wgpu::ShaderStage::None;
padEntries[i].buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
}
wgpu::BindGroupLayoutDescriptor padDesc;
padDesc.entryCount = padEntries.size();
padDesc.entries = padEntries.data();
wgpu::BindGroupLayout bglPad = device.CreateBindGroupLayout(&padDesc);
wgpu::BindGroupLayout bgls[] = {bgl0, bglPad, bglPad, bglPad};
wgpu::PipelineLayoutDescriptor plDesc;
plDesc.bindGroupLayoutCount = 4;
plDesc.bindGroupLayouts = bgls;
wgpu::PipelineLayout manyBindingsPL = device.CreatePipelineLayout(&plDesc);
wgpu::ComputePipelineDescriptor manyBindingsDesc;
manyBindingsDesc.layout = manyBindingsPL;
manyBindingsDesc.compute.module = primeDesc.compute.module;
wgpu::ComputePipeline manyBindingsPipeline = device.CreateComputePipeline(&manyBindingsDesc);
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, smallBuf}});
std::vector<wgpu::BindGroupEntry> padBinds(kPadPerGroup);
for (uint32_t i = 0; i < kPadPerGroup; i++) {
padBinds[i].binding = i;
padBinds[i].buffer = tinyBuffer;
}
wgpu::BindGroupDescriptor bgPadDesc;
bgPadDesc.layout = bglPad;
bgPadDesc.entryCount = padBinds.size();
bgPadDesc.entries = padBinds.data();
wgpu::BindGroup bgPad = device.CreateBindGroup(&bgPadDesc);
{
wgpu::CommandEncoder enc = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = enc.BeginComputePass();
pass.SetPipeline(manyBindingsPipeline);
pass.SetBindGroup(0, bg0);
pass.SetBindGroup(1, bgPad);
pass.SetBindGroup(2, bgPad);
pass.SetBindGroup(3, bgPad);
pass.DispatchWorkgroups(1);
pass.End();
wgpu::CommandBuffer cb = enc.Finish();
queue.Submit(1, &cb);
}
// Check that the stored arrayLength is the (new) small buffer length
// and not the (stale) large buffer length.
EXPECT_BUFFER_U32_EQ(kSmallSize / 4u, smallBuf, 0);
}
DAWN_INSTANTIATE_TEST(GLArrayLengthOverflowTest,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
OpenGLESBackend({"gl_use_array_length_from_uniform"}),
VulkanBackend(),
WebGPUBackend());
} // anonymous namespace
} // namespace dawn