blob: 2d7d5cf6e4e71632caf7758241abcebf0a51254f [file] [log] [blame]
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrTCluster_DEFINED
#define GrTCluster_DEFINED
#include "include/core/SkRefCnt.h"
#include "include/private/SkTHash.h"
#include "src/core/SkScopeExit.h"
#include "src/core/SkSpan.h"
#include "src/core/SkTInternalLList.h"
// Uncomment to get lots of logging.
#define CLUSTER_DEBUGF(...) //SkDebugf(__VA_ARGS__)
#ifdef SK_DEBUG
template <typename T, typename Traits>
SkString GrTCluster_DebugStr(T* t) {
if (Traits::NumTargets(t) != 1) {
return SkStringPrintf("%d", Traits::GetID(t));
} else {
return SkStringPrintf("%d(%d)", Traits::GetID(t), Traits::GetTarget(t, 0));
}
}
template <typename T, typename Traits>
SkString GrTCluster_DebugStr(SkSpan<const sk_sp<T>> collection) {
SkString s;
for (const sk_sp<T>& t_sp : collection) {
s.appendf("%s ", GrTCluster_DebugStr<T, Traits>(t_sp.get()).c_str());
}
return s;
}
template <typename T, typename Traits>
SkString GrTCluster_DebugStr(const SkTInternalLList<T>& collection) {
SkString s;
for (T* t : collection) {
s.appendf("%s ", GrTCluster_DebugStr<T, Traits>(t).c_str());
}
return s;
}
template<typename T, typename Traits>
void GrTCluster_Validate(SkSpan<const sk_sp<T>> input, const SkTInternalLList<T>& llist) {
// Check that we didn't break dependencies.
SkTHashSet<T*> seen;
for (T* t : llist) {
seen.add(t);
for (int i = 0; i < Traits::NumDependencies(t); i++) {
T* dep = Traits::Dependency(t, i);
SkASSERTF(seen.contains(dep),
"%s came before dependency %s",
GrTCluster_DebugStr<T, Traits>(t).c_str(),
GrTCluster_DebugStr<T, Traits>(dep).c_str());
}
}
// Check that llist has the same entries as the input.
for (const auto& orig : input) {
seen.remove(orig.get());
}
SkASSERT(seen.empty());
}
#endif // SK_DEBUG
// Returns whether reordering occurred.
template <typename T, typename Traits>
bool GrTCluster_Visit(T* task, SkTInternalLList<T>* llist,
SkTHashMap<uint32_t, T*>* lastTaskMap) {
SkScopeExit exit([&]{
llist->addToTail(task);
CLUSTER_DEBUGF("Cluster: Output order is now: %s\n",
GrTCluster_DebugStr<T, Traits>(*llist).c_str());
});
CLUSTER_DEBUGF("Cluster: ***Step***\nLooking at %s\n",
GrTCluster_DebugStr<T, Traits>(task).c_str());
if (Traits::NumTargets(task) != 1) {
CLUSTER_DEBUGF("Cluster: %d targets. Emitting barriers.\n", Traits::NumTargets(task));
// Tasks with 0 or multiple targets are treated as full barriers
// for all their targets.
for (int j = 0; j < Traits::NumTargets(task); j++) {
lastTaskMap->remove(Traits::GetTarget(task, j));
}
return false;
}
uint32_t target = Traits::GetTarget(task, 0);
T* clusterTail = (lastTaskMap->find(target) ? *lastTaskMap->find(target) : nullptr);
lastTaskMap->set(target, task);
if (!clusterTail) {
CLUSTER_DEBUGF("Cluster: Bail, no cluster to extend.\n", SkToInt(target));
return false;
}
CLUSTER_DEBUGF("Cluster: clusterTail is %s.\n",
GrTCluster_DebugStr<T, Traits>(clusterTail).c_str());
if (clusterTail == llist->tail()) {
CLUSTER_DEBUGF("Cluster: Bail, cluster is already tail.\n");
return false;
}
T* movedHead = clusterTail->fNext;
// Now, let's refer to the "cluster" as the chain of tasks with the same target, that we're
// hoping to extend by reordering. Let "moved tasks" be the ones we want to move to extend the
// cluster.
T* clusterHead = clusterTail;
while (clusterHead->fPrev
&& 1 == Traits::NumTargets(clusterHead->fPrev)
&& target == Traits::GetTarget(clusterHead->fPrev, 0)) {
clusterHead = clusterHead->fPrev;
}
// We can't reorder if any moved task depends on anything in the cluster.
// Collect the cluster into a hash set.
// TODO: Is this overkill? Is a linear search appropriate for small clusters?
SkTHashSet<T*> clusterSet;
for (T* t = clusterHead; t != movedHead; t = t->fNext) {
clusterSet.add(t);
}
// Then check the constraint.
for (T* moved = movedHead; moved; moved = moved->fNext) {
for (int j = 0; j < Traits::NumDependencies(moved); j++) {
T* dep = Traits::Dependency(moved, j);
if (clusterSet.contains(dep)) {
CLUSTER_DEBUGF("Cluster: Bail, %s depends on %s.\n",
GrTCluster_DebugStr<T, Traits>(moved).c_str(),
GrTCluster_DebugStr<T, Traits>(dep).c_str());
return false;
}
}
}
// Grab the moved tasks and pull them before clusterHead.
for (T* moved = movedHead; moved;) {
CLUSTER_DEBUGF("Cluster: Reorder %s behind %s.\n",
GrTCluster_DebugStr<T, Traits>(moved).c_str(),
GrTCluster_DebugStr<T, Traits>(clusterHead).c_str());
// Be careful to save fNext before each move.
T* nextMoved = moved->fNext;
llist->remove(moved);
llist->addBefore(moved, clusterHead);
moved = nextMoved;
}
return true;
}
// Take a topologically-sorted DAG and cluster the entries together while maintaining the
// dependency order.
//
// If no clustering is possible, returns false.
// Otherwise, returns true and populates the provided llist as such:
// - Contains the same set of entries as `input`.
// - Obeys the dependency rules in `input`.
// - Places entries with the same Target (see Traits) adjacent to each other.
// - Entries with multiple targets act as reordering barriers for all their targets.
//
// T must declare a public SkTInternalLList interface.
//
// Traits requires:
//
// static int NumTargets(const T* t) { ... } //
// static uint32_t GetTarget(const T* t, int i) //
// static int NumDependencies(const T* t) { ... } //
// static T* Dependency(T* t, int index) { ... } //
// static uint32_t GetID(T* t) { ... } //
template <typename T, typename Traits = T>
bool GrTCluster(SkSpan<const sk_sp<T>> input, SkTInternalLList<T>* llist) {
SkASSERT(llist->isEmpty());
if (input.count() < 3) {
return false;
}
CLUSTER_DEBUGF("Cluster: Original order is %s\n",
GrTCluster_DebugStr<T, Traits>(input).c_str());
SkTHashMap<uint32_t, T*> lastTaskMap;
bool didReorder = false;
for (const auto& t : input) {
didReorder |= GrTCluster_Visit<T, Traits>(t.get(), llist, &lastTaskMap);
}
#ifdef SK_DEBUG
if (didReorder) {
GrTCluster_Validate<T, Traits>(input, *llist);
}
#endif
return didReorder;
}
#undef CLUSTER_DEBUGF
#endif