Angle: Copy multiplanar d3d11 texture for readPixels

This change performs a copy to an intermediate texture for multiplanar
d3d11 textures before reading from the source texture. This is needed
as we cannot read a plane directly from a multiplanar d3d11 texture.

It also passes the correct format for the plane for multiplanar formats
when initializing the texture instead of getting the format using the
internal format.

We also add a unittest performing glReadPixels for NV12/P010/P016
formats.

Bug: angleproject:7998, chromium:1463924
Change-Id: I9a1708f5a846ace28ac3b6593ea80f1863348333
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4688118
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
Auto-Submit: Saifuddin Hitawala <hitawala@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.cpp b/src/libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.cpp
index ff2fc80..59b575e 100644
--- a/src/libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.cpp
@@ -13,6 +13,7 @@
 #include "libANGLE/renderer/d3d/d3d11/Context11.h"
 #include "libANGLE/renderer/d3d/d3d11/RenderTarget11.h"
 #include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
+#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
 #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
 
 namespace rx
@@ -34,11 +35,32 @@
     ID3D11Texture2D *texture =
         d3d11::DynamicCastComObject<ID3D11Texture2D>(static_cast<IUnknown *>(mBuffer));
     ASSERT(texture != nullptr);
-    // TextureHelper11 will release texture on destruction.
-    mTexture.set(texture, d3d11::Format::Get(angleFormat->glInternalFormat,
-                                             mRenderer->getRenderer11DeviceCaps()));
+
     D3D11_TEXTURE2D_DESC textureDesc = {};
-    mTexture.getDesc(&textureDesc);
+    texture->GetDesc(&textureDesc);
+
+    if (d3d11::IsSupportedMultiplanarFormat(textureDesc.Format))
+    {
+        if (!mAttribs.contains(EGL_D3D11_TEXTURE_PLANE_ANGLE))
+        {
+            return egl::EglBadParameter()
+                   << "EGL_D3D11_TEXTURE_PLANE_ANGLE must be specified for YUV textures.";
+        }
+
+        EGLint plane = mAttribs.getAsInt(EGL_D3D11_TEXTURE_PLANE_ANGLE);
+        if (plane < 0 || plane > 1)
+        {
+            return egl::EglBadParameter() << "Invalid client buffer texture plane: " << plane;
+        }
+
+        mTexture.set(texture, d3d11::GetYUVPlaneFormat(textureDesc.Format, plane));
+    }
+    else
+    {
+        // TextureHelper11 will release texture on destruction.
+        mTexture.set(texture, d3d11::Format::Get(angleFormat->glInternalFormat,
+                                                 mRenderer->getRenderer11DeviceCaps()));
+    }
 
     IDXGIResource *resource = d3d11::DynamicCastComObject<IDXGIResource>(mTexture.get());
     ASSERT(resource != nullptr);
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index b0d0c4a..8bf5102 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -3566,42 +3566,108 @@
     }
 
     gl::Extents safeSize(safeArea.width, safeArea.height, 1);
-    TextureHelper11 stagingHelper;
-    ANGLE_TRY(createStagingTexture(context, textureHelper.getTextureType(),
-                                   textureHelper.getFormatSet(), safeSize, StagingAccess::READ,
-                                   &stagingHelper));
-    stagingHelper.setInternalName("readFromAttachment::stagingHelper");
 
-    TextureHelper11 resolvedTextureHelper;
+    // Intermediate texture used for copy for multiplanar formats or resolving multisampled
+    // textures.
+    TextureHelper11 intermediateTextureHelper;
 
     // "srcTexture" usually points to the source texture.
     // For 2D multisampled textures, it points to the multisampled resolve texture.
     const TextureHelper11 *srcTexture = &textureHelper;
 
-    if (textureHelper.is2D() && textureHelper.getSampleCount() > 1)
+    if (textureHelper.is2D())
     {
-        D3D11_TEXTURE2D_DESC resolveDesc;
-        resolveDesc.Width              = static_cast<UINT>(texSize.width);
-        resolveDesc.Height             = static_cast<UINT>(texSize.height);
-        resolveDesc.MipLevels          = 1;
-        resolveDesc.ArraySize          = 1;
-        resolveDesc.Format             = textureHelper.getFormat();
-        resolveDesc.SampleDesc.Count   = 1;
-        resolveDesc.SampleDesc.Quality = 0;
-        resolveDesc.Usage              = D3D11_USAGE_DEFAULT;
-        resolveDesc.BindFlags          = 0;
-        resolveDesc.CPUAccessFlags     = 0;
-        resolveDesc.MiscFlags          = 0;
+        // For multiplanar d3d11 textures, perform a copy before reading.
+        if (d3d11::IsSupportedMultiplanarFormat(textureHelper.getFormat()))
+        {
+            D3D11_TEXTURE2D_DESC planeDesc;
+            planeDesc.Width              = static_cast<UINT>(safeSize.width);
+            planeDesc.Height             = static_cast<UINT>(safeSize.height);
+            planeDesc.MipLevels          = 1;
+            planeDesc.ArraySize          = 1;
+            planeDesc.Format             = textureHelper.getFormatSet().srvFormat;
+            planeDesc.SampleDesc.Count   = 1;
+            planeDesc.SampleDesc.Quality = 0;
+            planeDesc.Usage              = D3D11_USAGE_DEFAULT;
+            planeDesc.BindFlags          = D3D11_BIND_RENDER_TARGET;
+            planeDesc.CPUAccessFlags     = 0;
+            planeDesc.MiscFlags          = 0;
 
-        ANGLE_TRY(allocateTexture(GetImplAs<Context11>(context), resolveDesc,
-                                  textureHelper.getFormatSet(), &resolvedTextureHelper));
-        resolvedTextureHelper.setInternalName("readFromAttachment::resolvedTextureHelper");
+            GLenum internalFormat = textureHelper.getFormatSet().internalFormat;
+            ANGLE_TRY(allocateTexture(GetImplAs<Context11>(context), planeDesc,
+                                      d3d11::Format::Get(internalFormat, mRenderer11DeviceCaps),
+                                      &intermediateTextureHelper));
+            intermediateTextureHelper.setInternalName(
+                "readFromAttachment::intermediateTextureHelper");
 
-        mDeviceContext->ResolveSubresource(resolvedTextureHelper.get(), 0, textureHelper.get(),
-                                           sourceSubResource, textureHelper.getFormat());
+            Context11 *context11 = GetImplAs<Context11>(context);
+            d3d11::RenderTargetView rtv;
+            D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+            rtvDesc.Format             = textureHelper.getFormatSet().rtvFormat;
+            rtvDesc.ViewDimension      = D3D11_RTV_DIMENSION_TEXTURE2D;
+            rtvDesc.Texture2D.MipSlice = 0;
 
-        sourceSubResource = 0;
-        srcTexture        = &resolvedTextureHelper;
+            ANGLE_TRY(allocateResource(context11, rtvDesc, intermediateTextureHelper.get(), &rtv));
+            rtv.setInternalName("readFromAttachment.RTV");
+
+            d3d11::SharedSRV srv;
+            D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
+            srvDesc.Format                    = textureHelper.getFormatSet().srvFormat;
+            srvDesc.ViewDimension             = D3D11_SRV_DIMENSION_TEXTURE2D;
+            srvDesc.Texture2D.MostDetailedMip = 0;
+            srvDesc.Texture2D.MipLevels       = 1;
+
+            ANGLE_TRY(allocateResource(context11, srvDesc, textureHelper.get(), &srv));
+            srv.setInternalName("readFromAttachment.SRV");
+
+            gl::Box srcGlBox(safeArea.x, safeArea.y, 0, safeArea.width, safeArea.height, 1);
+            gl::Box destGlBox(0, 0, 0, safeSize.width, safeSize.height, 1);
+
+            // Perform a copy to planeTexture as we cannot read directly from NV12 d3d11 textures.
+            ANGLE_TRY(mBlit->copyTexture(
+                context, srv, srcGlBox, safeSize, internalFormat, rtv, destGlBox, safeSize, nullptr,
+                gl::GetUnsizedFormat(internalFormat), GL_NONE, GL_NEAREST, false, false, false));
+
+            // Update safeArea based on the destination.
+            safeArea.x      = destGlBox.x;
+            safeArea.y      = destGlBox.y;
+            safeArea.width  = destGlBox.width;
+            safeArea.height = destGlBox.height;
+
+            sourceSubResource = 0;
+            srcTexture        = &intermediateTextureHelper;
+        }
+        else
+        {
+            if (textureHelper.getSampleCount() > 1)
+            {
+                D3D11_TEXTURE2D_DESC resolveDesc;
+                resolveDesc.Width              = static_cast<UINT>(texSize.width);
+                resolveDesc.Height             = static_cast<UINT>(texSize.height);
+                resolveDesc.MipLevels          = 1;
+                resolveDesc.ArraySize          = 1;
+                resolveDesc.Format             = textureHelper.getFormat();
+                resolveDesc.SampleDesc.Count   = 1;
+                resolveDesc.SampleDesc.Quality = 0;
+                resolveDesc.Usage              = D3D11_USAGE_DEFAULT;
+                resolveDesc.BindFlags          = 0;
+                resolveDesc.CPUAccessFlags     = 0;
+                resolveDesc.MiscFlags          = 0;
+
+                ANGLE_TRY(allocateTexture(GetImplAs<Context11>(context), resolveDesc,
+                                          textureHelper.getFormatSet(),
+                                          &intermediateTextureHelper));
+                intermediateTextureHelper.setInternalName(
+                    "readFromAttachment::intermediateTextureHelper");
+
+                mDeviceContext->ResolveSubresource(intermediateTextureHelper.get(), 0,
+                                                   textureHelper.get(), sourceSubResource,
+                                                   textureHelper.getFormat());
+
+                sourceSubResource = 0;
+                srcTexture        = &intermediateTextureHelper;
+            }
+        }
     }
 
     D3D11_BOX srcBox;
@@ -3618,6 +3684,12 @@
     }
     srcBox.back = srcBox.front + 1;
 
+    TextureHelper11 stagingHelper;
+    ANGLE_TRY(createStagingTexture(context, textureHelper.getTextureType(),
+                                   srcTexture->getFormatSet(), safeSize, StagingAccess::READ,
+                                   &stagingHelper));
+    stagingHelper.setInternalName("readFromAttachment::stagingHelper");
+
     mDeviceContext->CopySubresourceRegion(stagingHelper.get(), 0, 0, 0, 0, srcTexture->get(),
                                           sourceSubResource, &srcBox);
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp b/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
index 75ddbd9..3e575d3 100644
--- a/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
@@ -17,6 +17,7 @@
 #include "libANGLE/renderer/copyvertex.h"
 #include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
 #include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h"
+#include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
 #include "libANGLE/renderer/dxgi_support_table.h"
 
 namespace rx
@@ -32,6 +33,69 @@
     return ((support.alwaysSupportedFlags & D3D11_FORMAT_SUPPORT_MIP_AUTOGEN) != 0);
 }
 
+bool IsSupportedMultiplanarFormat(DXGI_FORMAT dxgiFormat)
+{
+    return dxgiFormat == DXGI_FORMAT_NV12 || dxgiFormat == DXGI_FORMAT_P010 ||
+           dxgiFormat == DXGI_FORMAT_P016;
+}
+
+const Format &GetYUVPlaneFormat(DXGI_FORMAT dxgiFormat, int plane)
+{
+    static constexpr Format nv12Plane0Info(
+        GL_R8, angle::FormatID::R8_UNORM, DXGI_FORMAT_NV12, DXGI_FORMAT_R8_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8_TYPELESS, GL_RGBA8, nullptr);
+
+    static constexpr Format nv12Plane1Info(
+        GL_RG8, angle::FormatID::R8G8_UNORM, DXGI_FORMAT_NV12, DXGI_FORMAT_R8G8_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8_TYPELESS, GL_RGBA8, nullptr);
+
+    static constexpr Format p010Plane0Info(
+        GL_R16_EXT, angle::FormatID::R16_UNORM, DXGI_FORMAT_P010, DXGI_FORMAT_R16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_TYPELESS, GL_RGBA16_EXT, nullptr);
+
+    static constexpr Format p010Plane1Info(
+        GL_RG16_EXT, angle::FormatID::R16G16_UNORM, DXGI_FORMAT_P010, DXGI_FORMAT_R16G16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN,
+        DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16_TYPELESS, GL_RGBA16_EXT,
+        nullptr);
+
+    static constexpr Format p016Plane0Info(
+        GL_R16_EXT, angle::FormatID::R16_UNORM, DXGI_FORMAT_P016, DXGI_FORMAT_R16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16_TYPELESS, GL_RGBA16_EXT, nullptr);
+
+    static constexpr Format p016Plane1Info(
+        GL_RG16_EXT, angle::FormatID::R16G16_UNORM, DXGI_FORMAT_P016, DXGI_FORMAT_R16G16_UNORM,
+        DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN,
+        DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16_TYPELESS, GL_RGBA16_EXT,
+        nullptr);
+
+    ASSERT(IsSupportedMultiplanarFormat(dxgiFormat));
+    if (plane < 0 || plane > 1)
+    {
+        ERR() << "Invalid client buffer texture plane: " << plane;
+        static constexpr Format defaultInfo;
+        return defaultInfo;
+    }
+
+    switch (dxgiFormat)
+    {
+        case DXGI_FORMAT_NV12:
+            return plane == 0 ? nv12Plane0Info : nv12Plane1Info;
+        case DXGI_FORMAT_P010:
+            return plane == 0 ? p010Plane0Info : p010Plane1Info;
+        case DXGI_FORMAT_P016:
+            return plane == 0 ? p016Plane0Info : p016Plane1Info;
+        default:
+            ERR() << "Not supported multiplanar format: " << dxgiFormat;
+    }
+    static constexpr Format defaultInfo;
+    return defaultInfo;
+}
+
 DXGIFormatSize::DXGIFormatSize(GLuint pixelBits, GLuint blockWidth, GLuint blockHeight)
     : pixelBytes(pixelBits / 8), blockWidth(blockWidth), blockHeight(blockHeight)
 {}
diff --git a/src/libANGLE/renderer/d3d/d3d11/formatutils11.h b/src/libANGLE/renderer/d3d/d3d11/formatutils11.h
index e4c3994..79b548b 100644
--- a/src/libANGLE/renderer/d3d/d3d11/formatutils11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/formatutils11.h
@@ -27,12 +27,18 @@
 namespace d3d11
 {
 
+struct Format;
+
 // A texture might be stored as DXGI_FORMAT_R16_TYPELESS but store integer components,
 // which are accessed through an DXGI_FORMAT_R16_SINT view. It's easy to write code which queries
 // information about the wrong format. Therefore, use of this should be avoided where possible.
 
 bool SupportsMipGen(DXGI_FORMAT dxgiFormat, D3D_FEATURE_LEVEL featureLevel);
 
+bool IsSupportedMultiplanarFormat(DXGI_FORMAT dxgiFormat);
+
+const Format &GetYUVPlaneFormat(DXGI_FORMAT dxgiFormat, int plane);
+
 struct DXGIFormatSize
 {
     DXGIFormatSize(GLuint pixelBits, GLuint blockWidth, GLuint blockHeight);
diff --git a/src/tests/gl_tests/D3DTextureTest.cpp b/src/tests/gl_tests/D3DTextureTest.cpp
index 093135a..f0a54d8 100644
--- a/src/tests/gl_tests/D3DTextureTest.cpp
+++ b/src/tests/gl_tests/D3DTextureTest.cpp
@@ -1621,11 +1621,14 @@
     void CreateAndBindImageToTexture(EGLDisplay display,
                                      ID3D11Texture2D *d3d11Texture,
                                      EGLint plane,
+                                     GLenum internalFormat,
                                      GLenum target,
                                      EGLImage *image,
                                      GLuint *texture)
     {
-        const EGLint attribs[] = {EGL_D3D11_TEXTURE_PLANE_ANGLE, plane, EGL_NONE};
+        const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE,
+                                  static_cast<EGLint>(internalFormat),
+                                  EGL_D3D11_TEXTURE_PLANE_ANGLE, plane, EGL_NONE};
         *image                 = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_D3D11_TEXTURE_ANGLE,
                                                    static_cast<EGLClientBuffer>(d3d11Texture), attribs);
         ASSERT_EGL_SUCCESS();
@@ -1740,8 +1743,10 @@
         // Create and bind Y plane texture to image.
         EGLImage yImage;
         GLuint yTexture;
-        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 0, GL_TEXTURE_EXTERNAL_OES,
-                                    &yImage, &yTexture);
+
+        GLenum internalFormat = format == DXGI_FORMAT_NV12 ? GL_RED_EXT : GL_R16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 0, internalFormat,
+                                    GL_TEXTURE_EXTERNAL_OES, &yImage, &yTexture);
 
         GLuint rbo;
         glGenRenderbuffers(1, &rbo);
@@ -1783,8 +1788,10 @@
         // Create and bind UV plane texture to image.
         EGLImage uvImage;
         GLuint uvTexture;
-        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 1, GL_TEXTURE_EXTERNAL_OES,
-                                    &uvImage, &uvTexture);
+
+        internalFormat = format == DXGI_FORMAT_NV12 ? GL_RG_EXT : GL_RG16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 1, internalFormat,
+                                    GL_TEXTURE_EXTERNAL_OES, &uvImage, &uvTexture);
 
         // Draw the UV plane using a shader.
         glUseProgram(program);
@@ -1878,8 +1885,10 @@
         // Create and bind Y plane texture to image.
         EGLImage yImage;
         GLuint yTexture;
-        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 0, GL_TEXTURE_2D, &yImage,
-                                    &yTexture);
+
+        GLenum internalFormat = format == DXGI_FORMAT_NV12 ? GL_RED_EXT : GL_R16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 0, internalFormat, GL_TEXTURE_2D,
+                                    &yImage, &yTexture);
 
         glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, yImage);
         ASSERT_GL_NO_ERROR();
@@ -1903,8 +1912,10 @@
         // Create and bind UV plane texture to image.
         EGLImage uvImage;
         GLuint uvTexture;
-        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 1, GL_TEXTURE_2D, &uvImage,
-                                    &uvTexture);
+
+        internalFormat = format == DXGI_FORMAT_NV12 ? GL_RG_EXT : GL_RG16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 1, internalFormat, GL_TEXTURE_2D,
+                                    &uvImage, &uvTexture);
 
         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, uvTexture, 0);
         EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
@@ -1965,6 +1976,187 @@
         eglDestroyImageKHR(display, yImage);
         eglDestroyImageKHR(display, uvImage);
     }
+
+    void RunYUVReadPixelTest(DXGI_FORMAT format)
+    {
+        ASSERT(format == DXGI_FORMAT_NV12 || format == DXGI_FORMAT_P010 ||
+               format == DXGI_FORMAT_P016);
+        UINT formatSupport;
+        ANGLE_SKIP_TEST_IF(!valid() || !IsD3D11() ||
+                           FAILED(mD3D11Device->CheckFormatSupport(format, &formatSupport)));
+        ASSERT_TRUE(formatSupport &
+                    (D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_RENDER_TARGET));
+
+        const bool isNV12          = (format == DXGI_FORMAT_NV12);
+        const unsigned kYFillValue = isNV12 ? 0x12 : 0x1234;
+        const unsigned kUFillValue = isNV12 ? 0x23 : 0x2345;
+        const unsigned kVFillValue = isNV12 ? 0x34 : 0x3456;
+
+        constexpr char kVS[] =
+            R"(precision highp float;
+            attribute vec4 position;
+            varying vec2 texcoord;
+
+            void main()
+            {
+                gl_Position = position;
+                texcoord = (position.xy * 0.5) + 0.5;
+                texcoord.y = 1.0 - texcoord.y;
+            })";
+
+        constexpr char kFS[] =
+            R"(precision highp float;
+            uniform vec4 color;
+            varying vec2 texcoord;
+
+            void main()
+            {
+                gl_FragColor = color;
+            })";
+
+        GLuint program = CompileProgram(kVS, kFS);
+        ASSERT_NE(0u, program) << "shader compilation failed.";
+
+        GLint colorLocation = glGetUniformLocation(program, "color");
+        ASSERT_NE(-1, colorLocation);
+
+        EGLWindow *window  = getEGLWindow();
+        EGLDisplay display = window->getDisplay();
+
+        window->makeCurrent();
+
+        const UINT bufferSize = 32;
+        EXPECT_TRUE(mD3D11Device != nullptr);
+        Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11Texture;
+        CD3D11_TEXTURE2D_DESC desc(format, bufferSize, bufferSize, 1, 1,
+                                   D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET);
+
+        EXPECT_TRUE(SUCCEEDED(mD3D11Device->CreateTexture2D(&desc, nullptr, &d3d11Texture)));
+
+        // Create and bind Y plane texture to image.
+        EGLImage yImage;
+        GLuint yTexture;
+
+        GLenum internalFormat = format == DXGI_FORMAT_NV12 ? GL_RED_EXT : GL_R16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 0, internalFormat, GL_TEXTURE_2D,
+                                    &yImage, &yTexture);
+
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, yImage);
+        ASSERT_GL_NO_ERROR();
+
+        GLuint fbo;
+        glGenFramebuffers(1, &fbo);
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yTexture, 0);
+        EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
+                  static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
+        ASSERT_GL_NO_ERROR();
+
+        // Draw the Y plane using a shader.
+        glUseProgram(program);
+        glUniform4f(colorLocation, kYFillValue * 1.0f / (isNV12 ? 0xff : 0xffff), 0, 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        drawQuad(program, "position", 1.0f);
+        ASSERT_GL_NO_ERROR();
+
+        // Read the Y plane pixels.
+        if (isNV12)
+        {
+            GLubyte yPixels[4] = {};
+            glReadPixels(0, bufferSize / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, yPixels);
+            EXPECT_EQ(yPixels[0], kYFillValue);
+        }
+        else
+        {
+            GLushort yPixels[4] = {};
+            glReadPixels(0, bufferSize / 2, 1, 1, GL_RGBA, GL_UNSIGNED_SHORT, yPixels);
+            EXPECT_EQ(yPixels[0], kYFillValue);
+        }
+
+        // Create and bind UV plane texture to image.
+        EGLImage uvImage;
+        GLuint uvTexture;
+
+        internalFormat = format == DXGI_FORMAT_NV12 ? GL_RG_EXT : GL_RG16_EXT;
+        CreateAndBindImageToTexture(display, d3d11Texture.Get(), 1, internalFormat, GL_TEXTURE_2D,
+                                    &uvImage, &uvTexture);
+
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, uvTexture, 0);
+        EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER),
+                  static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
+        ASSERT_GL_NO_ERROR();
+
+        // Draw the UV plane using a shader.
+        glUseProgram(program);
+        glUniform4f(colorLocation, kUFillValue * 1.0f / (isNV12 ? 0xff : 0xffff),
+                    kVFillValue * 1.0f / (isNV12 ? 0xff : 0xffff), 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        drawQuad(program, "position", 1.0f);
+        ASSERT_GL_NO_ERROR();
+
+        // Read the UV plane pixels.
+        if (isNV12)
+        {
+            GLubyte uvPixels[4] = {};
+            glReadPixels(0, bufferSize / 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, uvPixels);
+            EXPECT_EQ(uvPixels[0], kUFillValue);
+            EXPECT_EQ(uvPixels[1], kVFillValue);
+        }
+        else
+        {
+            GLushort uvPixels[4] = {};
+            glReadPixels(0, bufferSize / 4, 1, 1, GL_RGBA, GL_UNSIGNED_SHORT, uvPixels);
+            EXPECT_EQ(uvPixels[0], kUFillValue);
+            EXPECT_EQ(uvPixels[1], kVFillValue);
+        }
+
+        Microsoft::WRL::ComPtr<ID3D11Texture2D> stagingTexture;
+        CD3D11_TEXTURE2D_DESC stagingDesc = desc;
+        stagingDesc.BindFlags             = 0;
+        stagingDesc.Usage                 = D3D11_USAGE_STAGING;
+        stagingDesc.CPUAccessFlags        = D3D11_CPU_ACCESS_READ;
+
+        EXPECT_TRUE(
+            SUCCEEDED(mD3D11Device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture)));
+
+        Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
+        mD3D11Device->GetImmediateContext(&context);
+
+        context->CopyResource(stagingTexture.Get(), d3d11Texture.Get());
+        ASSERT_GL_NO_ERROR();
+
+        D3D11_MAPPED_SUBRESOURCE mapped = {};
+        EXPECT_TRUE(SUCCEEDED(context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mapped)));
+
+        uint8_t *yPlane  = reinterpret_cast<uint8_t *>(mapped.pData);
+        uint8_t *uvPlane = yPlane + bufferSize * mapped.RowPitch;
+        if (isNV12)
+        {
+            EXPECT_EQ(yPlane[mapped.RowPitch * bufferSize / 2], kYFillValue);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4], kUFillValue);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4 + 1], kVFillValue);
+        }
+        else
+        {
+            EXPECT_EQ(yPlane[mapped.RowPitch * bufferSize / 2], kYFillValue & 0xff);
+            EXPECT_EQ(yPlane[mapped.RowPitch * bufferSize / 2 + 1], (kYFillValue >> 8) & 0xff);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4], kUFillValue & 0xff);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4 + 1], (kUFillValue >> 8) & 0xff);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4 + 2], kVFillValue & 0xff);
+            EXPECT_EQ(uvPlane[mapped.RowPitch * bufferSize / 4 + 3], (kVFillValue >> 8) & 0xff);
+        }
+
+        context->Unmap(stagingTexture.Get(), 0);
+
+        glDeleteProgram(program);
+        glDeleteTextures(1, &yTexture);
+        glDeleteTextures(1, &uvTexture);
+        glDeleteFramebuffers(1, &fbo);
+        eglDestroyImageKHR(display, yImage);
+        eglDestroyImageKHR(display, uvImage);
+    }
 };
 
 // Test that an NV12 D3D11 texture can be imported as two R8 and RG8 EGLImages and the resulting GL
@@ -2009,6 +2201,27 @@
     RunYUVRenderTest(DXGI_FORMAT_P016);
 }
 
+// Test that an NV12 D3D11 texture can be imported as two R8 and RG8 EGLImages and rendered to as
+// framebuffer attachments and then read from as individual planes.
+TEST_P(D3DTextureYUVTest, NV12TextureImageReadPixel)
+{
+    RunYUVReadPixelTest(DXGI_FORMAT_NV12);
+}
+
+// ANGLE ES2/D3D11 supports GL_EXT_texture_norm16 even though the extension spec says it's ES3 only.
+// Test P010 on ES2 since Chromium's Skia context is ES2 and it uses P010 for HDR video playback.
+TEST_P(D3DTextureYUVTest, P010TextureImageReadPixel)
+{
+    RunYUVReadPixelTest(DXGI_FORMAT_P010);
+}
+
+// Same as above, but for P016. P016 doesn't seem to be supported on all GPUs so it might be skipped
+// more often than P010 and NV12 e.g. on the NVIDIA GTX 1050 Ti.
+TEST_P(D3DTextureYUVTest, P016TextureImageReadPixel)
+{
+    RunYUVReadPixelTest(DXGI_FORMAT_P016);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
 ANGLE_INSTANTIATE_TEST_ES2(D3DTextureTest);