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

#ifndef COMPONENTS_VIZ_COMMON_FRAME_SINKS_BEGIN_FRAME_SOURCE_H_
#define COMPONENTS_VIZ_COMMON_FRAME_SINKS_BEGIN_FRAME_SOURCE_H_

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <vector>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/viz/common/display/display_scheduler_draw_result.h"
#include "components/viz/common/display/update_vsync_parameters_callback.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"

namespace perfetto {
class EventContext;
namespace protos {
namespace pbzero {
class BeginFrameObserverStateV2;
class BeginFrameSourceStateV2;
}  // namespace pbzero
}  // namespace protos
}  // namespace perfetto

namespace viz {

// (Pure) Interface for observing BeginFrame messages from BeginFrameSource
// objects.
class VIZ_COMMON_EXPORT BeginFrameObserver {
 public:
  virtual ~BeginFrameObserver() = default;

  // The |args| given to OnBeginFrame is guaranteed to have
  // |args|.IsValid()==true. If |args|.frame_id.source_id did not change
  // between invocations, |args|.frame_id.sequence_number is guaranteed to be
  // be strictly greater than the previous call. Further, |args|.frame_time is
  // guaranteed to be greater than or equal to the previous call even if the
  // source_id changes.
  //
  // Side effects: This function can (and most of the time *will*) change the
  // return value of the LastUsedBeginFrameArgs method. See the documentation
  // on that method for more information.
  //
  // The observer is required call BeginFrameSource::DidFinishFrame() as soon as
  // it has completed handling the BeginFrame.
  virtual void OnBeginFrame(const BeginFrameArgs& args) = 0;

  // Returns the last BeginFrameArgs used by the observer. This method's
  // return value is affected by the OnBeginFrame method!
  //
  //  - Before the first call of OnBeginFrame, this method should return a
  //    BeginFrameArgs on which IsValid() returns false.
  //
  //  - If the |args| passed to OnBeginFrame is (or *will be*) used, then
  //    LastUsedBeginFrameArgs return value should become the |args| given to
  //    OnBeginFrame.
  //
  //  - If the |args| passed to OnBeginFrame is dropped, then
  //    LastUsedBeginFrameArgs return value should *not* change.
  //
  // These requirements are designed to allow chaining and nesting of
  // BeginFrameObservers which filter the incoming BeginFrame messages while
  // preventing "double dropping" and other bad side effects.
  virtual const BeginFrameArgs& LastUsedBeginFrameArgs() const = 0;

  virtual void OnBeginFrameSourcePausedChanged(bool paused) = 0;

  // Whether the observer also wants to receive animate_only BeginFrames.
  virtual bool WantsAnimateOnlyBeginFrames() const = 0;
};

// Simple base class which implements a BeginFrameObserver which checks the
// incoming values meet the BeginFrameObserver requirements and implements the
// required LastUsedBeginFrameArgs behaviour.
//
// Users of this class should;
//  - Implement the OnBeginFrameDerivedImpl function.
//  - Recommended (but not required) to call
//    BeginFrameObserverBase::AsProtozeroInto in their overridden
//    AsProtozeroInto function.
class VIZ_COMMON_EXPORT BeginFrameObserverBase : public BeginFrameObserver {
 public:
  BeginFrameObserverBase();

  BeginFrameObserverBase(const BeginFrameObserverBase&) = delete;
  BeginFrameObserverBase& operator=(const BeginFrameObserverBase&) = delete;

  ~BeginFrameObserverBase() override;

  // BeginFrameObserver

  // Traces |args| and DCHECK |args| satisfies pre-conditions then calls
  // OnBeginFrameDerivedImpl and updates the last_begin_frame_args_ value on
  // true.
  void OnBeginFrame(const BeginFrameArgs& args) override;
  const BeginFrameArgs& LastUsedBeginFrameArgs() const override;
  bool WantsAnimateOnlyBeginFrames() const override;

 protected:
  // Return true if the given argument is (or will be) used.
  virtual bool OnBeginFrameDerivedImpl(const BeginFrameArgs& args) = 0;

  void AsProtozeroInto(
      perfetto::EventContext& ctx,
      perfetto::protos::pbzero::BeginFrameObserverStateV2* state) const;

  BeginFrameArgs last_begin_frame_args_;
  int64_t dropped_begin_frame_args_ = 0;
  bool wants_animate_only_begin_frames_ = false;
};

class VIZ_COMMON_EXPORT DynamicBeginFrameDeadlineOffsetSource {
 public:
  virtual ~DynamicBeginFrameDeadlineOffsetSource() = default;

  virtual base::TimeDelta GetDeadlineOffset(base::TimeDelta interval) const = 0;
};

// Interface for a class which produces BeginFrame calls to a
// BeginFrameObserver.
//
// BeginFrame calls *normally* occur just after a vsync interrupt when input
// processing has been finished and provide information about the time values
// of the vsync times. *However*, these values can be heavily modified or even
// plain made up (when no vsync signal is available or vsync throttling is
// turned off). See the BeginFrameObserver for information about the guarantees
// all BeginFrameSources *must* provide.
class VIZ_COMMON_EXPORT BeginFrameSource {
 public:
  // The `SchedulerClient` will be notified of the `BeginFrame` after all
  // `BeginFrameObservers` have first been notified. Thus guaranteeing that we
  // will know the expected state of the observers for making scheduling
  // decisions.
  class VIZ_COMMON_EXPORT SchedulerClient {
   public:
    virtual ~SchedulerClient() = default;
    virtual void OnBeginFrameForScheduling(const BeginFrameArgs& args) = 0;
  };

  // The `InputClient` will be notified of the `BeginFrame` before any
  // `BeginFrameObservers` have been notified. This is used for prioritizing
  // input handling (e.g. flings) to minimize latency. Only one `InputClient`
  // can be registered at a time.
  class VIZ_COMMON_EXPORT InputClient {
   public:
    virtual ~InputClient() = default;
    virtual void OnBeginFrameForInput(const BeginFrameArgs& args) = 0;
  };

  BeginFrameSource();

  class VIZ_COMMON_EXPORT BeginFrameArgsGenerator {
   public:
    BeginFrameArgsGenerator() = default;
    ~BeginFrameArgsGenerator() = default;

    BeginFrameArgs GenerateBeginFrameArgs(uint64_t source_id,
                                          base::TimeTicks frame_time,
                                          base::TimeTicks deadline,
                                          base::TimeDelta vsync_interval,
                                          base::TimeDelta unthrottled_interval);

   private:
    static uint64_t EstimateTickCountsBetween(
        base::TimeTicks frame_time,
        base::TimeTicks next_expected_frame_time,
        base::TimeDelta vsync_interval);

    // Used for determining what the sequence number should be on
    // CreateBeginFrameArgs.
    base::TimeTicks next_expected_frame_time_;

    // This is what the sequence number should be for any args created between
    // |next_expected_frame_time_| to |next_expected_frame_time_| + vsync
    // interval. Args created outside of this range will have their sequence
    // number assigned relative to this, based on how many intervals the frame
    // time is off.
    uint64_t next_sequence_number_ = BeginFrameArgs::kStartingFrameNumber;
  };

  // This restart_id should be used for BeginFrameSources that don't have to
  // worry about process restart. For example, if a BeginFrameSource won't
  // generate and forward BeginFrameArgs to another process or the process can't
  // crash then this constant is appropriate to use.
  static constexpr uint32_t kNotRestartableId = 0;

  // If the BeginFrameSource will generate BeginFrameArgs that are forwarded to
  // another processes *and* this process has crashed then |restart_id| should
  // be incremented. This ensures that |source_id_| is still unique after
  // process restart.
  explicit BeginFrameSource(uint32_t restart_id);

  BeginFrameSource(const BeginFrameSource&) = delete;
  BeginFrameSource& operator=(const BeginFrameSource&) = delete;

  virtual ~BeginFrameSource();

  // Returns an identifier for this BeginFrameSource. Guaranteed unique within a
  // process, but not across processes. This is used to create BeginFrames that
  // originate at this source. Note that BeginFrameSources may pass on
  // BeginFrames created by other sources, with different IDs.
  uint64_t source_id() const { return source_id_; }

  // Sets whether the gpu is busy or not. See below the documentation for
  // RequestCallbackOnGpuAvailable() for more details.
  void SetIsGpuBusy(bool busy);

  void SetSchedulerClient(SchedulerClient* scheduler_client);
  void SetInputClient(InputClient* input_client);

  static base::flat_set<base::TimeDelta> GetDefaultSupportedFrameIntervals(
      base::TimeDelta interval);

  // BeginFrameObservers use DidFinishFrame to provide back pressure to a frame
  // source about frame processing (rather than toggling SetNeedsBeginFrames
  // every frame). For example, the BackToBackFrameSource uses them to make sure
  // only one frame is pending at a time.
  virtual void DidFinishFrame(BeginFrameObserver* obs,
                              DisplaySchedulerDrawResult result) = 0;

  // Add/Remove an observer from the source. When no observers are added the BFS
  // should shut down its timers, disable vsync, etc.
  virtual void AddObserver(BeginFrameObserver* obs) = 0;
  virtual void RemoveObserver(BeginFrameObserver* obs) = 0;

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

  // Update the display ID for the source. This can change, e.g, as a window
  // moves across displays.
  virtual void SetVSyncDisplayID(int64_t display_id, bool force_update) {}

#if BUILDFLAG(IS_MAC)
  // Notifies the source that it may need to reconnect to a VSync source (e.g.,
  // DisplayLinkMac) for the specified display. This is typically triggered by
  // display configuration changes in the browser process.
  virtual void UpdateVSyncDisplay(int64_t display_id) {}

#endif

  virtual void SetUpdateVSyncParametersCallback(
      UpdateVSyncParametersCallback callback) {}

 protected:
  // Returns whether begin-frames to clients should be withheld (because the gpu
  // is still busy, for example). If this returns true, then OnGpuNoLongerBusy()
  // will be called once the gpu becomes available and the begin-frames can be
  // dispatched to clients again.
  bool RequestCallbackOnGpuAvailable();
  virtual void OnGpuNoLongerBusy() = 0;
#if BUILDFLAG(IS_MAC)
  void RecordBeginFrameSourceAccuracy(base::TimeDelta delta);
#endif
  // Notify the `SchedulerClient` of the `BeginFrame`. This is to be called by
  // subclasses only after having first called all observers.
  void IssueBeginFrameToSchedulerClient(const BeginFrameArgs& args);

  // Notify the `InputClient` of the `BeginFrame`. This is to be called by
  // subclasses before notifying any observers.
  void IssueBeginFrameToInputClient(const BeginFrameArgs& args);

 private:
  // The higher 32 bits are used for a process restart id that changes if a
  // process allocating BeginFrameSources has been restarted. The lower 32 bits
  // are allocated from an atomic sequence.
  const uint64_t source_id_;

  // The BeginFrameSource should not send the begin-frame messages to clients if
  // gpu is busy.
  bool is_gpu_busy_ = false;
  base::TimeTicks gpu_busy_start_time_;

  // Keeps track of whether a begin-frame was paused, and whether
  // OnGpuNoLongerBusy() should be invoked when the gpu is no longer busy.
  enum class GpuBusyThrottlingState {
    // No BeginFrames ticks were received since gpu was marked busy.
    kIdle,
    // One BeginFrame has been dispatched since gpu was marked busy.
    kOneBeginFrameAfterBusySent,
    // At least one BeginFrame was throttled since gpu was marked busy. If set
    // to throttled state, the sub-class is informed to send the throttled
    // BeginFrame once gpu is marked not busy.
    kThrottled
  };
  GpuBusyThrottlingState gpu_busy_response_state_ =
      GpuBusyThrottlingState::kIdle;

#if BUILDFLAG(IS_MAC)
  base::TimeDelta total_delta_;
  // The frame count since this histogram was recorded last time. It is recorded
  // every 3600 frames, which is equivalent to every minute on a 60Hz monitors .
  int frames_since_last_recording_ = 0;
#endif
  raw_ptr<SchedulerClient> scheduler_client_ = nullptr;
  raw_ptr<InputClient> input_client_ = nullptr;
};

// A BeginFrameSource that does nothing.
class VIZ_COMMON_EXPORT StubBeginFrameSource : public BeginFrameSource {
 public:
  StubBeginFrameSource();

  void DidFinishFrame(BeginFrameObserver* obs,
                      DisplaySchedulerDrawResult result) override {}
  void AddObserver(BeginFrameObserver* obs) override {}
  void RemoveObserver(BeginFrameObserver* obs) override {}
  void OnGpuNoLongerBusy() override {}
};

// A frame source which ticks itself independently.
class VIZ_COMMON_EXPORT SyntheticBeginFrameSource : public BeginFrameSource {
 public:
  explicit SyntheticBeginFrameSource(uint32_t restart_id);
  ~SyntheticBeginFrameSource() override;

  virtual void OnUpdateVSyncParameters(base::TimeTicks timebase,
                                       base::TimeDelta interval) = 0;
  // Sets the maximum interval allowable for use with VRR (variable refresh
  // rates). When set, this value should correspond to the maximum vsync
  // interval supported by the display. Absent when VRR is not enabled.
  virtual void SetMaxVrrInterval(
      const std::optional<base::TimeDelta>& max_vrr_interval) = 0;
};

// A frame source which calls BeginFrame (at the next possible time) as soon as
// an observer acknowledges the prior BeginFrame.
class VIZ_COMMON_EXPORT BackToBackBeginFrameSource
    : public SyntheticBeginFrameSource,
      public DelayBasedTimeSourceClient {
 public:
  explicit BackToBackBeginFrameSource(
      std::unique_ptr<DelayBasedTimeSource> time_source);

  BackToBackBeginFrameSource(const BackToBackBeginFrameSource&) = delete;
  BackToBackBeginFrameSource& operator=(const BackToBackBeginFrameSource&) =
      delete;

  ~BackToBackBeginFrameSource() override;

  // BeginFrameSource implementation.
  void AddObserver(BeginFrameObserver* obs) override;
  void RemoveObserver(BeginFrameObserver* obs) override;
  void DidFinishFrame(BeginFrameObserver* obs,
                      DisplaySchedulerDrawResult result) override;
  void OnGpuNoLongerBusy() override;

  // SyntheticBeginFrameSource implementation.
  void OnUpdateVSyncParameters(base::TimeTicks timebase,
                               base::TimeDelta interval) override;
  void SetMaxVrrInterval(
      const std::optional<base::TimeDelta>& max_vrr_interval) override;

  // DelayBasedTimeSourceClient implementation.
  void OnTimerTick() override;

 private:
  std::unique_ptr<DelayBasedTimeSource> time_source_;
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers_;
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>>
      pending_begin_frame_observers_;
  uint64_t next_sequence_number_;
  base::TimeDelta vsync_interval_ = BeginFrameArgs::DefaultInterval();
  std::optional<base::TimeDelta> max_vrr_interval_ = std::nullopt;
  base::WeakPtrFactory<BackToBackBeginFrameSource> weak_factory_{this};
};

// A frame source which is locked to an external parameters provides from a
// vsync source and generates BeginFrameArgs for it.
class VIZ_COMMON_EXPORT DelayBasedBeginFrameSource
    : public SyntheticBeginFrameSource,
      public DelayBasedTimeSourceClient {
 public:
  DelayBasedBeginFrameSource(std::unique_ptr<DelayBasedTimeSource> time_source,
                             uint32_t restart_id);

  DelayBasedBeginFrameSource(const DelayBasedBeginFrameSource&) = delete;
  DelayBasedBeginFrameSource& operator=(const DelayBasedBeginFrameSource&) =
      delete;

  ~DelayBasedBeginFrameSource() override;

  // BeginFrameSource implementation.
  void AddObserver(BeginFrameObserver* obs) override;
  void RemoveObserver(BeginFrameObserver* obs) override;
  void DidFinishFrame(BeginFrameObserver* obs,
                      DisplaySchedulerDrawResult result) override {}
  void OnGpuNoLongerBusy() override;

  // SyntheticBeginFrameSource implementation.
  void OnUpdateVSyncParameters(base::TimeTicks timebase,
                               base::TimeDelta interval) override;
  void SetMaxVrrInterval(
      const std::optional<base::TimeDelta>& max_vrr_interval) override;

  // DelayBasedTimeSourceClient implementation.
  void OnTimerTick() override;

  const BeginFrameArgs& last_begin_frame_args() const {
    return last_begin_frame_args_;
  }
  const DelayBasedTimeSource* time_source() const { return time_source_.get(); }

 private:
  // The created BeginFrameArgs' sequence_number is calculated based on what
  // interval |frame_time| is in. For example, if |last_frame_time_| is 100,
  // |next_sequence_number_| is 5, |last_timebase_| is 110 and the interval
  // is 20, then a |frame_time| of 175 would result in the sequence number
  // being 8 (3 intervals since 110).
  BeginFrameArgs CreateBeginFrameArgs(base::TimeTicks frame_time);
  void IssueBeginFrameToObserver(BeginFrameObserver* obs,
                                 const BeginFrameArgs& args);
  void SetActive(bool active);

  std::unique_ptr<DelayBasedTimeSource> time_source_;
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers_;
  base::TimeTicks last_timebase_;
  base::TimeDelta last_vsync_interval_;
  std::optional<base::TimeDelta> max_vrr_interval_ = std::nullopt;
  int vrr_tick_count_ = 0;
  BeginFrameArgs last_begin_frame_args_;
  BeginFrameArgsGenerator begin_frame_args_generator_;
};

class VIZ_COMMON_EXPORT ExternalBeginFrameSourceClient {
 public:
  // Only called when changed.  Assumed false by default.
  virtual void OnNeedsBeginFrames(bool needs_begin_frames) = 0;
};

// A BeginFrameSource that is only ticked manually.  Usually the endpoint
// of messages from some other thread/process that send OnBeginFrame and
// receive SetNeedsBeginFrame messages.  This turns such messages back into
// an observable BeginFrameSource.
class VIZ_COMMON_EXPORT ExternalBeginFrameSource : public BeginFrameSource {
 public:
  // Client lifetime must be preserved by owner for the lifetime of the class.
  // In order to allow derived classes to implement the client interface, no
  // calls to |client| are made during construction / destruction.
  explicit ExternalBeginFrameSource(ExternalBeginFrameSourceClient* client,
                                    uint32_t restart_id = kNotRestartableId);

  ExternalBeginFrameSource(const ExternalBeginFrameSource&) = delete;
  ExternalBeginFrameSource& operator=(const ExternalBeginFrameSource&) = delete;

  ~ExternalBeginFrameSource() override;

  // BeginFrameSource implementation.
  void AddObserver(BeginFrameObserver* obs) override;
  void RemoveObserver(BeginFrameObserver* obs) override;
  void DidFinishFrame(BeginFrameObserver* obs,
                      DisplaySchedulerDrawResult result) override {}
  void AsProtozeroInto(
      perfetto::EventContext& ctx,
      perfetto::protos::pbzero::BeginFrameSourceStateV2* state) const override;
  void OnGpuNoLongerBusy() override;

  void OnSetBeginFrameSourcePaused(bool paused);
  void OnBeginFrame(const BeginFrameArgs& args);

  const BeginFrameArgs& last_begin_frame_args() const {
    return last_begin_frame_args_;
  }

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
  // Notifies when the refresh rate of the display is updated. |refresh_rate| is
  // the rate in frames per second.
  virtual void UpdateRefreshRate(float refresh_rate) {}
#endif

  // Notifies the begin frame source of the desired frame interval for the
  // observers.
  virtual void SetPreferredInterval(base::TimeDelta interval) {}

  // Sets the refresh rates supported by the display. See
  // https://developer.android.com/reference/android/view/Display#getSupportedRefreshRates().
  //
  // `supported_rates` is a map from supported VSync intervals to the equivalent
  // supported refresh rates. For example, if the display supports 60 Hz and 120
  // Hz, `supported_rates` will contain two entries: `base::Milliseconds(8.333)`
  // → `120.0f` and `base::Milliseconds(16.666)` → `60.0f`.
  virtual void SetSupportedRefreshRates(
      const base::flat_map<base::TimeDelta, float>& supported_rates) {}

  // Returns the minimium supported frame interval for a given BFS.
  // This gives the maximium refresh rate that can be requested.
  virtual base::TimeDelta GetMinimumFrameInterval();

  virtual base::flat_set<base::TimeDelta> GetSupportedFrameIntervals(
      base::TimeDelta interval);

 protected:
  // Called on AddObserver and gets missed BeginFrameArgs for the given
  // observer. The missed BeginFrame is sent only if the returned
  // BeginFrameArgs is valid.
  virtual BeginFrameArgs GetMissedBeginFrameArgs(BeginFrameObserver* obs);

  BeginFrameArgs last_begin_frame_args_;
  base::flat_set<raw_ptr<BeginFrameObserver, CtnExperimental>> observers_;
  raw_ptr<ExternalBeginFrameSourceClient> client_;
  bool paused_ = false;

 private:
  BeginFrameArgs pending_begin_frame_args_;
};

}  // namespace viz

#endif  // COMPONENTS_VIZ_COMMON_FRAME_SINKS_BEGIN_FRAME_SOURCE_H_
