Add minimum buffer size validation
Bug: dawn:459
Change-Id: I755cc0ada7be7b1cb71724cb410ab0c3a88cea24
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22421
Commit-Queue: Idan Raiter <idanr@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 1686917..f313dcf 100644
--- a/dawn.json
+++ b/dawn.json
@@ -95,7 +95,8 @@
{"name": "multisampled", "type": "bool", "default": "false"},
{"name": "view dimension", "type": "texture view dimension", "default": "undefined"},
{"name": "texture component type", "type": "texture component type", "default": "float"},
- {"name": "storage texture format", "type": "texture format", "default": "undefined"}
+ {"name": "storage texture format", "type": "texture format", "default": "undefined"},
+ {"name": "min buffer binding size", "type": "uint64_t", "default": "0"}
]
},
"bind group layout descriptor": {
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp
index feb4abd..4e4e67a 100644
--- a/src/dawn_native/BindGroup.cpp
+++ b/src/dawn_native/BindGroup.cpp
@@ -31,7 +31,8 @@
MaybeError ValidateBufferBinding(const DeviceBase* device,
const BindGroupEntry& entry,
- wgpu::BufferUsage requiredUsage) {
+ wgpu::BufferUsage requiredUsage,
+ const BindingInfo& bindingInfo) {
if (entry.buffer == nullptr || entry.sampler != nullptr ||
entry.textureView != nullptr) {
return DAWN_VALIDATION_ERROR("expected buffer binding");
@@ -70,6 +71,14 @@
return DAWN_VALIDATION_ERROR("buffer binding usage mismatch");
}
+ if (bindingSize < bindingInfo.minBufferBindingSize) {
+ return DAWN_VALIDATION_ERROR(
+ "Binding size smaller than minimum buffer size: binding " +
+ std::to_string(entry.binding) + " given " + std::to_string(bindingSize) +
+ " bytes, required " + std::to_string(bindingInfo.minBufferBindingSize) +
+ " bytes");
+ }
+
return {};
}
@@ -182,11 +191,13 @@
// Perform binding-type specific validation.
switch (bindingInfo.type) {
case wgpu::BindingType::UniformBuffer:
- DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Uniform));
+ DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Uniform,
+ bindingInfo));
break;
case wgpu::BindingType::StorageBuffer:
case wgpu::BindingType::ReadonlyStorageBuffer:
- DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Storage));
+ DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Storage,
+ bindingInfo));
break;
case wgpu::BindingType::SampledTexture:
DAWN_TRY(ValidateTextureBinding(device, entry, wgpu::TextureUsage::Sampled,
@@ -264,6 +275,16 @@
continue;
}
}
+
+ uint32_t packedIdx = 0;
+ for (BindingIndex bindingIndex{0}; bindingIndex < descriptor->layout->GetBufferCount();
+ ++bindingIndex) {
+ if (descriptor->layout->GetBindingInfo(bindingIndex).minBufferBindingSize == 0) {
+ mBindingData.unverifiedBufferSizes[packedIdx] =
+ mBindingData.bufferData[bindingIndex].size;
+ ++packedIdx;
+ }
+ }
}
BindGroupBase::~BindGroupBase() {
@@ -302,6 +323,11 @@
return mLayout.Get();
}
+ const ityp::span<uint32_t, uint64_t>& BindGroupBase::GetUnverifiedBufferSizes() const {
+ ASSERT(!IsError());
+ return mBindingData.unverifiedBufferSizes;
+ }
+
BufferBinding BindGroupBase::GetBindingAsBufferBinding(BindingIndex bindingIndex) {
ASSERT(!IsError());
ASSERT(bindingIndex < mLayout->GetBindingCount());
diff --git a/src/dawn_native/BindGroup.h b/src/dawn_native/BindGroup.h
index 1240246..c29bbeb 100644
--- a/src/dawn_native/BindGroup.h
+++ b/src/dawn_native/BindGroup.h
@@ -48,6 +48,7 @@
BufferBinding GetBindingAsBufferBinding(BindingIndex bindingIndex);
SamplerBase* GetBindingAsSampler(BindingIndex bindingIndex) const;
TextureViewBase* GetBindingAsTextureView(BindingIndex bindingIndex);
+ const ityp::span<uint32_t, uint64_t>& GetUnverifiedBufferSizes() const;
protected:
// To save memory, the size of a bind group is dynamically determined and the bind group is
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 97c647e..f63fb71 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -269,7 +269,8 @@
void HashCombineBindingInfo(size_t* hash, const BindingInfo& info) {
HashCombine(hash, info.hasDynamicOffset, info.multisampled, info.visibility, info.type,
- info.textureComponentType, info.viewDimension, info.storageTextureFormat);
+ info.textureComponentType, info.viewDimension, info.storageTextureFormat,
+ info.minBufferBindingSize);
}
bool operator!=(const BindingInfo& a, const BindingInfo& b) {
@@ -279,7 +280,8 @@
a.type != b.type || //
a.textureComponentType != b.textureComponentType || //
a.viewDimension != b.viewDimension || //
- a.storageTextureFormat != b.storageTextureFormat;
+ a.storageTextureFormat != b.storageTextureFormat || //
+ a.minBufferBindingSize != b.minBufferBindingSize;
}
bool IsBufferBinding(wgpu::BindingType bindingType) {
@@ -345,6 +347,9 @@
if (a.storageTextureFormat != b.storageTextureFormat) {
return a.storageTextureFormat < b.storageTextureFormat;
}
+ if (a.minBufferBindingSize != b.minBufferBindingSize) {
+ return a.minBufferBindingSize < b.minBufferBindingSize;
+ }
return false;
}
@@ -382,11 +387,13 @@
for (BindingIndex i{0}; i < mBindingCount; ++i) {
const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)];
+ mBindingInfo[i].binding = BindingNumber(binding.binding);
mBindingInfo[i].type = binding.type;
mBindingInfo[i].visibility = binding.visibility;
mBindingInfo[i].textureComponentType =
Format::TextureComponentTypeToFormatType(binding.textureComponentType);
mBindingInfo[i].storageTextureFormat = binding.storageTextureFormat;
+ mBindingInfo[i].minBufferBindingSize = binding.minBufferBindingSize;
switch (binding.type) {
case wgpu::BindingType::UniformBuffer:
@@ -395,6 +402,9 @@
// Buffers must be contiguously packed at the start of the binding info.
ASSERT(mBufferCount == i);
++mBufferCount;
+ if (binding.minBufferBindingSize == 0) {
+ ++mUnverifiedBufferCount;
+ }
break;
default:
break;
@@ -490,6 +500,10 @@
return mBindingCount;
}
+ BindingIndex BindGroupLayoutBase::GetBufferCount() const {
+ return mBufferCount;
+ }
+
BindingIndex BindGroupLayoutBase::GetDynamicBufferCount() const {
// This is a binding index because dynamic buffers are packed at the front of the binding
// info.
@@ -504,12 +518,23 @@
return mDynamicStorageBufferCount;
}
+ uint32_t BindGroupLayoutBase::GetUnverifiedBufferCount() const {
+ return mUnverifiedBufferCount;
+ }
+
size_t BindGroupLayoutBase::GetBindingDataSize() const {
// | ------ buffer-specific ----------| ------------ object pointers -------------|
// | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------|
+ // Followed by:
+ // |---------buffer size array--------|
+ // |-uint64_t[mUnverifiedBufferCount]-|
size_t objectPointerStart = static_cast<uint32_t>(mBufferCount) * sizeof(BufferBindingData);
ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>)));
- return objectPointerStart + static_cast<uint32_t>(mBindingCount) * sizeof(Ref<ObjectBase>);
+ size_t bufferSizeArrayStart = Align(
+ objectPointerStart + static_cast<uint32_t>(mBindingCount) * sizeof(Ref<ObjectBase>),
+ sizeof(uint64_t));
+ ASSERT(IsAligned(bufferSizeArrayStart, alignof(uint64_t)));
+ return bufferSizeArrayStart + mUnverifiedBufferCount * sizeof(uint64_t);
}
BindGroupLayoutBase::BindingDataPointers BindGroupLayoutBase::ComputeBindingDataPointers(
@@ -517,11 +542,17 @@
BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart);
auto bindings =
reinterpret_cast<Ref<ObjectBase>*>(bufferData + static_cast<uint32_t>(mBufferCount));
+ uint64_t* unverifiedBufferSizes =
+ AlignPtr(reinterpret_cast<uint64_t*>(bindings + static_cast<uint32_t>(mBindingCount)),
+ sizeof(uint64_t));
ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData)));
ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>)));
+ ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t)));
- return {{bufferData, mBufferCount}, {bindings, mBindingCount}};
+ return {{bufferData, mBufferCount},
+ {bindings, mBindingCount},
+ {unverifiedBufferSizes, mUnverifiedBufferCount}};
}
} // namespace dawn_native
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 8c0061c..4c3c4c6 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -79,10 +79,12 @@
};
BindingIndex GetBindingCount() const;
+ BindingIndex GetBufferCount() const;
// Returns |BindingIndex| because dynamic buffers are packed at the front.
BindingIndex GetDynamicBufferCount() const;
uint32_t GetDynamicUniformBufferCount() const;
uint32_t GetDynamicStorageBufferCount() const;
+ uint32_t GetUnverifiedBufferCount() const;
struct BufferBindingData {
uint64_t offset;
@@ -92,6 +94,7 @@
struct BindingDataPointers {
ityp::span<BindingIndex, BufferBindingData> const bufferData = {};
ityp::span<BindingIndex, Ref<ObjectBase>> const bindings = {};
+ ityp::span<uint32_t, uint64_t> const unverifiedBufferSizes = {};
};
// Compute the amount of space / alignment required to store bindings for a bind group of
@@ -119,6 +122,7 @@
BindingIndex mBindingCount;
BindingIndex mBufferCount{0}; // |BindingIndex| because buffers are packed at the front.
+ uint32_t mUnverifiedBufferCount = 0; // Buffers with minimum buffer size unspecified
uint32_t mDynamicUniformBufferCount = 0;
uint32_t mDynamicStorageBufferCount = 0;
diff --git a/src/dawn_native/BindingInfo.h b/src/dawn_native/BindingInfo.h
index 738c5ae..5cd6aad 100644
--- a/src/dawn_native/BindingInfo.h
+++ b/src/dawn_native/BindingInfo.h
@@ -33,6 +33,7 @@
static constexpr BindingIndex kMaxBindingsPerGroupTyped = BindingIndex(kMaxBindingsPerGroup);
struct BindingInfo {
+ BindingNumber binding;
wgpu::ShaderStage visibility;
wgpu::BindingType type;
Format::Type textureComponentType = Format::Type::Float;
@@ -40,8 +41,12 @@
wgpu::TextureFormat storageTextureFormat = wgpu::TextureFormat::Undefined;
bool hasDynamicOffset = false;
bool multisampled = false;
+ uint64_t minBufferBindingSize = 0;
};
+ // For buffer size validation
+ using RequiredBufferSizes = std::array<std::vector<uint64_t>, kMaxBindGroups>;
+
} // namespace dawn_native
#endif // DAWNNATIVE_BINDINGINFO_H_
diff --git a/src/dawn_native/CommandBufferStateTracker.cpp b/src/dawn_native/CommandBufferStateTracker.cpp
index ff99d7b..20643ed 100644
--- a/src/dawn_native/CommandBufferStateTracker.cpp
+++ b/src/dawn_native/CommandBufferStateTracker.cpp
@@ -24,6 +24,21 @@
namespace dawn_native {
+ namespace {
+ bool BufferSizesAtLeastAsBig(const ityp::span<uint32_t, uint64_t> unverifiedBufferSizes,
+ const std::vector<uint64_t>& pipelineMinimumBufferSizes) {
+ ASSERT(unverifiedBufferSizes.size() == pipelineMinimumBufferSizes.size());
+
+ for (uint32_t i = 0; i < unverifiedBufferSizes.size(); ++i) {
+ if (unverifiedBufferSizes[i] < pipelineMinimumBufferSizes[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ } // namespace
+
enum ValidationAspect {
VALIDATION_ASPECT_PIPELINE,
VALIDATION_ASPECT_BIND_GROUPS,
@@ -87,7 +102,9 @@
for (uint32_t i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) {
if (mBindgroups[i] == nullptr ||
- mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout()) {
+ mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout() ||
+ !BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(),
+ (*mMinimumBufferSizes)[i])) {
matches = false;
break;
}
@@ -123,7 +140,27 @@
}
if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) {
- return DAWN_VALIDATION_ERROR("Missing bind group");
+ for (uint32_t i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) {
+ if (mBindgroups[i] == nullptr) {
+ return DAWN_VALIDATION_ERROR("Missing bind group " + std::to_string(i));
+ } else if (mLastPipelineLayout->GetBindGroupLayout(i) !=
+ mBindgroups[i]->GetLayout()) {
+ return DAWN_VALIDATION_ERROR(
+ "Pipeline and bind group layout doesn't match for bind group " +
+ std::to_string(i));
+ } else if (!BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(),
+ (*mMinimumBufferSizes)[i])) {
+ return DAWN_VALIDATION_ERROR("Binding sizes too small for bind group " +
+ std::to_string(i));
+ }
+ }
+
+ // The chunk of code above should be similar to the one in |RecomputeLazyAspects|.
+ // It returns the first invalid state found. We shouldn't be able to reach this line
+ // because to have invalid aspects one of the above conditions must have failed earlier.
+ // If this is reached, make sure lazy aspects and the error checks above are consistent.
+ UNREACHABLE();
+ return DAWN_VALIDATION_ERROR("Bind groups invalid");
}
if (aspects[VALIDATION_ASPECT_PIPELINE]) {
@@ -157,6 +194,7 @@
void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
mLastPipelineLayout = pipeline->GetLayout();
+ mMinimumBufferSizes = &pipeline->GetMinimumBufferSizes();
mAspects.set(VALIDATION_ASPECT_PIPELINE);
diff --git a/src/dawn_native/CommandBufferStateTracker.h b/src/dawn_native/CommandBufferStateTracker.h
index 8c9c989..478429c 100644
--- a/src/dawn_native/CommandBufferStateTracker.h
+++ b/src/dawn_native/CommandBufferStateTracker.h
@@ -16,6 +16,7 @@
#define DAWNNATIVE_COMMANDBUFFERSTATETRACKER_H
#include "common/Constants.h"
+#include "dawn_native/BindingInfo.h"
#include "dawn_native/Error.h"
#include "dawn_native/Forward.h"
@@ -57,6 +58,8 @@
PipelineLayoutBase* mLastPipelineLayout = nullptr;
RenderPipelineBase* mLastRenderPipeline = nullptr;
+
+ const RequiredBufferSizes* mMinimumBufferSizes = nullptr;
};
} // namespace dawn_native
diff --git a/src/dawn_native/ComputePipeline.cpp b/src/dawn_native/ComputePipeline.cpp
index c1394c6..ee49b11 100644
--- a/src/dawn_native/ComputePipeline.cpp
+++ b/src/dawn_native/ComputePipeline.cpp
@@ -19,6 +19,13 @@
namespace dawn_native {
+ namespace {
+ RequiredBufferSizes ComputeMinBufferSizes(const ComputePipelineDescriptor* descriptor) {
+ return descriptor->computeStage.module->ComputeRequiredBufferSizesForLayout(
+ descriptor->layout);
+ }
+ } // anonymous namespace
+
MaybeError ValidateComputePipelineDescriptor(DeviceBase* device,
const ComputePipelineDescriptor* descriptor) {
if (descriptor->nextInChain != nullptr) {
@@ -38,7 +45,10 @@
ComputePipelineBase::ComputePipelineBase(DeviceBase* device,
const ComputePipelineDescriptor* descriptor)
- : PipelineBase(device, descriptor->layout, wgpu::ShaderStage::Compute),
+ : PipelineBase(device,
+ descriptor->layout,
+ wgpu::ShaderStage::Compute,
+ ComputeMinBufferSizes(descriptor)),
mModule(descriptor->computeStage.module),
mEntryPoint(descriptor->computeStage.entryPoint) {
}
diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp
index c0c48b6..09771e7 100644
--- a/src/dawn_native/Pipeline.cpp
+++ b/src/dawn_native/Pipeline.cpp
@@ -43,8 +43,12 @@
PipelineBase::PipelineBase(DeviceBase* device,
PipelineLayoutBase* layout,
- wgpu::ShaderStage stages)
- : CachedObject(device), mStageMask(stages), mLayout(layout) {
+ wgpu::ShaderStage stages,
+ RequiredBufferSizes minimumBufferSizes)
+ : CachedObject(device),
+ mStageMask(stages),
+ mLayout(layout),
+ mMinimumBufferSizes(std::move(minimumBufferSizes)) {
}
PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@@ -66,6 +70,11 @@
return mLayout.Get();
}
+ const RequiredBufferSizes& PipelineBase::GetMinimumBufferSizes() const {
+ ASSERT(!IsError());
+ return mMinimumBufferSizes;
+ }
+
MaybeError PipelineBase::ValidateGetBindGroupLayout(uint32_t groupIndex) {
DAWN_TRY(GetDevice()->ValidateIsAlive());
DAWN_TRY(GetDevice()->ValidateObject(this));
diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h
index d248e49..bfc846b 100644
--- a/src/dawn_native/Pipeline.h
+++ b/src/dawn_native/Pipeline.h
@@ -39,9 +39,13 @@
PipelineLayoutBase* GetLayout();
const PipelineLayoutBase* GetLayout() const;
BindGroupLayoutBase* GetBindGroupLayout(uint32_t groupIndex);
+ const RequiredBufferSizes& GetMinimumBufferSizes() const;
protected:
- PipelineBase(DeviceBase* device, PipelineLayoutBase* layout, wgpu::ShaderStage stages);
+ PipelineBase(DeviceBase* device,
+ PipelineLayoutBase* layout,
+ wgpu::ShaderStage stages,
+ RequiredBufferSizes bufferSizes);
PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
private:
@@ -49,6 +53,7 @@
wgpu::ShaderStage mStageMask;
Ref<PipelineLayoutBase> mLayout;
+ RequiredBufferSizes mMinimumBufferSizes;
};
} // namespace dawn_native
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 418ff18..eedd6a9 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -25,7 +25,9 @@
namespace {
- bool operator==(const BindGroupLayoutEntry& lhs, const BindGroupLayoutEntry& rhs) {
+ bool InferredBindGroupLayoutEntriesCompatible(const BindGroupLayoutEntry& lhs,
+ const BindGroupLayoutEntry& rhs) {
+ // Minimum buffer binding size excluded because we take the maximum seen across stages
return lhs.binding == rhs.binding && lhs.visibility == rhs.visibility &&
lhs.type == rhs.type && lhs.hasDynamicOffset == rhs.hasDynamicOffset &&
lhs.multisampled == rhs.multisampled && lhs.viewDimension == rhs.viewDimension &&
@@ -170,18 +172,29 @@
bindingSlot.textureComponentType =
Format::FormatTypeToTextureComponentType(bindingInfo.textureComponentType);
bindingSlot.storageTextureFormat = bindingInfo.storageTextureFormat;
+ bindingSlot.minBufferBindingSize = bindingInfo.minBufferBindingSize;
{
const auto& it = usedBindingsMap[group].find(bindingNumber);
if (it != usedBindingsMap[group].end()) {
- if (bindingSlot == entryData[group][it->second]) {
- // Already used and the data is the same. Continue.
- continue;
- } else {
+ BindGroupLayoutEntry* existingEntry = &entryData[group][it->second];
+
+ // Check if any properties are incompatible with existing entry
+ // If compatible, we will merge some properties
+ if (!InferredBindGroupLayoutEntriesCompatible(*existingEntry,
+ bindingSlot)) {
return DAWN_VALIDATION_ERROR(
"Duplicate binding in default pipeline layout initialization "
"not compatible with previous declaration");
}
+
+ // Use the max |minBufferBindingSize| we find
+ existingEntry->minBufferBindingSize =
+ std::max(existingEntry->minBufferBindingSize,
+ bindingSlot.minBufferBindingSize);
+
+ // Already used slot, continue
+ continue;
}
}
diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp
index dc4a508..df0a8d7 100644
--- a/src/dawn_native/RenderPipeline.cpp
+++ b/src/dawn_native/RenderPipeline.cpp
@@ -183,6 +183,29 @@
return {};
}
+ RequiredBufferSizes ComputeMinBufferSizes(const RenderPipelineDescriptor* descriptor) {
+ RequiredBufferSizes bufferSizes =
+ descriptor->vertexStage.module->ComputeRequiredBufferSizesForLayout(
+ descriptor->layout);
+
+ // Merge the two buffer size requirements by taking the larger element from each
+ if (descriptor->fragmentStage != nullptr) {
+ RequiredBufferSizes fragmentSizes =
+ descriptor->fragmentStage->module->ComputeRequiredBufferSizesForLayout(
+ descriptor->layout);
+
+ for (uint32_t group = 0; group < bufferSizes.size(); ++group) {
+ ASSERT(bufferSizes[group].size() == fragmentSizes[group].size());
+ for (size_t i = 0; i < bufferSizes[group].size(); ++i) {
+ bufferSizes[group][i] =
+ std::max(bufferSizes[group][i], fragmentSizes[group][i]);
+ }
+ }
+ }
+
+ return bufferSizes;
+ }
+
} // anonymous namespace
// Helper functions
@@ -380,7 +403,8 @@
const RenderPipelineDescriptor* descriptor)
: PipelineBase(device,
descriptor->layout,
- wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment),
+ wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
+ ComputeMinBufferSizes(descriptor)),
mAttachmentState(device->GetOrCreateAttachmentState(descriptor)),
mPrimitiveTopology(descriptor->primitiveTopology),
mSampleMask(descriptor->sampleMask),
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index 6c27736..dc79d75 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -558,6 +558,11 @@
info->viewDimension = ToWGPUTextureViewDimension(binding.texture_dimension);
break;
}
+ case wgpu::BindingType::UniformBuffer:
+ case wgpu::BindingType::StorageBuffer:
+ case wgpu::BindingType::ReadonlyStorageBuffer:
+ info->minBufferBindingSize = binding.minimum_buffer_size;
+ break;
default:
break;
}
@@ -712,6 +717,15 @@
info->id = resource.id;
info->base_type_id = resource.base_type_id;
+ if (bindingType == wgpu::BindingType::UniformBuffer ||
+ bindingType == wgpu::BindingType::StorageBuffer ||
+ bindingType == wgpu::BindingType::ReadonlyStorageBuffer) {
+ // Determine buffer size, with a minimum of 1 element in the runtime array
+ spirv_cross::SPIRType type = compiler.get_type(info->base_type_id);
+ info->minBufferBindingSize =
+ compiler.get_declared_struct_size_runtime_array(type, 1);
+ }
+
switch (bindingType) {
case wgpu::BindingType::SampledTexture: {
spirv_cross::SPIRType::ImageType imageType =
@@ -867,6 +881,47 @@
return mExecutionModel;
}
+ RequiredBufferSizes ShaderModuleBase::ComputeRequiredBufferSizesForLayout(
+ const PipelineLayoutBase* layout) const {
+ RequiredBufferSizes bufferSizes;
+ for (uint32_t group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
+ bufferSizes[group] =
+ GetBindGroupMinBufferSizes(mBindingInfo[group], layout->GetBindGroupLayout(group));
+ }
+
+ return bufferSizes;
+ }
+
+ std::vector<uint64_t> ShaderModuleBase::GetBindGroupMinBufferSizes(
+ const BindingInfoMap& shaderMap,
+ const BindGroupLayoutBase* layout) const {
+ std::vector<uint64_t> requiredBufferSizes(layout->GetUnverifiedBufferCount());
+ uint32_t packedIdx = 0;
+
+ for (BindingIndex bindingIndex{0}; bindingIndex < layout->GetBufferCount();
+ ++bindingIndex) {
+ const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);
+ if (bindingInfo.minBufferBindingSize != 0) {
+ // Skip bindings that have minimum buffer size set in the layout
+ continue;
+ }
+
+ ASSERT(packedIdx < requiredBufferSizes.size());
+ const auto& shaderInfo = shaderMap.find(bindingInfo.binding);
+ if (shaderInfo != shaderMap.end()) {
+ requiredBufferSizes[packedIdx] = shaderInfo->second.minBufferBindingSize;
+ } else {
+ // We have to include buffers if they are included in the bind group's
+ // packed vector. We don't actually need to check these at draw time, so
+ // if this is a problem in the future we can optimize it further.
+ requiredBufferSizes[packedIdx] = 0;
+ }
+ ++packedIdx;
+ }
+
+ return requiredBufferSizes;
+ }
+
MaybeError ShaderModuleBase::ValidateCompatibilityWithPipelineLayout(
const PipelineLayoutBase* layout) const {
ASSERT(!IsError());
@@ -889,7 +944,7 @@
}
MaybeError ShaderModuleBase::ValidateCompatibilityWithBindGroupLayout(
- size_t group,
+ uint32_t group,
const BindGroupLayoutBase* layout) const {
ASSERT(!IsError());
@@ -980,7 +1035,16 @@
case wgpu::BindingType::UniformBuffer:
case wgpu::BindingType::ReadonlyStorageBuffer:
- case wgpu::BindingType::StorageBuffer:
+ case wgpu::BindingType::StorageBuffer: {
+ if (bindingInfo.minBufferBindingSize != 0 &&
+ moduleInfo.minBufferBindingSize > bindingInfo.minBufferBindingSize) {
+ return DAWN_VALIDATION_ERROR(
+ "The minimum buffer size of the bind group layout entry is smaller "
+ "than " +
+ GetShaderDeclarationString(group, bindingNumber));
+ }
+ break;
+ }
case wgpu::BindingType::Sampler:
case wgpu::BindingType::ComparisonSampler:
break;
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index 4cd9656..9e68896 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -63,8 +63,8 @@
using BindingInfo::visibility;
};
- using ModuleBindingInfo =
- std::array<std::map<BindingNumber, ShaderBindingInfo>, kMaxBindGroups>;
+ using BindingInfoMap = std::map<BindingNumber, ShaderBindingInfo>;
+ using ModuleBindingInfo = std::array<BindingInfoMap, kMaxBindGroups>;
const ModuleBindingInfo& GetBindingInfo() const;
const std::bitset<kMaxVertexAttributes>& GetUsedVertexAttributes() const;
@@ -77,6 +77,9 @@
MaybeError ValidateCompatibilityWithPipelineLayout(const PipelineLayoutBase* layout) const;
+ RequiredBufferSizes ComputeRequiredBufferSizesForLayout(
+ const PipelineLayoutBase* layout) const;
+
// Functors necessary for the unordered_set<ShaderModuleBase*>-based cache.
struct HashFunc {
size_t operator()(const ShaderModuleBase* module) const;
@@ -99,9 +102,12 @@
ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
MaybeError ValidateCompatibilityWithBindGroupLayout(
- size_t group,
+ uint32_t group,
const BindGroupLayoutBase* layout) const;
+ std::vector<uint64_t> GetBindGroupMinBufferSizes(const BindingInfoMap& shaderMap,
+ const BindGroupLayoutBase* layout) const;
+
// Different implementations reflection into the shader depending on
// whether using spvc, or directly accessing spirv-cross.
MaybeError ExtractSpirvInfoWithSpvc();
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 1723019..a1e7741 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -189,6 +189,7 @@
"unittests/validation/FenceValidationTests.cpp",
"unittests/validation/GetBindGroupLayoutValidationTests.cpp",
"unittests/validation/IndexBufferValidationTests.cpp",
+ "unittests/validation/MinimumBufferSizeValidationTests.cpp",
"unittests/validation/QuerySetValidationTests.cpp",
"unittests/validation/QueueSubmitValidationTests.cpp",
"unittests/validation/RenderBundleValidationTests.cpp",
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp
index 17ab3d7..4f8d783 100644
--- a/src/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -877,7 +877,9 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
renderPassEncoder.SetPipeline(renderPipeline);
- renderPassEncoder.SetBindGroup(0, bindGroup, count, offsets);
+ if (bindGroup != nullptr) {
+ renderPassEncoder.SetBindGroup(0, bindGroup, count, offsets);
+ }
renderPassEncoder.Draw(3);
renderPassEncoder.EndPass();
if (!expectation) {
@@ -896,7 +898,9 @@
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
computePassEncoder.SetPipeline(computePipeline);
- computePassEncoder.SetBindGroup(0, bindGroup, count, offsets);
+ if (bindGroup != nullptr) {
+ computePassEncoder.SetBindGroup(0, bindGroup, count, offsets);
+ }
computePassEncoder.Dispatch(1);
computePassEncoder.EndPass();
if (!expectation) {
@@ -926,6 +930,12 @@
TestComputePassBindGroup(bindGroup, offsets.data(), 3, true);
}
+// Draw/dispatch with a bind group missing is invalid
+TEST_F(SetBindGroupValidationTest, MissingBindGroup) {
+ TestRenderPassBindGroup(nullptr, nullptr, 0, false);
+ TestComputePassBindGroup(nullptr, nullptr, 0, false);
+}
+
// Setting bind group after a draw / dispatch should re-verify the layout is compatible
TEST_F(SetBindGroupValidationTest, VerifyGroupIfChangedAfterAction) {
// Set up the bind group
diff --git a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
index c14a3d2..f4a8ff0 100644
--- a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
+++ b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -106,6 +106,7 @@
binding.binding = 0;
binding.type = wgpu::BindingType::UniformBuffer;
binding.multisampled = false;
+ binding.minBufferBindingSize = 4 * sizeof(float);
wgpu::BindGroupLayoutDescriptor desc = {};
desc.entryCount = 1;
@@ -155,6 +156,7 @@
binding.type = wgpu::BindingType::UniformBuffer;
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
+ binding.minBufferBindingSize = 4 * sizeof(float);
wgpu::BindGroupLayoutDescriptor desc = {};
desc.entryCount = 1;
@@ -169,6 +171,7 @@
binding.binding = 0;
binding.hasDynamicOffset = false;
binding.multisampled = false;
+ binding.minBufferBindingSize = 4 * sizeof(float);
wgpu::BindGroupLayoutDescriptor desc = {};
desc.entryCount = 1;
@@ -213,6 +216,7 @@
EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get());
}
+ binding.minBufferBindingSize = 0;
{
binding.type = wgpu::BindingType::SampledTexture;
wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"(
@@ -392,6 +396,7 @@
binding.visibility = kVisibilityAll;
binding.hasDynamicOffset = false;
binding.multisampled = false;
+ binding.minBufferBindingSize = 4 * sizeof(float);
wgpu::BindGroupLayoutDescriptor desc = {};
desc.entryCount = 1;
diff --git a/src/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp b/src/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
new file mode 100644
index 0000000..ba00c07
--- /dev/null
+++ b/src/tests/unittests/validation/MinimumBufferSizeValidationTests.cpp
@@ -0,0 +1,587 @@
+// 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 set;
+ uint32_t binding;
+ std::string text;
+ uint64_t size;
+ wgpu::BindingType type = wgpu::BindingType::StorageBuffer;
+ 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); });
+ }
+
+ // Convert binding type to a glsl string
+ std::string BindingTypeToStr(wgpu::BindingType type) {
+ switch (type) {
+ case wgpu::BindingType::UniformBuffer:
+ return "uniform";
+ case wgpu::BindingType::StorageBuffer:
+ return "buffer";
+ case wgpu::BindingType::ReadonlyStorageBuffer:
+ return "readonly buffer";
+ default:
+ UNREACHABLE();
+ return "";
+ }
+ }
+
+ // Creates a bind group with given bindings for shader text
+ std::string GenerateBindingString(const std::string& layout,
+ const std::vector<BindingDescriptor>& bindings) {
+ std::ostringstream ostream;
+ size_t ctr = 0;
+ for (const BindingDescriptor& b : bindings) {
+ ostream << "layout(" << layout << ", set = " << b.set << ", binding = " << b.binding
+ << ") " << BindingTypeToStr(b.type) << " b" << ctr++ << "{\n"
+ << b.text << ";\n};\n";
+ }
+ return ostream.str();
+ }
+
+ // Used for adding custom types available throughout the tests
+ static const std::string kStructs = "struct ThreeFloats{float f1; float f2; float f3;};\n";
+
+ // Creates a compute shader with given bindings
+ std::string CreateComputeShaderWithBindings(const std::string& layoutType,
+ const std::vector<BindingDescriptor>& bindings) {
+ return R"(
+ #version 450
+ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+ )" +
+ kStructs + GenerateBindingString(layoutType, bindings) + "void main() {}";
+ }
+
+ // Creates a vertex shader with given bindings
+ std::string CreateVertexShaderWithBindings(const std::string& layoutType,
+ const std::vector<BindingDescriptor>& bindings) {
+ return "#version 450\n" + kStructs + GenerateBindingString(layoutType, bindings) +
+ "void main() {}";
+ }
+
+ // Creates a fragment shader with given bindings
+ std::string CreateFragmentShaderWithBindings(const std::string& layoutType,
+ const std::vector<BindingDescriptor>& bindings) {
+ return R"(
+ #version 450
+ layout(location = 0) out vec4 fragColor;
+ )" +
+ kStructs + GenerateBindingString(layoutType, bindings) + "void main() {}";
+ }
+
+ // 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, utils::SingleShaderStage::Compute, 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.computeStage.module = csModule;
+ csDesc.computeStage.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, utils::SingleShaderStage::Vertex, vertexShader.c_str());
+
+ wgpu::ShaderModule fsModule = utils::CreateShaderModule(
+ device, utils::SingleShaderStage::Fragment, fragShader.c_str());
+
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
+ pipelineDescriptor.vertexStage.module = vsModule;
+ pipelineDescriptor.cFragmentStage.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.type = b.type;
+ e.visibility = b.visibility;
+ e.minBufferBindingSize = 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, "float a; float b", 8}, {0, 1, "float c", 4}};
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", 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, "float a; float b", 8}, {0, 1, "float c", 4}};
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", 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, "float a; float b", 8},
+ {0, 1, "float c", 4}};
+ std::vector<BindingDescriptor> bg1Bindings = {{1, 0, "float d; float e; float f", 12},
+ {1, 1, "mat2 g", 32}};
+ std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", bindings);
+
+ CheckSizeBounds({8, 4, 12, 32}, [&](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, "float a; float b", 8}, {0, 1, "float 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) {
+ auto MakeLayout = [&](uint64_t size) {
+ return utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::UniformBuffer, false, false,
+ wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
+ wgpu::TextureFormat::Undefined, 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, "float a; float b", 8}, {0, 1, "float c", 4}};
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", 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, "float a; float b", 8},
+ {0, 0, "float c", 4},
+ {0, 4, "float d; float e; float f", 12}};
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", 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, "float a; float b", 8},
+ {0, 1, "float c", 4}};
+ std::vector<BindingDescriptor> bg1Bindings = {{1, 0, "float d; float e; float f", 12},
+ {1, 1, "mat2 g", 32}};
+ std::vector<BindingDescriptor> bindings = CombineBindings({bg0Bindings, bg1Bindings});
+
+ std::string computeShader = CreateComputeShaderWithBindings("std140", bindings);
+ std::string vertexShader = CreateVertexShaderWithBindings("std140", {});
+ std::string fragShader = CreateFragmentShaderWithBindings("std140", 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, 32}, [&](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(
+ const std::string& layoutType,
+ std::initializer_list<std::vector<BindingDescriptor>> bindings) {
+ std::vector<BindingDescriptor> combinedBindings = CombineBindings(bindings);
+ std::string computeShader = CreateComputeShaderWithBindings(layoutType, combinedBindings);
+ std::string vertexShader = CreateVertexShaderWithBindings(layoutType, {});
+ std::string fragShader = CreateFragmentShaderWithBindings(layoutType, 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;
+ }
+ }
+};
+
+// Various bindings in std140 have correct minimum size reflection
+TEST_F(MinBufferSizeDefaultLayoutTests, std140Inferred) {
+ CheckShaderBindingSizeReflection("std140", {{{0, 0, "float a", 4},
+ {0, 1, "float b[]", 16},
+ {0, 2, "mat2 c", 32},
+ {0, 3, "int d; float e[]", 32},
+ {0, 4, "ThreeFloats f", 12},
+ {0, 5, "ThreeFloats g[]", 16}}});
+}
+
+// Various bindings in std430 have correct minimum size reflection
+TEST_F(MinBufferSizeDefaultLayoutTests, std430Inferred) {
+ CheckShaderBindingSizeReflection("std430", {{{0, 0, "float a", 4},
+ {0, 1, "float b[]", 4},
+ {0, 2, "mat2 c", 16},
+ {0, 3, "int d; float e[]", 8},
+ {0, 4, "ThreeFloats f", 12},
+ {0, 5, "ThreeFloats g[]", 12}}});
+}
+
+// Sizes are inferred for all binding types with std140 layout
+TEST_F(MinBufferSizeDefaultLayoutTests, std140BindingTypes) {
+ CheckShaderBindingSizeReflection(
+ "std140", {{{0, 0, "int d; float e[]", 32, wgpu::BindingType::UniformBuffer},
+ {0, 1, "ThreeFloats f", 12, wgpu::BindingType::StorageBuffer},
+ {0, 2, "ThreeFloats g[]", 16, wgpu::BindingType::ReadonlyStorageBuffer}}});
+}
+
+// Sizes are inferred for all binding types with std430 layout
+TEST_F(MinBufferSizeDefaultLayoutTests, std430BindingTypes) {
+ CheckShaderBindingSizeReflection(
+ "std430", {{{0, 0, "float a", 4, wgpu::BindingType::StorageBuffer},
+ {0, 1, "ThreeFloats b[]", 12, wgpu::BindingType::ReadonlyStorageBuffer}}});
+}
+
+// Various bindings have correct size across multiple groups
+TEST_F(MinBufferSizeDefaultLayoutTests, std140MultipleBindGroups) {
+ CheckShaderBindingSizeReflection("std140",
+ {{{0, 0, "float a", 4}, {0, 1, "float b[]", 16}},
+ {{1, 2, "mat2 c", 32}, {1, 3, "int d; float e[]", 32}},
+ {{2, 4, "ThreeFloats f", 12}},
+ {{3, 5, "ThreeFloats g[]", 16}}});
+}
+
+// Various bindings have correct size across multiple groups
+TEST_F(MinBufferSizeDefaultLayoutTests, std430MultipleBindGroups) {
+ CheckShaderBindingSizeReflection("std430",
+ {{{0, 0, "float a", 4}, {0, 1, "float b[]", 4}},
+ {{1, 2, "mat2 c", 16}, {1, 3, "int d; float e[]", 8}},
+ {{2, 4, "ThreeFloats f", 12}},
+ {{3, 5, "ThreeFloats g[]", 12}}});
+}
+
+// Minimum size should be the max requirement of both vertex and fragment stages
+TEST_F(MinBufferSizeDefaultLayoutTests, RenderPassConsidersBothStages) {
+ std::string vertexShader = CreateVertexShaderWithBindings(
+ "std140", {{0, 0, "float a", 4, wgpu::BindingType::UniformBuffer},
+ {0, 1, "float b[]", 16, wgpu::BindingType::UniformBuffer}});
+ std::string fragShader = CreateFragmentShaderWithBindings(
+ "std140", {{0, 0, "float a; float b", 8, wgpu::BindingType::UniformBuffer},
+ {0, 1, "float c; float d", 8, wgpu::BindingType::UniformBuffer}});
+
+ wgpu::BindGroupLayout renderLayout = GetBGLFromRenderShaders(vertexShader, fragShader, 0);
+
+ CheckLayoutBindingSizeValidation(renderLayout, {{0, 0, "", 8}, {0, 1, "", 16}});
+}
diff --git a/src/tests/unittests/validation/RenderBundleValidationTests.cpp b/src/tests/unittests/validation/RenderBundleValidationTests.cpp
index 657e693..4e372e9 100644
--- a/src/tests/unittests/validation/RenderBundleValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderBundleValidationTests.cpp
@@ -66,8 +66,8 @@
InitializeRenderPipelineDescriptor(&descriptor);
pipeline = device.CreateRenderPipeline(&descriptor);
- float data[4];
- wgpu::Buffer buffer = utils::CreateBufferFromData(device, data, 4 * sizeof(float),
+ float data[8];
+ wgpu::Buffer buffer = utils::CreateBufferFromData(device, data, 8 * sizeof(float),
wgpu::BufferUsage::Uniform);
constexpr static float kVertices[] = {-1.f, 1.f, 1.f, -1.f, -1.f, 1.f};
@@ -84,13 +84,13 @@
utils::CreateBufferFromData(device, kVertices, sizeof(kVertices),
wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Storage);
- bg0 = utils::MakeBindGroup(device, bgls[0], {{0, buffer, 0, 4 * sizeof(float)}});
+ bg0 = utils::MakeBindGroup(device, bgls[0], {{0, buffer, 0, 8 * sizeof(float)}});
bg1 = utils::MakeBindGroup(
device, bgls[1],
{{0, buffer, 0, 4 * sizeof(float)}, {1, storageBuffer, 0, sizeof(kVertices)}});
bg1Vertex = utils::MakeBindGroup(device, bgls[1],
- {{0, buffer, 0, 4 * sizeof(float)},
+ {{0, buffer, 0, 8 * sizeof(float)},
{1, vertexStorageBuffer, 0, sizeof(kVertices)}});
}
diff --git a/src/tests/unittests/wire/WireArgumentTests.cpp b/src/tests/unittests/wire/WireArgumentTests.cpp
index d2266b8..deb3822 100644
--- a/src/tests/unittests/wire/WireArgumentTests.cpp
+++ b/src/tests/unittests/wire/WireArgumentTests.cpp
@@ -315,7 +315,8 @@
false,
WGPUTextureViewDimension_2D,
WGPUTextureComponentType_Float,
- WGPUTextureFormat_RGBA8Unorm},
+ WGPUTextureFormat_RGBA8Unorm,
+ 0},
{1,
WGPUShaderStage_Vertex,
WGPUBindingType_SampledTexture,
@@ -323,7 +324,8 @@
false,
WGPUTextureViewDimension_2D,
WGPUTextureComponentType_Float,
- WGPUTextureFormat_RGBA8Unorm},
+ WGPUTextureFormat_RGBA8Unorm,
+ 0},
{2,
static_cast<WGPUShaderStage>(WGPUShaderStage_Vertex | WGPUShaderStage_Fragment),
WGPUBindingType_UniformBuffer,
@@ -331,7 +333,8 @@
false,
WGPUTextureViewDimension_2D,
WGPUTextureComponentType_Float,
- WGPUTextureFormat_RGBA8Unorm},
+ WGPUTextureFormat_RGBA8Unorm,
+ 0},
};
WGPUBindGroupLayoutDescriptor bglDescriptor = {};
bglDescriptor.entryCount = NUM_BINDINGS;
diff --git a/src/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/tests/white_box/D3D12DescriptorHeapTests.cpp
index 9b4ffe7..6750586 100644
--- a/src/tests/white_box/D3D12DescriptorHeapTests.cpp
+++ b/src/tests/white_box/D3D12DescriptorHeapTests.cpp
@@ -732,12 +732,11 @@
wgpu::Buffer uniformBuffer = utils::CreateBufferFromData(
device, &fillColor, sizeof(fillColor), wgpu::BufferUsage::Uniform);
- bindGroups.push_back(
- utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
- {{0, transformBuffer, 0, sizeof(transformBuffer)},
- {1, sampler},
- {2, textureView},
- {3, uniformBuffer, 0, sizeof(fillColor)}}));
+ bindGroups.push_back(utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+ {{0, transformBuffer, 0, sizeof(transform)},
+ {1, sampler},
+ {2, textureView},
+ {3, uniformBuffer, 0, sizeof(fillColor)}}));
}
std::array<float, 4> redColor = {1, 0, 0, 1};