dawn-cmake/src/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
Brandon Jones 0d50a2c770 ComputePipelineDescriptor.computeStage->compute
Deprecates the computeStage member of the descriptor in favor of compute
as described by the spec. In order to support both variants without
breaking backwards compatibility some code had to be manually added to
the wire client to copy from the deprecated member to the new one and
visa versa.

Change-Id: I9d5c2fc9c446c927c5792c9af9ed56c90060b65b
Bug: dawn:800
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/53884
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
2021-06-09 18:07:32 +00:00

591 lines
27 KiB
C++

// Copyright 2020 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/unittests/validation/ValidationTest.h"
#include "common/Assert.h"
#include "common/Constants.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
namespace {
// Helper for describing bindings throughout the tests
struct BindingDescriptor {
uint32_t group;
uint32_t binding;
std::string decl;
std::string ref_type;
std::string ref_mem;
uint64_t size;
wgpu::BufferBindingType type = wgpu::BufferBindingType::Storage;
wgpu::ShaderStage visibility = wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment;
};
// Runs |func| with a modified version of |originalSizes| as an argument, adding |offset| to
// each element one at a time This is useful to verify some behavior happens if any element is
// offset from original
template <typename F>
void WithEachSizeOffsetBy(int64_t offset, const std::vector<uint64_t>& originalSizes, F func) {
std::vector<uint64_t> modifiedSizes = originalSizes;
for (size_t i = 0; i < originalSizes.size(); ++i) {
if (offset < 0) {
ASSERT(originalSizes[i] >= static_cast<uint64_t>(-offset));
}
// Run the function with an element offset, and restore element afterwards
modifiedSizes[i] += offset;
func(modifiedSizes);
modifiedSizes[i] -= offset;
}
}
// Runs |func| with |correctSizes|, and an expectation of success and failure
template <typename F>
void CheckSizeBounds(const std::vector<uint64_t>& correctSizes, F func) {
// To validate size:
// Check invalid with bind group with one less
// Check valid with bind group with correct size
// Make sure (every size - 1) produces an error
WithEachSizeOffsetBy(-1, correctSizes,
[&](const std::vector<uint64_t>& sizes) { func(sizes, false); });
// Make sure correct sizes work
func(correctSizes, true);
// Make sure (every size + 1) works
WithEachSizeOffsetBy(1, correctSizes,
[&](const std::vector<uint64_t>& sizes) { func(sizes, true); });
}
// Creates a bind group with given bindings for shader text
std::string GenerateBindingString(const std::vector<BindingDescriptor>& bindings) {
std::ostringstream ostream;
size_t index = 0;
for (const BindingDescriptor& b : bindings) {
ostream << "[[block]] struct S" << index << " { " << b.decl << "};\n";
ostream << "[[group(" << b.group << "), binding(" << b.binding << ")]] ";
switch (b.type) {
case wgpu::BufferBindingType::Uniform:
ostream << "var<uniform> b" << index << " : S" << index << ";\n";
break;
case wgpu::BufferBindingType::Storage:
ostream << "var<storage, read_write> b" << index << " : S" << index << ";\n";
break;
case wgpu::BufferBindingType::ReadOnlyStorage:
ostream << "var<storage, read> b" << index << " : S" << index << ";\n";
break;
default:
UNREACHABLE();
}
index++;
}
return ostream.str();
}
std::string GenerateReferenceString(const std::vector<BindingDescriptor>& bindings,
wgpu::ShaderStage stage) {
std::ostringstream ostream;
size_t index = 0;
for (const BindingDescriptor& b : bindings) {
if (b.visibility & stage) {
if (!b.ref_type.empty() && !b.ref_mem.empty()) {
ostream << "var r" << index << " : " << b.ref_type << " = b" << index << "."
<< b.ref_mem << ";\n";
}
}
index++;
}
return ostream.str();
}
// Used for adding custom types available throughout the tests
static const std::string kStructs = "struct ThreeFloats {f1 : f32; f2 : f32; f3 : f32;};\n";
// Creates a compute shader with given bindings
std::string CreateComputeShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
return kStructs + GenerateBindingString(bindings) +
"[[stage(compute), workgroup_size(1,1,1)]] fn main() {\n" +
GenerateReferenceString(bindings, wgpu::ShaderStage::Compute) + "}";
}
// Creates a vertex shader with given bindings
std::string CreateVertexShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
return kStructs + GenerateBindingString(bindings) +
"[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {\n" +
GenerateReferenceString(bindings, wgpu::ShaderStage::Vertex) +
"\n return vec4<f32>(); " + "}";
}
// Creates a fragment shader with given bindings
std::string CreateFragmentShaderWithBindings(const std::vector<BindingDescriptor>& bindings) {
return kStructs + GenerateBindingString(bindings) + "[[stage(fragment)]] fn main() {\n" +
GenerateReferenceString(bindings, wgpu::ShaderStage::Fragment) + "}";
}
// Concatenates vectors containing BindingDescriptor
std::vector<BindingDescriptor> CombineBindings(
std::initializer_list<std::vector<BindingDescriptor>> bindings) {
std::vector<BindingDescriptor> result;
for (const std::vector<BindingDescriptor>& b : bindings) {
result.insert(result.end(), b.begin(), b.end());
}
return result;
}
} // namespace
class MinBufferSizeTestsBase : public ValidationTest {
public:
void SetUp() override {
ValidationTest::SetUp();
}
wgpu::Buffer CreateBuffer(uint64_t bufferSize, wgpu::BufferUsage usage) {
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = bufferSize;
bufferDescriptor.usage = usage;
return device.CreateBuffer(&bufferDescriptor);
}
// Creates compute pipeline given a layout and shader
wgpu::ComputePipeline CreateComputePipeline(const std::vector<wgpu::BindGroupLayout>& layouts,
const std::string& shader) {
wgpu::ShaderModule csModule = utils::CreateShaderModule(device, shader.c_str());
wgpu::ComputePipelineDescriptor csDesc;
csDesc.layout = nullptr;
if (!layouts.empty()) {
wgpu::PipelineLayoutDescriptor descriptor;
descriptor.bindGroupLayoutCount = layouts.size();
descriptor.bindGroupLayouts = layouts.data();
csDesc.layout = device.CreatePipelineLayout(&descriptor);
}
csDesc.compute.module = csModule;
csDesc.compute.entryPoint = "main";
return device.CreateComputePipeline(&csDesc);
}
// Creates compute pipeline with default layout
wgpu::ComputePipeline CreateComputePipelineWithDefaultLayout(const std::string& shader) {
return CreateComputePipeline({}, shader);
}
// Creates render pipeline give na layout and shaders
wgpu::RenderPipeline CreateRenderPipeline(const std::vector<wgpu::BindGroupLayout>& layouts,
const std::string& vertexShader,
const std::string& fragShader) {
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, vertexShader.c_str());
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fragShader.c_str());
utils::ComboRenderPipelineDescriptor pipelineDescriptor;
pipelineDescriptor.vertex.module = vsModule;
pipelineDescriptor.cFragment.module = fsModule;
pipelineDescriptor.layout = nullptr;
if (!layouts.empty()) {
wgpu::PipelineLayoutDescriptor descriptor;
descriptor.bindGroupLayoutCount = layouts.size();
descriptor.bindGroupLayouts = layouts.data();
pipelineDescriptor.layout = device.CreatePipelineLayout(&descriptor);
}
return device.CreateRenderPipeline(&pipelineDescriptor);
}
// Creates render pipeline with default layout
wgpu::RenderPipeline CreateRenderPipelineWithDefaultLayout(const std::string& vertexShader,
const std::string& fragShader) {
return CreateRenderPipeline({}, vertexShader, fragShader);
}
// Creates bind group layout with given minimum sizes for each binding
wgpu::BindGroupLayout CreateBindGroupLayout(const std::vector<BindingDescriptor>& bindings,
const std::vector<uint64_t>& minimumSizes) {
ASSERT(bindings.size() == minimumSizes.size());
std::vector<wgpu::BindGroupLayoutEntry> entries;
for (size_t i = 0; i < bindings.size(); ++i) {
const BindingDescriptor& b = bindings[i];
wgpu::BindGroupLayoutEntry e = {};
e.binding = b.binding;
e.visibility = b.visibility;
e.buffer.type = b.type;
e.buffer.minBindingSize = minimumSizes[i];
entries.push_back(e);
}
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.entryCount = static_cast<uint32_t>(entries.size());
descriptor.entries = entries.data();
return device.CreateBindGroupLayout(&descriptor);
}
// Extract the first bind group from a compute shader
wgpu::BindGroupLayout GetBGLFromComputeShader(const std::string& shader, uint32_t index) {
wgpu::ComputePipeline pipeline = CreateComputePipelineWithDefaultLayout(shader);
return pipeline.GetBindGroupLayout(index);
}
// Extract the first bind group from a render pass
wgpu::BindGroupLayout GetBGLFromRenderShaders(const std::string& vertexShader,
const std::string& fragShader,
uint32_t index) {
wgpu::RenderPipeline pipeline =
CreateRenderPipelineWithDefaultLayout(vertexShader, fragShader);
return pipeline.GetBindGroupLayout(index);
}
// Create a bind group with given binding sizes for each entry (backed by the same buffer)
wgpu::BindGroup CreateBindGroup(wgpu::BindGroupLayout layout,
const std::vector<BindingDescriptor>& bindings,
const std::vector<uint64_t>& bindingSizes) {
ASSERT(bindings.size() == bindingSizes.size());
wgpu::Buffer buffer =
CreateBuffer(1024, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage);
std::vector<wgpu::BindGroupEntry> entries;
entries.reserve(bindingSizes.size());
for (uint32_t i = 0; i < bindingSizes.size(); ++i) {
wgpu::BindGroupEntry entry = {};
entry.binding = bindings[i].binding;
entry.buffer = buffer;
ASSERT(bindingSizes[i] < 1024);
entry.size = bindingSizes[i];
entries.push_back(entry);
}
wgpu::BindGroupDescriptor descriptor;
descriptor.layout = layout;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
return device.CreateBindGroup(&descriptor);
}
// Runs a single dispatch with given pipeline and bind group (to test lazy validation during
// dispatch)
void TestDispatch(const wgpu::ComputePipeline& computePipeline,
const std::vector<wgpu::BindGroup>& bindGroups,
bool expectation) {
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
computePassEncoder.SetPipeline(computePipeline);
for (size_t i = 0; i < bindGroups.size(); ++i) {
computePassEncoder.SetBindGroup(i, bindGroups[i]);
}
computePassEncoder.Dispatch(1);
computePassEncoder.EndPass();
if (!expectation) {
ASSERT_DEVICE_ERROR(commandEncoder.Finish());
} else {
commandEncoder.Finish();
}
}
// Runs a single draw with given pipeline and bind group (to test lazy validation during draw)
void TestDraw(const wgpu::RenderPipeline& renderPipeline,
const std::vector<wgpu::BindGroup>& bindGroups,
bool expectation) {
DummyRenderPass renderPass(device);
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
renderPassEncoder.SetPipeline(renderPipeline);
for (size_t i = 0; i < bindGroups.size(); ++i) {
renderPassEncoder.SetBindGroup(i, bindGroups[i]);
}
renderPassEncoder.Draw(3);
renderPassEncoder.EndPass();
if (!expectation) {
ASSERT_DEVICE_ERROR(commandEncoder.Finish());
} else {
commandEncoder.Finish();
}
}
};
// The check between BGL and pipeline at pipeline creation time
class MinBufferSizePipelineCreationTests : public MinBufferSizeTestsBase {};
// Pipeline can be created if minimum buffer size in layout is specified as 0
TEST_F(MinBufferSizePipelineCreationTests, ZeroMinBufferSize) {
std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0});
CreateRenderPipeline({layout}, vertexShader, fragShader);
CreateComputePipeline({layout}, computeShader);
}
// Fail if layout given has non-zero minimum sizes smaller than shader requirements
TEST_F(MinBufferSizePipelineCreationTests, LayoutSizesTooSmall) {
std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, sizes);
if (expectation) {
CreateRenderPipeline({layout}, vertexShader, fragShader);
CreateComputePipeline({layout}, computeShader);
} else {
ASSERT_DEVICE_ERROR(CreateRenderPipeline({layout}, vertexShader, fragShader));
ASSERT_DEVICE_ERROR(CreateComputePipeline({layout}, computeShader));
}
});
}
// Fail if layout given has non-zero minimum sizes smaller than shader requirements
TEST_F(MinBufferSizePipelineCreationTests, LayoutSizesTooSmallMultipleGroups) {
std::vector<BindingDescriptor> bg0Bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
std::vector<BindingDescriptor> bg1Bindings = {
{1, 0, "d : f32; e : f32; f : f32;", "f32", "e", 12},
{1, 1, "g : mat2x2<f32>;", "mat2x2<f32>", "g", 16}};
std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
CheckSizeBounds({8, 4, 12, 16}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
wgpu::BindGroupLayout layout0 = CreateBindGroupLayout(bg0Bindings, {sizes[0], sizes[1]});
wgpu::BindGroupLayout layout1 = CreateBindGroupLayout(bg1Bindings, {sizes[2], sizes[3]});
if (expectation) {
CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader);
CreateComputePipeline({layout0, layout1}, computeShader);
} else {
ASSERT_DEVICE_ERROR(CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader));
ASSERT_DEVICE_ERROR(CreateComputePipeline({layout0, layout1}, computeShader));
}
});
}
// The check between the BGL and the bindings at bindgroup creation time
class MinBufferSizeBindGroupCreationTests : public MinBufferSizeTestsBase {};
// Fail if a binding is smaller than minimum buffer size
TEST_F(MinBufferSizeBindGroupCreationTests, BindingTooSmall) {
std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {8, 4});
CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
if (expectation) {
CreateBindGroup(layout, bindings, sizes);
} else {
ASSERT_DEVICE_ERROR(CreateBindGroup(layout, bindings, sizes));
}
});
}
// Check two layouts with different minimum size are unequal
TEST_F(MinBufferSizeBindGroupCreationTests, LayoutEquality) {
// Returning the same pointer is an implementation detail of Dawn Native.
// It is not the same semantic with the Wire.
DAWN_SKIP_TEST_IF(UsesWire());
auto MakeLayout = [&](uint64_t size) {
return utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform, false, size}});
};
EXPECT_EQ(MakeLayout(0).Get(), MakeLayout(0).Get());
EXPECT_NE(MakeLayout(0).Get(), MakeLayout(4).Get());
}
// The check between the bindgroup binding sizes and the required pipeline sizes at draw time
class MinBufferSizeDrawTimeValidationTests : public MinBufferSizeTestsBase {};
// Fail if binding sizes are too small at draw time
TEST_F(MinBufferSizeDrawTimeValidationTests, ZeroMinSizeAndTooSmallBinding) {
std::vector<BindingDescriptor> bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0});
wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader);
wgpu::RenderPipeline renderPipeline = CreateRenderPipeline({layout}, vertexShader, fragShader);
CheckSizeBounds({8, 4}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
wgpu::BindGroup bindGroup = CreateBindGroup(layout, bindings, sizes);
TestDispatch(computePipeline, {bindGroup}, expectation);
TestDraw(renderPipeline, {bindGroup}, expectation);
});
}
// Draw time validation works for non-contiguous bindings
TEST_F(MinBufferSizeDrawTimeValidationTests, UnorderedBindings) {
std::vector<BindingDescriptor> bindings = {
{0, 2, "a : f32; b : f32;", "f32", "a", 8},
{0, 0, "c : f32;", "f32", "c", 4},
{0, 4, "d : f32; e : f32; f : f32;", "f32", "e", 12}};
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
wgpu::BindGroupLayout layout = CreateBindGroupLayout(bindings, {0, 0, 0});
wgpu::ComputePipeline computePipeline = CreateComputePipeline({layout}, computeShader);
wgpu::RenderPipeline renderPipeline = CreateRenderPipeline({layout}, vertexShader, fragShader);
CheckSizeBounds({8, 4, 12}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
wgpu::BindGroup bindGroup = CreateBindGroup(layout, bindings, sizes);
TestDispatch(computePipeline, {bindGroup}, expectation);
TestDraw(renderPipeline, {bindGroup}, expectation);
});
}
// Draw time validation works for multiple bind groups
TEST_F(MinBufferSizeDrawTimeValidationTests, MultipleGroups) {
std::vector<BindingDescriptor> bg0Bindings = {{0, 0, "a : f32; b : f32;", "f32", "a", 8},
{0, 1, "c : f32;", "f32", "c", 4}};
std::vector<BindingDescriptor> bg1Bindings = {
{1, 0, "d : f32; e : f32; f : f32;", "f32", "e", 12},
{1, 1, "g : mat2x2<f32>;", "mat2x2<f32>", "g", 16}};
std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
std::string computeShader = CreateComputeShaderWithBindings(bindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(bindings);
wgpu::BindGroupLayout layout0 = CreateBindGroupLayout(bg0Bindings, {0, 0});
wgpu::BindGroupLayout layout1 = CreateBindGroupLayout(bg1Bindings, {0, 0});
wgpu::ComputePipeline computePipeline =
CreateComputePipeline({layout0, layout1}, computeShader);
wgpu::RenderPipeline renderPipeline =
CreateRenderPipeline({layout0, layout1}, vertexShader, fragShader);
CheckSizeBounds({8, 4, 12, 16}, [&](const std::vector<uint64_t>& sizes, bool expectation) {
wgpu::BindGroup bindGroup0 = CreateBindGroup(layout0, bg0Bindings, {sizes[0], sizes[1]});
wgpu::BindGroup bindGroup1 = CreateBindGroup(layout0, bg0Bindings, {sizes[2], sizes[3]});
TestDispatch(computePipeline, {bindGroup0, bindGroup1}, expectation);
TestDraw(renderPipeline, {bindGroup0, bindGroup1}, expectation);
});
}
// The correctness of minimum buffer size for the defaulted layout for a pipeline
class MinBufferSizeDefaultLayoutTests : public MinBufferSizeTestsBase {
public:
// Checks BGL |layout| has minimum buffer sizes equal to sizes in |bindings|
void CheckLayoutBindingSizeValidation(const wgpu::BindGroupLayout& layout,
const std::vector<BindingDescriptor>& bindings) {
std::vector<uint64_t> correctSizes;
correctSizes.reserve(bindings.size());
for (const BindingDescriptor& b : bindings) {
correctSizes.push_back(b.size);
}
CheckSizeBounds(correctSizes, [&](const std::vector<uint64_t>& sizes, bool expectation) {
if (expectation) {
CreateBindGroup(layout, bindings, sizes);
} else {
ASSERT_DEVICE_ERROR(CreateBindGroup(layout, bindings, sizes));
}
});
}
// Constructs shaders with given layout type and bindings, checking defaulted sizes match sizes
// in |bindings|
void CheckShaderBindingSizeReflection(
std::initializer_list<std::vector<BindingDescriptor>> bindings) {
std::vector<BindingDescriptor> combinedBindings = CombineBindings(bindings);
std::string computeShader = CreateComputeShaderWithBindings(combinedBindings);
std::string vertexShader = CreateVertexShaderWithBindings({});
std::string fragShader = CreateFragmentShaderWithBindings(combinedBindings);
size_t i = 0;
for (const std::vector<BindingDescriptor>& b : bindings) {
wgpu::BindGroupLayout computeLayout = GetBGLFromComputeShader(computeShader, i);
wgpu::BindGroupLayout renderLayout =
GetBGLFromRenderShaders(vertexShader, fragShader, i);
CheckLayoutBindingSizeValidation(computeLayout, b);
CheckLayoutBindingSizeValidation(renderLayout, b);
++i;
}
}
};
// Test the minimum size computations for various WGSL types.
TEST_F(MinBufferSizeDefaultLayoutTests, DefaultLayoutVariousWGSLTypes) {
CheckShaderBindingSizeReflection({{{0, 0, "a : f32;", "f32", "a", 4},
{0, 1, "b : array<f32>;", "f32", "b[0]", 4},
{0, 2, "c : mat2x2<f32>;", "mat2x2<f32>", "c", 16}}});
CheckShaderBindingSizeReflection({{{0, 3, "d : u32; e : array<f32>;", "u32", "d", 8},
{0, 4, "f : ThreeFloats;", "f32", "f.f1", 12},
{0, 5, "g : array<ThreeFloats>;", "f32", "g[0].f1", 12}}});
}
// Test the minimum size computations for various buffer binding types.
TEST_F(MinBufferSizeDefaultLayoutTests, DefaultLayoutVariousBindingTypes) {
CheckShaderBindingSizeReflection(
{{{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform},
{0, 1, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Storage},
{0, 2, "a : f32; b : f32; c: f32;", "f32", "a", 12,
wgpu::BufferBindingType::ReadOnlyStorage}}});
}
// Test the minimum size computations works with multiple bind groups.
TEST_F(MinBufferSizeDefaultLayoutTests, MultipleBindGroups) {
CheckShaderBindingSizeReflection(
{{{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform}},
{{1, 0, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Storage}},
{{2, 0, "a : f32; b : f32; c : f32;", "f32", "a", 12,
wgpu::BufferBindingType::ReadOnlyStorage}}});
}
// Test the minimum size computations with manual size/align/stride decorations.
TEST_F(MinBufferSizeDefaultLayoutTests, NonDefaultLayout) {
CheckShaderBindingSizeReflection(
{{{0, 0, "[[size(256)]] a : u32; b : u32;", "u32", "a", 260},
{0, 1, "c : u32; [[align(16)]] d : u32;", "u32", "c", 20},
{0, 2, "d : [[stride(40)]] array<u32, 3>;", "u32", "d[0]", 120},
{0, 3, "e : [[stride(40)]] array<u32>;", "u32", "e[0]", 40}}});
}
// Minimum size should be the max requirement of both vertex and fragment stages.
TEST_F(MinBufferSizeDefaultLayoutTests, RenderPassConsidersBothStages) {
std::string vertexShader = CreateVertexShaderWithBindings(
{{0, 0, "a : f32; b : f32;", "f32", "a", 8, wgpu::BufferBindingType::Uniform,
wgpu::ShaderStage::Vertex},
{0, 1, "c : vec4<f32>;", "vec4<f32>", "c", 16, wgpu::BufferBindingType::Uniform,
wgpu::ShaderStage::Vertex}});
std::string fragShader = CreateFragmentShaderWithBindings(
{{0, 0, "a : f32;", "f32", "a", 4, wgpu::BufferBindingType::Uniform,
wgpu::ShaderStage::Fragment},
{0, 1, "b : f32; c : f32;", "f32", "b", 8, wgpu::BufferBindingType::Uniform,
wgpu::ShaderStage::Fragment}});
wgpu::BindGroupLayout renderLayout = GetBGLFromRenderShaders(vertexShader, fragShader, 0);
CheckLayoutBindingSizeValidation(renderLayout, {{0, 0, "", "", "", 8}, {0, 1, "", "", "", 16}});
}