Track depth/stencil aspects independently

This enables depth-stencil textures to track per aspect state
independently. It lifts the restriction that depth and stencil
store ops must be the same as they now have independent clear
states. It will also enable correct barriers on Vulkan and D3D12.

Bug: dawn:439
Change-Id: I8a73187df57a1d7eee6790cb4395bdecf42b63aa
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/26127
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/CommandBuffer.cpp b/src/dawn_native/CommandBuffer.cpp
index 8329e95..57ff4ec 100644
--- a/src/dawn_native/CommandBuffer.cpp
+++ b/src/dawn_native/CommandBuffer.cpp
@@ -56,7 +56,7 @@
                                                    const Extent3D& copySize) {
         switch (copy.texture->GetDimension()) {
             case wgpu::TextureDimension::e2D:
-                return {copy.mipLevel, 1, copy.origin.z, copySize.depth};
+                return {copy.mipLevel, 1, copy.origin.z, copySize.depth, copy.aspect};
             default:
                 UNREACHABLE();
                 return {};
@@ -113,36 +113,31 @@
             ASSERT(view->GetLevelCount() == 1);
             SubresourceRange range = view->GetSubresourceRange();
 
+            SubresourceRange depthRange = range;
+            depthRange.aspects = range.aspects & Aspect::Depth;
+
+            SubresourceRange stencilRange = range;
+            stencilRange.aspects = range.aspects & Aspect::Stencil;
+
             // If the depth stencil texture has not been initialized, we want to use loadop
             // clear to init the contents to 0's
-            if (!view->GetTexture()->IsSubresourceContentInitialized(range)) {
-                if (view->GetTexture()->GetFormat().HasDepth() &&
-                    attachmentInfo.depthLoadOp == wgpu::LoadOp::Load) {
-                    attachmentInfo.clearDepth = 0.0f;
-                    attachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
-                }
-                if (view->GetTexture()->GetFormat().HasStencil() &&
-                    attachmentInfo.stencilLoadOp == wgpu::LoadOp::Load) {
-                    attachmentInfo.clearStencil = 0u;
-                    attachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear;
-                }
+            if (!view->GetTexture()->IsSubresourceContentInitialized(depthRange) &&
+                attachmentInfo.depthLoadOp == wgpu::LoadOp::Load) {
+                attachmentInfo.clearDepth = 0.0f;
+                attachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
             }
 
-            // If these have different store ops, make them both Store because we can't track
-            // initialized state separately yet. TODO(crbug.com/dawn/145)
-            if (attachmentInfo.depthStoreOp != attachmentInfo.stencilStoreOp) {
-                attachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
-                attachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            if (!view->GetTexture()->IsSubresourceContentInitialized(stencilRange) &&
+                attachmentInfo.stencilLoadOp == wgpu::LoadOp::Load) {
+                attachmentInfo.clearStencil = 0u;
+                attachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear;
             }
 
-            if (attachmentInfo.depthStoreOp == wgpu::StoreOp::Store &&
-                attachmentInfo.stencilStoreOp == wgpu::StoreOp::Store) {
-                view->GetTexture()->SetIsSubresourceContentInitialized(true, range);
-            } else {
-                ASSERT(attachmentInfo.depthStoreOp == wgpu::StoreOp::Clear &&
-                       attachmentInfo.stencilStoreOp == wgpu::StoreOp::Clear);
-                view->GetTexture()->SetIsSubresourceContentInitialized(false, range);
-            }
+            view->GetTexture()->SetIsSubresourceContentInitialized(
+                attachmentInfo.depthStoreOp == wgpu::StoreOp::Store, depthRange);
+
+            view->GetTexture()->SetIsSubresourceContentInitialized(
+                attachmentInfo.stencilStoreOp == wgpu::StoreOp::Store, stencilRange);
         }
     }
 
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index b1a6348..62e730a 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -384,12 +384,6 @@
                 return DAWN_VALIDATION_ERROR("Depth clear value cannot be NaN");
             }
 
-            // This validates that the depth storeOp and stencil storeOps are the same
-            if (depthStencilAttachment->depthStoreOp != depthStencilAttachment->stencilStoreOp) {
-                return DAWN_VALIDATION_ERROR(
-                    "The depth storeOp and stencil storeOp are not the same");
-            }
-
             // *sampleCount == 0 must only happen when there is no color attachment. In that case we
             // do not need to validate the sample count of the depth stencil attachment.
             const uint32_t depthStencilSampleCount = attachment->GetTexture()->GetSampleCount();
@@ -732,7 +726,8 @@
             copy->destination.texture = destination->texture;
             copy->destination.origin = destination->origin;
             copy->destination.mipLevel = destination->mipLevel;
-            copy->destination.aspect = destination->aspect;
+            copy->destination.aspect =
+                ConvertAspect(destination->texture->GetFormat(), destination->aspect);
             copy->copySize = *copySize;
 
             return {};
@@ -795,7 +790,7 @@
             copy->source.texture = source->texture;
             copy->source.origin = source->origin;
             copy->source.mipLevel = source->mipLevel;
-            copy->source.aspect = source->aspect;
+            copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
             copy->destination.buffer = destination->buffer;
             copy->destination.offset = destination->layout.offset;
             copy->destination.bytesPerRow = bytesPerRow;
@@ -844,11 +839,12 @@
             copy->source.texture = source->texture;
             copy->source.origin = source->origin;
             copy->source.mipLevel = source->mipLevel;
-            copy->source.aspect = source->aspect;
+            copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect);
             copy->destination.texture = destination->texture;
             copy->destination.origin = destination->origin;
             copy->destination.mipLevel = destination->mipLevel;
-            copy->destination.aspect = destination->aspect;
+            copy->destination.aspect =
+                ConvertAspect(destination->texture->GetFormat(), destination->aspect);
             copy->copySize = *copySize;
 
             return {};
diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h
index 32ffcc3..0948678 100644
--- a/src/dawn_native/Commands.h
+++ b/src/dawn_native/Commands.h
@@ -105,7 +105,7 @@
         Ref<TextureBase> texture;
         uint32_t mipLevel;
         Origin3D origin;  // Texels / array layer
-        wgpu::TextureAspect aspect;
+        Aspect aspect;
     };
 
     struct CopyBufferToBufferCmd {
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index 3fe408c..b7bb94b 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -179,10 +179,13 @@
                                          uint32_t baseMipLevel,
                                          uint32_t levelCount,
                                          uint32_t baseArrayLayer,
-                                         uint32_t layerCount) {
+                                         uint32_t layerCount,
+                                         WGPUTextureAspect aspect) {
         dawn_native::TextureBase* textureBase =
             reinterpret_cast<dawn_native::TextureBase*>(texture);
-        SubresourceRange range = {baseMipLevel, levelCount, baseArrayLayer, layerCount};
+        SubresourceRange range = {
+            baseMipLevel, levelCount, baseArrayLayer, layerCount,
+            ConvertAspect(textureBase->GetFormat(), static_cast<wgpu::TextureAspect>(aspect))};
         return textureBase->IsSubresourceContentInitialized(range);
     }
 
diff --git a/src/dawn_native/EnumClassBitmasks.h b/src/dawn_native/EnumClassBitmasks.h
index 2072b60..3227cd2 100644
--- a/src/dawn_native/EnumClassBitmasks.h
+++ b/src/dawn_native/EnumClassBitmasks.h
@@ -36,6 +36,13 @@
     using wgpu::operator|=;
     using wgpu::operator^=;
 
+    using wgpu::HasZeroOrOneBits;
+
+    template <typename T>
+    constexpr bool HasOneBit(T value) {
+        return HasZeroOrOneBits(value) && value != T(0);
+    }
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_ENUMCLASSBITMASK_H_
diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp
index 92a8a60..3b65c3f 100644
--- a/src/dawn_native/Format.cpp
+++ b/src/dawn_native/Format.cpp
@@ -120,6 +120,36 @@
         }
     }
 
+    TexelBlockInfo Format::GetTexelBlockInfo(Aspect aspect) const {
+        ASSERT(HasOneBit(aspect));
+        ASSERT(aspects & aspect);
+        switch (aspect) {
+            case Aspect::Color:
+                ASSERT(aspects == aspect);
+                return *this;
+            case Aspect::Depth:
+                switch (format) {
+                    case wgpu::TextureFormat::Depth32Float:
+                        return *this;
+                    default:
+                        UNREACHABLE();
+                        break;
+                }
+            case Aspect::Stencil:
+                switch (format) {
+                    case wgpu::TextureFormat::Depth24PlusStencil8:
+                        return {1, 1, 1};
+                    default:
+                        UNREACHABLE();
+                        break;
+                }
+                break;
+            default:
+                UNREACHABLE();
+                break;
+        }
+    }
+
     size_t Format::GetIndex() const {
         return ComputeFormatIndex(format);
     }
diff --git a/src/dawn_native/Format.h b/src/dawn_native/Format.h
index f57370e..8113a94 100644
--- a/src/dawn_native/Format.h
+++ b/src/dawn_native/Format.h
@@ -67,6 +67,7 @@
         bool HasComponentType(Type componentType) const;
 
         TexelBlockInfo GetTexelBlockInfo(wgpu::TextureAspect aspect) const;
+        TexelBlockInfo GetTexelBlockInfo(Aspect aspect) const;
 
         // The index of the format in the list of all known formats: a unique number for each format
         // in [0, kKnownFormatCount)
diff --git a/src/dawn_native/PassResourceUsageTracker.cpp b/src/dawn_native/PassResourceUsageTracker.cpp
index bd176df..3f5a915 100644
--- a/src/dawn_native/PassResourceUsageTracker.cpp
+++ b/src/dawn_native/PassResourceUsageTracker.cpp
@@ -15,6 +15,8 @@
 #include "dawn_native/PassResourceUsageTracker.h"
 
 #include "dawn_native/Buffer.h"
+#include "dawn_native/EnumMaskIterator.h"
+#include "dawn_native/Format.h"
 #include "dawn_native/Texture.h"
 
 namespace dawn_native {
@@ -30,10 +32,7 @@
     void PassResourceUsageTracker::TextureViewUsedAs(TextureViewBase* view,
                                                      wgpu::TextureUsage usage) {
         TextureBase* texture = view->GetTexture();
-        uint32_t baseMipLevel = view->GetBaseMipLevel();
-        uint32_t levelCount = view->GetLevelCount();
-        uint32_t baseArrayLayer = view->GetBaseArrayLayer();
-        uint32_t layerCount = view->GetLayerCount();
+        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.
@@ -42,20 +41,25 @@
 
         // Set parameters for the whole texture
         textureUsage.usage |= usage;
-        uint32_t subresourceCount = texture->GetSubresourceCount();
-        textureUsage.sameUsagesAcrossSubresources &= levelCount * layerCount == subresourceCount;
+        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>(subresourceCount, wgpu::TextureUsage::None);
+            textureUsage.subresourceUsages = std::vector<wgpu::TextureUsage>(
+                texture->GetSubresourceCount(), wgpu::TextureUsage::None);
         }
-        for (uint32_t arrayLayer = baseArrayLayer; arrayLayer < baseArrayLayer + layerCount;
-             ++arrayLayer) {
-            for (uint32_t mipLevel = baseMipLevel; mipLevel < baseMipLevel + levelCount;
-                 ++mipLevel) {
-                uint32_t subresourceIndex = texture->GetSubresourceIndex(mipLevel, arrayLayer);
-                textureUsage.subresourceUsages[subresourceIndex] |= usage;
+        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;
+                }
             }
         }
     }
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index df46321..550846b 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -20,6 +20,7 @@
 #include "common/Constants.h"
 #include "common/Math.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/PassResourceUsage.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
@@ -200,6 +201,11 @@
             return {};
         }
 
+        uint8_t GetPlaneIndex(Aspect aspect) {
+            ASSERT(HasOneBit(aspect));
+            return static_cast<uint8_t>(Log2(static_cast<uint32_t>(aspect)));
+        }
+
     }  // anonymous namespace
 
     MaybeError ValidateTextureDescriptor(const DeviceBase* device,
@@ -357,10 +363,33 @@
         }
     }
 
+    Aspect ConvertSingleAspect(const Format& format, wgpu::TextureAspect aspect) {
+        Aspect aspectMask = ConvertAspect(format, aspect);
+        ASSERT(HasOneBit(aspectMask));
+        return aspectMask;
+    }
+
+    Aspect ConvertAspect(const Format& format, wgpu::TextureAspect aspect) {
+        switch (aspect) {
+            case wgpu::TextureAspect::All:
+                return format.aspects;
+            case wgpu::TextureAspect::DepthOnly:
+                ASSERT(format.aspects & Aspect::Depth);
+                return Aspect::Depth;
+            case wgpu::TextureAspect::StencilOnly:
+                ASSERT(format.aspects & Aspect::Stencil);
+                return Aspect::Stencil;
+            default:
+                UNREACHABLE();
+                break;
+        }
+    }
+
     // static
-    SubresourceRange SubresourceRange::SingleSubresource(uint32_t baseMipLevel,
-                                                         uint32_t baseArrayLayer) {
-        return {baseMipLevel, 1, baseArrayLayer, 1};
+    SubresourceRange SubresourceRange::SingleMipAndLayer(uint32_t baseMipLevel,
+                                                         uint32_t baseArrayLayer,
+                                                         Aspect aspects) {
+        return {baseMipLevel, 1, baseArrayLayer, 1, aspects};
     }
 
     // TextureBase
@@ -376,7 +405,13 @@
           mSampleCount(descriptor->sampleCount),
           mUsage(descriptor->usage),
           mState(state) {
-        uint32_t subresourceCount = GetSubresourceCount();
+        uint8_t planeIndex = 0;
+        for (Aspect aspect : IterateEnumMask(mFormat.aspects)) {
+            mPlaneIndices[GetPlaneIndex(aspect)] = planeIndex++;
+        }
+
+        uint32_t subresourceCount =
+            mMipLevelCount * mSize.depth * static_cast<uint32_t>(planeIndex);
         mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false);
 
         // Add readonly storage usage if the texture has a storage usage. The validation rules in
@@ -438,7 +473,7 @@
     }
     SubresourceRange TextureBase::GetAllSubresources() const {
         ASSERT(!IsError());
-        return {0, mMipLevelCount, 0, GetArrayLayers()};
+        return {0, mMipLevelCount, 0, GetArrayLayers(), mFormat.aspects};
     }
     uint32_t TextureBase::GetSampleCount() const {
         ASSERT(!IsError());
@@ -446,7 +481,7 @@
     }
     uint32_t TextureBase::GetSubresourceCount() const {
         ASSERT(!IsError());
-        return mMipLevelCount * mSize.depth;
+        return static_cast<uint32_t>(mIsSubresourceContentInitializedAtIndex.size());
     }
     wgpu::TextureUsage TextureBase::GetUsage() const {
         ASSERT(!IsError());
@@ -458,25 +493,32 @@
         return mState;
     }
 
-    uint32_t TextureBase::GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice) const {
+    uint32_t TextureBase::GetSubresourceIndex(uint32_t mipLevel,
+                                              uint32_t arraySlice,
+                                              Aspect aspect) const {
         ASSERT(arraySlice <= kMaxTexture2DArrayLayers);
         ASSERT(mipLevel <= kMaxTexture2DMipLevels);
+        ASSERT(HasOneBit(aspect));
         static_assert(kMaxTexture2DMipLevels <=
                           std::numeric_limits<uint32_t>::max() / kMaxTexture2DArrayLayers,
                       "texture size overflows uint32_t");
-        return GetNumMipLevels() * arraySlice + mipLevel;
+        return mipLevel +
+               GetNumMipLevels() *
+                   (arraySlice + GetArrayLayers() * mPlaneIndices[GetPlaneIndex(aspect)]);
     }
 
     bool TextureBase::IsSubresourceContentInitialized(const SubresourceRange& range) const {
         ASSERT(!IsError());
-        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 = GetSubresourceIndex(mipLevel, arrayLayer);
-                ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
-                if (!mIsSubresourceContentInitializedAtIndex[subresourceIndex]) {
-                    return false;
+        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 = GetSubresourceIndex(mipLevel, arrayLayer, aspect);
+                    ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
+                    if (!mIsSubresourceContentInitializedAtIndex[subresourceIndex]) {
+                        return false;
+                    }
                 }
             }
         }
@@ -486,13 +528,15 @@
     void TextureBase::SetIsSubresourceContentInitialized(bool isInitialized,
                                                          const SubresourceRange& range) {
         ASSERT(!IsError());
-        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 = GetSubresourceIndex(mipLevel, arrayLayer);
-                ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
-                mIsSubresourceContentInitializedAtIndex[subresourceIndex] = isInitialized;
+        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 = GetSubresourceIndex(mipLevel, arrayLayer, aspect);
+                    ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
+                    mIsSubresourceContentInitializedAtIndex[subresourceIndex] = isInitialized;
+                }
             }
         }
     }
@@ -588,7 +632,9 @@
           mFormat(GetDevice()->GetValidInternalFormat(descriptor->format)),
           mDimension(descriptor->dimension),
           mRange({descriptor->baseMipLevel, descriptor->mipLevelCount, descriptor->baseArrayLayer,
-                  descriptor->arrayLayerCount}) {
+                  descriptor->arrayLayerCount, ConvertAspect(mFormat, mAspect)}) {
+        // TODO(crbug.com/dawn/439): Current validation only allows texture views with aspect "all".
+        ASSERT(mAspect == wgpu::TextureAspect::All);
     }
 
     TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag)
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index 6c027e1..fbb2dea 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_TEXTURE_H_
 #define DAWNNATIVE_TEXTURE_H_
 
+#include "common/ityp_array.h"
 #include "common/ityp_bitset.h"
 #include "dawn_native/EnumClassBitmasks.h"
 #include "dawn_native/Error.h"
@@ -27,7 +28,11 @@
 
 namespace dawn_native {
 
+    // Note: Subresource indices are computed by iterating the aspects in increasing order.
+    // D3D12 uses these directly, so the order much match D3D12's indices.
+    //  - Depth/Stencil textures have Depth as Plane 0, and Stencil as Plane 1.
     enum class Aspect : uint8_t {
+        None = 0x0,
         Color = 0x1,
         Depth = 0x2,
         Stencil = 0x4,
@@ -73,13 +78,19 @@
         wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Storage |
         wgpu::TextureUsage::OutputAttachment;
 
+    Aspect ConvertSingleAspect(const Format& format, wgpu::TextureAspect aspect);
+    Aspect ConvertAspect(const Format& format, wgpu::TextureAspect aspect);
+
     struct SubresourceRange {
         uint32_t baseMipLevel;
         uint32_t levelCount;
         uint32_t baseArrayLayer;
         uint32_t layerCount;
+        Aspect aspects;
 
-        static SubresourceRange SingleSubresource(uint32_t baseMipLevel, uint32_t baseArrayLayer);
+        static SubresourceRange SingleMipAndLayer(uint32_t baseMipLevel,
+                                                  uint32_t baseArrayLayer,
+                                                  Aspect aspects);
     };
 
     class TextureBase : public ObjectBase {
@@ -103,7 +114,7 @@
         uint32_t GetSubresourceCount() const;
         wgpu::TextureUsage GetUsage() const;
         TextureState GetTextureState() const;
-        uint32_t GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice) const;
+        uint32_t GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice, Aspect aspect) const;
         bool IsSubresourceContentInitialized(const SubresourceRange& range) const;
         void SetIsSubresourceContentInitialized(bool isInitialized, const SubresourceRange& range);
 
@@ -145,6 +156,7 @@
 
         // TODO(natlee@microsoft.com): Use a more optimized data structure to save space
         std::vector<bool> mIsSubresourceContentInitializedAtIndex;
+        std::array<uint8_t, EnumBitmaskSize<Aspect>::value> mPlaneIndices;
     };
 
     class TextureViewBase : public ObjectBase {
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index a1baf89..c58f150 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -19,6 +19,7 @@
 #include "dawn_native/CommandEncoder.h"
 #include "dawn_native/CommandValidation.h"
 #include "dawn_native/Commands.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/RenderBundle.h"
 #include "dawn_native/d3d12/BindGroupD3D12.h"
 #include "dawn_native/d3d12/BindGroupLayoutD3D12.h"
@@ -101,9 +102,11 @@
                                                            uint64_t bufferBytesPerRow,
                                                            Texture* texture,
                                                            uint32_t textureMiplevel,
-                                                           uint32_t textureSlice) {
+                                                           uint32_t textureSlice,
+                                                           Aspect aspect) {
             const D3D12_TEXTURE_COPY_LOCATION textureLocation =
-                ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice);
+                ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice,
+                                                     aspect);
 
             const uint64_t offset = baseCopySplit.offset + baseOffset;
 
@@ -132,9 +135,11 @@
                                                            uint64_t bufferBytesPerRow,
                                                            Texture* texture,
                                                            uint32_t textureMiplevel,
-                                                           uint32_t textureSlice) {
+                                                           uint32_t textureSlice,
+                                                           Aspect aspect) {
             const D3D12_TEXTURE_COPY_LOCATION textureLocation =
-                ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice);
+                ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice,
+                                                     aspect);
 
             const uint64_t offset = baseCopySplit.offset + baseOffset;
 
@@ -551,7 +556,8 @@
                 ID3D12Resource* colorTextureHandle = colorTexture->GetD3D12Resource();
                 ID3D12Resource* resolveTextureHandle = resolveTexture->GetD3D12Resource();
                 const uint32_t resolveTextureSubresourceIndex = resolveTexture->GetSubresourceIndex(
-                    resolveTarget->GetBaseMipLevel(), resolveTarget->GetBaseArrayLayer());
+                    resolveTarget->GetBaseMipLevel(), resolveTarget->GetBaseArrayLayer(),
+                    Aspect::Color);
                 constexpr uint32_t kColorTextureSubresourceIndex = 0;
                 commandContext->GetCommandList()->ResolveSubresource(
                     resolveTextureHandle, resolveTextureSubresourceIndex, colorTextureHandle,
@@ -693,9 +699,9 @@
                     DAWN_TRY(buffer->EnsureDataInitialized(commandContext));
 
                     ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    SubresourceRange subresources = {copy->destination.mipLevel, 1,
-                                                     copy->destination.origin.z,
-                                                     copy->copySize.depth};
+                    SubresourceRange subresources =
+                        GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
+
                     if (IsCompleteSubresourceCopiedTo(texture, copy->copySize,
                                                       copy->destination.mipLevel)) {
                         texture->SetIsSubresourceContentInitialized(true, subresources);
@@ -737,7 +743,7 @@
                         RecordCopyBufferToTextureFromTextureCopySplit(
                             commandList, copySplitPerLayerBase, buffer, bufferOffsetForNextSlice,
                             copy->source.bytesPerRow, texture, copy->destination.mipLevel,
-                            copyTextureLayer);
+                            copyTextureLayer, subresources.aspects);
 
                         bufferOffsetsForNextSlice[splitIndex] +=
                             bytesPerSlice * copySplits.copies2D.size();
@@ -754,8 +760,9 @@
                     DAWN_TRY(buffer->EnsureDataInitializedAsDestination(commandContext, copy));
 
                     ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    SubresourceRange subresources = {copy->source.mipLevel, 1,
-                                                     copy->source.origin.z, copy->copySize.depth};
+                    SubresourceRange subresources =
+                        GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
+
                     texture->EnsureSubresourceContentInitialized(commandContext, subresources);
 
                     texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc,
@@ -793,7 +800,7 @@
                         RecordCopyTextureToBufferFromTextureCopySplit(
                             commandList, copySplitPerLayerBase, buffer, bufferOffsetForNextSlice,
                             copy->destination.bytesPerRow, texture, copy->source.mipLevel,
-                            copyTextureLayer);
+                            copyTextureLayer, subresources.aspects);
 
                         bufferOffsetsForNextSlice[splitIndex] +=
                             bytesPerSlice * copySplits.copies2D.size();
@@ -808,10 +815,11 @@
 
                     Texture* source = ToBackend(copy->source.texture.Get());
                     Texture* destination = ToBackend(copy->destination.texture.Get());
-                    SubresourceRange srcRange = {copy->source.mipLevel, 1, copy->source.origin.z,
-                                                 copy->copySize.depth};
-                    SubresourceRange dstRange = {copy->destination.mipLevel, 1,
-                                                 copy->destination.origin.z, copy->copySize.depth};
+
+                    SubresourceRange srcRange =
+                        GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
+                    SubresourceRange dstRange =
+                        GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
 
                     source->EnsureSubresourceContentInitialized(commandContext, srcRange);
                     if (IsCompleteSubresourceCopiedTo(destination, copy->copySize,
@@ -835,6 +843,7 @@
                     destination->TrackUsageAndTransitionNow(commandContext,
                                                             wgpu::TextureUsage::CopyDst, dstRange);
 
+                    ASSERT(srcRange.aspects == dstRange.aspects);
                     if (CanUseCopyResource(source, destination, copy->copySize)) {
                         commandList->CopyResource(destination->GetD3D12Resource(),
                                                   source->GetD3D12Resource());
@@ -844,24 +853,28 @@
                                destination->GetDimension() == wgpu::TextureDimension::e2D);
                         const dawn_native::Extent3D copyExtentOneSlice = {
                             copy->copySize.width, copy->copySize.height, 1u};
-                        for (uint32_t slice = 0; slice < copy->copySize.depth; ++slice) {
-                            D3D12_TEXTURE_COPY_LOCATION srcLocation =
-                                ComputeTextureCopyLocationForTexture(source, copy->source.mipLevel,
-                                                                     copy->source.origin.z + slice);
 
-                            D3D12_TEXTURE_COPY_LOCATION dstLocation =
-                                ComputeTextureCopyLocationForTexture(
-                                    destination, copy->destination.mipLevel,
-                                    copy->destination.origin.z + slice);
+                        for (Aspect aspect : IterateEnumMask(srcRange.aspects)) {
+                            for (uint32_t slice = 0; slice < copy->copySize.depth; ++slice) {
+                                D3D12_TEXTURE_COPY_LOCATION srcLocation =
+                                    ComputeTextureCopyLocationForTexture(
+                                        source, copy->source.mipLevel,
+                                        copy->source.origin.z + slice, aspect);
 
-                            Origin3D sourceOriginInSubresource = copy->source.origin;
-                            sourceOriginInSubresource.z = 0;
-                            D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize(
-                                sourceOriginInSubresource, copyExtentOneSlice);
+                                D3D12_TEXTURE_COPY_LOCATION dstLocation =
+                                    ComputeTextureCopyLocationForTexture(
+                                        destination, copy->destination.mipLevel,
+                                        copy->destination.origin.z + slice, aspect);
 
-                            commandList->CopyTextureRegion(&dstLocation, copy->destination.origin.x,
-                                                           copy->destination.origin.y, 0,
-                                                           &srcLocation, &sourceRegion);
+                                Origin3D sourceOriginInSubresource = copy->source.origin;
+                                sourceOriginInSubresource.z = 0;
+                                D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize(
+                                    sourceOriginInSubresource, copyExtentOneSlice);
+
+                                commandList->CopyTextureRegion(
+                                    &dstLocation, copy->destination.origin.x,
+                                    copy->destination.origin.y, 0, &srcLocation, &sourceRegion);
+                            }
                         }
                     }
                     break;
diff --git a/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
index 66558cb..587f48a 100644
--- a/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
+++ b/src/dawn_native/d3d12/RenderPassBuilderD3D12.cpp
@@ -86,12 +86,14 @@
         D3D12EndingAccessResolveSubresourceParameters(TextureView* resolveDestination) {
             D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS subresourceParameters;
             Texture* resolveDestinationTexture = ToBackend(resolveDestination->GetTexture());
+            ASSERT(resolveDestinationTexture->GetFormat().aspects == Aspect::Color);
 
             subresourceParameters.DstX = 0;
             subresourceParameters.DstY = 0;
             subresourceParameters.SrcSubresource = 0;
             subresourceParameters.DstSubresource = resolveDestinationTexture->GetSubresourceIndex(
-                resolveDestination->GetBaseMipLevel(), resolveDestination->GetBaseArrayLayer());
+                resolveDestination->GetBaseMipLevel(), resolveDestination->GetBaseArrayLayer(),
+                Aspect::Color);
             // Resolving a specified sub-rect is only valid on hardware that supports sample
             // positions. This means even {0, 0, width, height} would be invalid if unsupported. To
             // avoid this, we assume sub-rect resolves never work by setting them to all zeros or
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index b85f72a..62fe6e9 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -17,6 +17,7 @@
 #include "common/Constants.h"
 #include "common/Math.h"
 #include "dawn_native/DynamicUploader.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/Error.h"
 #include "dawn_native/d3d12/BufferD3D12.h"
 #include "dawn_native/d3d12/CommandRecordingContext.h"
@@ -574,7 +575,15 @@
         }
 
         std::vector<D3D12_RESOURCE_BARRIER> barriers;
-        barriers.reserve(range.levelCount * range.layerCount);
+
+        // TODO(enga): Consider adding a Count helper.
+        uint32_t aspectCount = 0;
+        for (Aspect aspect : IterateEnumMask(range.aspects)) {
+            aspectCount++;
+            DAWN_UNUSED(aspect);
+        }
+
+        barriers.reserve(range.levelCount * range.layerCount * aspectCount);
 
         TransitionUsageAndGetResourceBarrier(commandContext, &barriers, newState, range);
         if (barriers.size()) {
@@ -652,19 +661,6 @@
         barrier.Transition.Subresource =
             allSubresources ? D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES : index;
         barriers->push_back(barrier);
-        // TODO(yunchao.he@intel.com): support subresource for depth/stencil. Depth stencil
-        // texture has different plane slices. While the current implementation only has differernt
-        // mip slices and array slices for subresources.
-        // This is a hack because Dawn doesn't handle subresource of multiplanar resources
-        // correctly. We force the transition to be the same for all planes to match what the
-        // frontend validation checks for. This hack might be incorrect for stencil-only texture
-        // because we always set transition barrier for depth plane.
-        if (!allSubresources && newState == D3D12_RESOURCE_STATE_DEPTH_WRITE &&
-            GetFormat().HasStencil()) {
-            D3D12_RESOURCE_BARRIER barrierStencil = barrier;
-            barrierStencil.Transition.Subresource += GetArrayLayers() * GetNumMipLevels();
-            barriers->push_back(barrierStencil);
-        }
 
         state->isValidToDecay = false;
     }
@@ -686,7 +682,6 @@
         HandleTransitionSpecialCases(commandContext);
 
         const Serial pendingCommandSerial = ToBackend(GetDevice())->GetPendingCommandSerial();
-        uint32_t subresourceCount = GetSubresourceCount();
 
         // This transitions assume it is a 2D texture
         ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
@@ -695,25 +690,29 @@
         // 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 * range.layerCount == subresourceCount;
+        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 < subresourceCount; ++i) {
+            for (uint32_t i = 1; i < GetSubresourceCount(); ++i) {
                 mSubresourceStateAndDecay[i] = mSubresourceStateAndDecay[0];
             }
 
             return;
         }
-        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);
+        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);
+                    TransitionSingleOrAllSubresources(barriers, index, newState,
+                                                      pendingCommandSerial, false);
+                }
             }
         }
         mSameLastUsagesAcrossSubresources = areAllSubresourcesCovered;
@@ -748,20 +747,22 @@
             return;
         }
 
-        for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) {
-            for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) {
-                uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer);
+        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;
+                    // 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);
                 }
-
-                D3D12_RESOURCE_STATES newState =
-                    D3D12TextureUsage(textureUsages.subresourceUsages[index], GetFormat());
-
-                TransitionSingleOrAllSubresources(barriers, index, newState, pendingCommandSerial,
-                                                  false);
             }
         }
         mSameLastUsagesAcrossSubresources = textureUsages.sameUsagesAcrossSubresources;
@@ -838,16 +839,34 @@
             if (GetFormat().HasDepthOrStencil()) {
                 TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_DEPTH_WRITE, range);
 
-                D3D12_CLEAR_FLAGS clearFlags = {};
-
                 for (uint32_t level = range.baseMipLevel;
                      level < range.baseMipLevel + range.levelCount; ++level) {
                     for (uint32_t layer = range.baseArrayLayer;
                          layer < range.baseArrayLayer + range.layerCount; ++layer) {
-                        if (clearValue == TextureBase::ClearValue::Zero &&
-                            IsSubresourceContentInitialized(
-                                SubresourceRange::SingleSubresource(level, layer))) {
-                            // Skip lazy clears if already initialized.
+                        // Iterate the aspects individually to determine which clear flags to use.
+                        D3D12_CLEAR_FLAGS clearFlags = {};
+                        for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                            if (clearValue == TextureBase::ClearValue::Zero &&
+                                IsSubresourceContentInitialized(
+                                    SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
+                                // Skip lazy clears if already initialized.
+                                continue;
+                            }
+
+                            switch (aspect) {
+                                case Aspect::Depth:
+                                    clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
+                                    break;
+                                case Aspect::Stencil:
+                                    clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
+                                    break;
+                                default:
+                                    UNREACHABLE();
+                                    break;
+                            }
+                        }
+
+                        if (clearFlags == 0) {
                             continue;
                         }
 
@@ -860,13 +879,6 @@
                         device->GetD3D12Device()->CreateDepthStencilView(GetD3D12Resource(),
                                                                          &dsvDesc, baseDescriptor);
 
-                        if (GetFormat().HasDepth()) {
-                            clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
-                        }
-                        if (GetFormat().HasStencil()) {
-                            clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
-                        }
-
                         commandList->ClearDepthStencilView(baseDescriptor, clearFlags, fClearColor,
                                                            clearColor, 0, nullptr);
                     }
@@ -878,13 +890,14 @@
                 const float clearColorRGBA[4] = {fClearColor, fClearColor, fClearColor,
                                                  fClearColor};
 
+                ASSERT(range.aspects == Aspect::Color);
                 for (uint32_t level = range.baseMipLevel;
                      level < range.baseMipLevel + range.levelCount; ++level) {
                     for (uint32_t layer = range.baseArrayLayer;
                          layer < range.baseArrayLayer + range.layerCount; ++layer) {
                         if (clearValue == TextureBase::ClearValue::Zero &&
                             IsSubresourceContentInitialized(
-                                SubresourceRange::SingleSubresource(level, layer))) {
+                                SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                             // Skip lazy clears if already initialized.
                             continue;
                         }
@@ -920,6 +933,7 @@
 
             TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_COPY_DEST, range);
 
+            ASSERT(range.aspects == Aspect::Color);
             for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                  ++level) {
                 // compute d3d12 texture copy locations for texture and buffer
@@ -934,13 +948,13 @@
                      layer < range.baseArrayLayer + range.layerCount; ++layer) {
                     if (clearValue == TextureBase::ClearValue::Zero &&
                         IsSubresourceContentInitialized(
-                            SubresourceRange::SingleSubresource(level, layer))) {
+                            SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                         // Skip lazy clears if already initialized.
                         continue;
                     }
 
                     D3D12_TEXTURE_COPY_LOCATION textureLocation =
-                        ComputeTextureCopyLocationForTexture(this, level, layer);
+                        ComputeTextureCopyLocationForTexture(this, level, layer, Aspect::Color);
                     for (uint32_t i = 0; i < copySplit.count; ++i) {
                         Texture2DCopySplit::CopyInfo& info = copySplit.copies[i];
 
diff --git a/src/dawn_native/d3d12/UtilsD3D12.cpp b/src/dawn_native/d3d12/UtilsD3D12.cpp
index 33a1de7..97de204 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn_native/d3d12/UtilsD3D12.cpp
@@ -64,11 +64,12 @@
 
     D3D12_TEXTURE_COPY_LOCATION ComputeTextureCopyLocationForTexture(const Texture* texture,
                                                                      uint32_t level,
-                                                                     uint32_t slice) {
+                                                                     uint32_t slice,
+                                                                     Aspect aspect) {
         D3D12_TEXTURE_COPY_LOCATION copyLocation;
         copyLocation.pResource = texture->GetD3D12Resource();
         copyLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
-        copyLocation.SubresourceIndex = texture->GetSubresourceIndex(level, slice);
+        copyLocation.SubresourceIndex = texture->GetSubresourceIndex(level, slice, aspect);
 
         return copyLocation;
     }
diff --git a/src/dawn_native/d3d12/UtilsD3D12.h b/src/dawn_native/d3d12/UtilsD3D12.h
index 90842d7..97bf74c 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.h
+++ b/src/dawn_native/d3d12/UtilsD3D12.h
@@ -29,7 +29,8 @@
 
     D3D12_TEXTURE_COPY_LOCATION ComputeTextureCopyLocationForTexture(const Texture* texture,
                                                                      uint32_t level,
-                                                                     uint32_t slice);
+                                                                     uint32_t slice,
+                                                                     Aspect aspect);
 
     D3D12_TEXTURE_COPY_LOCATION ComputeBufferLocationForCopyTextureRegion(
         const Texture* texture,
diff --git a/src/dawn_native/metal/QueueMTL.mm b/src/dawn_native/metal/QueueMTL.mm
index a4c9caa..237750c 100644
--- a/src/dawn_native/metal/QueueMTL.mm
+++ b/src/dawn_native/metal/QueueMTL.mm
@@ -115,7 +115,7 @@
         textureCopy.texture = destination->texture;
         textureCopy.mipLevel = destination->mipLevel;
         textureCopy.origin = destination->origin;
-        textureCopy.aspect = destination->aspect;
+        textureCopy.aspect = ConvertAspect(destination->texture->GetFormat(), destination->aspect);
 
         return ToBackend(GetDevice())
             ->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, &textureCopy,
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 454a14b..da42d94 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -18,8 +18,10 @@
 #include "common/Math.h"
 #include "common/Platform.h"
 #include "dawn_native/DynamicUploader.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/metal/DeviceMTL.h"
 #include "dawn_native/metal/StagingBufferMTL.h"
+#include "dawn_native/metal/UtilsMetal.h"
 
 #include <CoreVideo/CVPixelBuffer.h>
 
@@ -353,7 +355,7 @@
                                                                  plane:plane];
         [mtlDesc release];
 
-        SetIsSubresourceContentInitialized(descriptor->isCleared, {0, 1, 0, 1});
+        SetIsSubresourceContentInitialized(descriptor->isCleared, GetAllSubresources());
     }
 
     Texture::~Texture() {
@@ -393,8 +395,8 @@
                     for (uint32_t arrayLayer = range.baseArrayLayer;
                          arrayLayer < range.baseArrayLayer + range.layerCount; arrayLayer++) {
                         if (clearValue == TextureBase::ClearValue::Zero &&
-                            IsSubresourceContentInitialized(
-                                SubresourceRange::SingleSubresource(level, arrayLayer))) {
+                            IsSubresourceContentInitialized(SubresourceRange::SingleMipAndLayer(
+                                level, arrayLayer, range.aspects))) {
                             // Skip lazy clears if already initialized.
                             continue;
                         }
@@ -402,18 +404,34 @@
                         MTLRenderPassDescriptor* descriptor =
                             [MTLRenderPassDescriptor renderPassDescriptor];
 
-                        if (GetFormat().HasDepth()) {
-                            descriptor.depthAttachment.texture = GetMTLTexture();
-                            descriptor.depthAttachment.loadAction = MTLLoadActionClear;
-                            descriptor.depthAttachment.storeAction = MTLStoreActionStore;
-                            descriptor.depthAttachment.clearDepth = dClearColor;
-                        }
-                        if (GetFormat().HasStencil()) {
-                            descriptor.stencilAttachment.texture = GetMTLTexture();
-                            descriptor.stencilAttachment.loadAction = MTLLoadActionClear;
-                            descriptor.stencilAttachment.storeAction = MTLStoreActionStore;
-                            descriptor.stencilAttachment.clearStencil =
-                                static_cast<uint32_t>(clearColor);
+                        // At least one aspect needs clearing. Iterate the aspects individually to
+                        // determine which to clear.
+                        for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                            if (clearValue == TextureBase::ClearValue::Zero &&
+                                IsSubresourceContentInitialized(SubresourceRange::SingleMipAndLayer(
+                                    level, arrayLayer, aspect))) {
+                                // Skip lazy clears if already initialized.
+                                continue;
+                            }
+
+                            switch (aspect) {
+                                case Aspect::Depth:
+                                    descriptor.depthAttachment.texture = GetMTLTexture();
+                                    descriptor.depthAttachment.loadAction = MTLLoadActionClear;
+                                    descriptor.depthAttachment.storeAction = MTLStoreActionStore;
+                                    descriptor.depthAttachment.clearDepth = dClearColor;
+                                    break;
+                                case Aspect::Stencil:
+                                    descriptor.stencilAttachment.texture = GetMTLTexture();
+                                    descriptor.stencilAttachment.loadAction = MTLLoadActionClear;
+                                    descriptor.stencilAttachment.storeAction = MTLStoreActionStore;
+                                    descriptor.stencilAttachment.clearStencil =
+                                        static_cast<uint32_t>(clearColor);
+                                    break;
+                                default:
+                                    UNREACHABLE();
+                                    break;
+                            }
                         }
 
                         commandContext->BeginRender(descriptor);
@@ -433,8 +451,8 @@
                     for (uint32_t arrayLayer = range.baseArrayLayer;
                          arrayLayer < range.baseArrayLayer + range.layerCount; arrayLayer++) {
                         if (clearValue == TextureBase::ClearValue::Zero &&
-                            IsSubresourceContentInitialized(
-                                SubresourceRange::SingleSubresource(level, arrayLayer))) {
+                            IsSubresourceContentInitialized(SubresourceRange::SingleMipAndLayer(
+                                level, arrayLayer, Aspect::Color))) {
                             // Skip lazy clears if already initialized.
                             continue;
                         }
@@ -499,34 +517,21 @@
             id<MTLBuffer> uploadBuffer = ToBackend(uploadHandle.stagingBuffer)->GetBufferHandle();
 
             // Encode a buffer to texture copy to clear each subresource.
-            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
-                 ++level) {
-                Extent3D virtualSize = GetMipLevelVirtualSize(level);
+            for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                for (uint32_t level = range.baseMipLevel;
+                     level < range.baseMipLevel + range.levelCount; ++level) {
+                    Extent3D virtualSize = GetMipLevelVirtualSize(level);
 
-                for (uint32_t arrayLayer = range.baseArrayLayer;
-                     arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) {
-                    if (clearValue == TextureBase::ClearValue::Zero &&
-                        IsSubresourceContentInitialized(
-                            SubresourceRange::SingleSubresource(level, arrayLayer))) {
-                        // Skip lazy clears if already initialized.
-                        continue;
-                    }
+                    for (uint32_t arrayLayer = range.baseArrayLayer;
+                         arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) {
+                        if (clearValue == TextureBase::ClearValue::Zero &&
+                            IsSubresourceContentInitialized(
+                                SubresourceRange::SingleMipAndLayer(level, arrayLayer, aspect))) {
+                            // Skip lazy clears if already initialized.
+                            continue;
+                        }
 
-                    // If the texture’s pixel format is a combined depth/stencil format, then
-                    // options must be set to either blit the depth attachment portion or blit the
-                    // stencil attachment portion.
-                    std::array<MTLBlitOption, 3> blitOptions = {
-                        MTLBlitOptionNone, MTLBlitOptionDepthFromDepthStencil,
-                        MTLBlitOptionStencilFromDepthStencil};
-
-                    auto blitOptionStart = blitOptions.begin();
-                    auto blitOptionEnd = blitOptionStart + 1;
-                    if (GetFormat().format == wgpu::TextureFormat::Depth24PlusStencil8) {
-                        blitOptionStart = blitOptions.begin() + 1;
-                        blitOptionEnd = blitOptionStart + 2;
-                    }
-
-                    for (auto it = blitOptionStart; it != blitOptionEnd; ++it) {
+                        MTLBlitOption blitOption = ComputeMTLBlitOption(GetFormat(), aspect);
                         [encoder copyFromBuffer:uploadBuffer
                                    sourceOffset:uploadHandle.startOffset
                               sourceBytesPerRow:largestMipBytesPerRow
@@ -537,7 +542,7 @@
                                destinationSlice:arrayLayer
                                destinationLevel:level
                               destinationOrigin:MTLOriginMake(0, 0, 0)
-                                        options:(*it)];
+                                        options:blitOption];
                     }
                 }
             }
diff --git a/src/dawn_native/metal/UtilsMetal.h b/src/dawn_native/metal/UtilsMetal.h
index f7d514d..d7c0a70 100644
--- a/src/dawn_native/metal/UtilsMetal.h
+++ b/src/dawn_native/metal/UtilsMetal.h
@@ -53,7 +53,7 @@
                                              const TextureCopy& dst,
                                              const Extent3D& size);
 
-    MTLBlitOption ComputeMTLBlitOption(const Format& format, wgpu::TextureAspect aspect);
+    MTLBlitOption ComputeMTLBlitOption(const Format& format, Aspect aspect);
 
 }}  // namespace dawn_native::metal
 
diff --git a/src/dawn_native/metal/UtilsMetal.mm b/src/dawn_native/metal/UtilsMetal.mm
index 5f50a0f..3b8f64c 100644
--- a/src/dawn_native/metal/UtilsMetal.mm
+++ b/src/dawn_native/metal/UtilsMetal.mm
@@ -164,15 +164,18 @@
         }
     }
 
-    MTLBlitOption ComputeMTLBlitOption(const Format& format, wgpu::TextureAspect aspect) {
+    MTLBlitOption ComputeMTLBlitOption(const Format& format, Aspect aspect) {
+        ASSERT(HasOneBit(aspect));
+        ASSERT(format.aspects & aspect);
+
         constexpr Aspect kDepthStencil = Aspect::Depth | Aspect::Stencil;
         if ((format.aspects & kDepthStencil) == kDepthStencil) {
             // We only provide a blit option if the format has both depth and stencil.
             // It is invalid to provide a blit option otherwise.
             switch (aspect) {
-                case wgpu::TextureAspect::DepthOnly:
+                case Aspect::Depth:
                     return MTLBlitOptionDepthFromDepthStencil;
-                case wgpu::TextureAspect::StencilOnly:
+                case Aspect::Stencil:
                     return MTLBlitOptionStencilFromDepthStencil;
                 default:
                     UNREACHABLE();
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index c15b943..695e939 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -525,8 +525,8 @@
                     buffer->EnsureDataInitialized();
 
                     ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    SubresourceRange subresources = {dst.mipLevel, 1, dst.origin.z,
-                                                     copy->copySize.depth};
+                    SubresourceRange subresources =
+                        GetSubresourcesAffectedByCopy(dst, copy->copySize);
                     if (IsCompleteSubresourceCopiedTo(texture, copySize, dst.mipLevel)) {
                         texture->SetIsSubresourceContentInitialized(true, subresources);
                     } else {
@@ -618,8 +618,8 @@
                     buffer->EnsureDataInitializedAsDestination(copy);
 
                     ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    SubresourceRange subresources = {src.mipLevel, 1, src.origin.z,
-                                                     copy->copySize.depth};
+                    SubresourceRange subresources =
+                        GetSubresourcesAffectedByCopy(src, copy->copySize);
                     texture->EnsureSubresourceContentInitialized(subresources);
                     // The only way to move data from a texture to a buffer in GL is via
                     // glReadPixels with a pack buffer. Create a temporary FBO for the copy.
@@ -699,10 +699,9 @@
                     Extent3D copySize = ComputeTextureCopyExtent(dst, copy->copySize);
                     Texture* srcTexture = ToBackend(src.texture.Get());
                     Texture* dstTexture = ToBackend(dst.texture.Get());
-                    SubresourceRange srcRange = {src.mipLevel, 1, src.origin.z,
-                                                 copy->copySize.depth};
-                    SubresourceRange dstRange = {dst.mipLevel, 1, dst.origin.z,
-                                                 copy->copySize.depth};
+
+                    SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
+                    SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
                     srcTexture->EnsureSubresourceContentInitialized(srcRange);
                     if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel)) {
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 0c4ea27..7cfb05f 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -17,6 +17,7 @@
 #include "common/Assert.h"
 #include "common/Constants.h"
 #include "common/Math.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/opengl/BufferGL.h"
 #include "dawn_native/opengl/DeviceGL.h"
 #include "dawn_native/opengl/UtilsGL.h"
@@ -199,25 +200,25 @@
         float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;
 
         if (GetFormat().isRenderable) {
-            if (GetFormat().HasDepthOrStencil()) {
-                bool doDepthClear = GetFormat().HasDepth();
-                bool doStencilClear = GetFormat().HasStencil();
+            if ((range.aspects & (Aspect::Depth | Aspect::Stencil)) != 0) {
                 GLfloat depth = fClearColor;
                 GLint stencil = clearColor;
-                if (doDepthClear) {
+                if (range.aspects & Aspect::Depth) {
                     gl.DepthMask(GL_TRUE);
                 }
-                if (doStencilClear) {
+                if (range.aspects & Aspect::Stencil) {
                     gl.StencilMask(GetStencilMaskFromStencilFormat(GetFormat().format));
                 }
 
-                auto DoClear = [&]() {
-                    if (doDepthClear && doStencilClear) {
+                auto DoClear = [&](Aspect aspects) {
+                    if (aspects == (Aspect::Depth | Aspect::Stencil)) {
                         gl.ClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil);
-                    } else if (doDepthClear) {
+                    } else if (aspects == Aspect::Depth) {
                         gl.ClearBufferfv(GL_DEPTH, 0, &depth);
-                    } else if (doStencilClear) {
+                    } else if (aspects == Aspect::Stencil) {
                         gl.ClearBufferiv(GL_STENCIL, 0, &stencil);
+                    } else {
+                        UNREACHABLE();
                     }
                 };
 
@@ -230,23 +231,42 @@
                     switch (GetDimension()) {
                         case wgpu::TextureDimension::e2D:
                             if (GetArrayLayers() == 1) {
-                                if (clearValue == TextureBase::ClearValue::Zero &&
-                                    IsSubresourceContentInitialized(
-                                        SubresourceRange::SingleSubresource(level, 0))) {
-                                    // Skip lazy clears if already initialized.
+                                Aspect aspectsToClear = Aspect::None;
+                                for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                                    if (clearValue == TextureBase::ClearValue::Zero &&
+                                        IsSubresourceContentInitialized(
+                                            SubresourceRange::SingleMipAndLayer(level, 0,
+                                                                                aspect))) {
+                                        // Skip lazy clears if already initialized.
+                                        continue;
+                                    }
+                                    aspectsToClear |= aspect;
+                                }
+
+                                if (aspectsToClear == Aspect::None) {
                                     continue;
                                 }
+
                                 gl.FramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
                                                         GL_DEPTH_STENCIL_ATTACHMENT, GetGLTarget(),
                                                         GetHandle(), static_cast<GLint>(level));
-                                DoClear();
+                                DoClear(aspectsToClear);
                             } else {
                                 for (uint32_t layer = range.baseArrayLayer;
                                      layer < range.baseArrayLayer + range.layerCount; ++layer) {
-                                    if (clearValue == TextureBase::ClearValue::Zero &&
-                                        IsSubresourceContentInitialized(
-                                            SubresourceRange::SingleSubresource(level, layer))) {
-                                        // Skip lazy clears if already initialized.
+                                    Aspect aspectsToClear = Aspect::None;
+                                    for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                                        if (clearValue == TextureBase::ClearValue::Zero &&
+                                            IsSubresourceContentInitialized(
+                                                SubresourceRange::SingleMipAndLayer(level, layer,
+                                                                                    aspect))) {
+                                            // Skip lazy clears if already initialized.
+                                            continue;
+                                        }
+                                        aspectsToClear |= aspect;
+                                    }
+
+                                    if (aspectsToClear == Aspect::None) {
                                         continue;
                                     }
 
@@ -254,7 +274,7 @@
                                         GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                         GetHandle(), static_cast<GLint>(level),
                                         static_cast<GLint>(layer));
-                                    DoClear();
+                                    DoClear(aspectsToClear);
                                 }
                             }
                             break;
@@ -266,6 +286,8 @@
 
                 gl.DeleteFramebuffers(1, &framebuffer);
             } else {
+                ASSERT(range.aspects == Aspect::Color);
+
                 static constexpr uint32_t MAX_TEXEL_SIZE = 16;
                 ASSERT(GetFormat().blockByteSize <= MAX_TEXEL_SIZE);
                 std::array<GLbyte, MAX_TEXEL_SIZE> clearColorData;
@@ -280,7 +302,7 @@
                          layer < range.baseArrayLayer + range.layerCount; ++layer) {
                         if (clearValue == TextureBase::ClearValue::Zero &&
                             IsSubresourceContentInitialized(
-                                SubresourceRange::SingleSubresource(level, layer))) {
+                                SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                             // Skip lazy clears if already initialized.
                             continue;
                         }
@@ -292,6 +314,8 @@
                 }
             }
         } else {
+            ASSERT(range.aspects == Aspect::Color);
+
             // TODO(natlee@microsoft.com): test compressed textures are cleared
             // create temp buffer with clear color to copy to the texture image
             ASSERT(kTextureBytesPerRowAlignment % GetFormat().blockByteSize == 0);
@@ -334,7 +358,7 @@
                         if (GetArrayLayers() == 1) {
                             if (clearValue == TextureBase::ClearValue::Zero &&
                                 IsSubresourceContentInitialized(
-                                    SubresourceRange::SingleSubresource(level, 0))) {
+                                    SubresourceRange::SingleMipAndLayer(level, 0, Aspect::Color))) {
                                 // Skip lazy clears if already initialized.
                                 continue;
                             }
@@ -346,7 +370,8 @@
                                  layer < range.baseArrayLayer + range.layerCount; ++layer) {
                                 if (clearValue == TextureBase::ClearValue::Zero &&
                                     IsSubresourceContentInitialized(
-                                        SubresourceRange::SingleSubresource(level, layer))) {
+                                        SubresourceRange::SingleMipAndLayer(level, layer,
+                                                                            Aspect::Color))) {
                                     // Skip lazy clears if already initialized.
                                     continue;
                                 }
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 3b7d016..27f92b7 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -68,7 +68,7 @@
             // TODO(jiawei.shao@intel.com): support 1D and 3D textures
             ASSERT(srcTexture->GetDimension() == wgpu::TextureDimension::e2D &&
                    dstTexture->GetDimension() == wgpu::TextureDimension::e2D);
-            region.srcSubresource.aspectMask = srcTexture->GetVkAspectMask(srcCopy.aspect);
+            region.srcSubresource.aspectMask = VulkanAspectMask(srcCopy.aspect);
             region.srcSubresource.mipLevel = srcCopy.mipLevel;
             region.srcSubresource.baseArrayLayer = srcCopy.origin.z;
             region.srcSubresource.layerCount = copySize.depth;
@@ -77,7 +77,7 @@
             region.srcOffset.y = srcCopy.origin.y;
             region.srcOffset.z = 0;
 
-            region.dstSubresource.aspectMask = dstTexture->GetVkAspectMask(dstCopy.aspect);
+            region.dstSubresource.aspectMask = VulkanAspectMask(dstCopy.aspect);
             region.dstSubresource.mipLevel = dstCopy.mipLevel;
             region.dstSubresource.baseArrayLayer = dstCopy.origin.z;
             region.dstSubresource.layerCount = copySize.depth;
@@ -472,8 +472,9 @@
                     VkImageSubresourceLayers subresource = region.imageSubresource;
 
                     ASSERT(dst.texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    SubresourceRange range = {subresource.mipLevel, 1, subresource.baseArrayLayer,
-                                              subresource.layerCount};
+                    SubresourceRange range =
+                        GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
+
                     if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize,
                                                       subresource.mipLevel)) {
                         // Since texture has been overwritten, it has been "initialized"
@@ -507,12 +508,11 @@
 
                     VkBufferImageCopy region =
                         ComputeBufferImageCopyRegion(dst, src, copy->copySize);
-                    VkImageSubresourceLayers subresource = region.imageSubresource;
 
                     ASSERT(src.texture->GetDimension() == wgpu::TextureDimension::e2D);
-                    const SubresourceRange range = {subresource.mipLevel, 1,
-                                                    subresource.baseArrayLayer,
-                                                    subresource.layerCount};
+                    SubresourceRange range =
+                        GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
+
                     ToBackend(src.texture)
                         ->EnsureSubresourceContentInitialized(recordingContext, range);
 
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 398e302..f6afcfd 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -628,8 +628,8 @@
         VkImageSubresourceLayers subresource = region.imageSubresource;
 
         ASSERT(dst->texture->GetDimension() == wgpu::TextureDimension::e2D);
-        SubresourceRange range = {subresource.mipLevel, 1, subresource.baseArrayLayer,
-                                  subresource.layerCount};
+        SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySize);
+
         if (IsCompleteSubresourceCopiedTo(dst->texture.Get(), copySize, subresource.mipLevel)) {
             // Since texture has been overwritten, it has been "initialized"
             dst->texture->SetIsSubresourceContentInitialized(true, range);
diff --git a/src/dawn_native/vulkan/QueueVk.cpp b/src/dawn_native/vulkan/QueueVk.cpp
index c7c4ad7..227ccb4 100644
--- a/src/dawn_native/vulkan/QueueVk.cpp
+++ b/src/dawn_native/vulkan/QueueVk.cpp
@@ -142,7 +142,7 @@
         textureCopy.texture = destination->texture;
         textureCopy.mipLevel = destination->mipLevel;
         textureCopy.origin = destination->origin;
-        textureCopy.aspect = destination->aspect;
+        textureCopy.aspect = ConvertAspect(destination->texture->GetFormat(), destination->aspect);
 
         return ToBackend(GetDevice())
             ->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, &textureCopy,
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 6d1291b..8c7bd3f 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -193,28 +193,6 @@
             return flags;
         }
 
-        // Computes which Vulkan texture aspects are relevant for the given Dawn format
-        VkImageAspectFlags VulkanAspectMask(const Aspect& aspects) {
-            VkImageAspectFlags flags = 0;
-            for (Aspect aspect : IterateEnumMask(aspects)) {
-                switch (aspect) {
-                    case Aspect::Color:
-                        flags |= VK_IMAGE_ASPECT_COLOR_BIT;
-                        break;
-                    case Aspect::Depth:
-                        flags |= VK_IMAGE_ASPECT_DEPTH_BIT;
-                        break;
-                    case Aspect::Stencil:
-                        flags |= VK_IMAGE_ASPECT_STENCIL_BIT;
-                        break;
-                    default:
-                        UNREACHABLE();
-                        break;
-                }
-            }
-            return flags;
-        }
-
         VkImageMemoryBarrier BuildMemoryBarrier(const Format& format,
                                                 const VkImage& image,
                                                 wgpu::TextureUsage lastUsage,
@@ -597,7 +575,7 @@
 
         // Don't clear imported texture if already cleared
         if (descriptor->isCleared) {
-            SetIsSubresourceContentInitialized(true, {0, 1, 0, 1});
+            SetIsSubresourceContentInitialized(true, GetAllSubresources());
         }
 
         // Success, acquire all the external objects.
@@ -700,7 +678,7 @@
             if (barriers->size() == transitionBarrierStart) {
                 barriers->push_back(BuildMemoryBarrier(
                     GetFormat(), mHandle, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
-                    SubresourceRange::SingleSubresource(0, 0)));
+                    SubresourceRange::SingleMipAndLayer(0, 0, GetFormat().aspects)));
             }
 
             // Transfer texture from external queue to graphics queue
@@ -714,7 +692,7 @@
             if (barriers->size() == transitionBarrierStart) {
                 barriers->push_back(BuildMemoryBarrier(
                     GetFormat(), mHandle, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
-                    SubresourceRange::SingleSubresource(0, 0)));
+                    SubresourceRange::SingleMipAndLayer(0, 0, GetFormat().aspects)));
             }
 
             // Transfer texture from graphics queue to external queue
@@ -781,24 +759,40 @@
         } else {
             for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) {
                 for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) {
-                    uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer);
+                    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 (textureUsages.subresourceUsages[index] == wgpu::TextureUsage::None) {
+                    if (usage == wgpu::TextureUsage::None) {
                         continue;
                     }
 
-                    if (CanReuseWithoutBarrier(mSubresourceLastUsages[index],
-                                               textureUsages.subresourceUsages[index])) {
+                    if (CanReuseWithoutBarrier(lastUsage, usage)) {
                         continue;
                     }
-                    imageBarriers->push_back(BuildMemoryBarrier(
-                        format, mHandle, mSubresourceLastUsages[index],
-                        textureUsages.subresourceUsages[index],
-                        SubresourceRange::SingleSubresource(mipLevel, arrayLayer)));
-                    allLastUsages |= mSubresourceLastUsages[index];
-                    allUsages |= textureUsages.subresourceUsages[index];
-                    mSubresourceLastUsages[index] = textureUsages.subresourceUsages[index];
+
+                    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)));
                 }
             }
         }
@@ -820,7 +814,6 @@
         const Format& format = GetFormat();
 
         wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
-        uint32_t subresourceCount = GetSubresourceCount();
 
         // This transitions assume it is a 2D texture
         ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
@@ -829,7 +822,9 @@
         // 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 * range.layerCount == subresourceCount;
+        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)) {
@@ -838,7 +833,7 @@
             barriers.push_back(
                 BuildMemoryBarrier(format, mHandle, mSubresourceLastUsages[0], usage, range));
             allLastUsages = mSubresourceLastUsages[0];
-            for (uint32_t i = 0; i < subresourceCount; ++i) {
+            for (uint32_t i = 0; i < GetSubresourceCount(); ++i) {
                 mSubresourceLastUsages[i] = usage;
             }
         } else {
@@ -846,17 +841,29 @@
                  layer < range.baseArrayLayer + range.layerCount; ++layer) {
                 for (uint32_t level = range.baseMipLevel;
                      level < range.baseMipLevel + range.levelCount; ++level) {
-                    uint32_t index = GetSubresourceIndex(level, layer);
+                    // 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(mSubresourceLastUsages[index], usage)) {
+                    if (CanReuseWithoutBarrier(lastUsage, usage)) {
                         continue;
                     }
 
-                    barriers.push_back(
-                        BuildMemoryBarrier(format, mHandle, mSubresourceLastUsages[index], usage,
-                                           SubresourceRange::SingleSubresource(level, layer)));
-                    allLastUsages |= mSubresourceLastUsages[index];
-                    mSubresourceLastUsages[index] = usage;
+                    allLastUsages |= lastUsage;
+
+                    for (Aspect aspect : IterateEnumMask(format.aspects)) {
+                        uint32_t index = GetSubresourceIndex(level, layer, aspect);
+                        mSubresourceLastUsages[index] = usage;
+                    }
+
+                    barriers.push_back(BuildMemoryBarrier(
+                        format, mHandle, lastUsage, usage,
+                        SubresourceRange::SingleMipAndLayer(level, layer, format.aspects)));
                 }
             }
         }
@@ -885,7 +892,6 @@
         TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, range);
         if (GetFormat().isRenderable) {
             VkImageSubresourceRange imageRange = {};
-            imageRange.aspectMask = GetVkAspectMask(wgpu::TextureAspect::All);
             imageRange.levelCount = 1;
             imageRange.layerCount = 1;
 
@@ -894,16 +900,25 @@
                 imageRange.baseMipLevel = level;
                 for (uint32_t layer = range.baseArrayLayer;
                      layer < range.baseArrayLayer + range.layerCount; ++layer) {
-                    if (clearValue == TextureBase::ClearValue::Zero &&
-                        IsSubresourceContentInitialized(
-                            SubresourceRange::SingleSubresource(level, layer))) {
-                        // Skip lazy clears if already initialized.
+                    Aspect aspects = Aspect::None;
+                    for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                        if (clearValue == TextureBase::ClearValue::Zero &&
+                            IsSubresourceContentInitialized(
+                                SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
+                            // Skip lazy clears if already initialized.
+                            continue;
+                        }
+                        aspects |= aspect;
+                    }
+
+                    if (aspects == Aspect::None) {
                         continue;
                     }
 
+                    imageRange.aspectMask = VulkanAspectMask(aspects);
                     imageRange.baseArrayLayer = layer;
 
-                    if (GetFormat().HasDepthOrStencil()) {
+                    if (aspects & (Aspect::Depth | Aspect::Stencil)) {
                         VkClearDepthStencilValue clearDepthStencilValue[1];
                         clearDepthStencilValue[0].depth = fClearColor;
                         clearDepthStencilValue[0].stencil = clearColor;
@@ -912,6 +927,7 @@
                             VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, clearDepthStencilValue, 1,
                             &imageRange);
                     } else {
+                        ASSERT(aspects == Aspect::Color);
                         VkClearColorValue clearColorValue = {
                             {fClearColor, fClearColor, fClearColor, fClearColor}};
                         device->fn.CmdClearColorImage(recordingContext->commandBuffer, GetHandle(),
@@ -943,6 +959,7 @@
             bufferCopy.offset = uploadHandle.startOffset;
             bufferCopy.bytesPerRow = bytesPerRow;
 
+            ASSERT(range.aspects == Aspect::Color);
             for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                  ++level) {
                 Extent3D copySize = GetMipLevelVirtualSize(level);
@@ -951,7 +968,7 @@
                      layer < range.baseArrayLayer + range.layerCount; ++layer) {
                     if (clearValue == TextureBase::ClearValue::Zero &&
                         IsSubresourceContentInitialized(
-                            SubresourceRange::SingleSubresource(level, layer))) {
+                            SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                         // Skip lazy clears if already initialized.
                         continue;
                     }
@@ -961,7 +978,7 @@
                     textureCopy.texture = this;
                     textureCopy.origin = {0, 0, layer};
                     textureCopy.mipLevel = level;
-                    textureCopy.aspect = wgpu::TextureAspect::All;
+                    textureCopy.aspect = GetFormat().aspects;
 
                     VkBufferImageCopy region =
                         ComputeBufferImageCopyRegion(bufferCopy, textureCopy, copySize);
@@ -1028,11 +1045,13 @@
         createInfo.format = VulkanImageFormat(device, descriptor->format);
         createInfo.components = VkComponentMapping{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G,
                                                    VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};
-        createInfo.subresourceRange.aspectMask = VulkanAspectMask(GetFormat().aspects);
-        createInfo.subresourceRange.baseMipLevel = descriptor->baseMipLevel;
-        createInfo.subresourceRange.levelCount = descriptor->mipLevelCount;
-        createInfo.subresourceRange.baseArrayLayer = descriptor->baseArrayLayer;
-        createInfo.subresourceRange.layerCount = descriptor->arrayLayerCount;
+
+        const SubresourceRange& subresources = GetSubresourceRange();
+        createInfo.subresourceRange.baseMipLevel = subresources.baseMipLevel;
+        createInfo.subresourceRange.levelCount = subresources.levelCount;
+        createInfo.subresourceRange.baseArrayLayer = subresources.baseArrayLayer;
+        createInfo.subresourceRange.layerCount = subresources.layerCount;
+        createInfo.subresourceRange.aspectMask = VulkanAspectMask(subresources.aspects);
 
         return CheckVkSuccess(
             device->fn.CreateImageView(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
diff --git a/src/dawn_native/vulkan/UtilsVulkan.cpp b/src/dawn_native/vulkan/UtilsVulkan.cpp
index b7116da..1b6b2d6 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn_native/vulkan/UtilsVulkan.cpp
@@ -15,6 +15,7 @@
 #include "dawn_native/vulkan/UtilsVulkan.h"
 
 #include "common/Assert.h"
+#include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/Format.h"
 #include "dawn_native/vulkan/Forward.h"
 #include "dawn_native/vulkan/TextureVk.h"
@@ -44,6 +45,28 @@
         }
     }
 
+    // Convert Dawn texture aspects to  Vulkan texture aspect flags
+    VkImageAspectFlags VulkanAspectMask(const Aspect& aspects) {
+        VkImageAspectFlags flags = 0;
+        for (Aspect aspect : IterateEnumMask(aspects)) {
+            switch (aspect) {
+                case Aspect::Color:
+                    flags |= VK_IMAGE_ASPECT_COLOR_BIT;
+                    break;
+                case Aspect::Depth:
+                    flags |= VK_IMAGE_ASPECT_DEPTH_BIT;
+                    break;
+                case Aspect::Stencil:
+                    flags |= VK_IMAGE_ASPECT_STENCIL_BIT;
+                    break;
+                default:
+                    UNREACHABLE();
+                    break;
+            }
+        }
+        return flags;
+    }
+
     // Vulkan SPEC requires the source/destination region specified by each element of
     // pRegions must be a region that is contained within srcImage/dstImage. Here the size of
     // the image refers to the virtual size, while Dawn validates texture copy extent with the
@@ -91,7 +114,7 @@
             dataLayout.bytesPerRow / blockInfo.blockByteSize * blockInfo.blockWidth;
         region.bufferImageHeight = dataLayout.rowsPerImage;
 
-        region.imageSubresource.aspectMask = texture->GetVkAspectMask(textureCopy.aspect);
+        region.imageSubresource.aspectMask = VulkanAspectMask(textureCopy.aspect);
         region.imageSubresource.mipLevel = textureCopy.mipLevel;
 
         switch (textureCopy.texture->GetDimension()) {
diff --git a/src/dawn_native/vulkan/UtilsVulkan.h b/src/dawn_native/vulkan/UtilsVulkan.h
index 36ed8fc..e57e3f4 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.h
+++ b/src/dawn_native/vulkan/UtilsVulkan.h
@@ -88,6 +88,8 @@
 
     VkCompareOp ToVulkanCompareOp(wgpu::CompareFunction op);
 
+    VkImageAspectFlags VulkanAspectMask(const Aspect& aspects);
+
     Extent3D ComputeTextureCopyExtent(const TextureCopy& textureCopy, const Extent3D& copySize);
 
     VkBufferImageCopy ComputeBufferImageCopyRegion(const BufferCopy& bufferCopy,
@@ -99,4 +101,4 @@
 
 }}  // namespace dawn_native::vulkan
 
-#endif  // DAWNNATIVE_VULKAN_UTILSVULKAN_H_
\ No newline at end of file
+#endif  // DAWNNATIVE_VULKAN_UTILSVULKAN_H_
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index d8bf6b4..a57baeb 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -182,11 +182,13 @@
     DAWN_NATIVE_EXPORT size_t GetDeprecationWarningCountForTesting(WGPUDevice device);
 
     //  Query if texture has been initialized
-    DAWN_NATIVE_EXPORT bool IsTextureSubresourceInitialized(WGPUTexture texture,
-                                                            uint32_t baseMipLevel,
-                                                            uint32_t levelCount,
-                                                            uint32_t baseArrayLayer,
-                                                            uint32_t layerCount);
+    DAWN_NATIVE_EXPORT bool IsTextureSubresourceInitialized(
+        WGPUTexture texture,
+        uint32_t baseMipLevel,
+        uint32_t levelCount,
+        uint32_t baseArrayLayer,
+        uint32_t layerCount,
+        WGPUTextureAspect aspect = WGPUTextureAspect_All);
 
     // Backdoor to get the order of the ProcMap for testing
     DAWN_NATIVE_EXPORT std::vector<const char*> GetProcMapNamesForTesting();
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index 41bb49b..b4646f5 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -62,9 +62,9 @@
         descriptor.dimension = wgpu::TextureViewDimension::e2D;
         return descriptor;
     }
-    wgpu::RenderPipeline CreatePipelineForTest() {
+    wgpu::RenderPipeline CreatePipelineForTest(float depth = 0.f) {
         utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
-        pipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest();
+        pipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest(depth);
         const char* fs =
             R"(#version 450
             layout(location = 0) out vec4 fragColor;
@@ -80,8 +80,8 @@
 
         return device.CreateRenderPipeline(&pipelineDescriptor);
     }
-    wgpu::ShaderModule CreateBasicVertexShaderForTest() {
-        return utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450
+    wgpu::ShaderModule CreateBasicVertexShaderForTest(float depth = 0.f) {
+        std::string source = R"(#version 450
             const vec2 pos[6] = vec2[6](vec2(-1.0f, -1.0f),
                                     vec2(-1.0f,  1.0f),
                                     vec2( 1.0f, -1.0f),
@@ -91,8 +91,10 @@
                                     );
 
             void main() {
-                gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
-            })");
+                gl_Position = vec4(pos[gl_VertexIndex], )" +
+                             std::to_string(depth) + R"(, 1.0);
+            })";
+        return utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, source.c_str());
     }
     wgpu::ShaderModule CreateSampledTextureFragmentShaderForTest() {
         return utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment,
@@ -574,6 +576,239 @@
     EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(srcTexture.Get(), 0, 1, 0, 1));
 }
 
+// Test that clear state is tracked independently for depth/stencil textures.
+TEST_P(TextureZeroInitTest, IndependentDepthStencilLoadAfterDiscard) {
+    // TODO(enga): Figure out why this fails on Metal Intel.
+    DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
+
+    wgpu::TextureDescriptor depthStencilDescriptor = CreateTextureDescriptor(
+        1, 1, wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc,
+        kDepthStencilFormat);
+    wgpu::Texture depthStencilTexture = device.CreateTexture(&depthStencilDescriptor);
+
+    // Uninitialize only depth
+    {
+        // Clear the stencil to 2 and discard the depth
+        {
+            utils::ComboRenderPassDescriptor renderPassDescriptor({},
+                                                                  depthStencilTexture.CreateView());
+            renderPassDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Clear;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.clearStencil = 2;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+            pass.EndPass();
+            wgpu::CommandBuffer commandBuffer = encoder.Finish();
+            EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+        }
+
+        // "all" subresources are not initialized; Depth is not initialized
+        EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(
+                             depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_All));
+        EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(
+                             depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_DepthOnly));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_StencilOnly));
+
+        // Now load both depth and stencil. Depth should be cleared and stencil should stay the same
+        // at 2.
+        {
+            wgpu::TextureDescriptor colorDescriptor =
+                CreateTextureDescriptor(1, 1,
+                                        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
+                                            wgpu::TextureUsage::OutputAttachment,
+                                        kColorFormat);
+            wgpu::Texture colorTexture = device.CreateTexture(&colorDescriptor);
+
+            utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()},
+                                                                  depthStencilTexture.CreateView());
+            renderPassDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+            pass.SetPipeline(CreatePipelineForTest());
+            pass.SetStencilReference(2);
+            pass.Draw(6);
+            pass.EndPass();
+            wgpu::CommandBuffer commandBuffer = encoder.Finish();
+            // No lazy clear because depth will be cleared with a loadOp
+            EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+            // Expect the texture to be red because the depth and stencil tests passed. Depth was 0
+            // and stencil was 2.
+            std::vector<RGBA8> expected(kSize * kSize, {255, 0, 0, 255});
+            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), colorTexture, 0, 0, kSize, kSize, 0, 0);
+        }
+
+        // Everything is initialized now
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_All));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_DepthOnly));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_StencilOnly));
+
+        // TODO(crbug.com/dawn/439): Implement stencil copies on other platforms
+        if (IsMetal() || IsVulkan()) {
+            // Check by copy that the stencil data is 2.
+            EXPECT_LAZY_CLEAR(0u, EXPECT_TEXTURE_EQ(uint8_t(2), depthStencilTexture, 0, 0, 0, 0,
+                                                    wgpu::TextureAspect::StencilOnly));
+        }
+    }
+
+    // Uninitialize only stencil
+    {
+        // Clear the depth to 0.7 and discard the stencil.
+        {
+            utils::ComboRenderPassDescriptor renderPassDescriptor({},
+                                                                  depthStencilTexture.CreateView());
+            renderPassDescriptor.cDepthStencilAttachmentInfo.clearDepth = 0.7;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Clear;
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+            pass.EndPass();
+            wgpu::CommandBuffer commandBuffer = encoder.Finish();
+            EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+        }
+
+        // "all" subresources are not initialized; Stencil is not initialized
+        EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(
+                             depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_All));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_DepthOnly));
+        EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(
+                             depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_StencilOnly));
+
+        // Now load both depth and stencil. Stencil should be cleared and depth should stay the same
+        // at 0.7.
+        {
+            wgpu::TextureDescriptor colorDescriptor =
+                CreateTextureDescriptor(1, 1,
+                                        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
+                                            wgpu::TextureUsage::OutputAttachment,
+                                        kColorFormat);
+            wgpu::Texture colorTexture = device.CreateTexture(&colorDescriptor);
+
+            utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()},
+                                                                  depthStencilTexture.CreateView());
+            renderPassDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
+            renderPassDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+            pass.SetPipeline(CreatePipelineForTest(0.7));
+            pass.Draw(6);
+            pass.EndPass();
+            wgpu::CommandBuffer commandBuffer = encoder.Finish();
+            // No lazy clear because stencil will clear using a loadOp.
+            EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+            // Expect the texture to be red because both the depth a stencil tests passed.
+            // Depth was 0.7 and stencil was 0
+            std::vector<RGBA8> expected(kSize * kSize, {255, 0, 0, 255});
+            EXPECT_TEXTURE_RGBA8_EQ(expected.data(), colorTexture, 0, 0, kSize, kSize, 0, 0);
+        }
+
+        // Everything is initialized now
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_All));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_DepthOnly));
+        EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(
+                            depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_StencilOnly));
+
+        // TODO(crbug.com/dawn/439): Implement stencil copies on other platforms
+        if (IsMetal() || IsVulkan()) {
+            // Check by copy that the stencil data is 0.
+            EXPECT_LAZY_CLEAR(0u, EXPECT_TEXTURE_EQ(uint8_t(0), depthStencilTexture, 0, 0, 0, 0,
+                                                    wgpu::TextureAspect::StencilOnly));
+        }
+    }
+}
+
+// Test that clear state is tracked independently for depth/stencil textures.
+// Lazy clear of the stencil aspect via copy should not touch depth.
+TEST_P(TextureZeroInitTest, IndependentDepthStencilCopyAfterDiscard) {
+    // TODO(crbug.com/dawn/439): Implement stencil copies on other platforms
+    DAWN_SKIP_TEST_IF(!(IsMetal() || IsVulkan()));
+
+    // TODO(enga): Figure out why this fails on Metal Intel.
+    DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
+
+    wgpu::TextureDescriptor depthStencilDescriptor = CreateTextureDescriptor(
+        1, 1, wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc,
+        kDepthStencilFormat);
+    wgpu::Texture depthStencilTexture = device.CreateTexture(&depthStencilDescriptor);
+
+    // Clear the depth to 0.3 and discard the stencil.
+    {
+        utils::ComboRenderPassDescriptor renderPassDescriptor({}, depthStencilTexture.CreateView());
+        renderPassDescriptor.cDepthStencilAttachmentInfo.clearDepth = 0.3;
+        renderPassDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
+        renderPassDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Clear;
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.EndPass();
+        wgpu::CommandBuffer commandBuffer = encoder.Finish();
+        EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+    }
+
+    // "all" subresources are not initialized; Stencil is not initialized
+    EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(depthStencilTexture.Get(), 0, 1,
+                                                                  0, 1, WGPUTextureAspect_All));
+    EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(depthStencilTexture.Get(), 0, 1, 0,
+                                                                 1, WGPUTextureAspect_DepthOnly));
+    EXPECT_EQ(false, dawn_native::IsTextureSubresourceInitialized(
+                         depthStencilTexture.Get(), 0, 1, 0, 1, WGPUTextureAspect_StencilOnly));
+
+    // Check by copy that the stencil data is lazily cleared to 0.
+    EXPECT_LAZY_CLEAR(1u, EXPECT_TEXTURE_EQ(uint8_t(0), depthStencilTexture, 0, 0, 0, 0,
+                                            wgpu::TextureAspect::StencilOnly));
+
+    // Everything is initialized now
+    EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(depthStencilTexture.Get(), 0, 1, 0,
+                                                                 1, WGPUTextureAspect_All));
+    EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(depthStencilTexture.Get(), 0, 1, 0,
+                                                                 1, WGPUTextureAspect_DepthOnly));
+    EXPECT_EQ(true, dawn_native::IsTextureSubresourceInitialized(depthStencilTexture.Get(), 0, 1, 0,
+                                                                 1, WGPUTextureAspect_StencilOnly));
+
+    // Now load both depth and stencil. Stencil should be cleared and depth should stay the same
+    // at 0.3.
+    {
+        wgpu::TextureDescriptor colorDescriptor =
+            CreateTextureDescriptor(1, 1,
+                                    wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
+                                        wgpu::TextureUsage::OutputAttachment,
+                                    kColorFormat);
+        wgpu::Texture colorTexture = device.CreateTexture(&colorDescriptor);
+
+        utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()},
+                                                              depthStencilTexture.CreateView());
+        renderPassDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
+        renderPassDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
+        pass.SetPipeline(CreatePipelineForTest(0.3));
+        pass.Draw(6);
+        pass.EndPass();
+        wgpu::CommandBuffer commandBuffer = encoder.Finish();
+        // No lazy clear because stencil will clear using a loadOp.
+        EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+        // Expect the texture to be red because both the depth a stencil tests passed.
+        // Depth was 0.3 and stencil was 0
+        std::vector<RGBA8> expected(kSize * kSize, {255, 0, 0, 255});
+        EXPECT_TEXTURE_RGBA8_EQ(expected.data(), colorTexture, 0, 0, kSize, kSize, 0, 0);
+    }
+}
+
 // This tests the color attachments clear to 0s
 TEST_P(TextureZeroInitTest, ColorAttachmentsClear) {
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
diff --git a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index b1658ea..277c12e 100644
--- a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -198,7 +198,7 @@
         }
     }
 
-    // Depth and stencil storeOps must match
+    // Depth and stencil storeOps can be different
     TEST_F(RenderPassDescriptorValidationTest, DepthStencilStoreOpMismatch) {
         constexpr uint32_t kArrayLayers = 1;
         constexpr uint32_t kLevelCount = 1;
@@ -223,15 +223,7 @@
         wgpu::TextureView colorTextureView = colorTexture.CreateView(&descriptor);
         wgpu::TextureView depthStencilView = depthStencilTexture.CreateView(&descriptor);
 
-        // StoreOps mismatch causing the render pass to error
-        {
-            utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
-            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
-            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Clear;
-            AssertBeginRenderPassError(&renderPass);
-        }
-
-        // StoreOps match so render pass is a success
+        // Base case: StoreOps match so render pass is a success
         {
             utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
             renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
@@ -239,13 +231,21 @@
             AssertBeginRenderPassSuccess(&renderPass);
         }
 
-        // StoreOps match so render pass is a success
+        // Base case: StoreOps match so render pass is a success
         {
             utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
             renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Clear;
             renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Clear;
             AssertBeginRenderPassSuccess(&renderPass);
         }
+
+        // StoreOps mismatch still is a success
+        {
+            utils::ComboRenderPassDescriptor renderPass({}, depthStencilView);
+            renderPass.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
+            renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Clear;
+            AssertBeginRenderPassSuccess(&renderPass);
+        }
     }
 
     // Currently only texture views with arrayLayerCount == 1 are allowed to be color and depth