// Copyright 2013 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/common/frame_sinks/begin_frame_args.h"

#include <utility>

#include "base/notreached.h"
#include "base/trace_event/interned_args_helper.h"
#include "base/trace_event/traced_value.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "build/build_config.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/source_location.pbzero.h"

namespace viz {

const char* BeginFrameArgs::TypeToString(BeginFrameArgsType type) {
  switch (type) {
    case BeginFrameArgs::INVALID:
      return "INVALID";
    case BeginFrameArgs::NORMAL:
      return "NORMAL";
    case BeginFrameArgs::MISSED:
      return "MISSED";
  }
  NOTREACHED();
}

namespace {
perfetto::protos::pbzero::BeginFrameArgsV2::BeginFrameArgsType
TypeToProtozeroEnum(BeginFrameArgs::BeginFrameArgsType type) {
  using pbzeroType = perfetto::protos::pbzero::BeginFrameArgsV2;
  switch (type) {
    case BeginFrameArgs::INVALID:
      return pbzeroType::BEGIN_FRAME_ARGS_TYPE_INVALID;
    case BeginFrameArgs::NORMAL:
      return pbzeroType::BEGIN_FRAME_ARGS_TYPE_NORMAL;
    case BeginFrameArgs::MISSED:
      return pbzeroType::BEGIN_FRAME_ARGS_TYPE_MISSED;
  }
  NOTREACHED();
}
}  // namespace

constexpr uint64_t BeginFrameArgs::kInvalidFrameNumber;
constexpr uint64_t BeginFrameArgs::kStartingFrameNumber;

BeginFrameId::BeginFrameId()
    : source_id(0), sequence_number(BeginFrameArgs::kInvalidFrameNumber) {}

BeginFrameId::BeginFrameId(const BeginFrameId& id) = default;
BeginFrameId& BeginFrameId::operator=(const BeginFrameId& id) = default;

BeginFrameId::BeginFrameId(uint64_t source_id, uint64_t sequence_number)
    : source_id(source_id), sequence_number(sequence_number) {}

bool BeginFrameId::IsNextInSequenceTo(const BeginFrameId& previous) const {
  return (source_id == previous.source_id &&
          sequence_number > previous.sequence_number);
}

bool BeginFrameId::IsSequenceValid() const {
  return (BeginFrameArgs::kInvalidFrameNumber != sequence_number);
}

std::string BeginFrameId::ToString() const {
  base::trace_event::TracedValueJSON value;
  value.SetInteger("source_id", source_id);
  value.SetInteger("sequence_number", sequence_number);
  return value.ToJSON();
}

PossibleDeadline::PossibleDeadline(int64_t vsync_id,
                                   base::TimeDelta latch_delta,
                                   base::TimeDelta present_delta)
    : vsync_id(vsync_id),
      latch_delta(latch_delta),
      present_delta(present_delta) {}
PossibleDeadline::PossibleDeadline(const PossibleDeadline& other) = default;
PossibleDeadline::PossibleDeadline(PossibleDeadline&& other) = default;
PossibleDeadline::~PossibleDeadline() = default;
PossibleDeadline& PossibleDeadline::operator=(const PossibleDeadline& other) =
    default;
PossibleDeadline& PossibleDeadline::operator=(PossibleDeadline&& other) =
    default;

void PossibleDeadline::SetTraceTimelineData(
    perfetto::protos::pbzero::
        AndroidChoreographerFrameCallbackData_FrameTimeline& timeline) const {
  timeline.set_vsync_id(vsync_id);
  timeline.set_latch_delta_us(latch_delta.InMicroseconds());
  timeline.set_present_delta_us(present_delta.InMicroseconds());
}

PossibleDeadlines::PossibleDeadlines(size_t os_preferred_index)
    : os_preferred_index(os_preferred_index) {}
PossibleDeadlines::PossibleDeadlines(const PossibleDeadlines& other) = default;
PossibleDeadlines::PossibleDeadlines(PossibleDeadlines&& other) = default;
PossibleDeadlines::~PossibleDeadlines() = default;
PossibleDeadlines& PossibleDeadlines::operator=(
    const PossibleDeadlines& other) = default;
PossibleDeadlines& PossibleDeadlines::operator=(PossibleDeadlines&& other) =
    default;

const PossibleDeadline& PossibleDeadlines::GetOSPreferredDeadline() const {
  return deadlines[os_preferred_index];
}

BeginFrameArgs::BeginFrameArgs()
    : frame_time(base::TimeTicks::Min()),
      deadline(base::TimeTicks::Min()),
      interval(base::Microseconds(-1)),
      unthrottled_interval(base::Microseconds(-1)),
      frame_id(BeginFrameId(0, kInvalidFrameNumber)) {}

BeginFrameArgs::~BeginFrameArgs() = default;

BeginFrameArgs::BeginFrameArgs(uint64_t source_id,
                               uint64_t sequence_number,
                               base::TimeTicks frame_time,
                               base::TimeTicks deadline,
                               base::TimeDelta interval,
                               BeginFrameArgs::BeginFrameArgsType type,
                               base::TimeDelta unthrottled_interval)
    : frame_time(frame_time),
      deadline(deadline),
      interval(interval),
      unthrottled_interval(
          unthrottled_interval.is_positive() ? unthrottled_interval : interval),
      frame_id(BeginFrameId(source_id, sequence_number)),
      type(type) {
  DCHECK_LE(kStartingFrameNumber, sequence_number);
#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
  // TODO(crbug.com/477242770): Re-enable on Mac. Changing the system display
  // refresh rate on macOS causes the unthrottled_interval state to go
  // stale, which incorrectly trips this DCHECK.
  //
  // TODO(crbug.com/477617022): Re-enable on Android.
  // ExternalBeginFrameSourceAndroid currently does not implement
  // GetMinimumFrameInterval() causing unthrottled_interval to always be
  // BeginFrameArgs::DefaultInterval()
  if (type != BeginFrameArgs::INVALID && interval.is_positive()) {
    DCHECK_LE(this->unthrottled_interval,
              this->interval * kUnthrottledIntervalJitterMultiplier);
  }
#endif
}

BeginFrameArgs::BeginFrameArgs(const BeginFrameArgs& args) = default;
BeginFrameArgs& BeginFrameArgs::operator=(const BeginFrameArgs& args) = default;

BeginFrameArgs BeginFrameArgs::Create(BeginFrameArgs::CreationLocation location,
                                      uint64_t source_id,
                                      uint64_t sequence_number,
                                      base::TimeTicks frame_time,
                                      base::TimeTicks deadline,
                                      base::TimeDelta interval,
                                      BeginFrameArgs::BeginFrameArgsType type,
                                      base::TimeDelta unthrottled_interval) {
  DCHECK_NE(type, BeginFrameArgs::INVALID);
#ifdef NDEBUG
  return BeginFrameArgs(source_id, sequence_number, frame_time, deadline,
                        interval, type, unthrottled_interval);
#else
  BeginFrameArgs args =
      BeginFrameArgs(source_id, sequence_number, frame_time, deadline, interval,
                     type, unthrottled_interval);
  args.created_from = location;
  return args;
#endif
}

std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
BeginFrameArgs::AsValue() const {
  std::unique_ptr<base::trace_event::TracedValue> state(
      new base::trace_event::TracedValue());
  AsValueInto(state.get());
  return std::move(state);
}

void BeginFrameArgs::AsValueInto(base::trace_event::TracedValue* state) const {
  state->SetString("type", "BeginFrameArgs");
  state->SetString("subtype", TypeToString(type));
  state->SetInteger("source_id", frame_id.source_id);
  state->SetInteger("sequence_number", frame_id.sequence_number);
  state->SetInteger("frames_throttled_since_last", frames_throttled_since_last);
  state->SetDouble("frame_time_us",
                   frame_time.since_origin().InMicrosecondsF());
  state->SetDouble("deadline_us", deadline.since_origin().InMicrosecondsF());
  state->SetDouble("interval_us", interval.InMicrosecondsF());
  state->SetDouble("unthrottled_interval_us",
                   unthrottled_interval.InMicrosecondsF());
#ifndef NDEBUG
  state->SetString("created_from", created_from.ToString());
#endif
  state->SetBoolean("on_critical_path", on_critical_path);
  state->SetBoolean("animate_only", animate_only);
  state->SetBoolean("has_possible_deadlines", !!possible_deadlines);
}

void BeginFrameArgs::AsProtozeroInto(
    perfetto::EventContext& ctx,
    perfetto::protos::pbzero::BeginFrameArgsV2* state) const {
  state->set_type(TypeToProtozeroEnum(type));
  state->set_source_id(frame_id.source_id);
  state->set_sequence_number(frame_id.sequence_number);
  state->set_frames_throttled_since_last(frames_throttled_since_last);
  state->set_frame_time_us(frame_time.since_origin().InMicroseconds());
  state->set_deadline_us(deadline.since_origin().InMicroseconds());
  state->set_interval_delta_us(interval.InMicroseconds());
  state->set_unthrottled_interval_delta_us(
      unthrottled_interval.InMicroseconds());
  state->set_on_critical_path(on_critical_path);
  state->set_animate_only(animate_only);
#ifndef NDEBUG
  state->set_source_location_iid(
      base::trace_event::InternedSourceLocation::Get(&ctx, created_from));
#endif
}

std::string BeginFrameArgs::ToString() const {
  base::trace_event::TracedValueJSON value;
  AsValueInto(&value);
  return value.ToJSON();
}

BeginFrameAck::BeginFrameAck(const BeginFrameArgs& args, bool has_damage)
    : frame_id(args.frame_id),
      trace_id(args.trace_id),
      has_damage(has_damage) {}

BeginFrameAck::BeginFrameAck(uint64_t source_id,
                             uint64_t sequence_number,
                             bool has_damage,
                             int64_t trace_id)
    : frame_id(BeginFrameId(source_id, sequence_number)),
      trace_id(trace_id),
      has_damage(has_damage) {
  DCHECK(frame_id.IsSequenceValid());
}

// static
BeginFrameAck BeginFrameAck::CreateManualAckWithDamage() {
  return BeginFrameAck(BeginFrameArgs::kManualSourceId,
                       BeginFrameArgs::kStartingFrameNumber, true);
}

void BeginFrameAck::AsValueInto(base::trace_event::TracedValue* dict) const {
  dict->SetInteger("source_id", static_cast<int>(frame_id.source_id));
  dict->SetInteger("sequence_number",
                   static_cast<int>(frame_id.sequence_number));
  dict->SetBoolean("has_damage", has_damage);
  dict->SetInteger("trace_id", static_cast<int>(trace_id));
}

}  // namespace viz
