Implement using a layer of a texture as color attachment - Part I

This patch implements using a layer of a texture as a color
attachment on D3D12, Metal and Vulkan.

This feature is not implemented on OpenGL back-ends in this patch.

BUG=dawn:16
TEST=dawn_unittests

Change-Id: Iffc194c6117f6e3e6506c6fcbfd51ca2fbe9ede8
Reviewed-on: https://dawn-review.googlesource.com/c/2660
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/RenderPassDescriptor.cpp b/src/dawn_native/RenderPassDescriptor.cpp
index 1426095..22f3b9f 100644
--- a/src/dawn_native/RenderPassDescriptor.cpp
+++ b/src/dawn_native/RenderPassDescriptor.cpp
@@ -103,19 +103,20 @@
 
     RenderPassDescriptorBase* RenderPassDescriptorBuilder::GetResultImpl() {
         auto CheckOrSetSize = [this](const TextureViewBase* attachment) -> bool {
+            uint32_t mipLevel = attachment->GetBaseMipLevel();
             if (this->mWidth == 0) {
                 ASSERT(this->mHeight == 0);
 
-                this->mWidth = attachment->GetTexture()->GetSize().width;
-                this->mHeight = attachment->GetTexture()->GetSize().height;
+                this->mWidth = attachment->GetTexture()->GetSize().width >> mipLevel;
+                this->mHeight = attachment->GetTexture()->GetSize().height >> mipLevel;
                 ASSERT(this->mWidth != 0 && this->mHeight != 0);
 
                 return true;
             }
 
             ASSERT(this->mWidth != 0 && this->mHeight != 0);
-            return this->mWidth == attachment->GetTexture()->GetSize().width &&
-                   this->mHeight == attachment->GetTexture()->GetSize().height;
+            return this->mWidth == attachment->GetTexture()->GetSize().width >> mipLevel &&
+                   this->mHeight == attachment->GetTexture()->GetSize().height >> mipLevel;
         };
 
         uint32_t attachmentCount = 0;
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 0923fcc..00c8b9f 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -300,7 +300,9 @@
         : ObjectBase(texture->GetDevice()),
           mTexture(texture),
           mFormat(descriptor->format),
+          mBaseMipLevel(descriptor->baseMipLevel),
           mLevelCount(descriptor->levelCount),
+          mBaseArrayLayer(descriptor->baseArrayLayer),
           mLayerCount(descriptor->layerCount) {
     }
 
@@ -316,10 +318,18 @@
         return mFormat;
     }
 
+    uint32_t TextureViewBase::GetBaseMipLevel() const {
+        return mBaseMipLevel;
+    }
+
     uint32_t TextureViewBase::GetLevelCount() const {
         return mLevelCount;
     }
 
+    uint32_t TextureViewBase::GetBaseArrayLayer() const {
+        return mBaseArrayLayer;
+    }
+
     uint32_t TextureViewBase::GetLayerCount() const {
         return mLayerCount;
     }
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index f1194f0..2020ec5 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -76,14 +76,18 @@
         TextureBase* GetTexture();
 
         dawn::TextureFormat GetFormat() const;
+        uint32_t GetBaseMipLevel() const;
         uint32_t GetLevelCount() const;
+        uint32_t GetBaseArrayLayer() const;
         uint32_t GetLayerCount() const;
 
       private:
         Ref<TextureBase> mTexture;
 
         dawn::TextureFormat mFormat;
+        uint32_t mBaseMipLevel;
         uint32_t mLevelCount;
+        uint32_t mBaseArrayLayer;
         uint32_t mLayerCount;
     };
 
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 4fd16ab..fb49dea 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -226,22 +226,33 @@
         }
     }
 
+    DXGI_FORMAT TextureView::GetD3D12Format() const {
+        return D3D12TextureFormat(GetFormat());
+    }
+
     const D3D12_SHADER_RESOURCE_VIEW_DESC& TextureView::GetSRVDescriptor() const {
         return mSrvDesc;
     }
 
-    // TODO(jiawei.shao@intel.com): support rendering into a layer of a texture.
-    D3D12_RENDER_TARGET_VIEW_DESC TextureView::GetRTVDescriptor() {
+    D3D12_RENDER_TARGET_VIEW_DESC TextureView::GetRTVDescriptor() const {
+        ASSERT(GetTexture()->GetDimension() == dawn::TextureDimension::e2D);
         D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
-        rtvDesc.Format = ToBackend(GetTexture())->GetD3D12Format();
-        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
-        rtvDesc.Texture2D.MipSlice = 0;
-        rtvDesc.Texture2D.PlaneSlice = 0;
+        rtvDesc.Format = GetD3D12Format();
+        // Currently we always use D3D12_TEX2D_ARRAY_RTV because we cannot specify base array layer
+        // and layer count in D3D12_TEX2D_RTV. For 2D texture views, we treat them as 1-layer 2D
+        // array textures. (Just like how we treat SRVs)
+        // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_rtv
+        // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array_rtv
+        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
+        rtvDesc.Texture2DArray.FirstArraySlice = GetBaseArrayLayer();
+        rtvDesc.Texture2DArray.ArraySize = GetLayerCount();
+        rtvDesc.Texture2DArray.MipSlice = GetBaseMipLevel();
+        rtvDesc.Texture2DArray.PlaneSlice = 0;
         return rtvDesc;
     }
 
     // TODO(jiawei.shao@intel.com): support rendering into a layer of a texture.
-    D3D12_DEPTH_STENCIL_VIEW_DESC TextureView::GetDSVDescriptor() {
+    D3D12_DEPTH_STENCIL_VIEW_DESC TextureView::GetDSVDescriptor() const {
         D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
         dsvDesc.Format = ToBackend(GetTexture())->GetD3D12Format();
         dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 23a0d52..2edfb7c 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -49,9 +49,11 @@
       public:
         TextureView(TextureBase* texture, const TextureViewDescriptor* descriptor);
 
+        DXGI_FORMAT GetD3D12Format() const;
+
         const D3D12_SHADER_RESOURCE_VIEW_DESC& GetSRVDescriptor() const;
-        D3D12_RENDER_TARGET_VIEW_DESC GetRTVDescriptor();
-        D3D12_DEPTH_STENCIL_VIEW_DESC GetDSVDescriptor();
+        D3D12_RENDER_TARGET_VIEW_DESC GetRTVDescriptor() const;
+        D3D12_DEPTH_STENCIL_VIEW_DESC GetDSVDescriptor() const;
 
       private:
         D3D12_SHADER_RESOURCE_VIEW_DESC mSrvDesc;
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index bf37d1f..26b2bb1 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -63,9 +63,8 @@
                     descriptor.colorAttachments[i].loadAction = MTLLoadActionLoad;
                 }
 
-                // TODO(jiawei.shao@intel.com): support rendering into a layer of a texture.
                 descriptor.colorAttachments[i].texture =
-                    ToBackend(attachmentInfo.view->GetTexture())->GetMTLTexture();
+                    ToBackend(attachmentInfo.view)->GetMTLTexture();
                 descriptor.colorAttachments[i].storeAction = MTLStoreActionStore;
             }
 
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 8001941..6e54cfe 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -292,6 +292,7 @@
                                                     uint32_t width,
                                                     uint32_t height,
                                                     uint32_t level,
+                                                    uint32_t slice,
                                                     uint32_t pixelSize,
                                                     detail::Expectation* expectation) {
     uint32_t rowPitch = Align(width * pixelSize, kTextureRowPitchAlignment);
@@ -302,7 +303,7 @@
     // We need to enqueue the copy immediately because by the time we resolve the expectation,
     // the texture might have been modified.
     dawn::TextureCopyView textureCopyView =
-        utils::CreateTextureCopyView(texture, level, 0, {x, y, 0}, dawn::TextureAspect::Color);
+        utils::CreateTextureCopyView(texture, level, slice, {x, y, 0}, dawn::TextureAspect::Color);
     dawn::BufferCopyView bufferCopyView =
         utils::CreateBufferCopyView(readback.buffer, readback.offset, rowPitch, 0);
     dawn::Extent3D copySize = {width, height, 1};
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 1c68116..f0c8823 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -35,12 +35,13 @@
                          new detail::ExpectEq<uint8_t>(expected))
 
 // Test a pixel of the mip level 0 of a 2D texture.
-#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y)                               \
-    AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, sizeof(RGBA8), \
+#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y)                                  \
+    AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \
                           new detail::ExpectEq<RGBA8>(expected))
 
-#define EXPECT_TEXTURE_RGBA8_EQ(expected, texture, x, y, width, height, level)                    \
-    AddTextureExpectation(__FILE__, __LINE__, texture, x, y, width, height, level, sizeof(RGBA8), \
+#define EXPECT_TEXTURE_RGBA8_EQ(expected, texture, x, y, width, height, level, slice)     \
+    AddTextureExpectation(__FILE__, __LINE__, texture, x, y, width, height, level, slice, \
+                          sizeof(RGBA8),                                                  \
                           new detail::ExpectEq<RGBA8>(expected, (width) * (height)))
 
 struct RGBA8 {
@@ -122,6 +123,7 @@
                                               uint32_t width,
                                               uint32_t height,
                                               uint32_t level,
+                                              uint32_t slice,
                                               uint32_t pixelSize,
                                               detail::Expectation* expectation);
 
diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp
index 3732303..02c2e51 100644
--- a/src/tests/end2end/CopyTests.cpp
+++ b/src/tests/end2end/CopyTests.cpp
@@ -235,7 +235,7 @@
         std::vector<RGBA8> expected(rowPitch / kBytesPerTexel * (textureSpec.copyHeight - 1) + textureSpec.copyWidth);
         PackTextureData(&bufferData[bufferSpec.offset / kBytesPerTexel], textureSpec.copyWidth, textureSpec.copyHeight, bufferSpec.rowPitch / kBytesPerTexel, expected.data(), textureSpec.copyWidth);
 
-        EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.x, textureSpec.y, textureSpec.copyWidth, textureSpec.copyHeight, textureSpec.level) <<
+        EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.x, textureSpec.y, textureSpec.copyWidth, textureSpec.copyHeight, textureSpec.level, 0) <<
             "Buffer to Texture copy failed copying "
             << bufferSpec.size << "-byte buffer with offset " << bufferSpec.offset << " and row pitch " << bufferSpec.rowPitch << " to [("
             << textureSpec.x << ", " << textureSpec.y << "), (" << textureSpec.x + textureSpec.copyWidth << ", " << textureSpec.y + textureSpec.copyHeight <<
diff --git a/src/tests/end2end/RenderPassLoadOpTests.cpp b/src/tests/end2end/RenderPassLoadOpTests.cpp
index 0c8cee3..d8efd8e 100644
--- a/src/tests/end2end/RenderPassLoadOpTests.cpp
+++ b/src/tests/end2end/RenderPassLoadOpTests.cpp
@@ -133,10 +133,10 @@
     auto commandsClearGreen = commandsClearGreenBuilder.GetResult();
 
     queue.Submit(1, &commandsClearZero);
-    EXPECT_TEXTURE_RGBA8_EQ(expectZero.data(), renderTarget, 0, 0, kRTSize, kRTSize, 0);
+    EXPECT_TEXTURE_RGBA8_EQ(expectZero.data(), renderTarget, 0, 0, kRTSize, kRTSize, 0, 0);
 
     queue.Submit(1, &commandsClearGreen);
-    EXPECT_TEXTURE_RGBA8_EQ(expectGreen.data(), renderTarget, 0, 0, kRTSize, kRTSize, 0);
+    EXPECT_TEXTURE_RGBA8_EQ(expectGreen.data(), renderTarget, 0, 0, kRTSize, kRTSize, 0, 0);
 
     // Part 2: draw a blue quad into the right half of the render target, and check result
 
@@ -155,9 +155,9 @@
 
     queue.Submit(1, &commandsLoad);
     // Left half should still be green
-    EXPECT_TEXTURE_RGBA8_EQ(expectGreen.data(), renderTarget, 0, 0, kRTSize / 2, kRTSize, 0);
+    EXPECT_TEXTURE_RGBA8_EQ(expectGreen.data(), renderTarget, 0, 0, kRTSize / 2, kRTSize, 0, 0);
     // Right half should now be blue
-    EXPECT_TEXTURE_RGBA8_EQ(expectBlue.data(), renderTarget, kRTSize / 2, 0, kRTSize / 2, kRTSize, 0);
+    EXPECT_TEXTURE_RGBA8_EQ(expectBlue.data(), renderTarget, kRTSize / 2, 0, kRTSize / 2, kRTSize, 0, 0);
 }
 
 DAWN_INSTANTIATE_TEST(RenderPassLoadOpTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)
diff --git a/src/tests/end2end/TextureViewTests.cpp b/src/tests/end2end/TextureViewTests.cpp
index 9a33470..98926a1 100644
--- a/src/tests/end2end/TextureViewTests.cpp
+++ b/src/tests/end2end/TextureViewTests.cpp
@@ -16,13 +16,59 @@
 
 #include "common/Assert.h"
 #include "common/Constants.h"
+#include "common/Math.h"
 #include "utils/DawnHelpers.h"
 
 #include <array>
 
 constexpr static unsigned int kRTSize = 64;
+constexpr dawn::TextureFormat kDefaultFormat = dawn::TextureFormat::R8G8B8A8Unorm;
+constexpr uint32_t kBytesPerTexel = 4;
 
-class TextureViewTest : public DawnTest {
+namespace {
+    dawn::Texture Create2DTexture(dawn::Device device,
+                                  uint32_t width,
+                                  uint32_t height,
+                                  uint32_t layerCount,
+                                  uint32_t levelCount,
+                                  dawn::TextureUsageBit usage) {
+        dawn::TextureDescriptor descriptor;
+        descriptor.dimension = dawn::TextureDimension::e2D;
+        descriptor.size.width = width;
+        descriptor.size.height = height;
+        descriptor.size.depth = 1;
+        descriptor.arrayLayer = layerCount;
+        descriptor.format = kDefaultFormat;
+        descriptor.levelCount = levelCount;
+        descriptor.usage = usage;
+        return device.CreateTexture(&descriptor);
+    }
+
+    dawn::ShaderModule CreateDefaultVertexShaderModule(dawn::Device device) {
+        return utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"(
+            #version 450
+            layout (location = 0) out vec2 o_texCoord;
+            void main() {
+                const vec2 pos[6] = vec2[6](vec2(-2.f, -2.f),
+                                            vec2(-2.f,  2.f),
+                                            vec2( 2.f, -2.f),
+                                            vec2(-2.f,  2.f),
+                                            vec2( 2.f, -2.f),
+                                            vec2( 2.f,  2.f));
+                const vec2 texCoord[6] = vec2[6](vec2(0.f, 0.f),
+                                                 vec2(0.f, 1.f),
+                                                 vec2(1.f, 0.f),
+                                                 vec2(0.f, 1.f),
+                                                 vec2(1.f, 0.f),
+                                                 vec2(1.f, 1.f));
+                gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
+                o_texCoord = texCoord[gl_VertexIndex];
+            }
+        )");
+    }
+}  // anonymous namespace
+
+class TextureViewSamplingTest : public DawnTest {
 protected:
     // Generates an arbitrary pixel value per-layer-per-level, used for the "actual" uploaded
     // textures and the "expected" results.
@@ -55,50 +101,22 @@
 
         mPipelineLayout = utils::MakeBasicPipelineLayout(device, &mBindGroupLayout);
 
-        mVSModule = utils::CreateShaderModule(device, dawn::ShaderStage::Vertex, R"(
-            #version 450
-            layout (location = 0) out vec2 o_texCoord;
-            void main() {
-                const vec2 pos[6] = vec2[6](vec2(-2.f, -2.f),
-                                            vec2(-2.f,  2.f),
-                                            vec2( 2.f, -2.f),
-                                            vec2(-2.f,  2.f),
-                                            vec2( 2.f, -2.f),
-                                            vec2( 2.f,  2.f));
-                const vec2 texCoord[6] = vec2[6](vec2(0.f, 0.f),
-                                                 vec2(0.f, 1.f),
-                                                 vec2(1.f, 0.f),
-                                                 vec2(0.f, 1.f),
-                                                 vec2(1.f, 0.f),
-                                                 vec2(1.f, 1.f));
-                gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
-                o_texCoord = texCoord[gl_VertexIndex];
-            }
-        )");
+        mVSModule = CreateDefaultVertexShaderModule(device);
     }
 
     void initTexture(uint32_t layerCount, uint32_t levelCount) {
         ASSERT(layerCount > 0 && levelCount > 0);
 
-        constexpr dawn::TextureFormat kFormat = dawn::TextureFormat::R8G8B8A8Unorm;
-
         const uint32_t textureWidthLevel0 = 1 << levelCount;
         const uint32_t textureHeightLevel0 = 1 << levelCount;
-
-        dawn::TextureDescriptor descriptor;
-        descriptor.dimension = dawn::TextureDimension::e2D;
-        descriptor.size.width = textureWidthLevel0;
-        descriptor.size.height = textureHeightLevel0;
-        descriptor.size.depth = 1;
-        descriptor.arrayLayer = layerCount;
-        descriptor.format = kFormat;
-        descriptor.levelCount = levelCount;
-        descriptor.usage = dawn::TextureUsageBit::TransferDst | dawn::TextureUsageBit::Sampled;
-        mTexture = device.CreateTexture(&descriptor);
+        constexpr dawn::TextureUsageBit kUsage =
+            dawn::TextureUsageBit::TransferDst | dawn::TextureUsageBit::Sampled;
+        mTexture = Create2DTexture(
+            device, textureWidthLevel0, textureHeightLevel0, layerCount, levelCount, kUsage);
 
         mDefaultTextureViewDescriptor.nextInChain = nullptr;
         mDefaultTextureViewDescriptor.dimension = dawn::TextureViewDimension::e2DArray;
-        mDefaultTextureViewDescriptor.format = kFormat;
+        mDefaultTextureViewDescriptor.format = kDefaultFormat;
         mDefaultTextureViewDescriptor.baseMipLevel = 0;
         mDefaultTextureViewDescriptor.levelCount = levelCount;
         mDefaultTextureViewDescriptor.baseArrayLayer = 0;
@@ -328,7 +346,7 @@
 };
 
 // Test drawing a rect with a 2D array texture.
-TEST_P(TextureViewTest, Default2DArrayTexture) {
+TEST_P(TextureViewSamplingTest, Default2DArrayTexture) {
     // TODO(cwallez@chromium.org) understand what the issue is
     DAWN_SKIP_TEST_IF(IsVulkan() && IsNvidia());
 
@@ -359,58 +377,58 @@
 }
 
 // Test sampling from a 2D texture view created on a 2D array texture.
-TEST_P(TextureViewTest, Texture2DViewOn2DArrayTexture) {
+TEST_P(TextureViewSamplingTest, Texture2DViewOn2DArrayTexture) {
     Texture2DViewTest(6, 1, 4, 0);
 }
 
 // Test sampling from a 2D array texture view created on a 2D array texture.
-TEST_P(TextureViewTest, Texture2DArrayViewOn2DArrayTexture) {
+TEST_P(TextureViewSamplingTest, Texture2DArrayViewOn2DArrayTexture) {
     DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
     Texture2DArrayViewTest(6, 1, 2, 0);
 }
 
 // Test sampling from a 2D texture view created on a mipmap level of a 2D texture.
-TEST_P(TextureViewTest, Texture2DViewOnOneLevelOf2DTexture) {
+TEST_P(TextureViewSamplingTest, Texture2DViewOnOneLevelOf2DTexture) {
     Texture2DViewTest(1, 6, 0, 4);
 }
 
 // Test sampling from a 2D texture view created on a mipmap level of a 2D array texture layer.
-TEST_P(TextureViewTest, Texture2DViewOnOneLevelOf2DArrayTexture) {
+TEST_P(TextureViewSamplingTest, Texture2DViewOnOneLevelOf2DArrayTexture) {
     Texture2DViewTest(6, 6, 3, 4);
 }
 
 // Test sampling from a 2D array texture view created on a mipmap level of a 2D array texture.
-TEST_P(TextureViewTest, Texture2DArrayViewOnOneLevelOf2DArrayTexture) {
+TEST_P(TextureViewSamplingTest, Texture2DArrayViewOnOneLevelOf2DArrayTexture) {
     DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
     Texture2DArrayViewTest(6, 6, 2, 4);
 }
 
 // Test sampling from a cube map texture view that covers a whole 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapOnWholeTexture) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapOnWholeTexture) {
     constexpr uint32_t kTotalLayers = 6;
     TextureCubeMapTest(kTotalLayers, 0, kTotalLayers, false);
 }
 
 // Test sampling from a cube map texture view that covers a sub part of a 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapViewOnPartOfTexture) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapViewOnPartOfTexture) {
     TextureCubeMapTest(10, 2, 6, false);
 }
 
 // Test sampling from a cube map texture view that covers the last layer of a 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapViewCoveringLastLayer) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapViewCoveringLastLayer) {
     constexpr uint32_t kTotalLayers = 10;
     constexpr uint32_t kBaseLayer = 4;
     TextureCubeMapTest(kTotalLayers, kBaseLayer, kTotalLayers - kBaseLayer, false);
 }
 
 // Test sampling from a cube map texture array view that covers a whole 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapArrayOnWholeTexture) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapArrayOnWholeTexture) {
     constexpr uint32_t kTotalLayers = 12;
     TextureCubeMapTest(kTotalLayers, 0, kTotalLayers, true);
 }
 
 // Test sampling from a cube map texture array view that covers a sub part of a 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapArrayViewOnPartOfTexture) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapArrayViewOnPartOfTexture) {
     // Test failing on the GPU FYI Mac Pro (AMD), see
     // https://bugs.chromium.org/p/dawn/issues/detail?id=58
     DAWN_SKIP_TEST_IF(IsMacOS() && IsMetal() && IsAMD());
@@ -419,7 +437,7 @@
 }
 
 // Test sampling from a cube map texture array view that covers the last layer of a 2D array texture.
-TEST_P(TextureViewTest, TextureCubeMapArrayViewCoveringLastLayer) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapArrayViewCoveringLastLayer) {
     // Test failing on the GPU FYI Mac Pro (AMD), see
     // https://bugs.chromium.org/p/dawn/issues/detail?id=58
     DAWN_SKIP_TEST_IF(IsMacOS() && IsMetal() && IsAMD());
@@ -430,7 +448,7 @@
 }
 
 // Test sampling from a cube map array texture view that only has a single cube map.
-TEST_P(TextureViewTest, TextureCubeMapArrayViewSingleCubeMap) {
+TEST_P(TextureViewSamplingTest, TextureCubeMapArrayViewSingleCubeMap) {
     // Test failing on the GPU FYI Mac Pro (AMD), see
     // https://bugs.chromium.org/p/dawn/issues/detail?id=58
     DAWN_SKIP_TEST_IF(IsMacOS() && IsMetal() && IsAMD());
@@ -438,4 +456,170 @@
     TextureCubeMapTest(20, 7, 6, true);
 }
 
-DAWN_INSTANTIATE_TEST(TextureViewTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)
+class TextureViewRenderingTest : public DawnTest {
+  protected:
+    void TextureLayerAsColorAttachmentTest(dawn::TextureViewDimension dimension,
+                                           uint32_t layerCount,
+                                           uint32_t levelCount,
+                                           uint32_t textureViewBaseLayer,
+                                           uint32_t textureViewBaseLevel) {
+        ASSERT(dimension == dawn::TextureViewDimension::e2D ||
+            dimension == dawn::TextureViewDimension::e2DArray);
+        ASSERT_LT(textureViewBaseLayer, layerCount);
+        ASSERT_LT(textureViewBaseLevel, levelCount);
+
+        const uint32_t textureWidthLevel0 = 1 << levelCount;
+        const uint32_t textureHeightLevel0 = 1 << levelCount;
+        constexpr dawn::TextureUsageBit kUsage = dawn::TextureUsageBit::OutputAttachment |
+                                                 dawn::TextureUsageBit::TransferSrc;
+        dawn::Texture texture = Create2DTexture(
+            device, textureWidthLevel0, textureHeightLevel0, layerCount, levelCount, kUsage);
+
+        dawn::TextureViewDescriptor descriptor;
+        descriptor.format = kDefaultFormat;
+        descriptor.dimension = dimension;
+        descriptor.baseArrayLayer = textureViewBaseLayer;
+        descriptor.layerCount = 1;
+        descriptor.baseMipLevel = textureViewBaseLevel;
+        descriptor.levelCount = 1;
+        dawn::TextureView textureView = texture.CreateTextureView(&descriptor);
+
+        dawn::ShaderModule vsModule = CreateDefaultVertexShaderModule(device);
+
+        // Clear textureView with Red(255, 0, 0, 255) and render Green(0, 255, 0, 255) into it
+        dawn::RenderPassDescriptor renderPassInfo = device.CreateRenderPassDescriptorBuilder()
+            .SetColorAttachment(0, textureView, dawn::LoadOp::Clear)
+            .SetColorAttachmentClearColor(0, 1.0, 0.0, 0.0, 1.0)
+            .GetResult();
+
+        const char* oneColorFragmentShader = R"(
+            #version 450
+            layout(location = 0) out vec4 fragColor;
+
+            void main() {
+                fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+            }
+        )";
+        dawn::ShaderModule oneColorFsModule =
+            utils::CreateShaderModule(device, dawn::ShaderStage::Fragment, oneColorFragmentShader);
+
+        dawn::RenderPipeline oneColorPipeline = device.CreateRenderPipelineBuilder()
+            .SetColorAttachmentFormat(0, kDefaultFormat)
+            .SetStage(dawn::ShaderStage::Vertex, vsModule, "main")
+            .SetStage(dawn::ShaderStage::Fragment, oneColorFsModule, "main")
+            .GetResult();
+        dawn::CommandBufferBuilder commandBufferBuilder = device.CreateCommandBufferBuilder();
+        {
+            dawn::RenderPassEncoder pass =
+                commandBufferBuilder.BeginRenderPass(renderPassInfo);
+            pass.SetRenderPipeline(oneColorPipeline);
+            pass.DrawArrays(6, 1, 0, 0);
+            pass.EndPass();
+        }
+
+        dawn::CommandBuffer commands = commandBufferBuilder.GetResult();
+        queue.Submit(1, &commands);
+
+        // Check if the right pixels (Green) have been written into the right part of the texture.
+        uint32_t textureViewWidth = textureWidthLevel0 >> textureViewBaseLevel;
+        uint32_t textureViewHeight = textureHeightLevel0 >> textureViewBaseLevel;
+        uint32_t rowPitch = Align(kBytesPerTexel * textureWidthLevel0, kTextureRowPitchAlignment);
+        uint32_t expectedDataSize =
+            rowPitch / kBytesPerTexel * (textureWidthLevel0 - 1) + textureHeightLevel0;
+        constexpr RGBA8 kExpectedPixel(0, 255, 0, 255);
+        std::vector<RGBA8> expected(expectedDataSize, kExpectedPixel);
+        EXPECT_TEXTURE_RGBA8_EQ(
+            expected.data(), texture, 0, 0, textureViewWidth, textureViewHeight,
+            textureViewBaseLevel, textureViewBaseLayer);
+    }
+};
+
+// Test rendering into a 2D texture view created on a mipmap level of a 2D texture.
+TEST_P(TextureViewRenderingTest, Texture2DViewOnALevelOf2DTextureAsColorAttachment) {
+    constexpr uint32_t kLayers = 1;
+    constexpr uint32_t kMipLevels = 4;
+    constexpr uint32_t kBaseLayer = 0;
+
+    // Rendering into the first level
+    {
+        constexpr uint32_t kBaseLevel = 0;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2D, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+
+    // Rendering into the last level
+    {
+        constexpr uint32_t kBaseLevel = kMipLevels - 1;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2D, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+}
+
+// Test rendering into a 2D texture view created on a layer of a 2D array texture.
+TEST_P(TextureViewRenderingTest, Texture2DViewOnALayerOf2DArrayTextureAsColorAttachment) {
+    constexpr uint32_t kMipLevels = 1;
+    constexpr uint32_t kBaseLevel = 0;
+    constexpr uint32_t kLayers = 10;
+
+    // Rendering into the first layer
+    {
+        constexpr uint32_t kBaseLayer = 0;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2D, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+
+    // Rendering into the last layer
+    {
+        constexpr uint32_t kBaseLayer = kLayers - 1;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2D, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+
+}
+
+// Test rendering into a 1-layer 2D array texture view created on a mipmap level of a 2D texture.
+TEST_P(TextureViewRenderingTest, Texture2DArrayViewOnALevelOf2DTextureAsColorAttachment) {
+    constexpr uint32_t kLayers = 1;
+    constexpr uint32_t kMipLevels = 4;
+    constexpr uint32_t kBaseLayer = 0;
+
+    // Rendering into the first level
+    {
+        constexpr uint32_t kBaseLevel = 0;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2DArray, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+
+    // Rendering into the last level
+    {
+        constexpr uint32_t kBaseLevel = kMipLevels - 1;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2DArray, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+}
+
+// Test rendering into a 1-layer 2D array texture view created on a layer of a 2D array texture.
+TEST_P(TextureViewRenderingTest, Texture2DArrayViewOnALayerOf2DArrayTextureAsColorAttachment) {
+    constexpr uint32_t kMipLevels = 1;
+    constexpr uint32_t kBaseLevel = 0;
+    constexpr uint32_t kLayers = 10;
+
+    // Rendering into the first layer
+    {
+        constexpr uint32_t kBaseLayer = 0;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2DArray, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+
+    // Rendering into the last layer
+    {
+        constexpr uint32_t kBaseLayer = kLayers - 1;
+        TextureLayerAsColorAttachmentTest(
+            dawn::TextureViewDimension::e2DArray, kLayers, kMipLevels, kBaseLayer, kBaseLevel);
+    }
+}
+
+DAWN_INSTANTIATE_TEST(TextureViewSamplingTest, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)
+
+// TODO(jiawei.shao@intel.com): support using a layer of a texture as color attachment on OpenGL
+DAWN_INSTANTIATE_TEST(TextureViewRenderingTest, D3D12Backend, MetalBackend, VulkanBackend)