blob: ab22655559a3cc0323a112462b0999a249750277 [file] [log] [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,
std::optional<base::TimeTicks> first_coalesced_frame_begin_time) {
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