Add checks on the texture view used as color or depth stencil attachment

This patch adds checks when we set a texture view as color or depth
stencil attachments:
1. The mipmap level of the texture view must be 1 as it is limited in
   D3D12, Metal and Vulkan.
2. The array layer count of the texture view must be 1 as currently we
   do not plan to support layered rendering in WebGPU.
3. The format of the texture view must be color renderable when it is
   used as a color attachment.
4. The format of the texture view must be a depth stencil format when it
   is used as a depth stencil attachment.

BUG=dawn:16
TEST=dawn_unittests

Change-Id: Ibce3bda20d49a725c26796aa627c5067532761ad
Reviewed-on: https://dawn-review.googlesource.com/c/2661
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 6147d24..1426095 100644
--- a/src/dawn_native/RenderPassDescriptor.cpp
+++ b/src/dawn_native/RenderPassDescriptor.cpp
@@ -83,6 +83,24 @@
     RenderPassDescriptorBuilder::RenderPassDescriptorBuilder(DeviceBase* device) : Builder(device) {
     }
 
+    bool RenderPassDescriptorBuilder::CheckArrayLayersAndLevelCountForAttachment(
+        const TextureViewBase* textureView) {
+        // Currently we do not support layered rendering.
+        if (textureView->GetLayerCount() > 1) {
+            HandleError(
+                "The layer count of the texture view used as attachment cannot be greater than 1");
+            return false;
+        }
+
+        if (textureView->GetLevelCount() > 1) {
+            HandleError(
+                "The mipmap level count of the texture view used as attachment cannot be greater "
+                "than 1");
+            return false;
+        }
+        return true;
+    }
+
     RenderPassDescriptorBase* RenderPassDescriptorBuilder::GetResultImpl() {
         auto CheckOrSetSize = [this](const TextureViewBase* attachment) -> bool {
             if (this->mWidth == 0) {
@@ -133,8 +151,13 @@
             return;
         }
 
-        if (TextureFormatHasDepthOrStencil(textureView->GetTexture()->GetFormat())) {
-            HandleError("Using depth stencil texture as color attachment");
+        if (!IsColorRenderableTextureFormat(textureView->GetFormat())) {
+            HandleError(
+                "The format of the texture view used as color attachment is not color renderable");
+            return;
+        }
+
+        if (!CheckArrayLayersAndLevelCountForAttachment(textureView)) {
             return;
         }
 
@@ -162,8 +185,14 @@
     void RenderPassDescriptorBuilder::SetDepthStencilAttachment(TextureViewBase* textureView,
                                                                 dawn::LoadOp depthLoadOp,
                                                                 dawn::LoadOp stencilLoadOp) {
-        if (!TextureFormatHasDepthOrStencil(textureView->GetTexture()->GetFormat())) {
-            HandleError("Using color texture as depth stencil attachment");
+        if (!TextureFormatHasDepthOrStencil(textureView->GetFormat())) {
+            HandleError(
+                "The format of the texture view used as depth stencil attachment is not a depth "
+                "stencil format");
+            return;
+        }
+
+        if (!CheckArrayLayersAndLevelCountForAttachment(textureView)) {
             return;
         }
 
diff --git a/src/dawn_native/RenderPassDescriptor.h b/src/dawn_native/RenderPassDescriptor.h
index 4079a15..9ee4cd0 100644
--- a/src/dawn_native/RenderPassDescriptor.h
+++ b/src/dawn_native/RenderPassDescriptor.h
@@ -94,6 +94,8 @@
       private:
         friend class RenderPassDescriptorBase;
 
+        bool CheckArrayLayersAndLevelCountForAttachment(const TextureViewBase* textureView);
+
         std::bitset<kMaxColorAttachments> mColorAttachmentsSet;
         std::array<RenderPassColorAttachmentInfo, kMaxColorAttachments> mColorAttachments;
 
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 0baedf2..0923fcc 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -230,6 +230,26 @@
         }
     }
 
+    bool IsColorRenderableTextureFormat(dawn::TextureFormat format) {
+        switch (format) {
+            case dawn::TextureFormat::B8G8R8A8Unorm:
+            case dawn::TextureFormat::R8G8B8A8Uint:
+            case dawn::TextureFormat::R8G8B8A8Unorm:
+            case dawn::TextureFormat::R8G8Uint:
+            case dawn::TextureFormat::R8G8Unorm:
+            case dawn::TextureFormat::R8Uint:
+            case dawn::TextureFormat::R8Unorm:
+                return true;
+
+            case dawn::TextureFormat::D32FloatS8Uint:
+                return false;
+
+            default:
+                UNREACHABLE();
+                return false;
+        }
+    }
+
     // TextureBase
 
     TextureBase::TextureBase(DeviceBase* device, const TextureDescriptor* descriptor)
@@ -277,7 +297,11 @@
     // TextureViewBase
 
     TextureViewBase::TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor)
-        : ObjectBase(texture->GetDevice()), mTexture(texture) {
+        : ObjectBase(texture->GetDevice()),
+          mTexture(texture),
+          mFormat(descriptor->format),
+          mLevelCount(descriptor->levelCount),
+          mLayerCount(descriptor->layerCount) {
     }
 
     const TextureBase* TextureViewBase::GetTexture() const {
@@ -288,4 +312,15 @@
         return mTexture.Get();
     }
 
+    dawn::TextureFormat TextureViewBase::GetFormat() const {
+        return mFormat;
+    }
+
+    uint32_t TextureViewBase::GetLevelCount() const {
+        return mLevelCount;
+    }
+
+    uint32_t TextureViewBase::GetLayerCount() const {
+        return mLayerCount;
+    }
 }  // namespace dawn_native
diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h
index ab34e96..f1194f0 100644
--- a/src/dawn_native/Texture.h
+++ b/src/dawn_native/Texture.h
@@ -32,6 +32,7 @@
     bool TextureFormatHasDepth(dawn::TextureFormat format);
     bool TextureFormatHasStencil(dawn::TextureFormat format);
     bool TextureFormatHasDepthOrStencil(dawn::TextureFormat format);
+    bool IsColorRenderableTextureFormat(dawn::TextureFormat format);
 
     static constexpr dawn::TextureUsageBit kReadOnlyTextureUsages =
         dawn::TextureUsageBit::TransferSrc | dawn::TextureUsageBit::Sampled |
@@ -74,8 +75,16 @@
         const TextureBase* GetTexture() const;
         TextureBase* GetTexture();
 
+        dawn::TextureFormat GetFormat() const;
+        uint32_t GetLevelCount() const;
+        uint32_t GetLayerCount() const;
+
       private:
         Ref<TextureBase> mTexture;
+
+        dawn::TextureFormat mFormat;
+        uint32_t mLevelCount;
+        uint32_t mLayerCount;
     };
 
 }  // namespace dawn_native
diff --git a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 63be562..e98d1c0 100644
--- a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -21,19 +21,33 @@
 class RenderPassDescriptorValidationTest : public ValidationTest {
 };
 
-dawn::TextureView Create2DAttachment(dawn::Device& device, uint32_t width, uint32_t height, dawn::TextureFormat format) {
+dawn::Texture CreateTexture(dawn::Device& device,
+                            dawn::TextureDimension dimension,
+                            dawn::TextureFormat format,
+                            uint32_t width,
+                            uint32_t height,
+                            uint32_t arrayLayer,
+                            uint32_t levelCount) {
     dawn::TextureDescriptor descriptor;
-    descriptor.dimension = dawn::TextureDimension::e2D;
+    descriptor.dimension = dimension;
     descriptor.size.width = width;
     descriptor.size.height = height;
     descriptor.size.depth = 1;
-    descriptor.arrayLayer = 1;
+    descriptor.arrayLayer = arrayLayer;
     descriptor.format = format;
-    descriptor.levelCount = 1;
+    descriptor.levelCount = levelCount;
     descriptor.usage = dawn::TextureUsageBit::OutputAttachment;
-    dawn::Texture attachment = device.CreateTexture(&descriptor);
 
-    return attachment.CreateDefaultTextureView();
+    return device.CreateTexture(&descriptor);
+}
+
+dawn::TextureView Create2DAttachment(dawn::Device& device,
+                                     uint32_t width,
+                                     uint32_t height,
+                                     dawn::TextureFormat format) {
+    dawn::Texture texture = CreateTexture(
+        device, dawn::TextureDimension::e2D, format, width, height, 1, 1);
+    return texture.CreateDefaultTextureView();
 }
 
 // A render pass with no attachments isn't valid
@@ -177,6 +191,213 @@
     }
 }
 
+// Currently only texture views with layerCount == 1 are allowed to be color and depth stencil
+// attachments
+TEST_F(RenderPassDescriptorValidationTest, TextureViewLayerCountForColorAndDepthStencil) {
+    constexpr uint32_t kLevelCount = 1;
+    constexpr uint32_t kSize = 32;
+    constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::R8G8B8A8Unorm;
+    constexpr dawn::TextureFormat kDepthStencilFormat = dawn::TextureFormat::D32FloatS8Uint;
+
+    constexpr uint32_t kArrayLayers = 10;
+
+    dawn::Texture colorTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers, kLevelCount);
+    dawn::Texture depthStencilTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize, kArrayLayers,
+        kLevelCount);
+
+    dawn::TextureViewDescriptor baseDescriptor;
+    baseDescriptor.dimension = dawn::TextureViewDimension::e2DArray;
+    baseDescriptor.baseArrayLayer = 0;
+    baseDescriptor.layerCount = kArrayLayers;
+    baseDescriptor.baseMipLevel = 0;
+    baseDescriptor.levelCount = kLevelCount;
+
+    // Using 2D array texture view with layerCount > 1 is not allowed for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.layerCount = 5;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeError(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D array texture view with layerCount > 1 is not allowed for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.layerCount = 5;
+
+        dawn::TextureView depthStencilView = depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeError(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(depthStencilView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D array texture view that covers the first layer of the texture is OK for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.baseArrayLayer = 0;
+        descriptor.layerCount = 1;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D array texture view that covers the first layer is OK for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.baseArrayLayer = 0;
+        descriptor.layerCount = 1;
+
+        dawn::TextureView depthStencilTextureView =
+            depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(
+                depthStencilTextureView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D array texture view that covers the last layer is OK for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.baseArrayLayer = kArrayLayers - 1;
+        descriptor.layerCount = 1;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D array texture view that covers the last layer is OK for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.baseArrayLayer = kArrayLayers - 1;
+        descriptor.layerCount = 1;
+
+        dawn::TextureView depthStencilTextureView =
+            depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(
+                depthStencilTextureView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+}
+
+// Only 2D texture views with levelCount == 1 are allowed to be color attachments
+TEST_F(RenderPassDescriptorValidationTest, TextureViewLevelCountForColorAndDepthStencil) {
+    constexpr uint32_t kArrayLayers = 1;
+    constexpr uint32_t kSize = 32;
+    constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::R8G8B8A8Unorm;
+    constexpr dawn::TextureFormat kDepthStencilFormat = dawn::TextureFormat::D32FloatS8Uint;
+
+    constexpr uint32_t kLevelCount = 4;
+
+    dawn::Texture colorTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers, kLevelCount);
+    dawn::Texture depthStencilTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kDepthStencilFormat, kSize, kSize, kArrayLayers,
+        kLevelCount);
+
+    dawn::TextureViewDescriptor baseDescriptor;
+    baseDescriptor.dimension = dawn::TextureViewDimension::e2D;
+    baseDescriptor.baseArrayLayer = 0;
+    baseDescriptor.layerCount = kArrayLayers;
+    baseDescriptor.baseMipLevel = 0;
+    baseDescriptor.levelCount = kLevelCount;
+
+    // Using 2D texture view with levelCount > 1 is not allowed for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.levelCount = 2;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeError(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D texture view with levelCount > 1 is not allowed for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.levelCount = 2;
+
+        dawn::TextureView depthStencilView = depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeError(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(depthStencilView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D texture view that covers the first level of the texture is OK for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.baseMipLevel = 0;
+        descriptor.levelCount = 1;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D texture view that covers the first level is OK for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.baseMipLevel = 0;
+        descriptor.levelCount = 1;
+
+        dawn::TextureView depthStencilTextureView =
+            depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(
+                depthStencilTextureView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D texture view that covers the last level is OK for color
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kColorFormat;
+        descriptor.baseMipLevel = kLevelCount - 1;
+        descriptor.levelCount = 1;
+
+        dawn::TextureView colorTextureView = colorTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetColorAttachment(0, colorTextureView, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+
+    // Using 2D texture view that covers the last level is OK for depth stencil
+    {
+        dawn::TextureViewDescriptor descriptor = baseDescriptor;
+        descriptor.format = kDepthStencilFormat;
+        descriptor.baseMipLevel = kLevelCount - 1;
+        descriptor.levelCount = 1;
+
+        dawn::TextureView depthStencilTextureView =
+            depthStencilTexture.CreateTextureView(&descriptor);
+        AssertWillBeSuccess(device.CreateRenderPassDescriptorBuilder())
+            .SetDepthStencilAttachment(
+                depthStencilTextureView, dawn::LoadOp::Clear, dawn::LoadOp::Clear)
+            .GetResult();
+    }
+}
+
 // TODO(cwallez@chromium.org): Constraints on attachment aliasing?
 
 } // anonymous namespace