// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef GPU_COMMAND_BUFFER_SERVICE_SCHEDULER_H_
#define GPU_COMMAND_BUFFER_SERVICE_SCHEDULER_H_

#include <vector>

#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/rand_util.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "gpu/command_buffer/common/scheduling_priority.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "gpu/command_buffer/service/gpu_command_buffer_service_export.h"
#include "gpu/command_buffer/service/sequence_id.h"
#include "gpu/command_buffer/service/task_graph.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"

namespace base {
class SingleThreadTaskRunner;
}

namespace gpu {

class GPU_COMMAND_BUFFER_SERVICE_EXPORT Scheduler {
 public:
  struct GPU_COMMAND_BUFFER_SERVICE_EXPORT Task {
    // Use the signature with TaskCallback if the task needs to determine when
    // to release fence sync during task execution. Please also see comments of
    // TaskCallback.
    // Use the signatures with base::OnceClosure if the task doesn't release
    // fence sync, or the release can be done automatically after task
    // execution.
    Task(SequenceId sequence_id,
         TaskCallback task_callback,
         std::vector<SyncToken> sync_token_fences,
         const SyncToken& release,
         ReportingCallback report_callback = ReportingCallback());

    Task(SequenceId sequence_id,
         base::OnceClosure task_closure,
         std::vector<SyncToken> sync_token_fences,
         const SyncToken& release,
         ReportingCallback report_callback = ReportingCallback());

    Task(SequenceId sequence_id,
         base::OnceClosure task_closure,
         std::vector<SyncToken> sync_token_fences,
         ReportingCallback report_callback = ReportingCallback());

    Task(Task&& other);
    ~Task();
    Task& operator=(Task&& other);

    SequenceId sequence_id;

    // Only one of the two is used.
    TaskCallback task_callback;
    base::OnceClosure task_closure;

    std::vector<SyncToken> sync_token_fences;

    // The release that is expected to be reached after execution of this task.
    SyncToken release;

    ReportingCallback report_callback;
  };

  struct GPU_COMMAND_BUFFER_SERVICE_EXPORT ScopedSetSequencePriority {
   public:
    ScopedSetSequencePriority(Scheduler* scheduler,
                              SequenceId sequence_id,
                              SchedulingPriority priority);
    ~ScopedSetSequencePriority();

   private:
    const raw_ptr<Scheduler> scheduler_;
    const SequenceId sequence_id_;
  };

  explicit Scheduler(SyncPointManager* sync_point_manager);

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

  ~Scheduler() LOCKS_EXCLUDED(lock());

  // Create a sequence with given priority. Returns an identifier for the
  // sequence that can be used with SyncPointManager for creating sync point
  // release clients. Sequences start off as enabled (see |EnableSequence|).
  // Sequence is bound to the provided |task_runner|.
  SequenceId CreateSequence(
      SchedulingPriority priority,
      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
      LOCKS_EXCLUDED(lock());

  // Similar to the method above, but also creates a SyncPointClientState
  // associated with the sequence. The SyncPointClientState object is destroyed
  // when the sequence is destroyed.
  SequenceId CreateSequence(
      SchedulingPriority priority,
      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
      CommandBufferNamespace namespace_id,
      CommandBufferId command_buffer_id) LOCKS_EXCLUDED(lock());

  // Destroy the sequence and run any scheduled tasks immediately. Sequence
  // could be destroyed outside of GPU thread.
  void DestroySequence(SequenceId sequence_id) LOCKS_EXCLUDED(lock());

  [[nodiscard]] ScopedSyncPointClientState CreateSyncPointClientState(
      SequenceId sequence_id,
      CommandBufferNamespace namespace_id,
      CommandBufferId command_buffer_id) LOCKS_EXCLUDED(lock());

  // Enables the sequence so that its tasks may be scheduled.
  void EnableSequence(SequenceId sequence_id) LOCKS_EXCLUDED(lock());

  // Disables the sequence.
  void DisableSequence(SequenceId sequence_id) LOCKS_EXCLUDED(lock());

  // Gets the priority that the sequence was created with.
  SchedulingPriority GetSequenceDefaultPriority(SequenceId sequence_id)
      LOCKS_EXCLUDED(lock());

  // Changes a sequence's priority. Used in WaitForGetOffset/TokenInRange to
  // temporarily increase a sequence's priority.
  void SetSequencePriority(SequenceId sequence_id, SchedulingPriority priority)
      LOCKS_EXCLUDED(lock());

  // Schedules task to run on the sequence. The task is blocked until the sync
  // token fences are released or determined to be invalid. Tasks are run in the
  // order in which they are submitted.
  void ScheduleTask(Scheduler::Task task) LOCKS_EXCLUDED(lock());

  void ScheduleTasks(std::vector<Scheduler::Task> tasks) LOCKS_EXCLUDED(lock());

  // Continue running task on the sequence with the callback. This must be
  // called while running a previously scheduled task.
  void ContinueTask(SequenceId sequence_id, TaskCallback task_callback)
      LOCKS_EXCLUDED(lock());
  void ContinueTask(SequenceId sequence_id, base::OnceClosure task_closure)
      LOCKS_EXCLUDED(lock());

  // If the sequence should yield so that a higher priority sequence may run.
  bool ShouldYield(SequenceId sequence_id) LOCKS_EXCLUDED(lock());

  base::SingleThreadTaskRunner* GetTaskRunnerForTesting(SequenceId sequence_id)
      LOCKS_EXCLUDED(lock());

  bool graph_validation_enabled() const {
    return task_graph_.graph_validation_enabled();
  }

  TaskGraph* task_graph() { return &task_graph_; }

 private:
  struct SchedulingState {
    SchedulingState();
    SchedulingState(const SchedulingState& other);
    ~SchedulingState();

    static bool RunsBefore(const SchedulingState& lhs,
                           const SchedulingState& rhs) {
      return std::tie(lhs.priority, lhs.order_num) <
             std::tie(rhs.priority, rhs.order_num);
    }

    void WriteIntoTrace(perfetto::TracedValue context) const;

    bool operator==(const SchedulingState& rhs) const;

    SequenceId sequence_id;
    SchedulingPriority priority = SchedulingPriority::kLow;
    uint64_t order_num = 0;
  };

  // All public methods except constructor must be accessed under TaskGraph's
  // lock. Please see locking annotation of individual methods.
  class GPU_COMMAND_BUFFER_SERVICE_EXPORT Sequence
      : public TaskGraph::Sequence {
   public:
    Sequence(
        Scheduler* scheduler,
        scoped_refptr<base::SingleThreadTaskRunner> task_runner,
        SchedulingPriority priority,
        CommandBufferNamespace namespace_id = CommandBufferNamespace::INVALID,
        CommandBufferId command_buffer_id = {});

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

    ~Sequence() override EXCLUSIVE_LOCKS_REQUIRED(lock());

    base::SingleThreadTaskRunner* task_runner() const {
      return task_runner_.get();
    }

    bool enabled() const EXCLUSIVE_LOCKS_REQUIRED(lock()) { return enabled_; }

    bool scheduled() const EXCLUSIVE_LOCKS_REQUIRED(lock()) {
      return running_state_ == SCHEDULED;
    }

    bool running() const EXCLUSIVE_LOCKS_REQUIRED(lock()) {
      return running_state_ == RUNNING;
    }

    bool HasTasksAndEnabled() const EXCLUSIVE_LOCKS_REQUIRED(lock()) {
      return enabled() && HasTasks();
    }

    // A sequence is runnable if it is enabled, is not already running, and has
    // tasks in its queue. Note that this does *not* necessarily mean that its
    // first task unblocked.
    bool IsRunnable() const EXCLUSIVE_LOCKS_REQUIRED(lock()) {
      return enabled() && !running() && HasTasks();
    }

    // Returns true if this sequence should yield to another sequence. Uses the
    // cached scheduling state for comparison.
    bool ShouldYieldTo(const Sequence* other) const
        EXCLUSIVE_LOCKS_REQUIRED(lock());

    // Enables or disables the sequence.
    void SetEnabled(bool enabled) EXCLUSIVE_LOCKS_REQUIRED(lock());

    // Sets running state to SCHEDULED. Returns scheduling state for this
    // sequence used for inserting in the scheduling queue.
    SchedulingState SetScheduled() EXCLUSIVE_LOCKS_REQUIRED(lock());

    // Update cached scheduling priority while running.
    void UpdateRunningPriority() EXCLUSIVE_LOCKS_REQUIRED(lock());

    using TaskGraph::Sequence::AddTask;

    // Returns the next order number and closure. Sets running state to RUNNING.
    uint64_t BeginTask(base::OnceClosure* task_closure) override
        EXCLUSIVE_LOCKS_REQUIRED(lock());

    // Called after running the closure returned by BeginTask. Sets running
    // state to SCHEDULED.
    void FinishTask() override EXCLUSIVE_LOCKS_REQUIRED(lock());

    using TaskGraph::Sequence::ContinueTask;

    // Continue running the current task with the given closure. Must be called
    // in between |BeginTask| and |FinishTask|.
    void ContinueTask(base::OnceClosure task_closure) override
        EXCLUSIVE_LOCKS_REQUIRED(lock());

    void OnFrontTaskUnblocked(uint64_t order_num) override
        EXCLUSIVE_LOCKS_REQUIRED(lock());

    SchedulingPriority current_priority() const
        EXCLUSIVE_LOCKS_REQUIRED(lock()) {
      return current_priority_;
    }

   private:
    friend class Scheduler;

    enum RunningState { IDLE, SCHEDULED, RUNNING };

    // If the sequence is enabled. Sequences are disabled/enabled based on when
    // the command buffer is descheduled/scheduled.
    bool enabled_ GUARDED_BY(lock()) = true;

    // TODO(elgarawany): This is no longer needed. Replace with bool running_.
    RunningState running_state_ GUARDED_BY(lock()) = IDLE;

    // Cached scheduling state used for comparison with other sequences while
    // running. Updated in |SetScheduled| and |UpdateRunningPriority|.
    SchedulingState scheduling_state_ GUARDED_BY(lock());

    // Uses UnprotectedInRelease for performance reasons: no release-build
    // overhead. See raw_ptr.h for more info.
    const raw_ptr<Scheduler, UnprotectedInRelease> scheduler_ = nullptr;
    const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

    const SchedulingPriority default_priority_;
    SchedulingPriority current_priority_ GUARDED_BY(lock());
  };

  // Each thread will have its own priority queue to schedule sequences
  // created on that thread.
  struct PerThreadState {
    PerThreadState();
    PerThreadState(PerThreadState&&);
    ~PerThreadState();
    PerThreadState& operator=(PerThreadState&&);

    // Keep track of the number of sequences that post tasks to this thread, to
    // determine when map entries can be pruned.
    int num_sequences = 0;

    // Sorted list of SchedulingState that contains sequences that Runnable. Is
    // only used so that GetSortedRunnableSequences does not have to re-allocate
    // a vector. It is rebuilt at each call to GetSortedRunnableSequences.
    std::vector<SchedulingState> sorted_sequences;

    // Indicates if the scheduler is actively running tasks on this thread.
    bool running = false;

    // Indicates when the next task run was scheduled
    base::TimeTicks run_next_task_scheduled;
  };

  base::Lock& lock() const LOCK_RETURNED(task_graph_.lock()) {
    return task_graph_.lock();
  }

  Sequence* GetSequence(SequenceId sequence_id)
      EXCLUSIVE_LOCKS_REQUIRED(lock());

  PerThreadState& GetThreadState(base::SingleThreadTaskRunner* task_runner)
      EXCLUSIVE_LOCKS_REQUIRED(lock());

  void ScheduleTaskHelper(Scheduler::Task task)
      EXCLUSIVE_LOCKS_REQUIRED(lock());

  void TryScheduleSequence(Sequence* sequence) EXCLUSIVE_LOCKS_REQUIRED(lock());

  // Returns a sorted list of runnable sequences.
  const std::vector<SchedulingState>& GetSortedRunnableSequences(
      PerThreadState& thread_state) EXCLUSIVE_LOCKS_REQUIRED(lock());

  // Returns true if there are *any* unblocked tasks in sequences assigned to
  // |task_runner|. This is used to decide if RunNextTask needs to be
  // rescheduled.
  bool HasAnyUnblockedTasksOnRunner(
      const base::SingleThreadTaskRunner* task_runner) const
      EXCLUSIVE_LOCKS_REQUIRED(lock());

  // Finds the sequence of the next task that can be run under |root_sequence|'s
  // dependency graph. This function will visit sequences that are tied to other
  // threads in case other threads depends on work on this thread; however, it
  // will not run any *leaves* that are tied to other threads. nullptr is
  // returned only if DrDC is enabled and when a sequence's only dependency is a
  // sequence that is tied to another thread.
  Sequence* FindNextTaskFromRoot(Sequence* root_sequence)
      EXCLUSIVE_LOCKS_REQUIRED(lock());

  // Calls |FindNextTaskFromRoot| on the ordered list of all sequences, and
  // returns the first runnable task. Returns nullptr if there is no work to do.
  Sequence* FindNextTask() EXCLUSIVE_LOCKS_REQUIRED(lock());

  // Executes the closure of the first task for |sequence_id|. Assumes that
  // the sequence has tasks, and that the first task is unblocked.
  void ExecuteSequence(SequenceId sequence_id) LOCKS_EXCLUDED(lock());

  // The scheduler's main loop. At each tick, it will call FindNextTask on the
  // sorted list of sequences to find the highest priority available sequence.
  // The scheduler then walks the sequence's dependency graph using DFS to find
  // any unblocked task. If there are multiple choices at any node, the
  // scheduler picks the dependency with the lowest SchedulingState, effectively
  // ordering the dependencies by priority and order_num.
  void RunNextTask() LOCKS_EXCLUDED(lock());

  TaskGraph task_graph_;

  // The Sequence instances in the map are owned by `task_graph_`.
  base::flat_map<SequenceId, Sequence*> scheduler_sequence_map_
      GUARDED_BY(lock());

  base::flat_map<base::SingleThreadTaskRunner*, PerThreadState>
      per_thread_state_map_ GUARDED_BY(lock());

  // Accumulated time the thread was blocked during running task
  base::TimeDelta total_blocked_time_ GUARDED_BY(lock());

 private:
  FRIEND_TEST_ALL_PREFIXES(SchedulerTest, StreamPriorities);
};

}  // namespace gpu

#endif  // GPU_COMMAND_BUFFER_SERVICE_SCHEDULER_H_
