VideoViewsTests: add test which uses a sampler per plane

This test sees flaky failures.
 - Check the entire checkerboard texture contents to get more
information.
 - Add a test which uses seperate sampling states to see if DX12
driver has difficulty sampling multi-planar formats using a
single sampler.
 - Also, double check support for the NV12 format in case
something in the driver flakily exposes support.

From https://dawn-review.googlesource.com/c/dawn/+/47660

Bug: dawn:733
Change-Id: I766907ff648f1dc35387902a70c3fb65debcaecd
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119343
Commit-Queue: Austin Eng <enga@chromium.org>
Kokoro: Austin Eng <enga@chromium.org>
Reviewed-by: Shrek Shao <shrekshao@google.com>
diff --git a/src/dawn/tests/end2end/VideoViewsTests.cpp b/src/dawn/tests/end2end/VideoViewsTests.cpp
index bbc04a2..eee92bd 100644
--- a/src/dawn/tests/end2end/VideoViewsTests.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests.cpp
@@ -71,45 +71,58 @@
     constexpr uint8_t Yu = kYellowYUVColor[kYUVChromaPlaneIndex].r;
     constexpr uint8_t Yv = kYellowYUVColor[kYUVChromaPlaneIndex].g;
 
+    constexpr uint8_t Wy = kWhiteYUVColor[kYUVLumaPlaneIndex].r;
+    constexpr uint8_t Wu = kWhiteYUVColor[kYUVChromaPlaneIndex].r;
+    constexpr uint8_t Wv = kWhiteYUVColor[kYUVChromaPlaneIndex].g;
+
+    constexpr uint8_t Ry = kRedYUVColor[kYUVLumaPlaneIndex].r;
+    constexpr uint8_t Ru = kRedYUVColor[kYUVChromaPlaneIndex].r;
+    constexpr uint8_t Rv = kRedYUVColor[kYUVChromaPlaneIndex].g;
+
+    constexpr uint8_t By = kBlueYUVColor[kYUVLumaPlaneIndex].r;
+    constexpr uint8_t Bu = kBlueYUVColor[kYUVChromaPlaneIndex].r;
+    constexpr uint8_t Bv = kBlueYUVColor[kYUVChromaPlaneIndex].g;
+
     switch (format) {
         // The first 16 bytes is the luma plane (Y), followed by the chroma plane (UV) which
         // is half the number of bytes (subsampled by 2) but same bytes per line as luma
         // plane.
         case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
             if (isCheckerboard) {
-                constexpr uint8_t Wy = kWhiteYUVColor[kYUVLumaPlaneIndex].r;
-                constexpr uint8_t Wu = kWhiteYUVColor[kYUVChromaPlaneIndex].r;
-                constexpr uint8_t Wv = kWhiteYUVColor[kYUVChromaPlaneIndex].g;
-
-                constexpr uint8_t Ry = kRedYUVColor[kYUVLumaPlaneIndex].r;
-                constexpr uint8_t Ru = kRedYUVColor[kYUVChromaPlaneIndex].r;
-                constexpr uint8_t Rv = kRedYUVColor[kYUVChromaPlaneIndex].g;
-
-                constexpr uint8_t By = kBlueYUVColor[kYUVLumaPlaneIndex].r;
-                constexpr uint8_t Bu = kBlueYUVColor[kYUVChromaPlaneIndex].r;
-                constexpr uint8_t Bv = kBlueYUVColor[kYUVChromaPlaneIndex].g;
-
-                // clang-format off
-                        return {
-                            Wy, Wy, Ry, Ry,  // plane 0, start + 0
-                            Wy, Wy, Ry, Ry,
-                            Yy, Yy, By, By,
-                            Yy, Yy, By, By,
-                            Wu, Wv, Ru, Rv,  // plane 1, start + 16
-                            Yu, Yv, Bu, Bv,
-                        };
-                // clang-format on
+                return {
+                    Wy, Wy, Ry, Ry,  // plane 0, start + 0
+                    Wy, Wy, Ry, Ry,  //
+                    Yy, Yy, By, By,  //
+                    Yy, Yy, By, By,  //
+                    Wu, Wv, Ru, Rv,  // plane 1, start + 16
+                    Yu, Yv, Bu, Bv,  //
+                };
             } else {
-                // clang-format off
-                        return {
-                            Yy, Yy, Yy, Yy,  // plane 0, start + 0
-                            Yy, Yy, Yy, Yy,
-                            Yy, Yy, Yy, Yy,
-                            Yy, Yy, Yy, Yy,
-                            Yu, Yv, Yu, Yv,  // plane 1, start + 16
-                            Yu, Yv, Yu, Yv,
-                        };
-                // clang-format on
+                return {
+                    Yy, Yy, Yy, Yy,  // plane 0, start + 0
+                    Yy, Yy, Yy, Yy,  //
+                    Yy, Yy, Yy, Yy,  //
+                    Yy, Yy, Yy, Yy,  //
+                    Yu, Yv, Yu, Yv,  // plane 1, start + 16
+                    Yu, Yv, Yu, Yv,  //
+                };
+            }
+        case wgpu::TextureFormat::RGBA8Unorm:
+            // Combines both NV12 planes by directly mapping back to RGB: R=Y, G=U, B=V.
+            if (isCheckerboard) {
+                return {
+                    Yy, Yu, Yv, Yy, Yu, Yv, By, Bu, Bv, By, Bu, Bv,  //
+                    Yy, Yu, Yv, Yy, Yu, Yv, By, Bu, Bv, By, Bu, Bv,  //
+                    Wy, Wu, Wv, Wy, Wu, Wv, Ry, Ru, Rv, Ry, Ru, Rv,  //
+                    Wy, Wu, Wv, Wy, Wu, Wv, Ry, Ru, Rv, Ry, Ru, Rv,  //
+                };
+            } else {
+                return {
+                    Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv,  //
+                    Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv,  //
+                    Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv,  //
+                    Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv, Yy, Yu, Yv,  //
+                };
             }
         default:
             UNREACHABLE();
@@ -323,8 +336,8 @@
     mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
 }
 
-// Renders a NV12 "checkerboard" texture into a RGB quad then checks the color at specific
-// points to ensure the image has not been flipped.
+// Renders a NV12 "checkerboard" texture into a RGB quad, then checks the the entire
+// contents to ensure the image has not been flipped.
 TEST_P(VideoViewsTests, NV12SampleYUVtoRGB) {
     std::unique_ptr<VideoViewsTestBackend::PlatformTexture> platformTexture =
         mBackend->CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
@@ -385,27 +398,90 @@
     wgpu::CommandBuffer commands = encoder.Finish();
     queue.Submit(1, &commands);
 
-    // Test four corners of the checkerboard image (YUV color space).
-    utils::RGBA8 yellowYUV(kYellowYUVColor[kYUVLumaPlaneIndex].r,
-                           kYellowYUVColor[kYUVChromaPlaneIndex].r,
-                           kYellowYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
-    EXPECT_PIXEL_RGBA8_EQ(yellowYUV, renderPass.color, 0, 0);  // top left
+    std::vector<uint8_t> expectedData = GetTestTextureData(wgpu::TextureFormat::RGBA8Unorm, true);
+    std::vector<utils::RGBA8> expectedRGBA;
+    for (uint8_t i = 0; i < expectedData.size(); i += 3) {
+        expectedRGBA.push_back({expectedData[i], expectedData[i + 1], expectedData[i + 2], 0xFF});
+    }
 
-    utils::RGBA8 redYUV(kRedYUVColor[kYUVLumaPlaneIndex].r, kRedYUVColor[kYUVChromaPlaneIndex].r,
-                        kRedYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
-    EXPECT_PIXEL_RGBA8_EQ(redYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
-                          kYUVImageDataHeightInTexels - 1);  // bottom right
+    EXPECT_TEXTURE_EQ(expectedRGBA.data(), renderPass.color, {0, 0},
+                      {kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels});
+    mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+}
 
-    utils::RGBA8 blueYUV(kBlueYUVColor[kYUVLumaPlaneIndex].r, kBlueYUVColor[kYUVChromaPlaneIndex].r,
-                         kBlueYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
-    EXPECT_PIXEL_RGBA8_EQ(blueYUV, renderPass.color, kYUVImageDataWidthInTexels - 1,
-                          0);  // top right
+// Renders a NV12 "checkerboard" texture into a RGB quad with two samplers, then checks the the
+// entire contents to ensure the image has not been flipped.
+TEST_P(VideoViewsTests, NV12SampleYUVtoRGBMultipleSamplers) {
+    std::unique_ptr<VideoViewsTestBackend::PlatformTexture> platformTexture =
+        mBackend->CreateVideoTextureForTest(wgpu::TextureFormat::R8BG8Biplanar420Unorm,
+                                            wgpu::TextureUsage::TextureBinding,
+                                            /*isCheckerboard*/ true,
+                                            /*initialized*/ true);
+    ASSERT_NE(platformTexture.get(), nullptr);
+    if (!platformTexture->CanWrapAsWGPUTexture()) {
+        mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
+        GTEST_SKIP() << "Skipped because not supported.";
+    }
 
-    utils::RGBA8 whiteYUV(kWhiteYUVColor[kYUVLumaPlaneIndex].r,
-                          kWhiteYUVColor[kYUVChromaPlaneIndex].r,
-                          kWhiteYUVColor[kYUVChromaPlaneIndex].g, 0xFF);
-    EXPECT_PIXEL_RGBA8_EQ(whiteYUV, renderPass.color, 0,
-                          kYUVImageDataHeightInTexels - 1);  // bottom left
+    wgpu::TextureViewDescriptor lumaViewDesc;
+    lumaViewDesc.format = wgpu::TextureFormat::R8Unorm;
+    lumaViewDesc.aspect = wgpu::TextureAspect::Plane0Only;
+    wgpu::TextureView lumaTextureView = platformTexture->wgpuTexture.CreateView(&lumaViewDesc);
+
+    wgpu::TextureViewDescriptor chromaViewDesc;
+    chromaViewDesc.format = wgpu::TextureFormat::RG8Unorm;
+    chromaViewDesc.aspect = wgpu::TextureAspect::Plane1Only;
+    wgpu::TextureView chromaTextureView = platformTexture->wgpuTexture.CreateView(&chromaViewDesc);
+
+    utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
+    renderPipelineDescriptor.vertex.module = GetTestVertexShaderModule();
+
+    renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
+            @group(0) @binding(0) var sampler0 : sampler;
+            @group(0) @binding(1) var sampler1 : sampler;
+            @group(0) @binding(2) var lumaTexture : texture_2d<f32>;
+            @group(0) @binding(3) var chromaTexture : texture_2d<f32>;
+
+            @fragment
+            fn main(@location(0) texCoord : vec2f) -> @location(0) vec4f {
+               let y : f32 = textureSample(lumaTexture, sampler0, texCoord).r;
+               let u : f32 = textureSample(chromaTexture, sampler1, texCoord).r;
+               let v : f32 = textureSample(chromaTexture, sampler1, texCoord).g;
+               return vec4f(y, u, v, 1.0);
+            })");
+
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
+        device, kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels);
+    renderPipelineDescriptor.cTargets[0].format = renderPass.colorFormat;
+
+    wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&renderPipelineDescriptor);
+
+    wgpu::Sampler sampler0 = device.CreateSampler();
+    wgpu::Sampler sampler1 = device.CreateSampler();
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.SetPipeline(renderPipeline);
+        pass.SetBindGroup(
+            0, utils::MakeBindGroup(
+                   device, renderPipeline.GetBindGroupLayout(0),
+                   {{0, sampler0}, {1, sampler1}, {2, lumaTextureView}, {3, chromaTextureView}}));
+        pass.Draw(6);
+        pass.End();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    std::vector<uint8_t> expectedData = GetTestTextureData(wgpu::TextureFormat::RGBA8Unorm, true);
+    std::vector<utils::RGBA8> expectedRGBA;
+    for (uint8_t i = 0; i < expectedData.size(); i += 3) {
+        expectedRGBA.push_back({expectedData[i], expectedData[i + 1], expectedData[i + 2], 0xFF});
+    }
+
+    EXPECT_TEXTURE_EQ(expectedRGBA.data(), renderPass.color, {0, 0},
+                      {kYUVImageDataWidthInTexels, kYUVImageDataHeightInTexels});
     mBackend->DestroyVideoTextureForTest(std::move(platformTexture));
 }
 
diff --git a/src/dawn/tests/end2end/VideoViewsTests_win.cpp b/src/dawn/tests/end2end/VideoViewsTests_win.cpp
index 45991dd..d8f4c25 100644
--- a/src/dawn/tests/end2end/VideoViewsTests_win.cpp
+++ b/src/dawn/tests/end2end/VideoViewsTests_win.cpp
@@ -74,6 +74,13 @@
 
         ASSERT_GE(featureOptions5.SharedResourceTier, D3D11_SHARED_RESOURCE_TIER_2);
 
+        // Not all D3D11 devices support NV12 textures.
+        UINT formatSupport;
+        hr = d3d11Device->CheckFormatSupport(DXGI_FORMAT_NV12, &formatSupport);
+        ASSERT_EQ(hr, S_OK);
+
+        ASSERT_TRUE(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D);
+
         mD3d11Device = std::move(d3d11Device);
     }