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<T> 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 <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index b98708c..a0b7778 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -234,6 +234,7 @@
     "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 d65ead6..9a80bf9 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -107,6 +107,7 @@
     "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 9d4124c..76dadff 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -308,6 +308,7 @@
     // 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 @@
                 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 @@
             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 0000000..e56833a
--- /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 60dcd21..9132f65 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 <set>
@@ -29,26 +30,18 @@
     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<wgpu::TextureUsage> subresourceUsages;
+        SubresourceStorage<wgpu::TextureUsage> 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 3f5a915..4a53ae7 100644
--- a/src/dawn_native/PassResourceUsageTracker.cpp
+++ b/src/dawn_native/PassResourceUsageTracker.cpp
@@ -34,51 +34,31 @@
         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<wgpu::TextureUsage>(
-                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 4e613a6..ad9e0ee 100644
--- a/src/dawn_native/Subresource.cpp
+++ b/src/dawn_native/Subresource.cpp
@@ -46,8 +46,8 @@
         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 @@
         // 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 3090e7b..1cf439c 100644
--- a/src/dawn_native/Subresource.h
+++ b/src/dawn_native/Subresource.h
@@ -28,11 +28,15 @@
         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<Aspect> {
-        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 fb444d7..2a7e91a 100644
--- a/src/dawn_native/SubresourceStorage.h
+++ b/src/dawn_native/SubresourceStorage.h
@@ -472,7 +472,7 @@
 
         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 @@
         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 bf4c87d..c4b61ea 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -513,7 +513,9 @@
     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 @@
         }
     }
 
-    void Texture::TransitionSingleOrAllSubresources(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
-                                                    uint32_t index,
-                                                    D3D12_RESOURCE_STATES newState,
-                                                    ExecutionSerial pendingCommandSerial,
-                                                    bool allSubresources) {
-        StateAndDecay* state = &mSubresourceStateAndDecay[index];
+    void Texture::TransitionSubresourceRange(std::vector<D3D12_RESOURCE_BARRIER>* 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 @@
         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 @@
         // 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 @@
 
         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 @@
         }
     }
 
+    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 79539eb..2e2e089 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -96,26 +96,26 @@
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue);
 
-        void TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
-                                                  std::vector<D3D12_RESOURCE_BARRIER>* barrier,
-                                                  D3D12_RESOURCE_STATES newState,
-                                                  const SubresourceRange& range);
-
-        void TransitionSingleOrAllSubresources(std::vector<D3D12_RESOURCE_BARRIER>* 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<StateAndDecay> mSubresourceStateAndDecay;
+        void TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
+                                                  std::vector<D3D12_RESOURCE_BARRIER>* barrier,
+                                                  D3D12_RESOURCE_STATES newState,
+                                                  const SubresourceRange& range);
+        void TransitionSubresourceRange(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
+                                        const SubresourceRange& range,
+                                        StateAndDecay* state,
+                                        D3D12_RESOURCE_STATES subresourceNewState,
+                                        ExecutionSerial pendingCommandSerial) const;
+        void HandleTransitionSpecialCases(CommandRecordingContext* commandContext);
+
+        SubresourceStorage<StateAndDecay> mSubresourceStateAndDecay;
 
         ResourceHeapAllocation mResourceAllocation;
         bool mSwapChainTexture = false;
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 942a2ab..239b108 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -306,6 +306,7 @@
                                     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 @@
                             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 d71437a..218e60b 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -218,7 +218,7 @@
             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 @@
         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 @@
         }
 
         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 @@
         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 @@
                                          std::vector<VkImageMemoryBarrier>* 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<TextureUsage>. The barriers will be produced
+        // for DEPTH | STENCIL since the SubresourceRange uses Aspect::CombinedDepthStencil.
+        if (ShouldCombineDepthStencilBarriers()) {
+            SubresourceStorage<wgpu::TextureUsage> 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<wgpu::TextureUsage>& subresourceUsages,
+        std::vector<VkImageMemoryBarrier>* 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 @@
 
         *srcStages |= VulkanPipelineStage(allLastUsages, format);
         *dstStages |= VulkanPipelineStage(allUsages, format);
-        mSameLastUsagesAcrossSubresources = textureUsages.sameUsagesAcrossSubresources;
     }
 
     void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
@@ -928,69 +933,50 @@
         std::vector<VkImageMemoryBarrier>* 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<VkImageMemoryBarrier> 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<VkImageMemoryBarrier>* 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 @@
                     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 @@
     }
 
     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 801eebc..2e4f79d 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -65,12 +65,11 @@
         // 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<VkImageMemoryBarrier>* imageBarriers,
@@ -101,7 +100,7 @@
 
       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 @@
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue);
 
+        // Implementation details of the barrier computations for the texture.
+        void TransitionUsageForPassImpl(
+            CommandRecordingContext* recordingContext,
+            const SubresourceStorage<wgpu::TextureUsage>& subresourceUsages,
+            std::vector<VkImageMemoryBarrier>* imageBarriers,
+            VkPipelineStageFlags* srcStages,
+            VkPipelineStageFlags* dstStages);
+        void TransitionUsageAndGetResourceBarrierImpl(
+            wgpu::TextureUsage usage,
+            const SubresourceRange& range,
+            std::vector<VkImageMemoryBarrier>* imageBarriers,
+            VkPipelineStageFlags* srcStages,
+            VkPipelineStageFlags* dstStages);
         void TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
                                              std::vector<VkImageMemoryBarrier>* 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 @@
         VkSemaphore mSignalSemaphore = VK_NULL_HANDLE;
         std::vector<VkSemaphore> 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<wgpu::TextureUsage> mSubresourceLastUsages =
-            std::vector<wgpu::TextureUsage>(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<wgpu::TextureUsage> mSubresourceLastUsages;
     };
 
     class TextureView final : public TextureViewBase {
diff --git a/src/dawn_native/vulkan/UtilsVulkan.cpp b/src/dawn_native/vulkan/UtilsVulkan.cpp
index a78bfef..b379e25 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn_native/vulkan/UtilsVulkan.cpp
@@ -60,6 +60,11 @@
                 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();
             }