1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "GrAuditTrail.h"
9 #include "ops/GrOp.h"
10 #include "SkJSONWriter.h"
11 
12 const int GrAuditTrail::kGrAuditTrailInvalidID = -1;
13 
14 void GrAuditTrail::addOp(const GrOp* op, GrRenderTargetProxy::UniqueID proxyID) {
15     SkASSERT(fEnabled);
16     Op* auditOp = new Op;
17     fOpPool.emplace_back(auditOp);
18     auditOp->fName = op->name();
19     auditOp->fBounds = op->bounds();
20     auditOp->fClientID = kGrAuditTrailInvalidID;
21     auditOp->fOpListID = kGrAuditTrailInvalidID;
22     auditOp->fChildID = kGrAuditTrailInvalidID;
23 
24     // consume the current stack trace if any
25     auditOp->fStackTrace = fCurrentStackTrace;
26     fCurrentStackTrace.reset();
27 
28     if (fClientID != kGrAuditTrailInvalidID) {
29         auditOp->fClientID = fClientID;
30         Ops** opsLookup = fClientIDLookup.find(fClientID);
31         Ops* ops = nullptr;
32         if (!opsLookup) {
33             ops = new Ops;
34             fClientIDLookup.set(fClientID, ops);
35         } else {
36             ops = *opsLookup;
37         }
38 
39         ops->push_back(auditOp);
40     }
41 
42     // Our algorithm doesn't bother to reorder inside of an OpNode so the ChildID will start at 0
43     auditOp->fOpListID = fOpList.count();
44     auditOp->fChildID = 0;
45 
46     // We use the op pointer as a key to find the OpNode we are 'glomming' ops onto
47     fIDLookup.set(op->uniqueID(), auditOp->fOpListID);
48     OpNode* opNode = new OpNode(proxyID);
49     opNode->fBounds = op->bounds();
50     opNode->fChildren.push_back(auditOp);
51     fOpList.emplace_back(opNode);
52 }
53 
54 void GrAuditTrail::opsCombined(const GrOp* consumer, const GrOp* consumed) {
55     // Look up the op we are going to glom onto
56     int* indexPtr = fIDLookup.find(consumer->uniqueID());
57     SkASSERT(indexPtr);
58     int index = *indexPtr;
59     SkASSERT(index < fOpList.count() && fOpList[index]);
60     OpNode& consumerOp = *fOpList[index];
61 
62     // Look up the op which will be glommed
63     int* consumedPtr = fIDLookup.find(consumed->uniqueID());
64     SkASSERT(consumedPtr);
65     int consumedIndex = *consumedPtr;
66     SkASSERT(consumedIndex < fOpList.count() && fOpList[consumedIndex]);
67     OpNode& consumedOp = *fOpList[consumedIndex];
68 
69     // steal all of consumed's ops
70     for (int i = 0; i < consumedOp.fChildren.count(); i++) {
71         Op* childOp = consumedOp.fChildren[i];
72 
73         // set the ids for the child op
74         childOp->fOpListID = index;
75         childOp->fChildID = consumerOp.fChildren.count();
76         consumerOp.fChildren.push_back(childOp);
77     }
78 
79     // Update the bounds for the combineWith node
80     consumerOp.fBounds = consumer->bounds();
81 
82     // remove the old node from our opList and clear the combinee's lookup
83     // NOTE: because we can't change the shape of the oplist, we use a sentinel
84     fOpList[consumedIndex].reset(nullptr);
85     fIDLookup.remove(consumed->uniqueID());
86 }
87 
88 void GrAuditTrail::copyOutFromOpList(OpInfo* outOpInfo, int opListID) {
89     SkASSERT(opListID < fOpList.count());
90     const OpNode* bn = fOpList[opListID].get();
91     SkASSERT(bn);
92     outOpInfo->fBounds = bn->fBounds;
93     outOpInfo->fProxyUniqueID    = bn->fProxyUniqueID;
94     for (int j = 0; j < bn->fChildren.count(); j++) {
95         OpInfo::Op& outOp = outOpInfo->fOps.push_back();
96         const Op* currentOp = bn->fChildren[j];
97         outOp.fBounds = currentOp->fBounds;
98         outOp.fClientID = currentOp->fClientID;
99     }
100 }
101 
102 void GrAuditTrail::getBoundsByClientID(SkTArray<OpInfo>* outInfo, int clientID) {
103     Ops** opsLookup = fClientIDLookup.find(clientID);
104     if (opsLookup) {
105         // We track which oplistID we're currently looking at.  If it changes, then we need to push
106         // back a new op info struct.  We happen to know that ops are in sequential order in the
107         // oplist, otherwise we'd have to do more bookkeeping
108         int currentOpListID = kGrAuditTrailInvalidID;
109         for (int i = 0; i < (*opsLookup)->count(); i++) {
110             const Op* op = (**opsLookup)[i];
111 
112             // Because we will copy out all of the ops associated with a given op list id everytime
113             // the id changes, we only have to update our struct when the id changes.
114             if (kGrAuditTrailInvalidID == currentOpListID || op->fOpListID != currentOpListID) {
115                 OpInfo& outOpInfo = outInfo->push_back();
116 
117                 // copy out all of the ops so the client can display them even if they have a
118                 // different clientID
119                 this->copyOutFromOpList(&outOpInfo, op->fOpListID);
120             }
121         }
122     }
123 }
124 
125 void GrAuditTrail::getBoundsByOpListID(OpInfo* outInfo, int opListID) {
126     this->copyOutFromOpList(outInfo, opListID);
127 }
128 
129 void GrAuditTrail::fullReset() {
130     SkASSERT(fEnabled);
131     fOpList.reset();
132     fIDLookup.reset();
133     // free all client ops
134     fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; });
135     fClientIDLookup.reset();
136     fOpPool.reset();  // must be last, frees all of the memory
137 }
138 
139 template <typename T>
140 void GrAuditTrail::JsonifyTArray(SkJSONWriter& writer, const char* name, const T& array) {
141     if (array.count()) {
142         writer.beginArray(name);
143         for (int i = 0; i < array.count(); i++) {
144             // Handle sentinel nullptrs
145             if (array[i]) {
146                 array[i]->toJson(writer);
147             }
148         }
149         writer.endArray();
150     }
151 }
152 
153 void GrAuditTrail::toJson(SkJSONWriter& writer) const {
154     writer.beginObject();
155     JsonifyTArray(writer, "Ops", fOpList);
156     writer.endObject();
157 }
158 
159 void GrAuditTrail::toJson(SkJSONWriter& writer, int clientID) const {
160     writer.beginObject();
161     Ops** ops = fClientIDLookup.find(clientID);
162     if (ops) {
163         JsonifyTArray(writer, "Ops", **ops);
164     }
165     writer.endObject();
166 }
167 
168 static void skrect_to_json(SkJSONWriter& writer, const char* name, const SkRect& rect) {
169     writer.beginObject(name);
170     writer.appendFloat("Left", rect.fLeft);
171     writer.appendFloat("Right", rect.fRight);
172     writer.appendFloat("Top", rect.fTop);
173     writer.appendFloat("Bottom", rect.fBottom);
174     writer.endObject();
175 }
176 
177 void GrAuditTrail::Op::toJson(SkJSONWriter& writer) const {
178     writer.beginObject();
179     writer.appendString("Name", fName.c_str());
180     writer.appendS32("ClientID", fClientID);
181     writer.appendS32("OpListID", fOpListID);
182     writer.appendS32("ChildID", fChildID);
183     skrect_to_json(writer, "Bounds", fBounds);
184     if (fStackTrace.count()) {
185         writer.beginArray("Stack");
186         for (int i = 0; i < fStackTrace.count(); i++) {
187             writer.appendString(fStackTrace[i].c_str());
188         }
189         writer.endArray();
190     }
191     writer.endObject();
192 }
193 
194 void GrAuditTrail::OpNode::toJson(SkJSONWriter& writer) const {
195     writer.beginObject();
196     writer.appendU32("ProxyID", fProxyUniqueID.asUInt());
197     skrect_to_json(writer, "Bounds", fBounds);
198     JsonifyTArray(writer, "Ops", fChildren);
199     writer.endObject();
200 }
201