Implement External Texture Binding Functionality Adds functionality to BindGroupLayout and BindGroup to allow GPUExternalTexture bindings. Bug: dawn:728 Change-Id: I651b28606dceda15f0a944711ddba639df77c1a3 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/47381 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json index 41ed56b..7029aad 100644 --- a/dawn.json +++ b/dawn.json
@@ -65,7 +65,7 @@ }, "bind group entry": { "category": "structure", - "extensible": false, + "extensible": true, "members": [ {"name": "binding", "type": "uint32_t"}, {"name": "buffer", "type": "buffer", "optional": true}, @@ -146,6 +146,20 @@ ] }, + "external texture binding entry": { + "category": "structure", + "chained": true, + "members": [ + {"name": "external texture", "type": "external texture"} + ] + }, + + "external texture binding layout": { + "category": "structure", + "chained": true, + "members": [] + }, + "storage texture access": { "category": "enum", "values": [ @@ -1787,7 +1801,9 @@ {"value": 5, "name": "shader module SPIRV descriptor"}, {"value": 6, "name": "shader module WGSL descriptor"}, {"value": 7, "name": "primitive depth clamping state"}, - {"value": 8, "name": "surface descriptor from windows core window"} + {"value": 8, "name": "surface descriptor from windows core window"}, + {"value": 9, "name": "external texture binding entry"}, + {"value": 10, "name": "external texture binding layout"} ] }, "texture": {
diff --git a/src/common/Constants.h b/src/common/Constants.h index 1ca0a66..f92c6b7 100644 --- a/src/common/Constants.h +++ b/src/common/Constants.h
@@ -68,4 +68,10 @@ // * 1024 / 8. static constexpr uint32_t kMaxQueryCount = 8192u; +// An external texture occupies multiple binding slots. These are the per-external-texture bindings +// needed. +static constexpr uint8_t kSampledTexturesPerExternalTexture = 3u; +static constexpr uint8_t kSamplersPerExternalTexture = 1u; +static constexpr uint8_t kUniformsPerExternalTexture = 1u; + #endif // COMMON_CONSTANTS_H_
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp index c84d578..6078eaa 100644 --- a/src/dawn_native/BindGroup.cpp +++ b/src/dawn_native/BindGroup.cpp
@@ -19,7 +19,9 @@ #include "common/ityp_bitset.h" #include "dawn_native/BindGroupLayout.h" #include "dawn_native/Buffer.h" +#include "dawn_native/ChainUtils_autogen.h" #include "dawn_native/Device.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/Sampler.h" #include "dawn_native/Texture.h" @@ -33,7 +35,7 @@ const BindGroupEntry& entry, const BindingInfo& bindingInfo) { if (entry.buffer == nullptr || entry.sampler != nullptr || - entry.textureView != nullptr) { + entry.textureView != nullptr || entry.nextInChain != nullptr) { return DAWN_VALIDATION_ERROR("Expected buffer binding"); } DAWN_TRY(device->ValidateObject(entry.buffer)); @@ -111,7 +113,7 @@ const BindGroupEntry& entry, const BindingInfo& bindingInfo) { if (entry.textureView == nullptr || entry.sampler != nullptr || - entry.buffer != nullptr) { + entry.buffer != nullptr || entry.nextInChain != nullptr) { return DAWN_VALIDATION_ERROR("Expected texture binding"); } DAWN_TRY(device->ValidateObject(entry.textureView)); @@ -176,7 +178,7 @@ const BindGroupEntry& entry, const BindingInfo& bindingInfo) { if (entry.sampler == nullptr || entry.textureView != nullptr || - entry.buffer != nullptr) { + entry.buffer != nullptr || entry.nextInChain != nullptr) { return DAWN_VALIDATION_ERROR("Expected sampler binding"); } DAWN_TRY(device->ValidateObject(entry.sampler)); @@ -203,6 +205,25 @@ return {}; } + MaybeError ValidateExternalTextureBinding(const DeviceBase* device, + const BindGroupEntry& entry, + const BindingInfo& bindingInfo) { + const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingEntry); + + if (entry.sampler != nullptr || entry.textureView != nullptr || + entry.buffer != nullptr || externalTextureBindingEntry == nullptr) { + return DAWN_VALIDATION_ERROR("Expected external texture binding"); + } + + DAWN_TRY(ValidateSingleSType(externalTextureBindingEntry->nextInChain, + wgpu::SType::ExternalTextureBindingEntry)); + + DAWN_TRY(device->ValidateObject(externalTextureBindingEntry->externalTexture)); + + return {}; + } + } // anonymous namespace MaybeError ValidateBindGroupDescriptor(DeviceBase* device, @@ -250,6 +271,9 @@ case BindingInfoType::Sampler: DAWN_TRY(ValidateSamplerBinding(device, entry, bindingInfo)); break; + case BindingInfoType::ExternalTexture: + DAWN_TRY(ValidateExternalTextureBinding(device, entry, bindingInfo)); + break; } } @@ -309,6 +333,14 @@ mBindingData.bindings[bindingIndex] = entry.sampler; continue; } + + const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingEntry); + if (externalTextureBindingEntry != nullptr) { + ASSERT(mBindingData.bindings[bindingIndex] == nullptr); + mBindingData.bindings[bindingIndex] = externalTextureBindingEntry->externalTexture; + continue; + } } uint32_t packedIdx = 0; @@ -388,4 +420,12 @@ return static_cast<TextureViewBase*>(mBindingData.bindings[bindingIndex].Get()); } + ExternalTextureBase* BindGroupBase::GetBindingAsExternalTexture(BindingIndex bindingIndex) { + ASSERT(!IsError()); + ASSERT(bindingIndex < mLayout->GetBindingCount()); + ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == + BindingInfoType::ExternalTexture); + return static_cast<ExternalTextureBase*>(mBindingData.bindings[bindingIndex].Get()); + } + } // namespace dawn_native
diff --git a/src/dawn_native/BindGroup.h b/src/dawn_native/BindGroup.h index c29bbeb..a636fe8 100644 --- a/src/dawn_native/BindGroup.h +++ b/src/dawn_native/BindGroup.h
@@ -49,6 +49,7 @@ SamplerBase* GetBindingAsSampler(BindingIndex bindingIndex) const; TextureViewBase* GetBindingAsTextureView(BindingIndex bindingIndex); const ityp::span<uint32_t, uint64_t>& GetUnverifiedBufferSizes() const; + ExternalTextureBase* GetBindingAsExternalTexture(BindingIndex bindingIndex); protected: // To save memory, the size of a bind group is dynamically determined and the bind group is
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp index e07656a..0f322af 100644 --- a/src/dawn_native/BindGroupLayout.cpp +++ b/src/dawn_native/BindGroupLayout.cpp
@@ -15,6 +15,8 @@ #include "dawn_native/BindGroupLayout.h" #include "common/BitSetIterator.h" + +#include "dawn_native/ChainUtils_autogen.h" #include "dawn_native/Device.h" #include "dawn_native/ObjectContentHasher.h" #include "dawn_native/PerStage.h" @@ -147,10 +149,16 @@ } } + const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + bindingMemberCount++; + } + if (bindingMemberCount != 1) { return DAWN_VALIDATION_ERROR( - "Exactly one of buffer, sampler, texture, or storageTexture must be set for " - "each BindGroupLayoutEntry"); + "Exactly one of buffer, sampler, texture, storageTexture, or externalTexture " + "must be set for each BindGroupLayoutEntry"); } if (!IsSubset(entry.visibility, allowedStages)) { @@ -190,6 +198,8 @@ return a.storageTexture.access != b.storageTexture.access || a.storageTexture.viewDimension != b.storageTexture.viewDimension || a.storageTexture.format != b.storageTexture.format; + case BindingInfoType::ExternalTexture: + return false; } } @@ -229,6 +239,12 @@ if (binding.storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) { bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D; } + } else { + const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr; + FindInChain(binding.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + bindingInfo.bindingType = BindingInfoType::ExternalTexture; + } } return bindingInfo; @@ -309,6 +325,8 @@ return aInfo.storageTexture.format < bInfo.storageTexture.format; } break; + case BindingInfoType::ExternalTexture: + break; } return false; }
diff --git a/src/dawn_native/BindingInfo.cpp b/src/dawn_native/BindingInfo.cpp index 3b13a33..e2facc4 100644 --- a/src/dawn_native/BindingInfo.cpp +++ b/src/dawn_native/BindingInfo.cpp
@@ -14,6 +14,8 @@ #include "dawn_native/BindingInfo.h" +#include "dawn_native/ChainUtils_autogen.h" + namespace dawn_native { void IncrementBindingCounts(BindingCounts* bindingCounts, const BindGroupLayoutEntry& entry) { @@ -56,6 +58,12 @@ perStageBindingCountMember = &PerStageBindingCounts::sampledTextureCount; } else if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) { perStageBindingCountMember = &PerStageBindingCounts::storageTextureCount; + } else { + const ExternalTextureBindingLayout* externalTextureBindingLayout; + FindInChain(entry.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + perStageBindingCountMember = &PerStageBindingCounts::externalTextureCount; + } } ASSERT(perStageBindingCountMember != nullptr); @@ -81,6 +89,8 @@ rhs.perStage[stage].storageTextureCount; bindingCounts->perStage[stage].uniformBufferCount += rhs.perStage[stage].uniformBufferCount; + bindingCounts->perStage[stage].externalTextureCount += + rhs.perStage[stage].externalTextureCount; } } @@ -104,25 +114,65 @@ "The number of sampled textures exceeds the maximum " "per-stage limit."); } + + // The per-stage number of external textures is bound by the maximum sampled textures + // per stage. + if (bindingCounts.perStage[stage].externalTextureCount > + kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture) { + return DAWN_VALIDATION_ERROR( + "The number of external textures exceeds the maximum " + "per-stage limit."); + } + + if (bindingCounts.perStage[stage].sampledTextureCount + + (bindingCounts.perStage[stage].externalTextureCount * + kSampledTexturesPerExternalTexture) > + kMaxSampledTexturesPerShaderStage) { + return DAWN_VALIDATION_ERROR( + "The combination of sampled textures and external textures exceeds the maximum " + "per-stage limit."); + } + if (bindingCounts.perStage[stage].samplerCount > kMaxSamplersPerShaderStage) { return DAWN_VALIDATION_ERROR( "The number of samplers exceeds the maximum per-stage limit."); } + + if (bindingCounts.perStage[stage].samplerCount + + (bindingCounts.perStage[stage].externalTextureCount * + kSamplersPerExternalTexture) > + kMaxSamplersPerShaderStage) { + return DAWN_VALIDATION_ERROR( + "The combination of samplers and external textures exceeds the maximum " + "per-stage limit."); + } + if (bindingCounts.perStage[stage].storageBufferCount > kMaxStorageBuffersPerShaderStage) { return DAWN_VALIDATION_ERROR( "The number of storage buffers exceeds the maximum per-stage limit."); } + if (bindingCounts.perStage[stage].storageTextureCount > kMaxStorageTexturesPerShaderStage) { return DAWN_VALIDATION_ERROR( "The number of storage textures exceeds the maximum per-stage limit."); } + if (bindingCounts.perStage[stage].uniformBufferCount > kMaxUniformBuffersPerShaderStage) { return DAWN_VALIDATION_ERROR( "The number of uniform buffers exceeds the maximum per-stage limit."); } + + if (bindingCounts.perStage[stage].uniformBufferCount + + (bindingCounts.perStage[stage].externalTextureCount * + kUniformsPerExternalTexture) > + kMaxUniformBuffersPerShaderStage) { + return DAWN_VALIDATION_ERROR( + "The combination of uniform buffers and external textures exceeds the maximum " + "per-stage limit."); + } } return {};
diff --git a/src/dawn_native/BindingInfo.h b/src/dawn_native/BindingInfo.h index ef3c7ac..f4a1730 100644 --- a/src/dawn_native/BindingInfo.h +++ b/src/dawn_native/BindingInfo.h
@@ -48,12 +48,7 @@ // TODO(enga): Figure out a good number for this. static constexpr uint32_t kMaxOptimalBindingsPerGroup = 32; - enum class BindingInfoType { - Buffer, - Sampler, - Texture, - StorageTexture, - }; + enum class BindingInfoType { Buffer, Sampler, Texture, StorageTexture, ExternalTexture }; struct BindingInfo { BindingNumber binding; @@ -74,6 +69,7 @@ uint32_t storageBufferCount; uint32_t storageTextureCount; uint32_t uniformBufferCount; + uint32_t externalTextureCount; }; struct BindingCounts {
diff --git a/src/dawn_native/ExternalTexture.cpp b/src/dawn_native/ExternalTexture.cpp index 76d06c5..6b1cee8 100644 --- a/src/dawn_native/ExternalTexture.cpp +++ b/src/dawn_native/ExternalTexture.cpp
@@ -85,7 +85,7 @@ ExternalTextureBase::ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor) - : ObjectBase(device) { + : ObjectBase(device), mState(ExternalTextureState::Alive) { textureViews[0] = descriptor->plane0; } @@ -93,15 +93,24 @@ : ObjectBase(device, tag) { } - std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat> ExternalTextureBase::GetTextureViews() - const { + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& + ExternalTextureBase::GetTextureViews() const { return textureViews; } + MaybeError ExternalTextureBase::ValidateCanUseInSubmitNow() const { + ASSERT(!IsError()); + if (mState == ExternalTextureState::Destroyed) { + return DAWN_VALIDATION_ERROR("Destroyed external texture used in a submit"); + } + return {}; + } + void ExternalTextureBase::APIDestroy() { if (GetDevice()->ConsumedError(GetDevice()->ValidateObject(this))) { return; } + mState = ExternalTextureState::Destroyed; ASSERT(!IsError()); }
diff --git a/src/dawn_native/ExternalTexture.h b/src/dawn_native/ExternalTexture.h index 7bc81e2..6dc5402 100644 --- a/src/dawn_native/ExternalTexture.h +++ b/src/dawn_native/ExternalTexture.h
@@ -31,11 +31,14 @@ class ExternalTextureBase : public ObjectBase { public: + enum class ExternalTextureState { Alive, Destroyed }; static ResultOrError<Ref<ExternalTextureBase>> Create( DeviceBase* device, const ExternalTextureDescriptor* descriptor); - std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat> GetTextureViews() const; + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& GetTextureViews() const; + + MaybeError ValidateCanUseInSubmitNow() const; static ExternalTextureBase* MakeError(DeviceBase* device); @@ -45,6 +48,7 @@ ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor); ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag); std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat> textureViews; + ExternalTextureState mState; }; } // namespace dawn_native
diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h index 66e07e4..9ee495d 100644 --- a/src/dawn_native/Forward.h +++ b/src/dawn_native/Forward.h
@@ -30,6 +30,7 @@ class CommandBufferBase; class CommandEncoder; class ComputePassEncoder; + class ExternalTextureBase; class Fence; class InstanceBase; class PipelineBase;
diff --git a/src/dawn_native/PassResourceUsage.h b/src/dawn_native/PassResourceUsage.h index 3168c39..555eb0f 100644 --- a/src/dawn_native/PassResourceUsage.h +++ b/src/dawn_native/PassResourceUsage.h
@@ -44,6 +44,8 @@ std::vector<TextureBase*> textures; std::vector<TextureSubresourceUsage> textureUsages; + + std::vector<ExternalTextureBase*> externalTextures; }; // Contains all the resource usage data for a compute pass. @@ -64,6 +66,7 @@ // All the resources referenced by this compute pass for validation in Queue::Submit. std::set<BufferBase*> referencedBuffers; std::set<TextureBase*> referencedTextures; + std::set<ExternalTextureBase*> referencedExternalTextures; }; // Contains all the resource usage data for a render pass.
diff --git a/src/dawn_native/PassResourceUsageTracker.cpp b/src/dawn_native/PassResourceUsageTracker.cpp index 75db456..18fd319 100644 --- a/src/dawn_native/PassResourceUsageTracker.cpp +++ b/src/dawn_native/PassResourceUsageTracker.cpp
@@ -17,6 +17,7 @@ #include "dawn_native/BindGroup.h" #include "dawn_native/Buffer.h" #include "dawn_native/EnumMaskIterator.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/Format.h" #include "dawn_native/QuerySet.h" #include "dawn_native/Texture.h" @@ -109,6 +110,23 @@ break; } + case BindingInfoType::ExternalTexture: { + ExternalTextureBase* externalTexture = + group->GetBindingAsExternalTexture(bindingIndex); + + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& textureViews = + externalTexture->GetTextureViews(); + + // Only single-plane formats are supported right now, so assert only one + // view exists. + ASSERT(textureViews[1].Get() == nullptr); + ASSERT(textureViews[2].Get() == nullptr); + + mExternalTextureUsages.insert(externalTexture); + TextureViewUsedAs(textureViews[0].Get(), wgpu::TextureUsage::Sampled); + break; + } + case BindingInfoType::Sampler: break; } @@ -132,8 +150,13 @@ result.textureUsages.push_back(std::move(it.second)); } + for (auto& it : mExternalTextureUsages) { + result.externalTextures.push_back(it); + } + mBufferUsages.clear(); mTextureUsages.clear(); + mExternalTextureUsages.clear(); return result; } @@ -162,6 +185,22 @@ break; } + case BindingInfoType::ExternalTexture: { + ExternalTextureBase* externalTexture = + group->GetBindingAsExternalTexture(index); + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& textureViews = + externalTexture->GetTextureViews(); + + // Only single-plane formats are supported right now, so assert only one + // view exists. + ASSERT(textureViews[1].Get() == nullptr); + ASSERT(textureViews[2].Get() == nullptr); + + mUsage.referencedExternalTextures.insert(externalTexture); + mUsage.referencedTextures.insert(textureViews[0].Get()->GetTexture()); + break; + } + case BindingInfoType::StorageTexture: case BindingInfoType::Sampler: break;
diff --git a/src/dawn_native/PassResourceUsageTracker.h b/src/dawn_native/PassResourceUsageTracker.h index 56b5854..d4de8fa 100644 --- a/src/dawn_native/PassResourceUsageTracker.h +++ b/src/dawn_native/PassResourceUsageTracker.h
@@ -25,6 +25,7 @@ class BindGroupBase; class BufferBase; + class ExternalTextureBase; class QuerySetBase; class TextureBase; @@ -46,6 +47,7 @@ private: std::map<BufferBase*, wgpu::BufferUsage> mBufferUsages; std::map<TextureBase*, TextureSubresourceUsage> mTextureUsages; + std::set<ExternalTextureBase*> mExternalTextureUsages; }; // Helper class to build ComputePassResourceUsages
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp index bc659ee..52e2008 100644 --- a/src/dawn_native/PipelineLayout.cpp +++ b/src/dawn_native/PipelineLayout.cpp
@@ -147,6 +147,15 @@ case BindingInfoType::StorageTexture: entry.storageTexture = shaderBinding.storageTexture; break; + case BindingInfoType::ExternalTexture: + // TODO(dawn:728) On backend configurations that use SPIRV-Cross to reflect + // shader info - the shader must have been already transformed prior to + // reflecting the shader. During transformation, all instances of + // texture_external are changed to texture_2d<f32>. This means that when + // extracting shader info, external textures will be seen as sampled 2d + // textures. In the future when Dawn no longer uses SPIRV-Cross, we should + // handle external textures here. + break; } return entry; };
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp index fc3d9b3..05e76a0 100644 --- a/src/dawn_native/Queue.cpp +++ b/src/dawn_native/Queue.cpp
@@ -23,6 +23,7 @@ #include "dawn_native/CopyTextureForBrowserHelper.h" #include "dawn_native/Device.h" #include "dawn_native/DynamicUploader.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/QuerySet.h" #include "dawn_native/RenderPassEncoder.h" #include "dawn_native/RenderPipeline.h" @@ -382,6 +383,10 @@ for (const TextureBase* texture : scope.textures) { DAWN_TRY(texture->ValidateCanUseInSubmitNow()); } + + for (const ExternalTextureBase* externalTexture : scope.externalTextures) { + DAWN_TRY(externalTexture->ValidateCanUseInSubmitNow()); + } } for (const ComputePassResourceUsage& pass : usages.computePasses) { @@ -391,6 +396,9 @@ for (const TextureBase* texture : pass.referencedTextures) { DAWN_TRY(texture->ValidateCanUseInSubmitNow()); } + for (const ExternalTextureBase* externalTexture : pass.referencedExternalTextures) { + DAWN_TRY(externalTexture->ValidateCanUseInSubmitNow()); + } } for (const BufferBase* buffer : usages.topLevelBuffers) {
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp index b191636..4bff6d6 100644 --- a/src/dawn_native/ShaderModule.cpp +++ b/src/dawn_native/ShaderModule.cpp
@@ -159,6 +159,8 @@ case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageTexture: case tint::inspector::ResourceBinding::ResourceType::kWriteOnlyStorageTexture: return BindingInfoType::StorageTexture; + case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: + return BindingInfoType::ExternalTexture; default: UNREACHABLE(); @@ -503,9 +505,19 @@ const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex); if (layoutInfo.bindingType != shaderInfo.bindingType) { - return DAWN_VALIDATION_ERROR( - "The binding type of the bind group layout entry conflicts " + - GetShaderDeclarationString(group, bindingNumber)); + // TODO(dawn:728) On backend configurations that use SPIRV-Cross to reflect + // shader info - the shader must have been already transformed prior to + // reflecting the shader. During transformation, all instances of + // texture_external are changed to texture_2d<f32>. This means that when + // extracting shader info, external textures will be seen as sampled 2d + // textures. In the future when Dawn no longer uses SPIRV-Cross, the + // if-statement below should be removed. + if (layoutInfo.bindingType != BindingInfoType::ExternalTexture || + shaderInfo.bindingType != BindingInfoType::Texture) { + return DAWN_VALIDATION_ERROR( + "The binding type of the bind group layout entry conflicts " + + GetShaderDeclarationString(group, bindingNumber)); + } } if ((layoutInfo.visibility & StageBit(entryPoint.stage)) == 0) { @@ -567,6 +579,17 @@ break; } + case BindingInfoType::ExternalTexture: { + // TODO(dawn:728) On backend configurations that use SPIRV-Cross to reflect + // shader info - the shader must have been already transformed prior to + // reflecting the shader. During transformation, all instances of + // texture_external are changed to texture_2d<f32>. This means that when + // extracting shader info, external textures will be seen as sampled 2d + // textures. In the future when Dawn no longer uses SPIRV-Cross, we should + // handle external textures here. + break; + } + case BindingInfoType::Buffer: { // Binding mismatch between shader and bind group is invalid. For example, a // writable binding in the shader with a readonly storage buffer in the bind @@ -755,6 +778,11 @@ } case BindingInfoType::Sampler: { info->sampler.type = wgpu::SamplerBindingType::Filtering; + break; + } + case BindingInfoType::ExternalTexture: { + return DAWN_VALIDATION_ERROR("External textures are not supported."); + break; } } } @@ -994,6 +1022,8 @@ TintTextureDimensionToTextureViewDimension(resource.dim); break; + case BindingInfoType::ExternalTexture: + break; default: return DAWN_VALIDATION_ERROR("Unknown binding type in Shader"); }
diff --git a/src/dawn_native/d3d12/BindGroupD3D12.cpp b/src/dawn_native/d3d12/BindGroupD3D12.cpp index 75eb734..a1b095b 100644 --- a/src/dawn_native/d3d12/BindGroupD3D12.cpp +++ b/src/dawn_native/d3d12/BindGroupD3D12.cpp
@@ -15,6 +15,7 @@ #include "dawn_native/d3d12/BindGroupD3D12.h" #include "common/BitSetIterator.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/d3d12/BindGroupLayoutD3D12.h" #include "dawn_native/d3d12/BufferD3D12.h" #include "dawn_native/d3d12/DeviceD3D12.h" @@ -185,6 +186,26 @@ break; } + case BindingInfoType::ExternalTexture: { + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& views = + GetBindingAsExternalTexture(bindingIndex)->GetTextureViews(); + + // Only single-plane formats are supported right now, so assert only one view + // exists. + ASSERT(views[1].Get() == nullptr); + ASSERT(views[2].Get() == nullptr); + + auto& srv = ToBackend(views[0])->GetSRVDescriptor(); + + ID3D12Resource* resource = + ToBackend(views[0]->GetTexture())->GetD3D12Resource(); + + d3d12Device->CreateShaderResourceView( + resource, &srv, + viewAllocation.OffsetFrom(viewSizeIncrement, bindingOffsets[bindingIndex])); + break; + } + case BindingInfoType::Sampler: { // No-op as samplers will be later initialized by CreateSamplers(). break;
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp index c515166..8557b44 100644 --- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp +++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
@@ -41,6 +41,7 @@ return BindGroupLayout::DescriptorType::Sampler; case BindingInfoType::Texture: + case BindingInfoType::ExternalTexture: return BindGroupLayout::DescriptorType::SRV; case BindingInfoType::StorageTexture: @@ -77,6 +78,8 @@ // dynamic resources in calculating the size of the descriptor heap. ASSERT(!bindingInfo.buffer.hasDynamicOffset); + // TODO(dawn:728) In the future, special handling will be needed for external textures + // here because they encompass multiple views. DescriptorType descriptorType = WGPUBindingInfoToDescriptorType(bindingInfo); mBindingOffsets[bindingIndex] = mDescriptorCounts[descriptorType]++; } @@ -135,6 +138,8 @@ } // TODO(shaobo.yan@intel.com): Implement dynamic buffer offset. + // TODO(dawn:728) In the future, special handling will be needed here for external + // textures because they encompass multiple views. DescriptorType descriptorType = WGPUBindingInfoToDescriptorType(bindingInfo); mBindingOffsets[bindingIndex] += descriptorOffsets[descriptorType]; }
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 04cb72d..e7da839 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -17,6 +17,7 @@ #include "dawn_native/BindGroupTracker.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/RenderBundle.h" #include "dawn_native/metal/BindGroupMTL.h" #include "dawn_native/metal/BufferMTL.h" @@ -467,6 +468,32 @@ } break; } + + case BindingInfoType::ExternalTexture: { + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& views = + group->GetBindingAsExternalTexture(bindingIndex)->GetTextureViews(); + + // Only single-plane formats are supported right now, so assert only one + // view exists. + ASSERT(views[1].Get() == nullptr); + ASSERT(views[2].Get() == nullptr); + + TextureView* textureView = ToBackend(views[0].Get()); + + if (hasVertStage) { + [render setVertexTexture:textureView->GetMTLTexture() + atIndex:vertIndex]; + } + if (hasFragStage) { + [render setFragmentTexture:textureView->GetMTLTexture() + atIndex:fragIndex]; + } + if (hasComputeStage) { + [compute setTexture:textureView->GetMTLTexture() + atIndex:computeIndex]; + } + break; + } } } }
diff --git a/src/dawn_native/metal/PipelineLayoutMTL.mm b/src/dawn_native/metal/PipelineLayoutMTL.mm index 34ddc44..4faf5db 100644 --- a/src/dawn_native/metal/PipelineLayoutMTL.mm +++ b/src/dawn_native/metal/PipelineLayoutMTL.mm
@@ -58,6 +58,7 @@ case BindingInfoType::Texture: case BindingInfoType::StorageTexture: + case BindingInfoType::ExternalTexture: mIndexInfo[stage][group][bindingIndex] = textureIndex; textureIndex++; break;
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 1fbbbba..0d8599f 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -19,6 +19,7 @@ #include "dawn_native/BindGroupTracker.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/Commands.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/RenderBundle.h" #include "dawn_native/opengl/BufferGL.h" #include "dawn_native/opengl/ComputePipelineGL.h" @@ -362,6 +363,29 @@ texture->GetGLFormat().internalFormat); break; } + + case BindingInfoType::ExternalTexture: { + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& + textureViews = mBindGroups[index] + ->GetBindingAsExternalTexture(bindingIndex) + ->GetTextureViews(); + + // Only single-plane formats are supported right now, so assert only one + // view exists. + ASSERT(textureViews[1].Get() == nullptr); + ASSERT(textureViews[2].Get() == nullptr); + + TextureView* view = ToBackend(textureViews[0].Get()); + GLuint handle = view->GetHandle(); + GLenum target = view->GetGLTarget(); + GLuint viewIndex = indices[bindingIndex]; + + for (auto unit : mPipeline->GetTextureUnitsForTextureView(viewIndex)) { + gl.ActiveTexture(GL_TEXTURE0 + unit); + gl.BindTexture(target, handle); + } + break; + } } } }
diff --git a/src/dawn_native/opengl/PipelineLayoutGL.cpp b/src/dawn_native/opengl/PipelineLayoutGL.cpp index 088eaf3..ef2cf7c 100644 --- a/src/dawn_native/opengl/PipelineLayoutGL.cpp +++ b/src/dawn_native/opengl/PipelineLayoutGL.cpp
@@ -58,6 +58,7 @@ break; case BindingInfoType::Texture: + case BindingInfoType::ExternalTexture: mIndexInfo[group][bindingIndex] = sampledTextureIndex; sampledTextureIndex++; break;
diff --git a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp index 78f7a7a..6ee1d49 100644 --- a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp +++ b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
@@ -67,6 +67,7 @@ case BindingInfoType::Sampler: return VK_DESCRIPTOR_TYPE_SAMPLER; case BindingInfoType::Texture: + case BindingInfoType::ExternalTexture: return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; case BindingInfoType::StorageTexture: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; @@ -99,6 +100,8 @@ VkDescriptorSetLayoutBinding vkBinding; vkBinding.binding = useBindingIndex ? static_cast<uint32_t>(bindingIndex) : static_cast<uint32_t>(bindingNumber); + // TODO(dawn:728) In the future, special handling will be needed for external textures + // here because they encompass multiple views. vkBinding.descriptorType = VulkanDescriptorType(bindingInfo); vkBinding.descriptorCount = 1; vkBinding.stageFlags = VulkanShaderStageFlags(bindingInfo.visibility); @@ -123,6 +126,8 @@ std::map<VkDescriptorType, uint32_t> descriptorCountPerType; for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) { + // TODO(dawn:728) In the future, special handling will be needed for external textures + // here because they encompass multiple views. VkDescriptorType vulkanType = VulkanDescriptorType(GetBindingInfo(bindingIndex)); // map::operator[] will return 0 if the key doesn't exist.
diff --git a/src/dawn_native/vulkan/BindGroupVk.cpp b/src/dawn_native/vulkan/BindGroupVk.cpp index b2334d1..251e4ff 100644 --- a/src/dawn_native/vulkan/BindGroupVk.cpp +++ b/src/dawn_native/vulkan/BindGroupVk.cpp
@@ -16,6 +16,7 @@ #include "common/BitSetIterator.h" #include "common/ityp_stack_vec.h" +#include "dawn_native/ExternalTexture.h" #include "dawn_native/vulkan/BindGroupLayoutVk.h" #include "dawn_native/vulkan/BufferVk.h" #include "dawn_native/vulkan/DeviceVk.h" @@ -113,6 +114,25 @@ write.pImageInfo = &writeImageInfo[numWrites]; break; } + + case BindingInfoType::ExternalTexture: { + const std::array<Ref<dawn_native::TextureViewBase>, kMaxPlanesPerFormat>& + textureViews = GetBindingAsExternalTexture(bindingIndex)->GetTextureViews(); + + // Only single-plane formats are supported right now, so ensure only one view + // exists. + ASSERT(textureViews[1].Get() == nullptr); + ASSERT(textureViews[2].Get() == nullptr); + + TextureView* view = ToBackend(textureViews[0].Get()); + + writeImageInfo[numWrites].imageView = view->GetHandle(); + writeImageInfo[numWrites].imageLayout = VulkanImageLayout( + ToBackend(view->GetTexture()), wgpu::TextureUsage::Sampled); + + write.pImageInfo = &writeImageInfo[numWrites]; + break; + } } numWrites++;
diff --git a/src/tests/end2end/BindGroupTests.cpp b/src/tests/end2end/BindGroupTests.cpp index 8408609..155bd25 100644 --- a/src/tests/end2end/BindGroupTests.cpp +++ b/src/tests/end2end/BindGroupTests.cpp
@@ -1183,12 +1183,12 @@ for (uint32_t i = 0; i < kMaxSampledTexturesPerShaderStage; ++i) { wgpu::Texture texture = CreateTextureWithRedData( wgpu::TextureFormat::R8Unorm, expectedValue, wgpu::TextureUsage::Sampled); - bgEntries.push_back({binding, nullptr, 0, 0, nullptr, texture.CreateView()}); + bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()}); interface << "[[group(0), binding(" << binding++ << ")]] " << "var tex" << i << " : texture_2d<f32>;\n"; - bgEntries.push_back({binding, nullptr, 0, 0, device.CreateSampler(), nullptr}); + bgEntries.push_back({nullptr, binding, nullptr, 0, 0, device.CreateSampler(), nullptr}); interface << "[[group(0), binding(" << binding++ << ")]]" << "var samp" << i << " : sampler;\n"; @@ -1202,7 +1202,7 @@ for (uint32_t i = 0; i < kMaxStorageTexturesPerShaderStage; ++i) { wgpu::Texture texture = CreateTextureWithRedData( wgpu::TextureFormat::R32Uint, expectedValue, wgpu::TextureUsage::Storage); - bgEntries.push_back({binding, nullptr, 0, 0, nullptr, texture.CreateView()}); + bgEntries.push_back({nullptr, binding, nullptr, 0, 0, nullptr, texture.CreateView()}); interface << "[[group(0), binding(" << binding++ << ")]] " << "var image" << i << " : [[access(read)]] texture_storage_2d<r32uint>;\n"; @@ -1216,7 +1216,7 @@ for (uint32_t i = 0; i < kMaxUniformBuffersPerShaderStage; ++i) { wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>( device, wgpu::BufferUsage::Uniform, {expectedValue, 0, 0, 0}); - bgEntries.push_back({binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr}); + bgEntries.push_back({nullptr, binding, buffer, 0, 4 * sizeof(uint32_t), nullptr, nullptr}); interface << "[[block]] struct UniformBuffer" << i << R"({ value : u32; @@ -1233,7 +1233,7 @@ for (uint32_t i = 0; i < kMaxStorageBuffersPerShaderStage - 1; ++i) { wgpu::Buffer buffer = utils::CreateBufferFromData<uint32_t>( device, wgpu::BufferUsage::Storage, {expectedValue}); - bgEntries.push_back({binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr}); + bgEntries.push_back({nullptr, binding, buffer, 0, sizeof(uint32_t), nullptr, nullptr}); interface << "[[block]] struct ReadOnlyStorageBuffer" << i << R"({ value : u32; @@ -1250,7 +1250,7 @@ wgpu::Buffer result = utils::CreateBufferFromData<uint32_t>( device, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, {0}); - bgEntries.push_back({binding, result, 0, sizeof(uint32_t), nullptr, nullptr}); + bgEntries.push_back({nullptr, binding, result, 0, sizeof(uint32_t), nullptr, nullptr}); interface << R"([[block]] struct ReadWriteStorageBuffer{ value : u32;
diff --git a/src/tests/end2end/ExternalTextureTests.cpp b/src/tests/end2end/ExternalTextureTests.cpp index 3060815..99fa9bf 100644 --- a/src/tests/end2end/ExternalTextureTests.cpp +++ b/src/tests/end2end/ExternalTextureTests.cpp
@@ -13,6 +13,8 @@ // limitations under the License. #include "tests/DawnTest.h" +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/WGPUHelpers.h" namespace { @@ -43,8 +45,6 @@ } // anonymous namespace TEST_P(ExternalTextureTests, CreateExternalTextureSuccess) { - DAWN_TEST_UNSUPPORTED_IF(UsesWire()); - wgpu::Texture texture = Create2DTexture(device, kWidth, kHeight, kFormat, kSampledUsage); // Create a texture view for the external texture @@ -61,6 +61,94 @@ ASSERT_NE(externalTexture.Get(), nullptr); } +TEST_P(ExternalTextureTests, SampleExternalTexture) { + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + [[stage(vertex)]] fn main([[builtin(vertex_idx)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> { + + let positions : array<vec4<f32>, 3> = array<vec4<f32>, 3>( + vec4<f32>(-1.0, 1.0, 0.0, 1.0), + vec4<f32>(-1.0, -1.0, 0.0, 1.0), + vec4<f32>(1.0, 1.0, 0.0, 1.0) + ); + + return positions[VertexIndex]; + })"); + + const wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var s : sampler; + [[group(0), binding(1)]] var t : texture_external; + + [[stage(fragment)]] fn main([[builtin(position)]] FragCoord : vec4<f32>) + -> [[location(0)]] vec4<f32> { + return textureSampleLevel(t, s, FragCoord.xy / vec2<f32>(4.0, 4.0)); + })"); + + wgpu::Texture sampledTexture = + Create2DTexture(device, kWidth, kHeight, kFormat, + wgpu::TextureUsage::Sampled | wgpu::TextureUsage::RenderAttachment); + wgpu::Texture renderTexture = + Create2DTexture(device, kWidth, kHeight, kFormat, + wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); + + // Create a texture view for the external texture + wgpu::TextureView externalView = sampledTexture.CreateView(); + + // Initialize texture with green to ensure it is sampled from later. + { + utils::ComboRenderPassDescriptor renderPass({externalView}, nullptr); + renderPass.cColorAttachments[0].clearColor = {0.0f, 1.0f, 0.0f, 1.0f}; + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.EndPass(); + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + } + + // Create an ExternalTextureDescriptor from the texture view + wgpu::ExternalTextureDescriptor externalDesc; + externalDesc.plane0 = externalView; + externalDesc.format = kFormat; + + // Import the external texture + wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); + + // Create a sampler and bind group + wgpu::Sampler sampler = device.CreateSampler(); + + wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, + {1, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}}); + wgpu::BindGroup bindGroup = + utils::MakeBindGroup(device, bgl, {{0, sampler}, {1, externalTexture}}); + + // Pipeline Creation + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); + descriptor.vertex.module = vsModule; + descriptor.cFragment.module = fsModule; + descriptor.cTargets[0].format = kFormat; + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); + + // Run the shader, which should sample from the external texture and draw a triangle into the + // upper left corner of the render texture. + wgpu::TextureView renderView = renderTexture.CreateView(); + utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + { + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(3); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kGreen, renderTexture, 0, 0); +} + DAWN_INSTANTIATE_TEST(ExternalTextureTests, D3D12Backend(), MetalBackend(),
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp index 601f421..33c656f 100644 --- a/src/tests/unittests/validation/BindGroupValidationTests.cpp +++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -53,9 +53,14 @@ } { mSampler = device.CreateSampler(); } { - mSampledTexture = - CreateTexture(wgpu::TextureUsage::Sampled, wgpu::TextureFormat::RGBA8Unorm, 1); + mSampledTexture = CreateTexture(wgpu::TextureUsage::Sampled, kDefaultTextureFormat, 1); mSampledTextureView = mSampledTexture.CreateView(); + + wgpu::ExternalTextureDescriptor externalTextureDesc; + externalTextureDesc.format = kDefaultTextureFormat; + externalTextureDesc.plane0 = mSampledTextureView; + mExternalTexture = device.CreateExternalTexture(&externalTextureDesc); + mExternalTextureBindingEntry.externalTexture = mExternalTexture; } } @@ -65,6 +70,12 @@ wgpu::Sampler mSampler; wgpu::Texture mSampledTexture; wgpu::TextureView mSampledTextureView; + wgpu::ExternalTextureBindingEntry mExternalTextureBindingEntry; + + static constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm; + + private: + wgpu::ExternalTexture mExternalTexture; }; // Test the validation of BindGroupDescriptor::nextInChain @@ -159,6 +170,11 @@ ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); binding.buffer = nullptr; + // Setting the external texture view as well is an error + binding.nextInChain = &mExternalTextureBindingEntry; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.nextInChain = nullptr; + // Setting the sampler to an error sampler is an error. { wgpu::SamplerDescriptor samplerDesc; @@ -208,10 +224,15 @@ ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); binding.buffer = nullptr; + // Setting the external texture view as well is an error + binding.nextInChain = &mExternalTextureBindingEntry; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.nextInChain = nullptr; + // Setting the texture view to an error texture view is an error. { wgpu::TextureViewDescriptor viewDesc; - viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; + viewDesc.format = kDefaultTextureFormat; viewDesc.dimension = wgpu::TextureViewDimension::e2D; viewDesc.baseMipLevel = 0; viewDesc.mipLevelCount = 0; @@ -262,6 +283,11 @@ ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); binding.sampler = nullptr; + // Setting the external texture view as well is an error + binding.nextInChain = &mExternalTextureBindingEntry; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.nextInChain = nullptr; + // Setting the buffer to an error buffer is an error. { wgpu::BufferDescriptor bufferDesc; @@ -277,6 +303,91 @@ } } +// Check that an external texture binding must contain exactly an external texture +TEST_F(BindGroupValidationTest, ExternalTextureBindingType) { + // Create an external texture + wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::Sampled, kDefaultTextureFormat, 1); + wgpu::ExternalTextureDescriptor externalDesc; + externalDesc.plane0 = texture.CreateView(); + externalDesc.format = kDefaultTextureFormat; + wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); + + // Create a bind group layout for a single external texture + wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}}); + + wgpu::BindGroupEntry binding; + binding.binding = 0; + binding.sampler = nullptr; + binding.textureView = nullptr; + binding.buffer = nullptr; + binding.offset = 0; + binding.size = 0; + + wgpu::BindGroupDescriptor descriptor; + descriptor.layout = layout; + descriptor.entryCount = 1; + descriptor.entries = &binding; + + // Not setting anything fails + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + + // Control case: setting just the external texture works + wgpu::ExternalTextureBindingEntry externalBindingEntry; + externalBindingEntry.externalTexture = externalTexture; + binding.nextInChain = &externalBindingEntry; + device.CreateBindGroup(&descriptor); + + // Setting the texture view as well is an error + binding.textureView = mSampledTextureView; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.textureView = nullptr; + + // Setting the sampler as well is an error + binding.sampler = mSampler; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.sampler = nullptr; + + // Setting the buffer as well is an error + binding.buffer = mUBO; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.buffer = nullptr; + + // Setting the external texture to an error external texture is an error. + { + wgpu::ExternalTextureDescriptor errorExternalDesciptor; + errorExternalDesciptor.plane0 = texture.CreateView(); + errorExternalDesciptor.format = wgpu::TextureFormat::R8Uint; + + wgpu::ExternalTexture errorExternalTexture; + ASSERT_DEVICE_ERROR(errorExternalTexture = + device.CreateExternalTexture(&errorExternalDesciptor)); + + wgpu::ExternalTextureBindingEntry errorExternalBindingEntry; + errorExternalBindingEntry.externalTexture = errorExternalTexture; + binding.nextInChain = &errorExternalBindingEntry; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + binding.nextInChain = nullptr; + } + + // Setting an external texture with another external texture chained is an error. + { + wgpu::ExternalTexture externalTexture2 = device.CreateExternalTexture(&externalDesc); + wgpu::ExternalTextureBindingEntry externalBindingEntry2; + externalBindingEntry2.externalTexture = externalTexture2; + externalBindingEntry.nextInChain = &externalBindingEntry2; + + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + } + + // Chaining a struct that isn't an external texture binding entry is an error. + { + wgpu::ExternalTextureBindingLayout externalBindingLayout; + binding.nextInChain = &externalBindingLayout; + ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor)); + } +} + // Check that a texture must have the correct usage TEST_F(BindGroupValidationTest, TextureUsage) { wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout( @@ -746,7 +857,7 @@ wgpu::BindGroupLayoutEntry otherEntry; }; - std::array<TestInfo, 7> kTestInfos = { + std::array<TestInfo, 8> kTestInfos = { TestInfo{kMaxSampledTexturesPerShaderStage, BGLEntryType(wgpu::TextureSampleType::Float), BGLEntryType(wgpu::BufferBindingType::Uniform)}, TestInfo{kMaxSamplersPerShaderStage, BGLEntryType(wgpu::SamplerBindingType::Filtering), @@ -765,7 +876,12 @@ BGLEntryType(wgpu::BufferBindingType::Uniform)}, TestInfo{kMaxUniformBuffersPerShaderStage, BGLEntryType(wgpu::BufferBindingType::Uniform), BGLEntryType(wgpu::TextureSampleType::Float)}, - }; + // External textures use multiple bindings (3 sampled textures, 1 sampler, 1 uniform buffer) + // that count towards the per stage binding limits. The number of external textures are + // currently restricted by the maximum number of sampled textures. + TestInfo{kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture, + BGLEntryType(&utils::kExternalTextureBindingLayout), + BGLEntryType(wgpu::BufferBindingType::Uniform)}}; for (TestInfo info : kTestInfos) { wgpu::BindGroupLayout bgl[2]; @@ -829,6 +945,98 @@ } } +// External textures require multiple binding slots (3 sampled texture, 1 uniform buffer, 1 +// sampler), so ensure that these count towards the limit when combined non-external texture +// bindings. +TEST_F(BindGroupLayoutValidationTest, PerStageLimitsWithExternalTexture) { + struct TestInfo { + uint32_t maxCount; + uint32_t bindingsPerExternalTexture; + wgpu::BindGroupLayoutEntry entry; + wgpu::BindGroupLayoutEntry otherEntry; + }; + + std::array<TestInfo, 3> kTestInfos = { + TestInfo{kMaxSampledTexturesPerShaderStage, kSampledTexturesPerExternalTexture, + BGLEntryType(wgpu::TextureSampleType::Float), + BGLEntryType(wgpu::BufferBindingType::Uniform)}, + TestInfo{kMaxSamplersPerShaderStage, kSamplersPerExternalTexture, + BGLEntryType(wgpu::SamplerBindingType::Filtering), + BGLEntryType(wgpu::BufferBindingType::Uniform)}, + TestInfo{kMaxUniformBuffersPerShaderStage, kUniformsPerExternalTexture, + BGLEntryType(wgpu::BufferBindingType::Uniform), + BGLEntryType(wgpu::TextureSampleType::Float)}, + }; + + for (TestInfo info : kTestInfos) { + wgpu::BindGroupLayout bgl[2]; + std::vector<utils::BindingLayoutEntryInitializationHelper> maxBindings; + + // Create an external texture binding layout entry + wgpu::BindGroupLayoutEntry entry = BGLEntryType(&utils::kExternalTextureBindingLayout); + entry.binding = 0; + maxBindings.push_back(entry); + + // Create the other bindings such that we reach the max bindings per stage when including + // the external texture. + for (uint32_t i = 1; i <= info.maxCount - info.bindingsPerExternalTexture; ++i) { + wgpu::BindGroupLayoutEntry entry = info.entry; + entry.binding = i; + maxBindings.push_back(entry); + } + + // Ensure that creation without the external texture works. + bgl[0] = MakeBindGroupLayout(maxBindings.data(), maxBindings.size()); + + // Adding an extra binding of a different type works. + { + std::vector<utils::BindingLayoutEntryInitializationHelper> bindings = maxBindings; + wgpu::BindGroupLayoutEntry entry = info.otherEntry; + entry.binding = info.maxCount; + bindings.push_back(entry); + MakeBindGroupLayout(bindings.data(), bindings.size()); + } + + // Adding an extra binding of the maxed type in a different stage works + { + std::vector<utils::BindingLayoutEntryInitializationHelper> bindings = maxBindings; + wgpu::BindGroupLayoutEntry entry = info.entry; + entry.binding = info.maxCount; + entry.visibility = wgpu::ShaderStage::Fragment; + bindings.push_back(entry); + MakeBindGroupLayout(bindings.data(), bindings.size()); + } + + // Adding an extra binding of the maxed type and stage exceeds the per stage limit. + { + std::vector<utils::BindingLayoutEntryInitializationHelper> bindings = maxBindings; + wgpu::BindGroupLayoutEntry entry = info.entry; + entry.binding = info.maxCount; + bindings.push_back(entry); + ASSERT_DEVICE_ERROR(MakeBindGroupLayout(bindings.data(), bindings.size())); + } + + // Creating a pipeline layout from the valid BGL works. + TestCreatePipelineLayout(bgl, 1, true); + + // Adding an extra binding of a different type in a different BGL works + bgl[1] = utils::MakeBindGroupLayout(device, {info.otherEntry}); + TestCreatePipelineLayout(bgl, 2, true); + + { + // Adding an extra binding of the maxed type in a different stage works + wgpu::BindGroupLayoutEntry entry = info.entry; + entry.visibility = wgpu::ShaderStage::Fragment; + bgl[1] = utils::MakeBindGroupLayout(device, {entry}); + TestCreatePipelineLayout(bgl, 2, true); + } + + // Adding an extra binding of the maxed type in a different BGL exceeds the per stage limit. + bgl[1] = utils::MakeBindGroupLayout(device, {info.entry}); + TestCreatePipelineLayout(bgl, 2, false); + } +} + // Check that dynamic buffer numbers exceed maximum value in one bind group layout. TEST_F(BindGroupLayoutValidationTest, DynamicBufferNumberLimit) { wgpu::BindGroupLayout bgl[2]; @@ -1847,6 +2055,28 @@ wgpu::TextureViewDimension::e2D}})})); } +// TODO(dawn:728) Enable this test when Dawn no longer relies on SPIRV-Cross to extract shader info. +TEST_F(BindGroupLayoutCompatibilityTest, DISABLED_ExternalTextureBindGroupLayoutCompatibility) { + wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}}); + + // Test that an external texture binding works with a texture_external in the shader. + CreateFSRenderPipeline(R"( + [[group(0), binding(0)]] var myExternalTexture: texture_external; + [[stage(fragment)]] fn main() { + textureDimensions(myExternalTexture); + })", + {bgl}); + + // Test that an external texture binding doesn't work with a texture_2d<f32> in the shader. + ASSERT_DEVICE_ERROR(CreateFSRenderPipeline(R"( + [[group(0), binding(0)]] var myTexture: texture_2d<f32>; + [[stage(fragment)]] fn main() { + textureDimensions(myTexture); + })", + {bgl})); +} + class BindingsValidationTest : public BindGroupLayoutCompatibilityTest { public: void TestRenderPassBindings(const wgpu::BindGroup* bg,
diff --git a/src/tests/unittests/validation/ExternalTextureTests.cpp b/src/tests/unittests/validation/ExternalTextureTests.cpp index 1095312..57ec556 100644 --- a/src/tests/unittests/validation/ExternalTextureTests.cpp +++ b/src/tests/unittests/validation/ExternalTextureTests.cpp
@@ -14,6 +14,9 @@ #include "tests/unittests/validation/ValidationTest.h" +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/WGPUHelpers.h" + namespace { class ExternalTextureTest : public ValidationTest { public: @@ -26,11 +29,41 @@ descriptor.sampleCount = kDefaultSampleCount; descriptor.dimension = wgpu::TextureDimension::e2D; descriptor.format = kDefaultTextureFormat; - descriptor.usage = wgpu::TextureUsage::Sampled; + descriptor.usage = wgpu::TextureUsage::Sampled | wgpu::TextureUsage::RenderAttachment; return descriptor; } protected: + void SetUp() override { + ValidationTest::SetUp(); + + queue = device.GetQueue(); + } + + wgpu::RenderPipeline CreateBasicRenderPipeline(wgpu::ExternalTexture externalTexture) { + wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}}); + + bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}}); + + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { + return vec4<f32>(); + })"); + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + [[group(0), binding(0)]] var myExternalTexture: texture_external; + [[stage(fragment)]] fn main() { + textureDimensions(myExternalTexture); + })"); + + utils::ComboRenderPipelineDescriptor pipelineDescriptor; + pipelineDescriptor.vertex.module = vsModule; + pipelineDescriptor.cFragment.module = fsModule; + wgpu::PipelineLayout pipelineLayout = utils::MakeBasicPipelineLayout(device, &bgl); + pipelineDescriptor.layout = pipelineLayout; + return device.CreateRenderPipeline(&pipelineDescriptor); + } + static constexpr uint32_t kWidth = 32; static constexpr uint32_t kHeight = 32; static constexpr uint32_t kDefaultDepth = 1; @@ -39,6 +72,10 @@ static constexpr wgpu::TextureFormat kDefaultTextureFormat = wgpu::TextureFormat::RGBA8Unorm; + + wgpu::Queue queue; + wgpu::RenderPipeline renderPipeline; + wgpu::BindGroup bindGroup; }; TEST_F(ExternalTextureTest, CreateExternalTextureValidation) { @@ -112,4 +149,110 @@ } } + // Test that submitting a command encoder that contains a destroyed external texture results in + // an error. + TEST_F(ExternalTextureTest, SubmitDestroyedExternalTexture) { + wgpu::TextureDescriptor textureDescriptor = CreateDefaultTextureDescriptor(); + wgpu::Texture texture = device.CreateTexture(&textureDescriptor); + + wgpu::ExternalTextureDescriptor externalDesc; + externalDesc.format = kDefaultTextureFormat; + externalDesc.plane0 = texture.CreateView(); + wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); + + wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(externalTexture); + + // Create another texture to use as a color attachment. + wgpu::TextureDescriptor renderTextureDescriptor = CreateDefaultTextureDescriptor(); + wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor); + wgpu::TextureView renderView = renderTexture.CreateView(); + + utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); + + // Control case should succeed. + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + { + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(1); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + + queue.Submit(1, &commands); + } + + // Destroying the external texture should result in an error. + { + externalTexture.Destroy(); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + { + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(1); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); + } + } + + // Test that submitting a command encoder that contains a destroyed external texture plane + // results in an error. + TEST_F(ExternalTextureTest, SubmitDestroyedExternalTexturePlane) { + wgpu::TextureDescriptor textureDescriptor = CreateDefaultTextureDescriptor(); + wgpu::Texture texture = device.CreateTexture(&textureDescriptor); + + wgpu::ExternalTextureDescriptor externalDesc; + externalDesc.format = kDefaultTextureFormat; + externalDesc.plane0 = texture.CreateView(); + wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); + + wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(externalTexture); + + // Create another texture to use as a color attachment. + wgpu::TextureDescriptor renderTextureDescriptor = CreateDefaultTextureDescriptor(); + wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor); + wgpu::TextureView renderView = renderTexture.CreateView(); + + utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); + + // Control case should succeed. + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + { + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(1); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + + queue.Submit(1, &commands); + } + + // Destroying an external texture underlying plane should result in an error. + { + texture.Destroy(); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + { + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.Draw(1); + pass.EndPass(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); + } + } + } // namespace \ No newline at end of file
diff --git a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp index f673148..de024b0 100644 --- a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp +++ b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -274,6 +274,33 @@ } } +// Test that an external texture binding type matches a shader using texture_external. +// TODO(dawn:728) Enable this test once Dawn no longer relies on SPIRV-Cross to extract shader info. +// Consider combining with the similar test above. +TEST_F(GetBindGroupLayoutTests, DISABLED_ExternalTextureBindingType) { + // This test works assuming Dawn Native's object deduplication. + // Getting the same pointer to equivalent bind group layouts is an implementation detail of Dawn + // Native. + DAWN_SKIP_TEST_IF(UsesWire()); + + wgpu::BindGroupLayoutEntry binding = {}; + binding.binding = 0; + binding.visibility = wgpu::ShaderStage::Fragment; + + wgpu::BindGroupLayoutDescriptor desc = {}; + desc.entryCount = 1; + desc.entries = &binding; + + binding.nextInChain = &utils::kExternalTextureBindingLayout; + wgpu::RenderPipeline pipeline = RenderPipelineFromFragmentShader(R"( + [[group(0), binding(0)]] var myExternalTexture: texture_external; + + [[stage(fragment)]] fn main() { + textureDimensions(myExternalTexture); + })"); + EXPECT_EQ(device.CreateBindGroupLayout(&desc).Get(), pipeline.GetBindGroupLayout(0).Get()); +} + // Test that texture view dimension matches the shader. TEST_F(GetBindGroupLayoutTests, ViewDimension) { // This test works assuming Dawn Native's object deduplication.
diff --git a/src/tests/unittests/wire/WireOptionalTests.cpp b/src/tests/unittests/wire/WireOptionalTests.cpp index 012f01d..c52c5ac 100644 --- a/src/tests/unittests/wire/WireOptionalTests.cpp +++ b/src/tests/unittests/wire/WireOptionalTests.cpp
@@ -40,6 +40,7 @@ entry.sampler = nullptr; entry.textureView = nullptr; entry.buffer = nullptr; + entry.nextInChain = nullptr; WGPUBindGroupDescriptor bgDesc = {}; bgDesc.layout = bgl;
diff --git a/src/utils/WGPUHelpers.cpp b/src/utils/WGPUHelpers.cpp index 7f29e62..61277e2 100644 --- a/src/utils/WGPUHelpers.cpp +++ b/src/utils/WGPUHelpers.cpp
@@ -26,7 +26,6 @@ #include <sstream> namespace utils { - wgpu::ShaderModule CreateShaderModuleFromASM(const wgpu::Device& device, const char* source) { // Use SPIRV-Tools's C API to assemble the SPIR-V assembly text to binary. Because the types // aren't RAII, we don't return directly on success and instead always go through the code @@ -296,6 +295,19 @@ storageTexture.viewDimension = textureViewDimension; } + // ExternalTextureBindingLayout never contains data, so just make one that can be reused instead + // of declaring a new one every time it's needed. + wgpu::ExternalTextureBindingLayout kExternalTextureBindingLayout = {}; + + BindingLayoutEntryInitializationHelper::BindingLayoutEntryInitializationHelper( + uint32_t entryBinding, + wgpu::ShaderStage entryVisibility, + wgpu::ExternalTextureBindingLayout* bindingLayout) { + binding = entryBinding; + visibility = entryVisibility; + nextInChain = bindingLayout; + } + BindingLayoutEntryInitializationHelper::BindingLayoutEntryInitializationHelper( const wgpu::BindGroupLayoutEntry& entry) : wgpu::BindGroupLayoutEntry(entry) { @@ -311,6 +323,13 @@ : binding(binding), textureView(textureView) { } + BindingInitializationHelper::BindingInitializationHelper( + uint32_t binding, + const wgpu::ExternalTexture& externalTexture) + : binding(binding) { + externalTextureBindingEntry.externalTexture = externalTexture; + } + BindingInitializationHelper::BindingInitializationHelper(uint32_t binding, const wgpu::Buffer& buffer, uint64_t offset, @@ -327,6 +346,9 @@ result.buffer = buffer; result.offset = offset; result.size = size; + if (externalTextureBindingEntry.externalTexture != nullptr) { + result.nextInChain = &externalTextureBindingEntry; + } return result; }
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h index 1ae082e..09070ca 100644 --- a/src/utils/WGPUHelpers.h +++ b/src/utils/WGPUHelpers.h
@@ -97,6 +97,8 @@ wgpu::PipelineLayout MakePipelineLayout(const wgpu::Device& device, std::vector<wgpu::BindGroupLayout> bgls); + extern wgpu::ExternalTextureBindingLayout kExternalTextureBindingLayout; + // Helpers to make creating bind group layouts look nicer: // // utils::MakeBindGroupLayout(device, { @@ -126,6 +128,9 @@ wgpu::StorageTextureAccess storageTextureAccess, wgpu::TextureFormat format, wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D); + BindingLayoutEntryInitializationHelper(uint32_t entryBinding, + wgpu::ShaderStage entryVisibility, + wgpu::ExternalTextureBindingLayout* bindingLayout); BindingLayoutEntryInitializationHelper(const wgpu::BindGroupLayoutEntry& entry); }; @@ -147,6 +152,7 @@ struct BindingInitializationHelper { BindingInitializationHelper(uint32_t binding, const wgpu::Sampler& sampler); BindingInitializationHelper(uint32_t binding, const wgpu::TextureView& textureView); + BindingInitializationHelper(uint32_t binding, const wgpu::ExternalTexture& externalTexture); BindingInitializationHelper(uint32_t binding, const wgpu::Buffer& buffer, uint64_t offset = 0, @@ -158,6 +164,7 @@ wgpu::Sampler sampler; wgpu::TextureView textureView; wgpu::Buffer buffer; + wgpu::ExternalTextureBindingEntry externalTextureBindingEntry; uint64_t offset = 0; uint64_t size = 0; };