mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-17 04:41:23 +00:00
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>
591 lines
27 KiB
C++
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}});
|
|
}
|