Support 2D array texture copying on D3D12, Metal and OpenGL

This patch implements the creation of 2D array textures and copying
between a buffer and a layer of a 2D array texture on D3D12, Metal
and OpenGL back-ends.

TEST=dawn_end2end_tests
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index e4d1975..2dab383 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -333,7 +333,9 @@
                     D3D12_TEXTURE_COPY_LOCATION textureLocation;
                     textureLocation.pResource = texture->GetD3D12Resource();
                     textureLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
-                    textureLocation.SubresourceIndex = copy->destination.level;
+                    textureLocation.SubresourceIndex =
+                        texture->GetNumMipLevels() * copy->destination.slice +
+                        copy->destination.level;
 
                     for (uint32_t i = 0; i < copySplit.count; ++i) {
                         auto& info = copySplit.copies[i];
@@ -379,7 +381,8 @@
                     D3D12_TEXTURE_COPY_LOCATION textureLocation;
                     textureLocation.pResource = texture->GetD3D12Resource();
                     textureLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
-                    textureLocation.SubresourceIndex = copy->source.level;
+                    textureLocation.SubresourceIndex =
+                        texture->GetNumMipLevels() * copy->source.slice + copy->source.level;
 
                     for (uint32_t i = 0; i < copySplit.count; ++i) {
                         auto& info = copySplit.copies[i];
diff --git a/src/dawn_native/d3d12/TextureCopySplitter.cpp b/src/dawn_native/d3d12/TextureCopySplitter.cpp
index 7b35597..32f5309 100644
--- a/src/dawn_native/d3d12/TextureCopySplitter.cpp
+++ b/src/dawn_native/d3d12/TextureCopySplitter.cpp
@@ -50,7 +50,7 @@
         TextureCopySplit copy;
 
         if (z != 0 || depth > 1) {
-            // TODO(enga@google.com): Handle 3D / 2D arrays
+            // TODO(enga@google.com): Handle 3D
             ASSERT(false);
             return copy;
         }
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 81e04ae..4c16c20 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -114,7 +114,7 @@
         resourceDescriptor.Alignment = 0;
         resourceDescriptor.Width = GetWidth();
         resourceDescriptor.Height = GetHeight();
-        resourceDescriptor.DepthOrArraySize = static_cast<UINT16>(GetDepth());
+        resourceDescriptor.DepthOrArraySize = GetDepthOrArraySize();
         resourceDescriptor.MipLevels = static_cast<UINT16>(GetNumMipLevels());
         resourceDescriptor.Format = D3D12TextureFormat(GetFormat());
         resourceDescriptor.SampleDesc.Count = 1;
@@ -151,6 +151,15 @@
         return mResourcePtr;
     }
 
+    UINT16 Texture::GetDepthOrArraySize() {
+        switch (GetDimension()) {
+            case dawn::TextureDimension::e2D:
+                return static_cast<UINT16>(GetArrayLayers());
+            default:
+                UNREACHABLE();
+        }
+    }
+
     void Texture::TransitionUsageNow(ComPtr<ID3D12GraphicsCommandList> commandList,
                                      dawn::TextureUsageBit usage) {
         // Avoid transitioning the texture when it isn't needed.
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 571681b..8492ba6 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -38,6 +38,8 @@
                                 dawn::TextureUsageBit usage);
 
       private:
+        UINT16 GetDepthOrArraySize();
+
         ComPtr<ID3D12Resource> mResource = {};
         ID3D12Resource* mResourcePtr = nullptr;
         dawn::TextureUsageBit mLastUsage = dawn::TextureUsageBit::None;
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 4fd66a3..746f298 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -270,7 +270,7 @@
                               sourceBytesPerImage:(copy->rowPitch * dst.height)
                                        sourceSize:size
                                         toTexture:texture->GetMTLTexture()
-                                 destinationSlice:0
+                                 destinationSlice:dst.slice
                                  destinationLevel:dst.level
                                 destinationOrigin:origin];
                 } break;
@@ -294,7 +294,7 @@
 
                     encoders.EnsureBlit(commandBuffer);
                     [encoders.blit copyFromTexture:texture->GetMTLTexture()
-                                       sourceSlice:0
+                                       sourceSlice:src.slice
                                        sourceLevel:src.level
                                       sourceOrigin:origin
                                         sourceSize:size
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 3c3a1c2..40f9c0f 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -58,10 +58,11 @@
             return result;
         }
 
-        MTLTextureType MetalTextureType(dawn::TextureDimension dimension) {
+        MTLTextureType MetalTextureType(dawn::TextureDimension dimension,
+                                        unsigned int arrayLayers) {
             switch (dimension) {
                 case dawn::TextureDimension::e2D:
-                    return MTLTextureType2D;
+                    return (arrayLayers > 1) ? MTLTextureType2DArray : MTLTextureType2D;
             }
         }
     }
@@ -70,14 +71,14 @@
         : TextureBase(device, descriptor) {
         auto desc = [MTLTextureDescriptor new];
         [desc autorelease];
-        desc.textureType = MetalTextureType(GetDimension());
+        desc.textureType = MetalTextureType(GetDimension(), GetArrayLayers());
         desc.usage = MetalTextureUsage(GetUsage());
         desc.pixelFormat = MetalPixelFormat(GetFormat());
         desc.width = GetWidth();
         desc.height = GetHeight();
         desc.depth = GetDepth();
         desc.mipmapLevelCount = GetNumMipLevels();
-        desc.arrayLength = 1;
+        desc.arrayLength = GetArrayLayers();
         desc.storageMode = MTLStorageModePrivate;
 
         auto mtlDevice = device->GetMTLDevice();
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 054a899..2f3737b 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -331,13 +331,28 @@
                     glActiveTexture(GL_TEXTURE0);
                     glBindTexture(target, texture->GetHandle());
 
-                    ASSERT(texture->GetDimension() == dawn::TextureDimension::e2D);
                     glPixelStorei(GL_UNPACK_ROW_LENGTH,
                                   copy->rowPitch / TextureFormatPixelSize(texture->GetFormat()));
-                    glTexSubImage2D(target, dst.level, dst.x, dst.y, dst.width, dst.height,
+                    switch (texture->GetDimension()) {
+                        case dawn::TextureDimension::e2D:
+                            if (texture->GetArrayLayers() > 1) {
+                                glTexSubImage3D(
+                                    target, dst.level, dst.x, dst.y, dst.slice, dst.width,
+                                    dst.height, 1, format.format, format.type,
+                                    reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                            } else {
+                                glTexSubImage2D(
+                                    target, dst.level, dst.x, dst.y, dst.width, dst.height,
                                     format.format, format.type,
                                     reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                            }
+                            break;
+
+                        default:
+                            UNREACHABLE();
+                    }
                     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
                     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                 } break;
 
@@ -348,18 +363,31 @@
                     Texture* texture = ToBackend(src.texture.Get());
                     Buffer* buffer = ToBackend(dst.buffer.Get());
                     auto format = texture->GetGLFormat();
+                    GLenum target = texture->GetGLTarget();
 
                     // 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.
-                    ASSERT(texture->GetDimension() == dawn::TextureDimension::e2D);
-                    glBindTexture(GL_TEXTURE_2D, texture->GetHandle());
+                    glBindTexture(target, texture->GetHandle());
 
                     GLuint readFBO = 0;
                     glGenFramebuffers(1, &readFBO);
                     glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
+                    switch (texture->GetDimension()) {
+                        case dawn::TextureDimension::e2D:
+                            if (texture->GetArrayLayers() > 1) {
+                                glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                                          texture->GetHandle(), src.level,
+                                                          src.slice);
+                            } else {
+                                glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                                       GL_TEXTURE_2D, texture->GetHandle(),
+                                                       src.level);
+                            }
+                            break;
 
-                    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-                                           texture->GetHandle(), src.level);
+                        default:
+                            UNREACHABLE();
+                    }
 
                     glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer->GetHandle());
                     glPixelStorei(GL_PACK_ROW_LENGTH,
diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp
index 12cadc8..9033f6d 100644
--- a/src/dawn_native/opengl/TextureGL.cpp
+++ b/src/dawn_native/opengl/TextureGL.cpp
@@ -24,10 +24,11 @@
 
     namespace {
 
-        GLenum TargetForDimension(dawn::TextureDimension dimension) {
+        GLenum TargetForDimensionAndArrayLayers(dawn::TextureDimension dimension,
+                                                uint32_t arrayLayer) {
             switch (dimension) {
                 case dawn::TextureDimension::e2D:
-                    return GL_TEXTURE_2D;
+                    return (arrayLayer > 1) ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
                 default:
                     UNREACHABLE();
             }
@@ -74,19 +75,32 @@
 
     Texture::Texture(Device* device, const TextureDescriptor* descriptor, GLuint handle)
         : TextureBase(device, descriptor), mHandle(handle) {
-        mTarget = TargetForDimension(GetDimension());
+        mTarget = TargetForDimensionAndArrayLayers(GetDimension(), GetArrayLayers());
 
         uint32_t width = GetWidth();
         uint32_t height = GetHeight();
         uint32_t levels = GetNumMipLevels();
+        uint32_t arrayLayers = GetArrayLayers();
 
         auto formatInfo = GetGLFormatInfo(GetFormat());
 
         glBindTexture(mTarget, handle);
 
         for (uint32_t i = 0; i < levels; ++i) {
-            glTexImage2D(mTarget, i, formatInfo.internalFormat, width, height, 0, formatInfo.format,
-                         formatInfo.type, nullptr);
+            switch (GetDimension()) {
+                case dawn::TextureDimension::e2D:
+                    if (arrayLayers > 1) {
+                        glTexImage3D(mTarget, i, formatInfo.internalFormat, width, height,
+                                     arrayLayers, 0, formatInfo.format, formatInfo.type, nullptr);
+                    } else {
+                        glTexImage2D(mTarget, i, formatInfo.internalFormat, width, height, 0,
+                                     formatInfo.format, formatInfo.type, nullptr);
+                    }
+                    break;
+                default:
+                    UNREACHABLE();
+            }
+
             width = std::max(uint32_t(1), width / 2);
             height = std::max(uint32_t(1), height / 2);
         }
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 912d8b6..c4780fe 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -365,11 +365,7 @@
 }
 
 // Test that copying regions of each texture 2D array layer works
-TEST_P(CopyTests_T2B, Texture2DArrayRegion)
-{
-    // TODO(jiawei.shao@intel.com): support 2D array texture on OpenGL, D3D12 and Metal.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12() || IsMetal());
-
+TEST_P(CopyTests_T2B, Texture2DArrayRegion) {
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;
@@ -378,9 +374,6 @@
 
 // Test that copying texture 2D array mips with 256-byte aligned sizes works
 TEST_P(CopyTests_T2B, Texture2DArrayMip) {
-    // TODO(jiawei.shao@intel.com): support 2D array texture on OpenGL, D3D12 and Metal.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12() || IsMetal());
-
     constexpr uint32_t kWidth = 256;
     constexpr uint32_t kHeight = 128;
     constexpr uint32_t kLayers = 6u;