blob: 5a25c4cd73cbb4bee1761f4cae89a7ffe4dc14bc [file] [log] [blame]
// 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.
#include "gpu/command_buffer/service/scheduler_dfs.h"
#include <algorithm>
#include <cstddef>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/hash/md5_constexpr.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/common/scheduling_priority.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "gpu/command_buffer/service/sync_point_manager.h"
#include "gpu/config/gpu_preferences.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
namespace gpu {
namespace {
using Task = ::gpu::Scheduler::Task;
uint64_t GetTaskFlowId(uint32_t sequence_id, uint32_t order_num) {
// Xor with a mask to ensure that the flow id does not collide with non-gpu
// tasks.
static constexpr uint64_t kMask =
base::MD5Hash64Constexpr("gpu::SchedulerDfs");
return kMask ^ (sequence_id) ^ (static_cast<uint64_t>(order_num) << 32);
}
} // namespace
SchedulerDfs::SchedulingState::SchedulingState() = default;
SchedulerDfs::SchedulingState::SchedulingState(const SchedulingState& other) =
default;
SchedulerDfs::SchedulingState::~SchedulingState() = default;
void SchedulerDfs::SchedulingState::WriteIntoTrace(
perfetto::TracedValue context) const {
auto dict = std::move(context).WriteDictionary();
dict.Add("sequence_id", sequence_id.GetUnsafeValue());
dict.Add("priority", SchedulingPriorityToString(priority));
dict.Add("order_num", order_num);
}
bool SchedulerDfs::SchedulingState::operator==(
const SchedulerDfs::SchedulingState& rhs) const {
return std::tie(sequence_id, priority, order_num) ==
std::tie(rhs.sequence_id, rhs.priority, rhs.order_num);
}
SchedulerDfs::Sequence::Task::Task(base::OnceClosure closure,
uint32_t order_num,
ReportingCallback report_callback)
: closure(std::move(closure)),
order_num(order_num),
report_callback(std::move(report_callback)) {}
SchedulerDfs::Sequence::Task::Task(Task&& other) = default;
SchedulerDfs::Sequence::Task::~Task() {
DCHECK(report_callback.is_null());
}
SchedulerDfs::Sequence::Task& SchedulerDfs::Sequence::Task::operator=(
Task&& other) = default;
SchedulerDfs::Sequence::WaitFence::WaitFence(const SyncToken& sync_token,
uint32_t order_num,
SequenceId release_sequence_id)
: sync_token(sync_token),
order_num(order_num),
release_sequence_id(release_sequence_id) {}
SchedulerDfs::Sequence::WaitFence::WaitFence(WaitFence&& other) = default;
SchedulerDfs::Sequence::WaitFence::~WaitFence() = default;
SchedulerDfs::Sequence::WaitFence& SchedulerDfs::Sequence::WaitFence::operator=(
WaitFence&& other) = default;
SchedulerDfs::PerThreadState::PerThreadState() = default;
SchedulerDfs::PerThreadState::PerThreadState(PerThreadState&& other) = default;
SchedulerDfs::PerThreadState::~PerThreadState() = default;
SchedulerDfs::PerThreadState& SchedulerDfs::PerThreadState::operator=(
PerThreadState&& other) = default;
SchedulerDfs::Sequence::Sequence(
SchedulerDfs* scheduler,
SequenceId sequence_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
SchedulingPriority priority,
scoped_refptr<SyncPointOrderData> order_data)
: scheduler_(scheduler),
sequence_id_(sequence_id),
task_runner_(std::move(task_runner)),
default_priority_(priority),
current_priority_(priority),
order_data_(std::move(order_data)) {}
SchedulerDfs::Sequence::~Sequence() {
for (auto& kv : wait_fences_) {
Sequence* release_sequence =
scheduler_->GetSequence(kv.first.release_sequence_id);
if (release_sequence) {
scheduler_->TryScheduleSequence(release_sequence);
}
}
order_data_->Destroy();
}
bool SchedulerDfs::Sequence::IsNextTaskUnblocked() const {
return !tasks_.empty() &&
(wait_fences_.empty() ||
wait_fences_.begin()->first.order_num > tasks_.front().order_num);
}
bool SchedulerDfs::Sequence::HasTasks() const {
return enabled_ && !tasks_.empty();
}
bool SchedulerDfs::Sequence::ShouldYieldTo(const Sequence* other) const {
if (task_runner() != other->task_runner())
return false;
if (!running())
return false;
return SchedulingState::RunsBefore(other->scheduling_state_,
scheduling_state_);
}
void SchedulerDfs::Sequence::SetEnabled(bool enabled) {
if (enabled_ == enabled)
return;
enabled_ = enabled;
if (enabled) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("gpu", "SequenceEnabled",
TRACE_ID_LOCAL(this), "sequence_id",
sequence_id_.GetUnsafeValue());
} else {
TRACE_EVENT_NESTABLE_ASYNC_END1("gpu", "SequenceEnabled",
TRACE_ID_LOCAL(this), "sequence_id",
sequence_id_.GetUnsafeValue());
}
scheduler_->TryScheduleSequence(this);
}
SchedulerDfs::SchedulingState SchedulerDfs::Sequence::SetScheduled() {
DCHECK(HasTasks());
DCHECK_NE(running_state_, RUNNING);
running_state_ = SCHEDULED;
scheduling_state_.sequence_id = sequence_id_;
scheduling_state_.priority = current_priority();
scheduling_state_.order_num = tasks_.front().order_num;
return scheduling_state_;
}
void SchedulerDfs::Sequence::UpdateRunningPriority() {
DCHECK_EQ(running_state_, RUNNING);
scheduling_state_.priority = current_priority();
}
void SchedulerDfs::Sequence::ContinueTask(base::OnceClosure closure) {
DCHECK_EQ(running_state_, RUNNING);
uint32_t order_num = order_data_->current_order_num();
tasks_.push_front({std::move(closure), order_num, ReportingCallback()});
order_data_->PauseProcessingOrderNumber(order_num);
}
uint32_t SchedulerDfs::Sequence::ScheduleTask(
base::OnceClosure closure,
ReportingCallback report_callback) {
uint32_t order_num = order_data_->GenerateUnprocessedOrderNumber();
TRACE_EVENT_WITH_FLOW0("gpu,toplevel.flow", "SchedulerDfs::ScheduleTask",
GetTaskFlowId(sequence_id_.value(), order_num),
TRACE_EVENT_FLAG_FLOW_OUT);
tasks_.push_back({std::move(closure), order_num, std::move(report_callback)});
return order_num;
}
base::TimeDelta SchedulerDfs::Sequence::FrontTaskWaitingDependencyDelta() {
DCHECK(!tasks_.empty());
if (tasks_.front().first_dependency_added.is_null()) {
// didn't wait for dependencies.
return base::TimeDelta();
}
return tasks_.front().running_ready - tasks_.front().first_dependency_added;
}
base::TimeDelta SchedulerDfs::Sequence::FrontTaskSchedulingDelay() {
DCHECK(!tasks_.empty());
return base::TimeTicks::Now() - tasks_.front().running_ready;
}
uint32_t SchedulerDfs::Sequence::BeginTask(base::OnceClosure* closure) {
DCHECK(closure);
DCHECK(!tasks_.empty());
DCHECK_EQ(running_state_, SCHEDULED);
DVLOG(10) << "Sequence " << sequence_id() << " is now running.";
running_state_ = RUNNING;
*closure = std::move(tasks_.front().closure);
uint32_t order_num = tasks_.front().order_num;
if (!tasks_.front().report_callback.is_null()) {
std::move(tasks_.front().report_callback).Run(tasks_.front().running_ready);
}
tasks_.pop_front();
return order_num;
}
void SchedulerDfs::Sequence::FinishTask() {
DCHECK_EQ(running_state_, RUNNING);
running_state_ = SCHEDULED;
DVLOG(10) << "Sequence " << sequence_id() << " is now ending.";
}
void SchedulerDfs::Sequence::SetLastTaskFirstDependencyTimeIfNeeded() {
DCHECK(!tasks_.empty());
if (tasks_.back().first_dependency_added.is_null()) {
// Fence are always added for the last task (which should always exists).
tasks_.back().first_dependency_added = base::TimeTicks::Now();
}
}
void SchedulerDfs::Sequence::AddWaitFence(const SyncToken& sync_token,
uint32_t order_num,
SequenceId release_sequence_id) {
auto it =
wait_fences_.find(WaitFence{sync_token, order_num, release_sequence_id});
if (it != wait_fences_.end())
return;
wait_fences_.emplace(
std::make_pair(WaitFence(sync_token, order_num, release_sequence_id),
default_priority_));
}
void SchedulerDfs::Sequence::RemoveWaitFence(const SyncToken& sync_token,
uint32_t order_num,
SequenceId release_sequence_id) {
DVLOG(10) << "Sequence " << sequence_id_.value()
<< " removing wait fence that was released by sequence "
<< release_sequence_id.value() << ".";
auto it =
wait_fences_.find(WaitFence{sync_token, order_num, release_sequence_id});
if (it != wait_fences_.end()) {
wait_fences_.erase(it);
for (auto& task : tasks_) {
if (order_num == task.order_num) {
// The fence applies to this task, bump the readiness timestamp
task.running_ready = base::TimeTicks::Now();
break;
} else if (order_num < task.order_num) {
// Updated all task related to this fence.
break;
}
}
Sequence* release_sequence = scheduler_->GetSequence(release_sequence_id);
if (release_sequence) {
// The release sequence's task runner could potentially need to be waken
// up now.
// TODO(elgarawany): Really? This doesn't make sense. The release sequence
// must have just been running because it released the fence!
scheduler_->TryScheduleSequence(release_sequence);
}
scheduler_->TryScheduleSequence(this);
}
}
SchedulerDfs::SchedulerDfs(SyncPointManager* sync_point_manager,
const GpuPreferences& gpu_preferences)
: sync_point_manager_(sync_point_manager),
blocked_time_collection_enabled_(
gpu_preferences.enable_gpu_blocked_time_metric) {
if (blocked_time_collection_enabled_ && !base::ThreadTicks::IsSupported())
DLOG(ERROR) << "GPU Blocked time collection is enabled but not supported.";
}
SchedulerDfs::~SchedulerDfs() {
base::AutoLock auto_lock(lock_);
// Sequences as well as tasks posted to the threads have "this" pointer of the
// SchedulerDfs. Hence adding DCHECKS to make sure sequences are
// finished/destroyed and none of the threads are running by the time
// scheduler is destroyed.
DCHECK(sequence_map_.empty());
for (const auto& per_thread_state : per_thread_state_map_)
DCHECK(!per_thread_state.second.running);
}
SequenceId SchedulerDfs::CreateSequence(
SchedulingPriority priority,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
base::AutoLock auto_lock(lock_);
scoped_refptr<SyncPointOrderData> order_data =
sync_point_manager_->CreateSyncPointOrderData();
SequenceId sequence_id = order_data->sequence_id();
auto sequence =
std::make_unique<Sequence>(this, sequence_id, std::move(task_runner),
priority, std::move(order_data));
sequence_map_.emplace(sequence_id, std::move(sequence));
return sequence_id;
}
SequenceId SchedulerDfs::CreateSequenceForTesting(SchedulingPriority priority) {
// This will create the sequence on the thread on which this method is called.
return CreateSequence(priority,
base::SingleThreadTaskRunner::GetCurrentDefault());
}
void SchedulerDfs::DestroySequence(SequenceId sequence_id) {
base::circular_deque<Sequence::Task> tasks_to_be_destroyed;
{
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
tasks_to_be_destroyed = std::move(sequence->tasks_);
sequence_map_.erase(sequence_id);
}
}
SchedulerDfs::Sequence* SchedulerDfs::GetSequence(SequenceId sequence_id) {
lock_.AssertAcquired();
auto it = sequence_map_.find(sequence_id);
if (it != sequence_map_.end())
return it->second.get();
return nullptr;
}
void SchedulerDfs::EnableSequence(SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
sequence->SetEnabled(true);
}
void SchedulerDfs::DisableSequence(SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
sequence->SetEnabled(false);
}
SchedulingPriority SchedulerDfs::GetSequenceDefaultPriority(
SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
return sequence->default_priority_;
}
void SchedulerDfs::SetSequencePriority(SequenceId sequence_id,
SchedulingPriority priority) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
sequence->current_priority_ = priority;
}
void SchedulerDfs::ScheduleTask(Task task) {
base::AutoLock auto_lock(lock_);
ScheduleTaskHelper(std::move(task));
}
void SchedulerDfs::ScheduleTasks(std::vector<Task> tasks) {
base::AutoLock auto_lock(lock_);
for (auto& task : tasks)
ScheduleTaskHelper(std::move(task));
}
void SchedulerDfs::ScheduleTaskHelper(Task task) {
SequenceId sequence_id = task.sequence_id;
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
auto* task_runner = sequence->task_runner();
uint32_t order_num = sequence->ScheduleTask(std::move(task.closure),
std::move(task.report_callback));
for (const SyncToken& sync_token : ReduceSyncTokens(task.sync_token_fences)) {
SequenceId release_sequence_id =
sync_point_manager_->GetSyncTokenReleaseSequenceId(sync_token);
// base::Unretained is safe here since all sequences and corresponding sync
// point callbacks will be released before the scheduler is destroyed (even
// though sync point manager itself outlives the scheduler briefly).
if (sync_point_manager_->WaitNonThreadSafe(
sync_token, sequence_id, order_num, task_runner,
base::BindOnce(&SchedulerDfs::SyncTokenFenceReleased,
base::Unretained(this), sync_token, order_num,
release_sequence_id, sequence_id))) {
sequence->AddWaitFence(sync_token, order_num, release_sequence_id);
sequence->SetLastTaskFirstDependencyTimeIfNeeded();
}
}
TryScheduleSequence(sequence);
}
void SchedulerDfs::ContinueTask(SequenceId sequence_id,
base::OnceClosure closure) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
DCHECK(sequence->task_runner()->BelongsToCurrentThread());
sequence->ContinueTask(std::move(closure));
}
bool SchedulerDfs::ShouldYield(SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
Sequence* running_sequence = GetSequence(sequence_id);
DCHECK(running_sequence);
DCHECK(running_sequence->running());
DCHECK(running_sequence->task_runner()->BelongsToCurrentThread());
// Call FindNextTask to find the sequence that will run next. This can
// potentially return nullptr if the only dependency on this thread is a
// sequence tied to another thread.
// TODO(elgarawany): Remove ShouldYield entirely and make CommandBufferStub,
// the only user of ShouldYield, always pause, and leave the scheduling
// decision to the scheduler.
Sequence* next_sequence = FindNextTask();
if (next_sequence == nullptr)
return false;
return running_sequence->ShouldYieldTo(next_sequence);
}
base::TimeDelta SchedulerDfs::TakeTotalBlockingTime() {
if (!blocked_time_collection_enabled_ || !base::ThreadTicks::IsSupported())
return base::TimeDelta::Min();
base::AutoLock auto_lock(lock_);
base::TimeDelta result;
std::swap(result, total_blocked_time_);
return result;
}
base::SingleThreadTaskRunner* SchedulerDfs::GetTaskRunnerForTesting(
SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
return GetSequence(sequence_id)->task_runner();
}
void SchedulerDfs::SyncTokenFenceReleased(const SyncToken& sync_token,
uint32_t order_num,
SequenceId release_sequence_id,
SequenceId waiting_sequence_id) {
base::AutoLock auto_lock(lock_);
Sequence* sequence = GetSequence(waiting_sequence_id);
if (sequence)
sequence->RemoveWaitFence(sync_token, order_num, release_sequence_id);
}
void SchedulerDfs::TryScheduleSequence(Sequence* sequence) {
lock_.AssertAcquired();
auto* task_runner = sequence->task_runner();
auto& thread_state = per_thread_state_map_[task_runner];
DVLOG(10) << "Trying to schedule or wake up sequence "
<< sequence->sequence_id().value()
<< ". running: " << sequence->running() << ".";
if (sequence->running()) {
// Update priority of running sequence because of sync token releases.
DCHECK(thread_state.running);
sequence->UpdateRunningPriority();
} else {
// Insert into scheduling queue if sequence isn't already scheduled.
if (!sequence->scheduled() && sequence->HasTasks()) {
sequence->SetScheduled();
}
// Wake up RunNextTask if the sequence has work to do. (If the thread is not
// running, that means that all other sequences were either empty, or
// waiting for work to be done on another thread).
if (!thread_state.running && HasAnyUnblockedTasksOnRunner(task_runner)) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("gpu", "SchedulerDfs::Running",
TRACE_ID_LOCAL(this));
DVLOG(10) << "Waking up thread because there is work to do.";
thread_state.running = true;
thread_state.run_next_task_scheduled = base::TimeTicks::Now();
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&SchedulerDfs::RunNextTask, base::Unretained(this)));
}
}
}
const std::vector<SchedulerDfs::SchedulingState>&
SchedulerDfs::GetSortedRunnableSequences(
base::SingleThreadTaskRunner* task_runner) {
auto& thread_state = per_thread_state_map_[task_runner];
std::vector<SchedulingState>& sorted_sequences =
thread_state.sorted_sequences;
sorted_sequences.clear();
for (const auto& kv : sequence_map_) {
Sequence* sequence = kv.second.get();
// Add any sequence that is enabled, not already running, and has any tasks.
if (sequence->IsRunnable()) {
SchedulingState scheduling_state = sequence->SetScheduled();
sorted_sequences.push_back(scheduling_state);
}
}
// Sort the sequence. We never have more than a few handful of sequences - so
// this is pretty cheap to do.
std::stable_sort(sorted_sequences.begin(), sorted_sequences.end(),
&SchedulingState::RunsBefore);
return sorted_sequences;
}
bool SchedulerDfs::HasAnyUnblockedTasksOnRunner(
const base::SingleThreadTaskRunner* task_runner) const {
// Loop over all sequences and check if any of them are unblocked and belong
// to |task_runner|.
for (const auto& [_, sequence] : sequence_map_) {
if (sequence->task_runner() == task_runner && sequence->enabled() &&
sequence->IsNextTaskUnblocked()) {
return true;
}
}
// Either we don't have any enabled sequences, or they are all blocked (this
// can happen if DrDC is enabled).
return false;
}
SchedulerDfs::Sequence* SchedulerDfs::FindNextTaskFromRoot(
Sequence* root_sequence) {
if (!root_sequence)
return nullptr;
VLOG_IF(10, !root_sequence->enabled())
<< "Sequence " << root_sequence->sequence_id() << " is not enabled!";
DVLOG_IF(10, !root_sequence->HasTasks())
<< "Sequence " << root_sequence->sequence_id()
<< " does not have any tasks!";
// Don't bother looking at disabled sequence, sequences that don't have tasks,
// and (leaf) sequences that are already running. We don't look at running
// sequences because their order number is updated *before* they finish, which
// can make dependencies appear circular.
if (!root_sequence->IsRunnable()) {
return nullptr;
}
// First, recurse into any dependency that needs to run before the first
// task in |root_sequence|. The dependencies are sorted by their order num
// (because of WaitFence ordering).
const uint32_t first_task_order_num = root_sequence->tasks_.front().order_num;
DVLOG(10) << "Sequence " << root_sequence->sequence_id()
<< " (order_num: " << first_task_order_num << ") has "
<< root_sequence->wait_fences_.size() << " waits.";
for (auto fence_iter = root_sequence->wait_fences_.begin();
fence_iter != root_sequence->wait_fences_.end() &&
fence_iter->first.order_num <= first_task_order_num;
++fence_iter) {
// Recurse into the dependent sequence. If a subtask was found, then
// we're done.
DVLOG(10) << "Recursing into dependency in sequence "
<< fence_iter->first.release_sequence_id
<< " (order_num: " << fence_iter->first.order_num << ").";
Sequence* release_sequence =
GetSequence(fence_iter->first.release_sequence_id);
// ShouldYield might be calling this function, and a dependency might depend
// on the calling sequence, which might have not released its fences yet.
if (release_sequence && release_sequence->HasTasks() &&
release_sequence->tasks_.front().order_num >=
fence_iter->first.order_num) {
continue;
}
if (Sequence* result = FindNextTaskFromRoot(release_sequence);
result != nullptr) {
return result;
}
}
// It's possible that none of root_sequence's dependencies can be run
// because they are tied to another thread.
const bool are_dependencies_done =
root_sequence->wait_fences_.empty() ||
root_sequence->wait_fences_.begin()->first.order_num >
first_task_order_num;
// Return |root_sequence| only if its dependencies are done, and if it can
// run on the current thread.
DVLOG_IF(10, root_sequence->task_runner() !=
base::SingleThreadTaskRunner::GetCurrentDefault().get())
<< "Will not run sequence because it does not belong to this thread.";
if (are_dependencies_done &&
root_sequence->task_runner() ==
base::SingleThreadTaskRunner::GetCurrentDefault().get()) {
return root_sequence;
} else {
DVLOG_IF(10, !are_dependencies_done)
<< "Sequence " << root_sequence->sequence_id()
<< "'s dependencies are not yet done.";
return nullptr;
}
}
SchedulerDfs::Sequence* SchedulerDfs::FindNextTask() {
auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
auto& sorted_sequences = GetSortedRunnableSequences(task_runner);
// Walk the scheduling queue starting with the highest priority sequence and
// find the first sequence that can be run. The loop will iterate more than
// once only if DrDC is enabled and the first sequence contains a single
// dependency tied to another thread.
for (const SchedulingState& state : sorted_sequences) {
Sequence* root_sequence = GetSequence(state.sequence_id);
DVLOG(10) << "FindNextTask: Calling FindNextTaskFromRoot on sequence "
<< root_sequence->sequence_id().value();
if (Sequence* sequence = FindNextTaskFromRoot(root_sequence);
sequence != nullptr) {
return sequence;
}
}
return nullptr;
}
// See comments in scheduler.h for a high-level overview of the algorithm.
void SchedulerDfs::RunNextTask() {
SequenceId sequence_id;
DCHECK(sequence_id.is_null());
{
base::AutoLock auto_lock(lock_);
auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
auto* thread_state = &per_thread_state_map_[task_runner];
DVLOG(10) << "RunNextTask: Task runner is " << (uint64_t)task_runner;
// Walk the job graph starting from the highest priority roots to find a
// task to run.
Sequence* sequence = FindNextTask();
if (sequence == nullptr) {
// If there is no sequence to run, it should mean that there are no
// runnable sequences.
// TODO(elgarawany): We shouldn't have run RunNextTask if there were no
// runnable sequences. Change logic to check for that too (that changes
// old behavior - so leaving for now).
// TODO(crbug.com/1472145): this assert is firing frequently on
// Release builds with dcheck_always_on on Intel Macs. It looks
// like it happens when the browser drops frames.
/*
DCHECK(GetSortedRunnableSequences(task_runner).empty())
<< "RunNextTask should not have been called "
"if it did not have any unblocked tasks.";
*/
TRACE_EVENT_NESTABLE_ASYNC_END0("gpu", "SchedulerDfs::Running",
TRACE_ID_LOCAL(this));
DVLOG(10) << "Empty scheduling queue. Sleeping.";
thread_state->running = false;
return;
}
DCHECK(sequence->task_runner() == task_runner)
<< "FindNextTaskFromRoot returned sequence that does not belong to "
"this thread.";
sequence_id = sequence->sequence_id();
}
// Now, execute the sequence's task.
ExecuteSequence(sequence_id);
// Finally, reschedule RunNextTask if there is any potential remaining work.
{
base::AutoLock auto_lock(lock_);
auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
auto* thread_state = &per_thread_state_map_[task_runner];
if (!HasAnyUnblockedTasksOnRunner(task_runner)) {
TRACE_EVENT_NESTABLE_ASYNC_END0("gpu", "SchedulerDfs::Running",
TRACE_ID_LOCAL(this));
DVLOG(10) << "Thread has no runnable sequences. Sleeping.";
thread_state->running = false;
return;
}
thread_state->run_next_task_scheduled = base::TimeTicks::Now();
task_runner->PostTask(FROM_HERE, base::BindOnce(&SchedulerDfs::RunNextTask,
base::Unretained(this)));
}
}
void SchedulerDfs::ExecuteSequence(const SequenceId sequence_id) {
base::AutoLock auto_lock(lock_);
auto* task_runner = base::SingleThreadTaskRunner::GetCurrentDefault().get();
auto* thread_state = &per_thread_state_map_[task_runner];
// Subsampling these metrics reduced CPU utilization (crbug.com/1295441).
const bool log_histograms = metrics_subsampler_.ShouldSample(0.001);
if (log_histograms) {
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"GPU.SchedulerDfs.ThreadSuspendedTime",
base::TimeTicks::Now() - thread_state->run_next_task_scheduled,
base::Microseconds(10), base::Seconds(30), 100);
}
Sequence* sequence = GetSequence(sequence_id);
DCHECK(sequence);
DCHECK(sequence->HasTasks());
DCHECK_EQ(sequence->task_runner(), task_runner);
DVLOG(10) << "Executing sequence " << sequence_id.value() << ".";
if (log_histograms) {
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"GPU.SchedulerDfs.TaskDependencyTime",
sequence->FrontTaskWaitingDependencyDelta(), base::Microseconds(10),
base::Seconds(30), 100);
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"GPU.SchedulerDfs.TaskSchedulingDelayTime",
sequence->FrontTaskSchedulingDelay(), base::Microseconds(10),
base::Seconds(30), 100);
}
base::OnceClosure closure;
uint32_t order_num = sequence->BeginTask(&closure);
TRACE_EVENT_WITH_FLOW0("gpu,toplevel.flow", "SchedulerDfs::RunNextTask",
GetTaskFlowId(sequence_id.value(), order_num),
TRACE_EVENT_FLAG_FLOW_IN);
// Begin/FinishProcessingOrderNumber must be called with the lock released
// because they can renter the scheduler in Enable/DisableSequence.
scoped_refptr<SyncPointOrderData> order_data = sequence->order_data();
// Unset pointers before releasing the lock to prevent accidental data race.
thread_state = nullptr;
sequence = nullptr;
base::TimeDelta blocked_time;
{
base::AutoUnlock auto_unlock(lock_);
order_data->BeginProcessingOrderNumber(order_num);
if (blocked_time_collection_enabled_ && base::ThreadTicks::IsSupported()) {
// We can't call base::ThreadTicks::Now() if it's not supported
base::ThreadTicks thread_time_start = base::ThreadTicks::Now();
base::TimeTicks wall_time_start = base::TimeTicks::Now();
std::move(closure).Run();
base::TimeDelta thread_time_elapsed =
base::ThreadTicks::Now() - thread_time_start;
base::TimeDelta wall_time_elapsed =
base::TimeTicks::Now() - wall_time_start;
blocked_time += (wall_time_elapsed - thread_time_elapsed);
} else {
std::move(closure).Run();
}
if (order_data->IsProcessingOrderNumber())
order_data->FinishProcessingOrderNumber(order_num);
}
total_blocked_time_ += blocked_time;
// Reset pointers after reaquiring the lock.
sequence = GetSequence(sequence_id);
if (sequence) {
sequence->FinishTask();
}
}
} // namespace gpu