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

#include <utility>

#include "base/functional/bind.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/platform_thread.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/base/histograms.h"
#include "cc/trees/layer_tree_frame_sink_client.h"
#include "components/power_scheduler/power_mode.h"
#include "components/power_scheduler/power_mode_arbiter.h"
#include "components/power_scheduler/power_mode_voter.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/hit_test/hit_test_region_list.h"
#include "components/viz/common/quads/compositor_frame.h"

#if BUILDFLAG(IS_OHOS)
#include "base/process/process_handle.h"
#endif

namespace cc {
namespace mojo_embedder {

AsyncLayerTreeFrameSink::InitParams::InitParams() = default;
AsyncLayerTreeFrameSink::InitParams::~InitParams() = default;

AsyncLayerTreeFrameSink::UnboundMessagePipes::UnboundMessagePipes() = default;
AsyncLayerTreeFrameSink::UnboundMessagePipes::~UnboundMessagePipes() = default;

bool AsyncLayerTreeFrameSink::UnboundMessagePipes::HasUnbound() const {
  return client_receiver.is_valid() &&
         (compositor_frame_sink_remote.is_valid() ^
          compositor_frame_sink_associated_remote.is_valid());
}

AsyncLayerTreeFrameSink::UnboundMessagePipes::UnboundMessagePipes(
    UnboundMessagePipes&& other) = default;

AsyncLayerTreeFrameSink::AsyncLayerTreeFrameSink(
    scoped_refptr<viz::ContextProvider> context_provider,
    scoped_refptr<RasterContextProviderWrapper> worker_context_provider_wrapper,
    InitParams* params)
    : LayerTreeFrameSink(std::move(context_provider),
                         std::move(worker_context_provider_wrapper),
                         std::move(params->compositor_task_runner),
                         params->gpu_memory_buffer_manager),
      synthetic_begin_frame_source_(
          std::move(params->synthetic_begin_frame_source)),
#if BUILDFLAG(IS_ANDROID)
      io_thread_id_(params->io_thread_id),
#endif
      pipes_(std::move(params->pipes)),
      wants_animate_only_begin_frames_(params->wants_animate_only_begin_frames),
      power_mode_voter_("PowerModeVoter.Animation") {
  DETACH_FROM_THREAD(thread_checker_);
}

AsyncLayerTreeFrameSink::~AsyncLayerTreeFrameSink() {}

bool AsyncLayerTreeFrameSink::BindToClient(LayerTreeFrameSinkClient* client) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (!LayerTreeFrameSink::BindToClient(client))
    return false;

  DCHECK(pipes_.HasUnbound());
  if (pipes_.compositor_frame_sink_remote.is_valid()) {
    compositor_frame_sink_.Bind(std::move(pipes_.compositor_frame_sink_remote));
    compositor_frame_sink_.set_disconnect_with_reason_handler(
        base::BindOnce(&AsyncLayerTreeFrameSink::OnMojoConnectionError,
                       weak_factory_.GetWeakPtr()));
    compositor_frame_sink_ptr_ = compositor_frame_sink_.get();
  } else if (pipes_.compositor_frame_sink_associated_remote.is_valid()) {
    compositor_frame_sink_associated_.Bind(
        std::move(pipes_.compositor_frame_sink_associated_remote));
    compositor_frame_sink_associated_.set_disconnect_with_reason_handler(
        base::BindOnce(&AsyncLayerTreeFrameSink::OnMojoConnectionError,
                       weak_factory_.GetWeakPtr()));
    compositor_frame_sink_ptr_ = compositor_frame_sink_associated_.get();
  }
  client_receiver_.Bind(std::move(pipes_.client_receiver),
                        compositor_task_runner_);

  if (synthetic_begin_frame_source_) {
    client->SetBeginFrameSource(synthetic_begin_frame_source_.get());
  } else {
    begin_frame_source_ = std::make_unique<viz::ExternalBeginFrameSource>(this);
    begin_frame_source_->OnSetBeginFrameSourcePaused(begin_frames_paused_);
    client->SetBeginFrameSource(begin_frame_source_.get());
  }

  if (wants_animate_only_begin_frames_)
    compositor_frame_sink_->SetWantsAnimateOnlyBeginFrames();
  compositor_frame_sink_ptr_->SetWantsBeginFrameAcks();

  compositor_frame_sink_ptr_->InitializeCompositorFrameSinkType(
      viz::mojom::CompositorFrameSinkType::kLayerTree);

#if BUILDFLAG(IS_ANDROID)
  std::vector<int32_t> thread_ids;
  thread_ids.push_back(base::PlatformThread::CurrentId());
  if (io_thread_id_ != base::kInvalidThreadId)
    thread_ids.push_back(io_thread_id_);
  compositor_frame_sink_ptr_->SetThreadIds(thread_ids);
#endif

#if BUILDFLAG(IS_OHOS)
  std::vector<int32_t> thread_ids;
  thread_ids.push_back(base::PlatformThread::CurrentRealId());
  bool is_created = true;
  compositor_frame_sink_ptr_->ReportKeyThreadIds(
      thread_ids, base::GetCurrentRealPid(), is_created);
#endif

#if defined(OHOS_SOFTWARE_COMPOSITOR)
  if (software_renderer_ohos_) {
    software_renderer_ohos_->BindToClient(client, begin_frame_source_.get());
  }
#endif

  return true;
}

void AsyncLayerTreeFrameSink::DetachFromClient() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  client_->SetBeginFrameSource(nullptr);
  begin_frame_source_.reset();
  synthetic_begin_frame_source_.reset();
  client_receiver_.reset();
  // `compositor_frame_sink_ptr_` points to either `compositor_frame_sink_` or
  // `compositor_frame_sink_associated_`, so it must be set to nullptr first.
  compositor_frame_sink_ptr_ = nullptr;
  compositor_frame_sink_.reset();
  compositor_frame_sink_associated_.reset();

#if defined(OHOS_SOFTWARE_COMPOSITOR)
  if (software_renderer_ohos_) {
    software_renderer_ohos_->DetachFromClient();
  }
#endif

  LayerTreeFrameSink::DetachFromClient();
}

void AsyncLayerTreeFrameSink::SetLocalSurfaceId(
    const viz::LocalSurfaceId& local_surface_id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(local_surface_id.is_valid());
  local_surface_id_ = local_surface_id;
}

void AsyncLayerTreeFrameSink::SubmitCompositorFrame(
    viz::CompositorFrame frame,
    bool hit_test_data_changed) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(compositor_frame_sink_ptr_);
  DCHECK(frame.metadata.begin_frame_ack.has_damage);
  DCHECK(frame.metadata.begin_frame_ack.frame_id.IsSequenceValid());

  TRACE_EVENT_WITH_FLOW2(
      "viz,benchmark", "Graphics.Pipeline",
      TRACE_ID_GLOBAL(frame.metadata.begin_frame_ack.trace_id),
      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step",
      "SubmitCompositorFrame", "local_surface_id",
      local_surface_id_.ToString());

  OHOS_TRACE_EVENT2("viz,benchmark", "Graphics.Pipeline", "trace_id",
                    std::to_string(frame.metadata.begin_frame_ack.trace_id), "step", "SubmitCompositorFrame");

  if (local_surface_id_ == last_submitted_local_surface_id_) {
    DCHECK_EQ(last_submitted_device_scale_factor_, frame.device_scale_factor());
    DCHECK_EQ(last_submitted_size_in_pixels_.height(),
              frame.size_in_pixels().height());
    DCHECK_EQ(last_submitted_size_in_pixels_.width(),
              frame.size_in_pixels().width());
  }

  absl::optional<viz::HitTestRegionList> hit_test_region_list =
      client_->BuildHitTestData();

  // If |hit_test_data_changed| was set or local_surface_id has been updated,
  // we always send hit-test data; otherwise we check for equality with the
  // last submitted hit-test data for possible optimization.
  if (!hit_test_region_list) {
    last_hit_test_data_ = viz::HitTestRegionList();
  } else if (!hit_test_data_changed &&
             local_surface_id_ == last_submitted_local_surface_id_) {
    if (viz::HitTestRegionList::IsEqual(*hit_test_region_list,
                                        last_hit_test_data_)) {
      DCHECK(!viz::HitTestRegionList::IsEqual(*hit_test_region_list,
                                              viz::HitTestRegionList()));
      hit_test_region_list = absl::nullopt;
    } else {
      last_hit_test_data_ = *hit_test_region_list;
    }
  } else {
    last_hit_test_data_ = *hit_test_region_list;
  }

#if BUILDFLAG(IS_OHOS)
  if (is_first_submit_) {
    is_first_submit_ = false;
    LOG(INFO) << "web render log: first call SubmitCompositorFrame, local_surface_id = "
      << local_surface_id_.ToString();
  }
#endif

  if (last_submitted_local_surface_id_ != local_surface_id_) {
    last_submitted_local_surface_id_ = local_surface_id_;
    last_submitted_device_scale_factor_ = frame.device_scale_factor();
    last_submitted_size_in_pixels_ = frame.size_in_pixels();

    // These traces are split into two due to the incoming flow using
    // TRACE_ID_LOCAL, and the outgoing flow using TRACE_ID_GLOBAL. This is
    // needed to ensure the incoming flow is not messed up. The outgoing flow is
    // going to a different process.
    TRACE_EVENT_WITH_FLOW2(
        TRACE_DISABLED_BY_DEFAULT("viz.surface_id_flow"),
        "LocalSurfaceId.Submission.Flow",
        TRACE_ID_LOCAL(local_surface_id_.submission_trace_id()),
        TRACE_EVENT_FLAG_FLOW_IN, "step", "SubmitCompositorFrame", "surface_id",
        local_surface_id_.ToString());
    TRACE_EVENT_WITH_FLOW2(
        TRACE_DISABLED_BY_DEFAULT("viz.surface_id_flow"),
        "LocalSurfaceId.Submission.Flow",
        TRACE_ID_GLOBAL(local_surface_id_.submission_trace_id()),
        TRACE_EVENT_FLAG_FLOW_OUT, "step", "SubmitCompositorFrame",
        "surface_id", local_surface_id_.ToString());
  }

  // The trace_id is negated in order to keep the Graphics.Pipeline and
  // Event.Pipeline flows separated.
  const int64_t trace_id = ~frame.metadata.begin_frame_ack.trace_id;
  TRACE_EVENT_WITH_FLOW1(TRACE_DISABLED_BY_DEFAULT("viz.hit_testing_flow"),
                         "Event.Pipeline", TRACE_ID_GLOBAL(trace_id),
                         TRACE_EVENT_FLAG_FLOW_OUT, "step",
                         "SubmitHitTestData");

  power_mode_voter_.OnFrameProduced(frame.render_pass_list.back()->damage_rect,
                                    frame.device_scale_factor());

#if defined(OHOS_SOFTWARE_COMPOSITOR)
  if (software_renderer_ohos_ && software_renderer_ohos_->InSoftwareDraw()) {
    software_renderer_ohos_->DrawAndSwapOnRenderer(std::move(frame));
    return;
  }
#endif
  compositor_frame_sink_ptr_->SubmitCompositorFrame(
      local_surface_id_, std::move(frame), std::move(hit_test_region_list), 0);
}

void AsyncLayerTreeFrameSink::DidNotProduceFrame(const viz::BeginFrameAck& ack,
                                                 FrameSkippedReason reason) {
  DCHECK(compositor_frame_sink_ptr_);
  DCHECK(!ack.has_damage);
  DCHECK(ack.frame_id.IsSequenceValid());
  TRACE_EVENT_WITH_FLOW2("viz,benchmark", "Graphics.Pipeline",
                         TRACE_ID_GLOBAL(ack.trace_id),
                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
                         "step", "DidNotProduceFrame", "reason", reason);
  std::string trace_content = "step: DidNotProduceFrame, reason: " + std::to_string(static_cast<int32_t>(reason));
  OHOS_TRACE_EVENT2("viz,benchmark", "Graphics.Pipeline", "trace_id",
      std::to_string(ack.trace_id), "trace_content", trace_content);

  bool frame_completed = reason == FrameSkippedReason::kNoDamage;
  bool waiting_on_main = reason == FrameSkippedReason::kWaitingOnMain;
  power_mode_voter_.OnFrameSkipped(frame_completed, waiting_on_main);
  compositor_frame_sink_ptr_->DidNotProduceFrame(ack);
}

void AsyncLayerTreeFrameSink::DidAllocateSharedBitmap(
    base::ReadOnlySharedMemoryRegion region,
    const viz::SharedBitmapId& id) {
  DCHECK(compositor_frame_sink_ptr_);
  compositor_frame_sink_ptr_->DidAllocateSharedBitmap(std::move(region), id);
}

void AsyncLayerTreeFrameSink::DidDeleteSharedBitmap(
    const viz::SharedBitmapId& id) {
  DCHECK(compositor_frame_sink_ptr_);
  compositor_frame_sink_ptr_->DidDeleteSharedBitmap(id);
}

void AsyncLayerTreeFrameSink::DidReceiveCompositorFrameAck(
    std::vector<viz::ReturnedResource> resources) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  client_->ReclaimResources(std::move(resources));
  client_->DidReceiveCompositorFrameAck();
}

void AsyncLayerTreeFrameSink::OnBeginFrame(
    const viz::BeginFrameArgs& args,
    const viz::FrameTimingDetailsMap& timing_details,
    bool frame_ack,
    std::vector<viz::ReturnedResource> resources) {
  if (features::IsOnBeginFrameAcksEnabled()) {
    if (frame_ack) {
      DidReceiveCompositorFrameAck(std::move(resources));
    } else if (!resources.empty()) {
      ReclaimResources(std::move(resources));
    }
  }

  for (const auto& pair : timing_details) {
    client_->DidPresentCompositorFrame(pair.first, pair.second);
  }

  if (!needs_begin_frames_) {
    TRACE_EVENT_WITH_FLOW1("viz,benchmark", "Graphics.Pipeline",
                           TRACE_ID_GLOBAL(args.trace_id),
                           TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
                           "step", "ReceiveBeginFrameDiscard");
    OHOS_TRACE_EVENT2("viz,benchmark", "Graphics.Pipeline", "trace_id",
      std::to_string(args.trace_id), "step", "ReceiveBeginFrameDiscard");
    // We had a race with SetNeedsBeginFrame(false) and still need to let the
    // sink know that we didn't use this BeginFrame. OnBeginFrame() can also be
    // called to deliver presentation feedback.
    DidNotProduceFrame(viz::BeginFrameAck(args, false),
                       FrameSkippedReason::kNoDamage);
    return;
  }
  TRACE_EVENT_WITH_FLOW2(
      "viz,benchmark", "Graphics.Pipeline", TRACE_ID_GLOBAL(args.trace_id),
      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step",
      "ReceiveBeginFrame", "frame_sequence", args.frame_id.sequence_number);
  SetDrawRect(args.draw_rect);
  OHOS_TRACE_EVENT2("viz,benchmark", "Graphics.Pipeline", "trace_id",
                    std::to_string(args.trace_id), "step", "ReceiveBeginFrame");
  if (begin_frame_source_)
    begin_frame_source_->OnBeginFrame(args);
}

void AsyncLayerTreeFrameSink::OnBeginFramePausedChanged(bool paused) {
  begin_frames_paused_ = paused;
  if (begin_frame_source_)
    begin_frame_source_->OnSetBeginFrameSourcePaused(paused);
}

void AsyncLayerTreeFrameSink::ReclaimResources(
    std::vector<viz::ReturnedResource> resources) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  client_->ReclaimResources(std::move(resources));
}

void AsyncLayerTreeFrameSink::OnCompositorFrameTransitionDirectiveProcessed(
    uint32_t sequence_id) {
  client_->OnCompositorFrameTransitionDirectiveProcessed(sequence_id);
}

void AsyncLayerTreeFrameSink::OnNeedsBeginFrames(bool needs_begin_frames) {
  DCHECK(compositor_frame_sink_ptr_);
  if (needs_begin_frames_ != needs_begin_frames) {
    if (needs_begin_frames) {
      TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("cc,benchmark", "NeedsBeginFrames",
                                        this);
    } else {
      TRACE_EVENT_NESTABLE_ASYNC_END0("cc,benchmark", "NeedsBeginFrames", this);
    }
    power_mode_voter_.OnNeedsBeginFramesChanged(needs_begin_frames);
  }
  needs_begin_frames_ = needs_begin_frames;
  compositor_frame_sink_ptr_->SetNeedsBeginFrame(needs_begin_frames);
}

void AsyncLayerTreeFrameSink::OnMojoConnectionError(
    uint32_t custom_reason,
    const std::string& description) {
  // TODO(rivr): Use DLOG(FATAL) once crbug.com/1043899 is resolved.
  if (custom_reason)
    DLOG(ERROR) << description;
  if (client_)
    client_->DidLoseLayerTreeFrameSink();
}

void AsyncLayerTreeFrameSink::SetDrawRect(const gfx::Rect& new_rect) {
  if (new_rect.IsEmpty()) {
    return;
  }
  client_->SetDrawRectState(true);
  client_->SetExternalTilePriorityConstraints(new_rect, gfx::Transform());
}

#if defined(OHOS_SOFTWARE_COMPOSITOR)
void AsyncLayerTreeFrameSink::InitSoftwareCompositorRender(
    SoftwareCompositorRegistryOhos* registry) {
  software_renderer_ohos_ =
      std::make_unique<SoftwareCompositorRendererOhos>(this, registry);
}
#endif

#if BUILDFLAG(IS_OHOS)
void AsyncLayerTreeFrameSink::TriggerVsyncImplTask() {
  TRACE_EVENT1("cc", "AsyncLayerTreeFrameSink::TriggerVsyncImplTask",
    "res", !compositor_frame_sink_ptr_);
  DCHECK(compositor_frame_sink_ptr_);

  compositor_frame_sink_ptr_->TriggerVsyncImplTask();
}

void AsyncLayerTreeFrameSink::SetHandledTouchEvent(bool handledTouchEvent) {
  DCHECK(compositor_frame_sink_ptr_);

  compositor_frame_sink_ptr_->SetHandledTouchEvent(handledTouchEvent);
}
#endif
}  // namespace mojo_embedder
}  // namespace cc