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

#include "components/viz/service/display_embedder/image_context_impl.h"

#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/graphite_shared_context.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_service_utils.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "skia/buildflags.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/ganesh/GrContextThreadSafeProxy.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/Recorder.h"
#include "third_party/skia/include/gpu/graphite/Surface.h"
#include "third_party/skia/include/gpu/graphite/dawn/DawnTypes.h"
#include "third_party/skia/include/private/chromium/GrPromiseImageTexture.h"

namespace {

// TODO(crbug.com/524822746): Killswitch for extra validation, remove after M152
// stable.
BASE_FEATURE(kValidatePromiseImageFormat, base::FEATURE_ENABLED_BY_DEFAULT);

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(CreateFallbackImageResult)
enum class CreateFallbackImageResult {
  kSuccess = 0,
  kFailedPrefersExternalSampler = 1,
  kFailedYcbcrMismatch = 2,
  kFailedYCbCrTextureCreationFailure = 3,
  kFailedInvalidTextureInfo = 4,
  kFailedCreateTexture = 5,
  kMaxValue = kFailedCreateTexture
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:CreateFallbackImageResult)

const char* CreateFallbackImageResultToString(
    CreateFallbackImageResult result) {
  switch (result) {
    case CreateFallbackImageResult::kSuccess:
      return "Success";
    case CreateFallbackImageResult::kFailedPrefersExternalSampler:
      return "FailedPrefersExternalSampler";
    case CreateFallbackImageResult::kFailedYcbcrMismatch:
      return "FailedYcbcrMismatch";
    case CreateFallbackImageResult::kFailedYCbCrTextureCreationFailure:
      return "FailedYCbCrTextureCreationFailure";
    case CreateFallbackImageResult::kFailedInvalidTextureInfo:
      return "FailedInvalidTextureInfo";
    case CreateFallbackImageResult::kFailedCreateTexture:
      return "FailedCreateTexture";
  }
}

#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(SKIA_USE_DAWN)
bool DawnYCbCrVkDescriptorsAreCompatible(const wgpu::YCbCrVkDescriptor& left,
                                         const wgpu::YCbCrVkDescriptor& right) {
  // NOTE: We deliberately do not compare the swizzle components as those
  // components are not plumbed through the Chrome-level information and thus
  // could cause spurious equality failures. By the Vulkan spec, those
  // components should not be set for external formats, but some drivers do not
  // adhere to the spec here.
  // Mismatch of model, range and chroma fields happens often enough to be
  // problematic if we skip drawing the video for those frames. While the video
  // may not draw 100% correctly it will still be better than not drawing it at
  // all.
  if (left.vkFormat != right.vkFormat) {
    return false;
  }
  if (left.forceExplicitReconstruction != right.forceExplicitReconstruction) {
    return false;
  }
  if (left.externalFormat != right.externalFormat) {
    return false;
  }
  return true;
}
#endif

SkColor4f GetFallbackColorForPlane(viz::SharedImageFormat format,
                                   int plane_index) {
  DCHECK(format.IsValidPlaneIndex(plane_index));
  // For DCHECKed builds return: 1) kRed for single planar formats; 2) kWhite
  // for all multiplanar format planes that translates to Purple/Magenta color
  // in RGB space. For non-DCHECKed builds return: 1) kWhite for single planar
  // formats; 2) kWhite for Y, A planes and kGray for U, V, UV planes that
  // translates to White color in RGB colorspace.
#if DCHECK_IS_ON()
  return format.is_single_plane() ? SkColors::kRed : SkColors::kWhite;
#else
  if (format.is_single_plane())
    return SkColors::kWhite;
  switch (format.plane_config()) {
    case viz::SharedImageFormat::PlaneConfig::kY_U_V:
    case viz::SharedImageFormat::PlaneConfig::kY_V_U:
      return plane_index == 0 ? SkColors::kWhite : SkColors::kGray;
    case viz::SharedImageFormat::PlaneConfig::kY_U_V_A:
      return (plane_index == 0 || plane_index == 3) ? SkColors::kWhite
                                                    : SkColors::kGray;
    case viz::SharedImageFormat::PlaneConfig::kY_UV:
      return plane_index == 0 ? SkColors::kWhite : SkColors::kGray;
    case viz::SharedImageFormat::PlaneConfig::kY_UV_A:
      return plane_index == 1 ? SkColors::kGray : SkColors::kWhite;
  }
#endif
}

}  // namespace

namespace viz {

ImageContextImpl::ImageContextImpl(const TransferableResource& resource,
                                   bool maybe_concurrent_reads,
                                   bool raw_draw_if_possible,
                                   uint32_t client_id)
    : ImageContext(resource),
      maybe_concurrent_reads_(maybe_concurrent_reads),
      raw_draw_if_possible_(raw_draw_if_possible) {}

ImageContextImpl::ImageContextImpl(const gpu::Mailbox& mailbox,
                                   const gfx::Size& size,
                                   SharedImageFormat format,
                                   const gfx::ColorSpace& color_space)
    : ImageContext(mailbox, size, format, color_space),
      is_for_render_pass_(true) {}

ImageContextImpl::~ImageContextImpl() {
  DeleteFallbackTextures();
}

void ImageContextImpl::OnContextLost() {
  if (texture_passthrough_) {
    texture_passthrough_->MarkContextLost();
    texture_passthrough_.reset();
  }

  if (representation_) {
    representation_->OnContextLost();
    representation_scoped_read_access_.reset();
    representation_.reset();
  }

  if (fallback_context_state_) {
    fallback_context_state_ = nullptr;
    fallback_textures_.clear();
    graphite_fallback_textures_.clear();
  }
}

void ImageContextImpl::SetPromiseImageTextures(
    std::vector<sk_sp<GrPromiseImageTexture>> promise_image_textures) {
  owned_promise_image_textures_ = std::move(promise_image_textures);
  promise_image_textures_.clear();
  for (auto& owned_texture : owned_promise_image_textures_) {
    promise_image_textures_.push_back(owned_texture.get());
  }
}

void ImageContextImpl::DeleteFallbackTextures() {
  if (fallback_context_state_) {
    if (fallback_context_state_->gr_context()) {
      CHECK(graphite_fallback_textures_.empty());
      for (auto& fallback_texture : fallback_textures_) {
        gpu::DeleteGrBackendTexture(fallback_context_state_, &fallback_texture);
      }
    } else {
      CHECK(fallback_context_state_->gpu_main_graphite_recorder());
      CHECK(fallback_textures_.empty());
      for (auto& fallback_texture : graphite_fallback_textures_) {
        fallback_context_state_->gpu_main_graphite_recorder()
            ->deleteBackendTexture(fallback_texture);
      }
    }
  }
  fallback_context_state_ = nullptr;
  fallback_textures_.clear();
  graphite_fallback_textures_.clear();
}

void ImageContextImpl::CreateFallbackImage(
    gpu::SharedContextState* context_state) {
  const int num_planes = format().NumberOfPlanes();
  TRACE_EVENT_BEGIN("viz", "ImageContextImpl::CreateFallbackImage");

  CreateFallbackImageResult result = CreateFallbackImageResult::kSuccess;
  absl::Cleanup record_results = [&result] {
    base::UmaHistogramEnumeration("Viz.CreateFallbackImageResult", result);
    TRACE_EVENT_END("viz", "result", CreateFallbackImageResultToString(result));
  };

  if (format().PrefersExternalSampler()) {
    // Skia can't allocate a fallback texture since the original texture was
    // externally allocated.
    result = CreateFallbackImageResult::kFailedPrefersExternalSampler;
    return;
  }

  if (context_state->graphite_shared_context()) {
    if (graphite_ycbcr_info_mismatch_) {
      // It is not possible to allocate a fallback texture if the failure was
      // due to a mismatch in YCBCr info between the promise image and the
      // fulfillment texture.
      result = CreateFallbackImageResult::kFailedYcbcrMismatch;
      return;
    }

    const auto& tex_infos = texture_infos();
    if (tex_infos.size() != static_cast<size_t>(num_planes) ||
        std::ranges::any_of(tex_infos, [](const auto& tex_info) {
          return !tex_info.isValid();
        })) {
      DLOG(ERROR) << "Invalid Graphite texture infos for format: "
                  << format().ToString();
      result = CreateFallbackImageResult::kFailedInvalidTextureInfo;
      return;
    }

#if BUILDFLAG(SKIA_USE_DAWN)
    skgpu::graphite::DawnTextureInfo dawn_info;
    bool success = skgpu::graphite::TextureInfos::GetDawnTextureInfo(
        tex_infos[0], &dawn_info);
    if (success &&
        dawn_info.fFormat == wgpu::TextureFormat::OpaqueYCbCrAndroid) {
      // Skia can't allocate a fallback texture since the original texture was
      // externally allocated.
      result = CreateFallbackImageResult::kFailedYCbCrTextureCreationFailure;
      return;
    }
#endif

    DCHECK(!fallback_context_state_);
    fallback_context_state_ = context_state;
    for (int plane_index = 0; plane_index < num_planes; plane_index++) {
      SkISize sk_size =
          gfx::SizeToSkISize(format().GetPlaneSize(plane_index, size()));
      auto tex_info =
          gpu::FallbackGraphiteBackendTextureInfo(tex_infos[plane_index]);
      auto texture = fallback_context_state_->gpu_main_graphite_recorder()
                         ->createBackendTexture(sk_size, tex_info);
      if (!texture.isValid()) {
        DLOG(ERROR) << "Failed to create fallback graphite backend texture";
        DeleteFallbackTextures();
        result = CreateFallbackImageResult::kFailedCreateTexture;
        return;
      }
      graphite_fallback_textures_.push_back(texture);
    }
    graphite_textures_ = graphite_fallback_textures_;

    for (int plane_index = 0; plane_index < num_planes; plane_index++) {
      SkColorType color_type = ToClosestSkColorType(format(), plane_index);
      auto sk_surface = SkSurfaces::WrapBackendTexture(
          fallback_context_state_->gpu_main_graphite_recorder(),
          graphite_fallback_textures_[plane_index], color_type,
          GetSkColorSpace(),
          /*props=*/nullptr);
      CHECK(sk_surface);
      sk_surface->getCanvas()->clear(
          GetFallbackColorForPlane(format(), plane_index));
    }

    // Snap and insert recording for GPU work needed for clearing the surface.
    // If this is not done, the fallback image may be used in a Submit after
    // destruction if it waits too long for snap to happen during rasterization
    // on GPU main thread.
    auto recording =
        fallback_context_state_->gpu_main_graphite_recorder()->snap();
    if (recording) {
      skgpu::graphite::InsertRecordingInfo info = {};
      info.fRecording = recording.get();
      bool insert_success =
          fallback_context_state_->graphite_shared_context()->insertRecording(
              info);
      if (!insert_success) {
        DLOG(ERROR) << "Failed to insert recording";
      }
    }
    return;
  }

  // We can't allocate a fallback texture as the original texture was externally
  // allocated. Skia will skip drawing a null GrPromiseImageTexture, do nothing
  // and leave it null.
  const auto& formats = backend_formats();
  if (formats.empty() || formats[0].textureType() == GrTextureType::kExternal) {
    result = CreateFallbackImageResult::kFailedYCbCrTextureCreationFailure;
    return;
  }

  DCHECK(!fallback_context_state_);
  fallback_context_state_ = context_state;

  std::vector<sk_sp<GrPromiseImageTexture>> promise_textures;
  for (int plane_index = 0; plane_index < num_planes; plane_index++) {
    DCHECK_NE(formats[plane_index].textureType(), GrTextureType::kExternal);
    auto plane_size = format().GetPlaneSize(plane_index, size());
    auto fallback_texture =
        fallback_context_state_->gr_context()->createBackendTexture(
            plane_size.width(), plane_size.height(), formats[plane_index],
            GetFallbackColorForPlane(format(), plane_index),
            skgpu::Mipmapped::kNo, GrRenderable::kYes);

    if (!fallback_texture.isValid()) {
      DeleteFallbackTextures();
      DLOG(ERROR) << "Could not create backend texture.";
      result = CreateFallbackImageResult::kFailedCreateTexture;
      return;
    }
    auto promise_texture = GrPromiseImageTexture::Make(fallback_texture);
    promise_textures.push_back(std::move(promise_texture));
    fallback_textures_.push_back(fallback_texture);
  }
  SetPromiseImageTextures(promise_textures);
}

void ImageContextImpl::BeginAccessIfNecessary(
    gpu::SharedContextState* context_state,
    gpu::SharedImageRepresentationFactory* representation_factory,
    std::vector<GrBackendSemaphore>* begin_semaphores,
    std::vector<GrBackendSemaphore>* end_semaphores) {
  if (representation_raster_scoped_access_)
    return;

  if (!BeginAccessIfNecessaryInternal(context_state, representation_factory,
                                      begin_semaphores, end_semaphores)) {
    CreateFallbackImage(context_state);
  }
}

bool ImageContextImpl::BeginRasterAccess(
    gpu::SharedImageRepresentationFactory* representation_factory) {
  if (paint_op_buffer()) {
    DCHECK(raster_representation_);
    DCHECK(representation_raster_scoped_access_);
    return true;
  }

  auto raster = raw_draw_if_possible_
                    ? representation_factory->ProduceRaster(mailbox())
                    : nullptr;
  if (!raster)
    return false;

  auto scoped_access = raster->BeginScopedReadAccess();
  if (!scoped_access)
    return false;

  set_paint_op_buffer(scoped_access->paint_op_buffer());
  set_clear_color(scoped_access->clear_color());

  raster_representation_ = std::move(raster);
  representation_raster_scoped_access_ = std::move(scoped_access);

  return true;
}

bool ImageContextImpl::BeginAccessIfNecessaryInternal(
    gpu::SharedContextState* context_state,
    gpu::SharedImageRepresentationFactory* representation_factory,
    std::vector<GrBackendSemaphore>* begin_semaphores,
    std::vector<GrBackendSemaphore>* end_semaphores) {
  // Skip the context if it has been processed.
  if (representation_scoped_read_access_) {
    CHECK(owned_promise_image_textures_.empty());
    CHECK(!context_state->gr_context() || !promise_image_textures_.empty());
    CHECK(!context_state->graphite_shared_context() ||
          !graphite_textures_.empty());
    return true;
  }

  // promise_image_textures_ are not empty here, it means we are using a
  // fallback image.
  if (!promise_image_textures_.empty()) {
    CHECK_EQ(promise_image_textures_.size(),
             owned_promise_image_textures_.size());
    return true;
  }

  if (!graphite_textures_.empty()) {
    CHECK_EQ(graphite_textures_.size(), graphite_fallback_textures_.size());
    return true;
  }

  if (!representation_) {
    auto representation = representation_factory->ProduceSkia(
        mailbox(), context_state,
        /*required_usages=*/{gpu::SHARED_IMAGE_USAGE_DISPLAY_READ});
    if (!representation) {
      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
                     "mailbox not found in SharedImageManager.";
      return false;
    }

    if (representation->size() != size()) {
      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
                     "size does not match TransferableResource size: "
                  << representation->size().ToString() << " vs "
                  << size().ToString();
      return false;
    }

    if ((representation->format() != format() ||
         representation->format().PrefersExternalSampler() !=
             format().PrefersExternalSampler()) &&
        base::FeatureList::IsEnabled(kValidatePromiseImageFormat)) {
      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
                     "format does not match TransferableResource format: "
                  << representation->format().ToString() << " vs "
                  << format().ToString();
      return false;
    }

    representation_ = std::move(representation);
  }

  representation_scoped_read_access_ =
      representation_->BeginScopedReadAccess(begin_semaphores, end_semaphores);
  if (!representation_scoped_read_access_) {
    clear_image();
    representation_ = nullptr;
    DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
                   "begin read access failed..";
    return false;
  }

  // Only one promise texture for external sampler case.
  int num_planes =
      format().PrefersExternalSampler() ? 1 : format().NumberOfPlanes();
  if (context_state->graphite_shared_context()) {
#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(SKIA_USE_DAWN)
    // In the case of video decoding, it is possible for there to be a mismatch
    // between the YCbCr info passed to Viz at the time of creating the promise
    // texture and that computed at the time of fulfilling the promise texture.
    // Detect such mismatches and error out, as Skia/Dawn will raise errors.
    graphite_ycbcr_info_mismatch_ = false;

    skgpu::graphite::DawnTextureInfo fulfillment_texture_info;
    CHECK(skgpu::graphite::TextureInfos::GetDawnTextureInfo(
        representation_scoped_read_access_->graphite_texture(0).info(),
        &fulfillment_texture_info));

    wgpu::YCbCrVkDescriptor promise_texture_ycbcr_desc = {};
    if (ycbcr_info()) {
      promise_texture_ycbcr_desc =
          gpu::ToDawnYCbCrVkDescriptor(ycbcr_info().value());
    }
    wgpu::YCbCrVkDescriptor fulfillment_texture_ycbcr_desc =
        fulfillment_texture_info.fYcbcrVkDescriptor;

    if (!DawnYCbCrVkDescriptorsAreCompatible(promise_texture_ycbcr_desc,
                                             fulfillment_texture_ycbcr_desc)) {
      graphite_ycbcr_info_mismatch_ = true;
      representation_scoped_read_access_.reset();
      return false;
    }
#endif

    for (int plane_index = 0; plane_index < num_planes; plane_index++) {
      graphite_textures_.push_back(
          representation_scoped_read_access_->graphite_texture(plane_index));
    }
  } else {
    CHECK(context_state->gr_context());
    for (int plane_index = 0; plane_index < num_planes; plane_index++) {
      promise_image_textures_.push_back(
          representation_scoped_read_access_->promise_image_texture(
              plane_index));
    }
  }

  return true;
}

void ImageContextImpl::EndAccessIfNecessary() {
  if (paint_op_buffer()) {
    DCHECK(!representation_scoped_read_access_);
    return;
  }

  if (!representation_scoped_read_access_)
    return;

  // Avoid unnecessary read access churn for representations that
  // support multiple readers.
  if (representation_->SupportsMultipleConcurrentReadAccess() &&
      !is_for_render_pass_) {
    return;
  }

  representation_scoped_read_access_.reset();
  promise_image_textures_.clear();
  graphite_textures_.clear();
}

}  // namespace viz
