Implement getBindGroupLayout

This patch makes the |layout| member of the Render|ComputePipelineDescriptor
optional. If it is not provided, a default layout is created from the
ShaderModules provided and used to replace the layout in the descriptor.

Then, pipeline.GetBindGroupLayout may be called to get the existing, or
the computed bind group layout. If no bind group layout exists at the
provided index, an empty bind group layout is returned.

Bug: dawn:276
Change-Id: I276ed0296a2f1f2d8131fa906a4aefe85d75b3a7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/13741
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
This commit is contained in:
Austin Eng 2019-11-22 17:02:22 +00:00 committed by Commit Bot service account
parent d0993ba83a
commit f6eb890f4c
15 changed files with 972 additions and 29 deletions

View File

@ -789,6 +789,7 @@ test("dawn_unittests") {
"src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp",
"src/tests/unittests/validation/ErrorScopeValidationTests.cpp",
"src/tests/unittests/validation/FenceValidationTests.cpp",
"src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp",
"src/tests/unittests/validation/QueueSubmitValidationTests.cpp",
"src/tests/unittests/validation/RenderBundleValidationTests.cpp",
"src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp",

View File

@ -437,14 +437,23 @@
]
},
"compute pipeline": {
"category": "object"
"category": "object",
"methods": [
{
"name": "get bind group layout",
"returns": "bind group layout",
"args": [
{"name": "group", "type": "uint32_t"}
]
}
]
},
"compute pipeline descriptor": {
"category": "structure",
"extensible": true,
"members": [
{"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true},
{"name": "layout", "type": "pipeline layout"},
{"name": "layout", "type": "pipeline layout", "optional": true},
{"name": "compute stage", "type": "programmable stage descriptor"}
]
},
@ -1118,14 +1127,23 @@
]
},
"render pipeline": {
"category": "object"
"category": "object",
"methods": [
{
"name": "get bind group layout",
"returns": "bind group layout",
"args": [
{"name": "group", "type": "uint32_t"}
]
}
]
},
"render pipeline descriptor": {
"category": "structure",
"extensible": true,
"members": [
{"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true},
{"name": "layout", "type": "pipeline layout"},
{"name": "layout", "type": "pipeline layout", "optional": true},
{"name": "vertex stage", "type": "programmable stage descriptor"},
{"name": "fragment stage", "type": "programmable stage descriptor", "annotation": "const*", "optional": true},
{"name": "vertex state", "type": "vertex state descriptor", "annotation": "const*", "optional": true},

View File

@ -108,6 +108,13 @@ namespace wgpu {
return static_cast<Derived&>(*this);
}
bool operator==(std::nullptr_t) const {
return mHandle == nullptr;
}
bool operator!=(std::nullptr_t) const {
return mHandle != nullptr;
}
explicit operator bool() const {
return mHandle != nullptr;
}

View File

@ -25,7 +25,10 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
DAWN_TRY(device->ValidateObject(descriptor->layout));
if (descriptor->layout != nullptr) {
DAWN_TRY(device->ValidateObject(descriptor->layout));
}
DAWN_TRY(ValidateProgrammableStageDescriptor(
device, &descriptor->computeStage, descriptor->layout, SingleShaderStage::Compute));
return {};

View File

@ -670,7 +670,21 @@ namespace dawn_native {
if (IsValidationEnabled()) {
DAWN_TRY(ValidateComputePipelineDescriptor(this, descriptor));
}
DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(descriptor));
if (descriptor->layout == nullptr) {
ComputePipelineDescriptor descriptorWithDefaultLayout = *descriptor;
DAWN_TRY_ASSIGN(
descriptorWithDefaultLayout.layout,
PipelineLayoutBase::CreateDefault(this, &descriptor->computeStage.module, 1));
// Ref will keep the pipeline layout alive until the end of the function where
// the pipeline will take another reference.
Ref<PipelineLayoutBase> layoutRef = AcquireRef(descriptorWithDefaultLayout.layout);
DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(&descriptorWithDefaultLayout));
} else {
DAWN_TRY_ASSIGN(*result, GetOrCreateComputePipeline(descriptor));
}
return {};
}
@ -705,7 +719,30 @@ namespace dawn_native {
if (IsValidationEnabled()) {
DAWN_TRY(ValidateRenderPipelineDescriptor(this, descriptor));
}
DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(descriptor));
if (descriptor->layout == nullptr) {
RenderPipelineDescriptor descriptorWithDefaultLayout = *descriptor;
const ShaderModuleBase* modules[2];
modules[0] = descriptor->vertexStage.module;
uint32_t count;
if (descriptor->fragmentStage == nullptr) {
count = 1;
} else {
modules[1] = descriptor->fragmentStage->module;
count = 2;
}
DAWN_TRY_ASSIGN(descriptorWithDefaultLayout.layout,
PipelineLayoutBase::CreateDefault(this, modules, count));
// Ref will keep the pipeline layout alive until the end of the function where
// the pipeline will take another reference.
Ref<PipelineLayoutBase> layoutRef = AcquireRef(descriptorWithDefaultLayout.layout);
DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(&descriptorWithDefaultLayout));
} else {
DAWN_TRY_ASSIGN(*result, GetOrCreateRenderPipeline(descriptor));
}
return {};
}

View File

@ -22,8 +22,17 @@ namespace dawn_native {
// Format
// static
Format::Type Format::TextureComponentTypeToFormatType(
wgpu::TextureComponentType componentType) {
switch (componentType) {
case wgpu::TextureComponentType::Float:
case wgpu::TextureComponentType::Sint:
case wgpu::TextureComponentType::Uint:
break;
default:
UNREACHABLE();
}
// Check that Type correctly mirrors TextureComponentType except for "Other".
static_assert(static_cast<Type>(wgpu::TextureComponentType::Float) == Type::Float, "");
static_assert(static_cast<Type>(wgpu::TextureComponentType::Sint) == Type::Sint, "");
@ -31,6 +40,23 @@ namespace dawn_native {
return static_cast<Type>(componentType);
}
// static
wgpu::TextureComponentType Format::FormatTypeToTextureComponentType(Type type) {
switch (type) {
case Type::Float:
case Type::Sint:
case Type::Uint:
break;
default:
UNREACHABLE();
}
// Check that Type correctly mirrors TextureComponentType except for "Other".
static_assert(static_cast<Type>(wgpu::TextureComponentType::Float) == Type::Float, "");
static_assert(static_cast<Type>(wgpu::TextureComponentType::Sint) == Type::Sint, "");
static_assert(static_cast<Type>(wgpu::TextureComponentType::Uint) == Type::Uint, "");
return static_cast<wgpu::TextureComponentType>(type);
}
bool Format::IsColor() const {
return aspect == Aspect::Color;
}

View File

@ -58,6 +58,7 @@ namespace dawn_native {
uint32_t blockHeight;
static Type TextureComponentTypeToFormatType(wgpu::TextureComponentType componentType);
static wgpu::TextureComponentType FormatTypeToTextureComponentType(Type type);
bool IsColor() const;
bool HasDepth() const;

View File

@ -14,6 +14,7 @@
#include "dawn_native/Pipeline.h"
#include "dawn_native/BindGroupLayout.h"
#include "dawn_native/Device.h"
#include "dawn_native/PipelineLayout.h"
#include "dawn_native/ShaderModule.h"
@ -32,7 +33,7 @@ namespace dawn_native {
if (descriptor->module->GetExecutionModel() != stage) {
return DAWN_VALIDATION_ERROR("Setting module with wrong stages");
}
if (!descriptor->module->IsCompatibleWithPipelineLayout(layout)) {
if (layout != nullptr && !descriptor->module->IsCompatibleWithPipelineLayout(layout)) {
return DAWN_VALIDATION_ERROR("Stage not compatible with layout");
}
return {};
@ -65,4 +66,41 @@ namespace dawn_native {
return mLayout.Get();
}
MaybeError PipelineBase::ValidateGetBindGroupLayout(uint32_t group) {
DAWN_TRY(GetDevice()->ValidateObject(this));
DAWN_TRY(GetDevice()->ValidateObject(mLayout.Get()));
if (group >= kMaxBindGroups) {
return DAWN_VALIDATION_ERROR("Bind group layout index out of bounds");
}
return {};
}
BindGroupLayoutBase* PipelineBase::GetBindGroupLayout(uint32_t group) {
if (GetDevice()->ConsumedError(ValidateGetBindGroupLayout(group))) {
return BindGroupLayoutBase::MakeError(GetDevice());
}
if (!mLayout->GetBindGroupLayoutsMask()[group]) {
// Get or create an empty bind group layout.
// TODO(enga): Consider caching this object on the Device and reusing it.
// Today, this can't be done correctly because of the order of Device destruction.
// For example, vulkan::~Device will be called before ~DeviceBase. If DeviceBase owns
// a Ref<BindGroupLayoutBase>, then the VkDevice will be destroyed before the
// VkDescriptorSetLayout.
BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 0;
desc.bindings = nullptr;
BindGroupLayoutBase* bgl = nullptr;
if (GetDevice()->ConsumedError(GetDevice()->GetOrCreateBindGroupLayout(&desc), &bgl)) {
return BindGroupLayoutBase::MakeError(GetDevice());
}
return bgl;
}
BindGroupLayoutBase* bgl = mLayout->GetBindGroupLayout(group);
bgl->Reference();
return bgl;
}
} // namespace dawn_native

View File

@ -38,12 +38,15 @@ namespace dawn_native {
wgpu::ShaderStage GetStageMask() const;
PipelineLayoutBase* GetLayout();
const PipelineLayoutBase* GetLayout() const;
BindGroupLayoutBase* GetBindGroupLayout(uint32_t group);
protected:
PipelineBase(DeviceBase* device, PipelineLayoutBase* layout, wgpu::ShaderStage stages);
PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
private:
MaybeError ValidateGetBindGroupLayout(uint32_t group);
wgpu::ShaderStage mStageMask;
Ref<PipelineLayoutBase> mLayout;
};

View File

@ -19,9 +19,22 @@
#include "common/HashUtils.h"
#include "dawn_native/BindGroupLayout.h"
#include "dawn_native/Device.h"
#include "dawn_native/ShaderModule.h"
namespace dawn_native {
namespace {
bool operator==(const BindGroupLayoutBinding& lhs, const BindGroupLayoutBinding& rhs) {
return lhs.binding == rhs.binding && lhs.visibility == rhs.visibility &&
lhs.type == rhs.type && lhs.hasDynamicOffset == rhs.hasDynamicOffset &&
lhs.multisampled == rhs.multisampled &&
lhs.textureDimension == rhs.textureDimension &&
lhs.textureComponentType == rhs.textureComponentType;
}
} // anonymous namespace
MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
const PipelineLayoutDescriptor* descriptor) {
if (descriptor->nextInChain != nullptr) {
@ -81,11 +94,126 @@ namespace dawn_native {
return new PipelineLayoutBase(device, ObjectBase::kError);
}
const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(size_t group) const {
// static
ResultOrError<PipelineLayoutBase*> PipelineLayoutBase::CreateDefault(
DeviceBase* device,
const ShaderModuleBase* const* modules,
uint32_t count) {
ASSERT(count > 0);
// Data which BindGroupLayoutDescriptor will point to for creation
std::array<std::array<BindGroupLayoutBinding, kMaxBindingsPerGroup>, kMaxBindGroups>
bindingData = {};
// Bitsets of used bindings
std::array<std::bitset<kMaxBindingsPerGroup>, kMaxBindGroups> usedBindings = {};
// A flat map of bindings to the index in |bindingData|
std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups> usedBindingsMap = {};
// A counter of how many bindings we've populated in |bindingData|
std::array<uint32_t, kMaxBindGroups> bindingCounts = {};
uint32_t bindGroupLayoutCount = 0;
for (uint32_t moduleIndex = 0; moduleIndex < count; ++moduleIndex) {
const ShaderModuleBase* module = modules[moduleIndex];
const ShaderModuleBase::ModuleBindingInfo& info = module->GetBindingInfo();
for (uint32_t group = 0; group < info.size(); ++group) {
for (uint32_t binding = 0; binding < info[group].size(); ++binding) {
const ShaderModuleBase::BindingInfo& bindingInfo = info[group][binding];
if (!bindingInfo.used) {
continue;
}
if (bindingInfo.multisampled) {
return DAWN_VALIDATION_ERROR("Multisampled textures not supported (yet)");
}
BindGroupLayoutBinding bindingSlot;
bindingSlot.binding = binding;
bindingSlot.visibility = wgpu::ShaderStage::Vertex |
wgpu::ShaderStage::Fragment |
wgpu::ShaderStage::Compute;
bindingSlot.type = bindingInfo.type;
bindingSlot.hasDynamicOffset = false;
bindingSlot.multisampled = bindingInfo.multisampled;
bindingSlot.textureDimension = bindingInfo.textureDimension;
bindingSlot.textureComponentType =
Format::FormatTypeToTextureComponentType(bindingInfo.textureComponentType);
if (usedBindings[group][binding]) {
if (bindingSlot == bindingData[group][usedBindingsMap[group][binding]]) {
// Already used and the data is the same. Continue.
continue;
} else {
return DAWN_VALIDATION_ERROR(
"Duplicate binding in default pipeline layout initialization not "
"compatible with previous declaration");
}
}
uint32_t currentBindingCount = bindingCounts[group];
bindingData[group][currentBindingCount] = bindingSlot;
usedBindingsMap[group][binding] = currentBindingCount;
usedBindings[group].set(binding);
bindingCounts[group]++;
bindGroupLayoutCount = std::max(bindGroupLayoutCount, group + 1);
}
}
}
std::array<BindGroupLayoutBase*, kMaxBindGroups> bindGroupLayouts = {};
for (uint32_t group = 0; group < bindGroupLayoutCount; ++group) {
BindGroupLayoutDescriptor desc = {};
desc.bindings = bindingData[group].data();
desc.bindingCount = bindingCounts[group];
// We should never produce a bad descriptor.
ASSERT(!ValidateBindGroupLayoutDescriptor(device, &desc).IsError());
DAWN_TRY_ASSIGN(bindGroupLayouts[group], device->GetOrCreateBindGroupLayout(&desc));
}
PipelineLayoutDescriptor desc = {};
desc.bindGroupLayouts = bindGroupLayouts.data();
desc.bindGroupLayoutCount = bindGroupLayoutCount;
PipelineLayoutBase* pipelineLayout = device->CreatePipelineLayout(&desc);
ASSERT(!pipelineLayout->IsError());
// These bind group layouts are created internally and referenced by the pipeline layout.
// Release the external refcount.
for (uint32_t group = 0; group < bindGroupLayoutCount; ++group) {
if (bindGroupLayouts[group] != nullptr) {
bindGroupLayouts[group]->Release();
}
}
for (uint32_t moduleIndex = 0; moduleIndex < count; ++moduleIndex) {
ASSERT(modules[moduleIndex]->IsCompatibleWithPipelineLayout(pipelineLayout));
}
return pipelineLayout;
}
const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(uint32_t group) const {
ASSERT(!IsError());
ASSERT(group < kMaxBindGroups);
ASSERT(mMask[group]);
return mBindGroupLayouts[group].Get();
const BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get();
ASSERT(bgl != nullptr);
return bgl;
}
BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(uint32_t group) {
ASSERT(!IsError());
ASSERT(group < kMaxBindGroups);
ASSERT(mMask[group]);
BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get();
ASSERT(bgl != nullptr);
return bgl;
}
const std::bitset<kMaxBindGroups> PipelineLayoutBase::GetBindGroupLayoutsMask() const {

View File

@ -38,8 +38,11 @@ namespace dawn_native {
~PipelineLayoutBase() override;
static PipelineLayoutBase* MakeError(DeviceBase* device);
static ResultOrError<PipelineLayoutBase*>
CreateDefault(DeviceBase* device, const ShaderModuleBase* const* modules, uint32_t count);
const BindGroupLayoutBase* GetBindGroupLayout(size_t group) const;
const BindGroupLayoutBase* GetBindGroupLayout(uint32_t group) const;
BindGroupLayoutBase* GetBindGroupLayout(uint32_t group);
const std::bitset<kMaxBindGroups> GetBindGroupLayoutsMask() const;
// Utility functions to compute inherited bind groups.

View File

@ -280,7 +280,9 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
DAWN_TRY(device->ValidateObject(descriptor->layout));
if (descriptor->layout != nullptr) {
DAWN_TRY(device->ValidateObject(descriptor->layout));
}
// TODO(crbug.com/dawn/136): Support vertex-only pipelines.
if (descriptor->fragmentStage == nullptr) {

View File

@ -175,36 +175,37 @@ namespace dawn_native {
continue;
}
auto& info = mBindingInfo[set][binding];
info.used = true;
info.id = resource.id;
info.base_type_id = resource.base_type_id;
BindingInfo* info = &mBindingInfo[set][binding];
*info = {};
info->used = true;
info->id = resource.id;
info->base_type_id = resource.base_type_id;
switch (bindingType) {
case wgpu::BindingType::SampledTexture: {
spirv_cross::SPIRType::ImageType imageType =
compiler.get_type(info.base_type_id).image;
compiler.get_type(info->base_type_id).image;
spirv_cross::SPIRType::BaseType textureComponentType =
compiler.get_type(imageType.type).basetype;
info.multisampled = imageType.ms;
info.textureDimension =
info->multisampled = imageType.ms;
info->textureDimension =
SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
info.textureComponentType =
info->textureComponentType =
SpirvCrossBaseTypeToFormatType(textureComponentType);
info.type = bindingType;
info->type = bindingType;
} break;
case wgpu::BindingType::StorageBuffer: {
// Differentiate between readonly storage bindings and writable ones based
// on the NonWritable decoration
spirv_cross::Bitset flags = compiler.get_buffer_block_flags(resource.id);
if (flags.get(spv::DecorationNonWritable)) {
info.type = wgpu::BindingType::ReadonlyStorageBuffer;
info->type = wgpu::BindingType::ReadonlyStorageBuffer;
} else {
info.type = wgpu::BindingType::StorageBuffer;
info->type = wgpu::BindingType::StorageBuffer;
}
} break;
default:
info.type = bindingType;
info->type = bindingType;
}
}
};
@ -296,7 +297,7 @@ namespace dawn_native {
return mExecutionModel;
}
bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) {
bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) const {
ASSERT(!IsError());
for (uint32_t group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
@ -316,8 +317,9 @@ namespace dawn_native {
return true;
}
bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(size_t group,
const BindGroupLayoutBase* layout) {
bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(
size_t group,
const BindGroupLayoutBase* layout) const {
ASSERT(!IsError());
const auto& layoutInfo = layout->GetBindingInfo();

View File

@ -69,7 +69,7 @@ namespace dawn_native {
using FragmentOutputBaseTypes = std::array<Format::Type, kMaxColorAttachments>;
const FragmentOutputBaseTypes& GetFragmentOutputBaseTypes() const;
bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout);
bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) const;
// Functors necessary for the unordered_set<ShaderModuleBase*>-based cache.
struct HashFunc {
@ -82,7 +82,7 @@ namespace dawn_native {
private:
ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout);
bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout) const;
// TODO(cwallez@chromium.org): The code is only stored for deduplication. We could maybe
// store a cryptographic hash of the code instead?

View File

@ -0,0 +1,674 @@
// Copyright 2019 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 "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
class GetBindGroupLayoutTests : public ValidationTest {
protected:
static constexpr wgpu::ShaderStage kVisibilityAll =
wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex;
wgpu::RenderPipeline RenderPipelineFromVertexShader(const char* shader) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, shader);
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
void main() {
})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
return device.CreateRenderPipeline(&descriptor);
}
};
// Test that GetBindGroupLayout returns the same object for the same index
// and for matching layouts.
TEST_F(GetBindGroupLayoutTests, SameObject) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform UniformBuffer1 {
vec4 pos1;
};
layout(set = 1, binding = 0) uniform UniformBuffer2 {
vec4 pos2;
};
void main() {
})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 2, binding = 0) uniform UniformBuffer3 {
vec4 pos3;
};
layout(set = 3, binding = 0) buffer StorageBuffer {
mat4 pos4;
};
void main() {
})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(0).Get());
EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), pipeline.GetBindGroupLayout(1).Get());
EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(1).Get());
EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(2).Get());
EXPECT_NE(pipeline.GetBindGroupLayout(0).Get(), pipeline.GetBindGroupLayout(3).Get());
}
// Test that getBindGroupLayout defaults are correct
// - shader stage visibility is All
// - dynamic offsets is false
TEST_F(GetBindGroupLayoutTests, DefaultShaderStageAndDynamicOffsets) {
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform UniformBuffer {
vec4 pos;
};
void main() {
})");
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::UniformBuffer;
binding.multisampled = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
// Check that visibility and dynamic offsets match
binding.hasDynamicOffset = false;
binding.visibility = kVisibilityAll;
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
// Check that any change in visibility doesn't match.
binding.visibility = wgpu::ShaderStage::Vertex;
EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
binding.visibility = wgpu::ShaderStage::Fragment;
EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
binding.visibility = wgpu::ShaderStage::Compute;
EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
// Check that any change in hasDynamicOffsets doesn't match.
binding.hasDynamicOffset = true;
binding.visibility = kVisibilityAll;
EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
// Test GetBindGroupLayout works with a compute pipeline
TEST_F(GetBindGroupLayoutTests, ComputePipeline) {
wgpu::ShaderModule csModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
#version 450
layout(set = 0, binding = 0) uniform UniformBuffer {
vec4 pos;
};
void main() {
})");
wgpu::ComputePipelineDescriptor descriptor;
descriptor.layout = nullptr;
descriptor.computeStage.module = csModule;
descriptor.computeStage.entryPoint = "main";
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor);
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::UniformBuffer;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
// Test that the binding type matches the shader.
TEST_F(GetBindGroupLayoutTests, BindingType) {
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
binding.multisampled = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
{
binding.type = wgpu::BindingType::UniformBuffer;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer {
vec4 pos;
};
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.type = wgpu::BindingType::StorageBuffer;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) buffer Storage {
vec4 pos;
};
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.type = wgpu::BindingType::ReadonlyStorageBuffer;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) readonly buffer Storage {
vec4 pos;
};
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.type = wgpu::BindingType::SampledTexture;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.type = wgpu::BindingType::Sampler;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform sampler samp;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
}
// Test that multisampling matches the shader.
TEST_F(GetBindGroupLayoutTests, Multisampled) {
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::SampledTexture;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
{
binding.multisampled = false;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
// TODO: Support multisampling
GTEST_SKIP() << "Multisampling unimplemented";
{
binding.multisampled = true;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2DMS tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
}
// Test that texture view dimension matches the shader.
TEST_F(GetBindGroupLayoutTests, TextureDimension) {
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::SampledTexture;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
binding.multisampled = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
{
binding.textureDimension = wgpu::TextureViewDimension::e1D;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture1D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureDimension = wgpu::TextureViewDimension::e2D;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureDimension = wgpu::TextureViewDimension::e2DArray;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2DArray tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureDimension = wgpu::TextureViewDimension::e3D;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture3D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureDimension = wgpu::TextureViewDimension::Cube;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform textureCube tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureDimension = wgpu::TextureViewDimension::CubeArray;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform textureCubeArray tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
}
// Test that texture component type matches the shader.
TEST_F(GetBindGroupLayoutTests, TextureComponentType) {
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::SampledTexture;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
binding.multisampled = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
{
binding.textureComponentType = wgpu::TextureComponentType::Float;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureComponentType = wgpu::TextureComponentType::Sint;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform itexture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.textureComponentType = wgpu::TextureComponentType::Uint;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform utexture2D tex;
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
}
// Test that binding= indices match.
TEST_F(GetBindGroupLayoutTests, BindingIndices) {
wgpu::BindGroupLayoutBinding binding = {};
binding.type = wgpu::BindingType::UniformBuffer;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
binding.multisampled = false;
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 1;
desc.bindings = &binding;
{
binding.binding = 0;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer {
vec4 pos;
};
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.binding = 1;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 1) uniform Buffer {
vec4 pos;
};
void main() {})");
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
{
binding.binding = 2;
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 1) uniform Buffer {
vec4 pos;
};
void main() {})");
EXPECT_NE(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
}
// Test it is valid to have duplicate bindings in the shaders.
TEST_F(GetBindGroupLayoutTests, DuplicateBinding) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform UniformBuffer1 {
vec4 pos1;
};
layout(set = 1, binding = 0) uniform UniformBuffer2 {
vec4 pos2;
};
void main() {})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 1, binding = 0) uniform UniformBuffer3 {
vec4 pos3;
};
void main() {})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
device.CreateRenderPipeline(&descriptor);
}
// Test it is invalid to have conflicting binding types in the shaders.
TEST_F(GetBindGroupLayoutTests, ConflictingBindingType) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform UniformBuffer {
vec4 pos;
};
void main() {})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 0, binding = 0) buffer StorageBuffer {
vec4 pos;
};
void main() {})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test it is invalid to have conflicting binding texture multisampling in the shaders.
TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureMultisampling) {
// TODO: Support multisampling
GTEST_SKIP() << "Multisampling unimplemented";
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 0, binding = 0) uniform texture2DMS tex;
void main() {})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test it is invalid to have conflicting binding texture dimension in the shaders.
TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureDimension) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 0, binding = 0) uniform texture3D tex;
void main() {})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test it is invalid to have conflicting binding texture component type in the shaders.
TEST_F(GetBindGroupLayoutTests, ConflictingBindingTextureComponentType) {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform texture2D tex;
void main() {})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(set = 0, binding = 0) uniform utexture2D tex;
void main() {})");
utils::ComboRenderPipelineDescriptor descriptor(device);
descriptor.layout = nullptr;
descriptor.vertexStage.module = vsModule;
descriptor.cFragmentStage.module = fsModule;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
}
// Test it is an error to query an out of range bind group layout.
TEST_F(GetBindGroupLayoutTests, OutOfRangeIndex) {
ASSERT_DEVICE_ERROR(RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer1 {
vec4 pos1;
};
void main() {})")
.GetBindGroupLayout(kMaxBindGroups));
ASSERT_DEVICE_ERROR(RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer1 {
vec4 pos1;
};
void main() {})")
.GetBindGroupLayout(kMaxBindGroups + 1));
}
// Test that unused indices return the empty bind group layout.
TEST_F(GetBindGroupLayoutTests, UnusedIndex) {
wgpu::RenderPipeline pipeline = RenderPipelineFromVertexShader(R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer1 {
vec4 pos1;
};
layout(set = 2, binding = 0) uniform Buffer2 {
vec4 pos2;
};
void main() {})");
wgpu::BindGroupLayoutDescriptor desc = {};
desc.bindingCount = 0;
desc.bindings = nullptr;
wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&desc);
EXPECT_NE(pipeline.GetBindGroupLayout(0).Get(), emptyBindGroupLayout.Get()); // Used
EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), emptyBindGroupLayout.Get()); // Not Used.
EXPECT_NE(pipeline.GetBindGroupLayout(2).Get(), emptyBindGroupLayout.Get()); // Used.
EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get()); // Not used
}
// Test that after explicitly creating a pipeline with a pipeline layout, calling
// GetBindGroupLayout reflects the same bind group layouts.
TEST_F(GetBindGroupLayoutTests, Reflection) {
wgpu::BindGroupLayoutBinding binding = {};
binding.binding = 0;
binding.type = wgpu::BindingType::UniformBuffer;
binding.visibility = wgpu::ShaderStage::Vertex;
wgpu::BindGroupLayoutDescriptor bglDesc = {};
bglDesc.bindingCount = 1;
bglDesc.bindings = &binding;
wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(&bglDesc);
wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = {};
pipelineLayoutDesc.bindGroupLayoutCount = 1;
pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout;
wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc);
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
layout(set = 0, binding = 0) uniform Buffer1 {
vec4 pos1;
};
void main() {
})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
void main() {
})");
utils::ComboRenderPipelineDescriptor pipelineDesc(device);
pipelineDesc.layout = pipelineLayout;
pipelineDesc.vertexStage.module = vsModule;
pipelineDesc.cFragmentStage.module = fsModule;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
EXPECT_EQ(pipeline.GetBindGroupLayout(0).Get(), bindGroupLayout.Get());
{
wgpu::BindGroupLayoutDescriptor emptyDesc = {};
emptyDesc.bindingCount = 0;
emptyDesc.bindings = nullptr;
wgpu::BindGroupLayout emptyBindGroupLayout = device.CreateBindGroupLayout(&emptyDesc);
// Check that the rest of the bind group layouts reflect the empty one.
EXPECT_EQ(pipeline.GetBindGroupLayout(1).Get(), emptyBindGroupLayout.Get());
EXPECT_EQ(pipeline.GetBindGroupLayout(2).Get(), emptyBindGroupLayout.Get());
EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());
}
}