Add tests for using a subresource as output attachment

This adds tests that it is possible to render into a subresource bound
as an output attachment. Attaching a subresource as an output attachment
is still not implemented correctly on OpenGL and Metal.

This CL also adds a helper to DawnTest to allow checking stencil buffer
contents.

Bug: dawn:430
Change-Id: Ic8652dd9da8d3c7a47d7b0548306e2054f642e7d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22164
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 9f6ddc1..b55bde4 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -289,6 +289,7 @@
     "end2end/ScissorTests.cpp",
     "end2end/ShaderFloat16Tests.cpp",
     "end2end/StorageTextureTests.cpp",
+    "end2end/SubresourceOutputAttachmentTests.cpp",
     "end2end/TextureFormatTests.cpp",
     "end2end/TextureSubresourceTests.cpp",
     "end2end/TextureViewTests.cpp",
diff --git a/src/tests/end2end/SubresourceOutputAttachmentTests.cpp b/src/tests/end2end/SubresourceOutputAttachmentTests.cpp
new file mode 100644
index 0000000..4266865
--- /dev/null
+++ b/src/tests/end2end/SubresourceOutputAttachmentTests.cpp
@@ -0,0 +1,162 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/DawnTest.h"
+
+#include "common/Assert.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+// Test that rendering to a subresource of a texture works.
+class SubresourceOutputAttachmentTest : public DawnTest {
+    constexpr static uint32_t kRTSize = 2;
+
+  protected:
+    enum class Type { Color, Depth, Stencil };
+
+    void DoSingleTest(Type type,
+                      wgpu::TextureFormat format,
+                      wgpu::Texture renderTarget,
+                      uint32_t textureSize,
+                      uint32_t baseArrayLayer,
+                      uint32_t baseMipLevel) {
+        wgpu::TextureViewDescriptor renderTargetViewDesc;
+        renderTargetViewDesc.baseArrayLayer = baseArrayLayer;
+        renderTargetViewDesc.arrayLayerCount = 1;
+        renderTargetViewDesc.baseMipLevel = baseMipLevel;
+        renderTargetViewDesc.mipLevelCount = 1;
+        wgpu::TextureView renderTargetView = renderTarget.CreateView(&renderTargetViewDesc);
+
+        RGBA8 expectedColor(0, 255, 0, 255);
+        float expectedDepth = 0.3f;
+        uint8_t expectedStencil = 7;
+
+        utils::ComboRenderPassDescriptor renderPass = [&]() {
+            switch (type) {
+                case Type::Color: {
+                    utils::ComboRenderPassDescriptor renderPass({renderTargetView});
+                    renderPass.cColorAttachments[0].clearColor = {
+                        static_cast<float>(expectedColor.r) / 255.f,
+                        static_cast<float>(expectedColor.g) / 255.f,
+                        static_cast<float>(expectedColor.b) / 255.f,
+                        static_cast<float>(expectedColor.a) / 255.f,
+                    };
+                    return renderPass;
+                }
+                case Type::Depth: {
+                    utils::ComboRenderPassDescriptor renderPass({}, renderTargetView);
+                    renderPass.cDepthStencilAttachmentInfo.clearDepth = expectedDepth;
+                    return renderPass;
+                }
+                case Type::Stencil: {
+                    utils::ComboRenderPassDescriptor renderPass({}, renderTargetView);
+                    renderPass.cDepthStencilAttachmentInfo.clearStencil = expectedStencil;
+                    return renderPass;
+                }
+                default:
+                    UNREACHABLE();
+            }
+        }();
+
+        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder passEncoder = commandEncoder.BeginRenderPass(&renderPass);
+        passEncoder.EndPass();
+        wgpu::CommandBuffer commands = commandEncoder.Finish();
+        queue.Submit(1, &commands);
+
+        const uint32_t renderTargetSize = textureSize >> baseMipLevel;
+        switch (type) {
+            case Type::Color: {
+                std::vector<RGBA8> expected(renderTargetSize * renderTargetSize, expectedColor);
+                EXPECT_TEXTURE_RGBA8_EQ(expected.data(), renderTarget, 0, 0, renderTargetSize,
+                                        renderTargetSize, baseMipLevel, baseArrayLayer);
+                break;
+            }
+            case Type::Depth: {
+                std::vector<float> expected(renderTargetSize * renderTargetSize, expectedDepth);
+                EXPECT_TEXTURE_FLOAT_EQ(expected.data(), renderTarget, 0, 0, renderTargetSize,
+                                        renderTargetSize, baseMipLevel, baseArrayLayer);
+                break;
+            }
+            case Type::Stencil:
+                // TODO(crbug.com/dawn/439): sample / copy of the stencil aspect.
+            default:
+                UNREACHABLE();
+        }
+    }
+
+    void DoTest(Type type) {
+        constexpr uint32_t kArrayLayerCount = 5;
+        constexpr uint32_t kMipLevelCount = 4;
+
+        wgpu::TextureFormat format;
+        switch (type) {
+            case Type::Color:
+                format = wgpu::TextureFormat::RGBA8Unorm;
+                break;
+            case Type::Depth:
+                format = wgpu::TextureFormat::Depth32Float;
+                break;
+            case Type::Stencil:
+                format = wgpu::TextureFormat::Depth24PlusStencil8;
+                break;
+            default:
+                UNREACHABLE();
+        }
+
+        constexpr uint32_t kTextureSize = kRTSize << (kMipLevelCount - 1);
+
+        wgpu::TextureDescriptor renderTargetDesc;
+        renderTargetDesc.dimension = wgpu::TextureDimension::e2D;
+        renderTargetDesc.size.width = kTextureSize;
+        renderTargetDesc.size.height = kTextureSize;
+        renderTargetDesc.size.depth = 1;
+        renderTargetDesc.arrayLayerCount = kArrayLayerCount;
+        renderTargetDesc.sampleCount = 1;
+        renderTargetDesc.format = format;
+        renderTargetDesc.mipLevelCount = kMipLevelCount;
+        renderTargetDesc.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc;
+
+        wgpu::Texture renderTarget = device.CreateTexture(&renderTargetDesc);
+
+        // Test rendering into the first, middle, and last of each of array layer and mip level.
+        for (uint32_t arrayLayer : {0u, kArrayLayerCount / 2, kArrayLayerCount - 1u}) {
+            for (uint32_t mipLevel : {0u, kMipLevelCount / 2, kMipLevelCount - 1u}) {
+                DoSingleTest(type, format, renderTarget, kTextureSize, arrayLayer, mipLevel);
+            }
+        }
+    }
+};
+
+// Test rendering into a subresource of a color texture
+TEST_P(SubresourceOutputAttachmentTest, ColorTexture) {
+    DoTest(Type::Color);
+}
+
+// Test rendering into a subresource of a depth texture
+TEST_P(SubresourceOutputAttachmentTest, DepthTexture) {
+    DoTest(Type::Depth);
+}
+
+// Test rendering into a subresource of a stencil texture
+// TODO(crbug.com/dawn/439): sample / copy of the stencil aspect.
+TEST_P(SubresourceOutputAttachmentTest, DISABLED_StencilTexture) {
+    DoTest(Type::Stencil);
+}
+
+// TODO(crbug.com/dawn/430): Implemented incorrectly on OpenGL and Metal.
+DAWN_INSTANTIATE_TEST(SubresourceOutputAttachmentTest,
+                      D3D12Backend(),
+                      D3D12Backend({}, {"use_d3d12_render_pass"}),
+                      VulkanBackend());