blob: 6249a05506bb09d2548ad8548db0aecdfe97aee4 [file] [log] [blame] [edit]
// 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 "dawn/common/Assert.h"
#include "dawn/common/Constants.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.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 * kImmediateConstantElementByteSize;
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 immediate constants. 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) * kImmediateConstantElementByteSize;
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 uint32_t kMaxExecutionTime = 2u;
static constexpr uint32_t kMaxImmediateCount = 4u;
static constexpr uint32_t kMaxStorageBufferCount = 3u;
static constexpr uint32_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 immediate constants 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 immediate constants 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