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

#include <algorithm>
#include <utility>

#include "base/check_op.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/rand_util.h"
#include "base/task/common/task_annotator.h"
#include "gpu/command_buffer/service/graphite_shared_context.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
#include "third_party/skia/include/core/SkSurface.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/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/Recording.h"
#include "third_party/skia/include/private/chromium/GrDeferredDisplayList.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/presentation_feedback.h"

#if BUILDFLAG(IS_WIN)
#include "components/viz/service/display/dc_layer_overlay.h"
#endif

namespace viz {
namespace {

void ReportLatency(const gfx::SwapTimings& timings,
                   std::vector<ui::LatencyInfo> latency_info) {
  for (auto& latency : latency_info) {
    latency.AddLatencyNumberWithTimestamp(
        ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, timings.swap_start);
    latency.AddLatencyNumberWithTimestamp(
        ui::INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT, timings.swap_end);
  }
}

}  // namespace

SkiaOutputDevice::ScopedPaint::ScopedPaint(
    std::vector<GrBackendSemaphore> end_semaphores,
    SkiaOutputDevice* device,
    SkSurface* sk_surface)
    : end_semaphores_(std::move(end_semaphores)),
      device_(device),
      sk_surface_(sk_surface) {}

SkiaOutputDevice::ScopedPaint::~ScopedPaint() {
  DCHECK(end_semaphores_.empty());
  device_->EndPaint();
}

SkCanvas* SkiaOutputDevice::ScopedPaint::GetCanvas() {
  return device_->GetCanvas(sk_surface_);
}

GrSemaphoresSubmitted SkiaOutputDevice::ScopedPaint::Flush(
    VulkanContextProvider* vulkan_context_provider,
    std::vector<GrBackendSemaphore> end_semaphores,
    base::OnceClosure on_finished) {
  return device_->Flush(sk_surface_, vulkan_context_provider,
                        std::move(end_semaphores), std::move(on_finished));
}

bool SkiaOutputDevice::ScopedPaint::Wait(
    int num_semaphores,
    const GrBackendSemaphore wait_semaphores[],
    bool delete_semaphores_after_wait) {
  return device_->Wait(sk_surface_, num_semaphores, wait_semaphores,
                       delete_semaphores_after_wait);
}

bool SkiaOutputDevice::ScopedPaint::Draw(
    sk_sp<const GrDeferredDisplayList> ddl) {
  return device_->Draw(sk_surface_, std::move(ddl));
}

bool SkiaOutputDevice::ScopedPaint::Draw(
    gpu::GraphiteSharedContext* graphite_shared_context,
    std::unique_ptr<skgpu::graphite::Recording> graphite_recording,
    base::OnceClosure on_finished) {
  return device_->Draw(graphite_shared_context, sk_surface_,
                       std::move(graphite_recording), std::move(on_finished));
}

SkiaOutputDevice::SkiaOutputDevice(
    GrDirectContext* gr_context,
    gpu::GraphiteSharedContext* graphite_shared_context,
    scoped_refptr<gpu::MemoryTracker> memory_tracker,
    DidSwapBufferCompleteCallback did_swap_buffer_complete_callback,
    ReleaseOverlaysCallback release_overlays_callback)
    : did_swap_buffer_complete_callback_(
          std::move(did_swap_buffer_complete_callback)),
      release_overlays_callback_(std::move(release_overlays_callback)),
      memory_type_tracker_(
          std::make_unique<gpu::MemoryTypeTracker>(std::move(memory_tracker))) {
  if (gr_context) {
    CHECK(!graphite_shared_context);
    capabilities_.max_render_target_size = gr_context->maxRenderTargetSize();
    capabilities_.max_texture_size = gr_context->maxTextureSize();
  } else if (graphite_shared_context) {
    capabilities_.max_render_target_size =
        graphite_shared_context->maxTextureSize();
    capabilities_.max_texture_size = graphite_shared_context->maxTextureSize();
  } else {
    // Unit tests may create SkiaOutputDevice without a GPU context.
    capabilities_.max_render_target_size = 8192;
    capabilities_.max_texture_size = 8192;
  }
}

SkiaOutputDevice::~SkiaOutputDevice() = default;

std::unique_ptr<SkiaOutputDevice::ScopedPaint>
SkiaOutputDevice::BeginScopedPaint() {
  std::vector<GrBackendSemaphore> end_semaphores;
  SkSurface* sk_surface = BeginPaint(&end_semaphores);
  if (!sk_surface) {
    return nullptr;
  }
  return std::make_unique<SkiaOutputDevice::ScopedPaint>(
      std::move(end_semaphores), this, sk_surface);
}

void SkiaOutputDevice::Submit(
    scoped_refptr<gpu::SharedContextState> context_state,
    bool sync_cpu,
    base::OnceClosure callback) {
  if (auto* gr_context = context_state->gr_context()) {
    gr_context->submit(sync_cpu ? GrSyncCpu::kYes : GrSyncCpu::kNo);
  } else {
    auto* graphite_shared_context = context_state->graphite_shared_context();
    CHECK(graphite_shared_context);

    const bool should_record_metric = base::ShouldRecordSubsampledMetric(0.01);
    base::TimeTicks start_time;
    if (should_record_metric) {
      start_time = base::TimeTicks::Now();
    }

    graphite_shared_context->submit(sync_cpu ? skgpu::graphite::SyncToCpu::kYes
                                             : skgpu::graphite::SyncToCpu::kNo);
    if (should_record_metric) {
      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
          "GPU.Graphite.SkiaOutputDeviceSubmitDurationUs",
          base::TimeTicks::Now() - start_time, base::Microseconds(1),
          base::Seconds(1), 50);
    }
  }
  std::move(callback).Run();
}

void SkiaOutputDevice::ScheduleOverlays(
    SkiaOutputSurface::OverlayList overlays) {
  NOTIMPLEMENTED();
}

void SkiaOutputDevice::EnsureBackbuffer() {}
void SkiaOutputDevice::DiscardBackbuffer() {}

void SkiaOutputDevice::SetDrawTimings(base::TimeTicks submitted,
                                      base::TimeTicks started) {
  viz_scheduled_draw_ = submitted;
  gpu_started_draw_ = started;
}

void SkiaOutputDevice::SetDependencyTimings(base::TimeTicks task_ready) {
  gpu_task_ready_ = task_ready;
}

void SkiaOutputDevice::SetOverlayStartTimings(
    base::TimeTicks gpu_start_overlay) {
  gpu_started_overlay_ = gpu_start_overlay;
}

void SkiaOutputDevice::ReadbackForTesting(
    base::OnceCallback<void(SkBitmap)> callback) {
  NOTIMPLEMENTED();
}

void SkiaOutputDevice::StartSwapBuffers(BufferPresentedCallback feedback) {
  DCHECK_LT(static_cast<int>(pending_swaps_.size()),
            capabilities_.pending_swap_params.GetMax());

  pending_swaps_.emplace(++swap_id_, std::move(feedback), viz_scheduled_draw_,
                         gpu_started_draw_, gpu_task_ready_,
                         gpu_started_overlay_);
  viz_scheduled_draw_ = base::TimeTicks();
  gpu_started_draw_ = base::TimeTicks();
  gpu_task_ready_ = base::TimeTicks();
  gpu_started_overlay_ = base::TimeTicks();
}

void SkiaOutputDevice::FinishSwapBuffers(
    gfx::SwapCompletionResult result,
    const gfx::Size& size,
    OutputSurfaceFrame frame,
    const std::optional<gfx::Rect>& damage_area,
    std::vector<gpu::Mailbox> released_overlays) {
  DCHECK(!pending_swaps_.empty());

  TRACE_EVENT(
      "viz,benchmark,graphics.pipeline", "Graphics.Pipeline",
      perfetto::Flow::Global(frame.data.swap_trace_id),
      [swap_trace_id = frame.data.swap_trace_id](perfetto::EventContext ctx) {
        base::TaskAnnotator::EmitTaskTimingDetails(ctx);
        auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
        auto* data = event->set_chrome_graphics_pipeline();
        data->set_step(perfetto::protos::pbzero::ChromeGraphicsPipeline::
                           StepName::STEP_FINISH_BUFFER_SWAP);
        data->set_display_trace_id(swap_trace_id);
      });

  auto release_fence = std::move(result.release_fence);
  gpu::SwapBuffersCompleteParams params = pending_swaps_.front().Complete(
      std::move(result), damage_area, std::move(released_overlays),
      frame.data.swap_trace_id);
  const auto timings = params.swap_response.timings;

  did_swap_buffer_complete_callback_.Run(std::move(params), size,
                                         std::move(release_fence));

  pending_swaps_.front().CallFeedback();

  ReportLatency(timings, std::move(frame.latency_info));

  pending_swaps_.pop();

  // If there are skipped swaps at the front of the queue, they are now ready
  // to be acknowledged without breaking ordering.
  if (!pending_swaps_.empty()) {
    auto iter = skipped_swap_info_.find(pending_swaps_.front().SwapId());
    if (iter != skipped_swap_info_.end()) {
      OutputSurfaceFrame output_frame = std::move(iter->second);
      gfx::Size frame_size = output_frame.size;
      skipped_swap_info_.erase(iter);
      // Recursively call into FinishSwapBuffers until the head of the queue is
      // no longer a skipped swap.
      FinishSwapBuffers(
          gfx::SwapCompletionResult(gfx::SwapResult::SWAP_SKIPPED), frame_size,
          std::move(output_frame));
    }
  }
}

void SkiaOutputDevice::SwapBuffersSkipped(BufferPresentedCallback feedback,
                                          OutputSurfaceFrame frame) {
  StartSwapBuffers(std::move(feedback));
  // If there are no other pending swaps, we can immediately close out the
  // "skipped" swap that was just enqueued. If there are outstanding pending
  // swaps, however, we need to wait for them to complete to avoid reordering
  // complete/presentation callbacks.
  if (pending_swaps_.size() == 1) {
    gfx::Size frame_size = frame.size;
    FinishSwapBuffers(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_SKIPPED),
                      frame_size, std::move(frame));
  } else {
    skipped_swap_info_[swap_id_] = std::move(frame);
  }
}

SkiaOutputDevice::SwapInfo::SwapInfo(
    uint64_t swap_id,
    SkiaOutputDevice::BufferPresentedCallback feedback,
    base::TimeTicks viz_scheduled_draw,
    base::TimeTicks gpu_started_draw,
    base::TimeTicks gpu_task_ready,
    base::TimeTicks gpu_started_overlay)
    : feedback_(std::move(feedback)) {
  swap_response_.swap_id = swap_id;
  swap_response_.timings.swap_start = base::TimeTicks::Now();
  swap_response_.timings.viz_scheduled_draw = viz_scheduled_draw;
  swap_response_.timings.gpu_started_draw = gpu_started_draw;
  swap_response_.timings.gpu_task_ready = gpu_task_ready;
  swap_response_.timings.gpu_started_overlay = gpu_started_overlay;
}

SkiaOutputDevice::SwapInfo::SwapInfo(SwapInfo&& other) = default;

SkiaOutputDevice::SwapInfo::~SwapInfo() = default;

uint64_t SkiaOutputDevice::SwapInfo::SwapId() {
  return swap_response_.swap_id;
}

gpu::SwapBuffersCompleteParams SkiaOutputDevice::SwapInfo::Complete(
    gfx::SwapCompletionResult result,
    const std::optional<gfx::Rect>& damage_rect,
    std::vector<gpu::Mailbox> released_overlays,
    int64_t swap_trace_id) {
  swap_response_.result = result.swap_result;
  swap_response_.timings.swap_end = base::TimeTicks::Now();

  gpu::SwapBuffersCompleteParams params;
  params.swap_response = swap_response_;
  params.frame_buffer_damage_area = damage_rect;
  params.ca_layer_params = std::move(result.ca_layer_params);
  params.released_overlays = std::move(released_overlays);
  params.swap_trace_id = swap_trace_id;
  return params;
}

void SkiaOutputDevice::SwapInfo::CallFeedback() {
  if (feedback_) {
    uint32_t flags = 0;
    if (swap_response_.result != gfx::SwapResult::SWAP_ACK) {
      flags = gfx::PresentationFeedback::Flags::kFailure;
    }

    std::move(feedback_).Run(
        gfx::PresentationFeedback(swap_response_.timings.swap_start,
                                  /*interval=*/base::TimeDelta(), flags));
  }
}

SkCanvas* SkiaOutputDevice::GetCanvas(SkSurface* sk_surface) {
  return sk_surface->getCanvas();
}

GrSemaphoresSubmitted SkiaOutputDevice::Flush(
    SkSurface* sk_surface,
    VulkanContextProvider* vulkan_context_provider,
    std::vector<GrBackendSemaphore> end_semaphores,
    base::OnceClosure on_finished) {
  CHECK(sk_surface);
  GrFlushInfo flush_info = {
      .fNumSemaphores = end_semaphores.size(),
      .fSignalSemaphores = end_semaphores.data(),
  };
  gpu::AddVulkanCleanupTaskForSkiaFlush(vulkan_context_provider, &flush_info);
  if (on_finished)
    gpu::AddCleanupTaskForSkiaFlush(std::move(on_finished), &flush_info);
  if (GrDirectContext* direct_context =
          GrAsDirectContext(sk_surface->recordingContext())) {
    return direct_context->flush(sk_surface, flush_info);
  }
  return {};
}

bool SkiaOutputDevice::Wait(SkSurface* sk_surface,
                            int num_semaphores,
                            const GrBackendSemaphore wait_semaphores[],
                            bool delete_semaphores_after_wait) {
  return sk_surface->wait(num_semaphores, wait_semaphores,
                          delete_semaphores_after_wait);
}

bool SkiaOutputDevice::Draw(SkSurface* sk_surface,
                            sk_sp<const GrDeferredDisplayList> ddl) {
#if DCHECK_IS_ON()
  const auto& characterization = ddl->characterization();
  if (!sk_surface->isCompatible(characterization)) {
    GrSurfaceCharacterization surface_characterization;
    DCHECK(sk_surface->characterize(&surface_characterization));
#define CHECK_PROPERTY(name) \
  DCHECK_EQ(characterization.name(), surface_characterization.name());
    CHECK_PROPERTY(cacheMaxResourceBytes);
    CHECK_PROPERTY(origin);
    CHECK_PROPERTY(width);
    CHECK_PROPERTY(height);
    CHECK_PROPERTY(colorType);
    CHECK_PROPERTY(sampleCount);
    CHECK_PROPERTY(isTextureable);
    CHECK_PROPERTY(isMipMapped);
    CHECK_PROPERTY(usesGLFBO0);
    CHECK_PROPERTY(vkRTSupportsInputAttachment);
    CHECK_PROPERTY(vulkanSecondaryCBCompatible);
    CHECK_PROPERTY(isProtected);
  }
#endif
  return skgpu::ganesh::DrawDDL(sk_surface, ddl);
}

bool SkiaOutputDevice::Draw(
    gpu::GraphiteSharedContext* graphite_shared_context,
    SkSurface* sk_surface,
    std::unique_ptr<skgpu::graphite::Recording> graphite_recording,
    base::OnceClosure on_finished) {
  CHECK(sk_surface);
  CHECK(graphite_recording);
  CHECK(graphite_shared_context);
  skgpu::graphite::InsertRecordingInfo info;
  info.fRecording = graphite_recording.get();
  info.fTargetSurface = sk_surface;
  if (on_finished) {
    gpu::AddCleanupTaskForGraphiteRecording(std::move(on_finished), &info);
  }
  return graphite_shared_context->insertRecording(info);
}

}  // namespace viz
