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

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

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

#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/shared_context_state.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_feature_info.h"
#include "gpu/config/gpu_preferences.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/gpu/GrBackendSemaphore.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/test/sk_color_eq.h"
#include "ui/gl/child_window_win.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_share_group.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/gl/test/gl_test_helper.h"
#include "ui/platform_window/win/win_window.h"

namespace gpu {

namespace {

constexpr SharedImageUsageSet kDXGISwapChainUsage =
    SHARED_IMAGE_USAGE_DISPLAY_READ | SHARED_IMAGE_USAGE_DISPLAY_WRITE |
    SHARED_IMAGE_USAGE_SCANOUT | SHARED_IMAGE_USAGE_SCANOUT_DXGI_SWAP_CHAIN;

constexpr SharedImageUsageSet kDCompSurfaceUsage =
    SHARED_IMAGE_USAGE_DISPLAY_WRITE | SHARED_IMAGE_USAGE_SCANOUT |
    SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE;

constexpr GrContextType kGrContextTypeDontCare = GrContextType::kGL;

}  // namespace

class DCompImageBackingFactoryTest : 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);

    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));

    d3d11_device_ = gl::QueryD3D11DeviceObjectFromANGLE();

    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<DCompImageBackingFactory>(context_state_);
  }

 protected:
  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;

  scoped_refptr<gl::GLSurface> surface_;
  scoped_refptr<gl::GLContext> context_;
  scoped_refptr<SharedContextState> context_state_;

  SharedImageManager shared_image_manager_;
  std::unique_ptr<MemoryTypeTracker> memory_type_tracker_;
  std::unique_ptr<SharedImageRepresentationFactory>
      shared_image_representation_factory_;
  std::unique_ptr<DCompImageBackingFactory> shared_image_factory_;

  void RunDXGISwapChainAlphaTest(bool has_alpha) {
    Mailbox mailbox = Mailbox::Generate();
    std::unique_ptr<SharedImageBacking> backing =
        shared_image_factory_->CreateSharedImage(
            mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
            gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
            kTopLeft_GrSurfaceOrigin,
            has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType,
            kDXGISwapChainUsage, "TestLabel", false);
    ASSERT_NE(nullptr, backing);
    std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
        shared_image_manager_.Register(std::move(backing),
                                       memory_type_tracker_.get());

    shared_image_representation_factory_->ProduceSkia(mailbox, context_state_)
        ->SetCleared();

    Microsoft::WRL::ComPtr<IUnknown> content =
        shared_image_representation_factory_->ProduceOverlay(mailbox)
            ->BeginScopedReadAccess()
            ->GetDCLayerOverlayImage()
            ->dcomp_visual_content();
    ASSERT_NE(nullptr, content);

    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain;
    ASSERT_HRESULT_SUCCEEDED(content.As(&swap_chain));
    DXGI_SWAP_CHAIN_DESC1 desc;
    ASSERT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
    if (has_alpha) {
      EXPECT_EQ(DXGI_ALPHA_MODE_PREMULTIPLIED, desc.AlphaMode);
    } else {
      EXPECT_EQ(DXGI_ALPHA_MODE_IGNORE, desc.AlphaMode);
    }
  }
};

TEST_F(DCompImageBackingFactoryTest, UsageFlags) {
  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      kDCompSurfaceUsage, viz::SinglePlaneFormat::kRGBA_8888,
      gfx::Size(100, 100), false, gfx::GpuMemoryBufferType::EMPTY_BUFFER,
      kGrContextTypeDontCare, {}));

  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      kDXGISwapChainUsage, viz::SinglePlaneFormat::kRGBA_8888,
      gfx::Size(100, 100), false, gfx::GpuMemoryBufferType::EMPTY_BUFFER,
      kGrContextTypeDontCare, {}));

  // DComp surfaces don't support readback.
  EXPECT_FALSE(shared_image_factory_->CanCreateSharedImage(
      kDCompSurfaceUsage | SHARED_IMAGE_USAGE_DISPLAY_READ,
      viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(100, 100), false,
      gfx::GpuMemoryBufferType::EMPTY_BUFFER, kGrContextTypeDontCare, {}));

  // We require callers to explicitly state DXGI swap chains are readable.
  EXPECT_FALSE(shared_image_factory_->CanCreateSharedImage(
      SHARED_IMAGE_USAGE_DISPLAY_WRITE | SHARED_IMAGE_USAGE_SCANOUT,
      viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(100, 100), false,
      gfx::GpuMemoryBufferType::EMPTY_BUFFER, kGrContextTypeDontCare, {}));
}

TEST_F(DCompImageBackingFactoryTest, HDR10Support) {
  EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
      kDXGISwapChainUsage, viz::SinglePlaneFormat::kRGBA_1010102,
      gfx::Size(100, 100), false, gfx::GpuMemoryBufferType::EMPTY_BUFFER,
      kGrContextTypeDontCare, {}));

  EXPECT_FALSE(shared_image_factory_->CanCreateSharedImage(
      kDCompSurfaceUsage, viz::SinglePlaneFormat::kRGBA_1010102,
      gfx::Size(100, 100), false, gfx::GpuMemoryBufferType::EMPTY_BUFFER,
      kGrContextTypeDontCare, {}));
}

TEST_F(DCompImageBackingFactoryTest, ValidFormats) {
  SharedImageUsageSet valid_usages[2] = {kDCompSurfaceUsage,
                                         kDXGISwapChainUsage};

  viz::SharedImageFormat valid_formats[5] = {
      viz::SinglePlaneFormat::kRGBA_8888, viz::SinglePlaneFormat::kBGRA_8888,
      viz::SinglePlaneFormat::kRGBX_8888, viz::SinglePlaneFormat::kBGRX_8888,
      viz::SinglePlaneFormat::kRGBA_F16,
  };

  for (auto valid_usage : valid_usages) {
    for (auto valid_format : valid_formats) {
      // We don't support sharing memory
      EXPECT_TRUE(shared_image_factory_->CanCreateSharedImage(
          valid_usage, valid_format, gfx::Size(100, 100), false,
          gfx::GpuMemoryBufferType::EMPTY_BUFFER, kGrContextTypeDontCare, {}))
          << "usage = " << CreateLabelForSharedImageUsage(valid_usage)
          << ", format = " << valid_format.ToString();
    }
  }
}

// Test that |asyncRescaleAndReadPixels| works on a DXGI swap chain-backed
// SharedImage for CopyOutput support.
TEST_F(DCompImageBackingFactoryTest, CanReadDXGISwapChain) {
  Mailbox mailbox = Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> backing =
      shared_image_factory_->CreateSharedImage(
          mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
          gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
          kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, kDXGISwapChainUsage,
          "TestLabel", false);
  ASSERT_NE(nullptr, backing);
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  std::unique_ptr<SkiaImageRepresentation> skia_representation =
      shared_image_representation_factory_->ProduceSkia(mailbox,
                                                        context_state_);
  ASSERT_NE(nullptr, skia_representation);

  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;
  std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess> write_access =
      skia_representation->BeginScopedWriteAccess(
          &begin_semaphores, &end_semaphores,
          SkiaImageRepresentation::AllowUnclearedAccess::kYes);
  ASSERT_NE(nullptr, write_access);

  write_access->surface()->getCanvas()->clear(SK_ColorRED);

  EXPECT_EQ(0u, end_semaphores.size());
  GrFlushInfo flush_info;
  GrDirectContext* direct_context = context_state_->gr_context();
  EXPECT_EQ(
      GrSemaphoresSubmitted::kYes,
      direct_context->flush(write_access->surface(), flush_info, nullptr));
  skia_representation->SetCleared();

  std::unique_ptr<const SkImage::AsyncReadResult> readback_result;
  write_access->surface()->asyncRescaleAndReadPixels(
      SkImageInfo::MakeN32Premul(100, 100), SkIRect::MakeXYWH(25, 25, 1, 1),
      SkSurface::RescaleGamma::kLinear, SkSurface::RescaleMode::kNearest,
      [](void* context,
         std::unique_ptr<const SkImage::AsyncReadResult> result) {
        *reinterpret_cast<std::unique_ptr<const SkImage::AsyncReadResult>*>(
            context) = std::move(result);
      },
      &readback_result);
  direct_context->submit(GrSyncCpu::kYes);

  ASSERT_NE(nullptr, readback_result);
  EXPECT_EQ(1, readback_result->count());
  EXPECT_EQ(SK_ColorRED,
            *reinterpret_cast<const SkColor*>(readback_result->data(0)));
}

// The DComp surface backing binds to the GL default framebuffer. This ensures
// that we correctly restore the previous current surface after we're done
// drawing.
TEST_F(DCompImageBackingFactoryTest, DCompSurfaceRestoresGLSurfaceAfterDraw) {
  Mailbox mailbox = Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> backing =
      shared_image_factory_->CreateSharedImage(
          mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
          gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
          kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, kDCompSurfaceUsage,
          "TestLabel", false);
  ASSERT_NE(nullptr, backing);
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  std::unique_ptr<SkiaImageRepresentation> skia_representation =
      shared_image_representation_factory_->ProduceSkia(mailbox,
                                                        context_state_);

  gl::GLSurface* original_surface = gl::GLSurface::GetCurrent();
  EXPECT_NE(nullptr, original_surface);
  {
    std::vector<GrBackendSemaphore> begin_semaphores;
    std::vector<GrBackendSemaphore> end_semaphores;
    std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess> write_access =
        skia_representation->BeginScopedWriteAccess(
            &begin_semaphores, &end_semaphores,
            SkiaImageRepresentation::AllowUnclearedAccess::kYes);
    EXPECT_NE(nullptr, write_access);

    // DCompImageBacking has its own GLSurface that wraps the DComp surface
    // update texture.
    EXPECT_NE(gl::GLSurface::GetCurrent(), original_surface);
  }
  EXPECT_EQ(gl::GLSurface::GetCurrent(), original_surface);
}

// The DComp surface returns a "surface serial" to indicate that its contents
// have changed (since we need to Commit the DComp tree to reflect the surface
// changes). This test ensures that this value changes after draws.
TEST_F(DCompImageBackingFactoryTest,
       DCompSurfaceMultipleDrawsIncrementSurfaceSerial) {
  Mailbox mailbox = Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> backing =
      shared_image_factory_->CreateSharedImage(
          mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
          gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
          kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, kDCompSurfaceUsage,
          "TestLabel", false);
  ASSERT_NE(nullptr, backing);
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  std::unique_ptr<SkiaImageRepresentation> skia_representation =
      shared_image_representation_factory_->ProduceSkia(mailbox,
                                                        context_state_);

  // Force cleared, so we can get an overlay read access
  skia_representation->SetCleared();

  uint64_t previous_serial =
      shared_image_representation_factory_->ProduceOverlay(mailbox)
          ->BeginScopedReadAccess()
          ->GetDCLayerOverlayImage()
          ->dcomp_surface_serial();

  for (int i = 0; i < 10; i++) {
    {
      std::vector<GrBackendSemaphore> begin_semaphores;
      std::vector<GrBackendSemaphore> end_semaphores;
      std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess> write_access =
          skia_representation->BeginScopedWriteAccess(
              &begin_semaphores, &end_semaphores,
              SkiaImageRepresentation::AllowUnclearedAccess::kYes);
      EXPECT_NE(nullptr, write_access);
    }

    uint64_t current_serial =
        shared_image_representation_factory_->ProduceOverlay(mailbox)
            ->BeginScopedReadAccess()
            ->GetDCLayerOverlayImage()
            ->dcomp_surface_serial();

    // We only care that the previous serial is not the same as the previous
    EXPECT_NE(current_serial, previous_serial);

    previous_serial = current_serial;
  }
}

// Ensure that creating a DXGI swap chain SI, we get a swap chain with the right
// alpha mode.
TEST_F(DCompImageBackingFactoryTest, DXGISwapChainAlphaOpaque) {
  RunDXGISwapChainAlphaTest(/*has_alpha=*/false);
}

TEST_F(DCompImageBackingFactoryTest, DXGISwapChainAlphaPremultiplied) {
  RunDXGISwapChainAlphaTest(/*has_alpha=*/true);
}

class DCompImageBackingFactoryBufferCountTest
    : public DCompImageBackingFactoryTest,
      public testing::WithParamInterface<bool> {
 public:
  static const char* GetParamName(
      const testing::TestParamInfo<ParamType>& info) {
    return info.param ? "DCompTripleBufferRootSwapChain" : "default";
  }

 protected:
  void SetUp() override {
    if (GetParam()) {
      enabled_features_.InitWithFeatures(
          {features::kDCompTripleBufferRootSwapChain}, {});
    } else {
      enabled_features_.InitWithFeatures(
          {}, {features::kDCompTripleBufferRootSwapChain});
    }

    DCompImageBackingFactoryTest::SetUp();
  }

  base::test::ScopedFeatureList enabled_features_;
};

TEST_P(DCompImageBackingFactoryBufferCountTest, RootSwapChainBufferCount) {
  Mailbox mailbox = Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> backing =
      shared_image_factory_->CreateSharedImage(
          mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
          gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
          kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, kDXGISwapChainUsage,
          "TestLabel", false);
  ASSERT_NE(nullptr, backing);
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  shared_image_representation_factory_->ProduceSkia(mailbox, context_state_)
      ->SetCleared();

  Microsoft::WRL::ComPtr<IUnknown> content =
      shared_image_representation_factory_->ProduceOverlay(mailbox)
          ->BeginScopedReadAccess()
          ->GetDCLayerOverlayImage()
          ->dcomp_visual_content();

  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain;
  ASSERT_HRESULT_SUCCEEDED(content.As(&swap_chain));
  DXGI_SWAP_CHAIN_DESC1 desc;
  ASSERT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
  if (GetParam()) {
    EXPECT_EQ(3u, desc.BufferCount);
  } else {
    EXPECT_EQ(2u, desc.BufferCount);
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    DCompImageBackingFactoryBufferCountTest,
    testing::Bool(),
    &DCompImageBackingFactoryBufferCountTest::GetParamName);

class DCompImageBackingFactoryVisualTreeTest
    : public DCompImageBackingFactoryTest {
 protected:
  DCompImageBackingFactoryVisualTreeTest()
      : window_size_(100, 100),
        window_(&platform_delegate_, gfx::Rect(window_size_)),
        dcomp_device_(gl::GetDirectCompositionDevice()) {}

  void SetUp() override {
    DCompImageBackingFactoryTest::SetUp();

    static_cast<ui::PlatformWindow*>(&window_)->Show();
    child_window_.Initialize();
    child_window_.Resize(window_size_);
    ::SetParent(child_window_.window(), window_.hwnd());
  }

  // This does not need to be called unless the test expects alpha blending.
  void set_background_fill_override(const SkColor4f& background_fill_override) {
    background_fill_override_ = background_fill_override;
  }

  void InitializeVisualTreeWithContent(IUnknown* content) {
    Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;
    ASSERT_HRESULT_SUCCEEDED(dcomp_device_.As(&desktop_device));

    ASSERT_HRESULT_SUCCEEDED(desktop_device->CreateTargetForHwnd(
        child_window_.window(), TRUE, &dcomp_target_));

    ASSERT_HRESULT_SUCCEEDED(dcomp_device_->CreateVisual(&dcomp_root_visual_));
    ASSERT_NE(dcomp_root_visual_, nullptr);
    ASSERT_HRESULT_SUCCEEDED(dcomp_target_->SetRoot(dcomp_root_visual_.Get()));

    // Fill the background so we have a consistent backdrop instead of relying
    // on the color of the redirection surface when testing alpha blending. We
    // default to magenta to make it obvious when something shouldn't be
    // visible.
    const SkColor4f background_fill_color =
        background_fill_override_.value_or(SkColors::kMagenta);
    {
      Microsoft::WRL::ComPtr<IDCompositionSurface> background_fill;
      ASSERT_HRESULT_SUCCEEDED(dcomp_device_->CreateSurface(
          window_size_.width(), window_size_.height(),
          DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ALPHA_MODE_IGNORE,
          &background_fill));

      RECT update_rect = gfx::Rect(window_size_).ToRECT();
      Microsoft::WRL::ComPtr<ID3D11Texture2D> update_texture;
      POINT update_offset;
      ASSERT_HRESULT_SUCCEEDED(background_fill->BeginDraw(
          &update_rect, IID_PPV_ARGS(&update_texture), &update_offset));
      {
        Microsoft::WRL::ComPtr<ID3D11RenderTargetView> render_target;
        ASSERT_HRESULT_SUCCEEDED(d3d11_device_->CreateRenderTargetView(
            update_texture.Get(), nullptr, &render_target));
        ASSERT_NE(render_target, nullptr);
        Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
        d3d11_device_->GetImmediateContext(&d3d11_device_context);
        ASSERT_NE(d3d11_device_context, nullptr);
        d3d11_device_context->ClearRenderTargetView(
            render_target.Get(), background_fill_color.vec());
      }
      ASSERT_HRESULT_SUCCEEDED(background_fill->EndDraw());

      // The content of a visual is always drawn behind its children, so we'll
      // use it for the background fill.
      ASSERT_HRESULT_SUCCEEDED(
          dcomp_root_visual_->SetContent(background_fill.Get()));
    }

    Microsoft::WRL::ComPtr<IDCompositionVisual2> content_visual;
    ASSERT_HRESULT_SUCCEEDED(dcomp_device_->CreateVisual(&content_visual));
    ASSERT_HRESULT_SUCCEEDED(content_visual->SetContent(content));
    ASSERT_HRESULT_SUCCEEDED(
        dcomp_root_visual_->AddVisual(content_visual.Get(), FALSE, nullptr));
  }

  void CommitAndWait() {
    ASSERT_HRESULT_SUCCEEDED(dcomp_device_->Commit());
    ASSERT_HRESULT_SUCCEEDED(dcomp_device_->WaitForCommitCompletion());

    // Wait for DXGI swap chains to flip, just in case.
    Sleep(1000);
  }

  HWND window() const { return child_window_.window(); }

  void FillAreaAndSubmit(SkiaImageRepresentation* skia_representation,
                         const gfx::Rect& update_rect,
                         SkColor color) {
    std::vector<GrBackendSemaphore> begin_semaphores;
    std::vector<GrBackendSemaphore> end_semaphores;
    std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess> write_access =
        skia_representation->BeginScopedWriteAccess(
            1, SkSurfaceProps(), update_rect, &begin_semaphores,
            &end_semaphores,
            SkiaImageRepresentation::AllowUnclearedAccess::kYes, true);
    ASSERT_NE(nullptr, write_access);

    auto* canvas = write_access->surface()->getCanvas();

    // Clear |update_rect| instead of drawing, since drawing does alpha blending
    // and depends on initialized pixels in |update_rect|, which we might not
    // have.
    canvas->save();
    canvas->clipRect(gfx::RectToSkRect(update_rect));
    canvas->clear(color);
    canvas->restore();

    EXPECT_EQ(0u, end_semaphores.size());
    GrFlushInfo flush_info;
    GrDirectContext* direct_context = context_state_->gr_context();
    EXPECT_EQ(
        GrSemaphoresSubmitted::kYes,
        direct_context->flush(write_access->surface(), flush_info, nullptr));

    write_access->ApplyBackendSurfaceEndState();
    direct_context->submit(GrSyncCpu::kYes);
  }

  // Create a backing, fill |draw_area| with |draw_color|, and schedule the
  // overlay
  void ScheduleImageWithOneDraw(gpu::SharedImageUsageSet usage,
                                viz::SharedImageFormat format,
                                const gfx::ColorSpace& color_space,
                                bool has_alpha,
                                const gfx::Rect& draw_area,
                                SkColor draw_color) {
    Mailbox mailbox = Mailbox::Generate();
    std::unique_ptr<SharedImageBacking> backing =
        shared_image_factory_->CreateSharedImage(
            mailbox, format, nullptr, window_size_, color_space,
            kTopLeft_GrSurfaceOrigin,
            has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType,
            SharedImageUsageSet(usage), "TestLabel", false);
    ASSERT_NE(nullptr, backing);
    std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
        shared_image_manager_.Register(std::move(backing),
                                       memory_type_tracker_.get());

    std::unique_ptr<SkiaImageRepresentation> skia_representation =
        shared_image_representation_factory_->ProduceSkia(mailbox,
                                                          context_state_);
    FillAreaAndSubmit(skia_representation.get(), draw_area, draw_color);
    skia_representation->SetCleared();

    {
      std::unique_ptr<OverlayImageRepresentation> overlay_representation =
          shared_image_representation_factory_->ProduceOverlay(mailbox);
      ASSERT_NE(nullptr, overlay_representation);
      std::unique_ptr<OverlayImageRepresentation::ScopedReadAccess>
          read_access = overlay_representation->BeginScopedReadAccess();
      ASSERT_NE(nullptr, read_access);
      Microsoft::WRL::ComPtr<IUnknown> layer_content =
          read_access->GetDCLayerOverlayImage()->dcomp_visual_content();
      InitializeVisualTreeWithContent(layer_content.Get());
      CommitAndWait();
    }
  }

  // Runs a sanity check test that verifies backings with different color spaces
  // are valid and contain the values we expect.
  void RunFormatAndColorSpaceTest(gpu::SharedImageUsageSet usage,
                                  viz::SharedImageFormat format,
                                  gfx::ColorSpace color_space,
                                  bool has_alpha,
                                  SkColor clear_color,
                                  SkColor expected_window_color) {
    // Bake the SDR white level into the color space so SharedImage backings can
    // know about it.
    if (color_space.IsAffectedBySDRWhiteLevel()) {
      auto sk_color_space =
          color_space.ToSkColorSpace(gfx::ColorSpace::kDefaultSDRWhiteLevel);
      color_space = gfx::ColorSpace(*sk_color_space, /*is_hdr=*/true);
    }

    DVLOG(2) << "usage = " << CreateLabelForSharedImageUsage(usage)
             << ", format = " << format.ToString()
             << ", color_space = " << color_space.ToString()
             << ", has_alpha = " << has_alpha;

    if (has_alpha) {
      set_background_fill_override(SkColors::kBlack);
    }
    ScheduleImageWithOneDraw(usage, format, color_space, has_alpha,
                             gfx::Rect(window_size()), clear_color);

    // CheckColors accounts for color space conversion shift
    gfx::test::CheckColors(
        expected_window_color,
        gl::GLTestHelper::ReadBackWindowPixel(
            window(),
            gfx::Point(window_size_.width() / 4, window_size_.height() / 4)));
  }

  // Check that a backing whose first draw does not cover the entire surface is
  // initialized internally and the draw does not fail. It is invalid to read
  // from an uninitialized portion of a SharedImage. Incomplete draws still can
  // happen in valid scenarios, however. E.g. if a client over-allocates the
  // backing, but only reads from the part it draws to.
  void RunIncompleteFirstDrawTest(gpu::SharedImageUsageSet usage) {
    // First draw does not cover full surface
    const SkColor expected_color = SK_ColorGREEN;
    ScheduleImageWithOneDraw(usage, viz::SinglePlaneFormat::kRGBA_8888,
                             gfx::ColorSpace::CreateSRGB(), true,
                             gfx::Rect(10, 10), expected_color);

    SkBitmap window_readback =
        gl::GLTestHelper::ReadBackWindow(window(), window_size());

    // The part that we drew should be our expected color
    EXPECT_SKCOLOR_EQ(expected_color, gl::GLTestHelper::GetColorAtPoint(
                                          window_readback, gfx::Point(0, 0)));

#if DCHECK_IS_ON()
    // In DCHECK, DCompImageBackingFactory images are cleared to blue.
    const SkColor expected_window_color = SK_ColorBLUE;
#else
    // In non-DCHECK, the backings are cleared to transparent.
    set_background_fill_override(SkColors::kRed);
    const SkColor expected_window_color = SK_ColorRED;
#endif
    EXPECT_SKCOLOR_EQ(
        expected_window_color,
        gl::GLTestHelper::GetColorAtPoint(window_readback, gfx::Point(0, 10)));
    EXPECT_SKCOLOR_EQ(
        expected_window_color,
        gl::GLTestHelper::GetColorAtPoint(window_readback, gfx::Point(10, 0)));
    EXPECT_SKCOLOR_EQ(
        expected_window_color,
        gl::GLTestHelper::GetColorAtPoint(window_readback, gfx::Point(10, 10)));
    EXPECT_SKCOLOR_EQ(
        expected_window_color,
        gl::GLTestHelper::GetColorAtPoint(window_readback, gfx::Point(99, 99)));
  }

  const gfx::Size& window_size() const { return window_size_; }

 private:
  class TestPlatformDelegate : public ui::PlatformWindowDelegate {
   public:
    // ui::PlatformWindowDelegate implementation.
    void OnBoundsChanged(const BoundsChange& change) override {}
    void OnDamageRect(const gfx::Rect& damaged_region) override {}
    void DispatchEvent(ui::Event* event) override {}
    void OnCloseRequest() override {}
    void OnClosed() override {}
    void OnWindowStateChanged(ui::PlatformWindowState old_state,
                              ui::PlatformWindowState new_state) override {}
    void OnLostCapture() override {}
    void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {}
    void OnWillDestroyAcceleratedWidget() override {}
    void OnAcceleratedWidgetDestroyed() override {}
    void OnActivationChanged(bool active) override {}
    void OnMouseEnter() override {}
  };

  gfx::Size window_size_;
  std::optional<SkColor4f> background_fill_override_;

  TestPlatformDelegate platform_delegate_;
  ui::WinWindow window_;
  gl::ChildWindowWin child_window_;

  Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device_;
  Microsoft::WRL::ComPtr<IDCompositionTarget> dcomp_target_;
  Microsoft::WRL::ComPtr<IDCompositionVisual2> dcomp_root_visual_;
};

TEST_F(DCompImageBackingFactoryVisualTreeTest, DCompSurfaceCanDisplay) {
  viz::SharedImageFormat formats[4] = {
      viz::SinglePlaneFormat::kRGBA_8888,
      viz::SinglePlaneFormat::kBGRA_8888,
      viz::SinglePlaneFormat::kRGBX_8888,
      viz::SinglePlaneFormat::kBGRX_8888,
  };

  SkColor test_color = SkColorSetRGB(0x20, 0x40, 0x80);
  SkColor expected_color = test_color;
  for (auto format : formats) {
    RunFormatAndColorSpaceTest(kDCompSurfaceUsage, format,
                               gfx::ColorSpace::CreateSRGB(), false, test_color,
                               expected_color);
  }
}

TEST_F(DCompImageBackingFactoryVisualTreeTest, DCompSurfaceCanDisplayLinear) {
  SkColor test_color = SkColorSetRGB(0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x35, 0x65, 0xc3);
  RunFormatAndColorSpaceTest(kDCompSurfaceUsage,
                             viz::SinglePlaneFormat::kRGBA_F16,
                             gfx::ColorSpace::CreateSCRGBLinear80Nits(), false,
                             test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DCompSurfaceCanDisplayWithAlpha) {
  viz::SharedImageFormat formats[2] = {
      viz::SinglePlaneFormat::kRGBA_8888,
      viz::SinglePlaneFormat::kBGRA_8888,
  };

  SkColor test_color = SkColorSetARGB(0x80, 0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x10, 0x20, 0x40);
  for (auto format : formats) {
    RunFormatAndColorSpaceTest(kDCompSurfaceUsage, format,
                               gfx::ColorSpace::CreateSRGB(), true, test_color,
                               expected_color);
  }
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DCompSurfaceCanDisplayLinearWithAlpha) {
  SkColor test_color = SkColorSetARGB(0x80, 0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x10, 0x20, 0x40);
  RunFormatAndColorSpaceTest(kDCompSurfaceUsage,
                             viz::SinglePlaneFormat::kRGBA_F16,
                             gfx::ColorSpace::CreateSCRGBLinear80Nits(), true,
                             test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest, DXGISwapChainCanDisplay) {
  viz::SharedImageFormat formats[4] = {
      viz::SinglePlaneFormat::kRGBA_8888,
      viz::SinglePlaneFormat::kBGRA_8888,
      viz::SinglePlaneFormat::kRGBX_8888,
      viz::SinglePlaneFormat::kBGRX_8888,
  };

  SkColor test_color = SkColorSetRGB(0x20, 0x40, 0x80);
  SkColor expected_color = test_color;
  for (auto format : formats) {
    RunFormatAndColorSpaceTest(kDXGISwapChainUsage, format,
                               gfx::ColorSpace::CreateSRGB(), false, test_color,
                               expected_color);
  }
}

TEST_F(DCompImageBackingFactoryVisualTreeTest, DXGISwapChainCanDisplayLinear) {
  SkColor test_color = SkColorSetRGB(0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x35, 0x65, 0xc3);
  RunFormatAndColorSpaceTest(kDXGISwapChainUsage,
                             viz::SinglePlaneFormat::kRGBA_F16,
                             gfx::ColorSpace::CreateSCRGBLinear80Nits(), false,
                             test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest, DXGISwapChainCanDisplayHDR10) {
  SkColor test_color = SkColorSetRGB(0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x35, 0x65, 0xc3);
  RunFormatAndColorSpaceTest(
      kDXGISwapChainUsage, viz::SinglePlaneFormat::kRGBA_1010102,
      gfx::ColorSpace::CreateHDR10(), false, test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DXGISwapChainCanDisplayWithAlpha) {
  viz::SharedImageFormat formats[2] = {
      viz::SinglePlaneFormat::kRGBA_8888,
      viz::SinglePlaneFormat::kBGRA_8888,
  };

  SkColor test_color = SkColorSetARGB(0x80, 0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x10, 0x20, 0x40);
  for (auto format : formats) {
    RunFormatAndColorSpaceTest(kDXGISwapChainUsage, format,
                               gfx::ColorSpace::CreateSRGB(), true, test_color,
                               expected_color);
  }
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DXGISwapChainCanDisplayLinearWithAlpha) {
  SkColor test_color = SkColorSetARGB(0x80, 0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x10, 0x20, 0x40);
  RunFormatAndColorSpaceTest(kDXGISwapChainUsage,
                             viz::SinglePlaneFormat::kRGBA_F16,
                             gfx::ColorSpace::CreateSCRGBLinear80Nits(), true,
                             test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DXGISwapChainCanDisplayHDR10WithAlpha) {
  SkColor test_color = SkColorSetARGB(0x80, 0x20, 0x40, 0x80);
  SkColor expected_color = SkColorSetRGB(0x10, 0x20, 0x40);
  RunFormatAndColorSpaceTest(
      kDXGISwapChainUsage, viz::SinglePlaneFormat::kRGBA_1010102,
      gfx::ColorSpace::CreateHDR10(), true, test_color, expected_color);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DXGISwapChainBackingCanDrawMultipleTimes) {
  Mailbox mailbox = Mailbox::Generate();
  std::unique_ptr<SharedImageBacking> backing =
      shared_image_factory_->CreateSharedImage(
          mailbox, viz::SinglePlaneFormat::kRGBA_8888, nullptr,
          gfx::Size(100, 100), gfx::ColorSpace::CreateSRGB(),
          kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, kDXGISwapChainUsage,
          "TestLabel", false);
  ASSERT_NE(nullptr, backing);
  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
      shared_image_manager_.Register(std::move(backing),
                                     memory_type_tracker_.get());

  gfx::Point middle_of_left_half(window_size().width() / 4,
                                 window_size().height() / 2);
  gfx::Point middle_of_right_half(window_size().width() * 3 / 4,
                                  window_size().height() / 2);

  std::unique_ptr<SkiaImageRepresentation> skia_representation =
      shared_image_representation_factory_->ProduceSkia(mailbox,
                                                        context_state_);
  // First draw will clear the entire window
  {
    FillAreaAndSubmit(skia_representation.get(), gfx::Rect(window_size()),
                      SK_ColorRED);
    skia_representation->SetCleared();

    {
      std::unique_ptr<OverlayImageRepresentation> overlay_representation =
          shared_image_representation_factory_->ProduceOverlay(mailbox);
      ASSERT_NE(nullptr, overlay_representation);
      std::unique_ptr<OverlayImageRepresentation::ScopedReadAccess>
          read_access = overlay_representation->BeginScopedReadAccess();
      ASSERT_NE(nullptr, read_access);
      Microsoft::WRL::ComPtr<IUnknown> layer_content =
          read_access->GetDCLayerOverlayImage()->dcomp_visual_content();
      InitializeVisualTreeWithContent(layer_content.Get());
      CommitAndWait();
    }

    SkBitmap window_readback =
        gl::GLTestHelper::ReadBackWindow(window(), window_size());
    EXPECT_SKCOLOR_EQ(SK_ColorRED, gl::GLTestHelper::GetColorAtPoint(
                                       window_readback, middle_of_left_half));
    EXPECT_SKCOLOR_EQ(SK_ColorRED, gl::GLTestHelper::GetColorAtPoint(
                                       window_readback, middle_of_right_half));
  }

  // Next two draws will be partial, drawing different colors to each side
  {
    gfx::Vector2d update_rect_half_size(10, 10);

    FillAreaAndSubmit(
        skia_representation.get(),
        gfx::BoundingRect(middle_of_left_half - update_rect_half_size,
                          middle_of_left_half + update_rect_half_size),
        SK_ColorGREEN);
    FillAreaAndSubmit(
        skia_representation.get(),
        gfx::BoundingRect(middle_of_right_half - update_rect_half_size,
                          middle_of_right_half + update_rect_half_size),
        SK_ColorBLUE);

    // Force a Present on the multiple draws.
    std::ignore = shared_image_representation_factory_->ProduceOverlay(mailbox)
                      ->BeginScopedReadAccess();

    // No Commit is needed, but we should wait for swap chains to flip.
    CommitAndWait();

    SkBitmap window_readback =
        gl::GLTestHelper::ReadBackWindow(window(), window_size());
    EXPECT_SKCOLOR_EQ(SK_ColorGREEN, gl::GLTestHelper::GetColorAtPoint(
                                         window_readback, middle_of_left_half));
    EXPECT_SKCOLOR_EQ(SK_ColorBLUE, gl::GLTestHelper::GetColorAtPoint(
                                        window_readback, middle_of_right_half));
  }
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DCompSurfaceIncompleteFirstDrawInitializesSurface) {
  RunIncompleteFirstDrawTest(kDCompSurfaceUsage);
}

TEST_F(DCompImageBackingFactoryVisualTreeTest,
       DXGISwapChainIncompleteFirstDrawInitializesSurface) {
  RunIncompleteFirstDrawTest(kDXGISwapChainUsage);
}

}  // namespace gpu