// 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/test/fake_skia_output_surface.h"

#include <memory>
#include <string_view>
#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/notimplemented.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_util.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "components/viz/service/display/output_surface_client.h"
#include "components/viz/service/display/output_surface_frame.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "third_party/skia/include/core/SkCPURecorder.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/ganesh/mock/GrMockBackendSurface.h"
#include "ui/gfx/gpu_fence_handle.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/gfx/swap_result.h"
#include "ui/gl/gl_utils.h"

namespace viz {

FakeSkiaOutputSurface::FakeSkiaOutputSurface(
    scoped_refptr<ContextProvider> context_provider)
    : context_provider_(std::move(context_provider)) {}

FakeSkiaOutputSurface::~FakeSkiaOutputSurface() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}

void FakeSkiaOutputSurface::BindToClient(OutputSurfaceClient* client) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(client);
  DCHECK(!client_);
  client_ = client;
}

void FakeSkiaOutputSurface::EnsureBackbuffer() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}

void FakeSkiaOutputSurface::DiscardBackbuffer() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}

void FakeSkiaOutputSurface::Reshape(const ReshapeParams& params) {
  auto& sk_surface = sk_surfaces_[AggregatedRenderPassId{0}];
  SkColorType color_type = kRGBA_8888_SkColorType;
  SkImageInfo image_info = SkImageInfo::Make(
      params.size.width(), params.size.height(), color_type,
      kPremul_SkAlphaType, params.color_space.ToSkColorSpace());
  sk_surface =
      SkSurfaces::RenderTarget(gr_context(), skgpu::Budgeted::kNo, image_info);

  DCHECK(sk_surface);
}

void FakeSkiaOutputSurface::SwapBuffers(OutputSurfaceFrame frame) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (frame.delegated_ink_metadata)
    last_delegated_ink_metadata_ = std::move(frame.delegated_ink_metadata);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeSkiaOutputSurface::SwapBuffersAck,
                                weak_ptr_factory_.GetWeakPtr()));
}

void FakeSkiaOutputSurface::SetNeedsSwapSizeNotifications(
    bool needs_swap_size_notifications) {
  NOTIMPLEMENTED();
}

void FakeSkiaOutputSurface::SetUpdateVSyncParametersCallback(
    UpdateVSyncParametersCallback callback) {
  NOTIMPLEMENTED();
}

gfx::OverlayTransform FakeSkiaOutputSurface::GetDisplayTransform() {
  return gfx::OVERLAY_TRANSFORM_NONE;
}

SkCanvas* FakeSkiaOutputSurface::BeginPaintCurrentFrame() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  auto& sk_surface = sk_surfaces_[AggregatedRenderPassId{0}];
  DCHECK(sk_surface);
  DCHECK_EQ(current_render_pass_id_, AggregatedRenderPassId{0u});
  return sk_surface->getCanvas();
}

void FakeSkiaOutputSurface::MakePromiseSkImage(
    ImageContext* image_context,
    bool force_rgbx) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (image_context->has_image()) {
    return;
  }

  if (image_context->format().is_multi_plane()) {
    NOTIMPLEMENTED();
    return;
  }

  GrBackendTexture backend_texture;
  if (!GetGrBackendTexture(*image_context, &backend_texture)) {
    DLOG(ERROR) << "Failed to GetGrBackendTexture from mailbox.";
    return;
  }

  auto sk_color_type = ToClosestSkColorType(image_context->format());
  image_context->SetImage(
      SkImages::BorrowTextureFrom(gr_context(), backend_texture,
                                  kTopLeft_GrSurfaceOrigin, sk_color_type,
                                  image_context->alpha_type(),
                                  image_context->GetSkColorSpace()),
      {backend_texture.getBackendFormat()});
}

gpu::SyncToken FakeSkiaOutputSurface::ReleaseImageContexts(
    std::vector<std::unique_ptr<ImageContext>> image_contexts) {
  return gpu::SyncToken();
}

std::unique_ptr<ExternalUseClient::ImageContext>
FakeSkiaOutputSurface::CreateImageContext(const TransferableResource& resource,
                                          bool concurrent_reads,
                                          bool raw_draw_if_possible,
                                          uint32_t client_id) {
  return std::make_unique<ExternalUseClient::ImageContext>(resource);
}

SkCanvas* FakeSkiaOutputSurface::BeginPaintRenderPass(
    const AggregatedRenderPassId& id,
    const gfx::Size& surface_size,
    SharedImageFormat format,
    RenderPassAlphaType alpha_type,
    skgpu::Mipmapped,
    bool scanout_dcomp_surface,
    const gfx::ColorSpace& color_space,
    bool is_overlay,
    const gpu::Mailbox& mailbox) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // Make sure there is no unsubmitted PaintFrame or PaintRenderPass.
  DCHECK_EQ(current_render_pass_id_, AggregatedRenderPassId{0u});

  mailbox_pass_ids_.insert_or_assign(mailbox, id);
  auto& sk_surface = sk_surfaces_[id];

  if (!sk_surface) {
    SkColorType color_type = ToClosestSkColorType(format);
    SkImageInfo image_info = SkImageInfo::Make(
        surface_size.width(), surface_size.height(), color_type,
        kPremul_SkAlphaType, color_space.ToSkColorSpace());
    sk_surface = SkSurfaces::RenderTarget(gr_context(), skgpu::Budgeted::kNo,
                                          image_info);
  }
  return sk_surface->getCanvas();
}

SkCanvas* FakeSkiaOutputSurface::RecordOverdrawForCurrentPaint() {
  NOTIMPLEMENTED();
  return nullptr;
}

void FakeSkiaOutputSurface::EndPaint(
    base::OnceClosure on_finished,
    base::OnceCallback<void(gfx::GpuFenceHandle)> return_release_fence_cb,
    const gfx::Rect& update_rect,
    bool is_overlay) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  skgpu::ganesh::FlushAndSubmit(sk_surfaces_[current_render_pass_id_]);
  current_render_pass_id_ = AggregatedRenderPassId{0};

  if (on_finished)
    std::move(on_finished).Run();
  if (return_release_fence_cb)
    std::move(return_release_fence_cb).Run(gfx::GpuFenceHandle());
}

sk_sp<SkImage> FakeSkiaOutputSurface::MakePromiseSkImageFromRenderPass(
    const AggregatedRenderPassId& id,
    const gfx::Size& size,
    SharedImageFormat format,
    bool mipmap,
    const gfx::ColorSpace& color_space,
    const gpu::Mailbox& mailbox) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  auto it = sk_surfaces_.find(id);
  CHECK(it != sk_surfaces_.end());
  return it->second->makeImageSnapshot();
}

void FakeSkiaOutputSurface::RemoveRenderPassResource(
    std::vector<AggregatedRenderPassId> ids) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!ids.empty());

  for (const auto& id : ids) {
    auto it = sk_surfaces_.find(id);
    CHECK(it != sk_surfaces_.end());
    sk_surfaces_.erase(it);
  }

  // Erase mailbox mappings that exist for these ids.
  base::EraseIf(mailbox_pass_ids_, [&ids](auto& entry) {
    for (auto& id : ids) {
      if (id == entry.second) {
        return true;
      }
    }
    return false;
  });
}

void FakeSkiaOutputSurface::CopyOutput(
    const copy_output::RenderPassGeometry& geometry,
    const gfx::ColorSpace& color_space,
    std::unique_ptr<CopyOutputRequest> request,
    const gpu::Mailbox& mailbox) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  auto it = mailbox_pass_ids_.find(mailbox);
  DCHECK(mailbox.IsZero() || it != mailbox_pass_ids_.end());
  AggregatedRenderPassId id =
      mailbox.IsZero() ? AggregatedRenderPassId(0) : it->second;

  DCHECK(sk_surfaces_.find(id) != sk_surfaces_.end());
  auto* surface = sk_surfaces_[id].get();
  if (request->result_format() != CopyOutputResult::Format::RGBA ||
      request->is_scaled() ||
      geometry.result_bounds != geometry.result_selection) {
    // TODO(crbug.com/40483986): Complete the implementation for all request
    // types, scaling, etc.
    NOTIMPLEMENTED();
    return;
  }

  if (request->result_destination() ==
      CopyOutputResult::Destination::kSharedImage) {
    // NOTE: This implementation is incomplete and doesn't copy anything into
    // the mailbox, but currently the only tests that use this don't actually
    // check the returned texture data. A corollary to this fact is that the
    // usage flags passed in are currently arbitrary, since callers don't
    // actually do anything meaningful with the SharedImage.
    auto* sii = GetSharedImageInterface();
    auto client_shared_image =
        sii->CreateSharedImage({SinglePlaneFormat::kRGBA_8888,
                                geometry.result_selection.size(), color_space,
                                gpu::SHARED_IMAGE_USAGE_GLES2_READ |
                                    gpu::SHARED_IMAGE_USAGE_GLES2_WRITE,
                                "CopyOutput"},
                               gpu::kNullSurfaceHandle);
    CHECK(client_shared_image);
    gpu::Mailbox local_mailbox = client_shared_image->mailbox();

    ReleaseCallback release_callback =
        base::BindPostTaskToCurrentDefault(base::BindOnce(
            &FakeSkiaOutputSurface::DestroyCopyOutputTexture,
            weak_ptr_factory_.GetWeakPtr(), std::move(client_shared_image)));

    request->SendResult(std::make_unique<CopyOutputSharedImageResult>(
        CopyOutputResult::Format::RGBA, geometry.result_bounds, local_mailbox,
        color_space, "CopyOutput", std::move(release_callback)));
    return;
  }

  GrDirectContext* direct = GrAsDirectContext(gr_context());
  auto copy_image = surface->makeImageSnapshot()->makeSubset(
      direct ? direct->asRecorder() : skcpu::Recorder::TODO(),
      RectToSkIRect(geometry.sampling_bounds), {});
  // Send copy request by copying into a bitmap.
  SkBitmap bitmap;
  copy_image->asLegacyBitmap(&bitmap);
  // TODO(crbug.com/40554816): Plumb color space throughout SkiaRenderer up to
  // the SkSurface/SkImage here. Until then, play "musical chairs" with the
  // SkPixelRef to hack-in the RenderPass's |color_space|.
  sk_sp<SkPixelRef> pixels(SkSafeRef(bitmap.pixelRef()));
  SkIPoint origin = bitmap.pixelRefOrigin();
  bitmap.setInfo(bitmap.info().makeColorSpace(color_space.ToSkColorSpace()),
                 bitmap.rowBytes());
  bitmap.setPixelRef(std::move(pixels), origin.x(), origin.y());
  request->SendResult(std::make_unique<CopyOutputSkBitmapResult>(
      geometry.result_bounds, std::move(bitmap)));
}

gpu::SharedImageInterface* FakeSkiaOutputSurface::GetSharedImageInterface() {
  return context_provider_->SharedImageInterface();
}

void FakeSkiaOutputSurface::AddContextLostObserver(
    ContextLostObserver* observer) {}

void FakeSkiaOutputSurface::RemoveContextLostObserver(
    ContextLostObserver* observer) {}

gpu::SyncToken FakeSkiaOutputSurface::Flush() {
  return GenerateSyncToken();
}

void FakeSkiaOutputSurface::SetOutOfOrderCallbacks(
    bool out_of_order_callbacks) {
  TestContextSupport* support =
      static_cast<TestContextSupport*>(context_provider()->ContextSupport());
  support->set_out_of_order_callbacks(out_of_order_callbacks);
}

gpu::SyncToken FakeSkiaOutputSurface::GenerateSyncToken() {
  gpu::SyncToken sync_token(
      gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE,
      gpu::CommandBufferId(), ++next_sync_fence_release_);
  sync_token.SetVerifyFlush();
  return sync_token;
}

bool FakeSkiaOutputSurface::GetGrBackendTexture(
    const ImageContext& image_context,
    GrBackendTexture* backend_texture) {
  DCHECK(!image_context.mailbox().IsZero());

  static int next_id = 0;
  GrMockTextureInfo mock_texture_info = GrMockTextureInfo(
      SkColorTypeToGrColorType(ToClosestSkColorType(image_context.format())),
      SkTextureCompressionType::kNone, ++next_id);
  *backend_texture = GrBackendTextures::MakeMock(
      image_context.size().width(), image_context.size().height(),
      skgpu::Mipmapped::kNo, mock_texture_info);
  return true;
}

void FakeSkiaOutputSurface::SwapBuffersAck() {
  base::TimeTicks now = base::TimeTicks::Now();
  gpu::SwapBuffersCompleteParams params;
  params.swap_response.timings = {now, now};
  params.swap_response.result = gfx::SwapResult::SWAP_ACK;
  client_->DidReceiveSwapBuffersAck(std::move(params),
                                    /*release_fence=*/gfx::GpuFenceHandle());
  client_->DidReceivePresentationFeedback({now, base::TimeDelta(), 0});
}

void FakeSkiaOutputSurface::DestroyCopyOutputTexture(
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    const gpu::SyncToken& sync_token,
    bool is_lost) {
  shared_image->UpdateDestructionSyncToken(sync_token);
}

void FakeSkiaOutputSurface::ScheduleGpuTaskForTesting(
    base::OnceClosure callback,
    std::vector<gpu::SyncToken> sync_tokens) {
  NOTIMPLEMENTED();
}

void FakeSkiaOutputSurface::CheckAsyncWorkCompletionForTesting() {
  NOTIMPLEMENTED();
}

void FakeSkiaOutputSurface::InitDelegatedInkPointRendererReceiver(
    mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
        pending_receiver) {
  delegated_ink_renderer_receiver_arrived_ = true;
}

gpu::Mailbox FakeSkiaOutputSurface::CreateSharedImage(
    SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    RenderPassAlphaType alpha_type,
    gpu::SharedImageUsageSet usage,
    std::string_view debug_label,
    gpu::SurfaceHandle surface_handle) {
  return gpu::Mailbox::Generate();
}

gpu::Mailbox FakeSkiaOutputSurface::CreateSolidColorSharedImage(
    const SkColor4f& color,
    const gfx::ColorSpace& color_space) {
  return gpu::Mailbox::Generate();
}

void FakeSkiaOutputSurface::DestroySharedImage(const gpu::Mailbox& mailbox) {
  destroyed_mailboxes_.push_back(mailbox);
}

void FakeSkiaOutputSurface::SetSharedImagePurgeable(const gpu::Mailbox& mailbox,
                                                    bool purgeable) {
  if (set_purgeable_callback_) {
    set_purgeable_callback_.Run(mailbox, purgeable);
  }
}

bool FakeSkiaOutputSurface::SupportsBGRA() const {
  return true;
}

}  // namespace viz
