D3D12: Fix usage of CopyResource

CopyResource may only be used for resources that have exactly the same
format, dimension, mips, layers. And it can only be used if the entire
texture region is copied.

Bug: dawn:353
Change-Id: Ia8f96cc10c88fe026e23bce2d0532624725b12e0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16984
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 096440b..385cbda 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -53,18 +53,40 @@
             }
         }
 
-        bool CanUseCopyResource(const uint32_t sourceNumMipLevels,
-                                const Extent3D& srcSize,
-                                const Extent3D& dstSize,
-                                const Extent3D& copySize) {
-            if (sourceNumMipLevels == 1 && srcSize.width == dstSize.width &&
-                srcSize.height == dstSize.height && srcSize.depth == dstSize.depth &&
-                srcSize.width == copySize.width && srcSize.height == copySize.height &&
-                srcSize.depth == copySize.depth) {
-                return true;
-            }
+        bool CanUseCopyResource(const Texture* src, const Texture* dst, const Extent3D& copySize) {
+            // Checked by validation
+            ASSERT(src->GetSampleCount() == dst->GetSampleCount());
+            ASSERT(src->GetFormat().format == dst->GetFormat().format);
 
-            return false;
+            const Extent3D& srcSize = src->GetSize();
+            const Extent3D& dstSize = dst->GetSize();
+
+            auto GetCopyDepth = [](const Texture* texture) {
+                switch (texture->GetDimension()) {
+                    case wgpu::TextureDimension::e1D:
+                        return 1u;
+                    case wgpu::TextureDimension::e2D:
+                        return texture->GetArrayLayers();
+                    case wgpu::TextureDimension::e3D:
+                        return texture->GetSize().depth;
+                }
+            };
+
+            // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-copyresource
+            // In order to use D3D12's copy resource, the textures must be the same dimensions, and
+            // the copy must be of the entire resource.
+            // TODO(dawn:129): Support 1D textures.
+            return src->GetDimension() == dst->GetDimension() &&  //
+                   dst->GetNumMipLevels() == 1 &&                 //
+                   src->GetNumMipLevels() == 1 &&      // A copy command is of a single mip, so if a
+                                                       // resource has more than one, we definitely
+                                                       // cannot use CopyResource.
+                   copySize.width == dstSize.width &&  //
+                   copySize.width == srcSize.width &&  //
+                   copySize.height == dstSize.height &&    //
+                   copySize.height == srcSize.height &&    //
+                   copySize.depth == GetCopyDepth(src) &&  //
+                   copySize.depth == GetCopyDepth(dst);
         }
 
     }  // anonymous namespace
@@ -669,8 +691,7 @@
                     destination->TrackUsageAndTransitionNow(commandContext,
                                                             wgpu::TextureUsage::CopyDst);
 
-                    if (CanUseCopyResource(source->GetNumMipLevels(), source->GetSize(),
-                                           destination->GetSize(), copy->copySize)) {
+                    if (CanUseCopyResource(source, destination, copy->copySize)) {
                         commandList->CopyResource(destination->GetD3D12Resource(),
                                                   source->GetD3D12Resource());
                     } else {
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index b3c15b0..9513975 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -729,6 +729,24 @@
     }
 }
 
+TEST_P(CopyTests_T2T, SingleMipSrcMultipleMipDst) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    for (unsigned int i = 1; i < 4; ++i) {
+        DoTest({kWidth >> i, kHeight >> i, 0, 0, 0}, {kWidth, kHeight, 0, 0, i},
+               {kWidth >> i, kHeight >> i});
+    }
+}
+
+TEST_P(CopyTests_T2T, MultipleMipSrcSingleMipDst) {
+    constexpr uint32_t kWidth = 256;
+    constexpr uint32_t kHeight = 128;
+    for (unsigned int i = 1; i < 4; ++i) {
+        DoTest({kWidth, kHeight, 0, 0, i}, {kWidth >> i, kHeight >> i, 0, 0, 0},
+               {kWidth >> i, kHeight >> i});
+    }
+}
+
 // TODO(brandon1.jones@intel.com) Add test for ensuring blitCommandEncoder on Metal.
 
 DAWN_INSTANTIATE_TEST(CopyTests_T2T, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend());