| // Copyright 2023 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/native/BindGroupLayoutInternal.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <limits> |
| #include <list> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/common/Enumerator.h" |
| #include "dawn/common/MatchVariant.h" |
| #include "dawn/native/ChainUtils.h" |
| #include "dawn/native/Device.h" |
| #include "dawn/native/Error.h" |
| #include "dawn/native/Instance.h" |
| #include "dawn/native/ObjectBase.h" |
| #include "dawn/native/ObjectContentHasher.h" |
| #include "dawn/native/ObjectType_autogen.h" |
| #include "dawn/native/PerStage.h" |
| #include "dawn/native/Sampler.h" |
| #include "dawn/native/TexelBufferView.h" |
| #include "dawn/native/ValidationUtils_autogen.h" |
| #include "dawn/platform/metrics/HistogramMacros.h" |
| |
| namespace dawn::native { |
| |
| namespace { |
| |
| bool TextureFormatSupportStorageAccess(const Format& format, wgpu::StorageTextureAccess access) { |
| switch (access) { |
| case wgpu::StorageTextureAccess::ReadOnly: |
| return format.SupportsReadOnlyStorageUsage(); |
| case wgpu::StorageTextureAccess::WriteOnly: |
| return format.SupportsWriteOnlyStorageUsage(); |
| case wgpu::StorageTextureAccess::ReadWrite: |
| return format.SupportsReadWriteStorageUsage(); |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| MaybeError ValidateStorageTextureFormat(DeviceBase* device, |
| wgpu::TextureFormat storageTextureFormat, |
| wgpu::StorageTextureAccess access) { |
| const Format* format = nullptr; |
| DAWN_TRY_ASSIGN(format, device->GetInternalFormat(storageTextureFormat)); |
| DAWN_ASSERT(format != nullptr); |
| |
| DAWN_INVALID_IF(!TextureFormatSupportStorageAccess(*format, access), |
| "Texture format %s does not support storage texture access %s.", |
| storageTextureFormat, access); |
| |
| return {}; |
| } |
| |
| MaybeError ValidateStorageTextureViewDimension(wgpu::TextureViewDimension dimension) { |
| switch (dimension) { |
| case wgpu::TextureViewDimension::Cube: |
| case wgpu::TextureViewDimension::CubeArray: |
| return DAWN_VALIDATION_ERROR("%s texture views cannot be used as storage textures.", |
| dimension); |
| |
| case wgpu::TextureViewDimension::e1D: |
| case wgpu::TextureViewDimension::e2D: |
| case wgpu::TextureViewDimension::e2DArray: |
| case wgpu::TextureViewDimension::e3D: |
| return {}; |
| |
| case wgpu::TextureViewDimension::Undefined: |
| break; |
| } |
| DAWN_UNREACHABLE(); |
| } |
| |
| MaybeError ValidateBindGroupLayoutEntry(DeviceBase* device, |
| const UnpackedPtr<BindGroupLayoutEntry>& entry, |
| bool allowInternalBinding) { |
| DAWN_TRY(ValidateShaderStage(entry->visibility)); |
| |
| uint32_t arraySize = std::max(1u, entry->bindingArraySize); |
| |
| int bindingMemberCount = 0; |
| |
| if (entry->buffer.type != wgpu::BufferBindingType::BindingNotUsed) { |
| bindingMemberCount++; |
| const BufferBindingLayout& buffer = entry->buffer; |
| |
| // The kInternalStorageBufferBinding is used internally and not a value |
| // in wgpu::BufferBindingType. |
| if (buffer.type == kInternalStorageBufferBinding || |
| buffer.type == kInternalReadOnlyStorageBufferBinding) { |
| DAWN_INVALID_IF(!allowInternalBinding, "Internal binding types are disallowed"); |
| } else { |
| DAWN_TRY(ValidateBufferBindingType(buffer.type)); |
| } |
| |
| if (buffer.type == wgpu::BufferBindingType::Storage || |
| buffer.type == kInternalStorageBufferBinding) { |
| DAWN_INVALID_IF( |
| entry->visibility & wgpu::ShaderStage::Vertex, |
| "Read-write storage buffer binding is used with a visibility (%s) that contains %s " |
| "(note that read-only storage buffer bindings are allowed).", |
| entry->visibility, wgpu::ShaderStage::Vertex); |
| } |
| |
| // TODO(393558555): Support bindingArraySize > 1 for non-dynamic buffers. |
| DAWN_INVALID_IF(arraySize > 1, |
| "bindingArraySize (%u) > 1 for a buffer binding is not implemented yet.", |
| arraySize); |
| } |
| |
| if (entry->sampler.type != wgpu::SamplerBindingType::BindingNotUsed) { |
| bindingMemberCount++; |
| DAWN_TRY(ValidateSamplerBindingType(entry->sampler.type)); |
| |
| // TODO(393558555): Support bindingArraySize > 1 for samplers. |
| DAWN_INVALID_IF(arraySize > 1, |
| "bindingArraySize (%u) > 1 for a sampler binding is not implemented yet.", |
| arraySize); |
| } |
| |
| if (entry->texture.sampleType != wgpu::TextureSampleType::BindingNotUsed) { |
| bindingMemberCount++; |
| const TextureBindingLayout& texture = entry->texture; |
| // The kInternalResolveAttachmentSampleType is used internally and not a value |
| // in wgpu::TextureSampleType. |
| switch (texture.sampleType) { |
| case kInternalResolveAttachmentSampleType: |
| if (allowInternalBinding) { |
| break; |
| } |
| // should return validation error. |
| [[fallthrough]]; |
| default: |
| DAWN_TRY(ValidateTextureSampleType(texture.sampleType)); |
| break; |
| } |
| |
| // viewDimension defaults to 2D if left undefined, needs validation otherwise. |
| wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D; |
| if (texture.viewDimension != wgpu::TextureViewDimension::Undefined) { |
| switch (texture.viewDimension) { |
| case kInternalInputAttachmentDim: |
| if (allowInternalBinding) { |
| break; |
| } |
| // should return validation error. |
| [[fallthrough]]; |
| default: |
| DAWN_TRY(ValidateTextureViewDimension(texture.viewDimension)); |
| } |
| viewDimension = texture.viewDimension; |
| } |
| |
| DAWN_INVALID_IF(texture.multisampled && viewDimension != wgpu::TextureViewDimension::e2D, |
| "View dimension (%s) for a multisampled texture bindings was not %s.", |
| viewDimension, wgpu::TextureViewDimension::e2D); |
| |
| DAWN_INVALID_IF( |
| texture.multisampled && texture.sampleType == wgpu::TextureSampleType::Float, |
| "Sample type for multisampled texture binding was %s.", wgpu::TextureSampleType::Float); |
| } |
| |
| if (entry->storageTexture.access != wgpu::StorageTextureAccess::BindingNotUsed) { |
| bindingMemberCount++; |
| const StorageTextureBindingLayout& storageTexture = entry->storageTexture; |
| DAWN_TRY(ValidateStorageTextureAccess(storageTexture.access)); |
| DAWN_TRY( |
| ValidateStorageTextureFormat(device, storageTexture.format, storageTexture.access)); |
| |
| // viewDimension defaults to 2D if left undefined, needs validation otherwise. |
| if (storageTexture.viewDimension != wgpu::TextureViewDimension::Undefined) { |
| DAWN_TRY(ValidateTextureViewDimension(storageTexture.viewDimension)); |
| DAWN_TRY(ValidateStorageTextureViewDimension(storageTexture.viewDimension)); |
| } |
| |
| switch (storageTexture.access) { |
| case wgpu::StorageTextureAccess::ReadOnly: |
| break; |
| case wgpu::StorageTextureAccess::ReadWrite: |
| case wgpu::StorageTextureAccess::WriteOnly: |
| DAWN_INVALID_IF(entry->visibility & wgpu::ShaderStage::Vertex, |
| "Storage texture binding with %s is used with a visibility (%s) " |
| "that contains %s.", |
| storageTexture.access, entry->visibility, |
| wgpu::ShaderStage::Vertex); |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| |
| // TODO(393558555): Support bindingArraySize > 1 for storage textures. |
| DAWN_INVALID_IF( |
| arraySize > 1, |
| "bindingArraySize (%u) > 1 for a storage texture binding is not implemented yet.", |
| arraySize); |
| } |
| |
| if (auto* staticSamplerBindingLayout = entry.Get<StaticSamplerBindingLayout>()) { |
| bindingMemberCount++; |
| |
| DAWN_INVALID_IF(!device->HasFeature(Feature::StaticSamplers), |
| "Static samplers used without the %s feature enabled.", |
| wgpu::FeatureName::StaticSamplers); |
| |
| DAWN_TRY(device->ValidateObject(staticSamplerBindingLayout->sampler)); |
| DAWN_INVALID_IF(arraySize > 1, |
| "BindGroupLayoutEntry bindingArraySize (%u) > 1 for a static " |
| "sampler entry.", |
| arraySize); |
| |
| if (staticSamplerBindingLayout->sampledTextureBinding == WGPU_LIMIT_U32_UNDEFINED) { |
| DAWN_INVALID_IF(staticSamplerBindingLayout->sampler->IsYCbCr(), |
| "YCbCr static sampler requires a sampled texture binding"); |
| } |
| } |
| |
| if (auto* texelBufferLayout = entry.Get<TexelBufferBindingLayout>()) { |
| bindingMemberCount++; |
| DAWN_INVALID_IF(!device->AreTexelBuffersEnabled(), "%s is not enabled.", |
| wgpu::WGSLLanguageFeatureName::TexelBuffers); |
| |
| DAWN_TRY(ValidateTexelBufferAccess(texelBufferLayout->access)); |
| |
| // TODO(393558555): Support bindingArraySize > 1 for texel buffers. |
| DAWN_INVALID_IF( |
| arraySize > 1, |
| "bindingArraySize (%u) > 1 for a storage texture binding is not implemented yet.", |
| arraySize); |
| |
| DAWN_INVALID_IF(entry->visibility & wgpu::ShaderStage::Vertex && |
| texelBufferLayout->access != wgpu::TexelBufferAccess::ReadOnly, |
| "Vertex visibility requires read-only texel buffer access."); |
| |
| const Format* format; |
| DAWN_TRY_ASSIGN(format, device->GetInternalFormat(texelBufferLayout->format)); |
| DAWN_INVALID_IF(!IsFormatSupportedForTexelBuffer(format->format), |
| "Texel buffer layout format (%s) is not allowed for texel buffers.", |
| format->format); |
| } |
| |
| if (entry.Get<ExternalTextureBindingLayout>()) { |
| bindingMemberCount++; |
| DAWN_INVALID_IF(arraySize > 1, |
| "BindGroupLayoutEntry bindingArraySize (%u) > 1 for an " |
| "external texture entry.", |
| arraySize); |
| } |
| |
| DAWN_INVALID_IF(bindingMemberCount == 0, |
| "BindGroupLayoutEntry had none of buffer, sampler, texture, " |
| "storageTexture, texelBuffer, or externalTexture set"); |
| |
| DAWN_INVALID_IF(bindingMemberCount != 1, |
| "BindGroupLayoutEntry had more than one of buffer, sampler, texture, " |
| "storageTexture, texelBuffer, or externalTexture set"); |
| |
| DAWN_INVALID_IF( |
| arraySize > 1 && entry->texture.sampleType == wgpu::TextureSampleType::BindingNotUsed, |
| "Entry that is not a sampled texture has an bindingArraySize (%u) > 1.", arraySize); |
| |
| return {}; |
| } |
| |
| MaybeError ValidateStaticSamplersWithTextureBindings( |
| DeviceBase* device, |
| const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor, |
| const std::map<BindingNumber, uint32_t>& bindingNumberToIndexMap) { |
| // Map of texture binding number to static sampler binding number. |
| std::map<BindingNumber, BindingNumber> textureToStaticSamplerBindingMap; |
| |
| for (uint32_t i = 0; i < descriptor->entryCount; ++i) { |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]); |
| auto* staticSamplerLayout = entry.Get<StaticSamplerBindingLayout>(); |
| if (!staticSamplerLayout || |
| staticSamplerLayout->sampledTextureBinding == WGPU_LIMIT_U32_UNDEFINED) { |
| continue; |
| } |
| |
| BindingNumber samplerBinding(entry->binding); |
| BindingNumber sampledTextureBinding(staticSamplerLayout->sampledTextureBinding); |
| |
| bool inserted = |
| textureToStaticSamplerBindingMap.insert({sampledTextureBinding, samplerBinding}).second; |
| DAWN_INVALID_IF(!inserted, |
| "For static sampler binding (%u) the sampled texture binding (%u) is " |
| "already bound to a static sampler at binding (%u).", |
| samplerBinding, sampledTextureBinding, |
| textureToStaticSamplerBindingMap[sampledTextureBinding]); |
| |
| DAWN_INVALID_IF(!bindingNumberToIndexMap.contains(sampledTextureBinding), |
| "For static sampler binding (%u) the sampled texture binding (%u) is not a " |
| "valid binding number.", |
| samplerBinding, sampledTextureBinding); |
| |
| auto& textureEntry = descriptor->entries[bindingNumberToIndexMap.at(sampledTextureBinding)]; |
| DAWN_INVALID_IF(textureEntry.texture.sampleType == wgpu::TextureSampleType::BindingNotUsed, |
| "For static sampler binding (%u) the sampled texture binding (%u) is not a " |
| "texture binding.", |
| samplerBinding, sampledTextureBinding); |
| } |
| |
| return {}; |
| } |
| |
| } // anonymous namespace |
| |
| ResultOrError<UnpackedPtr<BindGroupLayoutDescriptor>> ValidateBindGroupLayoutDescriptor( |
| DeviceBase* device, |
| const BindGroupLayoutDescriptor* descriptorChain, |
| bool allowInternalBinding) { |
| UnpackedPtr<BindGroupLayoutDescriptor> descriptor; |
| DAWN_TRY_ASSIGN(descriptor, ValidateAndUnpack(descriptorChain)); |
| |
| // A running total of the number of bindings used by the layout. |
| BindingCounts bindingCounts = {}; |
| |
| // Map of binding number to entry index. |
| std::map<BindingNumber, uint32_t> bindingMap; |
| |
| for (uint32_t i = 0; i < descriptor->entryCount; ++i) { |
| UnpackedPtr<BindGroupLayoutEntry> entry; |
| DAWN_TRY_ASSIGN(entry, ValidateAndUnpack(&descriptor->entries[i])); |
| |
| BindingNumber bindingNumber = BindingNumber(entry->binding); |
| DAWN_INVALID_IF( |
| bindingNumber >= kMaxBindingsPerBindGroupTyped, |
| "On entries[%u]: binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).", |
| i, bindingNumber, kMaxBindingsPerBindGroup); |
| |
| BindingNumber arraySize{1}; |
| if (entry->bindingArraySize > 1) { |
| arraySize = BindingNumber(entry->bindingArraySize); |
| |
| DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisableBindGroupLayoutEntryArraySize), |
| "On entries[%u]: use of bindingArraySize > 1 is disabled.", i); |
| DAWN_INVALID_IF(!device->IsToggleEnabled(Toggle::AllowUnsafeAPIs), |
| "On entries[%u]: use of bindingArraySize > 1 is currently unsafe.", i); |
| |
| DAWN_INVALID_IF(arraySize > kMaxBindingsPerBindGroupTyped - bindingNumber, |
| "On entries[%u]: binding (%u) + arraySize (%u) is %u which is larger " |
| "than maxBindingsPerBindGroup (%u).", |
| i, arraySize, bindingNumber, |
| uint32_t(arraySize) + uint32_t(bindingNumber), |
| kMaxBindingsPerBindGroupTyped); |
| } |
| |
| // Check that the same binding is not set twice. bindingNumber + arraySize cannot overflow |
| // as they are both smaller than kMaxBindingsPerBindGroupTyped. |
| static_assert(kMaxBindingsPerBindGroup < std::numeric_limits<uint32_t>::max() / 2); |
| for (BindingNumber usedBinding : Range(bindingNumber, bindingNumber + arraySize)) { |
| DAWN_INVALID_IF(bindingMap.contains(usedBinding), |
| "On entries[%u]: binding index (%u) was specified by a previous entry.", |
| i, entry->binding); |
| bindingMap.insert({usedBinding, i}); |
| } |
| |
| DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding), |
| "validating entries[%u]", i); |
| |
| IncrementBindingCounts(&bindingCounts, entry); |
| } |
| |
| // Perform a second validation pass for static samplers. This is done after initial validation |
| // as static samplers can have associated texture entries that need to be validated first. |
| DAWN_TRY(ValidateStaticSamplersWithTextureBindings(device, descriptor, bindingMap)); |
| |
| DAWN_TRY_CONTEXT( |
| ValidateBindingCounts(device->GetLimits(), bindingCounts, device->GetAdapter()), |
| "validating binding counts"); |
| |
| return descriptor; |
| } |
| |
| namespace { |
| |
| BindingInfo CreateSampledTextureBindingForExternalTexture(BindingNumber binding, |
| wgpu::ShaderStage visibility) { |
| return { |
| .binding = binding, |
| .visibility = visibility, |
| .bindingLayout = TextureBindingInfo{{ |
| .sampleType = wgpu::TextureSampleType::Float, |
| .viewDimension = wgpu::TextureViewDimension::e2D, |
| .multisampled = false, |
| }}, |
| }; |
| } |
| |
| BindingInfo CreateUniformBindingForExternalTexture(BindingNumber binding, |
| wgpu::ShaderStage visibility) { |
| return { |
| .binding = binding, |
| .visibility = visibility, |
| .bindingLayout = BufferBindingInfo{{ |
| .type = wgpu::BufferBindingType::Uniform, |
| .minBindingSize = 0, |
| .hasDynamicOffset = false, |
| }}, |
| }; |
| } |
| |
| BindingInfo ConvertToBindingInfo(const UnpackedPtr<BindGroupLayoutEntry>& binding) { |
| BindingInfo bindingInfo; |
| bindingInfo.binding = BindingNumber(binding->binding); |
| bindingInfo.visibility = binding->visibility; |
| bindingInfo.arraySize = BindingIndex(std::max(1u, binding->bindingArraySize)); |
| |
| if (binding->buffer.type != wgpu::BufferBindingType::BindingNotUsed) { |
| bindingInfo.bindingLayout = BufferBindingInfo::From(binding->buffer); |
| } else if (binding->sampler.type != wgpu::SamplerBindingType::BindingNotUsed) { |
| bindingInfo.bindingLayout = SamplerBindingInfo::From(binding->sampler); |
| } else if (binding->texture.sampleType != wgpu::TextureSampleType::BindingNotUsed) { |
| auto textureBindingInfo = TextureBindingInfo::From(binding->texture); |
| if (binding->texture.viewDimension == kInternalInputAttachmentDim) { |
| bindingInfo.bindingLayout = InputAttachmentBindingInfo{{textureBindingInfo.sampleType}}; |
| } else { |
| bindingInfo.bindingLayout = textureBindingInfo; |
| } |
| } else if (binding->storageTexture.access != wgpu::StorageTextureAccess::BindingNotUsed) { |
| bindingInfo.bindingLayout = StorageTextureBindingInfo::From(binding->storageTexture); |
| } else if (auto* texelBufferLayout = binding.Get<TexelBufferBindingLayout>()) { |
| bindingInfo.bindingLayout = TexelBufferBindingInfo::From(*texelBufferLayout); |
| } else if (auto* staticSamplerBindingLayout = binding.Get<StaticSamplerBindingLayout>()) { |
| // The sampledTextureIndex will be filled later, after reordering of bindings. |
| bindingInfo.bindingLayout = StaticSamplerBindingInfo::From(*staticSamplerBindingLayout); |
| } else if (binding.Has<ExternalTextureBindingLayout>()) { |
| // The BindingIndex members are filled later once we know the order of bindings. |
| bindingInfo.bindingLayout = ExternalTextureBindingInfo{}; |
| } else { |
| DAWN_UNREACHABLE(); |
| } |
| |
| return bindingInfo; |
| } |
| |
| bool SortBindingsCompare(const BindingInfo& a, const BindingInfo& b) { |
| if (&a == &b) { |
| return false; |
| } |
| |
| // Buffers with dynamic offsets come first and then the rest of the buffers. Other bindings are |
| // only grouped by types. This is to make it easier and faster to handle them. |
| auto TypeOrder = [](const BindingInfo& info) { |
| return MatchVariant( |
| info.bindingLayout, |
| [&](const BufferBindingInfo& layout) { |
| return layout.hasDynamicOffset ? BindingTypeOrder_DynamicBuffer |
| : BindingTypeOrder_RegularBuffer; |
| }, |
| [&](const TextureBindingInfo&) { return BindingTypeOrder_SampledTexture; }, |
| [&](const StorageTextureBindingInfo&) { return BindingTypeOrder_StorageTexture; }, |
| [&](const SamplerBindingInfo&) { return BindingTypeOrder_RegularSampler; }, |
| [&](const StaticSamplerBindingInfo&) { return BindingTypeOrder_StaticSampler; }, |
| [&](const TexelBufferBindingInfo&) { return BindingTypeOrder_TexelBuffer; }, |
| [&](const InputAttachmentBindingInfo&) { return BindingTypeOrder_InputAttachment; }, |
| [&](const ExternalTextureBindingInfo&) { return BindingTypeOrder_ExternalTexture; }); |
| }; |
| |
| auto aOrder = TypeOrder(a); |
| auto bOrder = TypeOrder(b); |
| if (aOrder != bOrder) { |
| return aOrder < bOrder; |
| } |
| |
| // Afterwards sort the bindings by binding number. This is necessary because dynamic buffers |
| // are applied in order of increasing binding number in SetBindGroup. It also ensures that |
| // bindings for binding arrays stay contiguous as that's required by backends. |
| return a.binding < b.binding; |
| } |
| |
| // This function handles the conversion of the API format for each binding info to Dawn's internal |
| // representation of them. This is also where the ExternalTextures are replaced and expanded in the |
| // various bindings that are used internally in Dawn. Arrays are also expanded to individual |
| // bindings here. |
| struct ExpandedBindingInfo { |
| BindGroupLayoutInternalBase::BindingMap apiBindingMap; |
| ityp::vector<BindingIndex, BindingInfo> entries; |
| }; |
| ExpandedBindingInfo ConvertAndExpandBGLEntries( |
| const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor) { |
| // When new BGL entries are created, we use binding numbers decreasing from the max uint32_t |
| // to ensure there are no collisions and that validation will prevent using these BindingNumbers |
| // when creating a bindgroup (so there is no risk of applications injecting their own buffer for |
| // the metadata for example). |
| BindingNumber nextOpenBindingNumberForNewEntry = std::numeric_limits<BindingNumber>::max(); |
| |
| ityp::vector<BindingIndex, BindingInfo> entries; |
| absl::flat_hash_set<BindingNumber> internalEntries; |
| |
| // Keep track of the BindingNumbers for additional bindings for external textures, it is used |
| // below to link the ExternalTextureBindingInfo to the BindingIndex of the additional bindings. |
| struct ExternalTextureExpansion { |
| BindingNumber plane0; |
| BindingNumber plane1; |
| BindingNumber metadata; |
| }; |
| absl::flat_hash_map<BindingNumber, ExternalTextureExpansion> externalTextureExpansions; |
| |
| // Likewise keep track of the "single texture binding" for static samplers so that we can link |
| // StaticSamplerBindingInfo::sampledTextureIndex to the BindingIndex post reordering. |
| absl::flat_hash_map<BindingNumber, BindingNumber> staticSamplerToSingleTextureBinding; |
| |
| for (uint32_t i = 0; i < descriptor->entryCount; i++) { |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]); |
| |
| // External textures are expanded to add two sampled texture bindings and one uniform buffer |
| // binding. The external texture is still added to the entries to be used in validation and |
| // to know where the additional bindings are located. |
| if (entry.Get<ExternalTextureBindingLayout>()) { |
| DAWN_ASSERT(entry->bindingArraySize <= 1); |
| |
| BindingInfo plane0Entry = CreateSampledTextureBindingForExternalTexture( |
| nextOpenBindingNumberForNewEntry--, entry->visibility); |
| entries.push_back(plane0Entry); |
| internalEntries.insert(plane0Entry.binding); |
| |
| BindingInfo plane1Entry = CreateSampledTextureBindingForExternalTexture( |
| nextOpenBindingNumberForNewEntry--, entry->visibility); |
| entries.push_back(plane1Entry); |
| internalEntries.insert(plane1Entry.binding); |
| |
| BindingInfo metadataEntry = CreateUniformBindingForExternalTexture( |
| nextOpenBindingNumberForNewEntry--, entry->visibility); |
| entries.push_back(metadataEntry); |
| internalEntries.insert(metadataEntry.binding); |
| |
| externalTextureExpansions.insert({BindingNumber(entry->binding), |
| { |
| .plane0 = BindingNumber(plane0Entry.binding), |
| .plane1 = BindingNumber(plane1Entry.binding), |
| .metadata = BindingNumber(metadataEntry.binding), |
| }}); |
| } |
| |
| if (auto* staticSampler = entry.Get<StaticSamplerBindingLayout>()) { |
| if (staticSampler->sampledTextureBinding < WGPU_LIMIT_U32_UNDEFINED) { |
| staticSamplerToSingleTextureBinding.insert( |
| {BindingNumber(entry->binding), |
| BindingNumber(staticSampler->sampledTextureBinding)}); |
| } |
| } |
| |
| // Add one BindingInfo per element of the array with increasing indexInArray for backends to |
| // know which element it is when they need it, but also with increasing BindingNumber as the |
| // array takes consecutive binding numbers on the API side. |
| BindingInfo info = ConvertToBindingInfo(entry); |
| for (BindingIndex indexInArray : Range(info.arraySize)) { |
| info.indexInArray = indexInArray; |
| entries.push_back(info); |
| info.binding++; |
| } |
| } |
| |
| // Reorder bindings internally and compute the complete BindingNumber->BindingIndex map. |
| std::sort(entries.begin(), entries.end(), SortBindingsCompare); |
| |
| absl::flat_hash_map<BindingNumber, BindingIndex> fullBindingMap; |
| for (const auto [i, binding] : Enumerate(entries)) { |
| const auto& [_, inserted] = fullBindingMap.emplace(binding.binding, i); |
| DAWN_ASSERT(inserted); |
| } |
| |
| // Store the location of expanded entries in ExternalTexture layouts. |
| for (const auto& [etBindingNumber, expansion] : externalTextureExpansions) { |
| auto& layout = entries[fullBindingMap[etBindingNumber]]; |
| layout.bindingLayout = ExternalTextureBindingInfo{{ |
| .metadata = fullBindingMap[expansion.metadata], |
| .plane0 = fullBindingMap[expansion.plane0], |
| .plane1 = fullBindingMap[expansion.plane1], |
| }}; |
| } |
| |
| // Store the location of the single texture binding in the StaticSamplerBindingInfo. |
| for (auto [samplerNumber, textureNumber] : staticSamplerToSingleTextureBinding) { |
| auto& samplerBindingInfo = std::get<StaticSamplerBindingInfo>( |
| entries[fullBindingMap[samplerNumber]].bindingLayout); |
| samplerBindingInfo.sampledTextureIndex = fullBindingMap[textureNumber]; |
| } |
| |
| // Now build the result. |
| ExpandedBindingInfo result; |
| |
| // Build the user-facing binding map. |
| for (const auto [i, binding] : Enumerate(entries)) { |
| if (internalEntries.contains(binding.binding)) { |
| continue; |
| } |
| |
| APIBindingIndex index = APIBindingIndex(uint32_t(i)); |
| const auto& [_, inserted] = result.apiBindingMap.emplace(binding.binding, index); |
| DAWN_ASSERT(inserted); |
| } |
| |
| result.entries = std::move(entries); |
| return result; |
| } |
| |
| // This is a utility function to help DAWN_ASSERT that the BGL-binding comparator places buffers |
| // first. |
| bool CheckBufferBindingsFirst(ityp::span<BindingIndex, const BindingInfo> bindings) { |
| BindingIndex lastBufferIndex{0}; |
| BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max(); |
| for (auto [i, binding] : Enumerate(bindings)) { |
| if (std::holds_alternative<BufferBindingInfo>(binding.bindingLayout)) { |
| lastBufferIndex = std::max(i, lastBufferIndex); |
| } else { |
| firstNonBufferIndex = std::min(i, firstNonBufferIndex); |
| } |
| } |
| |
| // If there are no buffers, then |lastBufferIndex| is initialized to 0 and |
| // |firstNonBufferIndex| gets set to 0. |
| return firstNonBufferIndex >= lastBufferIndex; |
| } |
| |
| } // namespace |
| |
| // BindGroupLayoutInternalBase |
| |
| BindGroupLayoutInternalBase::BindGroupLayoutInternalBase( |
| DeviceBase* device, |
| const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor, |
| ApiObjectBase::UntrackedByDeviceTag tag) |
| : ApiObjectBase(device, descriptor->label) { |
| ExpandedBindingInfo unpackedBindings = ConvertAndExpandBGLEntries(descriptor); |
| mBindingInfo = std::move(unpackedBindings.entries); |
| mBindingMap = std::move(unpackedBindings.apiBindingMap); |
| |
| DAWN_ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()})); |
| DAWN_ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped); |
| |
| // Compute various counts of expanded bindings and other metadata. |
| std::array<BindingIndex, BindingTypeOrder_Count + 1> counts{}; |
| for (const auto& binding : mBindingInfo) { |
| MatchVariant( |
| binding.bindingLayout, |
| [&](const BufferBindingInfo& layout) { |
| if (layout.minBindingSize == 0) { |
| mUnverifiedBufferCount++; |
| } |
| if (layout.hasDynamicOffset) { |
| counts[BindingTypeOrder_DynamicBuffer]++; |
| switch (layout.type) { |
| case wgpu::BufferBindingType::Storage: |
| case kInternalStorageBufferBinding: |
| case kInternalReadOnlyStorageBufferBinding: |
| case wgpu::BufferBindingType::ReadOnlyStorage: |
| mDynamicStorageBufferCount++; |
| break; |
| |
| case wgpu::BufferBindingType::Uniform: |
| case wgpu::BufferBindingType::BindingNotUsed: |
| case wgpu::BufferBindingType::Undefined: |
| break; |
| } |
| } else { |
| counts[BindingTypeOrder_RegularBuffer]++; |
| } |
| }, |
| [&](const TextureBindingInfo&) { counts[BindingTypeOrder_SampledTexture]++; }, |
| [&](const StorageTextureBindingInfo&) { counts[BindingTypeOrder_StorageTexture]++; }, |
| [&](const SamplerBindingInfo&) { counts[BindingTypeOrder_RegularSampler]++; }, |
| [&](const StaticSamplerBindingInfo& layout) { |
| counts[BindingTypeOrder_StaticSampler]++; |
| if (layout.isUsedForSingleTexture) { |
| mNeedsCrossBindingValidation = true; |
| } |
| }, |
| [&](const TexelBufferBindingInfo&) { counts[BindingTypeOrder_TexelBuffer]++; }, |
| [&](const InputAttachmentBindingInfo&) { counts[BindingTypeOrder_InputAttachment]++; }, |
| [&](const ExternalTextureBindingInfo&) { counts[BindingTypeOrder_ExternalTexture]++; }); |
| } |
| |
| // Do a prefix sum to store the start offset of each binding type. |
| BindingIndex sum{0}; |
| for (auto [type, count] : Enumerate(counts)) { |
| mBindingTypeStart[type] = sum; |
| sum += count; |
| } |
| |
| // Recompute the number of bindings of each type from the descriptor since that is used for |
| // validation of the pipeline layout. |
| for (uint32_t i = 0; i < descriptor->entryCount; i++) { |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]); |
| IncrementBindingCounts(&mValidationBindingCounts, entry); |
| } |
| } |
| |
| BindGroupLayoutInternalBase::BindGroupLayoutInternalBase( |
| DeviceBase* device, |
| const UnpackedPtr<BindGroupLayoutDescriptor>& descriptor) |
| : BindGroupLayoutInternalBase(device, descriptor, kUntrackedByDevice) { |
| GetObjectTrackingList()->Track(this); |
| } |
| |
| BindGroupLayoutInternalBase::BindGroupLayoutInternalBase(DeviceBase* device, |
| ObjectBase::ErrorTag tag, |
| StringView label) |
| : ApiObjectBase(device, tag, label) {} |
| |
| BindGroupLayoutInternalBase::~BindGroupLayoutInternalBase() = default; |
| |
| void BindGroupLayoutInternalBase::DestroyImpl(DestroyReason reason) { |
| Uncache(); |
| } |
| |
| ObjectType BindGroupLayoutInternalBase::GetType() const { |
| return ObjectType::BindGroupLayoutInternal; |
| } |
| |
| const BindingInfo& BindGroupLayoutInternalBase::GetBindingInfo(BindingIndex bindingIndex) const { |
| DAWN_ASSERT(!IsError()); |
| // Assert that this is an internal binding. |
| DAWN_ASSERT(bindingIndex < GetBindingCount()); |
| return mBindingInfo[bindingIndex]; |
| } |
| |
| const BindingInfo& BindGroupLayoutInternalBase::GetAPIBindingInfo( |
| APIBindingIndex bindingIndex) const { |
| DAWN_ASSERT(!IsError()); |
| BindingIndex index = BindingIndex(uint32_t(bindingIndex)); |
| DAWN_ASSERT(index < mBindingInfo.size()); |
| // Assert this is a user-facing binding and not an private internal binding. |
| DAWN_ASSERT(mBindingMap.contains(mBindingInfo[index].binding)); |
| return mBindingInfo[index]; |
| } |
| |
| const BindGroupLayoutInternalBase::BindingMap& BindGroupLayoutInternalBase::GetBindingMap() const { |
| DAWN_ASSERT(!IsError()); |
| return mBindingMap; |
| } |
| |
| APIBindingIndex BindGroupLayoutInternalBase::GetAPIBindingIndex(BindingNumber bindingNumber) const { |
| DAWN_ASSERT(!IsError()); |
| const auto& it = mBindingMap.find(bindingNumber); |
| DAWN_ASSERT(it != mBindingMap.end()); |
| return it->second; |
| } |
| |
| BindingIndex BindGroupLayoutInternalBase::AsBindingIndex(APIBindingIndex bindingIndex) const { |
| DAWN_ASSERT(!IsError()); |
| // Assert this is a user-facing binding and not a private internal binding, and that it |
| // represents an internal bindings. |
| BindingIndex index = BindingIndex(uint32_t(bindingIndex)); |
| DAWN_ASSERT(index < GetBindingCount()); |
| DAWN_ASSERT(mBindingMap.contains(mBindingInfo[index].binding)); |
| return index; |
| } |
| |
| void BindGroupLayoutInternalBase::ReduceMemoryUsage() {} |
| |
| size_t BindGroupLayoutInternalBase::ComputeContentHash() { |
| ObjectContentHasher recorder; |
| |
| // std::map is sorted by key, so two BGLs constructed in different orders |
| // will still record the same. |
| for (const auto [id, index] : mBindingMap) { |
| recorder.Record(id, index); |
| } |
| |
| for (const auto& info : mBindingInfo) { |
| recorder.Record(info.visibility); |
| recorder.Record(info.arraySize); |
| recorder.Record(info.indexInArray); |
| |
| MatchVariant( |
| info.bindingLayout, |
| [&](const BufferBindingInfo& layout) { |
| recorder.Record(BindingInfoType::Buffer, layout.hasDynamicOffset, layout.type, |
| layout.minBindingSize); |
| }, |
| [&](const SamplerBindingInfo& layout) { |
| recorder.Record(BindingInfoType::Sampler, layout.type); |
| }, |
| [&](const TextureBindingInfo& layout) { |
| recorder.Record(BindingInfoType::Texture, layout.sampleType, layout.viewDimension, |
| layout.multisampled); |
| }, |
| [&](const StorageTextureBindingInfo& layout) { |
| recorder.Record(BindingInfoType::StorageTexture, layout.access, layout.format, |
| layout.viewDimension); |
| }, |
| [&](const TexelBufferBindingInfo& layout) { |
| recorder.Record(BindingInfoType::TexelBuffer, layout.format, layout.access); |
| }, |
| [&](const StaticSamplerBindingInfo& layout) { |
| recorder.Record(BindingInfoType::StaticSampler, layout.sampler->GetContentHash()); |
| }, |
| [&](const InputAttachmentBindingInfo& layout) { |
| recorder.Record(BindingInfoType::InputAttachment, layout.sampleType); |
| }, |
| [&](const ExternalTextureBindingInfo& layout) { |
| recorder.Record(BindingInfoType::ExternalTexture, layout.metadata, layout.plane0, |
| layout.plane1); |
| }); |
| } |
| |
| return recorder.GetContentHash(); |
| } |
| |
| bool BindGroupLayoutInternalBase::EqualityFunc::operator()( |
| const BindGroupLayoutInternalBase* a, |
| const BindGroupLayoutInternalBase* b) const { |
| if (a->GetBindingCount() != b->GetBindingCount()) { |
| return false; |
| } |
| for (BindingIndex i{0}; i < a->GetBindingCount(); ++i) { |
| if (a->mBindingInfo[i] != b->mBindingInfo[i]) { |
| return false; |
| } |
| } |
| if (a->mBindingMap != b->mBindingMap) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool BindGroupLayoutInternalBase::IsEmpty() const { |
| DAWN_ASSERT(!IsError()); |
| return mBindingInfo.empty(); |
| } |
| |
| BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const { |
| DAWN_ASSERT(!IsError()); |
| return GetBindingTypeStart(BindingTypeOrder_ExternalTexture); |
| } |
| |
| BindingIndex BindGroupLayoutInternalBase::GetDynamicBufferCount() const { |
| DAWN_ASSERT(!IsError()); |
| return GetBindingTypeEnd(BindingTypeOrder_DynamicBuffer) - |
| GetBindingTypeStart(BindingTypeOrder_DynamicBuffer); |
| } |
| |
| uint32_t BindGroupLayoutInternalBase::GetDynamicStorageBufferCount() const { |
| DAWN_ASSERT(!IsError()); |
| return mDynamicStorageBufferCount; |
| } |
| |
| uint32_t BindGroupLayoutInternalBase::GetUnverifiedBufferCount() const { |
| DAWN_ASSERT(!IsError()); |
| return mUnverifiedBufferCount; |
| } |
| |
| uint32_t BindGroupLayoutInternalBase::GetStaticSamplerCount() const { |
| DAWN_ASSERT(!IsError()); |
| return uint32_t(GetBindingTypeEnd(BindingTypeOrder_StaticSampler) - |
| GetBindingTypeStart(BindingTypeOrder_StaticSampler)); |
| } |
| |
| uint32_t BindGroupLayoutInternalBase::GetExternalTextureCount() const { |
| DAWN_ASSERT(!IsError()); |
| return uint32_t(GetBindingTypeEnd(BindingTypeOrder_ExternalTexture) - |
| GetBindingTypeStart(BindingTypeOrder_ExternalTexture)); |
| } |
| |
| const BindingCounts& BindGroupLayoutInternalBase::GetValidationBindingCounts() const { |
| DAWN_ASSERT(!IsError()); |
| return mValidationBindingCounts; |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetDynamicBufferIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_DynamicBuffer), |
| GetBindingTypeEnd(BindingTypeOrder_DynamicBuffer)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetBufferIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_DynamicBuffer), |
| GetBindingTypeEnd(BindingTypeOrder_RegularBuffer)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetStorageTextureIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_StorageTexture), |
| GetBindingTypeEnd(BindingTypeOrder_StorageTexture)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetTexelBufferIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_TexelBuffer), |
| GetBindingTypeEnd(BindingTypeOrder_TexelBuffer)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetSampledTextureIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_SampledTexture), |
| GetBindingTypeEnd(BindingTypeOrder_SampledTexture)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetTextureIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_SampledTexture), |
| GetBindingTypeEnd(BindingTypeOrder_InputAttachment)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetSamplerIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_StaticSampler), |
| GetBindingTypeEnd(BindingTypeOrder_RegularSampler)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetStaticSamplerIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_StaticSampler), |
| GetBindingTypeEnd(BindingTypeOrder_StaticSampler)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetNonStaticSamplerIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_RegularSampler), |
| GetBindingTypeEnd(BindingTypeOrder_RegularSampler)); |
| } |
| |
| BeginEndRange<BindingIndex> BindGroupLayoutInternalBase::GetInputAttachmentIndices() const { |
| return Range(GetBindingTypeStart(BindingTypeOrder_InputAttachment), |
| GetBindingTypeEnd(BindingTypeOrder_InputAttachment)); |
| } |
| |
| bool BindGroupLayoutInternalBase::NeedsCrossBindingValidation() const { |
| DAWN_ASSERT(!IsError()); |
| return mNeedsCrossBindingValidation; |
| } |
| |
| uint32_t BindGroupLayoutInternalBase::GetUnexpandedBindingCount() const { |
| DAWN_ASSERT(!IsError()); |
| return mValidationBindingCounts.totalCount; |
| } |
| |
| size_t BindGroupLayoutInternalBase::GetBindingDataSize() const { |
| DAWN_ASSERT(!IsError()); |
| // | ------ buffer-specific ----------| ------------ object pointers -------------| |
| // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------| |
| // Followed by: |
| // |---------buffer size array--------| |
| // |-uint64_t[mUnverifiedBufferCount]-| |
| const size_t bufferCount = size_t(GetBindingTypeEnd(BindingTypeOrder_RegularBuffer)); |
| const size_t bindingCount = size_t(mBindingInfo.size()); |
| |
| size_t objectPointerStart = bufferCount * sizeof(BufferBindingData); |
| DAWN_ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>))); |
| size_t bufferSizeArrayStart = |
| Align(objectPointerStart + bindingCount * sizeof(Ref<ObjectBase>), sizeof(uint64_t)); |
| DAWN_ASSERT(IsAligned(bufferSizeArrayStart, alignof(uint64_t))); |
| return bufferSizeArrayStart + mUnverifiedBufferCount * sizeof(uint64_t); |
| } |
| |
| BindGroupLayoutInternalBase::BindingDataPointers |
| BindGroupLayoutInternalBase::ComputeBindingDataPointers(void* dataStart) const { |
| const size_t bufferCount = size_t(GetBindingTypeEnd(BindingTypeOrder_RegularBuffer)); |
| const size_t bindingCount = size_t(mBindingInfo.size()); |
| |
| BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart); |
| auto bindings = reinterpret_cast<Ref<ObjectBase>*>(bufferData + bufferCount); |
| uint64_t* unverifiedBufferSizes = |
| AlignPtr(reinterpret_cast<uint64_t*>(bindings + bindingCount), sizeof(uint64_t)); |
| |
| DAWN_ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData))); |
| DAWN_ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>))); |
| DAWN_ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t))); |
| |
| return {{bufferData, GetBindingTypeEnd(BindingTypeOrder_RegularBuffer)}, |
| {bindings, GetBindingCount()}, |
| {unverifiedBufferSizes, mUnverifiedBufferCount}}; |
| } |
| |
| bool BindGroupLayoutInternalBase::IsStorageBufferBinding(BindingIndex bindingIndex) const { |
| switch (std::get<BufferBindingInfo>(GetBindingInfo(bindingIndex).bindingLayout).type) { |
| case wgpu::BufferBindingType::Uniform: |
| return false; |
| case kInternalStorageBufferBinding: |
| case kInternalReadOnlyStorageBufferBinding: |
| case wgpu::BufferBindingType::Storage: |
| case wgpu::BufferBindingType::ReadOnlyStorage: |
| return true; |
| case wgpu::BufferBindingType::BindingNotUsed: |
| case wgpu::BufferBindingType::Undefined: |
| break; |
| } |
| DAWN_UNREACHABLE(); |
| } |
| |
| std::string BindGroupLayoutInternalBase::EntriesToString() const { |
| std::string entries = "["; |
| std::string sep = ""; |
| const BindGroupLayoutInternalBase::BindingMap& bindingMap = GetBindingMap(); |
| for (const auto [bindingNumber, bindingIndex] : bindingMap) { |
| const BindingInfo& bindingInfo = GetAPIBindingInfo(bindingIndex); |
| entries += absl::StrFormat("%s%s", sep, bindingInfo); |
| sep = ", "; |
| } |
| entries += "]"; |
| return entries; |
| } |
| |
| BindingIndex BindGroupLayoutInternalBase::GetBindingTypeStart(BindingTypeOrder type) const { |
| return mBindingTypeStart[type]; |
| } |
| |
| BindingIndex BindGroupLayoutInternalBase::GetBindingTypeEnd(BindingTypeOrder type) const { |
| return mBindingTypeStart[BindingTypeOrder(static_cast<uint32_t>(type) + 1)]; |
| } |
| |
| } // namespace dawn::native |