// 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 "dawn/common/Assert.h" #include "dawn/common/Math.h" #include "dawn/common/ityp_bitset.h" #include "dawn/native/BindGroupLayout.h" #include "dawn/native/Buffer.h" #include "dawn/native/ChainUtils_autogen.h" #include "dawn/native/CommandValidation.h" #include "dawn/native/Device.h" #include "dawn/native/ExternalTexture.h" #include "dawn/native/ObjectBase.h" #include "dawn/native/ObjectType_autogen.h" #include "dawn/native/Sampler.h" #include "dawn/native/Texture.h" #include "dawn/native/utils/WGPUHelpers.h" namespace dawn::native { namespace { // Helper functions to perform binding-type specific validation MaybeError ValidateBufferBinding(const DeviceBase* device, const BindGroupEntry& entry, const BindingInfo& bindingInfo) { DAWN_INVALID_IF(entry.buffer == nullptr, "Binding entry buffer not set."); DAWN_INVALID_IF(entry.sampler != nullptr || entry.textureView != nullptr, "Expected only buffer to be set for binding entry."); DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); DAWN_TRY(device->ValidateObject(entry.buffer)); ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer); uint64_t bufferSize = entry.buffer->GetSize(); // Handle wgpu::WholeSize, avoiding overflows. DAWN_INVALID_IF(entry.offset > bufferSize, "Binding offset (%u) is larger than the size (%u) of %s.", entry.offset, bufferSize, entry.buffer); uint64_t bindingSize = (entry.size == wgpu::kWholeSize) ? bufferSize - entry.offset : entry.size; DAWN_INVALID_IF(bindingSize > bufferSize, "Binding size (%u) is larger than the size (%u) of %s.", bindingSize, bufferSize, entry.buffer); DAWN_INVALID_IF(bindingSize == 0, "Binding size is zero"); // Note that no overflow can happen because we already checked that // bufferSize >= bindingSize DAWN_INVALID_IF(entry.offset > bufferSize - bindingSize, "Binding range (offset: %u, size: %u) doesn't fit in the size (%u) of %s.", entry.offset, bufferSize, bindingSize, entry.buffer); wgpu::BufferUsage requiredUsage; uint64_t maxBindingSize; uint64_t requiredBindingAlignment; switch (bindingInfo.buffer.type) { case wgpu::BufferBindingType::Uniform: requiredUsage = wgpu::BufferUsage::Uniform; maxBindingSize = device->GetLimits().v1.maxUniformBufferBindingSize; requiredBindingAlignment = device->GetLimits().v1.minUniformBufferOffsetAlignment; break; case wgpu::BufferBindingType::Storage: case wgpu::BufferBindingType::ReadOnlyStorage: requiredUsage = wgpu::BufferUsage::Storage; maxBindingSize = device->GetLimits().v1.maxStorageBufferBindingSize; requiredBindingAlignment = device->GetLimits().v1.minStorageBufferOffsetAlignment; DAWN_INVALID_IF(bindingSize % 4 != 0, "Binding size (%u) isn't a multiple of 4 when binding type is (%s).", bindingSize, bindingInfo.buffer.type); break; case kInternalStorageBufferBinding: requiredUsage = kInternalStorageBuffer; maxBindingSize = device->GetLimits().v1.maxStorageBufferBindingSize; requiredBindingAlignment = device->GetLimits().v1.minStorageBufferOffsetAlignment; break; case wgpu::BufferBindingType::Undefined: UNREACHABLE(); } DAWN_INVALID_IF(!IsAligned(entry.offset, requiredBindingAlignment), "Offset (%u) does not satisfy the minimum %s alignment (%u).", entry.offset, bindingInfo.buffer.type, requiredBindingAlignment); DAWN_INVALID_IF(!(entry.buffer->GetUsage() & requiredUsage), "Binding usage (%s) of %s doesn't match expected usage (%s).", entry.buffer->GetUsageExternalOnly(), entry.buffer, requiredUsage); DAWN_INVALID_IF(bindingSize < bindingInfo.buffer.minBindingSize, "Binding size (%u) is smaller than the minimum binding size (%u).", bindingSize, bindingInfo.buffer.minBindingSize); DAWN_INVALID_IF(bindingSize > maxBindingSize, "Binding size (%u) is larger than the maximum binding size (%u).", bindingSize, maxBindingSize); return {}; } MaybeError ValidateTextureBinding(DeviceBase* device, const BindGroupEntry& entry, const BindingInfo& bindingInfo, UsageValidationMode mode) { DAWN_INVALID_IF(entry.textureView == nullptr, "Binding entry textureView not set."); DAWN_INVALID_IF(entry.sampler != nullptr || entry.buffer != nullptr, "Expected only textureView to be set for binding entry."); DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); DAWN_TRY(device->ValidateObject(entry.textureView)); TextureViewBase* view = entry.textureView; Aspect aspect = view->GetAspects(); DAWN_INVALID_IF(!HasOneBit(aspect), "Multiple aspects (%s) selected in %s.", aspect, view); TextureBase* texture = view->GetTexture(); switch (bindingInfo.bindingType) { case BindingInfoType::Texture: { SampleTypeBit supportedTypes = texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes; SampleTypeBit requiredType = SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType); DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::TextureBinding, mode)); DAWN_INVALID_IF(texture->IsMultisampledTexture() != bindingInfo.texture.multisampled, "Sample count (%u) of %s doesn't match expectation (multisampled: %d).", texture->GetSampleCount(), texture, bindingInfo.texture.multisampled); DAWN_INVALID_IF( (supportedTypes & requiredType) == 0, "None of the supported sample types (%s) of %s match the expected sample " "types (%s).", supportedTypes, texture, requiredType); DAWN_INVALID_IF(entry.textureView->GetDimension() != bindingInfo.texture.viewDimension, "Dimension (%s) of %s doesn't match the expected dimension (%s).", entry.textureView->GetDimension(), entry.textureView, bindingInfo.texture.viewDimension); break; } case BindingInfoType::StorageTexture: { DAWN_TRY(ValidateCanUseAs(texture, wgpu::TextureUsage::StorageBinding, mode)); ASSERT(!texture->IsMultisampledTexture()); DAWN_INVALID_IF(texture->GetFormat().format != bindingInfo.storageTexture.format, "Format (%s) of %s expected to be (%s).", texture->GetFormat().format, texture, bindingInfo.storageTexture.format); DAWN_INVALID_IF( entry.textureView->GetDimension() != bindingInfo.storageTexture.viewDimension, "Dimension (%s) of %s doesn't match the expected dimension (%s).", entry.textureView->GetDimension(), entry.textureView, bindingInfo.storageTexture.viewDimension); DAWN_INVALID_IF(entry.textureView->GetLevelCount() != 1, "mipLevelCount (%u) of %s expected to be 1.", entry.textureView->GetLevelCount(), entry.textureView); break; } default: UNREACHABLE(); break; } return {}; } MaybeError ValidateSamplerBinding(const DeviceBase* device, const BindGroupEntry& entry, const BindingInfo& bindingInfo) { DAWN_INVALID_IF(entry.sampler == nullptr, "Binding entry sampler not set."); DAWN_INVALID_IF(entry.textureView != nullptr || entry.buffer != nullptr, "Expected only sampler to be set for binding entry."); DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); DAWN_TRY(device->ValidateObject(entry.sampler)); ASSERT(bindingInfo.bindingType == BindingInfoType::Sampler); switch (bindingInfo.sampler.type) { case wgpu::SamplerBindingType::NonFiltering: DAWN_INVALID_IF(entry.sampler->IsFiltering(), "Filtering sampler %s is incompatible with non-filtering sampler " "binding.", entry.sampler); [[fallthrough]]; case wgpu::SamplerBindingType::Filtering: DAWN_INVALID_IF(entry.sampler->IsComparison(), "Comparison sampler %s is incompatible with non-comparison sampler " "binding.", entry.sampler); break; case wgpu::SamplerBindingType::Comparison: DAWN_INVALID_IF(!entry.sampler->IsComparison(), "Non-comparison sampler %s is imcompatible with comparison sampler " "binding.", entry.sampler); break; default: UNREACHABLE(); break; } return {}; } MaybeError ValidateExternalTextureBinding( const DeviceBase* device, const BindGroupEntry& entry, const ExternalTextureBindingEntry* externalTextureBindingEntry, const ExternalTextureBindingExpansionMap& expansions) { DAWN_INVALID_IF(externalTextureBindingEntry == nullptr, "Binding entry external texture not set."); DAWN_INVALID_IF( entry.sampler != nullptr || entry.textureView != nullptr || entry.buffer != nullptr, "Expected only external texture to be set for binding entry."); DAWN_INVALID_IF(expansions.find(BindingNumber(entry.binding)) == expansions.end(), "External texture binding entry %u is not present in the bind group layout.", entry.binding); DAWN_TRY(ValidateSingleSType(externalTextureBindingEntry->nextInChain, wgpu::SType::ExternalTextureBindingEntry)); DAWN_TRY(device->ValidateObject(externalTextureBindingEntry->externalTexture)); return {}; } } // anonymous namespace MaybeError ValidateBindGroupDescriptor(DeviceBase* device, const BindGroupDescriptor* descriptor, UsageValidationMode mode) { DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); DAWN_TRY(device->ValidateObject(descriptor->layout)); DAWN_INVALID_IF( descriptor->entryCount != descriptor->layout->GetUnexpandedBindingCount(), "Number of entries (%u) did not match the number of entries (%u) specified in %s." "\nExpected layout: %s", descriptor->entryCount, static_cast(descriptor->layout->GetBindingCount()), descriptor->layout, descriptor->layout->EntriesToString()); const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap(); ASSERT(bindingMap.size() <= kMaxBindingsPerPipelineLayout); ityp::bitset bindingsSet; for (uint32_t i = 0; i < descriptor->entryCount; ++i) { const BindGroupEntry& entry = descriptor->entries[i]; const auto& it = bindingMap.find(BindingNumber(entry.binding)); DAWN_INVALID_IF(it == bindingMap.end(), "In entries[%u], binding index %u not present in the bind group layout." "\nExpected layout: %s", i, entry.binding, descriptor->layout->EntriesToString()); BindingIndex bindingIndex = it->second; ASSERT(bindingIndex < descriptor->layout->GetBindingCount()); DAWN_INVALID_IF(bindingsSet[bindingIndex], "In entries[%u], binding index %u already used by a previous entry", i, entry.binding); bindingsSet.set(bindingIndex); // Below this block we validate entries based on the bind group layout, in which // external textures have been expanded into their underlying contents. For this reason // we must identify external texture binding entries by checking the bind group entry // itself. // TODO(dawn:1293): Store external textures in // BindGroupLayoutBase::BindingDataPointers::bindings so checking external textures can // be moved in the switch below. const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; FindInChain(entry.nextInChain, &externalTextureBindingEntry); if (externalTextureBindingEntry != nullptr) { DAWN_TRY(ValidateExternalTextureBinding( device, entry, externalTextureBindingEntry, descriptor->layout->GetExternalTextureBindingExpansionMap())); continue; } else { DAWN_INVALID_IF(descriptor->layout->GetExternalTextureBindingExpansionMap().count( BindingNumber(entry.binding)), "entries[%u] is not an ExternalTexture when the layout contains an " "ExternalTexture entry.", i); } const BindingInfo& bindingInfo = descriptor->layout->GetBindingInfo(bindingIndex); // Perform binding-type specific validation. switch (bindingInfo.bindingType) { case BindingInfoType::Buffer: // TODO(dawn:1485): Validate buffer binding with usage validation mode. DAWN_TRY_CONTEXT(ValidateBufferBinding(device, entry, bindingInfo), "validating entries[%u] as a Buffer." "\nExpected entry layout: %s", i, bindingInfo); break; case BindingInfoType::Texture: case BindingInfoType::StorageTexture: DAWN_TRY_CONTEXT(ValidateTextureBinding(device, entry, bindingInfo, mode), "validating entries[%u] as a Texture." "\nExpected entry layout: %s", i, bindingInfo); break; case BindingInfoType::Sampler: DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, bindingInfo), "validating entries[%u] as a Sampler." "\nExpected entry layout: %s", i, bindingInfo); break; case BindingInfoType::ExternalTexture: 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() == descriptor->layout->GetUnexpandedBindingCount()); return {}; } // anonymous namespace // BindGroup BindGroupBase::BindGroupBase(DeviceBase* device, const BindGroupDescriptor* descriptor, void* bindingDataStart) : ApiObjectBase(device, descriptor->label), 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 holes with nullptrs. new (&mBindingData.bindings[i]) Ref(); } 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] == 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] == nullptr); mBindingData.bindings[bindingIndex] = entry.textureView; continue; } if (entry.sampler != nullptr) { ASSERT(mBindingData.bindings[bindingIndex] == nullptr); mBindingData.bindings[bindingIndex] = entry.sampler; continue; } // Here we unpack external texture bindings into multiple additional bindings for the // external texture's contents. New binding locations previously determined in the bind // group layout are created in this bind group and filled with the external texture's // underlying resources. const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; FindInChain(entry.nextInChain, &externalTextureBindingEntry); if (externalTextureBindingEntry != nullptr) { mBoundExternalTextures.push_back(externalTextureBindingEntry->externalTexture); ExternalTextureBindingExpansionMap expansions = mLayout->GetExternalTextureBindingExpansionMap(); ExternalTextureBindingExpansionMap::iterator it = expansions.find(BindingNumber(entry.binding)); ASSERT(it != expansions.end()); BindingIndex plane0BindingIndex = descriptor->layout->GetBindingIndex(it->second.plane0); BindingIndex plane1BindingIndex = descriptor->layout->GetBindingIndex(it->second.plane1); BindingIndex paramsBindingIndex = descriptor->layout->GetBindingIndex(it->second.params); ASSERT(mBindingData.bindings[plane0BindingIndex] == nullptr); mBindingData.bindings[plane0BindingIndex] = externalTextureBindingEntry->externalTexture->GetTextureViews()[0]; ASSERT(mBindingData.bindings[plane1BindingIndex] == nullptr); mBindingData.bindings[plane1BindingIndex] = externalTextureBindingEntry->externalTexture->GetTextureViews()[1]; ASSERT(mBindingData.bindings[paramsBindingIndex] == nullptr); mBindingData.bindings[paramsBindingIndex] = externalTextureBindingEntry->externalTexture->GetParamsBuffer(); mBindingData.bufferData[paramsBindingIndex].offset = 0; mBindingData.bufferData[paramsBindingIndex].size = sizeof(dawn_native::ExternalTextureParams); continue; } } uint32_t packedIdx = 0; for (BindingIndex bindingIndex{0}; bindingIndex < descriptor->layout->GetBufferCount(); ++bindingIndex) { if (descriptor->layout->GetBindingInfo(bindingIndex).buffer.minBindingSize == 0) { mBindingData.unverifiedBufferSizes[packedIdx] = mBindingData.bufferData[bindingIndex].size; ++packedIdx; } } GetObjectTrackingList()->Track(this); } BindGroupBase::BindGroupBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { GetObjectTrackingList()->Track(this); } BindGroupBase::~BindGroupBase() = default; void BindGroupBase::DestroyImpl() { if (mLayout != nullptr) { ASSERT(!IsError()); for (BindingIndex i{0}; i < mLayout->GetBindingCount(); ++i) { mBindingData.bindings[i].~Ref(); } } } 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 layout = mLayout; ApiObjectBase::DeleteThis(); } BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag), mBindingData() {} // static BindGroupBase* BindGroupBase::MakeError(DeviceBase* device) { return new BindGroupBase(device, ObjectBase::kError); } ObjectType BindGroupBase::GetType() const { return ObjectType::BindGroup; } BindGroupLayoutBase* BindGroupBase::GetLayout() { ASSERT(!IsError()); return mLayout.Get(); } const BindGroupLayoutBase* BindGroupBase::GetLayout() const { ASSERT(!IsError()); return mLayout.Get(); } const ityp::span& BindGroupBase::GetUnverifiedBufferSizes() const { ASSERT(!IsError()); return mBindingData.unverifiedBufferSizes; } BufferBinding BindGroupBase::GetBindingAsBufferBinding(BindingIndex bindingIndex) { ASSERT(!IsError()); ASSERT(bindingIndex < mLayout->GetBindingCount()); ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Buffer); BufferBase* buffer = static_cast(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).bindingType == BindingInfoType::Sampler); return static_cast(mBindingData.bindings[bindingIndex].Get()); } TextureViewBase* BindGroupBase::GetBindingAsTextureView(BindingIndex bindingIndex) { ASSERT(!IsError()); ASSERT(bindingIndex < mLayout->GetBindingCount()); ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Texture || mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::StorageTexture); return static_cast(mBindingData.bindings[bindingIndex].Get()); } const std::vector>& BindGroupBase::GetBoundExternalTextures() const { return mBoundExternalTextures; } } // namespace dawn::native