1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169

ash / wm / overview / overview_ui_task_pool.cc [blame]

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

#include "ash/wm/overview/overview_ui_task_pool.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"

// IMPLEMENTATION details:
// One animation frame typically looks like this:
// 1) OverviewUiTaskPool::OnBeginFrame() - Marks start of a new compositor
//    frame.
// 2) For the next 4-8 ms, the UI thread is occupied generating a compositor
//    frame for the next frame of the animation.
// 3) OverviewUiTaskPool::OnCompositingStarted() - Called when the step above is
//    done, and UI thread's work is complete.
// 4) This leaves several milliseconds of empty space on the UI thread to run
//    miscellaneous tasks without interrupting the compositor's schedule. We can
//    call OverviewUiTaskPool::RunNextTask() during this period. For simplicity,
//    the task pool runs at most one task at this stage.
// 5) OverviewUiTaskPool::OnBeginFrame() - Marks start of next compositor frame.
//    Typically spaced 1/60th of a second apart (~17 ms).
//
// Caveats:
// * If step 2) takes longer than expected, then by the time we arrive at step
//   4), there may not be enough time to run the the next task in the pool. To
//   handle this, the task pool estimates how much time is left before the next
//   begin frame, and if it's less than some value (`kMinTimeRequiredPerTask`),
//   then it skips this compositor frame and waits for another one to do work.
// * This is just a best effort to avoid disrupting the animation. There are
//   still corner cases where it may not work as intended. Mainly, if the UI
//   thread is very congested, the tasks in the pool may not get a chance to
//   run until the caller calls `Flush()`. Or, if the caller provides tasks that
//   take longer than `kMinTimeRequiredPerTask`, it may disrupt the compositor's
//   timing and degrade animation smoothness.

namespace ash {
namespace {

// Rough estimate for the maximum amount of time each individual task should
// take. If tasks are not getting run frequently enough, this value may be too
// large. If tasks are interrupting the animation's smoothness, this value may
// be too small.
constexpr base::TimeDelta kMinTimeRequiredPerTask = base::Milliseconds(4);

// In practice, `OverviewUiTaskPool::OnBeginFrame()` is called shortly after
// the true start of a new frame. This delay is due to the time it takes to
// propagate the "begin frame" message from the gpu process to the browser
// process's UI thread. That gives a little extra time for a task in the pool to
// run. Factoring this into the equation increases the odds of tasks being run.
constexpr base::TimeDelta kExpectedBeginFramePropagationDelay =
    base::Milliseconds(1);

}  // namespace

OverviewUiTaskPool::OverviewUiTaskPool(ui::Compositor* compositor,
                                       base::TimeDelta initial_blackout_period)
    : compositor_(compositor),
      initial_blackout_period_(initial_blackout_period),
      construction_time_(base::TimeTicks::Now()) {
  CHECK(compositor_);
  compositor_observation_.Observe(compositor_);
}

OverviewUiTaskPool::~OverviewUiTaskPool() {
  StopObservingBeginFrames();
}

void OverviewUiTaskPool::AddTask(base::OnceClosure task) {
  pending_tasks_.push_back(std::move(task));
  StartObservingBeginFrames();
}

void OverviewUiTaskPool::Flush() {
  CancelScheduledTask();
  while (!pending_tasks_.empty()) {
    RunNextTask(/*force_task_to_run=*/true);
  }
}

void OverviewUiTaskPool::OnBeginFrame(base::TimeTicks frame_begin_time,
                                      base::TimeDelta frame_interval) {
  if (base::TimeTicks::Now() - construction_time_ <= initial_blackout_period_) {
    return;
  }
  CancelScheduledTask();
  next_expected_begin_frame_time_ = frame_begin_time + frame_interval;
}

void OverviewUiTaskPool::OnCompositingStarted(ui::Compositor* compositor,
                                              base::TimeTicks start_time) {
  if (!IsCurrentCompositorFrameATaskCandidate()) {
    // In the blackout period still, or there are no tasks yet.
    return;
  }
  // Do not call `RunNextTask()` synchronously or it risks disrupting the
  // internal timing of the compositor. Make sure the tasks are run in a
  // separate call stack.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&OverviewUiTaskPool::RunNextTask,
                                weak_ptr_factory_.GetWeakPtr(),
                                /*force_task_to_run=*/false));
}

void OverviewUiTaskPool::OnCompositingShuttingDown(ui::Compositor* compositor) {
  compositor_observation_.Reset();
  StopObservingBeginFrames();
  compositor_ = nullptr;
  CancelScheduledTask();
}

void OverviewUiTaskPool::RunNextTask(bool force_task_to_run) {
  if (pending_tasks_.empty()) {
    return;
  }
  if (!force_task_to_run) {
    const base::TimeDelta time_available_for_task_to_run =
        (next_expected_begin_frame_time_ - base::TimeTicks::Now()) +
        kExpectedBeginFramePropagationDelay;
    DVLOG(4) << __func__ << " time_available_for_task_to_run="
             << time_available_for_task_to_run;
    if (time_available_for_task_to_run < kMinTimeRequiredPerTask) {
      return;
    }
  }

  auto next_task = std::move(pending_tasks_.front());
  pending_tasks_.pop_front();
  if (pending_tasks_.empty()) {
    StopObservingBeginFrames();
  }
  std::move(next_task).Run();
}

void OverviewUiTaskPool::StartObservingBeginFrames() {
  if (is_observing_begin_frames_ || HasCompositingShutDown()) {
    return;
  }
  compositor_->AddSimpleBeginFrameObserver(this);
  is_observing_begin_frames_ = true;
}

void OverviewUiTaskPool::StopObservingBeginFrames() {
  if (!is_observing_begin_frames_) {
    return;
  }
  CHECK(!HasCompositingShutDown());
  compositor_->RemoveSimpleBeginFrameObserver(this);
  is_observing_begin_frames_ = false;
}

bool OverviewUiTaskPool::HasCompositingShutDown() const {
  return !compositor_;
}

void OverviewUiTaskPool::CancelScheduledTask() {
  weak_ptr_factory_.InvalidateWeakPtrs();
}

bool OverviewUiTaskPool::IsCurrentCompositorFrameATaskCandidate() const {
  return !next_expected_begin_frame_time_.is_null();
}

}  // namespace ash