910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <array>

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

#include <cmath>
#include <memory>
#include <tuple>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/path_service.h"
#include "base/test/test_switches.h"
#include "build/build_config.h"
#include "cc/base/completion_event.h"
#include "cc/paint/display_item_list.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/paint_image_builder.h"
#include "cc/raster/playback_image_provider.h"
#include "cc/test/fake_paint_image_generator.h"
#include "cc/test/pixel_comparator.h"
#include "cc/test/pixel_test_utils.h"
#include "cc/tiles/gpu_image_decode_cache.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/service/gl/gpu_service_impl.h"
#include "components/viz/test/buildflags.h"
#include "components/viz/test/paths.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "components/viz/test/test_in_process_context_provider.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/raster_implementation.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/gr_shader_cache.h"
#include "gpu/command_buffer/service/graphite_utils.h"
#include "gpu/config/gpu_finch_features.h"
#include "ipc/common/gpu_client_ids.h"
#include "skia/ext/font_utils.h"
#include "skia/ext/legacy_display_globals.h"
#include "skia/ext/skcolorspace_trfn.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkGraphics.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/GrTypes.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/GraphiteTypes.h"
#include "third_party/skia/include/gpu/graphite/Surface.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gl/gl_implementation.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/android_info.h"
#endif

namespace cc {
namespace {

SkV4 SkColorToSkV4(SkColor4f color) {
  return SkV4{color.fR, color.fG, color.fB, color.fA};
}

scoped_refptr<DisplayItemList> MakeNoopDisplayItemList() {
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<SaveOp>();
  display_item_list->push<RestoreOp>();
  display_item_list->EndPaintOfUnpaired(gfx::Rect(10000, 10000));
  display_item_list->Finalize();
  return display_item_list;
}

// Creates a bitmap of |size| filled with pixels of |color|.
SkBitmap MakeSolidColorBitmap(gfx::Size size,
                              SkColor4f color,
                              SkAlphaType alpha_type = kPremul_SkAlphaType) {
  SkBitmap bitmap;
  bitmap.allocPixels(SkImageInfo::Make(size.width(), size.height(),
                                       kN32_SkColorType, alpha_type));
  bitmap.eraseColor(color);
  return bitmap;
}

// Creates a SkImage filled with magenta and a 30x40 green rectangle.
sk_sp<SkImage> MakeSkImage(const gfx::Size& size,
                           sk_sp<SkColorSpace> color_space = nullptr) {
  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(size.width(), size.height(), color_space),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(SkColors::kMagenta);
  SkPaint green;
  green.setColor(SkColors::kGreen);
  canvas.drawRect(SkRect::MakeXYWH(10, 20, 30, 40), green);

  return SkImages::RasterFromBitmap(bitmap);
}

constexpr size_t kCacheLimitBytes = 1024 * 1024;
constexpr PaintFlags::FilterQuality kDefaultFilterQuality =
    PaintFlags::FilterQuality::kNone;

class OopPixelTest : public testing::Test,
                     public gpu::raster::GrShaderCache::Client {
 public:
  OopPixelTest() : gr_shader_cache_(kCacheLimitBytes, this) {}

  void SetUp() override { InitializeOOPContext(); }

  // gpu::raster::GrShaderCache::Client implementation.
  void StoreShader(const std::string& key, const std::string& shader) override {
  }

  void InitializeOOPContext() {
    if (oop_image_cache_)
      oop_image_cache_.reset();

    raster_context_provider_ =
        base::MakeRefCounted<viz::TestInProcessContextProvider>(
            viz::TestContextType::kGpuRaster, /*support_locking=*/false,
            &gr_shader_cache_, &use_shader_cache_shm_count_);
    gpu::ContextResult result =
        raster_context_provider_->BindToCurrentSequence();
    DCHECK_EQ(result, gpu::ContextResult::kSuccess);
    const int raster_max_texture_size =
        raster_context_provider_->ContextCapabilities().max_texture_size;
    oop_image_cache_ = std::make_unique<GpuImageDecodeCache>(
        raster_context_provider_.get(), kRGBA_8888_SkColorType, kWorkingSetSize,
        raster_max_texture_size, nullptr);
  }

  class RasterOptions {
   public:
    RasterOptions() = default;
    explicit RasterOptions(const gfx::Size& playback_size) {
      resource_size = playback_size;
      content_size = resource_size;
      full_raster_rect = gfx::Rect(playback_size);
      playback_rect = gfx::Rect(playback_size);
    }

    SkColor4f background_color = SkColors::kBlack;
    int msaa_sample_count = 0;
    bool use_lcd_text = false;
    PlaybackImageProvider::RasterMode image_provider_raster_mode =
        PlaybackImageProvider::RasterMode::kSoftware;
    gfx::Size resource_size;
    gfx::Size content_size;
    gfx::Rect full_raster_rect;
    gfx::Rect playback_rect;
    gfx::Vector2dF post_translate = {0.f, 0.f};
    float post_scale = 1.f;
    TargetColorParams target_color_params;
    bool requires_clear = false;
    bool preclear = false;
    SkColor4f preclear_color;
    // RAW_PTR_EXCLUSION: ImageDecodeCache is marked as not supported by
    // raw_ptr. See raw_ptr.h for more information.
    RAW_PTR_EXCLUSION ImageDecodeCache* image_cache = nullptr;
    std::vector<scoped_refptr<DisplayItemList>> additional_lists;
    raw_ptr<PaintShader> shader_with_animated_images = nullptr;
  };

  SkBitmap Raster(scoped_refptr<DisplayItemList> display_item_list,
                  const gfx::Size& playback_size) {
    RasterOptions options(playback_size);
    return Raster(display_item_list, options);
  }

  SkBitmap Raster(scoped_refptr<DisplayItemList> display_item_list,
                  const RasterOptions& options) {
    std::optional<PlaybackImageProvider::Settings> settings;
    settings.emplace(PlaybackImageProvider::Settings());
    settings->raster_mode = options.image_provider_raster_mode;
    PlaybackImageProvider image_provider(oop_image_cache_.get(),
                                         options.target_color_params,
                                         std::move(settings));

    int width = options.resource_size.width();
    int height = options.resource_size.height();

    // Create and allocate a shared image on the raster interface.
    // This SharedImage will be used as the destination of the raster of
    // `display_item_list` before having its contents read back (also via the
    // raster interface).
    auto* ri = raster_context_provider_->RasterInterface();
    auto* sii = raster_context_provider_->SharedImageInterface();
    gpu::SharedImageUsageSet flags = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
                                     gpu::SHARED_IMAGE_USAGE_RASTER_WRITE;
    auto client_shared_image = sii->CreateSharedImage(
        {viz::SinglePlaneFormat::kRGBA_8888, gfx::Size(width, height),
         options.target_color_params.color_space, flags, "TestLabel"},
        gpu::kNullSurfaceHandle);
    EXPECT_TRUE(client_shared_image->mailbox().Verify());
    std::unique_ptr<gpu::RasterScopedAccess> ri_access =
        client_shared_image->BeginRasterAccess(
            ri, client_shared_image->creation_sync_token(), /*readonly=*/false);

    // Assume legacy MSAA if sample count is positive.
    gpu::raster::MsaaMode msaa_mode = options.msaa_sample_count > 0
                                          ? gpu::raster::kMSAA
                                          : gpu::raster::kNoMSAA;

    if (options.preclear) {
      ri->BeginRasterCHROMIUM(
          options.preclear_color,
          /*needs_clear=*/options.preclear, options.msaa_sample_count,
          msaa_mode, options.use_lcd_text,
          /*visible=*/true, options.target_color_params.color_space,
          options.target_color_params.GetHdrHeadroom(),
          client_shared_image->mailbox().name);
      ri->EndRasterCHROMIUM();
    }

    // "Out of process" raster! \o/
    // If |options.preclear| is true, the mailbox has already been cleared by
    // the BeginRasterCHROMIUM call above, and we want to test that it is indeed
    // cleared, so set |needs_clear| to false here.
    ri->BeginRasterCHROMIUM(
        options.background_color,
        /*needs_clear=*/!options.preclear, options.msaa_sample_count, msaa_mode,
        options.use_lcd_text,
        /*visible=*/true, options.target_color_params.color_space,
        options.target_color_params.GetHdrHeadroom(),
        client_shared_image->mailbox().name);
    size_t max_op_size_limit =
        gpu::raster::RasterInterface::kDefaultMaxOpSizeHint;
    ri->RasterCHROMIUM(
        display_item_list.get(), &image_provider, options.content_size,
        options.full_raster_rect, options.playback_rect, options.post_translate,
        gfx::Vector2dF(options.post_scale, options.post_scale),
        options.requires_clear, /*raster_inducing_scroll_offsets=*/nullptr,
        &max_op_size_limit);
    for (const auto& list : options.additional_lists) {
      ri->RasterCHROMIUM(list.get(), &image_provider, options.content_size,
                         options.full_raster_rect, options.playback_rect,
                         options.post_translate,
                         gfx::Vector2dF(options.post_scale, options.post_scale),
                         options.requires_clear,
                         /*raster_inducing_scroll_offsets=*/nullptr,
                         &max_op_size_limit);
    }
    ri->EndRasterCHROMIUM();

    EXPECT_EQ(ri->GetError(), static_cast<unsigned>(GL_NO_ERROR));

    SkBitmap result = ReadbackMailbox(ri, client_shared_image->mailbox(),
                                      options.resource_size);
    gpu::SyncToken sync_token =
        gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
    sii->DestroySharedImage(sync_token, std::move(client_shared_image));
    return result;
  }

  SkBitmap ReadbackMailbox(gpu::raster::RasterInterface* ri,
                           const gpu::Mailbox& mailbox,
                           const gfx::Size& image_size,
                           sk_sp<SkColorSpace> color_space = nullptr) {
    SkImageInfo image_info = SkImageInfo::MakeN32Premul(
        image_size.width(), image_size.height(), color_space);
    SkBitmap result;
    result.allocPixels(image_info);
    ri->ReadbackImagePixels(mailbox, image_info, image_info.minRowBytes(), 0, 0,
                            /*plane_index=*/0, result.getPixels());
    return result;
  }

  scoped_refptr<gpu::ClientSharedImage> CreateClientSharedImage(
      gpu::raster::RasterInterface* ri,
      gpu::SharedImageInterface* sii,
      const RasterOptions& options,
      viz::SharedImageFormat format,
      std::optional<gfx::ColorSpace> color_space = std::nullopt) {
    // These SharedImages serve as both the source of reads and destination of
    // writes via the raster interface in these tests.
    gpu::SharedImageUsageSet flags = gpu::SHARED_IMAGE_USAGE_RASTER_READ |
                                     gpu::SHARED_IMAGE_USAGE_RASTER_WRITE;
    auto client_shared_image = sii->CreateSharedImage(
        {format, options.resource_size,
         color_space.value_or(options.target_color_params.color_space), flags,
         "TestLabel"},
        gpu::kNullSurfaceHandle);
    EXPECT_TRUE(client_shared_image->mailbox().Verify());

    return client_shared_image;
  }

  gpu::SyncToken UploadPixels(
      gpu::raster::RasterInterface* ri,
      const scoped_refptr<gpu::ClientSharedImage>& shared_image,
      const SkImageInfo& info,
      const SkBitmap& bitmap) {
    auto ri_access = shared_image->BeginRasterAccess(
        ri, shared_image->creation_sync_token(), /*readonly=*/false);
    ri->WritePixels(shared_image->mailbox(), /*dst_x_offset=*/0,
                    /*dst_y_offset=*/0,
                    /*texture_target=*/0,
                    SkPixmap(info, bitmap.getPixels(), info.minRowBytes()));
    EXPECT_EQ(ri->GetError(), static_cast<unsigned>(GL_NO_ERROR));
    return gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
  }

  void UploadPixelsYUV(
      gpu::raster::RasterInterface* ri,
      const scoped_refptr<gpu::ClientSharedImage>& shared_image,
      const SkYUVAPixmaps& yuv_pixmap) {
    auto ri_access = shared_image->BeginRasterAccess(
        ri, shared_image->creation_sync_token(), /*readonly=*/false);
    ri->WritePixelsYUV(shared_image->mailbox(), yuv_pixmap);
    EXPECT_EQ(ri->GetError(), static_cast<unsigned>(GL_NO_ERROR));
    gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
  }

  // Verifies |actual| matches the expected PNG image.
  void ExpectEquals(
      const SkBitmap& actual,
      const base::FilePath::StringType& ref_filename,
      const PixelComparator& comparator = ExactPixelComparator()) {
    base::FilePath test_data_dir;
    ASSERT_TRUE(
        base::PathService::Get(viz::Paths::DIR_TEST_DATA, &test_data_dir));

    base::FilePath png_path = test_data_dir.Append(ref_filename);

    auto* cmd = base::CommandLine::ForCurrentProcess();
    if (cmd->HasSwitch(switches::kRebaselinePixelTests)) {
      EXPECT_TRUE(WritePNGFile(actual, png_path, true));
    } else {
      EXPECT_TRUE(MatchesPNGFile(actual, png_path, comparator));
    }
  }

  void ExpectEquals(
      SkBitmap actual,
      SkBitmap expected,
      const PixelComparator& comparator = ExactPixelComparator()) {
    EXPECT_TRUE(MatchesBitmap(actual, expected, comparator));
  }

 protected:
  static constexpr size_t kWorkingSetSize = 64 * 1024 * 1024;
  scoped_refptr<viz::TestInProcessContextProvider> raster_context_provider_;
  std::unique_ptr<GpuImageDecodeCache> oop_image_cache_;
  gl::DisableNullDrawGLBindings enable_pixel_output_;
  std::unique_ptr<ImageProvider> image_provider_;
  int color_space_id_ = 0;
  gpu::raster::GrShaderCache gr_shader_cache_;
  gpu::GpuProcessShmCount use_shader_cache_shm_count_;
};

class OopClearPixelTest : public OopPixelTest,
                          public ::testing::WithParamInterface<bool> {
 public:
  bool IsPartialRaster() const { return GetParam(); }
};

TEST_F(OopPixelTest, DrawColor) {
  gfx::Rect rect(10, 10);
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawColorOp>(SkColors::kBlue, SkBlendMode::kSrc);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  SkBitmap expected = MakeSolidColorBitmap(rect.size(), SkColors::kBlue);

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, DrawColorWithTargetColorSpace) {
  gfx::Rect rect(10, 10);
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawColorOp>(SkColors::kBlue, SkBlendMode::kSrc);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  gfx::ColorSpace target_color_space = gfx::ColorSpace::CreateXYZD50();

  RasterOptions options(rect.size());
  options.target_color_params.color_space = target_color_space;

  SkBitmap expected = MakeSolidColorBitmap(
      rect.size(), SkColor4f::FromColor(SkColorSetARGB(255, 38, 15, 221)));

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, DrawRect) {
  gfx::Rect rect(10, 10);
  auto color_paint = [](int r, int g, int b) {
    PaintFlags flags;
    flags.setColor(SkColorSetARGB(255, r, g, b));
    return flags;
  };
  std::vector<std::pair<SkRect, PaintFlags>> input = {
      {SkRect::MakeXYWH(0, 0, 5, 5), color_paint(0, 0, 255)},
      {SkRect::MakeXYWH(5, 0, 5, 5), color_paint(0, 255, 0)},
      {SkRect::MakeXYWH(0, 5, 5, 5), color_paint(0, 255, 255)},
      {SkRect::MakeXYWH(5, 5, 5, 5), color_paint(255, 0, 0)}};

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  for (auto& op : input) {
    display_item_list->StartPaint();
    display_item_list->push<DrawRectOp>(op.first, op.second);
    display_item_list->EndPaintOfUnpaired(
        gfx::ToEnclosingRect(gfx::SkRectToRectF(op.first)));
  }
  display_item_list->Finalize();

  auto actual = Raster(std::move(display_item_list), rect.size());

  // Expected colors are 5x5 rects of
  //  BLUE GREEN
  //  CYAN  RED
  std::vector<SkPMColor> expected_pixels(rect.width() * rect.height());
  for (int h = 0; h < rect.height(); ++h) {
    auto start = expected_pixels.begin() + h * rect.width();
    SkPMColor left_color = SkPreMultiplyColor(
        h < 5 ? input[0].second.getColor() : input[2].second.getColor());
    SkPMColor right_color = SkPreMultiplyColor(
        h < 5 ? input[1].second.getColor() : input[3].second.getColor());

    std::fill(start, start + 5, left_color);
    std::fill(start + 5, start + 10, right_color);
  }
  SkBitmap expected;
  expected.installPixels(
      SkImageInfo::MakeN32Premul(rect.width(), rect.height()),
      expected_pixels.data(), rect.width() * sizeof(SkPMColor));
  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, DrawRecordPaintFilterTranslatedBounds) {
  gfx::Size output_size(10, 10);

  // The paint record filter's ops would fill the right half of the image with
  // green, but its record bounds are configured to clip it to the bottom right
  // quarter of the output.
  PaintFlags internal_flags;
  internal_flags.setColor(SkColors::kGreen);
  PaintOpBuffer filter_buffer;
  filter_buffer.push<DrawRectOp>(
      SkRect::MakeLTRB(output_size.width() / 2.f, 0.f, output_size.width(),
                       output_size.height()),
      internal_flags);
  sk_sp<RecordPaintFilter> record_filter = sk_make_sp<RecordPaintFilter>(
      filter_buffer.ReleaseAsRecord(),
      SkRect::MakeLTRB(output_size.width() / 2.f, output_size.height() / 2.f,
                       output_size.width(), output_size.height()));

  PaintFlags record_flags;
  record_flags.setImageFilter(record_filter);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawColorOp>(SkColors::kWhite, SkBlendMode::kSrc);
  display_item_list->push<SaveLayerOp>(record_flags);
  display_item_list->push<RestoreOp>();
  display_item_list->EndPaintOfUnpaired(gfx::Rect(output_size));
  display_item_list->Finalize();

  SkImageInfo ii =
      SkImageInfo::MakeN32Premul(output_size.width(), output_size.height());
  SkBitmap expected;
  expected.allocPixels(ii, ii.minRowBytes());
  expected.eraseColor(SkColors::kWhite);
  expected.erase(
      SkColors::kGreen.toSkColor(),
      SkIRect::MakeLTRB(output_size.width() / 2, output_size.height() / 2,
                        output_size.width(), output_size.height()));

  auto actual = Raster(display_item_list, output_size);
  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, DrawImage) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  const PaintImage::Id kSomeId = 32;
  auto builder =
      PaintImageBuilder::WithDefault().set_image(image, 0).set_id(kSomeId);
  auto paint_image = builder.TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));

  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_image.png"));
}

TEST_F(OopPixelTest, DrawImageAlpha) {
  constexpr gfx::Rect rect(100, 100);

  auto bitmap_black = MakeSolidColorBitmap(rect.size(), SkColors::kBlack);

  // Initialize the premul and unpremul SkPixmaps with the same color (note
  // that SkColor4f is always unpremultiplied).
  const SkColor4f kTestColor = {128.f / 255.f, 0.f, 0.f, 128.f / 255.f};
  auto bitmap_premul =
      MakeSolidColorBitmap(gfx::Size(50, 50), kTestColor, kPremul_SkAlphaType);
  auto bitmap_unpremul = MakeSolidColorBitmap(gfx::Size(50, 50), kTestColor,
                                              kUnpremul_SkAlphaType);

  // Despite being initialized by the same SkColor, the premultiplied and
  // unpremultiplied bitmaps will end up with different bits in them (reflecting
  // the premultiplied vs unpremultiplied representations of the color).
  EXPECT_NE(bitmap_premul.getAddr32(0, 0), bitmap_unpremul.getAddr32(0, 0));

  const PaintImage::Id kIdBlack = 32;
  const PaintImage::Id kIdPremul = 33;
  const PaintImage::Id kIdUnpremul = 34;

  auto paint_image_black =
      PaintImageBuilder::WithDefault()
          .set_image(SkImages::RasterFromBitmap(bitmap_black), 0)
          .set_id(kIdBlack)
          .TakePaintImage();
  auto paint_image_premul =
      PaintImageBuilder::WithDefault()
          .set_image(SkImages::RasterFromBitmap(bitmap_premul), 1)
          .set_id(kIdPremul)
          .TakePaintImage();
  auto paint_image_unpremul =
      PaintImageBuilder::WithDefault()
          .set_image(SkImages::RasterFromBitmap(bitmap_unpremul), 2)
          .set_id(kIdUnpremul)
          .TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));

  // Draw a black background.
  display_item_list->push<DrawImageOp>(paint_image_black, 0.f, 0.f, sampling,
                                       nullptr);
  // Draw a premul image on the left.
  display_item_list->push<DrawImageOp>(paint_image_premul, 0.f, 25.f, sampling,
                                       nullptr);
  // Draw an unpremul image on the right.
  display_item_list->push<DrawImageOp>(paint_image_unpremul, 50.f, 25.f,
                                       sampling, nullptr);

  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());

  // The premul and unpremul images should be identical.
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_alpha_premul_unpremul.png"));
}

TEST_F(OopPixelTest, DrawImageScaled) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  auto builder = PaintImageBuilder::WithDefault().set_image(image, 0).set_id(
      PaintImage::GetNextId());
  auto paint_image = builder.TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<ScaleOp>(0.5f, 0.5f);
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_image_scaled.png"));
}

TEST_F(OopPixelTest, DrawImageShaderScaled) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  auto builder = PaintImageBuilder::WithDefault().set_image(image, 0).set_id(
      PaintImage::GetNextId());
  auto paint_image = builder.TakePaintImage();
  auto paint_image_shader = PaintShader::MakeImage(
      paint_image, SkTileMode::kRepeat, SkTileMode::kRepeat, nullptr);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<ScaleOp>(0.5f, 0.5f);
  PaintFlags flags;
  flags.setShader(paint_image_shader);
  flags.setFilterQuality(kDefaultFilterQuality);
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(rect), flags);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_image_shader_scaled.png"));
}

TEST_F(OopPixelTest, DrawRecordShaderWithImageScaled) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  auto builder = PaintImageBuilder::WithDefault().set_image(image, 0).set_id(
      PaintImage::GetNextId());
  auto paint_image = builder.TakePaintImage();
  PaintOpBuffer paint_buffer;
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  paint_buffer.push<DrawImageOp>(paint_image, 0.f, 0.f, sampling, nullptr);
  auto paint_record_shader = PaintShader::MakePaintRecord(
      paint_buffer.ReleaseAsRecord(), gfx::RectToSkRect(rect),
      SkTileMode::kRepeat, SkTileMode::kRepeat, nullptr);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<ScaleOp>(0.5f, 0.5f);
  PaintFlags raster_flags;
  raster_flags.setShader(paint_record_shader);
  raster_flags.setFilterQuality(kDefaultFilterQuality);
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(rect), raster_flags);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_record_shader.png"));
}

TEST_F(OopPixelTest, DrawRecordShaderTranslatedTileRect) {
  // Arbitrary offsets.  The DrawRectOp inside the PaintShader draws
  // with this offset, but the tile rect also has this offset, so they
  // should cancel out, and it should be as if the DrawRectOp was at the
  // origin.
  int x_offset = 3901;
  int y_offset = -234;

  // Shader here is a tiled 2x3 rectangle with a 1x2 green block in the
  // upper left and a 10pixel wide right/bottom border.  The shader
  // tiling starts from the origin, so starting at 2,1 in the offset_rect
  // below cuts off part of that, leaving two green i's.
  PaintFlags internal_flags;
  internal_flags.setColor(SkColors::kGreen);
  PaintOpBuffer shader_buffer;
  shader_buffer.push<DrawRectOp>(SkRect::MakeXYWH(x_offset, y_offset, 1, 2),
                                 internal_flags);

  SkRect tile_rect = SkRect::MakeXYWH(x_offset, y_offset, 2, 3);
  sk_sp<PaintShader> paint_record_shader = PaintShader::MakePaintRecord(
      shader_buffer.ReleaseAsRecord(), tile_rect, SkTileMode::kRepeat,
      SkTileMode::kRepeat, nullptr,
      PaintShader::ScalingBehavior::kRasterAtScale);
  // Force paint_flags to convert this to kFixedScale, so we can safely compare
  // pixels between direct and oop-r modes (since oop will convert to
  // kFixedScale no matter what.
  paint_record_shader->set_has_animated_images(true);

  gfx::Size output_size(10, 10);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawColorOp>(SkColors::kWhite, SkBlendMode::kSrc);
  display_item_list->push<ScaleOp>(2.f, 2.f);
  PaintFlags raster_flags;
  raster_flags.setShader(paint_record_shader);
  SkRect offset_rect = SkRect::MakeXYWH(2, 1, 10, 10);
  display_item_list->push<DrawRectOp>(offset_rect, raster_flags);
  display_item_list->EndPaintOfUnpaired(gfx::Rect(output_size));
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, output_size);
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_record_shader_tiled.png"));
}

TEST_F(OopPixelTest, DrawImageWithTargetColorSpace) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  const PaintImage::Id kSomeId = 32;
  auto builder =
      PaintImageBuilder::WithDefault().set_image(image, 0).set_id(kSomeId);
  auto paint_image = builder.TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  RasterOptions options(rect.size());
  options.target_color_params.color_space =
      gfx::ColorSpace::CreateDisplayP3D65();

  auto actual = Raster(display_item_list, options);

#if BUILDFLAG(IS_ANDROID)
  // Android has slight differences in color.
  FuzzyPixelOffByOneComparator comparator;
#else
  ExactPixelComparator comparator;
#endif

  ExpectEquals(actual, FILE_PATH_LITERAL("oop_image_target_color_space.png"),
               comparator);

  // Verify some conversion occurred here and that actual != bitmap.
  EXPECT_NE(actual.getColor(0, 0), SkColors::kMagenta.toSkColor());
}

TEST_F(OopPixelTest, DrawGainmapImage) {
  constexpr gfx::Size kSize(8, 8);
  constexpr gfx::Rect kRect(kSize);

  // We'll be working in 2.2 space.
  const float kDegamma = 2.2f;
  const float kGamma = 1 / kDegamma;

  // The base image is (0.5, 0.5, 0.5) in linear space
  auto base_color_space =
      SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kSRGB);
  auto base_info = SkImageInfo::MakeN32Premul(kSize.width(), kSize.height(),
                                              base_color_space);
  auto base_image_generator = sk_make_sp<FakePaintImageGenerator>(base_info);
  const float kBaseLinear = 0.5f;
  const float kBaseSignal = std::pow(kBaseLinear, kGamma);
  {
    SkBitmap bitmap;
    bitmap.installPixels(base_image_generator->GetPixmap());
    SkCanvas canvas(bitmap, SkSurfaceProps{});
    SkColor4f color{kBaseSignal, kBaseSignal, kBaseSignal, 1.f};
    canvas.drawColor(color);
  }

  // The gainmap is fully applied at headroom 2, and has a maximum scale of 4.
  const float kHeadroom = 2.f;
  const float kRatioMax = 4.f;
  SkGainmapInfo gainmap_info;
  auto gain_info = SkImageInfo::MakeN32Premul(kSize.width(), kSize.height(),
                                              SkColorSpace::MakeSRGB());
  auto gain_image_generator = sk_make_sp<FakePaintImageGenerator>(gain_info);
  {
    gainmap_info.fDisplayRatioSdr = 1.f;
    gainmap_info.fDisplayRatioHdr = kHeadroom;
    gainmap_info.fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
    gainmap_info.fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
    gainmap_info.fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
    gainmap_info.fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
  }

  // The gainmap scales by (1, 2, 4) in linear space.
  {
    SkBitmap bitmap;
    bitmap.installPixels(gain_image_generator->GetPixmap());
    SkCanvas canvas(bitmap, SkSurfaceProps{});
    SkColor4f color{0.f, std::log(kHeadroom) / std::log(kRatioMax), 1.f, 1.f};
    canvas.drawColor(color);
  }

  static int counter = 0;
  const PaintImage::Id kSomeId = 32 + counter++;
  auto paint_image =
      PaintImageBuilder::WithDefault()
          .set_id(kSomeId)
          .set_paint_image_generator(base_image_generator)
          .set_gainmap_paint_image_generator(gain_image_generator, gainmap_info)
          .TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();

  // Use cubic sampling, to ensure that it does not cause corruption or crashes.
  // https://crbug.com/374783345
  SkSamplingOptions sampling =
      SkSamplingOptions(SkCubicResampler::CatmullRom());
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(kRect);
  display_item_list->Finalize();

  // Give an extremely generous epsilon, based on the observations of
  // DrawHdrImageWithMetadata. Local testing passed with epsilon of 2/255.
  float kEps = 16.f / 255.f;

  const float kDestScale = kHeadroom;
  auto dest_color_space = SkColorSpace::MakeRGB(
      skia::ScaleTransferFunction(SkNamedTransferFn::k2Dot2, kDestScale),
      SkNamedGamut::kSRGB);

  // Raster with headroom of 1, and the result should be the input, converted
  // to the output space.
  {
    RasterOptions options(kSize);
    options.target_color_params.color_space =
        gfx::ColorSpace(*dest_color_space);
    options.target_color_params.hdr_headroom = 0.f;
    auto result = Raster(display_item_list, options);
    auto out_color = result.getColor4f(0, 0);
    EXPECT_NEAR(out_color.fR, std::pow(kBaseLinear / kDestScale, kGamma), kEps);
    EXPECT_NEAR(out_color.fG, std::pow(kBaseLinear / kDestScale, kGamma), kEps);
    EXPECT_NEAR(out_color.fB, std::pow(kBaseLinear / kDestScale, kGamma), kEps);
  }

  // Raster with headroom of 2, and the result should be be the fully applied
  // gainmap, so (0.5, 1, 2) in linear space.
  {
    RasterOptions options(kSize);
    options.target_color_params.color_space =
        gfx::ColorSpace(*dest_color_space);
    options.target_color_params.hdr_headroom = std::log2(kDestScale);
    auto result = Raster(display_item_list, options);
    auto out_color = result.getColor4f(0, 0);
    EXPECT_NEAR(out_color.fR, std::pow(0.5f / kDestScale, kGamma), kEps);
    EXPECT_NEAR(out_color.fG, std::pow(1.0f / kDestScale, kGamma), kEps);
    EXPECT_NEAR(out_color.fB, std::pow(2.0f / kDestScale, kGamma), kEps);
  }
}

TEST_F(OopPixelTest, DrawGainmapImageCubic) {
  constexpr uint32_t kSrcSize = 2;
  constexpr uint32_t kDstSize = 4;

  // The gainmap is fully applied at headroom 2, and has a maximum scale of 4.
  const float kRatioMax = 4.f;
  SkGainmapInfo gainmap_info = {
      {1.f, 1.f, 1.f, 1.f},
      {kRatioMax, kRatioMax, kRatioMax, 1.f},
      {1.f, 1.f, 1.f, 1.f},
      {0.f, 0.f, 0.f, 1.f},
      {0.f, 0.f, 0.f, 1.f},
      1.f,
      kRatioMax,
      SkGainmapInfo::BaseImageType::kSDR,
      SkGainmapInfo::Type::kDefault,
      nullptr,
  };

  auto info = SkImageInfo::MakeN32Premul(kSrcSize, kSrcSize,
                                         SkColorSpace::MakeSRGBLinear());
  std::array<sk_sp<FakePaintImageGenerator>, 2> generators;
  std::array<std::array<uint8_t, 2>, 2> pixel_values = {
      {{100, 20},
       {static_cast<uint8_t>(
            std::round(255.f * std::log(2.f) / std::log(kRatioMax))),
        static_cast<uint8_t>(
            std::round(255.f * std::log(3.f) / std::log(kRatioMax)))}}};
  for (int i = 0; i < 2; ++i) {
    generators[i] = sk_make_sp<FakePaintImageGenerator>(info);
    SkPixmap pm = generators[i]->GetPixmap();
    for (size_t x = 0; x < kSrcSize; ++x) {
      for (size_t y = 0; y < kSrcSize; ++y) {
        uint32_t* pixel = pm.writable_addr32(x, y);
        uint8_t v = pixel_values[i][x];
        *pixel = SkColorSetARGB(255, v, v, v);
      }
    }
  }

  // Draw with cubic filtering. This will get demoted to linear filtering.
  static int counter = 0;
  const PaintImage::Id kSomeId = 32 + counter++;
  auto paint_image =
      PaintImageBuilder::WithDefault()
          .set_id(kSomeId)
          .set_paint_image_generator(generators[0])
          .set_gainmap_paint_image_generator(generators[1], gainmap_info)
          .TakePaintImage();
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  {
    display_item_list->StartPaint();
    display_item_list->push<DrawImageRectOp>(
        paint_image, SkRect::MakeWH(kSrcSize, kSrcSize),
        SkRect::MakeWH(kDstSize, kDstSize),
        SkSamplingOptions(SkCubicResampler::CatmullRom()), nullptr,
        SkCanvas::kStrict_SrcRectConstraint);
    display_item_list->EndPaintOfUnpaired(gfx::Rect(0, 0, kDstSize, kDstSize));
    display_item_list->Finalize();
  }
  RasterOptions options(gfx::Size(kDstSize, kDstSize));
  {
    auto dest_color_space = SkColorSpace::MakeSRGBLinear();
    options.target_color_params.color_space =
        gfx::ColorSpace(*dest_color_space);
    options.target_color_params.hdr_headroom = std::log2(kRatioMax);
  }
  auto result = Raster(display_item_list, options);

  // Check pixel values against manually computed expected values.
  for (int i = 0; i < 4; ++i) {
    // Compute the linearly interpolated base and gainmap pixel values.
    float base_value =
        ((3.f - i) * pixel_values[0][0] + i * pixel_values[0][1]) /
        (255.f * 3.f);
    float gain_value =
        ((3.f - i) * pixel_values[1][0] + i * pixel_values[1][1]) /
        (255.f * 3.f);

    // Compute the expected value using the interpolated values.
    float expected = base_value * std::exp(gain_value * std::log(kRatioMax));
    float actual = result.getColor4f(i, 0).fR;
    float kEpsilon = 16.f / 255.f;
    EXPECT_NEAR(expected, actual, kEpsilon);
  }
}

TEST_F(OopPixelTest, DrawGainmapImageFiltering) {
  constexpr gfx::Size kSize(4, 4);
  constexpr gfx::Rect kRect(kSize);
  constexpr gfx::Size kGainSize(2, 2);

  // The base image is (0.25, 0.25, 0.25) in linear space
  float kValueBase = 0.25f;
  auto base_info = SkImageInfo::MakeN32Premul(kSize.width(), kSize.height(),
                                              SkColorSpace::MakeSRGB());
  auto base_image_generator = sk_make_sp<FakePaintImageGenerator>(base_info);
  {
    SkBitmap bitmap;
    bitmap.installPixels(base_image_generator->GetPixmap());
    SkCanvas canvas(bitmap, SkSurfaceProps{});
    SkPaint paint;

    paint.setColor({kValueBase, kValueBase, kValueBase, 1.f},
                   SkColorSpace::MakeSRGBLinear().get());
    canvas.drawRect(SkRect(0, 0, 4, 4), paint);
  }

  // The gainmap is fully applied at headroom 2, and has a maximum scale of 4.
  const float kRatioMax = 4.f;
  auto gain_info = SkImageInfo::MakeN32Premul(
      kGainSize.width(), kGainSize.height(), SkColorSpace::MakeSRGBLinear());
  auto gain_image_generator = sk_make_sp<FakePaintImageGenerator>(gain_info);
  SkGainmapInfo gainmap_info = {
      {1.f, 1.f, 1.f, 1.f},
      {kRatioMax, kRatioMax, kRatioMax, 1.f},
      {1.f, 1.f, 1.f, 1.f},
      {0.f, 0.f, 0.f, 1.f},
      {0.f, 0.f, 0.f, 1.f},
      1.f,
      kRatioMax,
      SkGainmapInfo::BaseImageType::kSDR,
      SkGainmapInfo::Type::kDefault,
      nullptr,
  };

  // The gainmap scales by 2 on the left and 3 on the right.
  float kGainValue0 = std::log(2.f) / std::log(kRatioMax);
  float kGainValue1 = std::log(3.f) / std::log(kRatioMax);
  {
    SkBitmap bitmap;
    bitmap.installPixels(gain_image_generator->GetPixmap());
    SkCanvas canvas(bitmap, SkSurfaceProps{});

    SkPaint paint;

    paint.setColor({kGainValue0, kGainValue0, kGainValue0, 1.f});
    canvas.drawRect(SkRect::MakeXYWH(0, 0, 1, 2), paint);

    paint.setColor({kGainValue1, kGainValue1, kGainValue1, 1.f});
    canvas.drawRect(SkRect::MakeXYWH(1, 0, 1, 2), paint);
  }

  static int counter = 0;
  const PaintImage::Id kSomeId = 32 + counter++;
  auto paint_image =
      PaintImageBuilder::WithDefault()
          .set_id(kSomeId)
          .set_paint_image_generator(base_image_generator)
          .set_gainmap_paint_image_generator(gain_image_generator, gainmap_info)
          .TakePaintImage();

  // Draw with nearest-neighbour sampling.
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  {
    display_item_list->StartPaint();
    display_item_list->push<DrawImageOp>(
        paint_image, 0.f, 0.f, SkSamplingOptions(SkFilterMode::kNearest),
        nullptr);
    display_item_list->EndPaintOfUnpaired(kRect);
    display_item_list->Finalize();
  }

  RasterOptions options(kSize);
  {
    auto dest_color_space = SkColorSpace::MakeSRGBLinear();
    options.target_color_params.color_space =
        gfx::ColorSpace(*dest_color_space);
    options.target_color_params.hdr_headroom = std::log2(kRatioMax);
  }
  auto result = Raster(display_item_list, options);

  for (int i = 0; i < 4; ++i) {
    // Ensure that the gainmap values are interpolated.
    float gain_value = ((3.f - i) * kGainValue0 + i * kGainValue1) / 3.f;
    float expected = kValueBase * std::exp(gain_value * std::log(kRatioMax));
    float actual = result.getColor4f(i, 0).fR;

    float kEpsilon = 16.f / 255.f;
    EXPECT_NEAR(expected, actual, kEpsilon);
  }
}

TEST_F(OopPixelTest, DrawHdrImageWithMetadata) {
  constexpr gfx::Size kSize(8, 8);
  constexpr gfx::Rect kRect(kSize);
  constexpr float kContentAvgNits = 100;

#if BUILDFLAG(IS_ANDROID)
  // Allow large quantization error on Android.
  // TODO(crbug.com/40238547): Ensure higher precision for HDR images.
  constexpr float kEpsilon = 1 / 16.f;
#elif BUILDFLAG(IS_IOS) && BUILDFLAG(SKIA_USE_METAL)
  // TODO(crbug.com/40280014): Allow larger errors on iOS as well.
  constexpr float kEpsilon = 1 / 12.f;
#else
  constexpr float kEpsilon = 1 / 32.f;
#endif

  // Create `image` with 500 nits in PQ color space.
  const auto make_image = [&](float pixel_value) {
    SkBitmap bitmap;
    bitmap.allocPixelsFlags(
        SkImageInfo::MakeN32Premul(kSize.width(), kSize.height(),
                                   SkColorSpace::MakeSRGB()),
        SkBitmap::kZeroPixels_AllocFlag);

    SkCanvas canvas(bitmap, SkSurfaceProps{});
    SkColor4f color{pixel_value, pixel_value, pixel_value, 1.f};
    canvas.drawColor(color);

    return SkImages::RasterFromBitmap(bitmap)->reinterpretColorSpace(
        gfx::ColorSpace::CreateHDR10().ToSkColorSpace());
  };
  sk_sp<SkImage> image_500_nits = make_image(0.6765848107833876f);
  sk_sp<SkImage> image_250_nits = make_image(0.6025591549907524f);

  const auto make_display_item_list = [&](sk_sp<SkImage> image,
                                          std::optional<float> peak_luminance =
                                              std::nullopt,
                                          std::optional<float> white_luminance =
                                              std::nullopt,
                                          PaintFlags* paint_flags = nullptr) {
    auto image_generator =
        sk_make_sp<FakePaintImageGenerator>(image->imageInfo());
    {
      ImageHeaderMetadata image_metadata;
      image_metadata.hdr_metadata.emplace();
      if (peak_luminance.has_value()) {
        image_metadata.hdr_metadata->cta_861_3.emplace(peak_luminance.value(),
                                                       kContentAvgNits);
      }
      if (white_luminance.has_value()) {
        image_metadata.hdr_metadata->ndwl.emplace(white_luminance.value());
      }
      image_generator->SetImageHeaderMetadata(image_metadata);
      EXPECT_TRUE(image->peekPixels(&image_generator->GetPixmap()));
    }

    static int id_counter = 0;
    const PaintImage::Id kSomeId = 32 + id_counter++;
    auto paint_image = PaintImageBuilder::WithDefault()
                           .set_id(kSomeId)
                           .set_paint_image_generator(image_generator)
                           .TakePaintImage();

    auto display_item_list = base::MakeRefCounted<DisplayItemList>();
    display_item_list->StartPaint();
    SkSamplingOptions sampling(
        PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
    display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                         paint_flags);
    display_item_list->EndPaintOfUnpaired(kRect);
    display_item_list->Finalize();

    return display_item_list;
  };

  // Create a DisplayItemList drawing `image` with 10k nits and 500 nits HDR
  // metadata.
  RasterOptions options(kSize);
  options.target_color_params.color_space = gfx::ColorSpace::CreateSRGBLinear();

  // Draw using image HDR metadata indicating that 500 is the maximum luminance.
  // The result should map the image to solid white (up to rounding error).
  {
    constexpr float kExpected = 1.0;
    auto actual =
        Raster(make_display_item_list(image_500_nits, 500.f), options);
    auto color = actual.getColor4f(0, 0);
    EXPECT_NEAR(color.fR, kExpected, kEpsilon);
    EXPECT_NEAR(color.fG, kExpected, kEpsilon);
    EXPECT_NEAR(color.fB, kExpected, kEpsilon);
  }

  // Draw using image HDR metadata indicating that 10,000 nits is the maximum
  // luminance. The result should map the image to something darker than solid
  // white.
  constexpr float kExpected10kToSdr = 0.7114198123454021f;
  {
    auto actual =
        Raster(make_display_item_list(image_500_nits, 10000.f), options);
    auto color = actual.getColor4f(0, 0);
    EXPECT_NEAR(color.fR, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fG, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fB, kExpected10kToSdr, kEpsilon);
  }

  // Perform the same computation, but with the peak and white luminance cut
  // in half (to make sure metadata white overrides content white).
  {
    auto actual = Raster(
        make_display_item_list(image_250_nits, 10000.f / 2.f, 203.f / 2.f),
        options);
    auto color = actual.getColor4f(0, 0);
    EXPECT_NEAR(color.fR, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fG, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fB, kExpected10kToSdr, kEpsilon);
  }

  // Increase the destination HDR headroom. The result should now be brighter.
  {
    constexpr float kExpected = 0.933675419515227f;
    constexpr float kDstHeadroom = 1.5f;
    options.target_color_params.hdr_headroom = std::log2(kDstHeadroom);
    auto actual =
        Raster(make_display_item_list(image_500_nits, 10000.f), options);
    auto color = actual.getColor4f(0, 0);
    EXPECT_NEAR(color.fR, kExpected, kEpsilon);
    EXPECT_NEAR(color.fG, kExpected, kEpsilon);
    EXPECT_NEAR(color.fB, kExpected, kEpsilon);
  }

  // Draw with PaintFlags constraining the dynamic range as if by CSS property
  // `dynamic-range-limit`. The result should therefore be back to being darker,
  // despite the (still) increased headroom.
  {
    PaintFlags sdr_paint_flags;
    sdr_paint_flags.setDynamicRangeLimit(PaintFlags::DynamicRangeLimitMixture(
        PaintFlags::DynamicRangeLimit::kStandard));
    scoped_refptr<DisplayItemList> display_item_list_10k_nits_sdr =
        make_display_item_list(image_500_nits, 10000.f, std::nullopt,
                               &sdr_paint_flags);
    auto actual = Raster(display_item_list_10k_nits_sdr, options);
    auto color = actual.getColor4f(0, 0);
    EXPECT_NEAR(color.fR, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fG, kExpected10kToSdr, kEpsilon);
    EXPECT_NEAR(color.fB, kExpected10kToSdr, kEpsilon);
  }
}

TEST_F(OopPixelTest, DrawImageWithSourceColorSpace) {
  constexpr gfx::Rect rect(100, 100);

  auto color_space = gfx::ColorSpace::CreateDisplayP3D65().ToSkColorSpace();

  sk_sp<SkImage> image = MakeSkImage(rect.size(), color_space);
  const PaintImage::Id kSomeId = 32;
  auto builder =
      PaintImageBuilder::WithDefault().set_image(image, 0).set_id(kSomeId);
  auto paint_image = builder.TakePaintImage();
  EXPECT_EQ(paint_image.color_space(), color_space.get());

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  RasterOptions options(rect.size());

  auto actual = Raster(display_item_list, options);

#if BUILDFLAG(IS_ANDROID)
  // Android has slight differences in color.
  auto comparator = FuzzyPixelComparator()
                        .SetErrorPixelsPercentageLimit(100.0f)
                        .SetAvgAbsErrorLimit(1.2f)
                        .SetAbsErrorLimit(2);
#else
  ExactPixelComparator comparator;
#endif

  ExpectEquals(actual,
               FILE_PATH_LITERAL("oop_draw_image_source_color_space.png"),
               comparator);
}

TEST_F(OopPixelTest, DrawImageWithSourceAndTargetColorSpace) {
  constexpr gfx::Rect rect(100, 100);

  auto color_space = gfx::ColorSpace::CreateXYZD50().ToSkColorSpace();

  sk_sp<SkImage> image = MakeSkImage(rect.size(), color_space);
  const PaintImage::Id kSomeId = 32;
  auto builder =
      PaintImageBuilder::WithDefault().set_image(image, 0).set_id(kSomeId);
  auto paint_image = builder.TakePaintImage();
  EXPECT_EQ(paint_image.color_space(), color_space.get());

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  RasterOptions options(rect.size());
  options.target_color_params.color_space =
      gfx::ColorSpace::CreateDisplayP3D65();

  auto actual = Raster(display_item_list, options);

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  // Android has slight differences in color.
  FuzzyPixelOffByOneComparator comparator;
#else
  ExactPixelComparator comparator;
#endif

  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_image_both_color_space.png"),
               comparator);
}

#if BUILDFLAG(IS_FUCHSIA) && defined(ARCH_CPU_ARM64)
// SwiftShader crashes when running this test on ARM64 on Fuchsia,
// see b/369849405.
#define MAYBE_DrawImageReinterpretedAsSRGB DISABLED_DrawImageReinterpretedAsSRGB
#else
#define MAYBE_DrawImageReinterpretedAsSRGB DrawImageReinterpretedAsSRGB
#endif
TEST_F(OopPixelTest, MAYBE_DrawImageReinterpretedAsSRGB) {
  constexpr gfx::Rect rect(100, 100);

  auto image_color_space = gfx::ColorSpace::CreateHDR10().ToSkColorSpace();
  sk_sp<SkImage> image = MakeSkImage(rect.size());
  image = image->reinterpretColorSpace(image_color_space);
  const PaintImage::Id kSomeId = 32;
  auto builder = PaintImageBuilder::WithDefault()
                     .set_image(image, 0)
                     .set_id(kSomeId)
                     .set_reinterpret_as_srgb(true);
  auto paint_image = builder.TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  PaintFlags flags;
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling, &flags);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  RasterOptions options(rect.size());
  options.target_color_params.color_space =
      gfx::ColorSpace::CreateDisplayP3D65();

  auto actual = Raster(display_item_list, options);

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  // Android has slight differences in color.
  FuzzyPixelOffByOneComparator comparator;
#else
  ExactPixelComparator comparator;
#endif

  ExpectEquals(actual, FILE_PATH_LITERAL("oop_image_target_color_space.png"),
               comparator);
}

TEST_F(OopPixelTest, DrawImageWithSetMatrix) {
  constexpr gfx::Rect rect(100, 100);

  sk_sp<SkImage> image = MakeSkImage(rect.size());
  const PaintImage::Id kSomeId = 32;
  auto builder =
      PaintImageBuilder::WithDefault().set_image(image, 0).set_id(kSomeId);
  auto paint_image = builder.TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  SkSamplingOptions sampling(
      PaintFlags::FilterQualityToSkSamplingOptions(kDefaultFilterQuality));
  display_item_list->push<SetMatrixOp>(SkM44::Scale(0.5f, 0.5f));
  display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, sampling,
                                       nullptr);
  display_item_list->EndPaintOfUnpaired(rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, rect.size());
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_image_matrix.png"));
}

namespace {
class TestMailboxBacking : public TextureBacking {
 public:
  explicit TestMailboxBacking(gpu::Mailbox mailbox, SkImageInfo info)
      : mailbox_(mailbox), info_(info) {}

  const SkImageInfo& GetSkImageInfo() override { return info_; }
  gpu::Mailbox GetMailbox() const override { return mailbox_; }
  sk_sp<SkImage> GetSkImageViaReadback() override { return nullptr; }
  bool readPixels(const SkImageInfo& dstInfo,
                  void* dstPixels,
                  size_t dstRowBytes,
                  int srcX,
                  int srcY) override {
    return false;
  }

 private:
  gpu::Mailbox mailbox_;
  SkImageInfo info_;
};
}  // namespace

TEST_F(OopPixelTest, DrawMailboxBackedImage) {
  RasterOptions options(gfx::Size(16, 16));
  options.image_provider_raster_mode = PlaybackImageProvider::RasterMode::kGpu;
  SkImageInfo backing_info = SkImageInfo::MakeN32Premul(
      options.resource_size.width(), options.resource_size.height());

  SkBitmap expected_bitmap;
  expected_bitmap.allocPixels(backing_info);

  SkCanvas canvas(expected_bitmap, SkSurfaceProps{});
  canvas.drawColor(SkColors::kMagenta);
  SkPaint green;
  green.setColor(SkColors::kGreen);
  canvas.drawRect(SkRect::MakeXYWH(1, 2, 3, 4), green);

  auto* ri = raster_context_provider_->RasterInterface();
  auto* sii = raster_context_provider_->SharedImageInterface();
  scoped_refptr<gpu::ClientSharedImage> src_client_si = CreateClientSharedImage(
      ri, sii, options, viz::SinglePlaneFormat::kRGBA_8888);

  UploadPixels(ri, src_client_si, expected_bitmap.info(), expected_bitmap);

  auto src_paint_image =
      PaintImageBuilder::WithDefault()
          .set_id(PaintImage::GetNextId())
          .set_texture_backing(sk_sp<TestMailboxBacking>(new TestMailboxBacking(
                                   src_client_si->mailbox(), backing_info)),
                               PaintImage::GetNextContentId())
          .TakePaintImage();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawImageOp>(src_paint_image, 0.f, 0.f);
  display_item_list->EndPaintOfUnpaired(gfx::Rect(options.resource_size));
  display_item_list->Finalize();

  auto actual_bitmap = Raster(display_item_list, options);
  ExpectEquals(actual_bitmap, expected_bitmap);
}

TEST_F(OopPixelTest, Preclear) {
  gfx::Rect rect(10, 10);
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->Finalize();

  RasterOptions options;
  options.resource_size = rect.size();
  options.full_raster_rect = rect;
  options.playback_rect = rect;
  options.background_color = SkColors::kMagenta;
  options.preclear = true;
  options.preclear_color = SkColors::kGreen;

  auto actual = Raster(display_item_list, options);

  auto expected = MakeSolidColorBitmap(rect.size(), SkColors::kGreen);
  ExpectEquals(actual, expected);
}

TEST_P(OopClearPixelTest, ClearingOpaqueCorner) {
  // Verify that clears work properly for both the right and bottom sides
  // of an opaque corner tile.

  RasterOptions options;
  gfx::Point arbitrary_offset(10, 20);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(8, 7));
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  if (IsPartialRaster()) {
    options.playback_rect = gfx::Rect(options.full_raster_rect.x() + 1,
                                      options.full_raster_rect.y() + 1,
                                      options.full_raster_rect.width() - 1,
                                      options.full_raster_rect.height() - 1);
  } else {
    options.playback_rect = options.full_raster_rect;
  }
  options.background_color = SkColors::kGreen;
  float arbitrary_scale = 0.25f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);
  if (IsPartialRaster()) {
    // Expect a two pixel border from texels 7-9 on the column and 6-8 on row,
    // ignoring the top row and left column.
    canvas.drawRect(SkRect::MakeXYWH(7, 1, 2, 7), green);
    canvas.drawRect(SkRect::MakeXYWH(1, 6, 8, 2), green);
  } else {
    // Expect a two pixel border from texels 7-9 on the column and 6-8 on row.
    canvas.drawRect(SkRect::MakeXYWH(7, 0, 2, 8), green);
    canvas.drawRect(SkRect::MakeXYWH(0, 6, 9, 2), green);
  }

  ExpectEquals(result, bitmap);
}

TEST_F(OopPixelTest, ClearingOpaqueCornerExactEdge) {
  // Verify that clears work properly for both the right and bottom sides
  // of an opaque corner tile whose content rect exactly lines up with
  // the edge of the resource.

  RasterOptions options;
  gfx::Point arbitrary_offset(10, 20);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, options.resource_size);
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  options.playback_rect = options.full_raster_rect;
  options.background_color = SkColors::kGreen;
  float arbitrary_scale = 0.25f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto oop_result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  // Expect a one pixel border on the bottom/right edge.
  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);
  canvas.drawRect(SkRect::MakeXYWH(9, 0, 1, 10), green);
  canvas.drawRect(SkRect::MakeXYWH(0, 9, 10, 1), green);

  ExpectEquals(oop_result, bitmap);
}

TEST_F(OopPixelTest, ClearingOpaqueCornerPartialRaster) {
  // Verify that clears do nothing on an opaque corner tile whose
  // partial raster rect doesn't intersect the edge of the content.

  RasterOptions options;
  options.resource_size = gfx::Size(10, 10);
  gfx::Point arbitrary_offset(30, 12);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(8, 7));
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  options.playback_rect =
      gfx::Rect(arbitrary_offset.x() + 5, arbitrary_offset.y() + 3, 2, 3);
  options.background_color = SkColors::kGreen;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Verify this is internal.
  EXPECT_NE(options.playback_rect.right(), options.full_raster_rect.right());
  EXPECT_NE(options.playback_rect.bottom(), options.full_raster_rect.bottom());

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto oop_result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  // Expect no clearing here because the playback rect is internal.
  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);

  ExpectEquals(oop_result, bitmap);
}

TEST_P(OopClearPixelTest, ClearingOpaqueLeftEdge) {
  // Verify that a tile that intersects the left edge of content
  // but not other edges only clears the left pixels.
  RasterOptions options;
  options.resource_size = gfx::Size(10, 10);
  int arbitrary_y = 10;
  options.full_raster_rect = gfx::Rect(0, arbitrary_y, 3, 10);
  options.content_size = gfx::Size(options.full_raster_rect.right() + 1000,
                                   options.full_raster_rect.bottom() + 1000);
  if (IsPartialRaster()) {
    // Ignore the right column of pixels here to force partial raster.
    // Additionally ignore the top and bottom rows of pixels to make sure
    // that things are not cleared outside the rect.
    options.playback_rect = gfx::Rect(options.full_raster_rect.x(),
                                      options.full_raster_rect.y() + 1,
                                      options.full_raster_rect.width() - 1,
                                      options.full_raster_rect.height() - 2);
  } else {
    options.playback_rect = options.full_raster_rect;
  }

  options.background_color = SkColors::kGreen;
  options.post_translate = gfx::Vector2dF(0.3f, 0.7f);
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);
  if (IsPartialRaster()) {
    // Expect a one pixel column border on the first column, ignoring the first
    // and the last rows.
    canvas.drawRect(SkRect::MakeXYWH(0, 1, 1, 8), green);
  } else {
    // Expect a one pixel column border on the first column.
    canvas.drawRect(SkRect::MakeXYWH(0, 0, 1, 10), green);
  }

  ExpectEquals(result, bitmap);
}

TEST_P(OopClearPixelTest, ClearingOpaqueRightEdge) {
  // Verify that a tile that intersects the right edge of content
  // but not other edges only clears the right pixels.
  RasterOptions options;
  gfx::Point arbitrary_offset(30, 40);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(3, 10));
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom() + 1000);
  if (IsPartialRaster()) {
    // Ignore the left column of pixels here to force partial raster.
    // Additionally ignore the top and bottom rows of pixels to make sure
    // that things are not cleared outside the rect.
    options.playback_rect = gfx::Rect(options.full_raster_rect.x() + 1,
                                      options.full_raster_rect.y() + 1,
                                      options.full_raster_rect.width() - 1,
                                      options.full_raster_rect.height() - 2);
  } else {
    options.playback_rect = options.full_raster_rect;
  }

  options.background_color = SkColors::kGreen;
  float arbitrary_scale = 0.25f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);
  if (IsPartialRaster()) {
    // Expect a two pixel column border from texels 2-4, ignoring the first and
    // the last rows.
    canvas.drawRect(SkRect::MakeXYWH(2, 1, 2, 8), green);
  } else {
    // Expect a two pixel column border from texels 2-4.
    canvas.drawRect(SkRect::MakeXYWH(2, 0, 2, 10), green);
  }

  ExpectEquals(result, bitmap);
}

TEST_P(OopClearPixelTest, ClearingOpaqueTopEdge) {
  // Verify that a tile that intersects only the top edge of content
  // but not other edges only clears the top pixels.

  RasterOptions options;
  options.resource_size = gfx::Size(10, 10);
  int arbitrary_x = 10;
  options.full_raster_rect = gfx::Rect(arbitrary_x, 0, 10, 5);
  options.content_size = gfx::Size(options.full_raster_rect.right() + 1000,
                                   options.full_raster_rect.bottom() + 1000);
  if (IsPartialRaster()) {
    // Ignore the bottom row of pixels here to force partial raster.
    // Additionally ignore the left and right columns of pixels to make sure
    // that things are not cleared outside the rect.
    options.playback_rect = gfx::Rect(options.full_raster_rect.x() + 1,
                                      options.full_raster_rect.y(),
                                      options.full_raster_rect.width() - 2,
                                      options.full_raster_rect.height() - 1);
  } else {
    options.playback_rect = options.full_raster_rect;
  }
  options.background_color = SkColors::kGreen;
  options.post_translate = gfx::Vector2dF(0.3f, 0.7f);
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);

  if (IsPartialRaster()) {
    // Expect a one pixel border on the top row, ignoring the first and the last
    // columns.
    canvas.drawRect(SkRect::MakeXYWH(1, 0, 8, 1), green);
  } else {
    // Expect a one pixel border on the top row.
    canvas.drawRect(SkRect::MakeXYWH(0, 0, 10, 1), green);
  }

  ExpectEquals(result, bitmap);
}

TEST_P(OopClearPixelTest, ClearingOpaqueBottomEdge) {
  // Verify that a tile that intersects the bottom edge of content
  // but not other edges only clears the bottom pixels.

  RasterOptions options;
  gfx::Point arbitrary_offset(10, 20);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(10, 5));
  options.content_size = gfx::Size(options.full_raster_rect.right() + 1000,
                                   options.full_raster_rect.bottom());
  if (IsPartialRaster()) {
    // Ignore the top row of pixels here to force partial raster.
    // Additionally ignore the left and right columns of pixels to make sure
    // that things are not cleared outside the rect.
    options.playback_rect = gfx::Rect(options.full_raster_rect.x() + 1,
                                      options.full_raster_rect.y() + 1,
                                      options.full_raster_rect.width() - 2,
                                      options.full_raster_rect.height() - 1);
  } else {
    options.playback_rect = options.full_raster_rect;
  }
  options.background_color = SkColors::kGreen;
  float arbitrary_scale = 0.25f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  SkPaint green;
  green.setColor(options.background_color);

  if (IsPartialRaster()) {
    // Expect a two pixel border from texels 4-6 on the row, ignoring the first
    // and the last columns.
    canvas.drawRect(SkRect::MakeXYWH(1, 4, 8, 2), green);
  } else {
    // Expect a two pixel border from texels 4-6 on the row
    canvas.drawRect(SkRect::MakeXYWH(0, 4, 10, 2), green);
  }

  ExpectEquals(result, bitmap);
}

TEST_F(OopPixelTest, ClearingOpaqueInternal) {
  // Verify that an internal opaque tile does no clearing.

  RasterOptions options;
  gfx::Point arbitrary_offset(35, 12);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, options.resource_size);
  // Very large content rect to make this an internal tile.
  options.content_size = gfx::Size(1000, 1000);
  options.playback_rect = options.full_raster_rect;
  options.background_color = SkColors::kGreen;
  options.post_translate = gfx::Vector2dF(0.3f, 0.7f);
  float arbitrary_scale = 1.2345f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = false;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto oop_result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  // Expect no clears here, as this tile does not intersect the edge of the
  // tile.
  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);

  ExpectEquals(oop_result, bitmap);
}

TEST_F(OopPixelTest, ClearingTransparentCorner) {
  RasterOptions options;
  gfx::Point arbitrary_offset(5, 8);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(8, 7));
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  options.playback_rect = options.full_raster_rect;
  options.background_color = SkColors::kTransparent;
  float arbitrary_scale = 3.7f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = true;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto oop_result = Raster(display_item_list, options);

  // Because this is rastering the entire tile, clear the entire thing
  // even if the full raster rect doesn't cover the whole resource.
  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(SkColors::kTransparent);

  ExpectEquals(oop_result, bitmap);
}

TEST_F(OopPixelTest, ClearingTransparentInternalTile) {
  // Content rect much larger than full raster rect or playback rect.
  // This should still clear the tile.
  RasterOptions options;
  gfx::Point arbitrary_offset(100, 200);
  options.resource_size = gfx::Size(10, 10);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, options.resource_size);
  options.content_size = gfx::Size(1000, 1000);
  options.playback_rect = options.full_raster_rect;
  options.background_color = SkColors::kTransparent;
  float arbitrary_scale = 3.7f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = true;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Note that clearing of the tile should supersede any early outs due to an
  // empty display list. This is due to the fact that partial raster may in fact
  // result in no items being generated, in which case a clear should still
  // happen. See crbug.com/901897.
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();

  auto oop_result = Raster(display_item_list, options);

  // Because this is rastering the entire tile, clear the entire thing
  // even if the full raster rect doesn't cover the whole resource.
  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(SkColors::kTransparent);

  ExpectEquals(oop_result, bitmap);
}

TEST_F(OopPixelTest, ClearingTransparentCornerPartialRaster) {
  RasterOptions options;
  options.resource_size = gfx::Size(10, 10);
  gfx::Point arbitrary_offset(30, 12);
  options.full_raster_rect = gfx::Rect(arbitrary_offset, gfx::Size(8, 7));
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  options.playback_rect =
      gfx::Rect(arbitrary_offset.x() + 5, arbitrary_offset.y() + 3, 2, 4);
  options.background_color = SkColors::kTransparent;
  float arbitrary_scale = 0.23f;
  options.post_scale = arbitrary_scale;
  options.requires_clear = true;
  options.preclear = true;
  options.preclear_color = SkColors::kRed;

  // Make a non-empty but noop display list to avoid early outs.
  auto display_item_list = MakeNoopDisplayItemList();

  auto oop_result = Raster(display_item_list, options);

  SkBitmap bitmap;
  bitmap.allocPixelsFlags(
      SkImageInfo::MakeN32Premul(options.resource_size.width(),
                                 options.resource_size.height()),
      SkBitmap::kZeroPixels_AllocFlag);

  // Result should be a red background with a cleared hole where the
  // playback_rect is.
  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.drawColor(options.preclear_color);
  canvas.translate(-arbitrary_offset.x(), -arbitrary_offset.y());
  canvas.clipRect(gfx::RectToSkRect(options.playback_rect));
  canvas.drawColor(SkColors::kTransparent, SkBlendMode::kSrc);

  ExpectEquals(oop_result, bitmap);
}

// Test bitmap and playback rects in the raster options.
TEST_F(OopPixelTest, DrawRectPlaybackRect) {
  PaintFlags flags;
  flags.setColor(SkColorSetARGB(255, 250, 10, 20));
  gfx::Rect draw_rect(3, 1, 8, 9);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
  display_item_list->EndPaintOfUnpaired(draw_rect);
  display_item_list->Finalize();

  RasterOptions options;
  options.full_raster_rect = gfx::Rect(1, 2, 10, 10);
  options.resource_size = options.full_raster_rect.size();
  options.content_size = gfx::Size(options.full_raster_rect.right(),
                                   options.full_raster_rect.bottom());
  options.playback_rect = gfx::Rect(4, 2, 5, 6);
  options.background_color = SkColors::kMagenta;

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_rect_playback_rect.png"));
}

TEST_F(OopPixelTest, DrawRectScaleTransformOptions) {
  PaintFlags flags;
  // Use powers of two here to make floating point blending consistent.
  flags.setColor(SkColorSetARGB(128, 64, 128, 32));
  flags.setAntiAlias(true);
  gfx::Rect draw_rect(3, 4, 8, 9);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
  display_item_list->EndPaintOfUnpaired(draw_rect);
  display_item_list->Finalize();

  // Draw a greenish transparent box, partially offset and clipped in the
  // bottom right.  It should appear near the upper left of a cyan background,
  // with the left and top sides of the greenish box partially blended due to
  // the post translate.
  RasterOptions options;
  options.resource_size = {20, 20};
  options.content_size = {25, 25};
  options.full_raster_rect = {5, 5, 20, 20};
  options.playback_rect = {5, 5, 13, 9};
  options.background_color = SkColors::kCyan;
  options.post_translate = {0.5f, 0.25f};
  options.post_scale = 2.f;

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_rect_scale_transform.png"));
}

TEST_F(OopPixelTest, DrawRectTransformOptionsFullRaster) {
  PaintFlags flags;
  // Use powers of two here to make floating point blending consistent.
  flags.setColor(SkColorSetRGB(64, 128, 32));
  flags.setAntiAlias(true);
  gfx::Rect draw_rect(0, 0, 19, 19);

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
  display_item_list->EndPaintOfUnpaired(draw_rect);
  display_item_list->Finalize();

  // The opaque rect above is 1px smaller than the canvas. With the subpixel
  // translation, the rect fills the whole canvas, but the pixels at the edges
  // are translucent. We should clear the canvas before drawing the rect, so
  // the translucent pixels at the edges should not expose the preclear color,
  // even if requires_clear is not true.
  RasterOptions options;
  options.resource_size = {20, 20};
  options.content_size = {25, 25};
  options.full_raster_rect = {5, 5, 20, 20};
  options.playback_rect = {5, 5, 20, 20};
  options.preclear = true;
  options.preclear_color = SkColors::kRed;
  options.post_translate = {0.5f, 0.25f};
  options.post_scale = 2.f;

  auto actual = Raster(display_item_list, options);
  auto expected = MakeSolidColorBitmap(
      options.resource_size,
      SkColor4f::FromColor(SkColorSetARGB(255, 64, 128, 32)));

  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, DrawRectQueryMiddleOfDisplayList) {
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  std::vector<SkColor> colors = {
      SkColorSetARGB(255, 0, 0, 255),    SkColorSetARGB(255, 0, 255, 0),
      SkColorSetARGB(255, 0, 255, 255),  SkColorSetARGB(255, 255, 0, 0),
      SkColorSetARGB(255, 255, 0, 255),  SkColorSetARGB(255, 255, 255, 0),
      SkColorSetARGB(255, 255, 255, 255)};

  for (int i = 0; i < 20; ++i) {
    gfx::Rect draw_rect(0, i, 1, 1);
    PaintFlags flags;
    flags.setColor(colors[i % colors.size()]);
    display_item_list->StartPaint();
    display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
    display_item_list->EndPaintOfUnpaired(draw_rect);
  }
  display_item_list->Finalize();

  // Draw a "tile" in the middle of the display list with a post scale.
  RasterOptions options;
  options.resource_size = {10, 10};
  options.content_size = {20, 20};
  options.full_raster_rect = {0, 10, 1, 10};
  options.playback_rect = {0, 10, 1, 10};
  options.background_color = SkColors::kGray;
  options.post_translate = {0.f, 0.f};
  options.post_scale = 2.f;

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual, FILE_PATH_LITERAL("oop_draw_rect_query.png"),
               FuzzyPixelOffByOneComparator());
}

TEST_F(OopPixelTest, DrawRectColorSpace) {
  RasterOptions options;
  options.resource_size = gfx::Size(100, 100);
  options.content_size = options.resource_size;
  options.full_raster_rect = gfx::Rect(options.content_size);
  options.playback_rect = options.full_raster_rect;
  options.target_color_params.color_space =
      gfx::ColorSpace::CreateDisplayP3D65();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  PaintFlags flags;
  flags.setStyle(PaintFlags::kFill_Style);
  flags.setColor(SkColors::kGreen);
  display_item_list->push<DrawRectOp>(
      gfx::RectToSkRect(gfx::Rect(options.resource_size)), flags);
  display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list->Finalize();

  SkBitmap expected = MakeSolidColorBitmap(
      options.resource_size,
      SkColor4f::FromColor(SkColorSetARGB(255, 117, 251, 76)));

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual, expected);
}

sk_sp<SkTextBlob> BuildTextBlob(
    sk_sp<SkTypeface> typeface = skia::DefaultTypeface(),
    bool use_lcd_text = false) {
  if (!typeface) {
    typeface = skia::MakeTypefaceFromName("monospace", SkFontStyle());
  }

  SkFont font;
  font.setTypeface(typeface);
  font.setHinting(SkFontHinting::kNormal);
  font.setSize(8.f);
  font.setBaselineSnap(false);
  font.setLinearMetrics(true);
  if (use_lcd_text) {
    font.setSubpixel(true);
    font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
  }

  return SkTextBlob::MakeFromString("Hamburgefons", font);
}

// A reasonable Y offset given the font parameters of BuildTextBlob() that
// ensures the text is not just drawn above the top edge of the surface.
static constexpr SkScalar kTextBlobY = 16.f;

// OopTextBlobPixelTest's test suite runs through the cross product of these
// strategies.
enum class TextBlobStrategy {
  kDirect,        // DrawTextBlobOp directly in the display list
  kDrawRecord,    // DrawRecordOp where the paint record includes text
  kRecordShader,  // DrawRectOp where the paint has a RecordShader with text
  kRecordFilter   // DrawRectOp where the paint has a RecordFilter with text
};
enum class FilterStrategy {
  kNone,        // No additional PaintFilter interacting with text
  kPaintFlags,  // A blur is added to the PaintFlags of the draw
  kSaveLayer    // An explicit save layer with blur is made before the draw
};
enum class MatrixStrategy {
  kIdentity,     // Identity matrix (no extra scale factor for text then)
  kScaled,       // Matrix is an axis-aligned scale factor
  kComplex,      // Matrix is not axis-aligned and scale must be decomposed
  kPerspective,  // Matrix has perspective and an approximate scale is needed
};
enum class LCDStrategy { kNo, kYes };

using TextBlobTestConfig = ::testing::
    tuple<TextBlobStrategy, FilterStrategy, MatrixStrategy, LCDStrategy>;

class OopTextBlobPixelTest
    : public OopPixelTest,
      public ::testing::WithParamInterface<TextBlobTestConfig> {
 public:
  void RunTest() {
    RasterOptions options;
    options.resource_size = gfx::Size(100, 100);
    options.content_size = options.resource_size;
    options.full_raster_rect = gfx::Rect(options.content_size);
    options.playback_rect = options.full_raster_rect;
    options.target_color_params.color_space = gfx::ColorSpace::CreateSRGB();
    options.use_lcd_text = UseLcdText();

    auto display_item_list = base::MakeRefCounted<DisplayItemList>();
    display_item_list->StartPaint();

    // Set matrix before any image filter is applied, which may force the
    // matrix to be decomposed into a transform compatible with the filter.
    display_item_list->push<ConcatOp>(GetMatrix());

    const bool save_layer =
        GetFilterStrategy(GetParam()) == FilterStrategy::kSaveLayer;
    sk_sp<PaintFilter> filter = MakeFilter();
    if (save_layer) {
      PaintFlags layer_flags;
      layer_flags.setImageFilter(std::move(filter));
      filter = nullptr;
      display_item_list->push<SaveLayerOp>(layer_flags);
    }

    PushDrawOp(display_item_list, std::move(filter));

    if (save_layer) {
      display_item_list->push<RestoreOp>();
    }

    display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
    display_item_list->Finalize();

    auto actual = Raster(display_item_list, options);
    auto expected = GetExpected(options.resource_size);

    // Drawing text into an image and then transforming that can lead to small
    // flakiness in devices, although in practice they are very imperceptible,
    // and distinctly different from using the wrong glyph or text params.
    float error_pixels_percentage = 0.f;
    int max_abs_error = 0;
#if BUILDFLAG(IS_ANDROID)
    // The nexus5 and nexus5x bots are particularly susceptible to small changes
    // when bilerping an image (not visible).
    const int sdk = base::android::android_info::sdk_int();
    if (sdk <= base::android::android_info::SDK_VERSION_MARSHMALLOW) {
      error_pixels_percentage = 10.f;
      max_abs_error = 20;
    } else {
      // Newer OSes occasionally have smaller flakes when using the real GPU
      error_pixels_percentage = 1.5f;
      max_abs_error = 2;
    }
#endif
    // Many platforms need very small tolerances under complex transforms,
    // and higher tolerances for perspective, since it triggers path rendering
    // for each glyph. Additionally, record filters require higher tolerance
    // because oop-r converts raster-at-scale to fixed-scale.
    float avg_error = max_abs_error;

    if (GetMatrixStrategy(GetParam()) == MatrixStrategy::kComplex) {
      const bool is_record_filter =
          GetTextBlobStrategy(GetParam()) == TextBlobStrategy::kRecordFilter;
      error_pixels_percentage =
          std::max(is_record_filter ? 12.f : 0.2f, error_pixels_percentage);
      max_abs_error = std::max(is_record_filter ? 246 : 2, max_abs_error);
      avg_error = std::max(is_record_filter ? 59.6f : 2.f, avg_error);
    } else if (GetMatrixStrategy(GetParam()) == MatrixStrategy::kPerspective) {
      switch (GetTextBlobStrategy(GetParam())) {
        case TextBlobStrategy::kRecordFilter:
          error_pixels_percentage = std::max(13.f, error_pixels_percentage);
          max_abs_error = std::max(255, max_abs_error);
          avg_error = std::max(62.4f, avg_error);
          break;
        case TextBlobStrategy::kRecordShader:
          // For kRecordShader+kPerspective the scale factor used to draw the
          // shader ends up being different for OOP-R vs using SkCanvas
          // directly. This causes some larger pixel differences as text spacing
          // subtly varies between `expected` and `actual`.
          error_pixels_percentage = std::max(19.0f, error_pixels_percentage);
#if BUILDFLAG(IS_ANDROID)
          // For some reason the text spacing is less consistent on Android
          // causing larger average difference between pixels.
          max_abs_error = std::max(237, max_abs_error);
          avg_error = std::max(61.4f, avg_error);
#else
          max_abs_error = std::max(229, max_abs_error);
          avg_error = std::max(40.2f, avg_error);
#endif
          break;
        default:
          error_pixels_percentage = std::max(4.0f, error_pixels_percentage);
          max_abs_error = std::max(36, max_abs_error);
          avg_error = std::max(36.0f, avg_error);
          break;
      }
    }

    auto comparator =
        FuzzyPixelComparator()
            .SetErrorPixelsPercentageLimit(error_pixels_percentage)
            .SetAvgAbsErrorLimit(avg_error)
            .SetAbsErrorLimit(max_abs_error);
    ExpectEquals(actual, expected, comparator);
  }

  // Generates the expected image to compare against. This will draw on the GPU
  // thread and waits the current thread until results are ready.
  SkBitmap GetExpected(const gfx::Size& image_size) {
    SkBitmap bitmap;
    base::WaitableEvent waitable;
    auto* gpu_service = viz::TestGpuServiceHolder::GetInstance();

    // Draw the expected image to a GPU accelerated SkCanvas. This must be done
    // from the GPU thread so wait until that is done here.
    gpu_service->gpu_main_thread_task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&OopTextBlobPixelTest::DrawExpectedOnGpuThread,
                       base::Unretained(this), image_size, std::ref(bitmap),
                       std::ref(waitable)));
    waitable.Wait();

    DCHECK(!bitmap.drawsNothing());
    return bitmap;
  }

  void DrawExpectedOnGpuThread(const gfx::Size& image_size,
                               SkBitmap& expected,
                               base::WaitableEvent& waitable) {
    auto* gpu_service = viz::TestGpuServiceHolder::GetInstance()->gpu_service();

    // Must make context current before drawing.
    auto context_state = gpu_service->GetContextState();
    ASSERT_TRUE(context_state->MakeCurrent(nullptr));

    gpu::raster::GrShaderCache::ScopedCacheUse cache_use(
        gpu_service->gr_shader_cache(), gpu::kDisplayCompositorClientId);

    // Setup a GPU accelerated SkSurface to draw to.
    SkImageInfo image_info =
        SkImageInfo::MakeN32Premul(image_size.width(), image_size.height());
    SkSurfaceProps surface_props =
        UseLcdText() ? skia::LegacyDisplayGlobals::GetSkSurfaceProps(0)
                     : SkSurfaceProps(0, kUnknown_SkPixelGeometry);
    sk_sp<SkSurface> surface = nullptr;
    if (context_state->gr_context()) {
      surface = SkSurfaces::RenderTarget(
          context_state->gr_context(), skgpu::Budgeted::kNo, image_info, 0,
          kTopLeft_GrSurfaceOrigin, &surface_props);
    } else if (context_state->graphite_shared_context()) {
      surface = SkSurfaces::RenderTarget(
          context_state->gpu_main_graphite_recorder(), image_info,
          skgpu::Mipmapped::kNo, &surface_props);
    } else {
      NOTREACHED();
    }

    SkCanvas* canvas = surface->getCanvas();
    canvas->clear(SkColors::kBlack);
    DrawExpectedToCanvas(*canvas);

    // Readback the expected image into `expected`.
    expected.allocPixels(image_info);
    bool success = false;
    if (context_state->gr_context()) {
      context_state->gr_context()->flushAndSubmit(surface.get(),
                                                  GrSyncCpu::kNo);
      success = surface->readPixels(expected, 0, 0);
    } else if (context_state->graphite_shared_context()) {
      success = gpu::GraphiteReadPixelsSync(
          context_state->graphite_shared_context(),
          context_state->gpu_main_graphite_recorder(), surface.get(),
          image_info, expected.pixmap().writable_addr(),
          expected.pixmap().rowBytes(), 0, 0);
    }

    ASSERT_TRUE(success);
    waitable.Signal();
  }

  // Draws the expected image to SkCanvas directly.
  void DrawExpectedToCanvas(SkCanvas& canvas) {
    TextBlobTestConfig config = GetParam();

    // Set matrix before any image filter is applied, which may force the
    // matrix to be decomposed into a transform compatible with the filter.
    canvas.setMatrix(GetMatrix());

    TextBlobStrategy strategy = GetTextBlobStrategy(config);

    sk_sp<SkImageFilter> filter;
    if (GetFilterStrategy(config) != FilterStrategy::kNone) {
      filter =
          SkImageFilters::Blur(.1f, .1f, SkTileMode::kDecal, nullptr, nullptr);
    }

    const bool save_layer =
        GetFilterStrategy(config) == FilterStrategy::kSaveLayer;

    SkPaint save_paint;
    if (save_layer) {
      save_paint.setImageFilter(std::move(filter));
      filter = nullptr;
      canvas.saveLayer(nullptr, &save_paint);
    }

    SkPaint text_paint;
    text_paint.setColor(SkColors::kGreen);
    if (filter && (strategy == TextBlobStrategy::kDirect ||
                   strategy == TextBlobStrategy::kDrawRecord)) {
      text_paint.setImageFilter(std::move(filter));
      filter = nullptr;
    }

    auto text_blob = BuildTextBlob(skia::DefaultTypeface(), UseLcdText());

    if (strategy == TextBlobStrategy::kDirect) {
      // Draw text directly to the SkSurface.
      canvas.drawTextBlob(std::move(text_blob), 0, kTextBlobY, text_paint);
    } else {
      // All other strategies draw text to a SkPicture.
      SkPictureRecorder recorder;
      SkCanvas* record_canvas =
          recorder.beginRecording(SkRect::MakeWH(100, 100));
      record_canvas->drawTextBlob(std::move(text_blob), 0, kTextBlobY,
                                  text_paint);
      sk_sp<SkPicture> recording = recorder.finishRecordingAsPicture();

      if (strategy == TextBlobStrategy::kDrawRecord) {
        // Draw recorded SkPicture to SkSurface.
        canvas.drawPicture(recording.get());
      } else if (strategy == TextBlobStrategy::kRecordShader) {
        // Convert SkPicture to a shader and then draw the shader to SkSurface.
        SkRect shader_rect = SkRect::MakeWH(25, 25);
        auto draw_as_shader =
            recording->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
                                  SkFilterMode::kLinear, nullptr, &shader_rect);
        SkPaint shader_paint;
        shader_paint.setShader(std::move(draw_as_shader));
        if (filter) {
          shader_paint.setImageFilter(std::move(filter));
          filter = nullptr;
        }
        canvas.drawRect(SkRect::MakeWH(50, 50), shader_paint);
      } else {
        // Convert SkPicture to an image filter and then draw the filter to
        // SkSurface.
        DCHECK_EQ(strategy, TextBlobStrategy::kRecordFilter);
        sk_sp<SkImageFilter> draw_as_filter =
            SkImageFilters::Picture(std::move(recording));
        if (filter) {
          draw_as_filter = SkImageFilters::Compose(std::move(filter),
                                                   std::move(draw_as_filter));
          filter = nullptr;
        }
        SkPaint filter_paint;
        filter_paint.setImageFilter(std::move(draw_as_filter));
        canvas.drawRect(SkRect::MakeWH(50, 50), filter_paint);
      }
    }

    if (save_layer)
      canvas.restore();
  }

  sk_sp<PaintFilter> MakeFilter() {
    if (GetFilterStrategy(GetParam()) == FilterStrategy::kNone) {
      return nullptr;
    } else {
      // Keep the blur sigmas small to reduce test duration, it's the presence
      // of the blur filter that triggers the code path changes we care about.
      return sk_make_sp<BlurPaintFilter>(.1f, .1f, SkTileMode::kDecal, nullptr);
    }
  }

  SkM44 GetMatrix() {
    MatrixStrategy strategy = GetMatrixStrategy(GetParam());

    SkM44 m;  // Default constructed to identity
    if (strategy != MatrixStrategy::kIdentity) {
      // Scaled, Complex, and Perspective all have a 2x scale factor
      m.preScale(2.0f, 2.0f);
      if (strategy == MatrixStrategy::kComplex) {
        SkM44 skew = SkM44();
        skew.setRC(0, 1, 2.f);
        skew.setRC(1, 0, 2.f);
        m.preConcat(skew);
      } else if (strategy == MatrixStrategy::kPerspective) {
        SkM44 persp = SkM44::Perspective(0.01f, 10.f, SK_ScalarPI / 3.f);
        persp.preTranslate(0.f, 5.f, -0.1f);
        persp.preConcat(SkM44::Rotate({0.f, 1.f, 0.f}, 0.008f /* radians */));
        m.postConcat(persp);
      }
    }

    return m;
  }

  void PushDrawOp(scoped_refptr<DisplayItemList> display_list,
                  sk_sp<PaintFilter> filter) {
    TextBlobStrategy strategy = GetTextBlobStrategy(GetParam());

    auto text_blob = BuildTextBlob(skia::DefaultTypeface(), UseLcdText());

    PaintFlags text_flags;
    text_flags.setStyle(PaintFlags::kFill_Style);
    text_flags.setColor(SkColors::kGreen);
    if (filter && (strategy == TextBlobStrategy::kDirect ||
                   strategy == TextBlobStrategy::kDrawRecord)) {
      // If there's a filter, the only PaintFlags that are available for these
      // two text-drawing strategies is 'text_flags'.
      text_flags.setImageFilter(std::move(filter));
      filter = nullptr;
    }
    if (strategy == TextBlobStrategy::kDirect) {
      display_list->push<DrawTextBlobOp>(std::move(text_blob), 0.0f, kTextBlobY,
                                         text_flags);
      return;
    }

    // All remaining strategies add the DrawTextBlobOp to an inner paint record.
    PaintOpBuffer paint_buffer;
    paint_buffer.push<DrawTextBlobOp>(std::move(text_blob), 0.0f, kTextBlobY,
                                      text_flags);
    if (strategy == TextBlobStrategy::kDrawRecord) {
      display_list->push<DrawRecordOp>(paint_buffer.ReleaseAsRecord());
      return;
    }

    PaintFlags record_flags;
    if (strategy == TextBlobStrategy::kRecordShader) {
      auto paint_record_shader = PaintShader::MakePaintRecord(
          paint_buffer.ReleaseAsRecord(), SkRect::MakeWH(25, 25),
          SkTileMode::kRepeat, SkTileMode::kRepeat, nullptr,
          PaintShader::ScalingBehavior::kRasterAtScale);
      // Force paint_flags to convert this to kFixedScale, so we can safely
      // compare pixels between direct and oop-r modes (since oop will convert
      // to kFixedScale no matter what.
      paint_record_shader->set_has_animated_images(true);

      record_flags.setShader(paint_record_shader);
      record_flags.setImageFilter(std::move(filter));
    } else {
      DCHECK(strategy == TextBlobStrategy::kRecordFilter);

      sk_sp<PaintFilter> paint_record_filter = sk_make_sp<RecordPaintFilter>(
          paint_buffer.ReleaseAsRecord(), SkRect::MakeWH(100, 100));
      // If there's an additional filter, we have to compose it with the
      // paint record filter.
      if (filter) {
        paint_record_filter = sk_make_sp<ComposePaintFilter>(
            std::move(filter), std::move(paint_record_filter));
      }
      record_flags.setImageFilter(std::move(paint_record_filter));
    }

    // Use bilerp sampling with the PaintRecord to help reduce max RGB error
    // from pixel-snapping flakiness when using NN sampling.
    record_flags.setFilterQuality(PaintFlags::FilterQuality::kLow);

    // The text blob is embedded in a paint record, which is attached to the
    // paint via a shader or image filter. Just draw a rect with the paint.
    display_list->push<DrawRectOp>(SkRect::MakeWH(50, 50), record_flags);
  }

  static TextBlobStrategy GetTextBlobStrategy(
      const TextBlobTestConfig& config) {
    return ::testing::get<0>(config);
  }
  static FilterStrategy GetFilterStrategy(const TextBlobTestConfig& config) {
    return ::testing::get<1>(config);
  }
  static MatrixStrategy GetMatrixStrategy(const TextBlobTestConfig& config) {
    return ::testing::get<2>(config);
  }
  static LCDStrategy GetLCDStrategy(const TextBlobTestConfig& config) {
    return ::testing::get<3>(config);
  }

  bool UseLcdText() const {
    return GetLCDStrategy(GetParam()) == LCDStrategy::kYes;
  }

  static std::string PrintTestName(
      const ::testing::TestParamInfo<TextBlobTestConfig>& info) {
    std::stringstream ss;
    switch (GetTextBlobStrategy(info.param)) {
      case TextBlobStrategy::kDirect:
        ss << "Direct";
        break;
      case TextBlobStrategy::kDrawRecord:
        ss << "DrawRecord";
        break;
      case TextBlobStrategy::kRecordShader:
        ss << "RecordShader";
        break;
      case TextBlobStrategy::kRecordFilter:
        ss << "RecordFilter";
        break;
    }
    ss << "_";
    switch (GetFilterStrategy(info.param)) {
      case FilterStrategy::kNone:
        ss << "NoFilter";
        break;
      case FilterStrategy::kPaintFlags:
        ss << "FilterOnPaint";
        break;
      case FilterStrategy::kSaveLayer:
        ss << "FilterOnLayer";
        break;
    }
    ss << "_";
    switch (GetMatrixStrategy(info.param)) {
      case MatrixStrategy::kIdentity:
        ss << "IdentityCTM";
        break;
      case MatrixStrategy::kScaled:
        ss << "ScaledCTM";
        break;
      case MatrixStrategy::kComplex:
        ss << "ComplexCTM";
        break;
      case MatrixStrategy::kPerspective:
        ss << "PerspectiveCTM";
        break;
    }
    ss << "_";
    switch (GetLCDStrategy(info.param)) {
      case LCDStrategy::kNo:
        ss << "NoLCD";
        break;
      case LCDStrategy::kYes:
        ss << "LCD";
        break;
    }

    return ss.str();
  }
};

TEST_P(OopTextBlobPixelTest, Config) {
  RunTest();
}

INSTANTIATE_TEST_SUITE_P(
    P,
    OopTextBlobPixelTest,
    ::testing::Combine(::testing::Values(TextBlobStrategy::kDirect,
                                         TextBlobStrategy::kDrawRecord,
                                         TextBlobStrategy::kRecordShader,
                                         TextBlobStrategy::kRecordFilter),
                       ::testing::Values(FilterStrategy::kNone,
                                         FilterStrategy::kPaintFlags,
                                         FilterStrategy::kSaveLayer),
                       ::testing::Values(MatrixStrategy::kIdentity,
                                         MatrixStrategy::kScaled,
                                         MatrixStrategy::kComplex,
                                         MatrixStrategy::kPerspective),
                       ::testing::Values(LCDStrategy::kNo, LCDStrategy::kYes)),
    OopTextBlobPixelTest::PrintTestName);

void ClearFontCache(CompletionEvent* event) {
  SkGraphics::PurgeFontCache();
  event->Signal();
}

TEST_F(OopPixelTest, DrawTextMultipleRasterCHROMIUM) {
  RasterOptions options;
  options.resource_size = gfx::Size(100, 100);
  options.content_size = options.resource_size;
  options.full_raster_rect = gfx::Rect(options.content_size);
  options.playback_rect = options.full_raster_rect;
  options.target_color_params.color_space = gfx::ColorSpace::CreateSRGB();

  auto sk_typeface_1 = skia::MakeTypefaceFromName("monospace", SkFontStyle());
  auto sk_typeface_2 = skia::MakeTypefaceFromName("roboto", SkFontStyle());

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  PaintFlags flags;
  flags.setStyle(PaintFlags::kFill_Style);
  flags.setColor(SkColors::kGreen);
  display_item_list->push<DrawTextBlobOp>(BuildTextBlob(sk_typeface_1), 0.0f,
                                          kTextBlobY, flags);
  display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list->Finalize();

  // Create another list with a different typeface.
  auto display_item_list_2 = base::MakeRefCounted<DisplayItemList>();
  display_item_list_2->StartPaint();
  display_item_list_2->push<DrawTextBlobOp>(BuildTextBlob(sk_typeface_2), 0.0f,
                                            kTextBlobY, flags);
  display_item_list_2->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list_2->Finalize();

  // Raster both these lists with 2 RasterCHROMIUM commands between a single
  // Begin/EndRaster sequence.
  options.additional_lists = {display_item_list_2};
  Raster(display_item_list, options);

  // Clear skia's font cache. No entries should remain since the service
  // should unpin everything.
  EXPECT_GT(SkGraphics::GetFontCacheUsed(), 0u);
  CompletionEvent event;
  raster_context_provider_->ExecuteOnGpuThread(
      base::BindOnce(&ClearFontCache, &event));
  event.Wait();
  EXPECT_EQ(SkGraphics::GetFontCacheUsed(), 0u);
}

TEST_F(OopPixelTest, DrawTextBlobPersistentShaderCache) {
  RasterOptions options;
  options.resource_size = gfx::Size(100, 100);
  options.content_size = options.resource_size;
  options.full_raster_rect = gfx::Rect(options.content_size);
  options.playback_rect = options.full_raster_rect;
  options.target_color_params.color_space = gfx::ColorSpace::CreateSRGB();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  PaintFlags flags;
  flags.setStyle(PaintFlags::kFill_Style);
  flags.setColor(SkColors::kGreen);
  display_item_list->push<DrawTextBlobOp>(BuildTextBlob(), 0.0f, kTextBlobY,
                                          flags);
  display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, options);

  // Perform the same operations on a software SkCanvas to produce an expected
  // bitmap.
  SkBitmap expected =
      MakeSolidColorBitmap(options.resource_size, SkColors::kBlack);
  SkCanvas canvas(expected, SkSurfaceProps{});
  canvas.drawColor(SkColors::kBlack);
  SkPaint paint;
  paint.setColor(SkColors::kGreen);
  canvas.drawTextBlob(BuildTextBlob(), 0, kTextBlobY, paint);

  // Allow 1% of pixels to be off by 1 due to differences between software and
  // GPU canvas.
  auto comparator = FuzzyPixelComparator()
                        .SetErrorPixelsPercentageLimit(1.0f)
                        .SetAbsErrorLimit(1);

  ExpectEquals(actual, expected, comparator);

  // Re-create the context so we start with an uninitialized skia memory cache
  // and use shaders from the persistent cache.
  InitializeOOPContext();
  actual = Raster(display_item_list, options);
  ExpectEquals(actual, expected, comparator);
}

TEST_F(OopPixelTest, WritePixels) {
  gfx::Size dest_size(10, 10);
  RasterOptions options(dest_size);
  auto* ri = raster_context_provider_->RasterInterface();
  auto* sii = raster_context_provider_->SharedImageInterface();
  auto dest_client_si = CreateClientSharedImage(
      ri, sii, options, viz::SinglePlaneFormat::kRGBA_8888);
  std::vector<SkPMColor> expected_pixels(dest_size.width() * dest_size.height(),
                                         SkPreMultiplyARGB(255, 0, 0, 255));
  SkBitmap expected;
  expected.installPixels(
      SkImageInfo::MakeN32Premul(dest_size.width(), dest_size.height()),
      expected_pixels.data(), dest_size.width() * sizeof(SkColor));

  gpu::SyncToken sync_token =
      UploadPixels(ri, dest_client_si, expected.info(), expected);

  auto ri_access =
      dest_client_si->BeginRasterAccess(ri, sync_token, /*readonly=*/true);
  SkBitmap actual =
      ReadbackMailbox(ri, dest_client_si->mailbox(), options.resource_size);
  sync_token = gpu::RasterScopedAccess::EndAccess(std::move(ri_access));

  sii->DestroySharedImage(sync_token, std::move(dest_client_si));
  ExpectEquals(actual, expected);
}

TEST_F(OopPixelTest, CopySharedImage) {
  const gfx::Size size(16, 16);
  auto* ri = raster_context_provider_->RasterInterface();
  auto* sii = raster_context_provider_->SharedImageInterface();
  const gfx::ColorSpace source_color_space = gfx::ColorSpace::CreateSRGB();
  const gfx::ColorSpace dest_color_space =
      gfx::ColorSpace::CreateDisplayP3D65();

  // Create data to upload in sRGB (solid green).
  SkBitmap upload_bitmap;
  {
    upload_bitmap.allocPixels(SkImageInfo::MakeN32Premul(
        size.width(), size.height(), source_color_space.ToSkColorSpace()));
    SkCanvas canvas(upload_bitmap, SkSurfaceProps{});
    SkPaint paint;
    paint.setColor(SkColors::kGreen);
    canvas.drawRect(SkRect::MakeWH(size.width(), size.height()), paint);
  }

  // Create an sRGB SharedImage and upload to it.
  scoped_refptr<gpu::ClientSharedImage> source_client_si;
  {
    RasterOptions options(size);
    options.target_color_params.color_space = source_color_space;
    source_client_si = CreateClientSharedImage(
        ri, sii, options, viz::SinglePlaneFormat::kRGBA_8888);
    auto ri_access = source_client_si->BeginRasterAccess(
        ri, source_client_si->creation_sync_token(), /*readonly=*/false);

    ri->WritePixels(source_client_si->mailbox(), /*dst_x_offset=*/0,
                    /*dst_y_offset=*/0, GL_TEXTURE_2D, upload_bitmap.pixmap());
    gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
  }

  // Create a DisplayP3 SharedImage and copy to it.
  scoped_refptr<gpu::ClientSharedImage> dest_client_si;
  {
    RasterOptions options(size);
    options.target_color_params.color_space = dest_color_space;
    dest_client_si = CreateClientSharedImage(
        ri, sii, options, viz::SinglePlaneFormat::kRGBA_8888);
    auto src_ri_access = source_client_si->BeginRasterAccess(
        ri, source_client_si->creation_sync_token(), /*readonly=*/true);
    auto dest_ri_access = dest_client_si->BeginRasterAccess(
        ri, dest_client_si->creation_sync_token(), /*readonly=*/false);

    ri->CopySharedImage(source_client_si->mailbox(), dest_client_si->mailbox(),
                        0, 0, 0, 0, size.width(), size.height());
    gpu::RasterScopedAccess::EndAccess(std::move(dest_ri_access));
    gpu::RasterScopedAccess::EndAccess(std::move(src_ri_access));
  }

  // Read the data back as DisplayP3, from the Display P3 SharedImage.
  SkBitmap readback_bitmap;
  {
    readback_bitmap.allocPixels(SkImageInfo::MakeN32Premul(
        size.width(), size.height(), dest_color_space.ToSkColorSpace()));

    ri->ReadbackImagePixels(dest_client_si->mailbox(), readback_bitmap.info(),
                            readback_bitmap.rowBytes(), 0, 0,
                            /*plane_index=*/0, readback_bitmap.getPixels());
  }

  // The pixel value should be unchanged, even though the source and dest are
  // in different color spaces. No color conversion (which would change the
  // pixel value) should have happened.
  EXPECT_EQ(*upload_bitmap.getAddr32(0, 0), *readback_bitmap.getAddr32(0, 0));
}

// The Android emulator does not support RED_8 or RG_88 texture formats.
#if !BUILDFLAG(IS_ANDROID_EMULATOR)
class OopYUVToRGBPixelTest
    : public OopPixelTest,
      public ::testing::WithParamInterface<gfx::ColorSpace> {
 public:
  gfx::ColorSpace DestinationColorSpace() const { return GetParam(); }
};

TEST_P(OopYUVToRGBPixelTest, CopyI420SharedImage) {
  // The output SharedImage color space.
  const gfx::ColorSpace dest_color_space = DestinationColorSpace();

  RasterOptions options(gfx::Size(16, 16));
  RasterOptions uv_options(gfx::Size(options.resource_size.width() / 2,
                                     options.resource_size.height() / 2));
  auto* ri = raster_context_provider_->RasterInterface();
  auto* sii = raster_context_provider_->SharedImageInterface();

  auto dest_client_si = CreateClientSharedImage(
      ri, sii, options, viz::SinglePlaneFormat::kRGBA_8888, dest_color_space);

  scoped_refptr<gpu::ClientSharedImage> yuv_client_si =
      CreateClientSharedImage(ri, sii, options, viz::MultiPlaneFormat::kI420,
                              gfx::ColorSpace::CreateREC709());

  SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes] = {};

  SkImageInfo y_info = SkImageInfo::Make(
      options.resource_size.width(), options.resource_size.height(),
      kGray_8_SkColorType, kPremul_SkAlphaType,
      options.target_color_params.color_space.ToSkColorSpace());

  SkImageInfo uv_info = SkImageInfo::Make(
      uv_options.resource_size.width(), uv_options.resource_size.height(),
      kGray_8_SkColorType, kPremul_SkAlphaType,
      uv_options.target_color_params.color_space.ToSkColorSpace());

  // Create Y+U+V image planes for a solid blue image.
  SkBitmap y_bitmap;
  y_bitmap.allocPixels(y_info);
  memset(y_bitmap.getPixels(), 0x1d, y_bitmap.computeByteSize());
  pixmaps[0] = SkPixmap(y_info, y_bitmap.getPixels(), y_info.minRowBytes());

  SkBitmap u_bitmap;
  u_bitmap.allocPixels(uv_info);
  memset(u_bitmap.getPixels(), 0xff, u_bitmap.computeByteSize());
  pixmaps[1] = SkPixmap(uv_info, u_bitmap.getPixels(), uv_info.minRowBytes());

  SkBitmap v_bitmap;
  v_bitmap.allocPixels(uv_info);
  memset(v_bitmap.getPixels(), 0x6b, v_bitmap.computeByteSize());
  pixmaps[2] = SkPixmap(uv_info, v_bitmap.getPixels(), uv_info.minRowBytes());

  SkYUVColorSpace yuv_color_space = kIdentity_SkYUVColorSpace;
  SkYUVAInfo yuv_info = SkYUVAInfo(
      SizeToSkISize(options.resource_size), SkYUVAInfo::PlaneConfig::kY_U_V,
      SkYUVAInfo::Subsampling::k420, yuv_color_space);
  SkYUVAPixmaps yuv_pixmap =
      SkYUVAPixmaps::FromExternalPixmaps(yuv_info, pixmaps);

  // Upload initial Y+U+V planes and convert to RGB.
  UploadPixelsYUV(ri, yuv_client_si, yuv_pixmap);
  auto src_ri_access = yuv_client_si->BeginRasterAccess(
      ri, yuv_client_si->creation_sync_token(), /*readonly=*/true);
  auto dest_ri_access = dest_client_si->BeginRasterAccess(
      ri, dest_client_si->creation_sync_token(), /*readonly=*/false);
  ri->CopySharedImage(yuv_client_si->mailbox(), dest_client_si->mailbox(), 0, 0,
                      0, 0, options.resource_size.width(),
                      options.resource_size.height());

  SkBitmap actual_bitmap =
      ReadbackMailbox(ri, dest_client_si->mailbox(), options.resource_size,
                      dest_color_space.ToSkColorSpace());

  SkColor expected_color = SkColorSetARGB(255, 0, 0, 254);
  SkBitmap expected_bitmap = MakeSolidColorBitmap(
      options.resource_size, SkColor4f::FromColor(expected_color));

  // Allow slight rounding error on all pixels.
  auto comparator = FuzzyPixelComparator()
                        .SetErrorPixelsPercentageLimit(100.0f)
                        .SetAbsErrorLimit(2);
  ExpectEquals(actual_bitmap, expected_bitmap, comparator);

  gpu::RasterScopedAccess::EndAccess(std::move(src_ri_access));
  gpu::SyncToken sync_token =
      gpu::RasterScopedAccess::EndAccess(std::move(dest_ri_access));
  sii->DestroySharedImage(sync_token, std::move(dest_client_si));
  sii->DestroySharedImage(sync_token, std::move(yuv_client_si));
}

INSTANTIATE_TEST_SUITE_P(
    P,
    OopYUVToRGBPixelTest,
    ::testing::Values(gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
                                      gfx::ColorSpace::TransferID::SRGB),
                      gfx::ColorSpace()));

TEST_F(OopPixelTest, CopyNV12SharedImage) {
  RasterOptions options(gfx::Size(16, 16));
  RasterOptions uv_options(gfx::Size(options.resource_size.width() / 2,
                                     options.resource_size.height() / 2));
  auto* ri = raster_context_provider_->RasterInterface();
  auto* sii = raster_context_provider_->SharedImageInterface();

  scoped_refptr<gpu::ClientSharedImage> dest_client_si =
      CreateClientSharedImage(ri, sii, options,
                              viz::SinglePlaneFormat::kRGBA_8888);
  scoped_refptr<gpu::ClientSharedImage> y_uv_client_si =
      CreateClientSharedImage(ri, sii, options, viz::MultiPlaneFormat::kNV12,
                              gfx::ColorSpace::CreateJpeg());

  SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes] = {};

  SkImageInfo y_info = SkImageInfo::Make(
      options.resource_size.width(), options.resource_size.height(),
      kGray_8_SkColorType, kPremul_SkAlphaType,
      options.target_color_params.color_space.ToSkColorSpace());

  SkImageInfo uv_info = SkImageInfo::Make(
      uv_options.resource_size.width(), uv_options.resource_size.height(),
      kR8G8_unorm_SkColorType, kPremul_SkAlphaType,
      uv_options.target_color_params.color_space.ToSkColorSpace());

  // Create Y+UV image planes for a solid blue image.
  SkBitmap y_bitmap;
  y_bitmap.allocPixels(y_info);
  memset(y_bitmap.getPixels(), 0x1d, y_bitmap.computeByteSize());
  pixmaps[0] = SkPixmap(y_info, y_bitmap.getPixels(), y_info.minRowBytes());

  SkBitmap uv_bitmap;
  uv_bitmap.allocPixels(uv_info);
  uint8_t* uv_pix = static_cast<uint8_t*>(uv_bitmap.getPixels());
  for (size_t i = 0; i < uv_bitmap.computeByteSize(); i += 2) {
    uv_pix[i] = 0xff;
    uv_pix[i + 1] = 0x6d;
  }
  pixmaps[1] = SkPixmap(uv_info, uv_bitmap.getPixels(), uv_info.minRowBytes());

  SkYUVColorSpace yuv_color_space = kJPEG_SkYUVColorSpace;
  SkYUVAInfo yuv_info = SkYUVAInfo(
      SizeToSkISize(options.resource_size), SkYUVAInfo::PlaneConfig::kY_UV,
      SkYUVAInfo::Subsampling::k420, yuv_color_space);
  SkYUVAPixmaps yuv_pixmap =
      SkYUVAPixmaps::FromExternalPixmaps(yuv_info, pixmaps);

  // Upload initial Y+UV planes and convert to RGB.
  UploadPixelsYUV(ri, y_uv_client_si, yuv_pixmap);
  auto src_ri_access = y_uv_client_si->BeginRasterAccess(
      ri, y_uv_client_si->creation_sync_token(), /*readonly=*/true);
  auto dest_ri_access = dest_client_si->BeginRasterAccess(
      ri, dest_client_si->creation_sync_token(), /*readonly=*/false);

  ri->CopySharedImage(y_uv_client_si->mailbox(), dest_client_si->mailbox(), 0,
                      0, 0, 0, options.resource_size.width(),
                      options.resource_size.height());

  SkBitmap actual_bitmap =
      ReadbackMailbox(ri, dest_client_si->mailbox(), options.resource_size);

  SkBitmap expected_bitmap = MakeSolidColorBitmap(
      options.resource_size,
      SkColor4f::FromColor(SkColorSetARGB(255, 2, 0, 254)));

  ExpectEquals(actual_bitmap, expected_bitmap);

  gpu::RasterScopedAccess::EndAccess(std::move(src_ri_access));
  gpu::SyncToken sync_token =
      gpu::RasterScopedAccess::EndAccess(std::move(dest_ri_access));
  sii->DestroySharedImage(sync_token, std::move(dest_client_si));
  sii->DestroySharedImage(sync_token, std::move(y_uv_client_si));
}
#endif  // !BUILDFLAG(IS_ANDROID_EMULATOR)

class OopPathPixelTest : public OopPixelTest,
                         public ::testing::WithParamInterface<bool> {
 public:
  bool AllowInlining() const { return GetParam(); }
  void RunTest() {
    auto* ri = static_cast<gpu::raster::RasterImplementation*>(
        raster_context_provider_->RasterInterface());
    uint32_t max_inlined_entry_size =
        AllowInlining() ? std::numeric_limits<uint32_t>::max() : 0u;
    ri->set_max_inlined_entry_size_for_testing(max_inlined_entry_size);

    RasterOptions options;
    options.resource_size = gfx::Size(100, 100);
    options.content_size = options.resource_size;
    options.full_raster_rect = gfx::Rect(options.content_size);
    options.playback_rect = options.full_raster_rect;
    options.target_color_params.color_space = gfx::ColorSpace::CreateSRGB();

    auto display_item_list = base::MakeRefCounted<DisplayItemList>();
    display_item_list->StartPaint();
    display_item_list->push<DrawColorOp>(SkColors::kWhite, SkBlendMode::kSrc);
    PaintFlags flags;
    flags.setStyle(PaintFlags::kFill_Style);
    flags.setColor(SkColors::kGreen);
    display_item_list->push<DrawPathOp>(SkPath::Circle(20, 20, 10), flags);
    flags.setColor(SkColors::kBlue);
    display_item_list->push<DrawRectOp>(SkRect::MakeWH(10, 10), flags);
    display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
    display_item_list->Finalize();

    // Allow 8 pixels in 100x100 image to be different due to non-AA pixel
    // rounding.
    FuzzyPixelComparator comparator =
        FuzzyPixelComparator().SetErrorPixelsPercentageLimit(0.08f);

    // TODO(crbug.com/40280014): We have larger errors when running these
    // tests with Graphite, but the images here still seem visually
    // indistinguishable.
    auto* cmd = base::CommandLine::ForCurrentProcess();
    if (features::IsSkiaGraphiteEnabled(cmd)) {
      comparator.SetErrorPixelsPercentageLimit(0.5f);
    }

    auto actual = Raster(display_item_list, options);
    ExpectEquals(actual, FILE_PATH_LITERAL("oop_path.png"), comparator);
  }
};

TEST_P(OopPathPixelTest, Basic) {
  RunTest();
}

TEST_F(OopPixelTest, RecordShaderExceedsMaxTextureSize) {
  const int max_texture_size =
      raster_context_provider_->ContextCapabilities().max_texture_size;
  const SkRect rect = SkRect::MakeWH(max_texture_size + 10, 10);

  PaintOpBuffer shader_buffer;
  shader_buffer.push<DrawColorOp>(SkColors::kWhite, SkBlendMode::kSrc);
  PaintFlags flags;
  flags.setStyle(PaintFlags::kFill_Style);
  flags.setColor(SkColors::kGreen);
  shader_buffer.push<DrawRectOp>(rect, flags);
  auto shader = PaintShader::MakePaintRecord(shader_buffer.ReleaseAsRecord(),
                                             rect, SkTileMode::kRepeat,
                                             SkTileMode::kRepeat, nullptr);

  RasterOptions options;
  options.resource_size = gfx::Size(100, 100);
  options.content_size = gfx::Size(rect.width(), rect.height());
  options.full_raster_rect = gfx::Rect(options.content_size);
  options.playback_rect = options.full_raster_rect;
  options.target_color_params.color_space = gfx::ColorSpace::CreateSRGB();

  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawColorOp>(SkColors::kWhite, SkBlendMode::kSrc);
  flags.setShader(shader);
  display_item_list->push<DrawRectOp>(rect, flags);
  display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, options);
  ExpectEquals(actual,
               FILE_PATH_LITERAL("oop_record_shader_max_texture_size.png"));
}

INSTANTIATE_TEST_SUITE_P(P, OopClearPixelTest, ::testing::Bool());
INSTANTIATE_TEST_SUITE_P(P, OopPathPixelTest, ::testing::Bool());

TEST_F(OopPixelTest, SkSLCommandShader) {
  // Draws a red square.
  const std::string_view kDrawRedRect(R"(
    uniform float u_border_alpha;
    uniform float2 u_top_left;
    uniform float2 u_btm_right;
    uniform vec4 u_border_color;
    uniform vec4 u_center_color;
    uniform int u_nudge;

    vec4 main(float2 coord) {
      float2 adjusted = u_top_left;
      adjusted.x = u_top_left.x + float(u_nudge);
      if (all(greaterThanEqual(coord, adjusted)) &&
          all(lessThan(coord, u_btm_right))) {
        return u_center_color;
      } else {
        return vec4(u_border_color.xyz, u_border_alpha);
      }
    }
  )");
  auto shader = PaintShader::MakeSkSLCommand(
      kDrawRedRect,
      /*float_uniforms=*/{{.name = SkString("u_border_alpha"), .value = 0.5f}},
      /*float2_uniforms=*/
      {{.name = SkString("u_top_left"), .value = SkV2{23.f, 25.f}},
       {.name = SkString("u_btm_right"), .value = SkV2{75.f, 75.f}}},
      /*float4_uniforms=*/
      {{.name = SkString("u_border_color"),
        .value = SkColorToSkV4(SkColors::kRed)},
       {.name = SkString("u_center_color"),
        .value = SkColorToSkV4(SkColors::kGreen)}},
      /*int_uniforms=*/
      {{.name = SkString("u_nudge"), .value = 2}}, nullptr);
  ASSERT_TRUE(shader);

  const gfx::Size rect(100, 100);
  RasterOptions options(rect);
  options.preclear = true;
  options.preclear_color = SkColors::kWhite;
  PaintFlags flags;
  flags.setShader(std::move(shader));
  auto display_item_list = base::MakeRefCounted<DisplayItemList>();
  display_item_list->StartPaint();
  display_item_list->push<DrawRectOp>(gfx::RectToSkRect(gfx::Rect(rect)),
                                      flags);
  display_item_list->EndPaintOfUnpaired(options.full_raster_rect);
  display_item_list->Finalize();

  auto actual = Raster(display_item_list, options);

  SkBitmap expected = MakeSolidColorBitmap(rect, SkColors::kWhite);
  SkCanvas canvas(expected, SkSurfaceProps{});
  SkPaint red;
  red.setColor(SkColors::kRed);
  red.setAlphaf(0.5f);
  canvas.drawRect(SkRect::MakeXYWH(0, 0, 100, 100), red);
  SkPaint green;
  green.setColor(SkColors::kGreen);
  canvas.drawRect(SkRect::MakeXYWH(25, 25, 50, 50), green);

  EXPECT_TRUE(MatchesBitmap(actual, expected,
                            ManhattanDistancePixelComparator(/*tolerance=*/5)));
}

}  // namespace
}  // namespace cc