diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn index b98708c69d..a0b7778b40 100644 --- a/src/dawn_native/BUILD.gn +++ b/src/dawn_native/BUILD.gn @@ -234,6 +234,7 @@ source_set("dawn_native_sources") { "ObjectBase.h", "ObjectContentHasher.cpp", "ObjectContentHasher.h", + "PassResourceUsage.cpp", "PassResourceUsage.h", "PassResourceUsageTracker.cpp", "PassResourceUsageTracker.h", diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt index d65ead6b37..9a80bf9215 100644 --- a/src/dawn_native/CMakeLists.txt +++ b/src/dawn_native/CMakeLists.txt @@ -107,6 +107,7 @@ target_sources(dawn_native PRIVATE "IntegerTypes.h" "ObjectBase.cpp" "ObjectBase.h" + "PassResourceUsage.cpp" "PassResourceUsage.h" "PassResourceUsageTracker.cpp" "PassResourceUsageTracker.h" diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp index 9d4124cf5b..76dadff9d0 100644 --- a/src/dawn_native/CommandValidation.cpp +++ b/src/dawn_native/CommandValidation.cpp @@ -308,6 +308,7 @@ namespace dawn_native { // Performs the per-pass usage validation checks // This will eventually need to differentiate between render and compute passes. // It will be valid to use a buffer both as uniform and storage in the same compute pass. + // TODO(yunchao.he@intel.com): add read/write usage tracking for compute MaybeError ValidatePassResourceUsage(const PassResourceUsage& pass) { // Buffers can only be used as single-write or multiple read. for (size_t i = 0; i < pass.buffers.size(); ++i) { @@ -337,8 +338,6 @@ namespace dawn_native { return DAWN_VALIDATION_ERROR("Texture missing usage for the pass"); } - // TODO (yunchao.he@intel.com): add read/write usage tracking for compute - // The usage variable for the whole texture is a fast path for texture usage tracking. // Because in most cases a texture (with or without subresources) is used as // single-write or multiple read, then we can skip iterating the subresources' usages. @@ -347,16 +346,20 @@ namespace dawn_native { if (pass.passType != PassType::Render || readOnly || singleUse) { continue; } - // Inspect the subresources if the usage of the whole texture violates usage validation. - // Every single subresource can only be used as single-write or multiple read. - for (wgpu::TextureUsage subresourceUsage : textureUsage.subresourceUsages) { - bool readOnly = IsSubset(subresourceUsage, kReadOnlyTextureUsages); - bool singleUse = wgpu::HasZeroOrOneBits(subresourceUsage); - if (!readOnly && !singleUse) { - return DAWN_VALIDATION_ERROR( - "Texture used as writable usage and another usage in render pass"); - } - } + + // Check that every single subresource is used as either a single-write usage or a + // combination of readonly usages. + MaybeError error = {}; + textureUsage.subresourceUsages.Iterate( + [&](const SubresourceRange&, const wgpu::TextureUsage& usage) { + bool readOnly = IsSubset(usage, kReadOnlyTextureUsages); + bool singleUse = wgpu::HasZeroOrOneBits(usage); + if (!readOnly && !singleUse && !error.IsError()) { + error = DAWN_VALIDATION_ERROR( + "Texture used as writable usage and another usage in render pass"); + } + }); + DAWN_TRY(std::move(error)); } return {}; } diff --git a/src/dawn_native/PassResourceUsage.cpp b/src/dawn_native/PassResourceUsage.cpp new file mode 100644 index 0000000000..e56833afeb --- /dev/null +++ b/src/dawn_native/PassResourceUsage.cpp @@ -0,0 +1,29 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn_native/PassResourceUsage.h" + +#include "dawn_native/Format.h" +#include "dawn_native/Texture.h" + +namespace dawn_native { + + PassTextureUsage::PassTextureUsage(const TextureBase* texture) + : subresourceUsages(texture->GetFormat().aspects, + texture->GetArrayLayers(), + texture->GetNumMipLevels(), + wgpu::TextureUsage::None) { + } + +} // namespace dawn_native diff --git a/src/dawn_native/PassResourceUsage.h b/src/dawn_native/PassResourceUsage.h index 60dcd219a6..9132f652f2 100644 --- a/src/dawn_native/PassResourceUsage.h +++ b/src/dawn_native/PassResourceUsage.h @@ -15,6 +15,7 @@ #ifndef DAWNNATIVE_PASSRESOURCEUSAGE_H #define DAWNNATIVE_PASSRESOURCEUSAGE_H +#include "dawn_native/SubresourceStorage.h" #include "dawn_native/dawn_platform.h" #include @@ -29,26 +30,18 @@ namespace dawn_native { enum class PassType { Render, Compute }; // Describe the usage of the whole texture and its subresources. - // - subresourceUsages vector is used to track every subresource's usage within a texture. // // - usage variable is used the track the whole texture even though it can be deduced from // subresources' usages. This is designed deliberately to track texture usage in a fast path // at frontend. // - // - sameUsagesAcrossSubresources is used for optimization at backend. If the texture view - // we are using covers all subresources, then the texture's usages of all subresources are - // the same. Otherwise the texture's usages of all subresources are thought as different, - // although we can deliberately design some particular cases in which we have a few texture - // views and all of them have the same usages and they cover all subresources of the texture - // altogether. - - // TODO(yunchao.he@intel.com): if sameUsagesAcrossSubresources is true, we don't need - // the vector to record every single subresource's Usages. The texture usage is enough. And we - // can decompress texture usage to a vector if necessary. + // - subresourceUsages is used to track every subresource's usage within a texture. struct PassTextureUsage { + // Constructor used to size subresourceUsages correctly. + PassTextureUsage(const TextureBase* texture); + wgpu::TextureUsage usage = wgpu::TextureUsage::None; - bool sameUsagesAcrossSubresources = true; - std::vector subresourceUsages; + SubresourceStorage subresourceUsages; }; // Which resources are used by pass and how they are used. The command buffer validation diff --git a/src/dawn_native/PassResourceUsageTracker.cpp b/src/dawn_native/PassResourceUsageTracker.cpp index 3f5a9159dd..4a53ae7392 100644 --- a/src/dawn_native/PassResourceUsageTracker.cpp +++ b/src/dawn_native/PassResourceUsageTracker.cpp @@ -34,51 +34,31 @@ namespace dawn_native { TextureBase* texture = view->GetTexture(); const SubresourceRange& range = view->GetSubresourceRange(); - // std::map's operator[] will create the key and return a PassTextureUsage with usage = 0 - // and an empty vector for subresourceUsages. - // TODO (yunchao.he@intel.com): optimize this - PassTextureUsage& textureUsage = mTextureUsages[texture]; + // Get or create a new PassTextureUsage for that texture (initially filled with + // wgpu::TextureUsage::None) + auto it = mTextureUsages.emplace(texture, texture); + PassTextureUsage& textureUsage = it.first->second; - // Set parameters for the whole texture textureUsage.usage |= usage; - textureUsage.sameUsagesAcrossSubresources &= - (range.levelCount == texture->GetNumMipLevels() && // - range.layerCount == texture->GetArrayLayers() && // - range.aspects == texture->GetFormat().aspects); - - // Set usages for subresources - if (!textureUsage.subresourceUsages.size()) { - textureUsage.subresourceUsages = std::vector( - texture->GetSubresourceCount(), wgpu::TextureUsage::None); - } - for (Aspect aspect : IterateEnumMask(range.aspects)) { - for (uint32_t arrayLayer = range.baseArrayLayer; - arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) { - for (uint32_t mipLevel = range.baseMipLevel; - mipLevel < range.baseMipLevel + range.levelCount; ++mipLevel) { - uint32_t subresourceIndex = - texture->GetSubresourceIndex(mipLevel, arrayLayer, aspect); - textureUsage.subresourceUsages[subresourceIndex] |= usage; - } - } - } + textureUsage.subresourceUsages.Update( + range, [usage](const SubresourceRange&, wgpu::TextureUsage* storedUsage) { + *storedUsage |= usage; + }); } void PassResourceUsageTracker::AddTextureUsage(TextureBase* texture, const PassTextureUsage& textureUsage) { - PassTextureUsage& passTextureUsage = mTextureUsages[texture]; - passTextureUsage.usage |= textureUsage.usage; - passTextureUsage.sameUsagesAcrossSubresources &= textureUsage.sameUsagesAcrossSubresources; + // Get or create a new PassTextureUsage for that texture (initially filled with + // wgpu::TextureUsage::None) + auto it = mTextureUsages.emplace(texture, texture); + PassTextureUsage* passTextureUsage = &it.first->second; - uint32_t subresourceCount = texture->GetSubresourceCount(); - ASSERT(textureUsage.subresourceUsages.size() == subresourceCount); - if (!passTextureUsage.subresourceUsages.size()) { - passTextureUsage.subresourceUsages = textureUsage.subresourceUsages; - return; - } - for (uint32_t i = 0; i < subresourceCount; ++i) { - passTextureUsage.subresourceUsages[i] |= textureUsage.subresourceUsages[i]; - } + passTextureUsage->usage |= textureUsage.usage; + + passTextureUsage->subresourceUsages.Merge( + textureUsage.subresourceUsages, + [](const SubresourceRange&, wgpu::TextureUsage* storedUsage, + const wgpu::TextureUsage& addedUsage) { *storedUsage |= addedUsage; }); } // Returns the per-pass usage for use by backends for APIs with explicit barriers. diff --git a/src/dawn_native/Subresource.cpp b/src/dawn_native/Subresource.cpp index 4e613a64c7..ad9e0eedf7 100644 --- a/src/dawn_native/Subresource.cpp +++ b/src/dawn_native/Subresource.cpp @@ -46,8 +46,8 @@ namespace dawn_native { ASSERT(HasOneBit(aspect)); switch (aspect) { case Aspect::Color: - return 0; case Aspect::Depth: + case Aspect::CombinedDepthStencil: return 0; case Aspect::Stencil: return 1; @@ -60,7 +60,8 @@ namespace dawn_native { // TODO(cwallez@chromium.org): This should use popcount once Dawn has such a function. // Note that we can't do a switch because compilers complain that Depth | Stencil is not // a valid enum value. - if (aspects == Aspect::Color || aspects == Aspect::Depth) { + if (aspects == Aspect::Color || aspects == Aspect::Depth || + aspects == Aspect::CombinedDepthStencil) { return 1; } else { ASSERT(aspects == (Aspect::Depth | Aspect::Stencil)); diff --git a/src/dawn_native/Subresource.h b/src/dawn_native/Subresource.h index 3090e7b3c7..1cf439ce4c 100644 --- a/src/dawn_native/Subresource.h +++ b/src/dawn_native/Subresource.h @@ -28,11 +28,15 @@ namespace dawn_native { Color = 0x1, Depth = 0x2, Stencil = 0x4, + + // An aspect for that represents the combination of both the depth and stencil aspects. It + // can be ignored outside of the Vulkan backend. + CombinedDepthStencil = 0x8, }; template <> struct EnumBitmaskSize { - static constexpr unsigned value = 3; + static constexpr unsigned value = 4; }; // Convert the TextureAspect to an Aspect mask for the format. ASSERTs if the aspect diff --git a/src/dawn_native/SubresourceStorage.h b/src/dawn_native/SubresourceStorage.h index fb444d74b6..2a7e91a6ab 100644 --- a/src/dawn_native/SubresourceStorage.h +++ b/src/dawn_native/SubresourceStorage.h @@ -472,7 +472,7 @@ namespace dawn_native { T layer0Data = Data(aspectIndex, 0); for (uint32_t layer = 1; layer < mArrayLayerCount; layer++) { - if (Data(aspectIndex, layer) != layer0Data) { + if (!(Data(aspectIndex, layer) == layer0Data)) { return; } } @@ -502,7 +502,7 @@ namespace dawn_native { const T& level0Data = Data(aspectIndex, layer, 0); for (uint32_t level = 1; level < mMipLevelCount; level++) { - if (Data(aspectIndex, layer, level) != level0Data) { + if (!(Data(aspectIndex, layer, level) == level0Data)) { return; } } diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp index bf4c87dbd8..c4b61ea019 100644 --- a/src/dawn_native/d3d12/TextureD3D12.cpp +++ b/src/dawn_native/d3d12/TextureD3D12.cpp @@ -513,7 +513,9 @@ namespace dawn_native { namespace d3d12 { Texture::Texture(Device* device, const TextureDescriptor* descriptor, TextureState state) : TextureBase(device, descriptor, state), mSubresourceStateAndDecay( - GetSubresourceCount(), + GetFormat().aspects, + GetArrayLayers(), + GetNumMipLevels(), {D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON, kMaxExecutionSerial, false}) { } @@ -615,12 +617,11 @@ namespace dawn_native { namespace d3d12 { } } - void Texture::TransitionSingleOrAllSubresources(std::vector* barriers, - uint32_t index, - D3D12_RESOURCE_STATES newState, - ExecutionSerial pendingCommandSerial, - bool allSubresources) { - StateAndDecay* state = &mSubresourceStateAndDecay[index]; + void Texture::TransitionSubresourceRange(std::vector* barriers, + const SubresourceRange& range, + StateAndDecay* state, + D3D12_RESOURCE_STATES newState, + ExecutionSerial pendingCommandSerial) const { // Reuse the subresource(s) directly and avoid transition when it isn't needed, and // return false. // TODO(cwallez@chromium.org): Need some form of UAV barriers at some point. @@ -682,9 +683,28 @@ namespace dawn_native { namespace d3d12 { barrier.Transition.pResource = GetD3D12Resource(); barrier.Transition.StateBefore = lastState; barrier.Transition.StateAfter = newState; - barrier.Transition.Subresource = - allSubresources ? D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES : index; - barriers->push_back(barrier); + + bool isFullRange = range.baseArrayLayer == 0 && range.baseMipLevel == 0 && + range.layerCount == GetArrayLayers() && + range.levelCount == GetNumMipLevels() && + range.aspects == GetFormat().aspects; + + // Use a single transition for all subresources if possible. + if (isFullRange) { + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers->push_back(barrier); + } else { + for (Aspect aspect : IterateEnumMask(range.aspects)) { + for (uint32_t arrayLayer = 0; arrayLayer < range.layerCount; ++arrayLayer) { + for (uint32_t mipLevel = 0; mipLevel < range.levelCount; ++mipLevel) { + barrier.Transition.Subresource = + GetSubresourceIndex(range.baseMipLevel + mipLevel, + range.baseArrayLayer + arrayLayer, aspect); + barriers->push_back(barrier); + } + } + } + } state->isValidToDecay = false; } @@ -719,36 +739,11 @@ namespace dawn_native { namespace d3d12 { // This transitions assume it is a 2D texture ASSERT(GetDimension() == wgpu::TextureDimension::e2D); - // If the usages transitions can cover all subresources, and old usages of all subresources - // are the same, then we can use one barrier to do state transition for all subresources. - // Note that if the texture has only one mip level and one array slice, it will fall into - // this category. - bool areAllSubresourcesCovered = (range.levelCount == GetNumMipLevels() && // - range.layerCount == GetArrayLayers() && // - range.aspects == GetFormat().aspects); - if (mSameLastUsagesAcrossSubresources && areAllSubresourcesCovered) { - TransitionSingleOrAllSubresources(barriers, 0, newState, pendingCommandSerial, true); - - // TODO(yunchao.he@intel.com): compress and decompress if all subresources have the - // same states. We may need to retain mSubresourceStateAndDecay[0] only. - for (uint32_t i = 1; i < GetSubresourceCount(); ++i) { - mSubresourceStateAndDecay[i] = mSubresourceStateAndDecay[0]; - } - - return; - } - for (Aspect aspect : IterateEnumMask(range.aspects)) { - for (uint32_t arrayLayer = 0; arrayLayer < range.layerCount; ++arrayLayer) { - for (uint32_t mipLevel = 0; mipLevel < range.levelCount; ++mipLevel) { - uint32_t index = GetSubresourceIndex(range.baseMipLevel + mipLevel, - range.baseArrayLayer + arrayLayer, aspect); - - TransitionSingleOrAllSubresources(barriers, index, newState, - pendingCommandSerial, false); - } - } - } - mSameLastUsagesAcrossSubresources = areAllSubresourcesCovered; + mSubresourceStateAndDecay.Update( + range, [&](const SubresourceRange& updateRange, StateAndDecay* state) { + TransitionSubresourceRange(barriers, updateRange, state, newState, + pendingCommandSerial); + }); } void Texture::TrackUsageAndGetResourceBarrierForPass( @@ -765,47 +760,21 @@ namespace dawn_native { namespace d3d12 { const ExecutionSerial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial(); - uint32_t subresourceCount = GetSubresourceCount(); - ASSERT(textureUsages.subresourceUsages.size() == subresourceCount); // This transitions assume it is a 2D texture ASSERT(GetDimension() == wgpu::TextureDimension::e2D); - // If new usages of all subresources are the same and old usages of all subresources are - // the same too, we can use one barrier to do state transition for all subresources. - // Note that if the texture has only one mip level and one array slice, it will fall into - // this category. - if (textureUsages.sameUsagesAcrossSubresources && mSameLastUsagesAcrossSubresources) { - D3D12_RESOURCE_STATES newState = D3D12TextureUsage(textureUsages.usage, GetFormat()); - TransitionSingleOrAllSubresources(barriers, 0, newState, pendingCommandSerial, true); - - // TODO(yunchao.he@intel.com): compress and decompress if all subresources have the - // same states. We may need to retain mSubresourceStateAndDecay[0] only. - for (uint32_t i = 1; i < subresourceCount; ++i) { - mSubresourceStateAndDecay[i] = mSubresourceStateAndDecay[0]; - } - - return; - } - - for (Aspect aspect : IterateEnumMask(GetFormat().aspects)) { - for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) { - for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) { - uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer, aspect); - - // Skip if this subresource is not used during the current pass - if (textureUsages.subresourceUsages[index] == wgpu::TextureUsage::None) { - continue; - } - - D3D12_RESOURCE_STATES newState = - D3D12TextureUsage(textureUsages.subresourceUsages[index], GetFormat()); - - TransitionSingleOrAllSubresources(barriers, index, newState, - pendingCommandSerial, false); + mSubresourceStateAndDecay.Merge( + textureUsages.subresourceUsages, [&](const SubresourceRange& mergeRange, + StateAndDecay* state, wgpu::TextureUsage usage) { + // Skip if this subresource is not used during the current pass + if (usage == wgpu::TextureUsage::None) { + return; } - } - } - mSameLastUsagesAcrossSubresources = textureUsages.sameUsagesAcrossSubresources; + + D3D12_RESOURCE_STATES newState = D3D12TextureUsage(usage, GetFormat()); + TransitionSubresourceRange(barriers, mergeRange, state, newState, + pendingCommandSerial); + }); } D3D12_RENDER_TARGET_VIEW_DESC Texture::GetRTVDescriptor(uint32_t mipLevel, @@ -1025,6 +994,11 @@ namespace dawn_native { namespace d3d12 { } } + bool Texture::StateAndDecay::operator==(const Texture::StateAndDecay& other) const { + return lastState == other.lastState && lastDecaySerial == other.lastDecaySerial && + isValidToDecay == other.isValidToDecay; + } + TextureView::TextureView(TextureBase* texture, const TextureViewDescriptor* descriptor) : TextureViewBase(texture, descriptor) { mSrvDesc.Format = D3D12TextureFormat(descriptor->format); diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h index 79539eb0a8..2e2e089e64 100644 --- a/src/dawn_native/d3d12/TextureD3D12.h +++ b/src/dawn_native/d3d12/TextureD3D12.h @@ -96,26 +96,26 @@ namespace dawn_native { namespace d3d12 { const SubresourceRange& range, TextureBase::ClearValue clearValue); - void TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, - std::vector* barrier, - D3D12_RESOURCE_STATES newState, - const SubresourceRange& range); - - void TransitionSingleOrAllSubresources(std::vector* barriers, - uint32_t index, - D3D12_RESOURCE_STATES subresourceNewState, - ExecutionSerial pendingCommandSerial, - bool allSubresources); - void HandleTransitionSpecialCases(CommandRecordingContext* commandContext); - - bool mSameLastUsagesAcrossSubresources = true; - + // Barriers implementation details. struct StateAndDecay { D3D12_RESOURCE_STATES lastState; ExecutionSerial lastDecaySerial; bool isValidToDecay; + + bool operator==(const StateAndDecay& other) const; }; - std::vector mSubresourceStateAndDecay; + void TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, + std::vector* barrier, + D3D12_RESOURCE_STATES newState, + const SubresourceRange& range); + void TransitionSubresourceRange(std::vector* barriers, + const SubresourceRange& range, + StateAndDecay* state, + D3D12_RESOURCE_STATES subresourceNewState, + ExecutionSerial pendingCommandSerial) const; + void HandleTransitionSpecialCases(CommandRecordingContext* commandContext); + + SubresourceStorage mSubresourceStateAndDecay; ResourceHeapAllocation mResourceAllocation; bool mSwapChainTexture = false; diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 942a2ab332..239b10813d 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -306,6 +306,7 @@ namespace dawn_native { namespace opengl { switch (aspect) { case Aspect::None: case Aspect::Color: + case Aspect::CombinedDepthStencil: UNREACHABLE(); case Aspect::Depth: gl.TexParameteri(target, GL_DEPTH_STENCIL_TEXTURE_MODE, @@ -714,6 +715,7 @@ namespace dawn_native { namespace opengl { glType = GL_UNSIGNED_BYTE; break; + case Aspect::CombinedDepthStencil: case Aspect::None: UNREACHABLE(); } diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp index d71437afb3..218e60b8a6 100644 --- a/src/dawn_native/vulkan/TextureVk.cpp +++ b/src/dawn_native/vulkan/TextureVk.cpp @@ -218,7 +218,7 @@ namespace dawn_native { namespace vulkan { barrier.oldLayout = VulkanImageLayout(lastUsage, format); barrier.newLayout = VulkanImageLayout(usage, format); barrier.image = image; - barrier.subresourceRange.aspectMask = VulkanAspectMask(format.aspects); + barrier.subresourceRange.aspectMask = VulkanAspectMask(range.aspects); barrier.subresourceRange.baseMipLevel = range.baseMipLevel; barrier.subresourceRange.levelCount = range.levelCount; barrier.subresourceRange.baseArrayLayer = range.baseArrayLayer; @@ -489,6 +489,16 @@ namespace dawn_native { namespace vulkan { return texture; } + Texture::Texture(Device* device, const TextureDescriptor* descriptor, TextureState state) + : TextureBase(device, descriptor, state), + // A usage of none will make sure the texture is transitioned before its first use as + // required by the Vulkan spec. + mSubresourceLastUsages(ComputeAspectsForSubresourceStorage(), + GetArrayLayers(), + GetNumMipLevels(), + wgpu::TextureUsage::None) { + } + MaybeError Texture::InitializeAsInternalTexture(VkImageUsageFlags extraUsages) { Device* device = ToBackend(GetDevice()); @@ -619,12 +629,12 @@ namespace dawn_native { namespace vulkan { } ASSERT(mSignalSemaphore != VK_NULL_HANDLE); - ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1); // Release the texture mExternalState = ExternalState::Released; - wgpu::TextureUsage usage = mSubresourceLastUsages[0]; + ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1); + wgpu::TextureUsage usage = mSubresourceLastUsages.Get(Aspect::Color, 0, 0); VkImageMemoryBarrier barrier; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -812,9 +822,15 @@ namespace dawn_native { namespace vulkan { return false; } - void Texture::TransitionFullUsage(CommandRecordingContext* recordingContext, - wgpu::TextureUsage usage) { - TransitionUsageNow(recordingContext, usage, GetAllSubresources()); + bool Texture::ShouldCombineDepthStencilBarriers() const { + return GetFormat().aspects == (Aspect::Depth | Aspect::Stencil); + } + + Aspect Texture::ComputeAspectsForSubresourceStorage() const { + if (ShouldCombineDepthStencilBarriers()) { + return Aspect::CombinedDepthStencil; + } + return GetFormat().aspects; } void Texture::TransitionUsageForPass(CommandRecordingContext* recordingContext, @@ -822,73 +838,63 @@ namespace dawn_native { namespace vulkan { std::vector* imageBarriers, VkPipelineStageFlags* srcStages, VkPipelineStageFlags* dstStages) { + // Base Vulkan doesn't support transitioning depth and stencil separately. We work around + // this limitation by combining the usages in the two planes of `textureUsages` into a + // single plane in a new SubresourceStorage. The barriers will be produced + // for DEPTH | STENCIL since the SubresourceRange uses Aspect::CombinedDepthStencil. + if (ShouldCombineDepthStencilBarriers()) { + SubresourceStorage combinedUsages( + Aspect::CombinedDepthStencil, GetArrayLayers(), GetNumMipLevels()); + textureUsages.subresourceUsages.Iterate([&](const SubresourceRange& range, + wgpu::TextureUsage usage) { + SubresourceRange updateRange = range; + updateRange.aspects = Aspect::CombinedDepthStencil; + + combinedUsages.Update( + updateRange, [&](const SubresourceRange&, wgpu::TextureUsage* combinedUsage) { + *combinedUsage |= usage; + }); + }); + + TransitionUsageForPassImpl(recordingContext, combinedUsages, imageBarriers, srcStages, + dstStages); + } else { + TransitionUsageForPassImpl(recordingContext, textureUsages.subresourceUsages, + imageBarriers, srcStages, dstStages); + } + } + + void Texture::TransitionUsageForPassImpl( + CommandRecordingContext* recordingContext, + const SubresourceStorage& subresourceUsages, + std::vector* imageBarriers, + VkPipelineStageFlags* srcStages, + VkPipelineStageFlags* dstStages) { size_t transitionBarrierStart = imageBarriers->size(); const Format& format = GetFormat(); wgpu::TextureUsage allUsages = wgpu::TextureUsage::None; wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None; - uint32_t subresourceCount = GetSubresourceCount(); - ASSERT(textureUsages.subresourceUsages.size() == subresourceCount); // This transitions assume it is a 2D texture ASSERT(GetDimension() == wgpu::TextureDimension::e2D); - // If new usages of all subresources are the same and old usages of all subresources are - // the same too, we can use one barrier to do state transition for all subresources. - // Note that if the texture has only one mip level and one array slice, it will fall into - // this category. - if (textureUsages.sameUsagesAcrossSubresources && mSameLastUsagesAcrossSubresources) { - if (CanReuseWithoutBarrier(mSubresourceLastUsages[0], textureUsages.usage)) { - return; - } - - imageBarriers->push_back(BuildMemoryBarrier(format, mHandle, mSubresourceLastUsages[0], - textureUsages.usage, GetAllSubresources())); - allLastUsages = mSubresourceLastUsages[0]; - allUsages = textureUsages.usage; - for (uint32_t i = 0; i < subresourceCount; ++i) { - mSubresourceLastUsages[i] = textureUsages.usage; - } - } else { - for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) { - for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) { - wgpu::TextureUsage lastUsage = wgpu::TextureUsage::None; - wgpu::TextureUsage usage = wgpu::TextureUsage::None; - - // Accumulate usage for all format aspects because we cannot transition - // separately. - // TODO(enga): Use VK_KHR_separate_depth_stencil_layouts. - for (Aspect aspect : IterateEnumMask(GetFormat().aspects)) { - uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer, aspect); - - usage |= textureUsages.subresourceUsages[index]; - lastUsage |= mSubresourceLastUsages[index]; - } - - // Avoid encoding barriers when it isn't needed. - if (usage == wgpu::TextureUsage::None) { - continue; - } - - if (CanReuseWithoutBarrier(lastUsage, usage)) { - continue; - } - - allLastUsages |= lastUsage; - allUsages |= usage; - - for (Aspect aspect : IterateEnumMask(GetFormat().aspects)) { - uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer, aspect); - mSubresourceLastUsages[index] = usage; - } - - imageBarriers->push_back( - BuildMemoryBarrier(format, mHandle, lastUsage, usage, - SubresourceRange::SingleMipAndLayer( - mipLevel, arrayLayer, GetFormat().aspects))); + mSubresourceLastUsages.Merge( + subresourceUsages, [&](const SubresourceRange& range, wgpu::TextureUsage* lastUsage, + const wgpu::TextureUsage& newUsage) { + if (newUsage == wgpu::TextureUsage::None || + CanReuseWithoutBarrier(*lastUsage, newUsage)) { + return; } - } - } + + imageBarriers->push_back( + BuildMemoryBarrier(format, mHandle, *lastUsage, newUsage, range)); + + allLastUsages |= *lastUsage; + allUsages |= newUsage; + + *lastUsage = newUsage; + }); if (mExternalState != ExternalState::InternalOnly) { TweakTransitionForExternalUsage(recordingContext, imageBarriers, @@ -897,7 +903,6 @@ namespace dawn_native { namespace vulkan { *srcStages |= VulkanPipelineStage(allLastUsages, format); *dstStages |= VulkanPipelineStage(allUsages, format); - mSameLastUsagesAcrossSubresources = textureUsages.sameUsagesAcrossSubresources; } void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext, @@ -923,74 +928,55 @@ namespace dawn_native { namespace vulkan { } void Texture::TransitionUsageAndGetResourceBarrier( + wgpu::TextureUsage usage, + const SubresourceRange& range, + std::vector* imageBarriers, + VkPipelineStageFlags* srcStages, + VkPipelineStageFlags* dstStages) { + // Base Vulkan doesn't support transitioning depth and stencil separately. We work around + // this limitation by modifying the range to be on CombinedDepthStencil. The barriers will + // be produced for DEPTH | STENCIL since the SubresourceRange uses + // Aspect::CombinedDepthStencil. + if (ShouldCombineDepthStencilBarriers()) { + SubresourceRange updatedRange = range; + updatedRange.aspects = Aspect::CombinedDepthStencil; + + std::vector newBarriers; + TransitionUsageAndGetResourceBarrierImpl(usage, updatedRange, imageBarriers, srcStages, + dstStages); + } else { + TransitionUsageAndGetResourceBarrierImpl(usage, range, imageBarriers, srcStages, + dstStages); + } + } + + void Texture::TransitionUsageAndGetResourceBarrierImpl( wgpu::TextureUsage usage, const SubresourceRange& range, std::vector* imageBarriers, VkPipelineStageFlags* srcStages, VkPipelineStageFlags* dstStages) { ASSERT(imageBarriers != nullptr); - const Format& format = GetFormat(); - wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None; - // This transitions assume it is a 2D texture ASSERT(GetDimension() == wgpu::TextureDimension::e2D); - // If the usages transitions can cover all subresources, and old usages of all subresources - // are the same, then we can use one barrier to do state transition for all subresources. - // Note that if the texture has only one mip level and one array slice, it will fall into - // this category. - bool areAllSubresourcesCovered = (range.levelCount == GetNumMipLevels() && // - range.layerCount == GetArrayLayers() && // - range.aspects == format.aspects); - if (mSameLastUsagesAcrossSubresources && areAllSubresourcesCovered) { - ASSERT(range.baseMipLevel == 0 && range.baseArrayLayer == 0); - if (CanReuseWithoutBarrier(mSubresourceLastUsages[0], usage)) { + wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None; + mSubresourceLastUsages.Update(range, [&](const SubresourceRange& range, + wgpu::TextureUsage* lastUsage) { + if (CanReuseWithoutBarrier(*lastUsage, usage)) { return; } - imageBarriers->push_back( - BuildMemoryBarrier(format, mHandle, mSubresourceLastUsages[0], usage, range)); - allLastUsages = mSubresourceLastUsages[0]; - for (uint32_t i = 0; i < GetSubresourceCount(); ++i) { - mSubresourceLastUsages[i] = usage; - } - } else { - for (uint32_t layer = range.baseArrayLayer; - layer < range.baseArrayLayer + range.layerCount; ++layer) { - for (uint32_t level = range.baseMipLevel; - level < range.baseMipLevel + range.levelCount; ++level) { - // Accumulate usage for all format aspects because we cannot transition - // separately. - // TODO(enga): Use VK_KHR_separate_depth_stencil_layouts. - wgpu::TextureUsage lastUsage = wgpu::TextureUsage::None; - for (Aspect aspect : IterateEnumMask(format.aspects)) { - uint32_t index = GetSubresourceIndex(level, layer, aspect); - lastUsage |= mSubresourceLastUsages[index]; - } - if (CanReuseWithoutBarrier(lastUsage, usage)) { - continue; - } + imageBarriers->push_back(BuildMemoryBarrier(format, mHandle, *lastUsage, usage, range)); - allLastUsages |= lastUsage; - - for (Aspect aspect : IterateEnumMask(format.aspects)) { - uint32_t index = GetSubresourceIndex(level, layer, aspect); - mSubresourceLastUsages[index] = usage; - } - - imageBarriers->push_back(BuildMemoryBarrier( - format, mHandle, lastUsage, usage, - SubresourceRange::SingleMipAndLayer(level, layer, format.aspects))); - } - } - } + allLastUsages |= *lastUsage; + *lastUsage = usage; + }); *srcStages |= VulkanPipelineStage(allLastUsages, format); *dstStages |= VulkanPipelineStage(usage, format); - - mSameLastUsagesAcrossSubresources = areAllSubresourcesCovered; } MaybeError Texture::ClearTexture(CommandRecordingContext* recordingContext, @@ -1082,7 +1068,8 @@ namespace dawn_native { namespace vulkan { imageRange.aspectMask = VulkanAspectMask(aspects); imageRange.baseArrayLayer = layer; - if (aspects & (Aspect::Depth | Aspect::Stencil)) { + if (aspects & + (Aspect::Depth | Aspect::Stencil | Aspect::CombinedDepthStencil)) { VkClearDepthStencilValue clearDepthStencilValue[1]; clearDepthStencilValue[0].depth = fClearColor; clearDepthStencilValue[0].stencil = uClearColor; @@ -1144,8 +1131,7 @@ namespace dawn_native { namespace vulkan { } VkImageLayout Texture::GetCurrentLayoutForSwapChain() const { - ASSERT(mSubresourceLastUsages.size() == 1); - return VulkanImageLayout(mSubresourceLastUsages[0], GetFormat()); + return VulkanImageLayout(mSubresourceLastUsages.Get(Aspect::Color, 0, 0), GetFormat()); } // static diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h index 801eebcaee..2e4f79d0d3 100644 --- a/src/dawn_native/vulkan/TextureVk.h +++ b/src/dawn_native/vulkan/TextureVk.h @@ -65,12 +65,11 @@ namespace dawn_native { namespace vulkan { // Transitions the texture to be used as `usage`, recording any necessary barrier in // `commands`. // TODO(cwallez@chromium.org): coalesce barriers and do them early when possible. - void TransitionFullUsage(CommandRecordingContext* recordingContext, - wgpu::TextureUsage usage); - void TransitionUsageNow(CommandRecordingContext* recordingContext, wgpu::TextureUsage usage, const SubresourceRange& range); + // TODO(cwallez@chromium.org): This function should be an implementation detail of + // vulkan::Texture but it is currently used by the barrier tracking for compute passes. void TransitionUsageAndGetResourceBarrier(wgpu::TextureUsage usage, const SubresourceRange& range, std::vector* imageBarriers, @@ -101,7 +100,7 @@ namespace dawn_native { namespace vulkan { private: ~Texture() override; - using TextureBase::TextureBase; + Texture(Device* device, const TextureDescriptor* descriptor, TextureState state); MaybeError InitializeAsInternalTexture(VkImageUsageFlags extraUsages); MaybeError InitializeFromExternal(const ExternalImageDescriptorVk* descriptor, @@ -113,11 +112,32 @@ namespace dawn_native { namespace vulkan { const SubresourceRange& range, TextureBase::ClearValue); + // Implementation details of the barrier computations for the texture. + void TransitionUsageForPassImpl( + CommandRecordingContext* recordingContext, + const SubresourceStorage& subresourceUsages, + std::vector* imageBarriers, + VkPipelineStageFlags* srcStages, + VkPipelineStageFlags* dstStages); + void TransitionUsageAndGetResourceBarrierImpl( + wgpu::TextureUsage usage, + const SubresourceRange& range, + std::vector* imageBarriers, + VkPipelineStageFlags* srcStages, + VkPipelineStageFlags* dstStages); void TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext, std::vector* barriers, size_t transitionBarrierStart); bool CanReuseWithoutBarrier(wgpu::TextureUsage lastUsage, wgpu::TextureUsage usage); + // In base Vulkan, Depth and stencil can only be transitioned together. This function + // indicates whether we should combine depth and stencil barriers to accommodate this + // limitation. + bool ShouldCombineDepthStencilBarriers() const; + // Compute the Aspects of the SubresourceStoage for this texture depending on whether we're + // doing the workaround for combined depth and stencil barriers. + Aspect ComputeAspectsForSubresourceStorage() const; + VkImage mHandle = VK_NULL_HANDLE; ResourceMemoryAllocation mMemoryAllocation; VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE; @@ -137,12 +157,10 @@ namespace dawn_native { namespace vulkan { VkSemaphore mSignalSemaphore = VK_NULL_HANDLE; std::vector mWaitRequirements; - bool mSameLastUsagesAcrossSubresources = true; - - // A usage of none will make sure the texture is transitioned before its first use as - // required by the Vulkan spec. - std::vector mSubresourceLastUsages = - std::vector(GetSubresourceCount(), wgpu::TextureUsage::None); + // Note that in early Vulkan versions it is not possible to transition depth and stencil + // separately so textures with Depth|Stencil aspects will have a single Depth aspect in the + // storage. + SubresourceStorage mSubresourceLastUsages; }; class TextureView final : public TextureViewBase { diff --git a/src/dawn_native/vulkan/UtilsVulkan.cpp b/src/dawn_native/vulkan/UtilsVulkan.cpp index a78bfef100..b379e25be1 100644 --- a/src/dawn_native/vulkan/UtilsVulkan.cpp +++ b/src/dawn_native/vulkan/UtilsVulkan.cpp @@ -60,6 +60,11 @@ namespace dawn_native { namespace vulkan { case Aspect::Stencil: flags |= VK_IMAGE_ASPECT_STENCIL_BIT; break; + + case Aspect::CombinedDepthStencil: + flags |= VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + break; + case Aspect::None: UNREACHABLE(); }