blob: 647587c26df857c594d0d7d7ec1a34fefa44ff39 [file] [log] [blame]
// Copyright 2017 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 "dawn_native/BindGroup.h"
#include "common/Assert.h"
#include "common/Math.h"
#include "dawn_native/BindGroupLayout.h"
#include "dawn_native/Buffer.h"
#include "dawn_native/Device.h"
#include "dawn_native/Sampler.h"
#include "dawn_native/Texture.h"
namespace dawn_native {
namespace {
// Helper functions to perform binding-type specific validation
MaybeError ValidateBufferBinding(const DeviceBase* device,
const BindGroupEntry& entry,
wgpu::BufferUsage requiredUsage) {
if (entry.buffer == nullptr || entry.sampler != nullptr ||
entry.textureView != nullptr) {
return DAWN_VALIDATION_ERROR("expected buffer binding");
}
DAWN_TRY(device->ValidateObject(entry.buffer));
uint64_t bufferSize = entry.buffer->GetSize();
// Handle wgpu::WholeSize, avoiding overflows.
if (entry.offset > bufferSize) {
return DAWN_VALIDATION_ERROR("Buffer binding doesn't fit in the buffer");
}
uint64_t bindingSize =
(entry.size == wgpu::kWholeSize) ? bufferSize - entry.offset : entry.size;
if (bindingSize > bufferSize) {
return DAWN_VALIDATION_ERROR("Buffer binding size larger than the buffer");
}
if (bindingSize == 0) {
return DAWN_VALIDATION_ERROR("Buffer binding size cannot be zero.");
}
// Note that no overflow can happen because we already checked that
// bufferSize >= bindingSize
if (entry.offset > bufferSize - bindingSize) {
return DAWN_VALIDATION_ERROR("Buffer binding doesn't fit in the buffer");
}
if (!IsAligned(entry.offset, 256)) {
return DAWN_VALIDATION_ERROR(
"Buffer offset for bind group needs to be 256-byte aligned");
}
if (!(entry.buffer->GetUsage() & requiredUsage)) {
return DAWN_VALIDATION_ERROR("buffer binding usage mismatch");
}
return {};
}
MaybeError ValidateTextureBinding(const DeviceBase* device,
const BindGroupEntry& entry,
wgpu::TextureUsage requiredUsage,
const BindingInfo& bindingInfo) {
if (entry.textureView == nullptr || entry.sampler != nullptr ||
entry.buffer != nullptr) {
return DAWN_VALIDATION_ERROR("expected texture binding");
}
DAWN_TRY(device->ValidateObject(entry.textureView));
TextureBase* texture = entry.textureView->GetTexture();
if (!(texture->GetUsage() & requiredUsage)) {
return DAWN_VALIDATION_ERROR("texture binding usage mismatch");
}
if (texture->IsMultisampledTexture() != bindingInfo.multisampled) {
return DAWN_VALIDATION_ERROR("texture multisampling mismatch");
}
switch (requiredUsage) {
case wgpu::TextureUsage::Sampled: {
if (!texture->GetFormat().HasComponentType(bindingInfo.textureComponentType)) {
return DAWN_VALIDATION_ERROR("texture component type usage mismatch");
}
break;
}
case wgpu::TextureUsage::Storage: {
if (texture->GetFormat().format != bindingInfo.storageTextureFormat) {
return DAWN_VALIDATION_ERROR("storage texture format mismatch");
}
break;
}
default:
UNREACHABLE();
break;
}
if (entry.textureView->GetDimension() != bindingInfo.viewDimension) {
return DAWN_VALIDATION_ERROR("texture view dimension mismatch");
}
return {};
}
MaybeError ValidateSamplerBinding(const DeviceBase* device,
const BindGroupEntry& entry,
wgpu::BindingType bindingType) {
if (entry.sampler == nullptr || entry.textureView != nullptr ||
entry.buffer != nullptr) {
return DAWN_VALIDATION_ERROR("expected sampler binding");
}
DAWN_TRY(device->ValidateObject(entry.sampler));
switch (bindingType) {
case wgpu::BindingType::Sampler:
if (entry.sampler->HasCompareFunction()) {
return DAWN_VALIDATION_ERROR("Did not expect comparison sampler");
}
break;
case wgpu::BindingType::ComparisonSampler:
if (!entry.sampler->HasCompareFunction()) {
return DAWN_VALIDATION_ERROR("Expected comparison sampler");
}
break;
default:
UNREACHABLE();
}
return {};
}
} // anonymous namespace
MaybeError ValidateBindGroupDescriptor(DeviceBase* device,
const BindGroupDescriptor* descriptor) {
if (descriptor->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
DAWN_TRY(device->ValidateObject(descriptor->layout));
if (descriptor->entryCount != descriptor->layout->GetBindingCount()) {
return DAWN_VALIDATION_ERROR("numBindings mismatch");
}
const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
std::bitset<kMaxBindingsPerGroup> bindingsSet;
for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
const BindGroupEntry& entry = descriptor->entries[i];
const auto& it = bindingMap.find(BindingNumber(entry.binding));
if (it == bindingMap.end()) {
return DAWN_VALIDATION_ERROR("setting non-existent binding");
}
BindingIndex bindingIndex = it->second;
ASSERT(bindingIndex < descriptor->layout->GetBindingCount());
if (bindingsSet[bindingIndex]) {
return DAWN_VALIDATION_ERROR("binding set twice");
}
bindingsSet.set(bindingIndex);
const BindingInfo& bindingInfo = descriptor->layout->GetBindingInfo(bindingIndex);
// Perform binding-type specific validation.
switch (bindingInfo.type) {
case wgpu::BindingType::UniformBuffer:
DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Uniform));
break;
case wgpu::BindingType::StorageBuffer:
case wgpu::BindingType::ReadonlyStorageBuffer:
DAWN_TRY(ValidateBufferBinding(device, entry, wgpu::BufferUsage::Storage));
break;
case wgpu::BindingType::SampledTexture:
DAWN_TRY(ValidateTextureBinding(device, entry, wgpu::TextureUsage::Sampled,
bindingInfo));
break;
case wgpu::BindingType::Sampler:
case wgpu::BindingType::ComparisonSampler:
DAWN_TRY(ValidateSamplerBinding(device, entry, bindingInfo.type));
break;
// TODO(jiawei.shao@intel.com): support creating bind group with read-only and
// write-only storage textures.
case wgpu::BindingType::ReadonlyStorageTexture:
case wgpu::BindingType::WriteonlyStorageTexture:
DAWN_TRY(ValidateTextureBinding(device, entry, wgpu::TextureUsage::Storage,
bindingInfo));
break;
case wgpu::BindingType::StorageTexture:
UNREACHABLE();
break;
}
}
// This should always be true because
// - numBindings has to match between the bind group and its layout.
// - Each binding must be set at most once
//
// We don't validate the equality because it wouldn't be possible to cover it with a test.
ASSERT(bindingsSet.count() == bindingMap.size());
return {};
} // anonymous namespace
// BindGroup
BindGroupBase::BindGroupBase(DeviceBase* device,
const BindGroupDescriptor* descriptor,
void* bindingDataStart)
: ObjectBase(device),
mLayout(descriptor->layout),
mBindingData(mLayout->ComputeBindingDataPointers(bindingDataStart)) {
for (BindingIndex i = 0; i < mLayout->GetBindingCount(); ++i) {
// TODO(enga): Shouldn't be needed when bindings are tightly packed.
// This is to fill Ref<ObjectBase> holes with nullptrs.
new (&mBindingData.bindings[i]) Ref<ObjectBase>();
}
for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
const BindGroupEntry& entry = descriptor->entries[i];
BindingIndex bindingIndex =
descriptor->layout->GetBindingIndex(BindingNumber(entry.binding));
ASSERT(bindingIndex < mLayout->GetBindingCount());
// Only a single binding type should be set, so once we found it we can skip to the
// next loop iteration.
if (entry.buffer != nullptr) {
ASSERT(mBindingData.bindings[bindingIndex].Get() == nullptr);
mBindingData.bindings[bindingIndex] = entry.buffer;
mBindingData.bufferData[bindingIndex].offset = entry.offset;
uint64_t bufferSize = (entry.size == wgpu::kWholeSize)
? entry.buffer->GetSize() - entry.offset
: entry.size;
mBindingData.bufferData[bindingIndex].size = bufferSize;
continue;
}
if (entry.textureView != nullptr) {
ASSERT(mBindingData.bindings[bindingIndex].Get() == nullptr);
mBindingData.bindings[bindingIndex] = entry.textureView;
continue;
}
if (entry.sampler != nullptr) {
ASSERT(mBindingData.bindings[bindingIndex].Get() == nullptr);
mBindingData.bindings[bindingIndex] = entry.sampler;
continue;
}
}
}
BindGroupBase::~BindGroupBase() {
if (mLayout) {
ASSERT(!IsError());
for (BindingIndex i = 0; i < mLayout->GetBindingCount(); ++i) {
mBindingData.bindings[i].~Ref<ObjectBase>();
}
}
}
void BindGroupBase::DeleteThis() {
// Add another ref to the layout so that if this is the last ref, the layout
// is destroyed after the bind group. The bind group is slab-allocated inside
// memory owned by the layout (except for the null backend).
Ref<BindGroupLayoutBase> layout = mLayout;
RefCounted::DeleteThis();
}
BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ObjectBase(device, tag), mBindingData() {
}
// static
BindGroupBase* BindGroupBase::MakeError(DeviceBase* device) {
return new BindGroupBase(device, ObjectBase::kError);
}
BindGroupLayoutBase* BindGroupBase::GetLayout() {
ASSERT(!IsError());
return mLayout.Get();
}
const BindGroupLayoutBase* BindGroupBase::GetLayout() const {
ASSERT(!IsError());
return mLayout.Get();
}
BufferBinding BindGroupBase::GetBindingAsBufferBinding(BindingIndex bindingIndex) {
ASSERT(!IsError());
ASSERT(bindingIndex < mLayout->GetBindingCount());
ASSERT(mLayout->GetBindingInfo(bindingIndex).type == wgpu::BindingType::UniformBuffer ||
mLayout->GetBindingInfo(bindingIndex).type == wgpu::BindingType::StorageBuffer ||
mLayout->GetBindingInfo(bindingIndex).type ==
wgpu::BindingType::ReadonlyStorageBuffer);
BufferBase* buffer = static_cast<BufferBase*>(mBindingData.bindings[bindingIndex].Get());
return {buffer, mBindingData.bufferData[bindingIndex].offset,
mBindingData.bufferData[bindingIndex].size};
}
SamplerBase* BindGroupBase::GetBindingAsSampler(BindingIndex bindingIndex) const {
ASSERT(!IsError());
ASSERT(bindingIndex < mLayout->GetBindingCount());
ASSERT(mLayout->GetBindingInfo(bindingIndex).type == wgpu::BindingType::Sampler ||
mLayout->GetBindingInfo(bindingIndex).type == wgpu::BindingType::ComparisonSampler);
return static_cast<SamplerBase*>(mBindingData.bindings[bindingIndex].Get());
}
TextureViewBase* BindGroupBase::GetBindingAsTextureView(BindingIndex bindingIndex) {
ASSERT(!IsError());
ASSERT(bindingIndex < mLayout->GetBindingCount());
ASSERT(mLayout->GetBindingInfo(bindingIndex).type == wgpu::BindingType::SampledTexture ||
mLayout->GetBindingInfo(bindingIndex).type ==
wgpu::BindingType::ReadonlyStorageTexture ||
mLayout->GetBindingInfo(bindingIndex).type ==
wgpu::BindingType::WriteonlyStorageTexture);
return static_cast<TextureViewBase*>(mBindingData.bindings[bindingIndex].Get());
}
} // namespace dawn_native