| // 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 "src/dawn/native/BindGroupLayoutInternal.h" | |
| #include <algorithm> | |
| #include <functional> | |
| #include <limits> | |
| #include <list> | |
| #include <set> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "dawn/native/ObjectType_autogen.h" | |
| #include "dawn/native/ValidationUtils_autogen.h" | |
| #include "src/dawn/common/Enumerator.h" | |
| #include "src/dawn/common/MatchVariant.h" | |
| #include "src/dawn/native/ChainUtils.h" | |
| #include "src/dawn/native/Device.h" | |
| #include "src/dawn/native/Error.h" | |
| #include "src/dawn/native/Instance.h" | |
| #include "src/dawn/native/ObjectBase.h" | |
| #include "src/dawn/native/ObjectContentHasher.h" | |
| #include "src/dawn/native/PerStage.h" | |
| #include "src/dawn/native/Sampler.h" | |
| #include "src/dawn/native/TexelBufferView.h" | |
| #include "src/dawn/platform/metrics/HistogramMacros.h" | |
| #include "src/utils/compiler.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_CHECK(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.Has<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 (const BindGroupLayoutEntry& entryChain : descriptor->entries) { | |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&entryChain); | |
| 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 (auto [i, entryChain] : Enumerate(descriptorChain->entries)) { | |
| UnpackedPtr<BindGroupLayoutEntry> entry; | |
| DAWN_TRY_ASSIGN(entry, ValidateAndUnpack(&entryChain)); | |
| 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{1u}; | |
| 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 CreateStaticSamplerBindingForExternalTexture(const DeviceBase* device, | |
| BindingNumber binding, | |
| wgpu::ShaderStage visibility) { | |
| return { | |
| .binding = binding, | |
| .visibility = visibility, | |
| .bindingLayout = | |
| StaticSamplerBindingInfo{ | |
| .sampler = device->GetPlaceholderSampler(), | |
| .use = StaticSamplerUse::InternalForExternalTexture, | |
| }, | |
| }; | |
| } | |
| 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 DeviceBase* device, | |
| 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; | |
| std::optional<BindingNumber> staticSampler; | |
| }; | |
| 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 (const BindGroupLayoutEntry& entryChain : descriptor->entries) { | |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&entryChain); | |
| // External textures are expanded with additional bindings: | |
| // - Two sampled texture bindings and one uniform buffer | |
| // - (optionally) a static sampler that may be used to sample plane0. | |
| // 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.Has<ExternalTextureBindingLayout>()) { | |
| DAWN_CHECK(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); | |
| // External textures may alternatively use a static sampler to sample plane0, add it and | |
| // ensure its sampledTextureIndex is remapped. | |
| std::optional<BindingNumber> staticSamplerBinding = {}; | |
| if (device->NeedsStaticSamplerForExternalTexture()) { | |
| BindingInfo staticSamplerEntry = CreateStaticSamplerBindingForExternalTexture( | |
| device, nextOpenBindingNumberForNewEntry--, entry->visibility); | |
| entries.push_back(staticSamplerEntry); | |
| internalEntries.insert(staticSamplerEntry.binding); | |
| staticSamplerBinding = staticSamplerEntry.binding; | |
| staticSamplerToSingleTextureBinding.insert( | |
| {staticSamplerEntry.binding, plane0Entry.binding}); | |
| } | |
| externalTextureExpansions.insert({BindingNumber(entry->binding), | |
| { | |
| .plane0 = BindingNumber(plane0Entry.binding), | |
| .plane1 = BindingNumber(plane1Entry.binding), | |
| .metadata = BindingNumber(metadataEntry.binding), | |
| .staticSampler = staticSamplerBinding, | |
| }}); | |
| } | |
| 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_CHECK(inserted); | |
| } | |
| // Store the location of expanded entries in ExternalTexture layouts. | |
| for (const auto& [etBindingNumber, expansion] : externalTextureExpansions) { | |
| std::optional<BindingIndex> staticSamplerBinding = {}; | |
| if (expansion.staticSampler) { | |
| staticSamplerBinding = {fullBindingMap[expansion.staticSampler.value()]}; | |
| } | |
| entries[fullBindingMap[etBindingNumber]].bindingLayout = ExternalTextureBindingInfo{{ | |
| .metadata = fullBindingMap[expansion.metadata], | |
| .plane0 = fullBindingMap[expansion.plane0], | |
| .plane1 = fullBindingMap[expansion.plane1], | |
| .staticSampler = staticSamplerBinding, | |
| }}; | |
| } | |
| // 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_CHECK(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{0u}; | |
| 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(device, descriptor); | |
| mBindingInfo = std::move(unpackedBindings.entries); | |
| mBindingMap = std::move(unpackedBindings.apiBindingMap); | |
| DAWN_CHECK(CheckBufferBindingsFirst(mBindingInfo)); | |
| // 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.use == StaticSamplerUse::SingleTextureYCbCr) { | |
| mNeedsCrossBindingValidation = true; | |
| } | |
| }, | |
| [&](const TexelBufferBindingInfo&) { counts[BindingTypeOrder_TexelBuffer]++; }, | |
| [&](const InputAttachmentBindingInfo&) { counts[BindingTypeOrder_InputAttachment]++; }, | |
| [&](const ExternalTextureBindingInfo&) { counts[BindingTypeOrder_ExternalTexture]++; }); | |
| } | |
| // Populate map of binding to ordered index to access BindGroup::mBoundExternalTextures. | |
| size_t externalTextureIndex = 0; | |
| for (const auto& [bindingNumber, apiBindingIndex] : mBindingMap) { | |
| const BindingInfo& info = GetAPIBindingInfo(apiBindingIndex); | |
| if (std::holds_alternative<ExternalTextureBindingInfo>(info.bindingLayout)) { | |
| mBoundExternalTextureMap.emplace(apiBindingIndex, externalTextureIndex++); | |
| } | |
| } | |
| // Do a prefix sum to store the start offset of each binding type. | |
| BindingIndex sum{0u}; | |
| 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 (const BindGroupLayoutEntry& entryChain : descriptor->entries) { | |
| UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&entryChain); | |
| 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_CHECK(!IsError()); | |
| // Assert that this is an internal binding. | |
| DAWN_CHECK(bindingIndex < GetBindingCount()); | |
| return mBindingInfo[bindingIndex]; | |
| } | |
| const BindingInfo& BindGroupLayoutInternalBase::GetAPIBindingInfo( | |
| APIBindingIndex bindingIndex) const { | |
| DAWN_CHECK(!IsError()); | |
| BindingIndex index = BindingIndex(uint32_t(bindingIndex)); | |
| DAWN_CHECK(index < mBindingInfo.size()); | |
| // Assert this is a user-facing binding and not an private internal binding. | |
| DAWN_CHECK(mBindingMap.contains(mBindingInfo[index].binding)); | |
| return mBindingInfo[index]; | |
| } | |
| const BindGroupLayoutInternalBase::BindingMap& BindGroupLayoutInternalBase::GetBindingMap() const { | |
| DAWN_CHECK(!IsError()); | |
| return mBindingMap; | |
| } | |
| APIBindingIndex BindGroupLayoutInternalBase::GetAPIBindingIndex(BindingNumber bindingNumber) const { | |
| DAWN_CHECK(!IsError()); | |
| const auto& it = mBindingMap.find(bindingNumber); | |
| DAWN_CHECK(it != mBindingMap.end()); | |
| return it->second; | |
| } | |
| const BindGroupLayoutInternalBase::BoundExternalTextureMap& | |
| BindGroupLayoutInternalBase::GetBoundExternalTextureMap() const { | |
| DAWN_CHECK(!IsError()); | |
| return mBoundExternalTextureMap; | |
| } | |
| BindingIndex BindGroupLayoutInternalBase::AsBindingIndex(APIBindingIndex bindingIndex) const { | |
| DAWN_CHECK(!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_CHECK(index < GetBindingCount()); | |
| DAWN_CHECK(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{0u}; 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_CHECK(!IsError()); | |
| return mBindingInfo.empty(); | |
| } | |
| BindingIndex BindGroupLayoutInternalBase::GetBindingCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return GetBindingTypeStart(BindingTypeOrder_ExternalTexture); | |
| } | |
| BindingIndex BindGroupLayoutInternalBase::GetDynamicBufferCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return GetBindingTypeEnd(BindingTypeOrder_DynamicBuffer) - | |
| GetBindingTypeStart(BindingTypeOrder_DynamicBuffer); | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetDynamicStorageBufferCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return mDynamicStorageBufferCount; | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetUnverifiedBufferCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return mUnverifiedBufferCount; | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetAPIStaticSamplerCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return mValidationBindingCounts.staticSamplerCount; | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetStaticSamplerCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return uint32_t(GetBindingTypeEnd(BindingTypeOrder_StaticSampler) - | |
| GetBindingTypeStart(BindingTypeOrder_StaticSampler)); | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetExternalTextureCount() const { | |
| DAWN_CHECK(!IsError()); | |
| return uint32_t(GetBindingTypeEnd(BindingTypeOrder_ExternalTexture) - | |
| GetBindingTypeStart(BindingTypeOrder_ExternalTexture)); | |
| } | |
| const BindingCounts& BindGroupLayoutInternalBase::GetValidationBindingCounts() const { | |
| DAWN_CHECK(!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)); | |
| } | |
| BeginEndRange<APIBindingIndex> BindGroupLayoutInternalBase::GetExternalTextureIndices() const { | |
| // Cast the result of GetBindingType* as mBindingTypeStart works for ExternalTextures as well | |
| // but they are the only binding type that should be indexed with APIBindingIndex. | |
| return Range(APIBindingIndex{uint32_t{GetBindingTypeStart(BindingTypeOrder_ExternalTexture)}}, | |
| APIBindingIndex{uint32_t{GetBindingTypeEnd(BindingTypeOrder_ExternalTexture)}}); | |
| } | |
| bool BindGroupLayoutInternalBase::NeedsCrossBindingValidation() const { | |
| DAWN_CHECK(!IsError()); | |
| return mNeedsCrossBindingValidation; | |
| } | |
| uint32_t BindGroupLayoutInternalBase::GetBindingCountForBindGroupCreation() const { | |
| DAWN_CHECK(!IsError()); | |
| return mValidationBindingCounts.totalCount - mValidationBindingCounts.staticSamplerCount; | |
| } | |
| size_t BindGroupLayoutInternalBase::GetBindingDataSize() const { | |
| DAWN_CHECK(!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_CHECK(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>))); | |
| size_t bufferSizeArrayStart = | |
| Align(objectPointerStart + bindingCount * sizeof(Ref<ObjectBase>), sizeof(uint64_t)); | |
| DAWN_CHECK(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>*>(DAWN_UNSAFE_TODO(bufferData + bufferCount)); | |
| uint64_t* unverifiedBufferSizes = AlignPtr( | |
| reinterpret_cast<uint64_t*>(DAWN_UNSAFE_TODO(bindings + bindingCount)), sizeof(uint64_t)); | |
| DAWN_CHECK(IsPtrAligned(bufferData, alignof(BufferBindingData))); | |
| DAWN_CHECK(IsPtrAligned(bindings, alignof(Ref<ObjectBase>))); | |
| DAWN_CHECK(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(); | |
| } | |
| bool BindGroupLayoutInternalBase::IsExternalTextureBinding(APIBindingIndex bindingIndex) const { | |
| return std::holds_alternative<ExternalTextureBindingInfo>( | |
| GetAPIBindingInfo(bindingIndex).bindingLayout); | |
| } | |
| 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 |