blob: 174b10b0c4ae5924c025e72518817129ebe0e672 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/rendering_pipeline.h"
#include "base/containers/flat_map.h"
#include "base/task/current_thread.h"
#include "base/task/sequence_manager/task_time_observer.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_checker.h"
#include "ui/gfx/rendering_stage_scheduler.h"
namespace gfx {
namespace {
class ThreadSafeTimeObserver : public base::sequence_manager::TaskTimeObserver {
public:
explicit ThreadSafeTimeObserver(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(std::move(task_runner)) {}
~ThreadSafeTimeObserver() override {
// If the observer is being used on the target thread, unregister now. If it
// was being used on a different thread, then the target thread should have
// been torn down already.
SetEnabled(false);
}
ThreadSafeTimeObserver(const ThreadSafeTimeObserver&) = delete;
ThreadSafeTimeObserver& operator=(const ThreadSafeTimeObserver&) = delete;
void SetEnabled(bool enabled) {
{
base::AutoLock hold(time_lock_);
if (enabled_ == enabled)
return;
enabled_ = enabled;
}
if (!task_runner_)
return;
if (task_runner_->BelongsToCurrentThread()) {
UpdateOnTargetThread(enabled);
return;
}
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ThreadSafeTimeObserver::UpdateOnTargetThread,
base::Unretained(this), enabled));
}
base::TimeDelta GetAndResetTimeSinceLastFrame() {
base::AutoLock hold(time_lock_);
if (!start_time_active_task_.is_null()) {
auto now = base::TimeTicks::Now();
time_since_last_frame_ += now - start_time_active_task_;
start_time_active_task_ = now;
}
auto result = time_since_last_frame_;
time_since_last_frame_ = base::TimeDelta();
return result;
}
// TaskTimeObserver impl.
void WillProcessTask(base::TimeTicks start_time) override {
base::AutoLock hold(time_lock_);
if (!enabled_)
return;
DCHECK(start_time_active_task_.is_null());
start_time_active_task_ = start_time;
}
void DidProcessTask(base::TimeTicks start_time,
base::TimeTicks end_time) override {
base::AutoLock hold(time_lock_);
if (!enabled_) {
start_time_active_task_ = base::TimeTicks();
return;
}
// This should be null for the task which adds this object to the observer
// list.
if (start_time_active_task_.is_null())
return;
if (start_time_active_task_ <= end_time) {
time_since_last_frame_ += (end_time - start_time_active_task_);
} else {
// This could happen if |GetAndResetTimeSinceLastFrame| is called on a
// different thread and the observed thread had to wait to acquire the
// lock to call DidProcessTask. Assume the time for this task is already
// recorded in |GetAndResetTimeSinceLastFrame|.
DCHECK_NE(start_time_active_task_, start_time);
}
start_time_active_task_ = base::TimeTicks();
}
private:
void UpdateOnTargetThread(bool enabled) {
if (enabled) {
base::CurrentThread::Get().AddTaskTimeObserver(this);
base::AutoLock hold(time_lock_);
start_time_active_task_ = base::TimeTicks();
time_since_last_frame_ = base::TimeDelta();
} else {
base::CurrentThread::Get().RemoveTaskTimeObserver(this);
}
}
// Accessed only on the calling thread. The caller ensures no concurrent
// access.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Accessed on calling and target thread.
base::Lock time_lock_;
bool enabled_ GUARDED_BY(time_lock_) = false;
base::TimeTicks start_time_active_task_ GUARDED_BY(time_lock_);
base::TimeDelta time_since_last_frame_ GUARDED_BY(time_lock_);
};
} // namespace
class RenderingPipelineImpl final : public RenderingPipeline {
public:
explicit RenderingPipelineImpl(const char* pipeline_type)
: pipeline_type_(pipeline_type) {
DETACH_FROM_THREAD(bound_thread_);
}
~RenderingPipelineImpl() override { TearDown(); }
RenderingPipelineImpl(const RenderingPipelineImpl&) = delete;
RenderingPipelineImpl& operator=(const RenderingPipelineImpl&) = delete;
void SetTargetDuration(base::TimeDelta target_duration) override {
DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
DCHECK(!target_duration.is_zero());
if (target_duration_ == target_duration)
return;
target_duration_ = target_duration;
if (should_use_scheduler())
SetUp();
}
void AddSequenceManagerThread(
base::PlatformThreadId thread_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
base::AutoLock lock(lock_);
DCHECK(time_observers_.find(thread_id) == time_observers_.end());
time_observers_[thread_id] =
std::make_unique<ThreadSafeTimeObserver>(task_runner);
if (scheduler_)
CreateSchedulerAndEnableWithLockAcquired();
}
base::sequence_manager::TaskTimeObserver* AddSimpleThread(
base::PlatformThreadId thread_id) override {
base::AutoLock lock(lock_);
DCHECK(time_observers_.find(thread_id) == time_observers_.end());
time_observers_[thread_id] =
std::make_unique<ThreadSafeTimeObserver>(nullptr);
if (scheduler_)
CreateSchedulerAndEnableWithLockAcquired();
return time_observers_[thread_id].get();
}
void NotifyFrameFinished() override {
DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
base::AutoLock lock(lock_);
if (!scheduler_)
return;
// TODO(crbug.com/1157620): This can be optimized to exclude tasks which can
// be paused during rendering. The best use-case is idle tasks on the
// renderer main thread. If all non-optional work is close to the frame
// budget then the scheduler dynamically adjusts to pause work like idle
// tasks.
base::TimeDelta total_time;
for (auto& it : time_observers_) {
total_time += it.second->GetAndResetTimeSinceLastFrame();
}
scheduler_->ReportCpuCompletionTime(total_time + gpu_latency_);
}
void SetGpuLatency(base::TimeDelta gpu_latency) override {
DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
gpu_latency_ = gpu_latency;
}
void UpdateActiveCount(bool active) override {
DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
if (active) {
active_count_++;
} else {
DCHECK_GT(active_count_, 0);
active_count_--;
}
if (should_use_scheduler()) {
SetUp();
} else {
TearDown();
}
}
private:
bool should_use_scheduler() const {
// TODO(crbug.com/1157620) : Figure out what we should be doing if multiple
// independent pipelines of a type are running simultaneously. The common
// use-case for this in practice would be multi-window. The tabs could be
// hosted in the same renderer process and each window is composited
// independently by the GPU process.
return active_count_ == 1 && !target_duration_.is_zero();
}
void SetUp() {
base::AutoLock lock(lock_);
CreateSchedulerAndEnableWithLockAcquired();
}
void CreateSchedulerAndEnableWithLockAcquired() {
lock_.AssertAcquired();
scheduler_.reset();
std::vector<base::PlatformThreadId> platform_threads;
for (auto& it : time_observers_) {
platform_threads.push_back(it.first);
it.second->SetEnabled(true);
}
scheduler_ = RenderingStageScheduler::CreateAdpf(
pipeline_type_, std::move(platform_threads), target_duration_);
}
void TearDown() {
base::AutoLock lock(lock_);
for (auto& it : time_observers_)
it.second->SetEnabled(false);
scheduler_.reset();
}
THREAD_CHECKER(bound_thread_);
base::Lock lock_;
base::flat_map<base::PlatformThreadId,
std::unique_ptr<ThreadSafeTimeObserver>>
time_observers_ GUARDED_BY(lock_);
std::unique_ptr<RenderingStageScheduler> scheduler_ GUARDED_BY(lock_);
// Pipeline name, for tracing and metrics.
const char* pipeline_type_;
// The number of currently active pipelines of this type.
int active_count_ = 0;
// The target time for this rendering stage for a frame.
base::TimeDelta target_duration_;
base::TimeDelta gpu_latency_;
};
RenderingPipeline::ScopedPipelineActive::ScopedPipelineActive(
RenderingPipeline* pipeline)
: pipeline_(pipeline) {
pipeline_->UpdateActiveCount(true);
}
RenderingPipeline::ScopedPipelineActive::~ScopedPipelineActive() {
pipeline_->UpdateActiveCount(false);
}
std::unique_ptr<RenderingPipeline> RenderingPipeline::CreateRendererMain() {
static constexpr char kRendererMain[] = "RendererMain";
return std::make_unique<RenderingPipelineImpl>(kRendererMain);
}
std::unique_ptr<RenderingPipeline>
RenderingPipeline::CreateRendererCompositor() {
static constexpr char kRendererCompositor[] = "RendererCompositor";
return std::make_unique<RenderingPipelineImpl>(kRendererCompositor);
}
std::unique_ptr<RenderingPipeline> RenderingPipeline::CreateGpu() {
static constexpr char kGpu[] = "Gpu";
return std::make_unique<RenderingPipelineImpl>(kGpu);
}
} // namespace gfx