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

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

#include <memory>
#include <utility>

#include "base/check_is_test.h"
#include "base/logging.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/service/graphite_shared_context.h"
#include "gpu/command_buffer/service/service_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/Surface.h"
#include "third_party/skia/include/gpu/graphite/TextureInfo.h"

#if BUILDFLAG(ENABLE_VULKAN)
#include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h"
#endif

namespace viz {

SkiaOutputDeviceOffscreen::SkiaOutputDeviceOffscreen(
    scoped_refptr<gpu::SharedContextState> context_state,
    gfx::SurfaceOrigin origin,
    bool has_alpha,
    scoped_refptr<gpu::MemoryTracker> memory_tracker,
    DidSwapBufferCompleteCallback did_swap_buffer_complete_callback)
    : SkiaOutputDevice(context_state->gr_context(),
                       context_state->graphite_shared_context(),
                       std::move(memory_tracker),
                       did_swap_buffer_complete_callback),
      context_state_(context_state),
      has_alpha_(has_alpha) {
  capabilities_.uses_default_gl_framebuffer = false;
  capabilities_.output_surface_origin = origin;
  capabilities_.supports_post_sub_buffer = true;

  // Some Vulkan drivers do not support kRGB_888x_SkColorType. Always use
  // kRGBA/BGRA_8888_SkColorType instead and initialize surface to opaque as
  // necessary.
  // TODO(crbug.com/40141277): use the right color types base on GPU
  // capabilities.
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_8888] =
      kRGBA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBX_8888] =
      kRGBA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRA_8888] =
      kBGRA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRX_8888] =
      kBGRA_8888_SkColorType;
  capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_F16] =
      kRGBA_F16_SkColorType;
}

SkiaOutputDeviceOffscreen::~SkiaOutputDeviceOffscreen() {
  DiscardBackbuffer();
}

bool SkiaOutputDeviceOffscreen::Reshape(const ReshapeParams& params) {
  DCHECK_EQ(params.transform, gfx::OVERLAY_TRANSFORM_NONE);
  DiscardBackbuffer();
  size_ = params.GfxSize();
  if (size_.width() > capabilities_.max_texture_size ||
      size_.height() > capabilities_.max_texture_size) {
    LOG(ERROR) << "The requested size (" << size_.ToString()
               << ") exceeds the max texture size ("
               << capabilities_.max_texture_size << ")";
    return false;
  }
  sk_color_type_ = params.image_info.colorType();
  sk_color_space_ = params.image_info.refColorSpace();
  sample_count_ = params.sample_count;
  EnsureBackbuffer();
  return true;
}

void SkiaOutputDeviceOffscreen::Present(
    const std::optional<gfx::Rect>& update_rect,
    BufferPresentedCallback feedback,
    OutputSurfaceFrame frame) {
  // Reshape should have been called first.
  DCHECK(backend_texture_.isValid() || graphite_texture_.isValid());

  StartSwapBuffers(std::move(feedback));
  FinishSwapBuffers(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_ACK),
                    gfx::Size(size_.width(), size_.height()), std::move(frame));
}

void SkiaOutputDeviceOffscreen::EnsureBackbuffer() {
  // Ignore EnsureBackbuffer if Reshape has not been called yet.
  if (size_.IsEmpty()) {
    return;
  }

  CHECK(!backbuffer_estimated_size_);
  if (auto* gr_context = context_state_->gr_context()) {
    auto backend_format =
        gr_context->defaultBackendFormat(sk_color_type_, GrRenderable::kYes);
    DCHECK(backend_format.isValid())
        << "GrBackendFormat is invalid for color_type: " << sk_color_type_;

    if (has_alpha_) {
      backend_texture_ = gr_context->createBackendTexture(
          size_.width(), size_.height(), backend_format, skgpu::Mipmapped::kNo,
          GrRenderable::kYes);
    } else {
      is_emulated_rgbx_ = true;
      // Initialize alpha channel to opaque.
      backend_texture_ = gr_context->createBackendTexture(
          size_.width(), size_.height(), backend_format, SkColors::kBlack,
          skgpu::Mipmapped::kNo, GrRenderable::kYes);
    }
    DCHECK(backend_texture_.isValid());

    if (backend_texture_.backend() == GrBackendApi::kVulkan) {
#if BUILDFLAG(ENABLE_VULKAN)
      GrVkImageInfo vk_image_info;
      bool result =
          GrBackendTextures::GetVkImageInfo(backend_texture_, &vk_image_info);
      DCHECK(result);
      backbuffer_estimated_size_ = vk_image_info.fAlloc.fSize;
#else
      DCHECK(false);
#endif
    } else {
      auto info = SkImageInfo::Make(size_.width(), size_.height(),
                                    sk_color_type_, kUnpremul_SkAlphaType);
      size_t estimated_size = info.computeMinByteSize();
      backbuffer_estimated_size_ = estimated_size;
    }
  } else {
    CHECK(context_state_->graphite_shared_context());
    if (!has_alpha_) {
      is_emulated_rgbx_ = true;
    }
    // Get backend texture info needed for creating backend textures for
    // offscreen.
    skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
        context_state_->gr_context_type(),
        SkColorTypeToSinglePlaneSharedImageFormat(sk_color_type_),
        /*plane_index=*/0,
        /*is_yuv_plane=*/false, /*mipmapped=*/false,
        /*scanout_dcomp_surface=*/false);
    graphite_texture_ =
        context_state_->gpu_main_graphite_recorder()->createBackendTexture(
            gfx::SizeToSkISize(size_), texture_info);
    CHECK(graphite_texture_.isValid());
    auto info = SkImageInfo::Make(size_.width(), size_.height(), sk_color_type_,
                                  kUnpremul_SkAlphaType);
    size_t estimated_size = info.computeMinByteSize();
    backbuffer_estimated_size_ = estimated_size;
  }
  memory_type_tracker_->TrackMemAlloc(backbuffer_estimated_size_);
}

void SkiaOutputDeviceOffscreen::DiscardBackbuffer() {
  if (backend_texture_.isValid()) {
    sk_surface_.reset();
    DeleteGrBackendTexture(context_state_.get(), &backend_texture_);
    backend_texture_ = GrBackendTexture();
    memory_type_tracker_->TrackMemFree(backbuffer_estimated_size_);
    backbuffer_estimated_size_ = 0u;
  } else if (graphite_texture_.isValid()) {
    auto* graphite_shared_context = context_state_->graphite_shared_context();
    CHECK(graphite_shared_context);
    sk_surface_.reset();
    graphite_shared_context->deleteBackendTexture(graphite_texture_);
    graphite_texture_ = skgpu::graphite::BackendTexture();
    memory_type_tracker_->TrackMemFree(backbuffer_estimated_size_);
    backbuffer_estimated_size_ = 0u;
  }
}

SkSurface* SkiaOutputDeviceOffscreen::BeginPaint(
    std::vector<GrBackendSemaphore>* end_semaphores) {
  DCHECK(backend_texture_.isValid() || graphite_texture_.isValid());
  if (!sk_surface_) {
    SkSurfaceProps surface_props;
    if (auto* gr_context = context_state_->gr_context()) {
      sk_surface_ = SkSurfaces::WrapBackendTexture(
          gr_context, backend_texture_,
          capabilities_.output_surface_origin == gfx::SurfaceOrigin::kTopLeft
              ? kTopLeft_GrSurfaceOrigin
              : kBottomLeft_GrSurfaceOrigin,
          sample_count_, sk_color_type_, sk_color_space_, &surface_props);
    } else {
      CHECK(context_state_->graphite_shared_context());
      sk_surface_ = SkSurfaces::WrapBackendTexture(
          context_state_->gpu_main_graphite_recorder(), graphite_texture_,
          sk_color_type_, sk_color_space_, &surface_props);
    }
  }
  return sk_surface_.get();
}

void SkiaOutputDeviceOffscreen::EndPaint() {}

void SkiaOutputDeviceOffscreen::ReadbackForTesting(
    base::OnceCallback<void(SkBitmap)> callback) {
  CHECK_IS_TEST();

  struct ReadPixelsContext {
    std::unique_ptr<const SkImage::AsyncReadResult> async_result;
    bool finished = false;
    static void OnReadPixelsDone(
        void* raw_ctx,
        std::unique_ptr<const SkImage::AsyncReadResult> async_result) {
      ReadPixelsContext* context =
          reinterpret_cast<ReadPixelsContext*>(raw_ctx);
      context->async_result = std::move(async_result);
      context->finished = true;
    }
  };

  ReadPixelsContext context;
  if (auto* graphite_shared_context =
          context_state_->graphite_shared_context()) {
    context_state_->FlushGraphiteRecorder();
    // asyncRescaleAndReadPixels is a context operation that inserts its own
    // recording internally.
    graphite_shared_context->asyncRescaleAndReadPixelsAndSubmit(
        sk_surface_.get(), sk_surface_->imageInfo(),
        SkIRect::MakeSize(sk_surface_->imageInfo().dimensions()),
        SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedLinear,
        base::BindOnce(&ReadPixelsContext::OnReadPixelsDone), &context);
  } else {
    CHECK(context_state_->gr_context());
    sk_surface_->asyncRescaleAndReadPixels(
        sk_surface_->imageInfo(),
        SkIRect::MakeSize(sk_surface_->imageInfo().dimensions()),
        SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedLinear,
        &ReadPixelsContext::OnReadPixelsDone, &context);
    context_state_->FlushAndSubmit(true);
  }

  CHECK(context.finished);
  CHECK(context.async_result);

  CHECK_EQ(1, context.async_result->count());
  const SkPixmap src_pixmap(sk_surface_->imageInfo(),
                            const_cast<void*>(context.async_result->data(0)),
                            context.async_result->rowBytes(0));

  // Copy the pixels so we don't need to keep |context.async_result| alive.
  SkBitmap bitmap;
  bitmap.allocPixels(src_pixmap.info());
  CHECK(bitmap.writePixels(src_pixmap));

  std::move(callback).Run(std::move(bitmap));
}

}  // namespace viz
