| // 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 <string> | |
| #include <vector> | |
| #include "src/dawn/common/Constants.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 { | |
| // Storage buffer size in bytes. | |
| enum class Size { Small = 64u, Medium = 256u, Large = 512u }; | |
| // Test fixture specifically for testing the interaction between immediate data (SetImmediates) | |
| // and storage buffer length tracking. This is critical to ensure that when both immediate | |
| // constants and buffer length data are uploaded to the same GPU buffer, they don't interfere | |
| // with each other across multiple Apply calls with changing pipeline/bind group state. | |
| class ImmediateDataBufferLengthTest : public DawnTest { | |
| protected: | |
| void GetRequiredLimits(const dawn::utils::ComboLimits& supported, | |
| dawn::utils::ComboLimits& required) override { | |
| // Skip compat backends | |
| // TODO(crbug.com/458102548): Remove when implemented for GLES. | |
| DAWN_TEST_UNSUPPORTED_IF(supported.maxImmediateSize == 0); | |
| } | |
| void SetUp() override { | |
| DawnTest::SetUp(); | |
| // Create a result buffer. | |
| // Maximum: 2 draw calls, 3 storage buffers and 4 immediates. | |
| wgpu::BufferDescriptor bufferDesc; | |
| bufferDesc.size = kMaxExecutionTime * kResultSizeInExpectation * kImmediateElementByteSize; | |
| bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; | |
| mResultBuffer = device.CreateBuffer(&bufferDesc); | |
| } | |
| // Helper to create bind groups with different buffer configurations | |
| // Creates storage buffers of specified sizes and binds them to a bind group. | |
| // This tests buffer length tracking by creating buffers with known sizes that | |
| // can be queried in shaders using arrayLength(). | |
| wgpu::BindGroup MakeBindGroupWithBuffers(wgpu::BindGroupLayout layout, | |
| const std::vector<Size>& bufferSizes) { | |
| const uint32_t bufferCount = bufferSizes.size(); | |
| wgpu::BufferDescriptor bufferDesc; | |
| bufferDesc.usage = wgpu::BufferUsage::Storage; | |
| std::vector<wgpu::BindGroupEntry> entries; | |
| wgpu::BindGroupEntry entry; | |
| for (uint32_t i = 0; i < bufferCount; ++i) { | |
| bufferDesc.size = static_cast<uint32_t>(bufferSizes[i]); | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc); | |
| mStorageBuffers.push_back(buffer); | |
| entry.binding = i; | |
| entry.buffer = buffer; | |
| entries.push_back(entry); | |
| } | |
| wgpu::BindGroupDescriptor bindGroupDesc; | |
| bindGroupDesc.layout = layout; | |
| bindGroupDesc.entryCount = entries.size(); | |
| bindGroupDesc.entries = entries.data(); | |
| return device.CreateBindGroup(&bindGroupDesc); | |
| } | |
| // Helper to create unified shader with compute, vertex, and fragment entry points | |
| // This shader contains arrayLength() calls to query storage buffer sizes and | |
| // accesses immediates. Both data types are written to a shared result buffer | |
| // to verify they don't interfere with each other during GPU uploads. | |
| wgpu::ShaderModule CreateUnifiedShaderModule(uint32_t immediateCount, uint32_t bufferCount) { | |
| std::string bufferDeclarations; | |
| std::string lengthComputation; | |
| for (uint32_t i = 0; i < bufferCount; i++) { | |
| bufferDeclarations += " @group(0) @binding(" + std::to_string(i) + | |
| ") var<storage, read> buffer" + std::to_string(i) + | |
| " : array<f32>;\n"; | |
| lengthComputation += " result[resultIndex].bufferLengths[" + std::to_string(i) + | |
| "] = arrayLength(&buffer" + std::to_string(i) + ");\n"; | |
| } | |
| std::string immediateStruct = "struct ImmediateData {\n"; | |
| immediateStruct += " resultIndex : u32,\n"; // Add result index as first field | |
| for (uint32_t i = 0; i < immediateCount; i++) { | |
| immediateStruct += " value" + std::to_string(i) + " : u32,\n"; | |
| } | |
| immediateStruct += " }\n"; | |
| std::string immediateAccess; | |
| for (uint32_t i = 0; i < immediateCount; i++) { | |
| immediateAccess += " result[resultIndex].immediates[" + std::to_string(i) + | |
| "] = immediateData.value" + std::to_string(i) + ";\n"; | |
| } | |
| // Unified shader with all entry points | |
| std::string shader = R"( | |
| struct ResultData { | |
| bufferLengths : array<u32, 3>, | |
| immediates : array<u32, 4>, | |
| } | |
| @group(1) @binding(0) var<storage, read_write> result : array<ResultData, 2>; | |
| )" + bufferDeclarations + immediateStruct + | |
| R"( | |
| var<immediate> immediateData: ImmediateData; | |
| // Shared function for buffer length and immediate data processing | |
| fn processData() { | |
| let resultIndex = immediateData.resultIndex; | |
| )" + lengthComputation + immediateAccess + | |
| R"( | |
| } | |
| // Compute entry point | |
| @compute @workgroup_size(1) fn csMain() { | |
| processData(); | |
| } | |
| // Vertex entry point | |
| @vertex fn vsMain() -> @builtin(position) vec4f { | |
| return vec4f(0.0, 0.0, 0.0, 1.0); | |
| } | |
| // Fragment entry point | |
| @fragment fn fsMain() -> @location(0) vec4f { | |
| processData(); | |
| return vec4f(1.0, 0.0, 0.0, 1.0); | |
| } | |
| )"; | |
| return utils::CreateShaderModule(device, shader.c_str()); | |
| } | |
| // Helper to create a bind group layout with storage buffers for any shader stage | |
| // Creates layouts with 1-3 storage buffers that can be used by both compute and fragment | |
| // shaders. This enables testing buffer length tracking across different pipeline types. | |
| wgpu::BindGroupLayout MakeStorageBindGroupLayout(uint32_t bufferCount) { | |
| switch (bufferCount) { | |
| case 1u: | |
| return utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}}); | |
| case 2u: | |
| return utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}, | |
| {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}}); | |
| case 3u: | |
| return utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}, | |
| {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}, | |
| {2, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::ReadOnlyStorage}}); | |
| default: | |
| DAWN_UNREACHABLE(); | |
| return {}; | |
| } | |
| } | |
| // Helper to create unified result bind group (for both compute and render) | |
| // The result buffer stores both buffer length data and immediate data from shader execution. | |
| // Layout: [bufferLength0, bufferLength1, bufferLength2, immediate0, immediate1, immediate2, | |
| // immediate3] This unified approach allows both compute and render pipelines to write results | |
| // to the same buffer. | |
| wgpu::BindGroup MakeResultBindGroup() { | |
| wgpu::BindGroupLayout resultLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::Storage}}); | |
| return utils::MakeBindGroup(device, resultLayout, {{0, mResultBuffer}}); | |
| } | |
| // Creates a pipeline layout that supports both immediate data and storage buffer binding. | |
| // The immediate size calculation includes space for: | |
| // - User immediate data (immediateCount values) | |
| // - One additional slot for draw/dispatch index tracking | |
| // This layout is used by both compute and render pipelines in the tests. | |
| wgpu::PipelineLayout CreatePipelineLayout(uint32_t immediateCount, uint32_t bufferCount) { | |
| wgpu::BindGroupLayout storageLayout = MakeStorageBindGroupLayout(bufferCount); | |
| wgpu::BindGroupLayout resultLayout = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment, | |
| wgpu::BufferBindingType::Storage}}); | |
| wgpu::PipelineLayoutDescriptor plDesc; | |
| wgpu::BindGroupLayout layouts[] = {storageLayout, resultLayout}; | |
| plDesc.bindGroupLayoutCount = 2; | |
| plDesc.bindGroupLayouts = layouts; | |
| // Calculate exact size needed and +1 for drawIndex/dispatchIndex parameter. | |
| plDesc.immediateSize = (immediateCount + 1) * kImmediateElementByteSize; | |
| return device.CreatePipelineLayout(&plDesc); | |
| } | |
| template <typename Pass> | |
| void SetStorageBuffer(Pass pass, const std::vector<Size>& bufferSizes) { | |
| const uint32_t bufferCount = bufferSizes.size(); | |
| pass.SetBindGroup( | |
| 0, MakeBindGroupWithBuffers(MakeStorageBindGroupLayout(bufferCount), bufferSizes)); | |
| for (uint32_t i = 0; i < bufferCount; ++i) { | |
| mCurrentBufferLengths[i] = static_cast<uint32_t>(bufferSizes[i]) / sizeof(uint32_t); | |
| } | |
| } | |
| template <typename Pass> | |
| void SetImmediates(Pass pass, const std::vector<uint32_t>& immediates) { | |
| std::vector<uint32_t> immediateData; | |
| immediateData.push_back(resultIndex); | |
| immediateData.insert(immediateData.end(), immediates.begin(), immediates.end()); | |
| pass.SetImmediates(0, reinterpret_cast<uint8_t*>(immediateData.data()), | |
| immediateData.size() * sizeof(uint32_t)); | |
| for (uint32_t i = 0; i < immediates.size(); ++i) { | |
| mCurrentImmediateData[i] = immediates[i]; | |
| } | |
| } | |
| // Records expected results for validation after GPU execution. | |
| void OnOperationWritingExpected() { | |
| uint32_t startOffset = resultIndex * kResultSizeInExpectation; | |
| for (uint32_t i = 0; i < mCurrentPipelineStorageBufferCount; ++i) { | |
| expectedResults[startOffset + i] = mCurrentBufferLengths[i]; | |
| } | |
| startOffset += kMaxStorageBufferCount; | |
| for (uint32_t i = 0; i < mCurrentPipelineImmediateDataCount; ++i) { | |
| expectedResults[startOffset + i] = mCurrentImmediateData[i]; | |
| } | |
| resultIndex++; | |
| } | |
| static constexpr size_t kMaxExecutionTime = 2u; | |
| static constexpr size_t kMaxImmediateCount = 4u; | |
| static constexpr size_t kMaxStorageBufferCount = 3u; | |
| static constexpr size_t kResultSizeInExpectation = kMaxImmediateCount + kMaxStorageBufferCount; | |
| std::vector<wgpu::Buffer> mStorageBuffers; | |
| wgpu::Buffer mResultBuffer; | |
| // Test state tracking - maintains current pipeline configuration and expected results | |
| std::array<uint32_t, kMaxStorageBufferCount> mCurrentBufferLengths = {}; | |
| std::array<uint32_t, kMaxImmediateCount> mCurrentImmediateData = {}; | |
| uint32_t mCurrentPipelineStorageBufferCount = 0; | |
| uint32_t mCurrentPipelineImmediateDataCount = 0; | |
| std::array<uint32_t, kMaxExecutionTime * kResultSizeInExpectation> expectedResults = {}; | |
| uint32_t resultIndex = 0; | |
| }; | |
| class ImmediateDataBufferLengthComputePipelineTest : public ImmediateDataBufferLengthTest { | |
| protected: | |
| // Helper to create compute pipelines with different numbers of immediates and storage | |
| // buffers | |
| wgpu::ComputePipeline CreateComputePipeline(uint32_t immediateCount, uint32_t bufferCount) { | |
| wgpu::ComputePipelineDescriptor pipelineDesc; | |
| pipelineDesc.layout = CreatePipelineLayout(immediateCount, bufferCount); | |
| pipelineDesc.compute.module = CreateUnifiedShaderModule(immediateCount, bufferCount); | |
| return device.CreateComputePipeline(&pipelineDesc); | |
| } | |
| void UseComputePipeline(wgpu::ComputePassEncoder pass, | |
| uint32_t immediateCount, | |
| uint32_t bufferCount) { | |
| mCurrentPipelineStorageBufferCount = bufferCount; | |
| mCurrentPipelineImmediateDataCount = immediateCount; | |
| pass.SetPipeline(CreateComputePipeline(immediateCount, bufferCount)); | |
| } | |
| void Dispatch(wgpu::ComputePassEncoder pass) { | |
| if (resultIndex == 0) { | |
| wgpu::BindGroup resultBindGroup = MakeResultBindGroup(); | |
| pass.SetBindGroup(1, resultBindGroup); | |
| } | |
| pass.DispatchWorkgroups(1); | |
| OnOperationWritingExpected(); | |
| } | |
| void CheckExpectedResults() { | |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedResults.data(), mResultBuffer, 0, expectedResults.size()) | |
| << "dispatch index: " << std::to_string(resultIndex); | |
| } | |
| }; | |
| class ImmediateDataBufferLengthRenderPipelineTest : public ImmediateDataBufferLengthTest { | |
| protected: | |
| void SetUp() override { | |
| ImmediateDataBufferLengthTest::SetUp(); | |
| // Create render target for render pipeline tests | |
| wgpu::TextureDescriptor textureDesc; | |
| textureDesc.size = {1, 1, 1}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment; | |
| mRenderTarget = device.CreateTexture(&textureDesc); | |
| mRenderTargetView = mRenderTarget.CreateView(); | |
| } | |
| // Helper to create render pipelines with different numbers of immediates and storage | |
| // buffers | |
| wgpu::RenderPipeline CreateRenderPipeline(uint32_t immediateCount, uint32_t bufferCount) { | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.layout = CreatePipelineLayout(immediateCount, bufferCount); | |
| wgpu::ShaderModule module = CreateUnifiedShaderModule(immediateCount, bufferCount); | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| pipelineDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| return device.CreateRenderPipeline(&pipelineDesc); | |
| } | |
| void UseRenderPipeline(wgpu::RenderPassEncoder pass, | |
| uint32_t immediateCount, | |
| uint32_t bufferCount) { | |
| mCurrentPipelineStorageBufferCount = bufferCount; | |
| mCurrentPipelineImmediateDataCount = immediateCount; | |
| pass.SetPipeline(CreateRenderPipeline(immediateCount, bufferCount)); | |
| } | |
| void Draw(wgpu::RenderPassEncoder pass) { | |
| if (resultIndex == 0) { | |
| wgpu::BindGroup resultBindGroup = MakeResultBindGroup(); | |
| pass.SetBindGroup(1, resultBindGroup); | |
| } | |
| pass.Draw(1); | |
| OnOperationWritingExpected(); | |
| } | |
| void CheckExpectedResults() { | |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedResults.data(), mResultBuffer, 0, expectedResults.size()) | |
| << "draw index: " << std::to_string(resultIndex); | |
| } | |
| wgpu::Texture mRenderTarget; | |
| wgpu::TextureView mRenderTargetView; | |
| }; | |
| // Test: SetBindGroup + SetImmediates + Draw | |
| // This is the basic case to ensure immediate data and buffer lengths work together | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, BasicImmediateAndBufferLength) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| UseComputePipeline(pass, 3u, 3u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium, Size::Large}); | |
| SetImmediates(pass, {42, 123, 456}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: SetBindGroup + SetImmediates + Draw + SetPipeline (different immediates) + Draw | |
| // This tests that changing pipelines with different immediate counts still preserves buffer lengths | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, PipelineChangeWithDifferentImmediates) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| // First draw with pipeline1 - writes to index 0 | |
| UseComputePipeline(pass, 1u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {100}); | |
| Dispatch(pass); | |
| // Change to pipeline2 with more immediates | |
| UseComputePipeline(pass, 2u, 2u); | |
| SetImmediates(pass, {300, 400}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Compute pipeline change with same layout but different immediate counts | |
| // This tests a potentially problematic case where pipelines have the same bind group layout | |
| // but different immediate data sizes. This might expose issues with immediate data validation. | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, PipelineChangeWithReducedImmediateCounts) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| // First dispatch with pipeline1 (3 immediates) - writes to index 0 | |
| UseComputePipeline(pass, 3u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {100, 200, 300}); | |
| Dispatch(pass); | |
| // Second dispatch with pipeline2 (2 immediates, same layout) - writes to index 1 | |
| UseComputePipeline(pass, 2u, 2u); | |
| SetImmediates(pass, {500, 600}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: SetBindGroup + SetImmediates + Draw + SetPipeline (different buffer count) + | |
| // SetBindGroup + Draw This tests changing both pipeline and bind group, ensuring buffer length | |
| // tracking adapts correctly | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, PipelineAndBindGroupChange) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| // First draw with 2 buffers | |
| UseComputePipeline(pass, 2u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {111, 222}); | |
| Dispatch(pass); | |
| // Change to pipeline with 3 buffers and update bind group | |
| UseComputePipeline(pass, 2u, 3u); | |
| SetStorageBuffer(pass, {Size::Large, Size::Small, Size::Medium}); | |
| SetImmediates(pass, {333, 444}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Multiple SetImmediates calls with buffer length updates | |
| // This tests the edge case where immediate data is updated multiple times | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, MultipleImmediateDataUpdates) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| UseComputePipeline(pass, 3u, 1u); | |
| SetStorageBuffer(pass, {Size::Small}); | |
| SetImmediates(pass, {10, 20, 30}); | |
| SetImmediates(pass, {40, 50, 60}); | |
| SetImmediates(pass, {70, 80, 90}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Interleaved SetBindGroup and SetImmediates calls | |
| // This tests complex state management scenarios | |
| TEST_P(ImmediateDataBufferLengthComputePipelineTest, InterleavedUpdates) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| // Interleave bind group and immediate data updates | |
| UseComputePipeline(pass, 2u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {1000, 2000}); | |
| Dispatch(pass); | |
| SetStorageBuffer(pass, {Size::Medium, Size::Large}); | |
| SetImmediates(pass, {3000, 4000}); | |
| Dispatch(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| DAWN_INSTANTIATE_TEST(ImmediateDataBufferLengthComputePipelineTest, | |
| D3D11Backend(), | |
| D3D12Backend(), | |
| MetalBackend(), | |
| OpenGLBackend(), | |
| OpenGLESBackend(), | |
| VulkanBackend(), | |
| WebGPUBackend()); | |
| // Test: Render pipeline with SetBindGroup + SetImmediates + Draw | |
| // This tests the basic case for render pipelines | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, RenderPipelineBasicImmediateAndBufferLength) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| UseRenderPipeline(pass, 3u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {789, 987, 654}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Render pipeline with multiple draws and pipeline changes (updated to use single result | |
| // buffer) | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, PipelineChangeWithIncreasedImmediates) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| // First draw with pipeline1 | |
| UseRenderPipeline(pass, 2u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {1100, 1200}); | |
| Draw(pass); | |
| // Second draw with pipeline2 | |
| UseRenderPipeline(pass, 3u, 2u); | |
| SetImmediates(pass, {1300, 1400, 1500}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Pipeline change with same layout but different immediate counts | |
| // This tests a potentially problematic case where pipelines have the same bind group layout | |
| // but different immediate data sizes. This might expose issues with immediate data validation. | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, PipelineChangeWithReducedImmediateCounts) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| // First draw with pipeline1 (3 immediates) - writes to index 0 | |
| UseRenderPipeline(pass, 3u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {1000, 2000, 3000}); | |
| Draw(pass); | |
| // Second draw with pipeline2 (2 immediates, same layout) - writes to index 1 | |
| UseRenderPipeline(pass, 2u, 2u); | |
| SetImmediates(pass, {5000, 6000}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Render pipeline with buffer count changes (updated to use single result buffer) | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, PipelineAndBindGroupChange) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| // First draw with 2 buffers | |
| UseRenderPipeline(pass, 2u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {2111, 2222}); | |
| Draw(pass); | |
| // Second draw with 3 buffers - writes to index 1 | |
| UseRenderPipeline(pass, 2u, 3u); | |
| SetStorageBuffer(pass, {Size::Large, Size::Small, Size::Medium}); | |
| SetImmediates(pass, {2333, 2444}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Render pipeline with multiple immediate data updates | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, MultipleImmediateDataUpdates) { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| UseRenderPipeline(pass, 3u, 1u); | |
| SetStorageBuffer(pass, {Size::Small}); | |
| SetImmediates(pass, {3010, 3020, 3030}); | |
| SetImmediates(pass, {3040, 3050, 3060}); | |
| SetImmediates(pass, {3070, 3080, 3090}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| // Test: Render pipeline with interleaved updates | |
| TEST_P(ImmediateDataBufferLengthRenderPipelineTest, InterleavedUpdates) { | |
| // TODO(crbug.com/366291600): Android bots failed with incorrect arrayLength value in second | |
| // dispatch. Should be Size::Medium, Size::Large but got Size::Small, Size::Medium. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid()); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPassDesc({mRenderTargetView}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); | |
| UseRenderPipeline(pass, 2u, 2u); | |
| SetStorageBuffer(pass, {Size::Small, Size::Medium}); | |
| SetImmediates(pass, {4000, 5000}); | |
| Draw(pass); | |
| SetStorageBuffer(pass, {Size::Medium, Size::Large}); | |
| SetImmediates(pass, {6000, 7000}); | |
| Draw(pass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| CheckExpectedResults(); | |
| } | |
| DAWN_INSTANTIATE_TEST(ImmediateDataBufferLengthRenderPipelineTest, | |
| D3D11Backend(), | |
| D3D12Backend(), | |
| MetalBackend(), | |
| OpenGLBackend(), | |
| OpenGLESBackend(), | |
| VulkanBackend(), | |
| WebGPUBackend()); | |
| } // anonymous namespace | |
| } // namespace dawn |