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

#include "gpu/command_buffer/service/copy_shared_image_helper.h"

#include <memory>
#include <vector>

#include "base/check.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/expected.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/config/gpu_finch_features.h"
#include "skia/ext/rgba_to_yuva.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPromiseImageTexture.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/gpu/GrBackendSemaphore.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"

namespace gpu {

using GLError = CopySharedImageHelper::GLError;

namespace {

SkColorType GetCompatibleSurfaceColorType(GrGLenum format) {
  switch (format) {
    case GL_RGBA8:
      return kRGBA_8888_SkColorType;
    case GL_RGB565:
      return kRGB_565_SkColorType;
    case GL_RGBA16F:
      return kRGBA_F16_SkColorType;
    case GL_RGB8:
      return kRGB_888x_SkColorType;
    case GL_RGB10_A2:
      return kRGBA_1010102_SkColorType;
    case GL_RGBA4:
      return kARGB_4444_SkColorType;
    case GL_SRGB8_ALPHA8:
      return kRGBA_8888_SkColorType;
    default:
      NOTREACHED();
      return kUnknown_SkColorType;
  }
}

GrGLenum GetSurfaceColorFormat(GrGLenum format, GrGLenum type) {
  if (format == GL_RGBA) {
    if (type == GL_UNSIGNED_BYTE) {
      return GL_RGBA8;
    }
    if (type == GL_UNSIGNED_SHORT_4_4_4_4) {
      return GL_RGBA4;
    }
  }
  if (format == GL_RGB) {
    if (type == GL_UNSIGNED_BYTE) {
      return GL_RGB8;
    }
    if (type == GL_UNSIGNED_SHORT_5_6_5) {
      return GL_RGB565;
    }
  }
  return format;
}

// Return true if all of `sk_yuv_color_space`, `sk_plane_config`,
// `sk_subsampling`, `rgba_image, `num_yuva_images`, and `yuva_images` were
// successfully populated. Return false on error. If this returns false, some
// of the output arguments may be left populated.
base::expected<void, GLError> ConvertYUVACommon(
    const char* function_name,
    GLenum yuv_color_space_in,
    GLenum plane_config_in,
    GLenum subsampling_in,
    const volatile GLbyte* mailboxes_in,
    SharedImageRepresentationFactory* representation_factory,
    SharedContextState* shared_context_state,
    SkYUVColorSpace& sk_yuv_color_space,
    SkYUVAInfo::PlaneConfig& sk_plane_config,
    SkYUVAInfo::Subsampling& sk_subsampling,
    std::unique_ptr<SkiaImageRepresentation>& rgba_image,
    int& num_yuva_planes,
    std::array<std::unique_ptr<SkiaImageRepresentation>,
               SkYUVAInfo::kMaxPlanes>& yuva_images) {
  if (yuv_color_space_in < 0 ||
      yuv_color_space_in > kLastEnum_SkYUVColorSpace) {
    return base::unexpected(
        GLError(GL_INVALID_ENUM, function_name,
                "yuv_color_space must be a valid SkYUVColorSpace"));
  }
  sk_yuv_color_space = static_cast<SkYUVColorSpace>(yuv_color_space_in);
  if (plane_config_in < 0 ||
      plane_config_in > static_cast<GLenum>(SkYUVAInfo::PlaneConfig::kLast)) {
    return base::unexpected(
        GLError(GL_INVALID_ENUM, function_name,
                "plane_config must be a valid SkYUVAInfo::PlaneConfig"));
  }
  sk_plane_config = static_cast<SkYUVAInfo::PlaneConfig>(plane_config_in);
  if (subsampling_in < 0 ||
      subsampling_in > static_cast<GLenum>(SkYUVAInfo::Subsampling::kLast)) {
    return base::unexpected(
        GLError(GL_INVALID_ENUM, function_name,
                "subsampling must be a valid SkYUVAInfo::Subsampling"));
  }
  sk_subsampling = static_cast<SkYUVAInfo::Subsampling>(subsampling_in);

  std::array<gpu::Mailbox, SkYUVAInfo::kMaxPlanes> yuva_mailboxes;
  num_yuva_planes = SkYUVAInfo::NumPlanes(sk_plane_config);
  for (int i = 0; i < num_yuva_planes; ++i) {
    yuva_mailboxes[i] = Mailbox::FromVolatile(
        reinterpret_cast<const volatile Mailbox*>(mailboxes_in)[i]);
    DLOG_IF(ERROR, !yuva_mailboxes[i].Verify())
        << function_name
        << " was passed an invalid mailbox for YUVA plane: " << i
        << " with plane config " << plane_config_in;
  }
  gpu::Mailbox rgba_mailbox;
  rgba_mailbox =
      Mailbox::FromVolatile(reinterpret_cast<const volatile Mailbox*>(
          mailboxes_in)[SkYUVAInfo::kMaxPlanes]);
  DLOG_IF(ERROR, !rgba_mailbox.Verify())
      << function_name << " was passed an invalid mailbox for RGBA";

  for (int i = 0; i < num_yuva_planes; ++i) {
    yuva_images[i] = representation_factory->ProduceSkia(yuva_mailboxes[i],
                                                         shared_context_state);
    if (!yuva_images[i]) {
      std::string msg =
          "Attempting to operate on unknown mailbox for plane index " +
          base::NumberToString(i) + " using plane config " +
          base::NumberToString(plane_config_in) + ".";
      return base::unexpected(
          GLError(GL_INVALID_OPERATION, function_name, msg));
    }
  }
  rgba_image =
      representation_factory->ProduceSkia(rgba_mailbox, shared_context_state);
  if (!rgba_image) {
    return base::unexpected(
        GLError(GL_INVALID_OPERATION, "ConvertYUVAMailboxesToRGB",
                "Attempting to operate on unknown dest mailbox."));
  }
  return base::ok();
}

void FlushSurface(SkiaImageRepresentation::ScopedWriteAccess* access) {
  int num_planes = access->representation()->format().NumberOfPlanes();
  for (int plane_index = 0; plane_index < num_planes; plane_index++) {
    auto* surface = access->surface(plane_index);
    DCHECK(surface);
    surface->flush();
  }
  access->ApplyBackendSurfaceEndState();
}

void SubmitIfNecessary(std::vector<GrBackendSemaphore> signal_semaphores,
                       SharedContextState* context,
                       bool is_drdc_enabled) {
  // Note that when DrDc is enabled, we need to call
  // AddVulkanCleanupTaskForSkiaFlush() on gpu main thread and do skia flush.
  // This will ensure that vulkan memory allocated on gpu main thread will be
  // cleaned up.
  if (!signal_semaphores.empty() || is_drdc_enabled) {
    GrFlushInfo flush_info = {
        .fNumSemaphores = signal_semaphores.size(),
        .fSignalSemaphores = signal_semaphores.data(),
    };
    gpu::AddVulkanCleanupTaskForSkiaFlush(context->vk_context_provider(),
                                          &flush_info);

    auto result = context->gr_context()->flush(flush_info);
    DCHECK_EQ(result, GrSemaphoresSubmitted::kYes);
  }

  bool sync_cpu =
      gpu::ShouldVulkanSyncCpuForSkiaSubmit(context->vk_context_provider());

  // If DrDc is enabled, submit the gr_context() to ensure correct ordering
  // of vulkan commands between raster and display compositor.
  // TODO(vikassoni): This submit could be happening more often than
  // intended resulting in perf penalty. Explore ways to reduce it by
  // trying to issue submit only once per draw call for both gpu main and
  // drdc thread gr_context. Also add metric to see how often submits are
  // happening per frame.
  const bool need_submit =
      sync_cpu || !signal_semaphores.empty() || is_drdc_enabled;

  if (need_submit) {
    context->gr_context()->submit(sync_cpu);
  }
}

sk_sp<SkColorSpace> ReadSkColorSpace(const volatile GLbyte* bytes) {
  size_t offset = 0;
  const volatile skcms_TransferFunction* transfer =
      reinterpret_cast<const volatile skcms_TransferFunction*>(bytes + offset);
  offset += sizeof(skcms_TransferFunction);
  const volatile skcms_Matrix3x3* primaries =
      reinterpret_cast<const volatile skcms_Matrix3x3*>(bytes + offset);
  return SkColorSpace::MakeRGB(
      const_cast<const skcms_TransferFunction&>(*transfer),
      const_cast<const skcms_Matrix3x3&>(*primaries));
}

bool TryCopySubTextureINTERNALMemory(
    GLint xoffset,
    GLint yoffset,
    GLint x,
    GLint y,
    GLsizei width,
    GLsizei height,
    gfx::Rect dest_cleared_rect,
    GLboolean unpack_flip_y,
    const Mailbox& source_mailbox,
    SkiaImageRepresentation* dest_shared_image,
    SkiaImageRepresentation::ScopedWriteAccess* dest_scoped_access,
    SharedImageRepresentationFactory* representation_factory,
    SharedContextState* shared_context_state,
    bool is_drdc_enabled,
    const std::vector<GrBackendSemaphore>& begin_semaphores,
    std::vector<GrBackendSemaphore>& end_semaphores) {
  if (unpack_flip_y) {
    return false;
  }

  auto source_shared_image =
      representation_factory->ProduceMemory(source_mailbox);
  if (!source_shared_image) {
    return false;
  }

  gfx::Size source_size = source_shared_image->size();
  gfx::Rect source_rect(x, y, width, height);
  if (!gfx::Rect(source_size).Contains(source_rect)) {
    return false;
  }

  auto scoped_read_access = source_shared_image->BeginScopedReadAccess();
  if (!scoped_read_access) {
    return false;
  }

  SkPixmap pm = scoped_read_access->pixmap();
  SkIRect skIRect = RectToSkIRect(source_rect);
  SkPixmap subset;
  if (!pm.extractSubset(&subset, skIRect)) {
    return false;
  }

  if (!begin_semaphores.empty()) {
    bool result = dest_scoped_access->surface()->wait(
        begin_semaphores.size(), begin_semaphores.data(),
        /*deleteSemaphoresAfterWait=*/false);
    DCHECK(result);
  }

  dest_scoped_access->surface()->writePixels(subset, xoffset, yoffset);

  FlushSurface(dest_scoped_access);
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state,
                    is_drdc_enabled);

  if (!dest_shared_image->IsCleared()) {
    dest_shared_image->SetClearedRect(dest_cleared_rect);
  }

  return true;
}

}  // namespace

CopySharedImageHelper::CopySharedImageHelper(
    SharedImageRepresentationFactory* representation_factory,
    SharedContextState* shared_context_state)
    : representation_factory_(representation_factory),
      shared_context_state_(shared_context_state),
      is_drdc_enabled_(
          features::IsDrDcEnabled() &&
          !shared_context_state->feature_info()->workarounds().disable_drdc) {}

CopySharedImageHelper::~CopySharedImageHelper() = default;

CopySharedImageHelper::GLError::GLError(GLenum gl_error,
                                        std::string function_name,
                                        std::string msg)
    : gl_error(gl_error),
      function_name(std::move(function_name)),
      msg(std::move(msg)) {}

base::expected<void, GLError> CopySharedImageHelper::ConvertRGBAToYUVAMailboxes(
    GLenum yuv_color_space,
    GLenum plane_config,
    GLenum subsampling,
    const volatile GLbyte* mailboxes_in) {
  SkYUVColorSpace dst_color_space;
  SkYUVAInfo::PlaneConfig dst_plane_config;
  SkYUVAInfo::Subsampling dst_subsampling;
  std::unique_ptr<SkiaImageRepresentation> rgba_image;
  int num_yuva_planes;
  std::array<std::unique_ptr<SkiaImageRepresentation>, SkYUVAInfo::kMaxPlanes>
      yuva_images;
  auto result = ConvertYUVACommon(
      "ConvertYUVAMailboxesToRGB", yuv_color_space, plane_config, subsampling,
      mailboxes_in, representation_factory_, shared_context_state_,
      dst_color_space, dst_plane_config, dst_subsampling, rgba_image,
      num_yuva_planes, yuva_images);
  if (!result.has_value()) {
    return result;
  }

  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;

  auto rgba_scoped_access =
      rgba_image->BeginScopedReadAccess(&begin_semaphores, &end_semaphores);
  if (!rgba_scoped_access) {
    DCHECK(begin_semaphores.empty());
    return base::unexpected(GLError(GL_INVALID_OPERATION,
                                    "glConvertYUVAMailboxesToRGB",
                                    "RGBA shared image is not readable"));
  }
  auto rgba_sk_image =
      rgba_scoped_access->CreateSkImage(shared_context_state_->gr_context());
  if (!rgba_sk_image) {
    return base::unexpected(GLError(GL_INVALID_OPERATION,
                                    "glReadbackImagePixels",
                                    "Couldn't create SkImage for reading."));
  }

  std::array<std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess>,
             SkYUVAInfo::kMaxPlanes>
      yuva_scoped_access;
  for (int i = 0; i < num_yuva_planes; ++i) {
    yuva_scoped_access[i] = yuva_images[i]->BeginScopedWriteAccess(
        &begin_semaphores, &end_semaphores,
        SharedImageRepresentation::AllowUnclearedAccess::kYes);
    if (!yuva_scoped_access[i]) {
      std::string msg =
          "Couldn't write shared image for mailbox of plane index " +
          base::NumberToString(i) + " using plane config " +
          base::NumberToString(plane_config) + ".";
      return base::unexpected(
          GLError(GL_INVALID_OPERATION, "glConvertRGBAToYUVAMailboxes", msg));
    }
  }
  SkSurface* yuva_sk_surfaces[SkYUVAInfo::kMaxPlanes];
  for (int i = 0; i < num_yuva_planes; ++i) {
    yuva_sk_surfaces[i] = yuva_scoped_access[i]->surface();
    if (!begin_semaphores.empty()) {
      bool ret = yuva_sk_surfaces[i]->wait(begin_semaphores.size(),
                                           begin_semaphores.data(),
                                           /*deleteSemaphoresAfterWait=*/false);
      DCHECK(ret);
    }
  }

  SkYUVAInfo yuva_info(rgba_sk_image->dimensions(), dst_plane_config,
                       dst_subsampling, dst_color_space);
  skia::BlitRGBAToYUVA(rgba_sk_image.get(), yuva_sk_surfaces, yuva_info);

  for (int i = 0; i < num_yuva_planes; ++i) {
    FlushSurface(yuva_scoped_access[i].get());
    if (!yuva_images[i]->IsCleared()) {
      yuva_images[i]->SetCleared();
    }
  }
  rgba_scoped_access->ApplyBackendSurfaceEndState();
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                    is_drdc_enabled_);
  return base::ok();
}

base::expected<void, GLError> CopySharedImageHelper::ConvertYUVAMailboxesToRGB(
    GLenum planes_yuv_color_space,
    GLenum plane_config,
    GLenum subsampling,
    const volatile GLbyte* bytes_in) {
  SkYUVColorSpace src_yuv_color_space;
  SkYUVAInfo::PlaneConfig src_plane_config;
  SkYUVAInfo::Subsampling src_subsampling;
  std::unique_ptr<SkiaImageRepresentation> rgba_image;
  int num_src_planes;
  std::array<std::unique_ptr<SkiaImageRepresentation>, SkYUVAInfo::kMaxPlanes>
      yuva_images;
  auto result = ConvertYUVACommon(
      "ConvertYUVAMailboxesToRGB", planes_yuv_color_space, plane_config,
      subsampling, bytes_in, representation_factory_, shared_context_state_,
      src_yuv_color_space, src_plane_config, src_subsampling, rgba_image,
      num_src_planes, yuva_images);
  if (!result.has_value()) {
    return result;
  }

  sk_sp<SkColorSpace> src_rgb_color_space = ReadSkColorSpace(
      bytes_in + (SkYUVAInfo::kMaxPlanes + 1) * sizeof(gpu::Mailbox));

  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;

  auto dest_scoped_access = rgba_image->BeginScopedWriteAccess(
      &begin_semaphores, &end_semaphores,
      SharedImageRepresentation::AllowUnclearedAccess::kYes);
  if (!dest_scoped_access) {
    DCHECK(begin_semaphores.empty());
    return base::unexpected(
        GLError(GL_INVALID_VALUE, "glConvertYUVAMailboxesToRGB",
                "Destination shared image is not writable"));
  }

  bool source_access_valid = true;
  std::array<std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>,
             SkYUVAInfo::kMaxPlanes>
      source_scoped_access;
  for (int i = 0; i < num_src_planes; ++i) {
    source_scoped_access[i] = yuva_images[i]->BeginScopedReadAccess(
        &begin_semaphores, &end_semaphores);

    if (!source_scoped_access[i]) {
      source_access_valid = false;
      std::string msg =
          "Couldn't access shared image for mailbox of plane index " +
          base::NumberToString(i) + " using plane config " +
          base::NumberToString(plane_config) + ".";
      result = base::unexpected(
          GLError(GL_INVALID_OPERATION, "glConvertYUVAMailboxesToRGB", msg));
      break;
    }
  }

  auto* dest_surface = dest_scoped_access->surface();
  if (!begin_semaphores.empty()) {
    bool ret =
        dest_surface->wait(begin_semaphores.size(), begin_semaphores.data(),
                           /*deleteSemaphoresAfterWait=*/false);
    DCHECK(ret);
  }

  bool drew_image = false;
  if (source_access_valid) {
    std::array<GrBackendTexture, SkYUVAInfo::kMaxPlanes> yuva_textures;
    for (int i = 0; i < num_src_planes; ++i) {
      yuva_textures[i] =
          source_scoped_access[i]->promise_image_texture()->backendTexture();
    }

    // Disable color space conversion if no source color space was specified.
    if (!src_rgb_color_space) {
      if (auto dest_color_space = dest_surface->imageInfo().refColorSpace()) {
        src_rgb_color_space = std::move(dest_color_space);
      }
    }

    SkISize dest_size =
        SkISize::Make(dest_surface->width(), dest_surface->height());
    SkYUVAInfo yuva_info(dest_size, src_plane_config, src_subsampling,
                         src_yuv_color_space);
    GrYUVABackendTextures yuva_backend_textures(yuva_info, yuva_textures.data(),
                                                kTopLeft_GrSurfaceOrigin);
    auto result_image = SkImages::TextureFromYUVATextures(
        shared_context_state_->gr_context(), yuva_backend_textures,
        src_rgb_color_space);
    if (!result_image) {
      result = base::unexpected(
          GLError(GL_INVALID_OPERATION, "glConvertYUVAMailboxesToRGB",
                  "Couldn't create destination images from provided sources"));
    } else {
      SkPaint paint;
      paint.setBlendMode(SkBlendMode::kSrc);
      dest_surface->getCanvas()->drawImage(result_image, 0, 0,
                                           SkSamplingOptions(), &paint);
      drew_image = true;
    }
  }

  FlushSurface(dest_scoped_access.get());
  for (int i = 0; i < num_src_planes; ++i) {
    if (source_scoped_access[i]) {
      source_scoped_access[i]->ApplyBackendSurfaceEndState();
    }
  }
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                    is_drdc_enabled_);

  if (!rgba_image->IsCleared() && drew_image) {
    rgba_image->SetCleared();
  }

  return result;
}

base::expected<void, GLError> CopySharedImageHelper::CopySharedImage(
    GLint xoffset,
    GLint yoffset,
    GLint x,
    GLint y,
    GLsizei width,
    GLsizei height,
    GLboolean unpack_flip_y,
    const volatile GLbyte* mailboxes) {
  Mailbox source_mailbox = Mailbox::FromVolatile(
      reinterpret_cast<const volatile Mailbox*>(mailboxes)[0]);
  DLOG_IF(ERROR, !source_mailbox.Verify())
      << "CopySubTexture was passed an invalid mailbox";
  Mailbox dest_mailbox = Mailbox::FromVolatile(
      reinterpret_cast<const volatile Mailbox*>(mailboxes)[1]);
  DLOG_IF(ERROR, !dest_mailbox.Verify())
      << "CopySubTexture was passed an invalid mailbox";

  if (source_mailbox == dest_mailbox) {
    return base::unexpected(
        GLError(GL_INVALID_OPERATION, "glCopySubTexture",
                "source and destination mailboxes are the same"));
  }

  auto dest_shared_image = representation_factory_->ProduceSkia(
      dest_mailbox,
      scoped_refptr<gpu::SharedContextState>(shared_context_state_));
  if (!dest_shared_image) {
    return base::unexpected(
        GLError(GL_INVALID_VALUE, "glCopySubTexture", "unknown mailbox"));
  }

  gfx::Size dest_size = dest_shared_image->size();
  gfx::Rect dest_rect(xoffset, yoffset, width, height);
  if (!gfx::Rect(dest_size).Contains(dest_rect)) {
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "destination texture bad dimensions."));
  }

  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;

  // Allow uncleared access, as we manually handle clear tracking.
  std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess>
      dest_scoped_access = dest_shared_image->BeginScopedWriteAccess(
          &begin_semaphores, &end_semaphores,
          SharedImageRepresentation::AllowUnclearedAccess::kYes);
  if (!dest_scoped_access) {
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "Dest shared image is not writable"));
  }

  gfx::Rect new_cleared_rect;
  gfx::Rect old_cleared_rect = dest_shared_image->ClearedRect();
  if (!gles2::TextureManager::CombineAdjacentRects(old_cleared_rect, dest_rect,
                                                   &new_cleared_rect)) {
    // No users of RasterDecoder leverage this functionality. Clearing uncleared
    // regions could be added here if needed.
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "Cannot clear non-combineable rects."));
  }
  DCHECK(old_cleared_rect.IsEmpty() ||
         new_cleared_rect.Contains(old_cleared_rect));

  // Attempt to upload directly from CPU shared memory to destination texture.
  if (TryCopySubTextureINTERNALMemory(
          xoffset, yoffset, x, y, width, height, new_cleared_rect,
          unpack_flip_y, source_mailbox, dest_shared_image.get(),
          dest_scoped_access.get(), representation_factory_,
          shared_context_state_, is_drdc_enabled_, begin_semaphores,
          end_semaphores)) {
    return base::ok();
  }

  // Fall back to GPU->GPU copy if src image is not CPU-backed.
  auto source_shared_image = representation_factory_->ProduceSkia(
      source_mailbox,
      scoped_refptr<gpu::SharedContextState>(shared_context_state_));

  // In some cases (e.g android video that is promoted to overlay) we can't
  // create representation of the valid mailbox. To avoid problems with
  // uncleared destination later, we do clear destination rect with black
  // color.
  if (!source_shared_image) {
    auto* canvas = dest_scoped_access->surface()->getCanvas();

    SkAutoCanvasRestore autoRestore(canvas, /*doSave=*/true);
    canvas->clipRect(gfx::RectToSkRect(dest_rect));
    canvas->clear(SkColors::kBlack);

    if (!dest_shared_image->IsCleared()) {
      dest_shared_image->SetClearedRect(new_cleared_rect);
    }
    FlushSurface(dest_scoped_access.get());
    SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                      is_drdc_enabled_);

    // Note, that we still generate error for the client to indicate there was
    // problem.
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "unknown source image mailbox."));
  }

  gfx::Size source_size = source_shared_image->size();
  gfx::Rect source_rect(x, y, width, height);
  if (!gfx::Rect(source_size).Contains(source_rect)) {
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "source texture bad dimensions."));
  }

  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      source_scoped_access = source_shared_image->BeginScopedReadAccess(
          &begin_semaphores, &end_semaphores);
  if (!begin_semaphores.empty()) {
    bool ret = dest_scoped_access->surface()->wait(
        begin_semaphores.size(), begin_semaphores.data(),
        /*deleteSemaphoresAfterWait=*/false);
    DCHECK(ret);
  }
  if (!source_scoped_access) {
    // We still need to flush surface for begin semaphores above.
    FlushSurface(dest_scoped_access.get());
    SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                      is_drdc_enabled_);
    return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
                                    "Source shared image is not accessable"));
  }

  base::expected<void, GLError> result = base::ok();
  auto source_image =
      source_scoped_access->CreateSkImage(shared_context_state_->gr_context());
  if (!source_image) {
    result = base::unexpected(
        GLError(GL_INVALID_VALUE, "glCopySubTexture",
                "Couldn't create SkImage from source shared image."));
  } else {
    // Skia will flip the image if the surface origins do not match.
    DCHECK_EQ(unpack_flip_y, source_shared_image->surface_origin() !=
                                 dest_shared_image->surface_origin());
    auto dest_format = dest_shared_image->format();
    if (dest_format.is_single_plane()) {
      // Destination shared image cannot prefer external sampler.
      DCHECK(!dest_format.IsLegacyMultiplanar());

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

      // Reinterpret the source image as being in the destination color space,
      // to disable color conversion.
      auto source_image_reinterpreted = source_image;
      if (canvas->imageInfo().colorSpace()) {
        source_image_reinterpreted = source_image->reinterpretColorSpace(
            canvas->imageInfo().refColorSpace());
      }
      canvas->drawImageRect(source_image_reinterpreted,
                            gfx::RectToSkRect(source_rect),
                            gfx::RectToSkRect(dest_rect), SkSamplingOptions(),
                            &paint, SkCanvas::kStrict_SrcRectConstraint);
    } else {
      SkSurface* yuva_sk_surfaces[SkYUVAInfo::kMaxPlanes] = {};
      for (int plane_index = 0; plane_index < dest_format.NumberOfPlanes();
           plane_index++) {
        // Get surface per plane from destination scoped write access.
        yuva_sk_surfaces[plane_index] =
            dest_scoped_access->surface(plane_index);
      }

      // TODO(crbug.com/828599): This should really default to rec709.
      SkYUVColorSpace yuv_color_space = kRec601_SkYUVColorSpace;
      dest_shared_image->color_space().ToSkYUVColorSpace(
          dest_format.MultiplanarBitDepth(), &yuv_color_space);

      SkYUVAInfo yuva_info(gfx::SizeToSkISize(dest_shared_image->size()),
                           ToSkYUVAPlaneConfig(dest_format),
                           ToSkYUVASubsampling(dest_format), yuv_color_space);
      // Perform skia::BlitRGBAToYUVA for the multiplanar YUV format image.
      skia::BlitRGBAToYUVA(source_image.get(), yuva_sk_surfaces, yuva_info);
    }

    if (!dest_shared_image->IsCleared()) {
      dest_shared_image->SetClearedRect(new_cleared_rect);
    }
  }

  FlushSurface(dest_scoped_access.get());
  source_scoped_access->ApplyBackendSurfaceEndState();
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                    is_drdc_enabled_);
  return result;
}

base::expected<void, GLError> CopySharedImageHelper::CopySharedImageToGLTexture(
    GLuint dest_texture_id,
    GLenum target,
    GLuint internal_format,
    GLenum type,
    GLint src_x,
    GLint src_y,
    GLsizei width,
    GLsizei height,
    GLboolean flip_y,
    const volatile GLbyte* src_mailbox) {
  Mailbox source_mailbox = Mailbox::FromVolatile(
      reinterpret_cast<const volatile Mailbox*>(src_mailbox)[0]);
  DLOG_IF(ERROR, !source_mailbox.Verify())
      << "CopySharedImageToGLTexture was passed an invalid mailbox";

  CHECK_NE(dest_texture_id, 0u);
  CHECK(shared_context_state_->GrContextIsGL());
  GrGLTextureInfo texture_info;
  texture_info.fID = dest_texture_id;
  texture_info.fTarget = target;
  // Get the surface color format similar to that in VideoFrameYUVConverter.
  texture_info.fFormat = GetSurfaceColorFormat(internal_format, type);
  GrBackendTexture backend_texture(width, height, GrMipMapped::kNo,
                                   texture_info);

  auto dest_color_space = SkColorSpace::MakeSRGB();
  sk_sp<SkSurface> dest_surface = SkSurface::MakeFromBackendTexture(
      shared_context_state_->gr_context(), backend_texture,
      flip_y ? GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin
             : GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
      /*sampleCnt=*/1, GetCompatibleSurfaceColorType(texture_info.fFormat),
      dest_color_space, nullptr);
  if (!dest_surface) {
    return base::unexpected<GLError>(
        GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
                "Cannot create destination surface"));
  }

  // `dest_rect` always starts from (0, 0).
  SkRect dest_rect =
      SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
  auto source_shared_image = representation_factory_->ProduceSkia(
      source_mailbox,
      scoped_refptr<gpu::SharedContextState>(shared_context_state_));

  // In some cases (e.g android video that is promoted to overlay) we can't
  // create representation of the valid mailbox. To avoid problems with
  // uncleared destination later, we do clear destination rect with black
  // color.
  if (!source_shared_image) {
    auto* canvas = dest_surface->getCanvas();

    SkAutoCanvasRestore autoRestore(canvas, /*doSave=*/true);
    canvas->clipRect(dest_rect);
    canvas->clear(SkColors::kBlack);

    dest_surface->flush();
    SubmitIfNecessary({}, shared_context_state_, is_drdc_enabled_);

    // Note, that we still generate error for the client to indicate there was
    // problem.
    return base::unexpected<GLError>(GLError(GL_INVALID_VALUE,
                                             "glCopySharedImageToTexture",
                                             "unknown source image mailbox."));
  }

  gfx::Size source_size = source_shared_image->size();
  gfx::Rect source_rect(src_x, src_y, width, height);
  if (!gfx::Rect(source_size).Contains(source_rect)) {
    return base::unexpected<GLError>(GLError(GL_INVALID_VALUE,
                                             "glCopySharedImageToTexture",
                                             "source texture bad dimensions."));
  }

  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;
  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      source_scoped_access = source_shared_image->BeginScopedReadAccess(
          &begin_semaphores, &end_semaphores);
  if (!begin_semaphores.empty()) {
    bool ret =
        dest_surface->wait(begin_semaphores.size(), begin_semaphores.data(),
                           /*deleteSemaphoresAfterWait=*/false);
    DCHECK(ret);
  }
  if (!source_scoped_access) {
    // We still need to flush surface for begin semaphores above.
    dest_surface->flush();
    SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                      is_drdc_enabled_);

    return base::unexpected<GLError>(
        GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
                "Source shared image is not accessable"));
  }

  base::expected<void, GLError> result = base::ok();
  auto source_image =
      source_scoped_access->CreateSkImage(shared_context_state_->gr_context());
  if (!source_image) {
    result = base::unexpected<GLError>(
        GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
                "Couldn't create SkImage from source shared image."));
  } else {
    auto* canvas = dest_surface->getCanvas();
    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kSrc);

    // Reinterpret the source image as being in the destination color space,
    // to disable color conversion.
    auto source_image_reinterpreted = source_image;
    if (canvas->imageInfo().colorSpace()) {
      source_image_reinterpreted = source_image->reinterpretColorSpace(
          canvas->imageInfo().refColorSpace());
    }
    canvas->drawImageRect(
        source_image_reinterpreted, gfx::RectToSkRect(source_rect), dest_rect,
        SkSamplingOptions(), &paint, SkCanvas::kStrict_SrcRectConstraint);
  }

  dest_surface->flush();
  source_scoped_access->ApplyBackendSurfaceEndState();
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                    is_drdc_enabled_);
  return result;
}

base::expected<void, GLError> CopySharedImageHelper::ReadPixels(
    GLint src_x,
    GLint src_y,
    GLint plane_index,
    GLuint row_bytes,
    SkImageInfo dst_info,
    void* pixel_address,
    std::unique_ptr<SkiaImageRepresentation> source_shared_image) {
  std::vector<GrBackendSemaphore> begin_semaphores;
  std::vector<GrBackendSemaphore> end_semaphores;
  std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
      source_scoped_access = source_shared_image->BeginScopedReadAccess(
          &begin_semaphores, &end_semaphores);

  if (!source_scoped_access) {
    return base::unexpected(GLError(GL_INVALID_VALUE, "glReadbackImagePixels",
                                    "Source shared image is not accessible"));
  }

  if (!begin_semaphores.empty()) {
    bool wait_result = shared_context_state_->gr_context()->wait(
        begin_semaphores.size(), begin_semaphores.data(),
        /*deleteSemaphoresAfterWait=*/false);
    DCHECK(wait_result);
  }

  sk_sp<SkImage> sk_image;
  if (source_shared_image->format().is_single_plane()) {
    // Create SkImage without plane index for single planar formats or legacy
    // multiplanar formats with external sampler.
    sk_image = source_scoped_access->CreateSkImage(
        shared_context_state_->gr_context());
  } else {
    // Pass plane index for creating an SkImage for multiplanar formats.
    sk_image = source_scoped_access->CreateSkImageForPlane(
        plane_index, shared_context_state_->gr_context());
  }

  base::expected<void, GLError> result = base::ok();
  if (!sk_image) {
    result =
        base::unexpected(GLError(GL_INVALID_OPERATION, "glReadbackImagePixels",
                                 "Couldn't create SkImage for reading."));
  } else if (!sk_image->readPixels(dst_info, pixel_address, row_bytes, src_x,
                                   src_y)) {
    result =
        base::unexpected(GLError(GL_INVALID_OPERATION, "glReadbackImagePixels",
                                 "Failed to read pixels from SkImage"));
  }

  source_scoped_access->ApplyBackendSurfaceEndState();
  SubmitIfNecessary(std::move(end_semaphores), shared_context_state_,
                    is_drdc_enabled_);
  return result;
}

}  // namespace gpu