// Copyright 2019 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "tests/DawnTest.h" #include "common/Math.h" #include "utils/ComboRenderPipelineDescriptor.h" #include "utils/WGPUHelpers.h" #include constexpr uint32_t kRTSize = 400; constexpr uint32_t kBindingSize = 8; class DynamicBufferOffsetTests : public DawnTest { protected: void SetUp() override { DawnTest::SetUp(); mMinUniformBufferOffsetAlignment = GetSupportedLimits().limits.minUniformBufferOffsetAlignment; // Mix up dynamic and non dynamic resources in one bind group and using not continuous // binding number to cover more cases. std::vector uniformData(mMinUniformBufferOffsetAlignment / sizeof(uint32_t) + 2); uniformData[0] = 1; uniformData[1] = 2; mUniformBuffers[0] = utils::CreateBufferFromData(device, uniformData.data(), sizeof(uint32_t) * uniformData.size(), wgpu::BufferUsage::Uniform); uniformData[uniformData.size() - 2] = 5; uniformData[uniformData.size() - 1] = 6; // Dynamic uniform buffer mUniformBuffers[1] = utils::CreateBufferFromData(device, uniformData.data(), sizeof(uint32_t) * uniformData.size(), wgpu::BufferUsage::Uniform); wgpu::BufferDescriptor storageBufferDescriptor; storageBufferDescriptor.size = sizeof(uint32_t) * uniformData.size(); storageBufferDescriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; mStorageBuffers[0] = device.CreateBuffer(&storageBufferDescriptor); // Dynamic storage buffer mStorageBuffers[1] = device.CreateBuffer(&storageBufferDescriptor); // Default bind group layout mBindGroupLayouts[0] = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}, {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}, {3, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform, true}, {4, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage, true}}); // Default bind group mBindGroups[0] = utils::MakeBindGroup(device, mBindGroupLayouts[0], {{0, mUniformBuffers[0], 0, kBindingSize}, {1, mStorageBuffers[0], 0, kBindingSize}, {3, mUniformBuffers[1], 0, kBindingSize}, {4, mStorageBuffers[1], 0, kBindingSize}}); // Extra uniform buffer for inheriting test mUniformBuffers[2] = utils::CreateBufferFromData(device, uniformData.data(), sizeof(uint32_t) * uniformData.size(), wgpu::BufferUsage::Uniform); // Bind group layout for inheriting test mBindGroupLayouts[1] = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}}); // Bind group for inheriting test mBindGroups[1] = utils::MakeBindGroup(device, mBindGroupLayouts[1], {{0, mUniformBuffers[2], 0, kBindingSize}}); } // Create objects to use as resources inside test bind groups. uint32_t mMinUniformBufferOffsetAlignment; wgpu::BindGroup mBindGroups[2]; wgpu::BindGroupLayout mBindGroupLayouts[2]; wgpu::Buffer mUniformBuffers[3]; wgpu::Buffer mStorageBuffers[2]; wgpu::Texture mColorAttachment; wgpu::RenderPipeline CreateRenderPipeline(bool isInheritedPipeline = false) { wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( [[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4 { var pos = array, 3>( vec2(-1.0, 0.0), vec2(-1.0, 1.0), vec2( 0.0, 1.0)); return vec4(pos[VertexIndex], 0.0, 1.0); })"); // Construct fragment shader source std::ostringstream fs; std::string multipleNumber = isInheritedPipeline ? "2" : "1"; fs << R"( struct Buf { value : vec2; }; [[group(0), binding(0)]] var uBufferNotDynamic : Buf; [[group(0), binding(1)]] var sBufferNotDynamic : Buf; [[group(0), binding(3)]] var uBuffer : Buf; [[group(0), binding(4)]] var sBuffer : Buf; )"; if (isInheritedPipeline) { fs << R"( [[group(1), binding(0)]] var paddingBlock : Buf; )"; } fs << "let multipleNumber : u32 = " << multipleNumber << "u;\n"; fs << R"( [[stage(fragment)]] fn main() -> [[location(0)]] vec4 { sBufferNotDynamic.value = uBufferNotDynamic.value.xy; sBuffer.value = vec2(multipleNumber, multipleNumber) * (uBuffer.value.xy + uBufferNotDynamic.value.xy); return vec4(f32(uBuffer.value.x) / 255.0, f32(uBuffer.value.y) / 255.0, 1.0, 1.0); } )"; wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fs.str().c_str()); utils::ComboRenderPipelineDescriptor pipelineDescriptor; pipelineDescriptor.vertex.module = vsModule; pipelineDescriptor.cFragment.module = fsModule; pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; if (isInheritedPipeline) { pipelineLayoutDescriptor.bindGroupLayoutCount = 2; } else { pipelineLayoutDescriptor.bindGroupLayoutCount = 1; } pipelineLayoutDescriptor.bindGroupLayouts = mBindGroupLayouts; pipelineDescriptor.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); return device.CreateRenderPipeline(&pipelineDescriptor); } wgpu::ComputePipeline CreateComputePipeline(bool isInheritedPipeline = false) { // Construct compute shader source std::ostringstream cs; std::string multipleNumber = isInheritedPipeline ? "2" : "1"; cs << R"( struct Buf { value : vec2; }; [[group(0), binding(0)]] var uBufferNotDynamic : Buf; [[group(0), binding(1)]] var sBufferNotDynamic : Buf; [[group(0), binding(3)]] var uBuffer : Buf; [[group(0), binding(4)]] var sBuffer : Buf; )"; if (isInheritedPipeline) { cs << R"( [[group(1), binding(0)]] var paddingBlock : Buf; )"; } cs << "let multipleNumber : u32 = " << multipleNumber << "u;\n"; cs << R"( [[stage(compute), workgroup_size(1)]] fn main() { sBufferNotDynamic.value = uBufferNotDynamic.value.xy; sBuffer.value = vec2(multipleNumber, multipleNumber) * (uBuffer.value.xy + uBufferNotDynamic.value.xy); } )"; wgpu::ShaderModule csModule = utils::CreateShaderModule(device, cs.str().c_str()); wgpu::ComputePipelineDescriptor csDesc; csDesc.compute.module = csModule; csDesc.compute.entryPoint = "main"; wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor; if (isInheritedPipeline) { pipelineLayoutDescriptor.bindGroupLayoutCount = 2; } else { pipelineLayoutDescriptor.bindGroupLayoutCount = 1; } pipelineLayoutDescriptor.bindGroupLayouts = mBindGroupLayouts; csDesc.layout = device.CreatePipelineLayout(&pipelineLayoutDescriptor); return device.CreateComputePipeline(&csDesc); } }; // Dynamic offsets are all zero and no effect to result. TEST_P(DynamicBufferOffsetTests, BasicRenderPipeline) { wgpu::RenderPipeline pipeline = CreateRenderPipeline(); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); std::array offsets = {0, 0}; wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass.renderPassInfo); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); renderPassEncoder.Draw(3); renderPassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {2, 4}; EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 255, 255), renderPass.color, 0, 0); EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size()); } // Have non-zero dynamic offsets. TEST_P(DynamicBufferOffsetTests, SetDynamicOffsetsRenderPipeline) { wgpu::RenderPipeline pipeline = CreateRenderPipeline(); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass.renderPassInfo); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); renderPassEncoder.Draw(3); renderPassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {6, 8}; EXPECT_PIXEL_RGBA8_EQ(RGBA8(5, 6, 255, 255), renderPass.color, 0, 0); EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], mMinUniformBufferOffsetAlignment, expectedData.size()); } // Dynamic offsets are all zero and no effect to result. TEST_P(DynamicBufferOffsetTests, BasicComputePipeline) { wgpu::ComputePipeline pipeline = CreateComputePipeline(); std::array offsets = {0, 0}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(pipeline); computePassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {2, 4}; EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size()); } // Have non-zero dynamic offsets. TEST_P(DynamicBufferOffsetTests, SetDynamicOffsetsComputePipeline) { wgpu::ComputePipeline pipeline = CreateComputePipeline(); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(pipeline); computePassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {6, 8}; EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], mMinUniformBufferOffsetAlignment, expectedData.size()); } // Test inherit dynamic offsets on render pipeline TEST_P(DynamicBufferOffsetTests, InheritDynamicOffsetsRenderPipeline) { // Using default pipeline and setting dynamic offsets wgpu::RenderPipeline pipeline = CreateRenderPipeline(); wgpu::RenderPipeline testPipeline = CreateRenderPipeline(true); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass.renderPassInfo); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); renderPassEncoder.Draw(3); renderPassEncoder.SetPipeline(testPipeline); renderPassEncoder.SetBindGroup(1, mBindGroups[1]); renderPassEncoder.Draw(3); renderPassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {12, 16}; EXPECT_PIXEL_RGBA8_EQ(RGBA8(5, 6, 255, 255), renderPass.color, 0, 0); EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], mMinUniformBufferOffsetAlignment, expectedData.size()); } // Test inherit dynamic offsets on compute pipeline // TODO(shaobo.yan@intel.com) : Try this test on GTX1080 and cannot reproduce the failure. // Suspect it is due to dawn doesn't handle sync between two dispatch and disable this case. // Will double check root cause after got GTX1660. TEST_P(DynamicBufferOffsetTests, InheritDynamicOffsetsComputePipeline) { DAWN_SUPPRESS_TEST_IF(IsWindows()); wgpu::ComputePipeline pipeline = CreateComputePipeline(); wgpu::ComputePipeline testPipeline = CreateComputePipeline(true); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(pipeline); computePassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.SetPipeline(testPipeline); computePassEncoder.SetBindGroup(1, mBindGroups[1]); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {12, 16}; EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], mMinUniformBufferOffsetAlignment, expectedData.size()); } // Setting multiple dynamic offsets for the same bindgroup in one render pass. TEST_P(DynamicBufferOffsetTests, UpdateDynamicOffsetsMultipleTimesRenderPipeline) { // Using default pipeline and setting dynamic offsets wgpu::RenderPipeline pipeline = CreateRenderPipeline(); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; std::array testOffsets = {0, 0}; wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass.renderPassInfo); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); renderPassEncoder.Draw(3); renderPassEncoder.SetBindGroup(0, mBindGroups[0], testOffsets.size(), testOffsets.data()); renderPassEncoder.Draw(3); renderPassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {2, 4}; EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 255, 255), renderPass.color, 0, 0); EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size()); } // Setting multiple dynamic offsets for the same bindgroup in one compute pass. TEST_P(DynamicBufferOffsetTests, UpdateDynamicOffsetsMultipleTimesComputePipeline) { wgpu::ComputePipeline pipeline = CreateComputePipeline(); std::array offsets = {mMinUniformBufferOffsetAlignment, mMinUniformBufferOffsetAlignment}; std::array testOffsets = {0, 0}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(pipeline); computePassEncoder.SetBindGroup(0, mBindGroups[0], offsets.size(), offsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.SetBindGroup(0, mBindGroups[0], testOffsets.size(), testOffsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); std::vector expectedData = {2, 4}; EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size()); } namespace { using ReadBufferUsage = wgpu::BufferUsage; using OOBRead = bool; using OOBWrite = bool; DAWN_TEST_PARAM_STRUCT(ClampedOOBDynamicBufferOffsetParams, ReadBufferUsage, OOBRead, OOBWrite); } // anonymous namespace class ClampedOOBDynamicBufferOffsetTests : public DawnTestWithParams {}; // Test robust buffer access behavior for out of bounds accesses to dynamic buffer bindings. TEST_P(ClampedOOBDynamicBufferOffsetTests, CheckOOBAccess) { static constexpr uint32_t kArrayLength = 10u; // Out-of-bounds access will start halfway into the array and index off the end. static constexpr uint32_t kOOBOffset = kArrayLength / 2; wgpu::BufferBindingType sourceBindingType; switch (GetParam().mReadBufferUsage) { case wgpu::BufferUsage::Uniform: sourceBindingType = wgpu::BufferBindingType::Uniform; break; case wgpu::BufferUsage::Storage: sourceBindingType = wgpu::BufferBindingType::ReadOnlyStorage; break; default: UNREACHABLE(); } wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( device, {{0, wgpu::ShaderStage::Compute, sourceBindingType, true}, {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage, true}}); wgpu::PipelineLayout layout = utils::MakeBasicPipelineLayout(device, &bgl); wgpu::ComputePipeline pipeline; { std::ostringstream shader; shader << "let kArrayLength: u32 = " << kArrayLength << "u;\n"; if (GetParam().mOOBRead) { shader << "let kReadOffset: u32 = " << kOOBOffset << "u;\n"; } else { shader << "let kReadOffset: u32 = 0u;\n"; } if (GetParam().mOOBWrite) { shader << "let kWriteOffset: u32 = " << kOOBOffset << "u;\n"; } else { shader << "let kWriteOffset: u32 = 0u;\n"; } switch (GetParam().mReadBufferUsage) { case wgpu::BufferUsage::Uniform: shader << R"( struct Src { values : array, kArrayLength>; }; [[group(0), binding(0)]] var src : Src; )"; break; case wgpu::BufferUsage::Storage: shader << R"( struct Src { values : array>; }; [[group(0), binding(0)]] var src : Src; )"; break; default: UNREACHABLE(); } shader << R"( struct Dst { values : array>; }; [[group(0), binding(1)]] var dst : Dst; )"; shader << R"( [[stage(compute), workgroup_size(1)]] fn main() { for (var i: u32 = 0u; i < kArrayLength; i = i + 1u) { dst.values[i + kWriteOffset] = src.values[i + kReadOffset]; } } )"; wgpu::ComputePipelineDescriptor pipelineDesc; pipelineDesc.layout = layout; pipelineDesc.compute.module = utils::CreateShaderModule(device, shader.str().c_str()); pipelineDesc.compute.entryPoint = "main"; pipeline = device.CreateComputePipeline(&pipelineDesc); } uint32_t minUniformBufferOffsetAlignment = GetSupportedLimits().limits.minUniformBufferOffsetAlignment; uint32_t minStorageBufferOffsetAlignment = GetSupportedLimits().limits.minStorageBufferOffsetAlignment; uint32_t arrayByteLength = kArrayLength * 4 * sizeof(uint32_t); uint32_t uniformBufferOffset = Align(arrayByteLength, minUniformBufferOffsetAlignment); uint32_t storageBufferOffset = Align(arrayByteLength, minStorageBufferOffsetAlignment); // Enough space to bind at a dynamic offset. uint32_t uniformBufferSize = uniformBufferOffset + arrayByteLength; uint32_t storageBufferSize = storageBufferOffset + arrayByteLength; // Buffers are padded so we can check that bytes after the bound range are not changed. static constexpr uint32_t kEndPadding = 16; uint64_t srcBufferSize; uint32_t srcBufferByteOffset; uint32_t dstBufferByteOffset = storageBufferOffset; uint64_t dstBufferSize = storageBufferSize + kEndPadding; switch (GetParam().mReadBufferUsage) { case wgpu::BufferUsage::Uniform: srcBufferSize = uniformBufferSize + kEndPadding; srcBufferByteOffset = uniformBufferOffset; break; case wgpu::BufferUsage::Storage: srcBufferSize = storageBufferSize + kEndPadding; srcBufferByteOffset = storageBufferOffset; break; default: UNREACHABLE(); } std::vector srcData(srcBufferSize / sizeof(uint32_t)); std::vector expectedDst(dstBufferSize / sizeof(uint32_t)); // Fill the src buffer with 0, 1, 2, ... std::iota(srcData.begin(), srcData.end(), 0); wgpu::Buffer src = utils::CreateBufferFromData(device, &srcData[0], srcBufferSize, GetParam().mReadBufferUsage); // Fill the dst buffer with 0xFF. memset(expectedDst.data(), 0xFF, dstBufferSize); wgpu::Buffer dst = utils::CreateBufferFromData(device, &expectedDst[0], dstBufferSize, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); // Produce expected data assuming the implementation performs clamping. for (uint32_t i = 0; i < kArrayLength; ++i) { uint32_t readIndex = GetParam().mOOBRead ? std::min(kOOBOffset + i, kArrayLength - 1) : i; uint32_t writeIndex = GetParam().mOOBWrite ? std::min(kOOBOffset + i, kArrayLength - 1) : i; for (uint32_t c = 0; c < 4; ++c) { uint32_t value = srcData[srcBufferByteOffset / 4 + 4 * readIndex + c]; expectedDst[dstBufferByteOffset / 4 + 4 * writeIndex + c] = value; } } std::array dynamicOffsets = {srcBufferByteOffset, dstBufferByteOffset}; wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, { {0, src, 0, arrayByteLength}, {1, dst, 0, arrayByteLength}, }); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass(); computePassEncoder.SetPipeline(pipeline); computePassEncoder.SetBindGroup(0, bindGroup, dynamicOffsets.size(), dynamicOffsets.data()); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); EXPECT_BUFFER_U32_RANGE_EQ(expectedDst.data(), dst, 0, dstBufferSize / sizeof(uint32_t)); } DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests, D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()); // Only instantiate on D3D12 / Metal where we are sure of the robustness implementation. // Tint injects clamping in the shader. OpenGL(ES) / Vulkan robustness is less constrained. DAWN_INSTANTIATE_TEST_P(ClampedOOBDynamicBufferOffsetTests, {D3D12Backend(), MetalBackend()}, {wgpu::BufferUsage::Uniform, wgpu::BufferUsage::Storage}, {false, true}, {false, true});