Add validations to multisampled color attachments and resolve targets

This patch adds validations to the multisampled color attachments and
resolve targets.

The resolve target must be a valid texture view, and it must follow
the following validations:

According to Vulkan SPEC:
1. If we set resolve target, the texture of the color attachment must
   be a multisampled texture, and the texture of the resolve target
   must be a non-multisampled texture.
2. The format of the resolve target must be the same as that of the
   color attachment.
3. The sample count of all the color attachments must be same.
4. The resolve target must have dawn::TextureUsage::OutputAttachment
   usage bit set when it was created.

On D3D12:
1. ID3D12GraphicsCommandList::ResolveSubresource() method only allow
   resolving in a subresource of a texture, thus the level count and
   mipmap level count of the resolve target can only be 1.
2. ID3D12GraphicsCommandList::ResolveSubresource() method requires
   the dimension of the source and destination must match.

BUG=dawn:56
TEST=dawn_unittests

Change-Id: I080415bef0d600600083a95f641815188798dca3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/5340
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 89297eb..d2c1543 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -230,20 +230,80 @@
             return {};
         }
 
+        MaybeError ValidateOrSetColorAttachmentSampleCount(const TextureViewBase* colorAttachment,
+                                                           uint32_t* sampleCount) {
+            if (*sampleCount == 0) {
+                *sampleCount = colorAttachment->GetTexture()->GetSampleCount();
+                DAWN_ASSERT(*sampleCount != 0);
+            } else if (*sampleCount != colorAttachment->GetTexture()->GetSampleCount()) {
+                return DAWN_VALIDATION_ERROR("Color attachment sample counts mismatch");
+            }
+
+            return {};
+        }
+
+        MaybeError ValidateResolveTarget(
+            const DeviceBase* device,
+            const RenderPassColorAttachmentDescriptor* colorAttachment) {
+            if (colorAttachment->resolveTarget == nullptr) {
+                return {};
+            }
+
+            DAWN_TRY(device->ValidateObject(colorAttachment->resolveTarget));
+
+            if (!colorAttachment->attachment->GetTexture()->IsMultisampledTexture()) {
+                return DAWN_VALIDATION_ERROR(
+                    "Cannot set resolve target when the sample count of the color attachment is 1");
+            }
+
+            if (colorAttachment->resolveTarget->GetTexture()->IsMultisampledTexture()) {
+                return DAWN_VALIDATION_ERROR("Cannot use multisampled texture as resolve target");
+            }
+
+            if (colorAttachment->resolveTarget->GetLayerCount() > 1) {
+                return DAWN_VALIDATION_ERROR(
+                    "The array layer count of the resolve target must be 1");
+            }
+
+            if (colorAttachment->resolveTarget->GetLevelCount() > 1) {
+                return DAWN_VALIDATION_ERROR("The mip level count of the resolve target must be 1");
+            }
+
+            uint32_t colorAttachmentBaseMipLevel = colorAttachment->attachment->GetBaseMipLevel();
+            const Extent3D& colorTextureSize = colorAttachment->attachment->GetTexture()->GetSize();
+            uint32_t colorAttachmentWidth = colorTextureSize.width >> colorAttachmentBaseMipLevel;
+            uint32_t colorAttachmentHeight = colorTextureSize.height >> colorAttachmentBaseMipLevel;
+
+            uint32_t resolveTargetBaseMipLevel = colorAttachment->resolveTarget->GetBaseMipLevel();
+            const Extent3D& resolveTextureSize =
+                colorAttachment->resolveTarget->GetTexture()->GetSize();
+            uint32_t resolveTargetWidth = resolveTextureSize.width >> resolveTargetBaseMipLevel;
+            uint32_t resolveTargetHeight = resolveTextureSize.height >> resolveTargetBaseMipLevel;
+            if (colorAttachmentWidth != resolveTargetWidth ||
+                colorAttachmentHeight != resolveTargetHeight) {
+                return DAWN_VALIDATION_ERROR(
+                    "The size of the resolve target must be the same as the color attachment");
+            }
+
+            dawn::TextureFormat resolveTargetFormat = colorAttachment->resolveTarget->GetFormat();
+            if (resolveTargetFormat != colorAttachment->attachment->GetFormat()) {
+                return DAWN_VALIDATION_ERROR(
+                    "The format of the resolve target must be the same as the color attachment");
+            }
+
+            return {};
+        }
+
         MaybeError ValidateRenderPassColorAttachment(
             const DeviceBase* device,
             const RenderPassColorAttachmentDescriptor* colorAttachment,
             uint32_t* width,
-            uint32_t* height) {
+            uint32_t* height,
+            uint32_t* sampleCount) {
             DAWN_ASSERT(colorAttachment != nullptr);
 
             DAWN_TRY(device->ValidateObject(colorAttachment->attachment));
 
-            // TODO(jiawei.shao@intel.com): support resolve target for multisample color attachment.
-            if (colorAttachment->resolveTarget != nullptr) {
-                return DAWN_VALIDATION_ERROR("Resolve target is not supported now");
-            }
-
             const TextureViewBase* attachment = colorAttachment->attachment;
             if (!IsColorRenderableTextureFormat(attachment->GetFormat())) {
                 return DAWN_VALIDATION_ERROR(
@@ -251,6 +311,10 @@
                     "renderable");
             }
 
+            DAWN_TRY(ValidateOrSetColorAttachmentSampleCount(attachment, sampleCount));
+
+            DAWN_TRY(ValidateResolveTarget(device, colorAttachment));
+
             DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment));
             DAWN_TRY(ValidateOrSetAttachmentSize(attachment, width, height));
 
@@ -287,9 +351,10 @@
                 return DAWN_VALIDATION_ERROR("Setting color attachments out of bounds");
             }
 
+            uint32_t sampleCount = 0;
             for (uint32_t i = 0; i < renderPass->colorAttachmentCount; ++i) {
                 DAWN_TRY(ValidateRenderPassColorAttachment(device, renderPass->colorAttachments[i],
-                                                           width, height));
+                                                           width, height, &sampleCount));
             }
 
             if (renderPass->depthStencilAttachment != nullptr) {
@@ -871,6 +936,12 @@
             RenderPassColorAttachmentInfo* colorAttachment = &renderPass->colorAttachments[i];
             TextureBase* texture = colorAttachment->view->GetTexture();
             usageTracker.TextureUsedAs(texture, dawn::TextureUsageBit::OutputAttachment);
+
+            TextureViewBase* resolveTarget = colorAttachment->resolveTarget.Get();
+            if (resolveTarget != nullptr) {
+                usageTracker.TextureUsedAs(resolveTarget->GetTexture(),
+                                           dawn::TextureUsageBit::OutputAttachment);
+            }
         }
 
         if (renderPass->hasDepthStencilAttachment) {
diff --git a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
index 03c4e5e..b8130d3 100644
--- a/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPassDescriptorValidationTests.cpp
@@ -46,17 +46,19 @@
                             uint32_t width,
                             uint32_t height,
                             uint32_t arrayLayerCount,
-                            uint32_t mipLevelCount) {
+                            uint32_t mipLevelCount,
+                            uint32_t sampleCount = 1,
+                            dawn::TextureUsageBit usage = dawn::TextureUsageBit::OutputAttachment) {
     dawn::TextureDescriptor descriptor;
     descriptor.dimension = dimension;
     descriptor.size.width = width;
     descriptor.size.height = height;
     descriptor.size.depth = 1;
     descriptor.arrayLayerCount = arrayLayerCount;
-    descriptor.sampleCount = 1;
+    descriptor.sampleCount = sampleCount;
     descriptor.format = format;
     descriptor.mipLevelCount = mipLevelCount;
-    descriptor.usage = dawn::TextureUsageBit::OutputAttachment;
+    descriptor.usage = usage;
 
     return device.CreateTexture(&descriptor);
 }
@@ -378,32 +380,209 @@
     }
 }
 
-// Tests on the resolve target of RenderPassColorAttachmentDescriptor.
-// TODO(jiawei.shao@intel.com): add more tests when we support multisample color attachments.
-TEST_F(RenderPassDescriptorValidationTest, ResolveTarget) {
-    constexpr uint32_t kArrayLayers = 1;
-    constexpr uint32_t kSize = 32;
-    constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::R8G8B8A8Unorm;
-
-    constexpr uint32_t kLevelCount = 1;
+// It is not allowed to set resolve target when the color attachment is non-multisampled.
+TEST_F(RenderPassDescriptorValidationTest, NonMultisampledColorWithResolveTarget) {
+    static constexpr uint32_t kArrayLayers = 1;
+    static constexpr uint32_t kLevelCount = 1;
+    static constexpr uint32_t kSize = 32;
+    static constexpr uint32_t kSampleCount = 1;
+    static constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::R8G8B8A8Unorm;
 
     dawn::Texture colorTexture = CreateTexture(
-        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers, kLevelCount);
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+        kLevelCount, kSampleCount);
+    dawn::Texture resolveTargetTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+        kLevelCount, kSampleCount);
+    dawn::TextureView colorTextureView = colorTexture.CreateDefaultTextureView();
+    dawn::TextureView resolveTargetTextureView = resolveTargetTexture.CreateDefaultTextureView();
 
-    dawn::Texture resolveTexture = CreateTexture(
-        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers, kLevelCount);
+    utils::ComboRenderPassDescriptor renderPass({colorTextureView});
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTargetTextureView;
+    AssertBeginRenderPassError(&renderPass);
+}
 
-    // It is not allowed to set resolve target when the sample count of the color attachment is 1.
+class MultisampledRenderPassDescriptorValidationTest : public RenderPassDescriptorValidationTest {
+  public:
+    utils::ComboRenderPassDescriptor CreateMultisampledRenderPass() {
+        return utils::ComboRenderPassDescriptor({CreateMultisampledColorTextureView()});
+    }
+
+    dawn::TextureView CreateMultisampledColorTextureView() {
+        return CreateColorTextureView(kSampleCount);
+    }
+
+    dawn::TextureView CreateNonMultisampledColorTextureView() {
+        return CreateColorTextureView(1);
+    }
+
+    static constexpr uint32_t kArrayLayers = 1;
+    static constexpr uint32_t kLevelCount = 1;
+    static constexpr uint32_t kSize = 32;
+    static constexpr uint32_t kSampleCount = 4;
+    static constexpr dawn::TextureFormat kColorFormat = dawn::TextureFormat::R8G8B8A8Unorm;
+
+  private:
+    dawn::TextureView CreateColorTextureView(uint32_t sampleCount) {
+        dawn::Texture colorTexture = CreateTexture(
+            device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+            kLevelCount, sampleCount);
+
+        return colorTexture.CreateDefaultTextureView();
+    }
+};
+
+// Tests on the use of multisampled textures as color attachments
+TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledColorAttachments) {
+    dawn::TextureView colorTextureView = CreateNonMultisampledColorTextureView();
+    dawn::TextureView resolveTargetTextureView = CreateNonMultisampledColorTextureView();
+    dawn::TextureView multisampledColorTextureView = CreateMultisampledColorTextureView();
+
+    // It is allowed to use a multisampled color attachment without setting resolve target.
     {
-        dawn::TextureView colorTextureView = colorTexture.CreateDefaultTextureView();
-        dawn::TextureView resolveTargetTextureView = resolveTexture.CreateDefaultTextureView();
+        utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+        AssertBeginRenderPassSuccess(&renderPass);
+    }
 
-        utils::ComboRenderPassDescriptor renderPass({colorTextureView});
-        renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTargetTextureView;
+    // It is not allowed to use multiple color attachments with different sample counts.
+    {
+        utils::ComboRenderPassDescriptor renderPass(
+            {multisampledColorTextureView, colorTextureView});
         AssertBeginRenderPassError(&renderPass);
     }
 }
 
+// It is not allowed to use a multisampled resolve target.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledResolveTarget) {
+    dawn::TextureView multisampledResolveTargetView = CreateMultisampledColorTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = multisampledResolveTargetView;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// It is not allowed to use a resolve target with array layer count > 1.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetArrayLayerMoreThanOne) {
+    constexpr uint32_t kArrayLayers2 = 2;
+    dawn::Texture resolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers2,
+        kLevelCount);
+    dawn::TextureView resolveTextureView = resolveTexture.CreateDefaultTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTextureView;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// It is not allowed to use a resolve target with mipmap level count > 1.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetMipmapLevelMoreThanOne) {
+    constexpr uint32_t kLevelCount2 = 2;
+    dawn::Texture resolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+        kLevelCount2);
+    dawn::TextureView resolveTextureView = resolveTexture.CreateDefaultTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTextureView;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// It is not allowed to use a resolve target which is created from a texture whose usage does not
+// include dawn::TextureUsageBit::OutputAttachment.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetUsageNoOutputAttachment) {
+    constexpr dawn::TextureUsageBit kUsage =
+        dawn::TextureUsageBit::TransferDst | dawn::TextureUsageBit::TransferSrc;
+    dawn::Texture nonColorUsageResolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+        kLevelCount, 1, kUsage);
+    dawn::TextureView nonColorUsageResolveTextureView =
+        nonColorUsageResolveTexture.CreateDefaultTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = nonColorUsageResolveTextureView;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// It is not allowed to use a resolve target which is in error state.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetInErrorState) {
+    dawn::Texture resolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize, kSize, kArrayLayers,
+        kLevelCount);
+    dawn::TextureViewDescriptor errorTextureView;
+    errorTextureView.dimension = dawn::TextureViewDimension::e2D;
+    errorTextureView.format = kColorFormat;
+    errorTextureView.baseArrayLayer = kArrayLayers + 1;
+    ASSERT_DEVICE_ERROR(
+        dawn::TextureView errorResolveTarget =
+        resolveTexture.CreateTextureView(&errorTextureView));
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = errorResolveTarget;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// It is allowed to use a multisampled color attachment and a non-multisampled resolve target.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, MultisampledColorWithResolveTarget) {
+    dawn::TextureView resolveTargetTextureView = CreateNonMultisampledColorTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTargetTextureView;
+    AssertBeginRenderPassSuccess(&renderPass);
+}
+
+// It is not allowed to use a resolve target in a format different from the color attachment.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ResolveTargetDifferentFormat) {
+    constexpr dawn::TextureFormat kColorFormat2 = dawn::TextureFormat::B8G8R8A8Unorm;
+    dawn::Texture resolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat2, kSize, kSize, kArrayLayers,
+        kLevelCount);
+    dawn::TextureView resolveTextureView = resolveTexture.CreateDefaultTextureView();
+
+    utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+    renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTextureView;
+    AssertBeginRenderPassError(&renderPass);
+}
+
+// Tests on the size of the resolve target.
+TEST_F(MultisampledRenderPassDescriptorValidationTest, ColorAttachmentResolveTargetCompatibility) {
+    constexpr uint32_t kSize2 = kSize * 2;
+    dawn::Texture resolveTexture = CreateTexture(
+        device, dawn::TextureDimension::e2D, kColorFormat, kSize2, kSize2, kArrayLayers,
+        kLevelCount + 1);
+
+    dawn::TextureViewDescriptor textureViewDescriptor;
+    textureViewDescriptor.nextInChain = nullptr;
+    textureViewDescriptor.dimension = dawn::TextureViewDimension::e2D;
+    textureViewDescriptor.format = kColorFormat;
+    textureViewDescriptor.mipLevelCount = 1;
+    textureViewDescriptor.baseArrayLayer = 0;
+    textureViewDescriptor.arrayLayerCount = 1;
+
+    {
+        dawn::TextureViewDescriptor firstMipLevelDescriptor = textureViewDescriptor;
+        firstMipLevelDescriptor.baseMipLevel = 0;
+
+        dawn::TextureView resolveTextureView =
+            resolveTexture.CreateTextureView(&firstMipLevelDescriptor);
+
+        utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+        renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTextureView;
+        AssertBeginRenderPassError(&renderPass);
+    }
+
+    {
+        dawn::TextureViewDescriptor secondMipLevelDescriptor = textureViewDescriptor;
+        secondMipLevelDescriptor.baseMipLevel = 1;
+
+        dawn::TextureView resolveTextureView =
+            resolveTexture.CreateTextureView(&secondMipLevelDescriptor);
+
+        utils::ComboRenderPassDescriptor renderPass = CreateMultisampledRenderPass();
+        renderPass.cColorAttachmentsInfoPtr[0]->resolveTarget = resolveTextureView;
+        AssertBeginRenderPassSuccess(&renderPass);
+    }
+}
+
 // TODO(cwallez@chromium.org): Constraints on attachment aliasing?
 
 } // anonymous namespace