chromium/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h"

#include <dawn/dawn_proc.h>
#include <dawn/native/DawnNative.h>
#include <dawn/webgpu_cpp.h>

#include <memory>
#include <utility>

#include "base/bits.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "cc/test/pixel_comparator.h"
#include "cc/test/pixel_test_utils.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/dxgi_shared_handle_manager.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/compound_image_backing.h"
#include "gpu/command_buffer/service/shared_image/d3d_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/config/gpu_finch_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/private/chromium/GrPromiseImageTexture.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gfx/win/d3d_shared_fence.h"
#include "ui/gl/buildflags.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"

#define SCOPED_GL_CLEANUP_VAR(api, func, var)            \
  base::ScopedClosureRunner delete_##var(base::BindOnce( \
      [](gl::GLApi* api, GLuint var) { api->gl##func##Fn(var); }, api, var))

#define SCOPED_GL_CLEANUP_PTR(api, func, n, var)                           \
  base::ScopedClosureRunner delete_##var(base::BindOnce(                   \
      [](gl::GLApi* api, GLuint var) { api->gl##func##Fn(n, &var); }, api, \
      var))

namespace gpu {
namespace {

static const char* kVertexShaderSrc =
    "attribute vec2 a_position;\n"
    "varying vec2 v_texCoord;\n"
    "void main() {\n"
    "  gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);\n"
    "  v_texCoord = (a_position + vec2(1.0, 1.0)) * 0.5;\n"
    "}\n";

static const char* kFragmentShaderSrc =
    "precision mediump float;\n"
    "uniform mediump sampler2D u_texture;\n"
    "varying vec2 v_texCoord;\n"
    "void main() {\n"
    "  gl_FragColor = texture2D(u_texture, v_texCoord);"
    "}\n";

void FillNV12(uint8_t* data,
              const gfx::Size& size,
              uint8_t y_fill_value,
              uint8_t u_fill_value,
              uint8_t v_fill_value) {
  const size_t kYPlaneSize = size.width() * size.height();
  memset(data, y_fill_value, kYPlaneSize);
  uint8_t* uv_data = data + kYPlaneSize;
  const size_t kUVPlaneSize = kYPlaneSize / 2;
  for (size_t i = 0; i < kUVPlaneSize; i += 2) {
    uv_data[i] = u_fill_value;
    uv_data[i + 1] = v_fill_value;
  }
}

void CheckNV12(const uint8_t* data,
               size_t stride,
               const gfx::Size& size,
               uint8_t y_fill_value,
               uint8_t u_fill_value,
               uint8_t v_fill_value) {
  const size_t kYPlaneSize = stride * size.height();
  const uint8_t* uv_data = data + kYPlaneSize;
  for (int i = 0; i < size.height(); i++) {
    for (int j = 0; j < size.width(); j++) {
      // ASSERT instead of EXPECT to exit on first failure to avoid log spam.
      ASSERT_EQ(*(data + i * stride + j), y_fill_value);
      if (i < size.height() / 2) {
        const uint8_t uv_value = (j % 2 == 0) ? u_fill_value : v_fill_value;
        ASSERT_EQ(*(uv_data + i * stride + j), uv_value);
      }
    }
  }
}

GLuint MakeTextureAndSetParameters(gl::GLApi* api, GLenum target, bool fbo) {
  GLuint texture_id = 0;
  api->glGenTexturesFn(1, &texture_id);
  api->glBindTextureFn(target, texture_id);
  api->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  api->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  if (fbo) {
    api->glTexParameteriFn(target, GL_TEXTURE_USAGE_ANGLE,
                           GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
  }
  return texture_id;
}

}  // anonymous namespace

class D3DImageBackingFactoryTestBase : public testing::Test {
 public:
  void SetUp() override {
    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
                                                  gfx::Size());
    ASSERT_TRUE(surface_);
    context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                         gl::GLContextAttribs());
    ASSERT_TRUE(context_);
    bool result = context_->MakeCurrent(surface_.get());
    ASSERT_TRUE(result);

    memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
    shared_image_representation_factory_ =
        std::make_unique<SharedImageRepresentationFactory>(
            &shared_image_manager_, nullptr);
    shared_image_factory_ = std::make_unique<D3DImageBackingFactory>(
        gl::QueryD3D11DeviceObjectFromANGLE(),
        shared_image_manager_.dxgi_shared_handle_manager(), GLFormatCaps());
    dawnProcSetProcs(&dawn::native::GetProcs());
  }

  void TearDown() override { dawnProcSetProcs(nullptr); }

 protected:
  scoped_refptr<gl::GLSurface> surface_;
  scoped_refptr<gl::GLContext> context_;
  SharedImageManager shared_image_manager_;
  std::unique_ptr<MemoryTypeTracker> memory_type_tracker_;
  std::unique_ptr<SharedImageRepresentationFactory>
      shared_image_representation_factory_;
  std::unique_ptr<D3DImageBackingFactory> shared_image_factory_;
};

class D3DImageBackingFactoryTestSwapChain
    : public D3DImageBackingFactoryTestBase {
 public:
  void SetUp() override {
    if (!D3DImageBackingFactory::IsSwapChainSupported(GpuPreferences())) {
      GTEST_SKIP();
    }
    D3DImageBackingFactoryTestBase::SetUp();
  }
};

TEST_F(D3DImageBackingFactoryTestSwapChain, InvalidFormat) {
  auto front_buffer_mailbox = Mailbox::Generate();
  auto back_buffer_mailbox = Mailbox::Generate();
  gfx::Size size(1, 1);
  auto color_space = gfx::ColorSpace::CreateSRGB();
  auto surface_origin = kTopLeft_GrSurfaceOrigin;
  auto alpha_type = kPremul_SkAlphaType;
  gpu::SharedImageUsageSet usage = gpu::SHARED_IMAGE_USAGE_SCANOUT;
  {
    auto valid_format = viz::SinglePlaneFormat::kRGBA_8888;
    auto backings = shared_image_factory_->CreateSwapChain(
        front_buffer_mailbox, back_buffer_mailbox, valid_format, size,
        color_space, surface_origin, alpha_type, usage);
    EXPECT_TRUE(backings.front_buffer);
    EXPECT_TRUE(backings.back_buffer);
  }
  {
    auto valid_format = viz::SinglePlaneFormat::kBGRA_8888;
    auto backings = shared_image_factory_->CreateSwapChain(
        front_buffer_mailbox, back_buffer_mailbox, valid_format, size,
        color_space, surface_origin, alpha_type, usage);
    EXPECT_TRUE(backings.front_buffer);
    EXPECT_TRUE(backings.back_buffer);
  }
  {
    auto valid_format = viz::SinglePlaneFormat::kRGBA_F16;
    auto backings = shared_image_factory_->CreateSwapChain(
        front_buffer_mailbox, back_buffer_mailbox, valid_format, size,
        color_space, surface_origin, alpha_type, usage);
    EXPECT_TRUE(backings.front_buffer);
    EXPECT_TRUE(backings.back_buffer);
  }
  {
    auto invalid_format = viz::SinglePlaneFormat::kRGBA_4444;
    auto backings = shared_image_factory_->CreateSwapChain(
        front_buffer_mailbox, back_buffer_mailbox, invalid_format, size,
        color_space, surface_origin, alpha_type, usage);
    EXPECT_FALSE(backings.front_buffer);
    EXPECT_FALSE(backings.back_buffer);
  }
}

TEST_F(D3DImageBackingFactoryTestSwapChain, CreateAndPresentSwapChain) {
  auto front_buffer_mailbox = Mailbox::Generate();
  auto back_buffer_mailbox = Mailbox::Generate();
  auto format = viz::SinglePlaneFormat::kRGBA_8888;
  gfx::Size size(1, 1);
  auto color_space = gfx::ColorSpace::CreateSRGB();
  auto surface_origin = kTopLeft_GrSurfaceOrigin;
  auto alpha_type = kPremul_SkAlphaType;
  gpu::SharedImageUsageSet usage =
      // This test both reads from and writes to the created SharedImages via
      // GL.
      gpu::SHARED_IMAGE_USAGE_GLES2_READ | SHARED_IMAGE_USAGE_GLES2_WRITE |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;

  auto backings = shared_image_factory_->CreateSwapChain(
      front_buffer_mailbox, back_buffer_mailbox, format, size, color_space,
      surface_origin, alpha_type, usage);
  ASSERT_TRUE(backings.front_buffer);
  EXPECT_TRUE(backings.front_buffer->IsCleared());

  ASSERT_TRUE(backings.back_buffer);
  EXPECT_TRUE(backings.back_buffer->IsCleared());

  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
  auto* back_buffer_d3d_backing =
      static_cast<D3DImageBacking*>(backings.back_buffer.get());
  auto* front_buffer_d3d_backing =
      static_cast<D3DImageBacking*>(backings.front_buffer.get());
  ASSERT_TRUE(back_buffer_d3d_backing);
  ASSERT_TRUE(front_buffer_d3d_backing);

  EXPECT_EQ(S_OK, back_buffer_d3d_backing->swap_chain_for_testing()->GetBuffer(
                      /*buffer_index=*/0, IID_PPV_ARGS(&d3d11_texture)));
  EXPECT_TRUE(d3d11_texture);
  EXPECT_EQ(d3d11_texture,
            back_buffer_d3d_backing->d3d11_texture_for_testing());
  d3d11_texture.Reset();

  EXPECT_EQ(back_buffer_d3d_backing->swap_chain_for_testing(),
            front_buffer_d3d_backing->swap_chain_for_testing());
  EXPECT_EQ(S_OK, front_buffer_d3d_backing->swap_chain_for_testing()->GetBuffer(
                      /*buffer_index=*/1, IID_PPV_ARGS(&d3d11_texture)));
  EXPECT_TRUE(d3d11_texture);
  EXPECT_EQ(d3d11_texture,
            front_buffer_d3d_backing->d3d11_texture_for_testing());
  d3d11_texture.Reset();

  std::unique_ptr<SharedImageRepresentationFactoryRef> back_factory_ref =
      shared_image_manager_.Register(std::move(backings.back_buffer),
                                     memory_type_tracker_.get());
  std::unique_ptr<SharedImageRepresentationFactoryRef> front_factory_ref =
      shared_image_manager_.Register(std::move(backings.front_buffer),
                                     memory_type_tracker_.get());

  auto back_gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          back_buffer_mailbox);
  EXPECT_TRUE(back_gl_representation);

  std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
      back_scoped_access = back_gl_representation->BeginScopedAccess(
          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  EXPECT_TRUE(back_scoped_access);

  auto back_texture = back_gl_representation->GetTexturePassthrough();
  ASSERT_TRUE(back_texture);
  EXPECT_EQ(back_texture->target(), static_cast<unsigned>(GL_TEXTURE_2D));

  GLuint back_texture_id = back_texture->service_id();
  EXPECT_NE(back_texture_id, 0u);

  auto front_gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          front_buffer_mailbox);
  EXPECT_TRUE(front_gl_representation);

  std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
      front_scoped_access = front_gl_representation->BeginScopedAccess(
          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  EXPECT_TRUE(front_scoped_access);

  auto front_texture = front_gl_representation->GetTexturePassthrough();
  ASSERT_TRUE(front_texture);
  EXPECT_EQ(front_texture->target(), static_cast<unsigned>(GL_TEXTURE_2D));

  GLuint front_texture_id = front_texture->service_id();
  EXPECT_NE(front_texture_id, 0u);

  gl::GLApi* api = gl::g_current_gl_context;
  // Create a multisampled FBO.
  GLuint multisample_fbo, renderbuffer = 0u;
  api->glGenFramebuffersEXTFn(1, &multisample_fbo);
  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, multisample_fbo);
  api->glGenRenderbuffersEXTFn(1, &renderbuffer);
  api->glBindRenderbufferEXTFn(GL_RENDERBUFFER, renderbuffer);
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glRenderbufferStorageMultisampleFn(GL_RENDERBUFFER, /*sample_count=*/4,
                                          GL_RGBA8_OES, 1, 1);
  api->glFramebufferRenderbufferEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      GL_RENDERBUFFER, renderbuffer);
  EXPECT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  // Set the clear color to green.
  api->glViewportFn(0, 0, size.width(), size.height());
  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
  api->glClearFn(GL_COLOR_BUFFER_BIT);
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, multisample_fbo);

  // Attach the back buffer texture to an FBO.
  GLuint fbo = 0u;
  api->glGenFramebuffersEXTFn(1, &fbo);
  api->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, fbo);
  api->glBindTextureFn(GL_TEXTURE_2D, back_texture_id);
  api->glFramebufferTexture2DEXTFn(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_2D, back_texture_id, 0);
  EXPECT_EQ(api->glCheckFramebufferStatusEXTFn(GL_DRAW_FRAMEBUFFER),
            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glBlitFramebufferFn(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT,
                           GL_NEAREST);
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);

  // Checks if rendering to back buffer was successful.
  {
    GLubyte pixel_color[4];
    const uint8_t expected_color[4] = {0, 255, 0, 255};
    api->glReadPixelsFn(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);
    EXPECT_EQ(expected_color[0], pixel_color[0]);
    EXPECT_EQ(expected_color[1], pixel_color[1]);
    EXPECT_EQ(expected_color[2], pixel_color[2]);
    EXPECT_EQ(expected_color[3], pixel_color[3]);
  }

  EXPECT_TRUE(back_factory_ref->PresentSwapChain());

  // After present, back buffer should now have a clear texture.
  {
    GLubyte pixel_color[4];
    const uint8_t expected_color[4] = {0, 0, 0, 255};
    api->glReadPixelsFn(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);
    EXPECT_EQ(expected_color[0], pixel_color[0]);
    EXPECT_EQ(expected_color[1], pixel_color[1]);
    EXPECT_EQ(expected_color[2], pixel_color[2]);
    EXPECT_EQ(expected_color[3], pixel_color[3]);
  }

  // And front buffer should have the rendered contents.  Test that binding
  // front buffer as a sampler works.
  {
    // Create a destination texture to render into since we can't bind front
    // buffer to an FBO.
    GLuint dest_texture_id =
        MakeTextureAndSetParameters(api, GL_TEXTURE_2D, true);
    api->glTexImage2DFn(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
                        GL_UNSIGNED_BYTE, nullptr);
    api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                     GL_TEXTURE_2D, dest_texture_id, 0);
    EXPECT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
              static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
    api->glClearColorFn(0.0f, 0.0f, 0.0f, 0.0f);
    api->glClearFn(GL_COLOR_BUFFER_BIT);
    ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

    GLint status = 0;
    GLuint vertex_shader = api->glCreateShaderFn(GL_VERTEX_SHADER);
    ASSERT_NE(vertex_shader, 0u);
    api->glShaderSourceFn(vertex_shader, 1, &kVertexShaderSrc, nullptr);
    api->glCompileShaderFn(vertex_shader);
    api->glGetShaderivFn(vertex_shader, GL_COMPILE_STATUS, &status);
    ASSERT_NE(status, 0);

    GLuint fragment_shader = api->glCreateShaderFn(GL_FRAGMENT_SHADER);
    ASSERT_NE(fragment_shader, 0u);
    api->glShaderSourceFn(fragment_shader, 1, &kFragmentShaderSrc, nullptr);
    api->glCompileShaderFn(fragment_shader);
    api->glGetShaderivFn(fragment_shader, GL_COMPILE_STATUS, &status);
    ASSERT_NE(status, 0);

    GLuint program = api->glCreateProgramFn();
    ASSERT_NE(program, 0u);
    api->glAttachShaderFn(program, vertex_shader);
    api->glAttachShaderFn(program, fragment_shader);
    api->glLinkProgramFn(program);
    api->glGetProgramivFn(program, GL_LINK_STATUS, &status);
    ASSERT_NE(status, 0);

    GLuint vbo = 0u;
    api->glGenBuffersARBFn(1, &vbo);
    ASSERT_NE(vbo, 0u);
    api->glBindBufferFn(GL_ARRAY_BUFFER, vbo);
    static const float vertices[] = {
        1.0f, 1.0f, -1.0f, 1.0f,  -1.0f, -1.0f,
        1.0f, 1.0f, -1.0f, -1.0f, 1.0f,  -1.0f,
    };
    api->glBufferDataFn(GL_ARRAY_BUFFER, sizeof(vertices), vertices,
                        GL_STATIC_DRAW);
    GLint vertex_location = api->glGetAttribLocationFn(program, "a_position");
    ASSERT_NE(vertex_location, -1);
    api->glEnableVertexAttribArrayFn(vertex_location);
    api->glVertexAttribPointerFn(vertex_location, 2, GL_FLOAT, GL_FALSE, 0,
                                 nullptr);

    GLint sampler_location = api->glGetUniformLocationFn(program, "u_texture");
    ASSERT_NE(sampler_location, -1);
    api->glActiveTextureFn(GL_TEXTURE0);
    api->glBindTextureFn(GL_TEXTURE_2D, front_texture_id);
    api->glUniform1iFn(sampler_location, 0);

    api->glUseProgramFn(program);
    api->glDrawArraysFn(GL_TRIANGLES, 0, 6);

    {
      GLubyte pixel_color[4];
      const uint8_t expected_color[4] = {0, 255, 0, 255};
      api->glReadPixelsFn(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);
      EXPECT_EQ(expected_color[0], pixel_color[0]);
      EXPECT_EQ(expected_color[1], pixel_color[1]);
      EXPECT_EQ(expected_color[2], pixel_color[2]);
      EXPECT_EQ(expected_color[3], pixel_color[3]);
    }

    api->glDeleteProgramFn(program);
    api->glDeleteShaderFn(vertex_shader);
    api->glDeleteShaderFn(fragment_shader);
    api->glDeleteBuffersARBFn(1, &vbo);
  }

  api->glDeleteFramebuffersEXTFn(1, &fbo);
}

class D3DImageBackingFactoryTest : public D3DImageBackingFactoryTestBase {
 public:
  void SetUp() override {
    D3DImageBackingFactoryTestBase::SetUp();
    GpuDriverBugWorkarounds workarounds;
    scoped_refptr<gl::GLShareGroup> share_group = new gl::GLShareGroup();
    context_state_ = base::MakeRefCounted<SharedContextState>(
        std::move(share_group), surface_, context_,
        /*use_virtualized_gl_contexts=*/false, base::DoNothing(),
        GrContextType::kGL);
    context_state_->InitializeSkia(GpuPreferences(), workarounds);
    auto feature_info =
        base::MakeRefCounted<gles2::FeatureInfo>(workarounds, GpuFeatureInfo());
    context_state_->InitializeGL(GpuPreferences(), std::move(feature_info));
  }

 protected:
  GrDirectContext* gr_context() const { return context_state_->gr_context(); }

  void CheckSkiaPixels(
      SkiaImageRepresentation::ScopedReadAccess* scoped_read_access,
      const gfx::Size& size,
      const std::vector<uint8_t>& expected_color) const {
    auto* promise_texture = scoped_read_access->promise_image_texture();
    GrBackendTexture backend_texture = promise_texture->backendTexture();

    EXPECT_TRUE(backend_texture.isValid());
    EXPECT_EQ(size.width(), backend_texture.width());
    EXPECT_EQ(size.height(), backend_texture.height());

    // Create an Sk Image from GrBackendTexture.
    auto sk_image = SkImages::BorrowTextureFrom(
        gr_context(), backend_texture, kTopLeft_GrSurfaceOrigin,
        kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);

    const SkImageInfo dst_info =
        SkImageInfo::Make(size.width(), size.height(), kRGBA_8888_SkColorType,
                          kOpaque_SkAlphaType, nullptr);

    const int num_pixels = size.width() * size.height();
    std::vector<uint8_t> dst_pixels(num_pixels * 4);

    // Read back pixels from Sk Image.
    EXPECT_TRUE(sk_image->readPixels(dst_info, dst_pixels.data(),
                                     dst_info.minRowBytes(), 0, 0));

    for (int i = 0; i < num_pixels; i++) {
      // Compare the pixel values.
      const uint8_t* pixel = dst_pixels.data() + (i * 4);
      EXPECT_EQ(pixel[0], expected_color[0]);
      EXPECT_EQ(pixel[1], expected_color[1]);
      EXPECT_EQ(pixel[2], expected_color[2]);
      EXPECT_EQ(pixel[3], expected_color[3]);
    }
  }

  void CheckSkiaPixels(const Mailbox& mailbox,
                       const gfx::Size& size,
                       const std::vector<uint8_t>& expected_color) const {
    auto skia_representation =
        shared_image_representation_factory_->ProduceSkia(mailbox,
                                                          context_state_);
    ASSERT_NE(skia_representation, nullptr);

    std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
        scoped_read_access =
            skia_representation->BeginScopedReadAccess(nullptr, nullptr);
    EXPECT_TRUE(scoped_read_access);

    CheckSkiaPixels(scoped_read_access.get(), size, expected_color);
  }

  void CheckDawnPixels(
      DawnImageRepresentation::ScopedAccess* scoped_read_access,
      const wgpu::Instance& instance,
      const wgpu::Device& device,
      const gfx::Size& size,
      const std::vector<uint8_t>& expected_color) const;

  // Helper for opening multiple Dawn and Skia scoped access on given mailbox,
  // and checking for the expected color using both APIs concurrently.
  void DawnConcurrentReadTestHelper(const Mailbox& mailbox,
                                    const wgpu::Instance& instance,
                                    const wgpu::Device& device,
                                    const gfx::Size& size,
                                    const std::vector<uint8_t>& expected_color);

  std::unique_ptr<SharedImageRepresentationFactoryRef> CreateVideoImage(
      const gfx::Size& size,
      uint8_t y_fill_value,
      uint8_t u_fill_value,
      uint8_t v_fill_value,
      bool use_shared_handle,
      bool use_factory);
  void RunVideoTest(bool use_shared_handle, bool use_factory);
  void RunOverlayTest(bool use_shared_handle, bool use_factory);
  void RunCreateSharedImageFromHandleTest(DXGI_FORMAT dxgi_format);
  void RunCreateFromSharedMemoryMultiplanarTest(bool use_async_copy);
  void RunMultiplanarUploadAndReadback(bool use_upload_subresource);

  static constexpr wgpu::FeatureName kRequiredFeatures[] = {
      // We need to request internal usage to be able to do operations with
      // internal methods that would need specific usages.
      wgpu::FeatureName::DawnInternalUsages,

      // Required for Dawn interop.
      wgpu::FeatureName::SharedTextureMemoryDXGISharedHandle,
      wgpu::FeatureName::SharedFenceDXGISharedHandle,
  };

  scoped_refptr<SharedContextState> context_state_;
};

// Test to check interaction between Gl and skia GL representations.
// We write to a GL texture using gl representation and then read from skia
// representation.
TEST_F(D3DImageBackingFactoryTest, GL_SkiaGL) {
  // Create a backing using mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_GLES2_WRITE | SHARED_IMAGE_USAGE_DISPLAY_READ;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  GLenum expected_target = GL_TEXTURE_2D;
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Create a GLTextureImageRepresentation.
  auto gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          mailbox);
  EXPECT_EQ(expected_target,
            gl_representation->GetTexturePassthrough()->target());

  std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
      scoped_access = gl_representation->BeginScopedAccess(
          GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  EXPECT_TRUE(scoped_access);

  // Create an FBO.
  GLuint fbo = 0;
  gl::GLApi* api = gl::g_current_gl_context;
  api->glGenFramebuffersEXTFn(1, &fbo);
  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);

  // Attach the texture to FBO.
  api->glFramebufferTexture2DEXTFn(
      GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
      gl_representation->GetTexturePassthrough()->target(),
      gl_representation->GetTexturePassthrough()->service_id(), 0);

  // Set the clear color to green.
  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
  api->glClearFn(GL_COLOR_BUFFER_BIT);
  gl_representation->SetCleared();

  scoped_access.reset();
  gl_representation.reset();

  CheckSkiaPixels(mailbox, size, {0, 255, 0, 255});

  factory_ref.reset();
}

// Test to check interaction between Dawn and skia GL representations.
TEST_F(D3DImageBackingFactoryTest, Dawn_SkiaGL) {
  // Find a Dawn D3D12 adapter
  dawn::native::Instance instance;
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));

  // Create a backing using mailbox.
  const auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_WEBGPU_WRITE | SHARED_IMAGE_USAGE_DISPLAY_READ;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Clear the shared image to green using Dawn.
  {
    // Create a DawnImageRepresentation.
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    auto scoped_access = dawn_representation->BeginScopedAccess(
        wgpu::TextureUsage::RenderAttachment,
        SharedImageRepresentation::AllowUnclearedAccess::kYes);
    ASSERT_TRUE(scoped_access);

    wgpu::Texture texture(scoped_access->texture());

    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Clear;
    color_desc.storeOp = wgpu::StoreOp::Store;
    color_desc.clearValue = {0, 255, 0, 255};

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  CheckSkiaPixels(mailbox, size, {0, 255, 0, 255});
  factory_ref.reset();
}

void D3DImageBackingFactoryTest::CheckDawnPixels(
    DawnImageRepresentation::ScopedAccess* scoped_read_access,
    const wgpu::Instance& instance,
    const wgpu::Device& device,
    const gfx::Size& size,
    const std::vector<uint8_t>& expected_color) const {
  wgpu::Texture texture(scoped_read_access->texture());

  uint32_t buffer_stride = static_cast<uint32_t>(
      base::bits::AlignUpDeprecatedDoNotUse(size.width() * 4, 256));
  size_t buffer_size = static_cast<size_t>(size.height()) * buffer_stride;
  wgpu::BufferDescriptor buffer_desc{
      .usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
      .size = buffer_size};
  wgpu::Buffer buffer = device.CreateBuffer(&buffer_desc);

  wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
  auto src = wgpu::ImageCopyTexture{.texture = texture, .origin = {0, 0, 0}};
  auto dst = wgpu::ImageCopyBuffer{.layout = {.bytesPerRow = buffer_stride},
                                   .buffer = buffer};
  auto copy_size = wgpu::Extent3D{static_cast<uint32_t>(size.width()),
                                  static_cast<uint32_t>(size.height(), 1)};
  encoder.CopyTextureToBuffer(&src, &dst, &copy_size);
  wgpu::CommandBuffer commands = encoder.Finish();

  wgpu::Queue queue = device.GetQueue();
  queue.Submit(1, &commands);

  wgpu::FutureWaitInfo wait_info{buffer.MapAsync(
      wgpu::MapMode::Read, 0, buffer_desc.size, wgpu::CallbackMode::WaitAnyOnly,
      [&](wgpu::MapAsyncStatus status, const char*) {
        ASSERT_EQ(status, wgpu::MapAsyncStatus::Success);
      })};
  wgpu::WaitStatus status =
      instance.WaitAny(1, &wait_info, std::numeric_limits<uint64_t>::max());
  DCHECK(status == wgpu::WaitStatus::Success);

  const uint8_t* dst_pixels =
      reinterpret_cast<const uint8_t*>(buffer.GetConstMappedRange());
  for (int row = 0; row < size.height(); row++) {
    for (int col = 0; col < size.width(); col++) {
      // Compare the pixel values.
      const uint8_t* pixel = dst_pixels + (row * buffer_stride) + col * 4;
      EXPECT_EQ(pixel[0], expected_color[0]);
      EXPECT_EQ(pixel[1], expected_color[1]);
      EXPECT_EQ(pixel[2], expected_color[2]);
      EXPECT_EQ(pixel[3], expected_color[3]);
    }
  }
}

// Opens two Skia and two Dawn ScopedReadAccess and performs concurrent reads
// using both APIs.
void D3DImageBackingFactoryTest::DawnConcurrentReadTestHelper(
    const Mailbox& mailbox,
    const wgpu::Instance& instance,
    const wgpu::Device& device,
    const gfx::Size& size,
    const std::vector<uint8_t>& expected_color) {
  auto dawn_representation1 = shared_image_representation_factory_->ProduceDawn(
      mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
  ASSERT_TRUE(dawn_representation1);

  auto dawn_access1 = dawn_representation1->BeginScopedAccess(
      wgpu::TextureUsage::CopySrc,
      SharedImageRepresentation::AllowUnclearedAccess::kNo);
  ASSERT_TRUE(dawn_access1);

  auto dawn_representation2 = shared_image_representation_factory_->ProduceDawn(
      mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
  ASSERT_TRUE(dawn_representation2);

  auto dawn_access2 = dawn_representation2->BeginScopedAccess(
      wgpu::TextureUsage::CopySrc,
      SharedImageRepresentation::AllowUnclearedAccess::kNo);
  ASSERT_TRUE(dawn_access2);

  auto skia_representation1 = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_NE(skia_representation1, nullptr);

  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess> skia_access1 =
      skia_representation1->BeginScopedReadAccess(nullptr, nullptr);
  EXPECT_TRUE(skia_access1);

  auto skia_representation2 = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_NE(skia_representation2, nullptr);

  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess> skia_access2 =
      skia_representation2->BeginScopedReadAccess(nullptr, nullptr);
  EXPECT_TRUE(skia_access2);

  CheckDawnPixels(dawn_access1.get(), instance, device, size, expected_color);
  CheckDawnPixels(dawn_access2.get(), instance, device, size, expected_color);
  CheckSkiaPixels(skia_access1.get(), size, expected_color);
  CheckSkiaPixels(skia_access2.get(), size, expected_color);
}

TEST_F(D3DImageBackingFactoryTest, Dawn_ConcurrentReads) {
  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
      shared_image_factory_->GetDeviceForTesting();
  if (!gfx::D3DSharedFence::IsSupported(d3d11_device.Get())) {
    GTEST_SKIP();
  }

  // Find a Dawn D3D12 adapter
  WGPUInstanceDescriptor instance_desc = {
      .features =
          {
              .timedWaitAnyEnable = true,
          },
  };
  dawn::native::Instance instance(&instance_desc);
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));

  // Create a backing using mailbox.
  const auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_WEBGPU_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE |
      SHARED_IMAGE_USAGE_DISPLAY_READ | SHARED_IMAGE_USAGE_DISPLAY_WRITE;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Clear to red using Skia.
  {
    auto skia_representation =
        shared_image_representation_factory_->ProduceSkia(mailbox,
                                                          context_state_);
    ASSERT_NE(skia_representation, nullptr);

    auto scoped_write_access = skia_representation->BeginScopedWriteAccess(
        /*begin_semaphores=*/nullptr, /*end_semaphores=*/nullptr,
        SharedImageRepresentation::AllowUnclearedAccess::kYes);
    EXPECT_TRUE(scoped_write_access);

    SkCanvas* canvas = scoped_write_access->surface()->getCanvas();
    canvas->clear(SkColors::kRed);
    skgpu::ganesh::Flush(scoped_write_access->surface());

    skia_representation->SetCleared();
  }

  // Check if pixels are red using concurrent reads.
  DawnConcurrentReadTestHelper(mailbox, instance.Get(), device, size,
                               {255, 0, 0, 255});

  // Clear the shared image to green using Dawn.
  {
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    auto scoped_access = dawn_representation->BeginScopedAccess(
        wgpu::TextureUsage::RenderAttachment,
        SharedImageRepresentation::AllowUnclearedAccess::kNo);
    ASSERT_TRUE(scoped_access);

    wgpu::Texture texture(scoped_access->texture());

    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Clear;
    color_desc.storeOp = wgpu::StoreOp::Store;
    color_desc.clearValue = {0, 255, 0, 255};

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  // Check if pixels are green using concurrent reads.
  DawnConcurrentReadTestHelper(mailbox, instance.Get(), device, size,
                               {0, 255, 0, 255});
}

// 1. Draw a color to texture through GL
// 2. Do not call SetCleared so we can test Dawn Lazy clear
// 3. Begin render pass in Dawn, but do not do anything
// 4. Verify through CheckSkiaPixel that GL drawn color not seen
TEST_F(D3DImageBackingFactoryTest, GL_Dawn_Skia_UnclearTexture) {
  // Create a backing using mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage = SHARED_IMAGE_USAGE_GLES2_WRITE |
                                         SHARED_IMAGE_USAGE_DISPLAY_READ |
                                         SHARED_IMAGE_USAGE_WEBGPU_WRITE;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  GLenum expected_target = GL_TEXTURE_2D;
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());
  {
    // Create a GLTextureImageRepresentation.
    auto gl_representation =
        shared_image_representation_factory_->ProduceGLTexturePassthrough(
            mailbox);
    EXPECT_EQ(expected_target,
              gl_representation->GetTexturePassthrough()->target());

    std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
        gl_scoped_access = gl_representation->BeginScopedAccess(
            GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
            SharedImageRepresentation::AllowUnclearedAccess::kYes);
    EXPECT_TRUE(gl_scoped_access);

    // Create an FBO.
    GLuint fbo = 0;
    gl::GLApi* api = gl::g_current_gl_context;
    api->glGenFramebuffersEXTFn(1, &fbo);
    api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);

    // Attach the texture to FBO.
    api->glFramebufferTexture2DEXTFn(
        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
        gl_representation->GetTexturePassthrough()->target(),
        gl_representation->GetTexturePassthrough()->service_id(), 0);

    // Set the clear color to green.
    api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
    api->glClearFn(GL_COLOR_BUFFER_BIT);

    // Don't call SetCleared, we want to see if Dawn will lazy clear the texture
    EXPECT_FALSE(factory_ref->IsCleared());
  }

  // Find a Dawn D3D12 adapter
  dawn::native::Instance instance;
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));

  {
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    auto dawn_scoped_access = dawn_representation->BeginScopedAccess(
        wgpu::TextureUsage::RenderAttachment,
        SharedImageRepresentation::AllowUnclearedAccess::kYes);
    ASSERT_TRUE(dawn_scoped_access);

    wgpu::Texture texture(dawn_scoped_access->texture());
    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Load;
    color_desc.storeOp = wgpu::StoreOp::Store;

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  // Check skia pixels returns black since texture was lazy cleared in Dawn
  EXPECT_TRUE(factory_ref->IsCleared());
  CheckSkiaPixels(mailbox, size, {0, 0, 0, 0});
  factory_ref.reset();
}

// 1. Draw  a color to texture through Dawn
// 2. Set the renderpass storeOp = Discard
// 3. Texture in Dawn will stay as uninitialized
// 3. Expect skia to fail to access the texture because texture is not
// initialized
TEST_F(D3DImageBackingFactoryTest, UnclearDawn_SkiaFails) {
  // Create a backing using mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_DISPLAY_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Find a Dawn D3D12 adapter
  dawn::native::Instance instance;
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));
  {
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    auto dawn_scoped_access = dawn_representation->BeginScopedAccess(
        wgpu::TextureUsage::RenderAttachment,
        SharedImageRepresentation::AllowUnclearedAccess::kYes);
    ASSERT_TRUE(dawn_scoped_access);

    wgpu::Texture texture(dawn_scoped_access->texture());
    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Clear;
    color_desc.storeOp = wgpu::StoreOp::Discard;
    color_desc.clearValue = {0, 255, 0, 255};

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  EXPECT_FALSE(factory_ref->IsCleared());

  // Produce skia representation
  auto skia_representation = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_NE(skia_representation, nullptr);

  // Expect BeginScopedReadAccess to fail because sharedImage is uninitialized
  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      scoped_read_access =
          skia_representation->BeginScopedReadAccess(nullptr, nullptr);
  EXPECT_EQ(scoped_read_access, nullptr);
}

// Test that Skia trying to access uninitialized SharedImage will fail
TEST_F(D3DImageBackingFactoryTest, SkiaAccessFirstFails) {
  // Create a mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage = SHARED_IMAGE_USAGE_DISPLAY_READ;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Produce skia representation
  auto skia_representation = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_NE(skia_representation, nullptr);
  EXPECT_FALSE(skia_representation->IsCleared());

  // Expect BeginScopedReadAccess to fail because sharedImage is uninitialized
  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      scoped_read_access =
          skia_representation->BeginScopedReadAccess(nullptr, nullptr);
  EXPECT_EQ(scoped_read_access, nullptr);
}

TEST_F(D3DImageBackingFactoryTest, CreateFromPixelData) {
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage = SHARED_IMAGE_USAGE_DISPLAY_READ;
  const std::vector<uint8_t> pixel_data = {0x01, 0x02, 0x03, 0x04};
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, size, color_space, kTopLeft_GrSurfaceOrigin,
      kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false, base::span<const uint8_t>(pixel_data));
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  CheckSkiaPixels(mailbox, size, pixel_data);
}

void D3DImageBackingFactoryTest::RunCreateSharedImageFromHandleTest(
    DXGI_FORMAT dxgi_format) {
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  // This function tests concurrent GL reads of two SharedImages created from
  // the underlying handle.
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_GLES2_READ | SHARED_IMAGE_USAGE_DISPLAY_READ;
  const GrSurfaceOrigin surface_origin = kTopLeft_GrSurfaceOrigin;
  const SkAlphaType alpha_type = kPremul_SkAlphaType;

  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      usage, format, size, /*thread_safe=*/false, gfx::DXGI_SHARED_HANDLE,
      GrContextType::kGL, /*pixel_data=*/{}));

  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
      shared_image_factory_->GetDeviceForTesting();

  D3D11_TEXTURE2D_DESC desc;
  desc.Width = size.width();
  desc.Height = size.height();
  desc.MipLevels = 1;
  desc.ArraySize = 1;
  desc.Format = dxgi_format;
  desc.SampleDesc.Count = 1;
  desc.SampleDesc.Quality = 0;
  desc.Usage = D3D11_USAGE_DEFAULT;
  desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
  desc.CPUAccessFlags = 0;
  desc.MiscFlags =
      D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED;
  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
  HRESULT hr = d3d11_device->CreateTexture2D(&desc, nullptr, &d3d11_texture);
  ASSERT_EQ(hr, S_OK);

  Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
  hr = d3d11_texture.As(&dxgi_resource);
  ASSERT_EQ(hr, S_OK);

  HANDLE shared_handle;
  hr = dxgi_resource->CreateSharedHandle(
      nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
      &shared_handle);
  ASSERT_EQ(hr, S_OK);

  gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle;
  gpu_memory_buffer_handle.dxgi_handle.Set(shared_handle);
  gpu_memory_buffer_handle.dxgi_token = gfx::DXGIHandleToken();
  gpu_memory_buffer_handle.type = gfx::DXGI_SHARED_HANDLE;

  // Clone before moving the handle in CreateSharedImage.
  auto dup_handle = gpu_memory_buffer_handle.Clone();

  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
      "TestLabel", std::move(gpu_memory_buffer_handle));
  ASSERT_NE(backing, nullptr);

  EXPECT_EQ(backing->format(), format);
  EXPECT_EQ(backing->size(), size);
  EXPECT_EQ(backing->color_space(), color_space);
  EXPECT_EQ(backing->surface_origin(), surface_origin);
  EXPECT_EQ(backing->alpha_type(), alpha_type);
  EXPECT_EQ(backing->mailbox(), mailbox);
  EXPECT_TRUE(backing->IsCleared());

  D3DImageBacking* backing_d3d = static_cast<D3DImageBacking*>(backing.get());
  EXPECT_EQ(
      backing_d3d->dxgi_shared_handle_state_for_testing()->GetSharedHandle(),
      shared_handle);

  // Check that a second backing created from the duplicated handle shares the
  // shared handle state and texture with the first backing.
  auto dup_mailbox = Mailbox::Generate();
  auto dup_backing = shared_image_factory_->CreateSharedImage(
      dup_mailbox, format, size, color_space, surface_origin, alpha_type, usage,
      "TestLabel", std::move(dup_handle));
  ASSERT_NE(dup_backing, nullptr);

  EXPECT_EQ(dup_backing->format(), format);
  EXPECT_EQ(dup_backing->size(), size);
  EXPECT_EQ(dup_backing->color_space(), color_space);
  EXPECT_EQ(dup_backing->surface_origin(), surface_origin);
  EXPECT_EQ(dup_backing->alpha_type(), alpha_type);
  EXPECT_EQ(dup_backing->mailbox(), dup_mailbox);
  EXPECT_TRUE(dup_backing->IsCleared());

  D3DImageBacking* dup_backing_d3d =
      static_cast<D3DImageBacking*>(dup_backing.get());
  EXPECT_EQ(dup_backing_d3d->dxgi_shared_handle_state_for_testing(),
            backing_d3d->dxgi_shared_handle_state_for_testing());
  EXPECT_EQ(dup_backing_d3d->d3d11_texture_for_testing(),
            backing_d3d->d3d11_texture_for_testing());

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  std::unique_ptr<SharedImageRepresentationFactoryRef> dup_factory_ref =
      shared_image_manager_.Register(std::move(dup_backing),
                                     memory_type_tracker_.get());

  // Check that concurrent read access using the duplicated handle works.
  auto gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          mailbox);
  EXPECT_TRUE(gl_representation);

  std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
      scoped_access = gl_representation->BeginScopedAccess(
          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  EXPECT_TRUE(scoped_access);

  auto dup_gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          dup_mailbox);
  EXPECT_TRUE(dup_gl_representation);

  std::unique_ptr<GLTexturePassthroughImageRepresentation::ScopedAccess>
      dup_scoped_access = dup_gl_representation->BeginScopedAccess(
          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  EXPECT_TRUE(dup_scoped_access);
}

TEST_F(D3DImageBackingFactoryTest, CreateSharedImageFromHandleFormatUNORM) {
  RunCreateSharedImageFromHandleTest(DXGI_FORMAT_R8G8B8A8_UNORM);
}

TEST_F(D3DImageBackingFactoryTest, CreateSharedImageFromHandleFormatTYPELESS) {
  RunCreateSharedImageFromHandleTest(DXGI_FORMAT_R8G8B8A8_TYPELESS);
}

// Test to check external image stored in the backing can be reused
TEST_F(D3DImageBackingFactoryTest, Dawn_ReuseExternalImage) {
  // Create a backing using mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_DISPLAY_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Find a Dawn D3D12 adapter
  dawn::native::Instance instance;
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));

  const wgpu::TextureUsage texture_usage = wgpu::TextureUsage::RenderAttachment;

  // Create the first Dawn texture then clear it to green.
  {
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    auto scoped_access = dawn_representation->BeginScopedAccess(
        texture_usage, SharedImageRepresentation::AllowUnclearedAccess::kYes);
    ASSERT_TRUE(scoped_access);

    wgpu::Texture texture(scoped_access->texture());

    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Clear;
    color_desc.storeOp = wgpu::StoreOp::Store;
    color_desc.clearValue = {0, 255, 0, 255};

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  CheckSkiaPixels(mailbox, size, {0, 255, 0, 255});

  // Create another Dawn texture then clear it with another color.
  {
    auto dawn_representation =
        shared_image_representation_factory_->ProduceDawn(
            mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
    ASSERT_TRUE(dawn_representation);

    // Check again that the texture is still green
    CheckSkiaPixels(mailbox, size, {0, 255, 0, 255});

    auto scoped_access = dawn_representation->BeginScopedAccess(
        texture_usage, SharedImageRepresentation::AllowUnclearedAccess::kYes);
    ASSERT_TRUE(scoped_access);

    wgpu::Texture texture(scoped_access->texture());

    wgpu::RenderPassColorAttachment color_desc;
    color_desc.view = texture.CreateView();
    color_desc.resolveTarget = nullptr;
    color_desc.loadOp = wgpu::LoadOp::Clear;
    color_desc.storeOp = wgpu::StoreOp::Store;
    color_desc.clearValue = {255, 0, 0, 255};

    wgpu::RenderPassDescriptor renderPassDesc = {};
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &color_desc;
    renderPassDesc.depthStencilAttachment = nullptr;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
    pass.End();
    wgpu::CommandBuffer commands = encoder.Finish();

    wgpu::Queue queue = device.GetQueue();
    queue.Submit(1, &commands);
  }

  CheckSkiaPixels(mailbox, size, {255, 0, 0, 255});

  factory_ref.reset();
}

// Check if making Dawn have the last ref works without a current GL context.
TEST_F(D3DImageBackingFactoryTest, Dawn_HasLastRef) {
  // Create a backing using mailbox.
  auto mailbox = Mailbox::Generate();
  const auto format = viz::SinglePlaneFormat::kRGBA_8888;
  const gfx::Size size(1, 1);
  const auto color_space = gfx::ColorSpace::CreateSRGB();
  const gpu::SharedImageUsageSet usage =
      SHARED_IMAGE_USAGE_DISPLAY_READ | SHARED_IMAGE_USAGE_WEBGPU_READ;
  const gpu::SurfaceHandle surface_handle = gpu::kNullSurfaceHandle;
  auto backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, surface_handle, size, color_space,
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(backing, nullptr);

  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  // Find a Dawn D3D12 adapter
  dawn::native::Instance instance;
  wgpu::RequestAdapterOptions adapter_options;
  adapter_options.backendType = wgpu::BackendType::D3D12;
  std::vector<dawn::native::Adapter> adapters =
      instance.EnumerateAdapters(&adapter_options);
  ASSERT_GT(adapters.size(), 0u);

  wgpu::DeviceDescriptor device_descriptor;
  device_descriptor.requiredFeatureCount = std::size(kRequiredFeatures);
  device_descriptor.requiredFeatures = kRequiredFeatures;

  wgpu::Device device =
      wgpu::Device::Acquire(adapters[0].CreateDevice(&device_descriptor));
  auto dawn_representation = shared_image_representation_factory_->ProduceDawn(
      mailbox, device, wgpu::BackendType::D3D12, {}, context_state_);
  ASSERT_NE(dawn_representation, nullptr);

  // Creating the Skia representation will also create a temporary GL texture.
  auto skia_representation = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_NE(skia_representation, nullptr);

  // Drop Skia representation and factory ref so that the Dawn representation
  // has the last ref.
  skia_representation.reset();
  factory_ref.reset();

  // Ensure no GL context is current.
  context_->ReleaseCurrent(surface_.get());

  // This shouldn't crash due to no GL context being current.
  dawn_representation.reset();

  // Make context current so that it can be destroyed.
  context_->MakeCurrent(surface_.get());
}

std::unique_ptr<SharedImageRepresentationFactoryRef>
D3DImageBackingFactoryTest::CreateVideoImage(const gfx::Size& size,
                                             uint8_t y_fill_value,
                                             uint8_t u_fill_value,
                                             uint8_t v_fill_value,
                                             bool use_shared_handle,
                                             bool use_factory) {
  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
      shared_image_factory_->GetDeviceForTesting();

  const size_t kDataSize = size.width() * size.height() * 3 / 2;

  std::vector<uint8_t> video_data(kDataSize);
  FillNV12(video_data.data(), size, y_fill_value, u_fill_value, v_fill_value);

  D3D11_SUBRESOURCE_DATA data = {};
  data.pSysMem = static_cast<const void*>(video_data.data());
  data.SysMemPitch = static_cast<UINT>(size.width());

  CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_NV12, size.width(), size.height(), 1,
                             1, D3D11_BIND_SHADER_RESOURCE);
  if (use_shared_handle) {
    desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
                     D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
  }

  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
  HRESULT hr = d3d11_device->CreateTexture2D(&desc, &data, &d3d11_texture);
  if (FAILED(hr))
    return {};

  // The video tests read from the created SharedImages via GL.
  gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE |
      gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_RASTER_READ |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;

  base::win::ScopedHandle shared_handle;
  if (use_shared_handle) {
    Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
    hr = d3d11_texture.As(&dxgi_resource);
    DCHECK_EQ(hr, S_OK);

    HANDLE handle = nullptr;
    hr = dxgi_resource->CreateSharedHandle(
        nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
        nullptr, &handle);
    if (FAILED(hr))
      return {};

    shared_handle.Set(handle);
    DCHECK(shared_handle.is_valid());

    // TODO(dawn:551): Extend the tests using this util to test reading the
    // textures via Dawn.
    usage |= gpu::SHARED_IMAGE_USAGE_WEBGPU_READ;
  }

  const gpu::Mailbox mailbox = gpu::Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> shared_image_backing;
  if (use_factory) {
    gfx::GpuMemoryBufferHandle gmb_handle;
    gmb_handle.type = gfx::DXGI_SHARED_HANDLE;
    gmb_handle.dxgi_handle = std::move(shared_handle);
    DCHECK(gmb_handle.dxgi_handle.IsValid());
    gmb_handle.dxgi_token = gfx::DXGIHandleToken();

    shared_image_backing = shared_image_factory_->CreateSharedImage(
        mailbox, viz::MultiPlaneFormat::kNV12, size, gfx::ColorSpace(),
        kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
        std::move(gmb_handle));
    if (!shared_image_backing) {
      return {};
    }
  } else {
    scoped_refptr<DXGISharedHandleState> dxgi_shared_handle_state;
    if (use_shared_handle) {
      dxgi_shared_handle_state =
          shared_image_manager_.dxgi_shared_handle_manager()
              ->CreateAnonymousSharedHandleState(std::move(shared_handle),
                                                 d3d11_texture);
    }
    shared_image_backing = D3DImageBacking::Create(
        mailbox, viz::MultiPlaneFormat::kNV12, size, gfx::ColorSpace(),
        kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel",
        d3d11_texture, /*dcomp_texture=*/nullptr,
        std::move(dxgi_shared_handle_state), context_state_->GetGLFormatCaps(),
        GL_TEXTURE_EXTERNAL_OES,
        /*array_slice=*/0, /*plane_index=*/0u);
    // Need to clear the backing created with shared handle.
    shared_image_backing->SetCleared();
  }

  auto& backing = shared_image_backing;
  EXPECT_EQ(backing->mailbox(), mailbox);
  EXPECT_EQ(backing->size(), size);
  EXPECT_EQ(backing->format(), viz::MultiPlaneFormat::kNV12);
  EXPECT_EQ(backing->color_space(), gfx::ColorSpace());
  EXPECT_EQ(backing->surface_origin(), kTopLeft_GrSurfaceOrigin);
  EXPECT_EQ(backing->alpha_type(), kPremul_SkAlphaType);
  EXPECT_EQ(backing->usage(), usage);
  EXPECT_TRUE(backing->IsCleared());

  auto shared_image_ref = shared_image_manager_.Register(
      std::move(backing), memory_type_tracker_.get());

  return shared_image_ref;
}

void D3DImageBackingFactoryTest::RunVideoTest(bool use_shared_handle,
                                              bool use_factory) {
  const gfx::Size size(32, 32);

  const uint8_t kYFillValue = 0x12;
  const uint8_t kUFillValue = 0x23;
  const uint8_t kVFillValue = 0x34;

  auto shared_image_ref =
      CreateVideoImage(size, kYFillValue, kUFillValue, kVFillValue,
                       use_shared_handle, use_factory);
  ASSERT_TRUE(shared_image_ref);

  // Setup GL shaders, framebuffers, uniforms, etc.
  static const char* kVideoFragmentShaderSrcTextureExternal =
      "#extension GL_OES_EGL_image_external : require\n"
      "precision mediump float;\n"
      "uniform samplerExternalOES u_texture_y;\n"
      "uniform samplerExternalOES u_texture_uv;\n"
      "varying vec2 v_texCoord;\n"
      "void main() {\n"
      "  gl_FragColor.r = texture2D(u_texture_y, v_texCoord).r;\n"
      "  gl_FragColor.gb = texture2D(u_texture_uv, v_texCoord).rg;\n"
      "  gl_FragColor.a = 1.0;\n"
      "}\n";
  static const char* kVideoFragmentShaderSrcTexture2D =
      "precision mediump float;\n"
      "uniform sampler2D u_texture_y;\n"
      "uniform sampler2D u_texture_uv;\n"
      "varying vec2 v_texCoord;\n"
      "void main() {\n"
      "  gl_FragColor.r = texture2D(u_texture_y, v_texCoord).r;\n"
      "  gl_FragColor.gb = texture2D(u_texture_uv, v_texCoord).rg;\n"
      "  gl_FragColor.a = 1.0;\n"
      "}\n";

  gl::GLApi* api = gl::g_current_gl_context;

  GLint status = 0;
  GLuint vertex_shader = api->glCreateShaderFn(GL_VERTEX_SHADER);
  SCOPED_GL_CLEANUP_VAR(api, DeleteShader, vertex_shader);
  ASSERT_NE(vertex_shader, 0u);
  api->glShaderSourceFn(vertex_shader, 1, &kVertexShaderSrc, nullptr);
  api->glCompileShaderFn(vertex_shader);
  api->glGetShaderivFn(vertex_shader, GL_COMPILE_STATUS, &status);
  ASSERT_NE(status, 0);

  GLuint fragment_shader = api->glCreateShaderFn(GL_FRAGMENT_SHADER);
  SCOPED_GL_CLEANUP_VAR(api, DeleteShader, fragment_shader);
  ASSERT_NE(fragment_shader, 0u);
  api->glShaderSourceFn(fragment_shader, 1,
                        use_factory ? &kVideoFragmentShaderSrcTexture2D
                                    : &kVideoFragmentShaderSrcTextureExternal,
                        nullptr);
  api->glCompileShaderFn(fragment_shader);
  api->glGetShaderivFn(fragment_shader, GL_COMPILE_STATUS, &status);
  ASSERT_NE(status, 0);

  GLuint program = api->glCreateProgramFn();
  ASSERT_NE(program, 0u);
  SCOPED_GL_CLEANUP_VAR(api, DeleteProgram, program);
  api->glAttachShaderFn(program, vertex_shader);
  api->glAttachShaderFn(program, fragment_shader);
  api->glLinkProgramFn(program);
  api->glGetProgramivFn(program, GL_LINK_STATUS, &status);
  ASSERT_NE(status, 0);

  GLint vertex_location = api->glGetAttribLocationFn(program, "a_position");
  ASSERT_NE(vertex_location, -1);

  GLint y_texture_location =
      api->glGetUniformLocationFn(program, "u_texture_y");
  ASSERT_NE(y_texture_location, -1);

  GLint uv_texture_location =
      api->glGetUniformLocationFn(program, "u_texture_uv");
  ASSERT_NE(uv_texture_location, -1);

  GLuint fbo, renderbuffer = 0u;
  api->glGenFramebuffersEXTFn(1, &fbo);
  ASSERT_NE(fbo, 0u);
  SCOPED_GL_CLEANUP_PTR(api, DeleteFramebuffersEXT, 1, fbo);
  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);

  api->glGenRenderbuffersEXTFn(1, &renderbuffer);
  ASSERT_NE(renderbuffer, 0u);
  SCOPED_GL_CLEANUP_PTR(api, DeleteRenderbuffersEXT, 1, renderbuffer);
  api->glBindRenderbufferEXTFn(GL_RENDERBUFFER, renderbuffer);

  api->glRenderbufferStorageEXTFn(GL_RENDERBUFFER, GL_RGBA8_OES, size.width(),
                                  size.height());
  api->glFramebufferRenderbufferEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      GL_RENDERBUFFER, renderbuffer);
  ASSERT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));

  // Set the clear color to green.
  api->glViewportFn(0, 0, size.width(), size.height());
  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
  api->glClearFn(GL_COLOR_BUFFER_BIT);

  GLuint vbo = 0u;
  api->glGenBuffersARBFn(1, &vbo);
  ASSERT_NE(vbo, 0u);
  SCOPED_GL_CLEANUP_PTR(api, DeleteBuffersARB, 1, vbo);
  api->glBindBufferFn(GL_ARRAY_BUFFER, vbo);
  static const float vertices[] = {
      1.0f, 1.0f, -1.0f, 1.0f,  -1.0f, -1.0f,
      1.0f, 1.0f, -1.0f, -1.0f, 1.0f,  -1.0f,
  };
  api->glBufferDataFn(GL_ARRAY_BUFFER, sizeof(vertices), vertices,
                      GL_STATIC_DRAW);

  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  // Create the representations for the planes, get the texture ids, bind to
  // samplers, and draw.
  auto texture =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          shared_image_ref->mailbox());
  ASSERT_NE(texture, nullptr);

  auto texture_access = texture->BeginScopedAccess(
      GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM,
      SharedImageRepresentation::AllowUnclearedAccess::kNo);
  ASSERT_NE(texture_access, nullptr);

  api->glActiveTextureFn(GL_TEXTURE0);
  api->glBindTextureFn(
      use_factory ? GL_TEXTURE_2D : GL_TEXTURE_EXTERNAL_OES,
      texture->GetTexturePassthrough(/*plane_index=*/0)->service_id());
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glActiveTextureFn(GL_TEXTURE1);
  api->glBindTextureFn(
      use_factory ? GL_TEXTURE_2D : GL_TEXTURE_EXTERNAL_OES,
      texture->GetTexturePassthrough(/*plane_index=*/1)->service_id());
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  api->glUseProgramFn(program);

  api->glEnableVertexAttribArrayFn(vertex_location);
  api->glVertexAttribPointerFn(vertex_location, 2, GL_FLOAT, GL_FALSE, 0,
                               nullptr);

  api->glUniform1iFn(y_texture_location, 0);
  api->glUniform1iFn(uv_texture_location, 1);

  api->glDrawArraysFn(GL_TRIANGLES, 0, 6);
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  GLubyte pixel_color[4];
  api->glReadPixelsFn(size.width() / 2, size.height() / 2, 1, 1, GL_RGBA,
                      GL_UNSIGNED_BYTE, pixel_color);
  EXPECT_EQ(kYFillValue, pixel_color[0]);
  EXPECT_EQ(kUFillValue, pixel_color[1]);
  EXPECT_EQ(kVFillValue, pixel_color[2]);
  EXPECT_EQ(255, pixel_color[3]);
  // TODO(dawn:551): Test Dawn access after multi-planar support lands in Dawn.
}

TEST_F(D3DImageBackingFactoryTest, CreateFromVideoTexture) {
  RunVideoTest(/*use_shared_handle=*/false, /*use_factory=*/false);
}

TEST_F(D3DImageBackingFactoryTest, CreateFromVideoTextureSharedHandle) {
  RunVideoTest(/*use_shared_handle=*/true, /*use_factory=*/false);
}

TEST_F(D3DImageBackingFactoryTest,
       CreateFromVideoTextureViaFactoryMultiplanar) {
  RunVideoTest(/*use_shared_handle=*/true,
               /*use_factory=*/true);
}

void D3DImageBackingFactoryTest::RunOverlayTest(bool use_shared_handle,
                                                bool use_factory) {
  constexpr gfx::Size size(32, 32);

  constexpr uint8_t kYFillValue = 0x12;
  constexpr uint8_t kUFillValue = 0x23;
  constexpr uint8_t kVFillValue = 0x34;

  auto shared_image_ref =
      CreateVideoImage(size, kYFillValue, kUFillValue, kVFillValue,
                       use_shared_handle, use_factory);
  ASSERT_TRUE(shared_image_ref);

  auto overlay_representation =
      shared_image_representation_factory_->ProduceOverlay(
          shared_image_ref->mailbox());

  auto scoped_read_access = overlay_representation->BeginScopedReadAccess();
  ASSERT_TRUE(scoped_read_access);

  std::optional<gl::DCLayerOverlayImage> overlay_image =
      scoped_read_access->GetDCLayerOverlayImage();
  ASSERT_TRUE(overlay_image);
  EXPECT_EQ(overlay_image->type(), gl::DCLayerOverlayType::kNV12Texture);

  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
      shared_image_factory_->GetDeviceForTesting();

  CD3D11_TEXTURE2D_DESC staging_desc(
      DXGI_FORMAT_NV12, size.width(), size.height(), 1, 1, 0,
      D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ);

  Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_texture;
  HRESULT hr =
      d3d11_device->CreateTexture2D(&staging_desc, nullptr, &staging_texture);
  ASSERT_EQ(hr, S_OK);

  Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;
  d3d11_device->GetImmediateContext(&device_context);

  device_context->CopyResource(staging_texture.Get(),
                               overlay_image->nv12_texture());
  D3D11_MAPPED_SUBRESOURCE mapped_resource = {};
  hr = device_context->Map(staging_texture.Get(), 0, D3D11_MAP_READ, 0,
                           &mapped_resource);
  ASSERT_EQ(hr, S_OK);

  CheckNV12(static_cast<const uint8_t*>(mapped_resource.pData),
            mapped_resource.RowPitch, size, kYFillValue, kUFillValue,
            kVFillValue);

  device_context->Unmap(staging_texture.Get(), 0);
}

TEST_F(D3DImageBackingFactoryTest, CreateFromVideoTextureOverlay) {
  RunOverlayTest(/*use_shared_handle=*/false, /*use_factory=*/false);
}

TEST_F(D3DImageBackingFactoryTest, CreateFromVideoTextureSharedHandleOverlay) {
  RunOverlayTest(/*use_shared_handle=*/true, /*use_factory=*/false);
}

TEST_F(D3DImageBackingFactoryTest,
       CreateFromVideoTextureViaFactoryMultiplanarOverlay) {
  RunOverlayTest(/*use_shared_handle=*/true,
                 /*use_factory=*/true);
}

void D3DImageBackingFactoryTest::RunCreateFromSharedMemoryMultiplanarTest(
    bool use_async_copy) {
  constexpr gfx::Size size(32, 32);
  constexpr size_t kDataSize = size.width() * size.height() * 3 / 2;

  const gpu::Mailbox mailbox = gpu::Mailbox::Generate();

  // This test writes to the created SharedImages via GL and then reads back
  // those contents via GL for verification.
  constexpr gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE |
      gpu::SHARED_IMAGE_USAGE_GLES2_READ | SHARED_IMAGE_USAGE_GLES2_WRITE |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;

  auto shm_region = base::UnsafeSharedMemoryRegion::Create(kDataSize);
  {
    base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
    FillNV12(shm_mapping.GetMemoryAs<uint8_t>(), size, 255, 255, 255);
  }

  gfx::GpuMemoryBufferHandle shm_gmb_handle;
  shm_gmb_handle.type = gfx::SHARED_MEMORY_BUFFER;
  shm_gmb_handle.region = shm_region.Duplicate();
  DCHECK(shm_gmb_handle.region.IsValid());
  shm_gmb_handle.stride = size.width();

  // CompoundImageBacking wrapping D3DImageBacking is required for shared
  // memory support.
  auto backing = CompoundImageBacking::CreateSharedMemory(
      shared_image_factory_.get(), mailbox, std::move(shm_gmb_handle),
      viz::MultiPlaneFormat::kNV12, size, gfx::ColorSpace(),
      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, "TestLabel");
  EXPECT_NE(backing, nullptr);

  EXPECT_EQ(backing->mailbox(), mailbox);
  EXPECT_EQ(backing->size(), size);
  EXPECT_EQ(backing->format(), viz::MultiPlaneFormat::kNV12);
  EXPECT_EQ(backing->color_space(), gfx::ColorSpace());
  EXPECT_EQ(backing->surface_origin(), kTopLeft_GrSurfaceOrigin);
  EXPECT_EQ(backing->alpha_type(), kPremul_SkAlphaType);
  EXPECT_EQ(backing->usage(), usage);
  EXPECT_TRUE(backing->IsCleared());

  auto shared_image_ref = shared_image_manager_.Register(
      std::move(backing), memory_type_tracker_.get());

  constexpr uint8_t kYClearValue = 0x12;
  constexpr uint8_t kUClearValue = 0x23;
  constexpr uint8_t kVClearValue = 0x34;

  gl::GLApi* api = gl::g_current_gl_context;

  GLuint fbo;
  api->glGenFramebuffersEXTFn(1, &fbo);
  ASSERT_NE(fbo, 0u);
  SCOPED_GL_CLEANUP_PTR(api, DeleteFramebuffersEXT, 1, fbo);
  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);

  auto gl_representation =
      shared_image_representation_factory_->ProduceGLTexturePassthrough(
          mailbox);
  ASSERT_NE(gl_representation, nullptr);

  auto gl_access = gl_representation->BeginScopedAccess(
      GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM,
      SharedImageRepresentation::AllowUnclearedAccess::kNo);
  ASSERT_NE(gl_access, nullptr);

  GLuint y_texture_id =
      gl_representation->GetTexturePassthrough(/*plane_index=*/0)->service_id();
  api->glBindTextureFn(GL_TEXTURE_2D, y_texture_id);
  api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_2D, y_texture_id, 0);
  ASSERT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  GLubyte y_value;
  api->glReadPixelsFn(size.width() / 2, size.height() / 2, 1, 1, GL_RED,
                      GL_UNSIGNED_BYTE, &y_value);
  EXPECT_EQ(255, y_value);

  api->glViewportFn(0, 0, size.width(), size.height());
  api->glClearColorFn(kYClearValue / 255.0f, 0, 0, 0);
  api->glClearFn(GL_COLOR_BUFFER_BIT);

  api->glReadPixelsFn(size.width() / 2, size.height() / 2, 1, 1, GL_RED,
                      GL_UNSIGNED_BYTE, &y_value);
  EXPECT_EQ(kYClearValue, y_value);

  GLuint uv_texture_id =
      gl_representation->GetTexturePassthrough(/*plane_index=*/1)->service_id();
  api->glBindTextureFn(GL_TEXTURE_2D, uv_texture_id);
  api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_2D, uv_texture_id, 0);
  ASSERT_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
            static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE));
  ASSERT_EQ(api->glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

  GLubyte uv_value[2];
  api->glReadPixelsFn(size.width() / 4, size.height() / 4, 1, 1, GL_RG,
                      GL_UNSIGNED_BYTE, uv_value);
  EXPECT_EQ(255, uv_value[0]);
  EXPECT_EQ(255, uv_value[1]);

  api->glViewportFn(0, 0, size.width(), size.height());
  api->glClearColorFn(kUClearValue / 255.0f, kVClearValue / 255.0f, 0, 0);
  api->glClearFn(GL_COLOR_BUFFER_BIT);

  api->glReadPixelsFn(size.width() / 4, size.height() / 4, 1, 1, GL_RG,
                      GL_UNSIGNED_BYTE, uv_value);
  EXPECT_EQ(kUClearValue, uv_value[0]);
  EXPECT_EQ(kVClearValue, uv_value[1]);

  gl_access.reset();
  gl_representation.reset();

  if (use_async_copy) {
    base::RunLoop run_loop;
    shared_image_ref->CopyToGpuMemoryBufferAsync(
        base::BindLambdaForTesting([&](bool succeeded) {
            EXPECT_TRUE(succeeded);
            run_loop.Quit();
        }));
    run_loop.Run();
  } else {
    EXPECT_TRUE(shared_image_ref->CopyToGpuMemoryBuffer());
  }

  {
    base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
    CheckNV12(shm_mapping.GetMemoryAs<uint8_t>(), size.width(), size,
              kYClearValue, kUClearValue, kVClearValue);
  }

  {
    auto overlay_representation =
        shared_image_representation_factory_->ProduceOverlay(mailbox);

    auto scoped_read_access = overlay_representation->BeginScopedReadAccess();
    ASSERT_TRUE(scoped_read_access);

    std::optional<gl::DCLayerOverlayImage> overlay_image =
        scoped_read_access->GetDCLayerOverlayImage();
    ASSERT_TRUE(overlay_image);
    EXPECT_EQ(overlay_image->type(), gl::DCLayerOverlayType::kNV12Pixmap);

    CheckNV12(overlay_image->nv12_pixmap(), overlay_image->pixmap_stride(),
              size, kYClearValue, kUClearValue, kVClearValue);
  }
}

TEST_F(D3DImageBackingFactoryTest, CreateFromSharedMemoryMultiplanar) {
    RunCreateFromSharedMemoryMultiplanarTest(/*use_async_copy=*/false);
}

TEST_F(D3DImageBackingFactoryTest, CreateFromSharedMemoryMultiplanarAsyncCopy) {
    RunCreateFromSharedMemoryMultiplanarTest(/*use_async_copy=*/true);
}

// Verifies that a multi-planar NV12 image can be created without DXGI handle
// for use with software GMBs.
void D3DImageBackingFactoryTest::RunMultiplanarUploadAndReadback(
    bool use_update_subresource) {
  base::test::ScopedFeatureList feature_list;
  if (use_update_subresource) {
    feature_list.InitAndEnableFeature(
        features::kD3DBackingUploadWithUpdateSubresource);
  } else {
    feature_list.InitAndDisableFeature(
        features::kD3DBackingUploadWithUpdateSubresource);
  }

  // Recreate a new share image factory which will pick up the
  // kD3DBackingUploadWithUpdateSubresource feature.
  shared_image_factory_ = std::make_unique<D3DImageBackingFactory>(
      gl::QueryD3D11DeviceObjectFromANGLE(),
      shared_image_manager_.dxgi_shared_handle_manager(), GLFormatCaps());

  constexpr gfx::Size size(32, 32);
  constexpr size_t kDataSize = size.width() * size.height() * 3 / 2;
  constexpr SkAlphaType alpha_type = kPremul_SkAlphaType;
  constexpr gfx::ColorSpace color_space;
  constexpr gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_RASTER_READ |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_CPU_UPLOAD;
  constexpr auto format = viz::MultiPlaneFormat::kNV12;
  const gpu::Mailbox mailbox = gpu::Mailbox::Generate();

  auto owned_backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, kNullSurfaceHandle, size, color_space,
      kTopLeft_GrSurfaceOrigin, alpha_type, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(owned_backing, nullptr);
  SharedImageBacking* backing = owned_backing.get();

  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image_ref =
      shared_image_manager_.Register(std::move(owned_backing),
                                     memory_type_tracker_.get());
  ASSERT_TRUE(shared_image_ref);

  constexpr uint8_t kInitialY = 255;
  constexpr uint8_t kInitialU = 255;
  constexpr uint8_t kInitialV = 0;

  std::vector<uint8_t> buffer(kDataSize);
  FillNV12(buffer.data(), size, kInitialY, kInitialU, kInitialV);

  // Make pixmaps that point to each plane for use with upload/readback.
  std::vector<SkPixmap> pixmaps;
  {
    size_t plane_offset = 0;
    for (int plane = 0; plane < format.NumberOfPlanes(); ++plane) {
      gfx::Size plane_size = format.GetPlaneSize(plane, size);
      auto info =
          SkImageInfo::Make(gfx::SizeToSkISize(plane_size),
                            viz::ToClosestSkColorType(
                                /*gpu_compositing=*/true, format, plane),
                            alpha_type, color_space.ToSkColorSpace());
      DCHECK_LE(info.computeMinByteSize() + plane_offset, kDataSize);
      pixmaps.emplace_back(info, buffer.data() + plane_offset,
                           info.minRowBytes());
      plane_offset += info.computeMinByteSize();
    }
  }

  // Upload initial data into the image.
  backing->UploadFromMemory(pixmaps);
  backing->SetCleared();

  // If UpdateSubresource() is used, the staging texture shouldn't be created.
  EXPECT_EQ(
      !static_cast<D3DImageBacking*>(backing)->HasStagingTextureForTesting(),
      use_update_subresource);

  auto skia_representation = shared_image_representation_factory_->ProduceSkia(
      mailbox, context_state_);
  ASSERT_TRUE(skia_representation);

  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      scoped_read_access =
          skia_representation->BeginScopedReadAccess(nullptr, nullptr);
  ASSERT_TRUE(scoped_read_access);

  // Using glReadPixels() to check each plane has expected data after upload
  // doesn't work due to https://anglebug.com/7998. Instead draw from NV12
  // textures into a RGBA texture and readback from the RGBA texture.
  SkImageInfo rgba_image_info =
      SkImageInfo::Make(size.width(), size.height(), kRGBA_8888_SkColorType,
                        alpha_type, color_space.ToSkColorSpace());

  SkBitmap dst_bitmap;
  dst_bitmap.allocPixels(rgba_image_info);
  {
    auto source_image = scoped_read_access->CreateSkImage(context_state_.get());
    ASSERT_TRUE(source_image);

    SkSurfaceProps surface_props(0, kUnknown_SkPixelGeometry);
    sk_sp<SkSurface> dest_surface = SkSurfaces::RenderTarget(
        context_state_->gr_context(), skgpu::Budgeted::kNo, rgba_image_info, 0,
        kTopLeft_GrSurfaceOrigin, &surface_props);
    ASSERT_NE(dest_surface, nullptr);

    {
      auto* canvas = dest_surface->getCanvas();
      SkPaint paint;
      paint.setBlendMode(SkBlendMode::kSrc);

      canvas->drawImageRect(source_image, gfx::RectToSkRect(gfx::Rect(size)),
                            gfx::RectToSkRect(gfx::Rect(size)),
                            SkSamplingOptions(), &paint,
                            SkCanvas::kStrict_SrcRectConstraint);
    }

    GrBackendTexture backend_texture = SkSurfaces::GetBackendTexture(
        dest_surface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite);
    auto dst_image = SkImages::BorrowTextureFrom(
        context_state_->gr_context(), backend_texture, kTopLeft_GrSurfaceOrigin,
        kRGBA_8888_SkColorType, alpha_type, nullptr);
    ASSERT_TRUE(dst_image);

    EXPECT_TRUE(dst_image->readPixels(rgba_image_info, dst_bitmap.getPixels(),
                                      rgba_image_info.minRowBytes(), 0, 0));
  }

  // YUV(255, 255, 0) maps to RGB(255, 74, 255).
  SkColor expected_rgba_color = SkColorSetARGB(255, 74, 255, 255);

  SkBitmap expected_bitmap;
  expected_bitmap.allocPixels(rgba_image_info);
  expected_bitmap.eraseColor(expected_rgba_color);

  EXPECT_TRUE(cc::MatchesBitmap(dst_bitmap, expected_bitmap,
                                cc::ExactPixelComparator()));

  // Clear out `buffer` and then readback into it and verify YUV(255, 255, 0)
  // was read back.
  FillNV12(buffer.data(), size, 0, 0, 0);
  ASSERT_TRUE(backing->ReadbackToMemory(pixmaps));
  CheckNV12(buffer.data(), size.width(), size, kInitialY, kInitialU, kInitialV);
}

TEST_F(D3DImageBackingFactoryTest, MultiplanarUploadAndReadback) {
  RunMultiplanarUploadAndReadback(/*use_update_subresource=*/false);
}

TEST_F(D3DImageBackingFactoryTest,
       MultiplanarUploadAndReadbackWithUpdateSubresource) {
  RunMultiplanarUploadAndReadback(/*use_update_subresource=*/true);
}

TEST_F(D3DImageBackingFactoryTest, CanCreateScanoutBacking) {
  const gfx::Size arbitrary_size = gfx::Size(4, 4);

  // E.g. a hardware decoded video frame
  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_SCANOUT,
      viz::MultiPlaneFormat::kNV12, arbitrary_size, /*thread_safe=*/false,
      gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE, GrContextType ::kGL, {}));

  // E.g. getDisplayMedia content, like a shared window
  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      gpu::SHARED_IMAGE_USAGE_SCANOUT, viz::MultiPlaneFormat::kNV12,
      arbitrary_size, /*thread_safe=*/false,
      gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE, GrContextType ::kGL, {}));

  // E.g. delegated compositing draw quad resources
  EXPECT_EQ(
      gl::DirectCompositionTextureSupported(),
      shared_image_factory_->CanCreateSharedImage(
          gpu::SHARED_IMAGE_USAGE_SCANOUT, viz::SinglePlaneFormat::kBGRA_8888,
          arbitrary_size, /*thread_safe=*/false,
          gfx::GpuMemoryBufferType::EMPTY_BUFFER, GrContextType ::kGL, {}));
}

TEST_F(D3DImageBackingFactoryTest, CanProduceDCompTextureOverlay) {
  if (!gl::DirectCompositionTextureSupported()) {
    GTEST_SKIP() << "IDCompositionTexture not supported";
  }

  constexpr gfx::Size size(32, 32);
  constexpr SkAlphaType alpha_type = kPremul_SkAlphaType;
  constexpr gfx::ColorSpace color_space;
  constexpr gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_RASTER_READ |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;
  constexpr auto format = viz::SinglePlaneFormat::kBGRA_8888;
  const gpu::Mailbox mailbox = gpu::Mailbox::Generate();

  auto owned_backing = shared_image_factory_->CreateSharedImage(
      mailbox, format, kNullSurfaceHandle, size, color_space,
      kTopLeft_GrSurfaceOrigin, alpha_type, usage, "TestLabel",
      /*is_thread_safe=*/false);
  ASSERT_NE(owned_backing, nullptr);
  SharedImageBacking* backing = owned_backing.get();

  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image_ref =
      shared_image_manager_.Register(std::move(owned_backing),
                                     memory_type_tracker_.get());
  ASSERT_TRUE(shared_image_ref);

  // Mark it as initialized-- we won't be reading the actual pixels.
  backing->SetCleared();

  auto overlay_representation =
      shared_image_representation_factory_->ProduceOverlay(mailbox);

  auto scoped_read_access = overlay_representation->BeginScopedReadAccess();
  ASSERT_TRUE(scoped_read_access) << "Can begin overlay read access";
  auto overlay_image = scoped_read_access->GetDCLayerOverlayImage();
  ASSERT_TRUE(overlay_image);
  ASSERT_TRUE(overlay_image->dcomp_visual_content())
      << "Overlay image is visual content";
  Microsoft::WRL::ComPtr<IUnknown> visual_content =
      overlay_image->dcomp_visual_content();
  Microsoft::WRL::ComPtr<IDCompositionTexture> dcomp_texture;
  ASSERT_HRESULT_SUCCEEDED(visual_content.As(&dcomp_texture))
      << "Overlay image visual content is a DComp texture";
  ASSERT_TRUE(dcomp_texture);
}

}  // namespace gpu