#include "cc/raster/task_graph_work_queue.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <unordered_map>
#include <utility>
#include "base/containers/contains.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/trace_event/typed_macros.h"
namespace cc {
namespace {
bool CompareTaskPriority(const TaskGraphWorkQueue::PrioritizedTask& a,
const TaskGraphWorkQueue::PrioritizedTask& b) {
return a.priority > b.priority;
}
class CompareTaskNamespacePriority {
public:
explicit CompareTaskNamespacePriority(uint16_t category)
: category_(category) {}
bool operator()(const TaskGraphWorkQueue::TaskNamespace* a,
const TaskGraphWorkQueue::TaskNamespace* b) {
DCHECK(!a->ready_to_run_tasks.at(category_).empty());
DCHECK(!b->ready_to_run_tasks.at(category_).empty());
return CompareTaskPriority(a->ready_to_run_tasks.at(category_).front(),
b->ready_to_run_tasks.at(category_).front());
}
private:
uint16_t category_;
};
class DependentIterator {
public:
DependentIterator(TaskGraph* graph, const Task* task)
: graph_(graph),
task_(task),
current_index_(static_cast<size_t>(-1)),
current_node_(nullptr) {
++(*this);
}
TaskGraph::Node& operator->() const {
DCHECK_LT(current_index_, graph_->edges.size());
DCHECK_EQ(graph_->edges[current_index_].task, task_);
DCHECK(current_node_);
return *current_node_;
}
TaskGraph::Node& operator*() const {
DCHECK_LT(current_index_, graph_->edges.size());
DCHECK_EQ(graph_->edges[current_index_].task, task_);
DCHECK(current_node_);
return *current_node_;
}
DependentIterator& operator++() {
do {
++current_index_;
if (current_index_ == graph_->edges.size())
return *this;
} while (graph_->edges[current_index_].task != task_);
auto it = std::ranges::find(graph_->nodes,
graph_->edges[current_index_].dependent.get(),
&TaskGraph::Node::task);
CHECK(it != graph_->nodes.end());
current_node_ = &(*it);
return *this;
}
operator bool() const { return current_index_ < graph_->edges.size(); }
private:
RAW_PTR_EXCLUSION TaskGraph* graph_;
RAW_PTR_EXCLUSION const Task* task_;
size_t current_index_;
RAW_PTR_EXCLUSION TaskGraph::Node* current_node_;
};
void RebuildNamespaceHeaps(TaskGraphWorkQueue::ReadyNamespaces& namespaces) {
for (auto& it : namespaces) {
uint16_t category = it.first;
auto& task_namespace = it.second;
std::make_heap(task_namespace.begin(), task_namespace.end(),
CompareTaskNamespacePriority(category));
}
}
}
TaskGraphWorkQueue::TaskNamespace::TaskNamespace() = default;
TaskGraphWorkQueue::TaskNamespace::TaskNamespace(TaskNamespace&& other) =
default;
TaskGraphWorkQueue::TaskNamespace::~TaskNamespace() = default;
TaskGraphWorkQueue::TaskGraphWorkQueue() : next_namespace_id_(1) {}
TaskGraphWorkQueue::~TaskGraphWorkQueue() = default;
TaskGraphWorkQueue::PrioritizedTask::PrioritizedTask(
scoped_refptr<Task> task,
TaskNamespace* task_namespace,
uint16_t category,
uint16_t priority)
: task(std::move(task)),
task_namespace(task_namespace),
category(category),
priority(priority) {}
TaskGraphWorkQueue::PrioritizedTask::PrioritizedTask(PrioritizedTask&& other) =
default;
TaskGraphWorkQueue::PrioritizedTask::~PrioritizedTask() = default;
NamespaceToken TaskGraphWorkQueue::GenerateNamespaceToken() {
NamespaceToken token(next_namespace_id_++);
DCHECK(!base::Contains(namespaces_, token));
return token;
}
void TaskGraphWorkQueue::ScheduleTasks(NamespaceToken token, TaskGraph* graph) {
TaskNamespace& task_namespace = namespaces_[token];
for (const scoped_refptr<Task>& task : task_namespace.completed_tasks) {
for (DependentIterator node_it(graph, task.get()); node_it; ++node_it) {
TaskGraph::Node& node = *node_it;
DCHECK_LT(0u, node.dependencies);
node.dependencies--;
}
}
for (auto& ready_to_run_tasks_it : task_namespace.ready_to_run_tasks) {
ready_to_run_tasks_it.second.clear();
}
TRACE_EVENT(
"toplevel", "cc::TaskGraphWorkQueue::ScheduleTasks",
[&](perfetto::EventContext ctx) {
for (const TaskGraph::Node& node : graph->nodes) {
DCHECK(node.task->trace_task_id() != 0)
<< "Every raster task should be associated with a task id.\n";
ctx.event()->add_flow_ids(node.task->trace_task_id());
}
});
for (const TaskGraph::Node& node : graph->nodes) {
auto old_it = std::ranges::find(task_namespace.graph.nodes, node.task,
&TaskGraph::Node::task);
if (old_it != task_namespace.graph.nodes.end()) {
std::swap(*old_it, task_namespace.graph.nodes.back());
if (node.task->state().IsScheduled())
node.task->state().Reset();
task_namespace.graph.nodes.pop_back();
}
if (node.dependencies)
continue;
if (node.task->state().IsFinished())
continue;
if (base::Contains(task_namespace.running_tasks, node.task.get(),
&CategorizedTask::second)) {
continue;
}
node.task->state().DidSchedule();
task_namespace.ready_to_run_tasks[node.category].emplace_back(
node.task, &task_namespace, node.category, node.priority);
}
for (auto& it : task_namespace.ready_to_run_tasks) {
auto& ready_to_run_tasks = it.second;
std::make_heap(ready_to_run_tasks.begin(), ready_to_run_tasks.end(),
CompareTaskPriority);
}
task_namespace.graph.Swap(graph);
for (auto it = graph->nodes.begin(); it != graph->nodes.end(); ++it) {
TaskGraph::Node& node = *it;
if (node.task->state().IsFinished())
continue;
if (base::Contains(task_namespace.running_tasks, node.task.get(),
&CategorizedTask::second)) {
continue;
}
DCHECK(!base::Contains(task_namespace.completed_tasks, node.task.get()));
node.task->state().DidCancel();
task_namespace.completed_tasks.push_back(node.task);
}
for (auto& ready_to_run_namespaces_it : ready_to_run_namespaces_) {
ready_to_run_namespaces_it.second.clear();
}
for (auto& namespace_it : namespaces_) {
auto& task_namespace_to_check = namespace_it.second;
for (auto& ready_to_run_tasks_it :
task_namespace_to_check.ready_to_run_tasks) {
auto& ready_to_run_tasks = ready_to_run_tasks_it.second;
uint16_t category = ready_to_run_tasks_it.first;
if (!ready_to_run_tasks.empty()) {
ready_to_run_namespaces_[category].push_back(&task_namespace_to_check);
}
}
}
RebuildNamespaceHeaps(ready_to_run_namespaces_);
}
TaskGraphWorkQueue::PrioritizedTask TaskGraphWorkQueue::GetNextTaskToRun(
uint16_t category) {
TaskNamespace::Vector& ready_to_run_namespaces =
ready_to_run_namespaces_[category];
DCHECK(!ready_to_run_namespaces.empty());
std::pop_heap(ready_to_run_namespaces.begin(), ready_to_run_namespaces.end(),
CompareTaskNamespacePriority(category));
TaskNamespace* task_namespace = ready_to_run_namespaces.back();
ready_to_run_namespaces.pop_back();
PrioritizedTask::Vector& ready_to_run_tasks =
task_namespace->ready_to_run_tasks[category];
DCHECK(!ready_to_run_tasks.empty());
std::pop_heap(ready_to_run_tasks.begin(), ready_to_run_tasks.end(),
CompareTaskPriority);
PrioritizedTask task = std::move(ready_to_run_tasks.back());
ready_to_run_tasks.pop_back();
if (!ready_to_run_tasks.empty()) {
ready_to_run_namespaces.push_back(task_namespace);
std::push_heap(ready_to_run_namespaces.begin(),
ready_to_run_namespaces.end(),
CompareTaskNamespacePriority(category));
}
task.task->state().DidStart();
task_namespace->running_tasks.push_back(
std::make_pair(task.category, task.task));
return task;
}
bool TaskGraphWorkQueue::ExternalDependencyCompletedForTask(
NamespaceToken token,
scoped_refptr<Task> task) {
TaskNamespace* task_namespace = GetNamespaceForToken(token);
CHECK(task_namespace || task->state().IsCanceled());
if (task_namespace) {
auto iter = std::ranges::find(task_namespace->graph.nodes, task.get(),
&TaskGraph::Node::task);
if (iter == task_namespace->graph.nodes.end()) {
return false;
}
iter->has_external_dependency = false;
return DecrementNodeDependencies(*iter, task_namespace,
true);
}
return false;
}
bool TaskGraphWorkQueue::DecrementNodeDependencies(
TaskGraph::Node& node,
TaskNamespace* task_namespace,
bool rebuild_heap) {
DCHECK_LT(0u, node.dependencies);
node.dependencies--;
if (!node.dependencies && node.task->state().IsNew()) {
PrioritizedTask::Vector& ready_to_run_tasks =
task_namespace->ready_to_run_tasks[node.category];
bool was_empty = ready_to_run_tasks.empty();
node.task->state().DidSchedule();
ready_to_run_tasks.emplace_back(node.task, task_namespace, node.category,
node.priority);
std::push_heap(ready_to_run_tasks.begin(), ready_to_run_tasks.end(),
CompareTaskPriority);
if (was_empty) {
TaskNamespace::Vector& ready_to_run_namespaces =
ready_to_run_namespaces_[node.category];
DCHECK(!base::Contains(ready_to_run_namespaces, task_namespace));
ready_to_run_namespaces.push_back(task_namespace);
}
if (rebuild_heap) {
auto& category_namespaces = ready_to_run_namespaces_[node.category];
std::make_heap(category_namespaces.begin(), category_namespaces.end(),
CompareTaskNamespacePriority(node.category));
}
return true;
}
return false;
}
void TaskGraphWorkQueue::CompleteTask(PrioritizedTask completed_task) {
TaskNamespace* task_namespace = completed_task.task_namespace;
scoped_refptr<Task> task(std::move(completed_task.task));
auto it = std::ranges::find(task_namespace->running_tasks, task,
&CategorizedTask::second);
CHECK(it != task_namespace->running_tasks.end());
std::swap(*it, task_namespace->running_tasks.back());
task_namespace->running_tasks.pop_back();
bool ready_to_run_namespaces_has_heap_properties = true;
for (DependentIterator dependent_it(&task_namespace->graph, task.get());
dependent_it; ++dependent_it) {
ready_to_run_namespaces_has_heap_properties &= !DecrementNodeDependencies(
*dependent_it, task_namespace, false );
}
if (!ready_to_run_namespaces_has_heap_properties) {
RebuildNamespaceHeaps(ready_to_run_namespaces_);
}
task->state().DidFinish();
task_namespace->completed_tasks.push_back(std::move(task));
}
void TaskGraphWorkQueue::CollectCompletedTasks(NamespaceToken token,
Task::Vector* completed_tasks) {
auto it = namespaces_.find(token);
if (it == namespaces_.end())
return;
TaskNamespace& task_namespace = it->second;
DCHECK_EQ(0u, completed_tasks->size());
completed_tasks->swap(task_namespace.completed_tasks);
if (!HasFinishedRunningTasksInNamespace(&task_namespace))
return;
DCHECK_EQ(0u, task_namespace.completed_tasks.size());
DCHECK(!HasReadyToRunTasksInNamespace(&task_namespace));
DCHECK_EQ(0u, task_namespace.running_tasks.size());
namespaces_.erase(it);
}
bool TaskGraphWorkQueue::DependencyMismatch(const TaskGraph* graph) {
std::unordered_map<const Task*, size_t> dependencies;
for (const TaskGraph::Edge& edge : graph->edges)
dependencies[edge.dependent]++;
for (const TaskGraph::Node& node : graph->nodes) {
size_t graph_dependency_count = dependencies[node.task.get()];
if (node.has_external_dependency) {
graph_dependency_count++;
}
if (graph_dependency_count != node.dependencies) {
return true;
}
}
return false;
}
}