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

#include <utility>

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/task/common/task_annotator.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/service/display/output_surface_client.h"
#include "components/viz/service/display/output_surface_frame.h"
#include "components/viz/service/display/software_output_device.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/gfx/swap_result.h"
#include "ui/gfx/vsync_provider.h"
#include "ui/latency/latency_info.h"

namespace viz {

SoftwareOutputSurface::SoftwareOutputSurface(
    std::unique_ptr<SoftwareOutputDevice> device)
    : OutputSurface(std::move(device)) {
  capabilities_.pending_swap_params.max_pending_swaps =
      software_device()->MaxFramesPending();
  capabilities_.resize_based_on_root_surface =
      software_device()->SupportsOverridePlatformSize();
}

SoftwareOutputSurface::~SoftwareOutputSurface() = default;

void SoftwareOutputSurface::BindToClient(OutputSurfaceClient* client) {
  DCHECK(client);
  DCHECK(!client_);
  client_ = client;
}

void SoftwareOutputSurface::EnsureBackbuffer() {
  software_device()->EnsureBackbuffer();
}

void SoftwareOutputSurface::DiscardBackbuffer() {
  software_device()->DiscardBackbuffer();
}

void SoftwareOutputSurface::Reshape(const ReshapeParams& params) {
  software_device()->Resize(params.size, params.device_scale_factor);
}

void SoftwareOutputSurface::SwapBuffers(OutputSurfaceFrame frame) {
  DCHECK(client_);
  base::TimeTicks swap_time = base::TimeTicks::Now();
  for (auto& latency : frame.latency_info) {
    latency.AddLatencyNumberWithTimestamp(
        ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, swap_time);
    latency.AddLatencyNumberWithTimestamp(
        ui::INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT, swap_time);
  }

  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_BUFFER_SWAP_POST_SUBMIT);
        data->set_display_trace_id(swap_trace_id);
      });

  software_device()->OnSwapBuffers(
      base::BindOnce(&SoftwareOutputSurface::SwapBuffersCallback,
                     weak_factory_.GetWeakPtr(), swap_time,
                     frame.data.swap_trace_id),
      frame.data);

  gfx::VSyncProvider* vsync_provider = software_device()->GetVSyncProvider();
  if (vsync_provider && update_vsync_parameters_callback_) {
    vsync_provider->GetVSyncParameters(
        base::BindOnce(&SoftwareOutputSurface::UpdateVSyncParameters,
                       weak_factory_.GetWeakPtr()));
  }
}

void SoftwareOutputSurface::SwapBuffersCallback(base::TimeTicks swap_time,
                                                int64_t swap_trace_id,
                                                const gfx::Size& pixel_size) {
  TRACE_EVENT(
      "viz,benchmark,graphics.pipeline", "Graphics.Pipeline",
      perfetto::Flow::Global(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);
      });

  gpu::SwapBuffersCompleteParams params;
  params.swap_response.timings = {swap_time, swap_time};
  params.swap_response.result = gfx::SwapResult::SWAP_ACK;
  params.swap_trace_id = swap_trace_id;
  client_->DidReceiveSwapBuffersAck(std::move(params),
                                    /*release_fence=*/gfx::GpuFenceHandle());

  base::TimeTicks now = base::TimeTicks::Now();
  base::TimeDelta interval_to_next_refresh =
      now.SnappedToNextTick(refresh_timebase_, refresh_interval_) - now;
#if BUILDFLAG(IS_LINUX)
  if (needs_swap_size_notifications_)
    client_->DidSwapWithSize(pixel_size);
#endif
  gfx::PresentationFeedback feedback(now, interval_to_next_refresh, 0u);
  feedback.display_trace_id = swap_trace_id;
  client_->DidReceivePresentationFeedback(feedback);
}

void SoftwareOutputSurface::UpdateVSyncParameters(base::TimeTicks timebase,
                                                  base::TimeDelta interval) {
  DCHECK(update_vsync_parameters_callback_);
  refresh_timebase_ = timebase;
  // We should not be receiving 0 intervals.
  refresh_interval_ =
      interval.is_zero() ? BeginFrameArgs::DefaultInterval() : interval;
  update_vsync_parameters_callback_.Run(timebase, refresh_interval_);
}

void SoftwareOutputSurface::SetUpdateVSyncParametersCallback(
    UpdateVSyncParametersCallback callback) {
  update_vsync_parameters_callback_ = std::move(callback);
}

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

#if BUILDFLAG(IS_LINUX)
void SoftwareOutputSurface::SetNeedsSwapSizeNotifications(
    bool needs_swap_size_notifications) {
  needs_swap_size_notifications_ = needs_swap_size_notifications;
}
#endif
}  // namespace viz
