| // 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), |
| "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 incompatible 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 {}; |
| } |
| |
| template <typename F> |
| void ForEachUnverifiedBufferBindingIndexImpl(const BindGroupLayoutBase* bgl, F&& f) { |
| uint32_t packedIndex = 0; |
| for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBufferCount(); ++bindingIndex) { |
| if (bgl->GetBindingInfo(bindingIndex).buffer.minBindingSize == 0) { |
| f(bindingIndex, packedIndex++); |
| } |
| } |
| } |
| |
| } // 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<uint32_t>(descriptor->layout->GetBindingCount()), |
| descriptor->layout, descriptor->layout->EntriesToString()); |
| |
| const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap(); |
| ASSERT(bindingMap.size() <= kMaxBindingsPerPipelineLayout); |
| |
| ityp::bitset<BindingIndex, kMaxBindingsPerPipelineLayout> 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 {}; |
| } |
| |
| // 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<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] == 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; |
| } |
| } |
| |
| ForEachUnverifiedBufferBindingIndexImpl(mLayout.Get(), |
| [&](BindingIndex bindingIndex, uint32_t packedIndex) { |
| mBindingData.unverifiedBufferSizes[packedIndex] = |
| mBindingData.bufferData[bindingIndex].size; |
| }); |
| |
| 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<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; |
| ApiObjectBase::DeleteThis(); |
| } |
| |
| BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label) |
| : ApiObjectBase(device, tag, label), mBindingData() {} |
| |
| // static |
| BindGroupBase* BindGroupBase::MakeError(DeviceBase* device, const char* label) { |
| return new BindGroupBase(device, ObjectBase::kError, label); |
| } |
| |
| 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<uint32_t, uint64_t>& 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<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).bindingType == BindingInfoType::Sampler); |
| return static_cast<SamplerBase*>(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<TextureViewBase*>(mBindingData.bindings[bindingIndex].Get()); |
| } |
| |
| const std::vector<Ref<ExternalTextureBase>>& BindGroupBase::GetBoundExternalTextures() const { |
| return mBoundExternalTextures; |
| } |
| |
| void BindGroupBase::ForEachUnverifiedBufferBindingIndex( |
| std::function<void(BindingIndex, uint32_t)> fn) const { |
| ForEachUnverifiedBufferBindingIndexImpl(mLayout.Get(), fn); |
| } |
| |
| } // namespace dawn::native |