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

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <utility>

#include "base/atomic_sequence_num.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"

namespace viz {

namespace {
// kDoubleTickDivisor prevents the SyntheticBFS from sending BeginFrames too
// often to an observer.
constexpr double kDoubleTickDivisor = 2.0;

base::AtomicSequenceNumber g_next_source_id;

// Generates a source_id with upper 32 bits from |restart_id| and lower 32 bits
// from an atomic sequence.
uint64_t GenerateSourceId(uint32_t restart_id) {
  return static_cast<uint64_t>(restart_id) << 32 | g_next_source_id.GetNext();
}

// Notifies the observer of the BeginFrame. If the BeginFrame is a
// animate_only BeginFrame, the observer may not be notified of the
// BeginFrame.
void FilterAndIssueBeginFrame(BeginFrameObserver* observer,
                              const BeginFrameArgs& args) {
  if (args.animate_only && !observer->WantsAnimateOnlyBeginFrames())
    return;
  observer->OnBeginFrame(args);
}

// Checks |args| for continuity with our last args.  It is possible that the
// source in which |args| originate changes, or that our hookup to this source
// changes, so we have to check for continuity.  See also
// https://crbug.com/690127 for what may happen without this check.
bool CheckBeginFrameContinuity(BeginFrameObserver* observer,
                               const BeginFrameArgs& args) {
  const BeginFrameArgs& last_args = observer->LastUsedBeginFrameArgs();
  if (!last_args.IsValid() || (args.frame_time > last_args.frame_time)) {
    DCHECK(args.frame_id.source_id == BeginFrameArgs::kManualSourceId ||
           !last_args.frame_id.IsNextInSequenceTo(args.frame_id))
        << "current " << args.ToString() << ", last " << last_args.ToString();
    return true;
  }
  return false;
}
}  // namespace

// BeginFrameObserverBase -------------------------------------------------
BeginFrameObserverBase::BeginFrameObserverBase() = default;

BeginFrameObserverBase::~BeginFrameObserverBase() = default;

const BeginFrameArgs& BeginFrameObserverBase::LastUsedBeginFrameArgs() const {
  return last_begin_frame_args_;
}

bool BeginFrameObserverBase::WantsAnimateOnlyBeginFrames() const {
  return wants_animate_only_begin_frames_;
}

void BeginFrameObserverBase::OnBeginFrame(const BeginFrameArgs& args) {
  DCHECK(args.IsValid());
  DCHECK_GE(args.frame_time, last_begin_frame_args_.frame_time);
  DCHECK((args.frame_id.source_id == BeginFrameArgs::kManualSourceId) ||
         !last_begin_frame_args_.frame_id.IsNextInSequenceTo(args.frame_id))
      << "current " << args.ToString() << ", last "
      << last_begin_frame_args_.ToString();
  bool used = OnBeginFrameDerivedImpl(args);
  if (used) {
    last_begin_frame_args_ = args;
  } else {
    ++dropped_begin_frame_args_;
  }
}

void BeginFrameObserverBase::AsProtozeroInto(
    perfetto::EventContext& ctx,
    perfetto::protos::pbzero::BeginFrameObserverStateV2* state) const {
  state->set_dropped_begin_frame_args(dropped_begin_frame_args_);

  last_begin_frame_args_.AsProtozeroInto(ctx,
                                         state->set_last_begin_frame_args());
}

BeginFrameArgs
BeginFrameSource::BeginFrameArgsGenerator::GenerateBeginFrameArgs(
    uint64_t source_id,
    base::TimeTicks frame_time,
    base::TimeTicks deadline,
    base::TimeDelta vsync_interval,
    base::TimeDelta unthrottled_interval) {
  uint64_t sequence_number =
      next_sequence_number_ +
      EstimateTickCountsBetween(frame_time, next_expected_frame_time_,
                                vsync_interval);
  next_expected_frame_time_ = deadline;
  next_sequence_number_ = sequence_number + 1;
  return BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, source_id, sequence_number, frame_time, deadline,
      vsync_interval, BeginFrameArgs::NORMAL, unthrottled_interval);
}

uint64_t BeginFrameSource::BeginFrameArgsGenerator::EstimateTickCountsBetween(
    base::TimeTicks frame_time,
    base::TimeTicks next_expected_frame_time,
    base::TimeDelta vsync_interval) {
  if (next_expected_frame_time.is_null())
    return 0;

  // kErrorMarginIntervalPct used to determine what percentage of the time tick
  // interval should be used as a margin of error when comparing times to
  // deadlines.
  constexpr double kErrorMarginIntervalPct = 0.05;
  base::TimeDelta error_margin = vsync_interval * kErrorMarginIntervalPct;
  int ticks_since_estimated_frame_time = base::ClampFloor(
      (frame_time + error_margin - next_expected_frame_time) / vsync_interval);
  return std::max(0, ticks_since_estimated_frame_time);
}

// BeginFrameSource -------------------------------------------------------

// static
constexpr uint32_t BeginFrameSource::kNotRestartableId;

BeginFrameSource::BeginFrameSource(uint32_t restart_id)
    : source_id_(GenerateSourceId(restart_id)) {}

BeginFrameSource::~BeginFrameSource() = default;

void BeginFrameSource::SetIsGpuBusy(bool busy) {
  if (is_gpu_busy_ == busy)
    return;
  is_gpu_busy_ = busy;
  if (is_gpu_busy_) {
    DCHECK_EQ(gpu_busy_response_state_, GpuBusyThrottlingState::kIdle);
    gpu_busy_start_time_ = base::TimeTicks::Now();
    return;
  }

  const bool was_throttled =
      gpu_busy_response_state_ == GpuBusyThrottlingState::kThrottled;
  gpu_busy_response_state_ = GpuBusyThrottlingState::kIdle;
  if (was_throttled) {
    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "Viz.FrameSink.GpuBusyDuration",
        base::TimeTicks::Now() - gpu_busy_start_time_, base::Microseconds(1),
        base::Seconds(5), /*bucket_count=*/100);
    OnGpuNoLongerBusy();
  }
}

void BeginFrameSource::SetSchedulerClient(SchedulerClient* scheduler_client) {
  scheduler_client_ = scheduler_client;
}

void BeginFrameSource::SetInputClient(InputClient* input_client) {
  input_client_ = input_client;
}

// static
base::flat_set<base::TimeDelta>
BeginFrameSource::GetDefaultSupportedFrameIntervals(base::TimeDelta interval) {
  return {interval, interval * 2};
}

bool BeginFrameSource::RequestCallbackOnGpuAvailable() {
  if (!is_gpu_busy_) {
    DCHECK_EQ(gpu_busy_response_state_, GpuBusyThrottlingState::kIdle);
    return false;
  }

  switch (gpu_busy_response_state_) {
    case GpuBusyThrottlingState::kIdle:
        gpu_busy_response_state_ =
            GpuBusyThrottlingState::kOneBeginFrameAfterBusySent;
        return false;
    case GpuBusyThrottlingState::kOneBeginFrameAfterBusySent:
      gpu_busy_response_state_ = GpuBusyThrottlingState::kThrottled;
      return true;
    case GpuBusyThrottlingState::kThrottled:
      return true;
  }

  NOTREACHED();
}

void BeginFrameSource::AsProtozeroInto(
    perfetto::EventContext&,
    perfetto::protos::pbzero::BeginFrameSourceStateV2* state) const {
  // The lower 32 bits of source_id are the interesting piece of |source_id_|.
  state->set_source_id(static_cast<uint32_t>(source_id_));
}

#if BUILDFLAG(IS_MAC)
void BeginFrameSource::RecordBeginFrameSourceAccuracy(base::TimeDelta delta) {
  if (base::ShouldRecordSubsampledMetric(0.001)) {
    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
        "Viz.BeginFrameSource.Accuracy.CallbackTimeError", delta,
        /*min=*/base::Microseconds(100),
        /*max=*/base::Milliseconds(33), /*bucket_count=*/30);
  }

  total_delta_ += delta.magnitude();
  frames_since_last_recording_++;

  // Emit the histogram every 3600 frames.
  constexpr int kFramesToEmitHistogram = 3600;
  if (frames_since_last_recording_ < kFramesToEmitHistogram) {
    return;
  }

  UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
      "Viz.BeginFrameSource.Accuracy.AverageDelta2",
      total_delta_ / kFramesToEmitHistogram,
      /*min=*/base::Microseconds(100),
      /*max=*/base::Milliseconds(33), /*bucket_count=*/30);
  frames_since_last_recording_ = 0;
  total_delta_ = base::TimeDelta();
}
#endif

void BeginFrameSource::IssueBeginFrameToSchedulerClient(
    const BeginFrameArgs& args) {
  if (scheduler_client_) {
    scheduler_client_->OnBeginFrameForScheduling(args);
  }
}

void BeginFrameSource::IssueBeginFrameToInputClient(
    const BeginFrameArgs& args) {
  if (input_client_) {
    input_client_->OnBeginFrameForInput(args);
  }
}

// StubBeginFrameSource ---------------------------------------------------
StubBeginFrameSource::StubBeginFrameSource()
    : BeginFrameSource(kNotRestartableId) {}

// SyntheticBeginFrameSource ----------------------------------------------
SyntheticBeginFrameSource::SyntheticBeginFrameSource(uint32_t restart_id)
    : BeginFrameSource(restart_id) {}

SyntheticBeginFrameSource::~SyntheticBeginFrameSource() = default;

// BackToBackBeginFrameSource ---------------------------------------------
BackToBackBeginFrameSource::BackToBackBeginFrameSource(
    std::unique_ptr<DelayBasedTimeSource> time_source)
    : SyntheticBeginFrameSource(kNotRestartableId),
      time_source_(std::move(time_source)),
      next_sequence_number_(BeginFrameArgs::kStartingFrameNumber) {
  time_source_->SetClient(this);
  // The time_source_ ticks immediately, so we SetActive(true) for a single
  // tick when we need it, and keep it as SetActive(false) otherwise.
  time_source_->SetTimebaseAndInterval(base::TimeTicks(), base::TimeDelta());
}

BackToBackBeginFrameSource::~BackToBackBeginFrameSource() = default;

void BackToBackBeginFrameSource::AddObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(!observers_.contains(obs));
  observers_.insert(obs);
  pending_begin_frame_observers_.insert(obs);
  obs->OnBeginFrameSourcePausedChanged(false);
  time_source_->SetActive(true);
}

void BackToBackBeginFrameSource::RemoveObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(observers_.contains(obs));
  observers_.erase(obs);
  pending_begin_frame_observers_.erase(obs);
  if (pending_begin_frame_observers_.empty()) {
    time_source_->SetActive(false);
  }
}

void BackToBackBeginFrameSource::DidFinishFrame(
    BeginFrameObserver* obs,
    DisplaySchedulerDrawResult result) {
  if (result == DisplaySchedulerDrawResult::kDrawnLate ||
      result == DisplaySchedulerDrawResult::kMayDrawLate) {
    // BackToBackBeginFrameSource does not support multiple deadlines, so it
    // should never receive late draw results.
    NOTREACHED();
  }
  if (observers_.contains(obs)) {
    pending_begin_frame_observers_.insert(obs);
    time_source_->SetActive(true);
  }
}

void BackToBackBeginFrameSource::OnGpuNoLongerBusy() {
  OnTimerTick();
}

void BackToBackBeginFrameSource::OnUpdateVSyncParameters(
    base::TimeTicks timebase,
    base::TimeDelta interval) {
  if (interval.is_zero()) {
    interval = BeginFrameArgs::DefaultInterval();
  }
  vsync_interval_ = interval;
}

void BackToBackBeginFrameSource::SetMaxVrrInterval(
    const std::optional<base::TimeDelta>& max_vrr_interval) {
  DCHECK(!max_vrr_interval.has_value() || max_vrr_interval->is_positive());
  max_vrr_interval_ = max_vrr_interval;
}

void BackToBackBeginFrameSource::OnTimerTick() {
  if (RequestCallbackOnGpuAvailable())
    return;
  if (!time_source_->Active()) {
    return;
  }
  base::TimeTicks frame_time = time_source_->LastTickTime();
  base::TimeDelta interval = max_vrr_interval_.value_or(vsync_interval_);
  BeginFrameArgs args = BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, source_id(), next_sequence_number_, frame_time,
      frame_time + interval, interval, BeginFrameArgs::NORMAL, vsync_interval_);
  next_sequence_number_++;

  // This must happen after getting the LastTickTime() from the time source.
  time_source_->SetActive(false);

  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>>
      pending_observers;
  pending_observers.swap(pending_begin_frame_observers_);
  DCHECK(!pending_observers.empty());

  IssueBeginFrameToInputClient(args);
  for (BeginFrameObserver* obs : pending_observers)
    FilterAndIssueBeginFrame(obs, args);
  IssueBeginFrameToSchedulerClient(args);
}

// DelayBasedBeginFrameSource ---------------------------------------------
DelayBasedBeginFrameSource::DelayBasedBeginFrameSource(
    std::unique_ptr<DelayBasedTimeSource> time_source,
    uint32_t restart_id)
    : SyntheticBeginFrameSource(restart_id),
      time_source_(std::move(time_source)) {
  time_source_->SetClient(this);
  last_vsync_interval_ = BeginFrameArgs::DefaultInterval();
}

DelayBasedBeginFrameSource::~DelayBasedBeginFrameSource() {
  if (max_vrr_interval_.has_value()) {
    UMA_HISTOGRAM_COUNTS_10M("Viz.BeginFrameSource.VrrFrameCount",
                             vrr_tick_count_);
  }
}

void DelayBasedBeginFrameSource::OnUpdateVSyncParameters(
    base::TimeTicks timebase,
    base::TimeDelta interval) {
  if (interval.is_zero()) {
    // TODO(brianderson): We should not be receiving 0 intervals.
    interval = BeginFrameArgs::DefaultInterval();
  }

  last_timebase_ = timebase;
  time_source_->SetTimebaseAndInterval(timebase, interval);
}

BeginFrameArgs DelayBasedBeginFrameSource::CreateBeginFrameArgs(
    base::TimeTicks frame_time) {
  base::TimeDelta interval =
      max_vrr_interval_.value_or(time_source_->Interval());
  // Use `Next-` instead of `LastTickTime` because it is snapped to
  // `last_timebase_`
  base::TimeTicks deadline =
      time_source_->NextTickTime() - time_source_->Interval() + interval;
  return begin_frame_args_generator_.GenerateBeginFrameArgs(
      source_id(), frame_time, deadline, interval, time_source_->Interval());
}

void DelayBasedBeginFrameSource::AddObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(!observers_.contains(obs));

  observers_.insert(obs);
  obs->OnBeginFrameSourcePausedChanged(false);
  SetActive(true);

  // Missed args should correspond to |last_begin_frame_args_| (particularly,
  // have the same sequence number) if |last_begin_frame_args_| still correspond
  // to the last time the time source should have ticked. This may not be the
  // case if the time source was inactive before AddObserver() was called. In
  // such a case, we create new args with a new sequence number only if
  // sufficient time has passed since the last tick.
  base::TimeTicks last_or_missed_tick_time =
      time_source_->NextTickTime() - time_source_->Interval();
  const base::TimeDelta double_tick_margin =
      max_vrr_interval_.has_value()
          ? base::TimeDelta()
          : time_source_->Interval() / kDoubleTickDivisor;
  if (!last_begin_frame_args_.IsValid() ||
      last_or_missed_tick_time >
          last_begin_frame_args_.frame_time + double_tick_margin) {
    last_begin_frame_args_ = CreateBeginFrameArgs(last_or_missed_tick_time);
  }
  if (base::FeatureList::IsEnabled(features::kNoLateBeginFrames)) {
    return;
  }
  BeginFrameArgs missed_args = last_begin_frame_args_;
  missed_args.type = BeginFrameArgs::MISSED;
  IssueBeginFrameToObserver(obs, missed_args);
}

void DelayBasedBeginFrameSource::RemoveObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(observers_.contains(obs));

  observers_.erase(obs);
  if (observers_.empty())
    SetActive(false);
}

void DelayBasedBeginFrameSource::OnGpuNoLongerBusy() {
  OnTimerTick();
}

void DelayBasedBeginFrameSource::SetMaxVrrInterval(
    const std::optional<base::TimeDelta>& max_vrr_interval) {
  DCHECK(!max_vrr_interval.has_value() || max_vrr_interval->is_positive());

  // If VRR is deactivating, record the number of frames produced.
  if (max_vrr_interval_.has_value() && !max_vrr_interval.has_value()) {
    UMA_HISTOGRAM_COUNTS_10M("Viz.BeginFrameSource.VrrFrameCount",
                             vrr_tick_count_);
    vrr_tick_count_ = 0;
  }

  max_vrr_interval_ = max_vrr_interval;
}

void DelayBasedBeginFrameSource::OnTimerTick() {
  if (RequestCallbackOnGpuAvailable())
    return;
  // In case of gpu back pressure LastTickTime can fall behind, and in case of
  // a change in vsync using (NextTickTime-interval) could be before
  // LastTickTime, so should use the latest of the two.
  last_begin_frame_args_ = CreateBeginFrameArgs(
      std::max(time_source_->LastTickTime(),
               time_source_->NextTickTime() - time_source_->Interval()));
  TRACE_EVENT2(
      "viz", "DelayBasedBeginFrameSource::OnTimerTick", "frame_time",
      last_begin_frame_args_.frame_time.since_origin().InMicroseconds(),
      "interval", last_begin_frame_args_.interval.InMicroseconds());
  if (max_vrr_interval_.has_value()) {
    vrr_tick_count_++;
  }

  IssueBeginFrameToInputClient(last_begin_frame_args_);
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers(
      observers_);
  for (BeginFrameObserver* obs : observers) {
    IssueBeginFrameToObserver(obs, last_begin_frame_args_);
  }
  IssueBeginFrameToSchedulerClient(last_begin_frame_args_);
  last_vsync_interval_ = time_source_->Interval();
}

void DelayBasedBeginFrameSource::IssueBeginFrameToObserver(
    BeginFrameObserver* obs,
    const BeginFrameArgs& args) {
  BeginFrameArgs last_args = obs->LastUsedBeginFrameArgs();

  // * If a FrameSink is throttled, |last_args.interval| is the throttled
  //   interval (e.g. 50ms) while the frame_time delta is still the actual
  //   vsync (OnTimerTick) interval.
  // * If the vsync (OnTimerTick) interval is throttled, at the first tick after
  //   throttling, the |args.interval| is updated to the throttled interval
  //   while the frame_time delta is not.
  //
  // Both cases can cause the double tick check below to fail and an unexpected
  // frame drop. To avoid this, we use the cached |last_vsync_interval_| here.
  const base::TimeDelta double_tick_margin =
      max_vrr_interval_.has_value() ? base::TimeDelta()
                                    : last_vsync_interval_ / kDoubleTickDivisor;
  if (!last_args.IsValid() ||
      (args.frame_time > last_args.frame_time + double_tick_margin)) {
    if (args.type == BeginFrameArgs::MISSED) {
      DCHECK(!last_args.frame_id.IsNextInSequenceTo(args.frame_id))
          << "missed " << args.ToString() << ", last " << last_args.ToString();
    }
    FilterAndIssueBeginFrame(obs, args);
  }
}

void DelayBasedBeginFrameSource::SetActive(bool active) {
  if (time_source_->Active() == active)
    return;
  time_source_->SetActive(active);
}

// ExternalBeginFrameSource -----------------------------------------------
ExternalBeginFrameSource::ExternalBeginFrameSource(
    ExternalBeginFrameSourceClient* client,
    uint32_t restart_id)
    : BeginFrameSource(restart_id), client_(client) {
  DCHECK(client_);
}

ExternalBeginFrameSource::~ExternalBeginFrameSource() {
  DCHECK(observers_.empty());
}

void ExternalBeginFrameSource::AsProtozeroInto(
    perfetto::EventContext& ctx,
    perfetto::protos::pbzero::BeginFrameSourceStateV2* state) const {
  BeginFrameSource::AsProtozeroInto(ctx, state);

  state->set_paused(paused_);
  state->set_num_observers(observers_.size());
  last_begin_frame_args_.AsProtozeroInto(ctx,
                                         state->set_last_begin_frame_args());
}

void ExternalBeginFrameSource::AddObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(!observers_.contains(obs));

  if (observers_.empty()) {
    client_->OnNeedsBeginFrames(true);
  }

  observers_.insert(obs);
  obs->OnBeginFrameSourcePausedChanged(paused_);

  if (base::FeatureList::IsEnabled(features::kNoLateBeginFrames)) {
    return;
  }
  // Send a MISSED begin frame if necessary.
  BeginFrameArgs missed_args = GetMissedBeginFrameArgs(obs);
  if (missed_args.IsValid()) {
    DCHECK_EQ(BeginFrameArgs::MISSED, missed_args.type);
    FilterAndIssueBeginFrame(obs, missed_args);
  }
}

void ExternalBeginFrameSource::RemoveObserver(BeginFrameObserver* obs) {
  DCHECK(obs);
  DCHECK(observers_.contains(obs));

  observers_.erase(obs);
  if (observers_.empty()) {
    client_->OnNeedsBeginFrames(false);
  }
}

void ExternalBeginFrameSource::OnGpuNoLongerBusy() {
  OnBeginFrame(pending_begin_frame_args_);
  pending_begin_frame_args_ = BeginFrameArgs();
}

void ExternalBeginFrameSource::OnSetBeginFrameSourcePaused(bool paused) {
  if (paused_ == paused)
    return;
  paused_ = paused;
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers(
      observers_);
  for (BeginFrameObserver* obs : observers) {
    obs->OnBeginFrameSourcePausedChanged(paused_);
  }
}

void ExternalBeginFrameSource::OnBeginFrame(const BeginFrameArgs& args) {
  // Ignore out of order begin frames because of layer tree frame sink being
  // recreated.
  if (last_begin_frame_args_.IsValid() &&
      (args.frame_time <= last_begin_frame_args_.frame_time ||
       (args.frame_id.source_id == last_begin_frame_args_.frame_id.source_id &&
        args.frame_id.sequence_number <=
            last_begin_frame_args_.frame_id.sequence_number)))
    return;

  if (RequestCallbackOnGpuAvailable()) {
    pending_begin_frame_args_ = args;
    return;
  }

  TRACE_EVENT(
      "viz,input.scrolling", "ExternalBeginFrameSource::OnBeginFrame",
      [&](perfetto::EventContext context) {
        // Not using `BeginFrameArgs::AsProtozeroInto()` so that we would only
        // log the relevant pieces of information and save trace space.
        auto* event =
            context.event<perfetto::protos::pbzero::ChromeTrackEvent>();
        auto* out = event->set_begin_frame_args();
        out->set_frame_time_us(args.frame_time.since_origin().InMicroseconds());
        out->set_interval_delta_us(args.interval.InMicroseconds());
      });

  if (base::ShouldRecordSubsampledMetric(0.01)) {
    // We do not expect anything more than 1/24th of a second, but let's support
    // up to 1/10th.
    //
    // Recorded on a per-frame basis, so that the results are weighted by usage,
    // and take into account all framerate changes.
    UMA_HISTOGRAM_EXACT_LINEAR("Viz.ExternalBeginFrameSource.Interval",
                               args.interval.InMilliseconds(), 100);
  }

  last_begin_frame_args_ = args;

  IssueBeginFrameToInputClient(args);
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers(
      observers_);

  for (BeginFrameObserver* obs : observers) {
    if (!CheckBeginFrameContinuity(obs, args))
      continue;
    FilterAndIssueBeginFrame(obs, args);
  }
  IssueBeginFrameToSchedulerClient(args);
}

BeginFrameArgs ExternalBeginFrameSource::GetMissedBeginFrameArgs(
    BeginFrameObserver* obs) {
  if (!last_begin_frame_args_.IsValid())
    return BeginFrameArgs();
  if (!CheckBeginFrameContinuity(obs, last_begin_frame_args_))
    return BeginFrameArgs();

  BeginFrameArgs missed_args = last_begin_frame_args_;
  missed_args.type = BeginFrameArgs::MISSED;
  return missed_args;
}

base::TimeDelta ExternalBeginFrameSource::GetMinimumFrameInterval() {
  return BeginFrameArgs::DefaultInterval();
}

base::flat_set<base::TimeDelta>
ExternalBeginFrameSource::GetSupportedFrameIntervals(base::TimeDelta interval) {
  return GetDefaultSupportedFrameIntervals(interval);
}

}  // namespace viz
