Add texture aspect to texture copy view and validation tests

Bug: dawn:439
Change-Id: I0ca283f58fe2b63ac3a8c468f8ea1bb2d300856f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24683
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index 109eeae..e2cd79a 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1592,7 +1592,8 @@
             {"name": "texture", "type": "texture"},
             {"name": "mip level", "type": "uint32_t", "default": "0"},
             {"name": "array layer", "type": "uint32_t", "default": "0"},
-            {"name": "origin", "type": "origin 3D"}
+            {"name": "origin", "type": "origin 3D"},
+            {"name": "aspect", "type": "texture aspect", "default": "all"}
         ]
     },
     "texture data layout": {
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 1a387b7..71a66fc 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -100,6 +100,12 @@
                 return DAWN_VALIDATION_ERROR("Source and destination texture formats must match.");
             }
 
+            if (src.aspect != wgpu::TextureAspect::All || dst.aspect != wgpu::TextureAspect::All) {
+                // Metal cannot select a single aspect for texture-to-texture copies
+                return DAWN_VALIDATION_ERROR(
+                    "Texture aspect must be \"all\" for texture to texture copies");
+            }
+
             if (src.texture->GetFormat().HasDepthOrStencil()) {
                 // D3D12 requires entire subresource to be copied when using CopyTextureRegion is
                 // used with depth/stencil.
@@ -119,6 +125,55 @@
             return {};
         }
 
+        MaybeError ValidateTextureToBufferCopyRestrictions(const TextureCopyView& src) {
+            const Format& format = src.texture->GetFormat();
+
+            bool depthSelected = false;
+            switch (src.aspect) {
+                case wgpu::TextureAspect::All:
+                    switch (format.aspects) {
+                        case Aspect::Color:
+                        case Aspect::Stencil:
+                            break;
+                        case Aspect::Depth:
+                            depthSelected = true;
+                            break;
+                        default:
+                            return DAWN_VALIDATION_ERROR(
+                                "A single aspect must be selected for multi planar formats in "
+                                "texture to buffer copies");
+                    }
+                    break;
+                case wgpu::TextureAspect::DepthOnly:
+                    ASSERT(format.aspects & Aspect::Depth);
+                    depthSelected = true;
+                    break;
+                case wgpu::TextureAspect::StencilOnly:
+                    ASSERT(format.aspects & Aspect::Stencil);
+                    break;
+                default:
+                    UNREACHABLE();
+            }
+
+            if (depthSelected) {
+                switch (format.format) {
+                    case wgpu::TextureFormat::Depth24Plus:
+                    case wgpu::TextureFormat::Depth24PlusStencil8:
+                        return DAWN_VALIDATION_ERROR(
+                            "The depth aspect of depth24plus texture cannot be selected in a "
+                            "texture to buffer copy");
+                        break;
+                    case wgpu::TextureFormat::Depth32Float:
+                        break;
+                    default:
+                        UNREACHABLE();
+                        break;
+                }
+            }
+
+            return {};
+        }
+
         MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage) {
             ASSERT(wgpu::HasZeroOrOneBits(usage));
             if (!(buffer->GetUsage() & usage)) {
@@ -661,8 +716,11 @@
                 // copyExtent.height by blockHeight while the divisibility conditions are
                 // checked in validating texture copy range.
                 DAWN_TRY(ValidateTextureCopyRange(*destination, *copySize));
-                DAWN_TRY(ValidateLinearTextureData(source->layout, source->buffer->GetSize(),
-                                                   destination->texture->GetFormat(), *copySize));
+                DAWN_TRY(ValidateBufferToTextureCopyRestrictions(*destination));
+                DAWN_TRY(ValidateLinearTextureData(
+                    source->layout, source->buffer->GetSize(),
+                    destination->texture->GetFormat().GetTexelBlockInfo(destination->aspect),
+                    *copySize));
 
                 mTopLevelBuffers.insert(source->buffer);
                 mTopLevelTextures.insert(destination->texture);
@@ -718,9 +776,10 @@
                 // copyExtent.height by blockHeight while the divisibility conditions are
                 // checked in validating texture copy range.
                 DAWN_TRY(ValidateTextureCopyRange(*source, *copySize));
-                DAWN_TRY(ValidateLinearTextureData(destination->layout,
-                                                   destination->buffer->GetSize(),
-                                                   source->texture->GetFormat(), *copySize));
+                DAWN_TRY(ValidateTextureToBufferCopyRestrictions(*source));
+                DAWN_TRY(ValidateLinearTextureData(
+                    destination->layout, destination->buffer->GetSize(),
+                    source->texture->GetFormat().GetTexelBlockInfo(source->aspect), *copySize));
 
                 mTopLevelTextures.insert(source->texture);
                 mTopLevelBuffers.insert(destination->buffer);
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 11a363a..095cf61 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -370,7 +370,7 @@
                static_cast<uint64_t>(maxStart);
     }
 
-    uint32_t ComputeRequiredBytesInCopy(const Format& textureFormat,
+    uint32_t ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
                                         const Extent3D& copySize,
                                         uint32_t bytesPerRow,
                                         uint32_t rowsPerImage) {
@@ -386,11 +386,11 @@
         ASSERT(copySize.height >= 1);
         ASSERT(copySize.depth >= 1);
 
-        uint64_t texelBlockRowsPerImage = rowsPerImage / textureFormat.blockHeight;
+        uint64_t texelBlockRowsPerImage = rowsPerImage / blockInfo.blockHeight;
         uint64_t bytesPerImage = bytesPerRow * texelBlockRowsPerImage;
         uint64_t bytesInLastSlice =
-            bytesPerRow * (copySize.height / textureFormat.blockHeight - 1) +
-            (copySize.width / textureFormat.blockWidth * textureFormat.blockByteSize);
+            bytesPerRow * (copySize.height / blockInfo.blockHeight - 1) +
+            (copySize.width / blockInfo.blockWidth * blockInfo.blockByteSize);
         return bytesPerImage * (copySize.depth - 1) + bytesInLastSlice;
     }
 
@@ -408,15 +408,15 @@
 
     MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
                                          uint64_t byteSize,
-                                         const Format& format,
+                                         const TexelBlockInfo& blockInfo,
                                          const Extent3D& copyExtent) {
         // Validation for the texel block alignments:
-        if (layout.rowsPerImage % format.blockHeight != 0) {
+        if (layout.rowsPerImage % blockInfo.blockHeight != 0) {
             return DAWN_VALIDATION_ERROR(
                 "rowsPerImage must be a multiple of compressed texture format block height");
         }
 
-        if (layout.offset % format.blockByteSize != 0) {
+        if (layout.offset % blockInfo.blockByteSize != 0) {
             return DAWN_VALIDATION_ERROR("Offset must be a multiple of the texel or block size");
         }
 
@@ -429,8 +429,8 @@
         // because the divisibility conditions are necessary for the algorithm to be valid.
         // TODO(tommek@google.com): to match the spec this should only be checked when
         // copyExtent.depth > 1.
-        uint32_t requiredBytesInCopy =
-            ComputeRequiredBytesInCopy(format, copyExtent, layout.bytesPerRow, layout.rowsPerImage);
+        uint32_t requiredBytesInCopy = ComputeRequiredBytesInCopy(
+            blockInfo, copyExtent, layout.bytesPerRow, layout.rowsPerImage);
 
         bool fitsInData =
             layout.offset <= byteSize && (requiredBytesInCopy <= (byteSize - layout.offset));
@@ -440,7 +440,8 @@
         }
 
         // Validation for other members in layout:
-        if (layout.bytesPerRow < copyExtent.width / format.blockWidth * format.blockByteSize) {
+        if (layout.bytesPerRow <
+            copyExtent.width / blockInfo.blockWidth * blockInfo.blockByteSize) {
             return DAWN_VALIDATION_ERROR(
                 "bytesPerRow must not be less than the number of bytes per row");
         }
@@ -482,6 +483,26 @@
                 "Offset.y must be a multiple of compressed texture format block height");
         }
 
+        switch (textureCopy.aspect) {
+            case wgpu::TextureAspect::All:
+                break;
+            case wgpu::TextureAspect::DepthOnly:
+                if ((textureCopy.texture->GetFormat().aspects & Aspect::Depth) == 0) {
+                    return DAWN_VALIDATION_ERROR(
+                        "Texture does not have depth aspect for texture copy");
+                }
+                break;
+            case wgpu::TextureAspect::StencilOnly:
+                if ((textureCopy.texture->GetFormat().aspects & Aspect::Stencil) == 0) {
+                    return DAWN_VALIDATION_ERROR(
+                        "Texture does not have stencil aspect for texture copy");
+                }
+                break;
+            default:
+                UNREACHABLE();
+                break;
+        }
+
         return {};
     }
 
@@ -523,4 +544,37 @@
         return {};
     }
 
+    MaybeError ValidateBufferToTextureCopyRestrictions(const TextureCopyView& dst) {
+        const Format& format = dst.texture->GetFormat();
+
+        bool depthSelected = false;
+        switch (dst.aspect) {
+            case wgpu::TextureAspect::All:
+                switch (format.aspects) {
+                    case Aspect::Color:
+                    case Aspect::Stencil:
+                        break;
+                    case Aspect::Depth:
+                        depthSelected = true;
+                        break;
+                    default:
+                        return DAWN_VALIDATION_ERROR(
+                            "A single aspect must be selected for multi planar formats in buffer "
+                            "to texture copies");
+                }
+                break;
+            case wgpu::TextureAspect::DepthOnly:
+                ASSERT(format.aspects & Aspect::Depth);
+                depthSelected = true;
+                break;
+            case wgpu::TextureAspect::StencilOnly:
+                ASSERT(format.aspects & Aspect::Stencil);
+                break;
+        }
+        if (depthSelected) {
+            return DAWN_VALIDATION_ERROR("Cannot copy into the depth aspect of a texture");
+        }
+        return {};
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index ae0464c..719ce17 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -27,6 +27,7 @@
     class QuerySetBase;
     struct BeginRenderPassCmd;
     struct PassResourceUsage;
+    struct TexelBlockInfo;
 
     MaybeError ValidateCanPopDebugGroup(uint64_t debugGroupStackSize);
     MaybeError ValidateFinalDebugGroupStackSize(uint64_t debugGroupStackSize);
@@ -40,17 +41,18 @@
 
     MaybeError ValidateTimestampQuery(QuerySetBase* querySet, uint32_t queryIndex);
 
-    uint32_t ComputeRequiredBytesInCopy(const Format& textureFormat,
+    uint32_t ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
                                         const Extent3D& copySize,
                                         uint32_t bytesPerRow,
                                         uint32_t rowsPerImage);
 
     MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
                                          uint64_t byteSize,
-                                         const Format& format,
+                                         const TexelBlockInfo& blockInfo,
                                          const Extent3D& copyExtent);
     MaybeError ValidateTextureCopyRange(const TextureCopyView& textureCopyView,
                                         const Extent3D& copySize);
+    MaybeError ValidateBufferToTextureCopyRestrictions(const TextureCopyView& dst);
 
     MaybeError ValidateBufferCopyView(DeviceBase const* device,
                                       const BufferCopyView& bufferCopyView);
diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp
index d80db78..92a8a60 100644
--- a/src/dawn_native/Format.cpp
+++ b/src/dawn_native/Format.cpp
@@ -79,6 +79,47 @@
         return componentType == type;
     }
 
+    TexelBlockInfo Format::GetTexelBlockInfo(wgpu::TextureAspect aspect) const {
+        switch (aspect) {
+            case wgpu::TextureAspect::All:
+                switch (aspects) {
+                    case Aspect::Color:
+                    case Aspect::Depth:
+                    case Aspect::Stencil:
+                        break;
+                    default:
+                        UNREACHABLE();
+                }
+                return *this;
+
+            case wgpu::TextureAspect::DepthOnly:
+                ASSERT(HasDepth());
+                switch (format) {
+                    case wgpu::TextureFormat::Depth32Float:
+                        return *this;
+                    default:
+                        UNREACHABLE();
+                        break;
+                }
+                break;
+
+            case wgpu::TextureAspect::StencilOnly:
+                ASSERT(HasStencil());
+                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 a8907ad..f57370e 100644
--- a/src/dawn_native/Format.h
+++ b/src/dawn_native/Format.h
@@ -29,12 +29,18 @@
     enum class Aspect : uint8_t;
     class DeviceBase;
 
+    struct TexelBlockInfo {
+        uint32_t blockByteSize;
+        uint32_t blockWidth;
+        uint32_t blockHeight;
+    };
+
     // The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
     // exact number of known format.
     static constexpr size_t kKnownFormatCount = 52;
 
     // A wgpu::TextureFormat along with all the information about it necessary for validation.
-    struct Format {
+    struct Format : TexelBlockInfo {
         enum class Type {
             Float,
             Sint,
@@ -51,10 +57,6 @@
         Type type;
         Aspect aspects;
 
-        uint32_t blockByteSize;
-        uint32_t blockWidth;
-        uint32_t blockHeight;
-
         static Type TextureComponentTypeToFormatType(wgpu::TextureComponentType componentType);
         static wgpu::TextureComponentType FormatTypeToTextureComponentType(Type type);
 
@@ -64,6 +66,8 @@
         bool HasDepthOrStencil() const;
         bool HasComponentType(Type componentType) const;
 
+        TexelBlockInfo GetTexelBlockInfo(wgpu::TextureAspect aspect) const;
+
         // The index of the format in the list of all known formats: a unique number for each format
         // in [0, kKnownFormatCount)
         size_t GetIndex() const;
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 372885b..900b92a 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -275,8 +275,10 @@
         // copyExtent.height by blockHeight while the divisibility conditions are
         // checked in validating texture copy range.
         DAWN_TRY(ValidateTextureCopyRange(*destination, *writeSize));
-        DAWN_TRY(ValidateLinearTextureData(*dataLayout, dataSize, destination->texture->GetFormat(),
-                                           *writeSize));
+        DAWN_TRY(ValidateBufferToTextureCopyRestrictions(*destination));
+        DAWN_TRY(ValidateLinearTextureData(
+            *dataLayout, dataSize,
+            destination->texture->GetFormat().GetTexelBlockInfo(destination->aspect), *writeSize));
 
         return {};
     }
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index e5877af..f544346 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -75,11 +75,12 @@
                      wgpu::Texture destTexture,
                      uint32_t destLevel,
                      wgpu::Origin3D destOrigin,
-                     wgpu::Extent3D extent3D) {
+                     wgpu::Extent3D extent3D,
+                     wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
         wgpu::BufferCopyView bufferCopyView =
             utils::CreateBufferCopyView(srcBuffer, srcOffset, srcBytesPerRow, srcRowsPerImage);
         wgpu::TextureCopyView textureCopyView =
-            utils::CreateTextureCopyView(destTexture, destLevel, destOrigin);
+            utils::CreateTextureCopyView(destTexture, destLevel, destOrigin, aspect);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &extent3D);
@@ -95,11 +96,12 @@
                      uint64_t destOffset,
                      uint32_t destBytesPerRow,
                      uint32_t destRowsPerImage,
-                     wgpu::Extent3D extent3D) {
+                     wgpu::Extent3D extent3D,
+                     wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
         wgpu::BufferCopyView bufferCopyView =
             utils::CreateBufferCopyView(destBuffer, destOffset, destBytesPerRow, destRowsPerImage);
         wgpu::TextureCopyView textureCopyView =
-            utils::CreateTextureCopyView(srcTexture, srcLevel, srcOrigin);
+            utils::CreateTextureCopyView(srcTexture, srcLevel, srcOrigin, aspect);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &extent3D);
@@ -114,11 +116,12 @@
                      wgpu::Texture dstTexture,
                      uint32_t dstLevel,
                      wgpu::Origin3D dstOrigin,
-                     wgpu::Extent3D extent3D) {
+                     wgpu::Extent3D extent3D,
+                     wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
         wgpu::TextureCopyView srcTextureCopyView =
-            utils::CreateTextureCopyView(srcTexture, srcLevel, srcOrigin);
+            utils::CreateTextureCopyView(srcTexture, srcLevel, srcOrigin, aspect);
         wgpu::TextureCopyView dstTextureCopyView =
-            utils::CreateTextureCopyView(dstTexture, dstLevel, dstOrigin);
+            utils::CreateTextureCopyView(dstTexture, dstLevel, dstOrigin, aspect);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &extent3D);
@@ -682,6 +685,83 @@
                 {0, 0, 0}, {2, 2, 1});
 }
 
+// Test it is invalid to copy to a depth texture
+TEST_F(CopyCommandTest_B2T, CopyToDepthAspect) {
+    // Test it is invalid to copy from a buffer into Depth32Float
+    {
+        uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R32Float);
+        wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
+
+        wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth32Float,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::All);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+
+    // Test it is invalid to copy from a buffer into Depth24Plus
+    {
+        uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R32Float);
+        wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
+
+        wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24Plus,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::All);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+}
+
+// Test copy to only the stencil aspect of a texture
+TEST_F(CopyCommandTest_B2T, CopyToStencilAspect) {
+    // Test it is valid to copy from a buffer into the stencil aspect of Depth24PlusStencil8
+    {
+        uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R8Uint);
+        wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
+
+        wgpu::Texture destination = Create2DTexture(
+            16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopyDst);
+
+        TestB2TCopy(utils::Expectation::Success, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+
+        // And that it fails if the buffer is one byte too small
+        wgpu::Buffer sourceSmall = CreateBuffer(bufferSize - 1, wgpu::BufferUsage::CopySrc);
+        TestB2TCopy(utils::Expectation::Failure, sourceSmall, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+
+    // Test it is invalid to copy from a buffer into the stencil aspect of Depth24Plus (no stencil)
+    {
+        uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R8Uint);
+        wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
+
+        wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24Plus,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+
+    // Test it is invalid to copy from a buffer into the stencil aspect of a color texture
+    {
+        uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R8Uint);
+        wgpu::Buffer source = CreateBuffer(bufferSize, wgpu::BufferUsage::CopySrc);
+
+        wgpu::Texture destination = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::RGBA8Uint,
+                                                    wgpu::TextureUsage::CopyDst);
+
+        TestB2TCopy(utils::Expectation::Failure, source, 0, 256, 0, destination, 0, {0, 0, 0},
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+}
+
 class CopyCommandTest_T2B : public CopyCommandTest {};
 
 // Test a successfull T2B copy
@@ -1010,6 +1090,83 @@
                 256, 0, {2, 1, 1});
 }
 
+// Test copy from only the depth aspect of a texture
+TEST_F(CopyCommandTest_T2B, CopyFromDepthAspect) {
+    uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R32Float);
+    wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
+    {
+        wgpu::Texture source = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth32Float,
+                                               wgpu::TextureUsage::CopySrc);
+
+        // Test "all" of a depth texture which is only the depth aspect.
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::All);
+
+        // Test it is valid to copy the depth aspect of a depth texture
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+    {
+        wgpu::Texture source = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24Plus,
+                                               wgpu::TextureUsage::CopySrc);
+
+        // Test it is invalid to copy from the depth aspect of depth24plus
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+    {
+        wgpu::Texture source = Create2DTexture(
+            16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
+
+        // Test it is invalid to copy from the depth aspect of depth24plus-stencil8
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+    {
+        wgpu::Texture source = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::R32Float,
+                                               wgpu::TextureUsage::CopySrc);
+
+        // Test it is invalid to copy from the depth aspect of a color texture
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+    }
+}
+
+// Test copy from only the stencil aspect of a texture
+TEST_F(CopyCommandTest_T2B, CopyFromStencilAspect) {
+    uint64_t bufferSize = BufferSizeForTextureCopy(16, 16, 1, wgpu::TextureFormat::R8Uint);
+    wgpu::Buffer destination = CreateBuffer(bufferSize, wgpu::BufferUsage::CopyDst);
+    {
+        wgpu::Texture source = Create2DTexture(
+            16, 16, 1, 1, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureUsage::CopySrc);
+
+        // Test it is valid to copy from the stencil aspect of a depth24plus-stencil8 texture
+        TestT2BCopy(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+
+        // Test it is invalid if the buffer is too small
+        wgpu::Buffer destinationSmall = CreateBuffer(bufferSize - 1, wgpu::BufferUsage::CopyDst);
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destinationSmall, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+    {
+        wgpu::Texture source =
+            Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::R8Uint, wgpu::TextureUsage::CopySrc);
+
+        // Test it is invalid to copy from the stencil aspect of a color texture
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+    {
+        wgpu::Texture source = Create2DTexture(16, 16, 1, 1, wgpu::TextureFormat::Depth24Plus,
+                                               wgpu::TextureUsage::CopySrc);
+
+        // Test it is invalid to copy from the stencil aspect of a depth-only texture
+        TestT2BCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, 256, 0,
+                    {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
+    }
+}
+
 class CopyCommandTest_T2T : public CopyCommandTest {};
 
 TEST_F(CopyCommandTest_T2T, Success) {
@@ -1164,6 +1321,14 @@
     // Failure when depth stencil subresource is partially copied
     TestT2TCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, {0, 0, 0},
                 {15, 15, 1});
+
+    // Failure when selecting the depth aspect (not all)
+    TestT2TCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, {0, 0, 0},
+                {16, 16, 1}, wgpu::TextureAspect::DepthOnly);
+
+    // Failure when selecting the stencil aspect (not all)
+    TestT2TCopy(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0, {0, 0, 0},
+                {16, 16, 1}, wgpu::TextureAspect::StencilOnly);
 }
 
 TEST_F(CopyCommandTest_T2T, 2DTextureArrayDepthStencil) {
diff --git a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
index c238acf..c689f58 100644
--- a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
@@ -53,7 +53,8 @@
                               wgpu::Texture texture,
                               uint32_t texLevel,
                               wgpu::Origin3D texOrigin,
-                              wgpu::Extent3D size) {
+                              wgpu::Extent3D size,
+                              wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
             std::vector<uint8_t> data(dataSize);
 
             wgpu::TextureDataLayout textureDataLayout;
@@ -62,7 +63,7 @@
             textureDataLayout.rowsPerImage = dataRowsPerImage;
 
             wgpu::TextureCopyView textureCopyView =
-                utils::CreateTextureCopyView(texture, texLevel, texOrigin);
+                utils::CreateTextureCopyView(texture, texLevel, texOrigin, aspect);
 
             queue.WriteTexture(&textureCopyView, data.data(), dataSize, &textureDataLayout, &size);
         }
@@ -402,6 +403,71 @@
                                       {0, 0, 1}, {4, 2, 3});
     }
 
+    // Test it is invalid to write into a depth texture.
+    TEST_F(QueueWriteTextureValidationTest, WriteToDepthAspect) {
+        uint32_t bytesPerRow = sizeof(float) * 4;
+        const uint64_t dataSize = utils::RequiredBytesInCopy(bytesPerRow, 0, {4, 4, 1},
+                                                             wgpu::TextureFormat::Depth32Float);
+
+        // Invalid to write into depth32float
+        {
+            wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
+                {4, 4, 1}, 1, wgpu::TextureFormat::Depth32Float, wgpu::TextureUsage::CopyDst);
+
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
+
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1},
+                                                 wgpu::TextureAspect::DepthOnly));
+        }
+
+        // Invalid to write into depth24plus
+        {
+            wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
+                {4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
+
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
+
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1},
+                                                 wgpu::TextureAspect::DepthOnly));
+        }
+    }
+
+    // Test write texture to the stencil aspect
+    TEST_F(QueueWriteTextureValidationTest, WriteToStencilAspect) {
+        uint32_t bytesPerRow = 4;
+        const uint64_t dataSize =
+            utils::RequiredBytesInCopy(bytesPerRow, 0, {4, 4, 1}, wgpu::TextureFormat::R8Uint);
+
+        // It is valid to write into the stencil aspect of depth24plus-stencil8
+        {
+            wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
+                {4, 4, 1}, 1, wgpu::TextureFormat::Depth24PlusStencil8,
+                wgpu::TextureUsage::CopyDst);
+
+            TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0, {0, 0, 0}, {4, 4, 1},
+                             wgpu::TextureAspect::StencilOnly);
+
+            // And that it fails if the buffer is one byte too small
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize - 1, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1},
+                                                 wgpu::TextureAspect::StencilOnly));
+        }
+
+        // It is invalid to write into the stencil aspect of depth24plus (no stencil)
+        {
+            wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
+                {4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
+
+            ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
+                                                 {0, 0, 0}, {4, 4, 1},
+                                                 wgpu::TextureAspect::StencilOnly));
+        }
+    }
+
     class WriteTextureTest_CompressedTextureFormats : public QueueWriteTextureValidationTest {
       public:
         WriteTextureTest_CompressedTextureFormats() : QueueWriteTextureValidationTest() {
diff --git a/src/utils/WGPUHelpers.cpp b/src/utils/WGPUHelpers.cpp
index 0a47b8d..b9a51bb 100644
--- a/src/utils/WGPUHelpers.cpp
+++ b/src/utils/WGPUHelpers.cpp
@@ -276,11 +276,13 @@
 
     wgpu::TextureCopyView CreateTextureCopyView(wgpu::Texture texture,
                                                 uint32_t mipLevel,
-                                                wgpu::Origin3D origin) {
+                                                wgpu::Origin3D origin,
+                                                wgpu::TextureAspect aspect) {
         wgpu::TextureCopyView textureCopyView;
         textureCopyView.texture = texture;
         textureCopyView.mipLevel = mipLevel;
         textureCopyView.origin = origin;
+        textureCopyView.aspect = aspect;
 
         return textureCopyView;
     }
diff --git a/src/utils/WGPUHelpers.h b/src/utils/WGPUHelpers.h
index 0c63c4d..03e618c 100644
--- a/src/utils/WGPUHelpers.h
+++ b/src/utils/WGPUHelpers.h
@@ -52,9 +52,11 @@
                                               uint64_t offset,
                                               uint32_t bytesPerRow,
                                               uint32_t rowsPerImage);
-    wgpu::TextureCopyView CreateTextureCopyView(wgpu::Texture texture,
-                                                uint32_t level,
-                                                wgpu::Origin3D origin);
+    wgpu::TextureCopyView CreateTextureCopyView(
+        wgpu::Texture texture,
+        uint32_t level,
+        wgpu::Origin3D origin,
+        wgpu::TextureAspect aspect = wgpu::TextureAspect::All);
     wgpu::TextureDataLayout CreateTextureDataLayout(uint64_t offset,
                                                     uint32_t bytesPerRow,
                                                     uint32_t rowsPerImage);