From ef869c2d01d5241ba8d16e6863e8595bc1fac9fd Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Thu, 21 Jan 2021 11:07:44 +0000 Subject: [PATCH] Use SubresourceStorage to track per-subresource state. Using this container is a small performance regression in the simple cases where all subresources are the same, or when the texture has few subrsources. However it give better performance in the hard subresource tracking cases of textures with many subresources. Using SubresourceStorage also makes it easier to work with compressed storage since the compression is mostly transparent. It reduces code duplication and prevent bugs from appearing when a developer would forget to handle compression. This fixes a state tracking issue in ValidatePassResourceUsage where the function didn't correctly handle the case where the PassResourceUsage was compressed. Also removes the unused vulkan::Texture::TransitionFullUsage. Also makes SubresourceStorage only require operator== on T and not operator !=. Also fixes the texture format's aspect being used to create pipeline barriers instead of the range's aspects. Bug: dawn:441 Change-Id: I234b8191f39a09b541c1c63a60cccd6cee970550 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/37706 Reviewed-by: Corentin Wallez Commit-Queue: Corentin Wallez Auto-Submit: Corentin Wallez --- src/dawn_native/BUILD.gn | 1 + src/dawn_native/CMakeLists.txt | 1 + src/dawn_native/CommandValidation.cpp | 27 ++- src/dawn_native/PassResourceUsage.cpp | 29 +++ src/dawn_native/PassResourceUsage.h | 19 +- src/dawn_native/PassResourceUsageTracker.cpp | 56 ++--- src/dawn_native/Subresource.cpp | 5 +- src/dawn_native/Subresource.h | 6 +- src/dawn_native/SubresourceStorage.h | 4 +- src/dawn_native/d3d12/TextureD3D12.cpp | 128 +++++------ src/dawn_native/d3d12/TextureD3D12.h | 30 +-- src/dawn_native/opengl/CommandBufferGL.cpp | 2 + src/dawn_native/vulkan/TextureVk.cpp | 220 +++++++++---------- src/dawn_native/vulkan/TextureVk.h | 38 +++- src/dawn_native/vulkan/UtilsVulkan.cpp | 5 + 15 files changed, 284 insertions(+), 287 deletions(-) create mode 100644 src/dawn_native/PassResourceUsage.cpp 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(); }