mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-16 20:31:20 +00:00
Following spec change https://github.com/gpuweb/gpuweb/pull/1918 Fixed: dawn:962 Change-Id: I9d7eaee588301227736cf523bec46e5a60fe861b Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/59042 Commit-Queue: Austin Eng <enga@chromium.org> Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
592 lines
27 KiB
C++
592 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.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
|
|
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}});
|
|
}
|