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