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 
11 const int GrAuditTrail::kGrAuditTrailInvalidID = -1;
12 
addOp(const GrOp * op,GrRenderTargetProxy::UniqueID proxyID)13 void GrAuditTrail::addOp(const GrOp* op, GrRenderTargetProxy::UniqueID proxyID) {
14     SkASSERT(fEnabled);
15     Op* auditOp = new Op;
16     fOpPool.emplace_back(auditOp);
17     auditOp->fName = op->name();
18     auditOp->fBounds = op->bounds();
19     auditOp->fClientID = kGrAuditTrailInvalidID;
20     auditOp->fOpListID = kGrAuditTrailInvalidID;
21     auditOp->fChildID = kGrAuditTrailInvalidID;
22 
23     // consume the current stack trace if any
24     auditOp->fStackTrace = fCurrentStackTrace;
25     fCurrentStackTrace.reset();
26 
27     if (fClientID != kGrAuditTrailInvalidID) {
28         auditOp->fClientID = fClientID;
29         Ops** opsLookup = fClientIDLookup.find(fClientID);
30         Ops* ops = nullptr;
31         if (!opsLookup) {
32             ops = new Ops;
33             fClientIDLookup.set(fClientID, ops);
34         } else {
35             ops = *opsLookup;
36         }
37 
38         ops->push_back(auditOp);
39     }
40 
41     // Our algorithm doesn't bother to reorder inside of an OpNode so the ChildID will start at 0
42     auditOp->fOpListID = fOpList.count();
43     auditOp->fChildID = 0;
44 
45     // We use the op pointer as a key to find the OpNode we are 'glomming' ops onto
46     fIDLookup.set(op->uniqueID(), auditOp->fOpListID);
47     OpNode* opNode = new OpNode(proxyID);
48     opNode->fBounds = op->bounds();
49     opNode->fChildren.push_back(auditOp);
50     fOpList.emplace_back(opNode);
51 }
52 
opsCombined(const GrOp * consumer,const GrOp * consumed)53 void GrAuditTrail::opsCombined(const GrOp* consumer, const GrOp* consumed) {
54     // Look up the op we are going to glom onto
55     int* indexPtr = fIDLookup.find(consumer->uniqueID());
56     SkASSERT(indexPtr);
57     int index = *indexPtr;
58     SkASSERT(index < fOpList.count() && fOpList[index]);
59     OpNode& consumerOp = *fOpList[index];
60 
61     // Look up the op which will be glommed
62     int* consumedPtr = fIDLookup.find(consumed->uniqueID());
63     SkASSERT(consumedPtr);
64     int consumedIndex = *consumedPtr;
65     SkASSERT(consumedIndex < fOpList.count() && fOpList[consumedIndex]);
66     OpNode& consumedOp = *fOpList[consumedIndex];
67 
68     // steal all of consumed's ops
69     for (int i = 0; i < consumedOp.fChildren.count(); i++) {
70         Op* childOp = consumedOp.fChildren[i];
71 
72         // set the ids for the child op
73         childOp->fOpListID = index;
74         childOp->fChildID = consumerOp.fChildren.count();
75         consumerOp.fChildren.push_back(childOp);
76     }
77 
78     // Update the bounds for the combineWith node
79     consumerOp.fBounds = consumer->bounds();
80 
81     // remove the old node from our opList and clear the combinee's lookup
82     // NOTE: because we can't change the shape of the oplist, we use a sentinel
83     fOpList[consumedIndex].reset(nullptr);
84     fIDLookup.remove(consumed->uniqueID());
85 }
86 
copyOutFromOpList(OpInfo * outOpInfo,int opListID)87 void GrAuditTrail::copyOutFromOpList(OpInfo* outOpInfo, int opListID) {
88     SkASSERT(opListID < fOpList.count());
89     const OpNode* bn = fOpList[opListID].get();
90     SkASSERT(bn);
91     outOpInfo->fBounds = bn->fBounds;
92     outOpInfo->fProxyUniqueID    = bn->fProxyUniqueID;
93     for (int j = 0; j < bn->fChildren.count(); j++) {
94         OpInfo::Op& outOp = outOpInfo->fOps.push_back();
95         const Op* currentOp = bn->fChildren[j];
96         outOp.fBounds = currentOp->fBounds;
97         outOp.fClientID = currentOp->fClientID;
98     }
99 }
100 
getBoundsByClientID(SkTArray<OpInfo> * outInfo,int clientID)101 void GrAuditTrail::getBoundsByClientID(SkTArray<OpInfo>* outInfo, int clientID) {
102     Ops** opsLookup = fClientIDLookup.find(clientID);
103     if (opsLookup) {
104         // We track which oplistID we're currently looking at.  If it changes, then we need to push
105         // back a new op info struct.  We happen to know that ops are in sequential order in the
106         // oplist, otherwise we'd have to do more bookkeeping
107         int currentOpListID = kGrAuditTrailInvalidID;
108         for (int i = 0; i < (*opsLookup)->count(); i++) {
109             const Op* op = (**opsLookup)[i];
110 
111             // Because we will copy out all of the ops associated with a given op list id everytime
112             // the id changes, we only have to update our struct when the id changes.
113             if (kGrAuditTrailInvalidID == currentOpListID || op->fOpListID != currentOpListID) {
114                 OpInfo& outOpInfo = outInfo->push_back();
115 
116                 // copy out all of the ops so the client can display them even if they have a
117                 // different clientID
118                 this->copyOutFromOpList(&outOpInfo, op->fOpListID);
119             }
120         }
121     }
122 }
123 
getBoundsByOpListID(OpInfo * outInfo,int opListID)124 void GrAuditTrail::getBoundsByOpListID(OpInfo* outInfo, int opListID) {
125     this->copyOutFromOpList(outInfo, opListID);
126 }
127 
fullReset()128 void GrAuditTrail::fullReset() {
129     SkASSERT(fEnabled);
130     fOpList.reset();
131     fIDLookup.reset();
132     // free all client ops
133     fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; });
134     fClientIDLookup.reset();
135     fOpPool.reset();  // must be last, frees all of the memory
136 }
137 
138 template <typename T>
JsonifyTArray(SkString * json,const char * name,const T & array,bool addComma)139 void GrAuditTrail::JsonifyTArray(SkString* json, const char* name, const T& array,
140                                  bool addComma) {
141     if (array.count()) {
142         if (addComma) {
143             json->appendf(",");
144         }
145         json->appendf("\"%s\": [", name);
146         const char* separator = "";
147         for (int i = 0; i < array.count(); i++) {
148             // Handle sentinel nullptrs
149             if (array[i]) {
150                 json->appendf("%s", separator);
151                 json->append(array[i]->toJson());
152                 separator = ",";
153             }
154         }
155         json->append("]");
156     }
157 }
158 
159 // This will pretty print a very small subset of json
160 // The parsing rules are straightforward, aside from the fact that we do not want an extra newline
161 // before ',' and after '}', so we have a comma exception rule.
162 class PrettyPrintJson {
163 public:
prettify(const SkString & json)164     SkString prettify(const SkString& json) {
165         fPrettyJson.reset();
166         fTabCount = 0;
167         fFreshLine = false;
168         fCommaException = false;
169         for (size_t i = 0; i < json.size(); i++) {
170             if ('[' == json[i] || '{' == json[i]) {
171                 this->newline();
172                 this->appendChar(json[i]);
173                 fTabCount++;
174                 this->newline();
175             } else if (']' == json[i] || '}' == json[i]) {
176                 fTabCount--;
177                 this->newline();
178                 this->appendChar(json[i]);
179                 fCommaException = true;
180             } else if (',' == json[i]) {
181                 this->appendChar(json[i]);
182                 this->newline();
183             } else {
184                 this->appendChar(json[i]);
185             }
186         }
187         return fPrettyJson;
188     }
189 private:
appendChar(char appendee)190     void appendChar(char appendee) {
191         if (fCommaException && ',' != appendee) {
192             this->newline();
193         }
194         this->tab();
195         fPrettyJson += appendee;
196         fFreshLine = false;
197         fCommaException = false;
198     }
199 
tab()200     void tab() {
201         if (fFreshLine) {
202             for (int i = 0; i < fTabCount; i++) {
203                 fPrettyJson += '\t';
204             }
205         }
206     }
207 
newline()208     void newline() {
209         if (!fFreshLine) {
210             fFreshLine = true;
211             fPrettyJson += '\n';
212         }
213     }
214 
215     SkString fPrettyJson;
216     int fTabCount;
217     bool fFreshLine;
218     bool fCommaException;
219 };
220 
pretty_print_json(SkString json)221 static SkString pretty_print_json(SkString json) {
222     class PrettyPrintJson prettyPrintJson;
223     return prettyPrintJson.prettify(json);
224 }
225 
toJson(bool prettyPrint) const226 SkString GrAuditTrail::toJson(bool prettyPrint) const {
227     SkString json;
228     json.append("{");
229     JsonifyTArray(&json, "Ops", fOpList, false);
230     json.append("}");
231 
232     if (prettyPrint) {
233         return pretty_print_json(json);
234     } else {
235         return json;
236     }
237 }
238 
toJson(int clientID,bool prettyPrint) const239 SkString GrAuditTrail::toJson(int clientID, bool prettyPrint) const {
240     SkString json;
241     json.append("{");
242     Ops** ops = fClientIDLookup.find(clientID);
243     if (ops) {
244         JsonifyTArray(&json, "Ops", **ops, false);
245     }
246     json.appendf("}");
247 
248     if (prettyPrint) {
249         return pretty_print_json(json);
250     } else {
251         return json;
252     }
253 }
254 
skrect_to_json(SkString * json,const char * name,const SkRect & rect)255 static void skrect_to_json(SkString* json, const char* name, const SkRect& rect) {
256     json->appendf("\"%s\": {", name);
257     json->appendf("\"Left\": %f,", rect.fLeft);
258     json->appendf("\"Right\": %f,", rect.fRight);
259     json->appendf("\"Top\": %f,", rect.fTop);
260     json->appendf("\"Bottom\": %f", rect.fBottom);
261     json->append("}");
262 }
263 
toJson() const264 SkString GrAuditTrail::Op::toJson() const {
265     SkString json;
266     json.append("{");
267     json.appendf("\"Name\": \"%s\",", fName.c_str());
268     json.appendf("\"ClientID\": \"%d\",", fClientID);
269     json.appendf("\"OpListID\": \"%d\",", fOpListID);
270     json.appendf("\"ChildID\": \"%d\",", fChildID);
271     skrect_to_json(&json, "Bounds", fBounds);
272     if (fStackTrace.count()) {
273         json.append(",\"Stack\": [");
274         for (int i = 0; i < fStackTrace.count(); i++) {
275             json.appendf("\"%s\"", fStackTrace[i].c_str());
276             if (i < fStackTrace.count() - 1) {
277                 json.append(",");
278             }
279         }
280         json.append("]");
281     }
282     json.append("}");
283     return json;
284 }
285 
toJson() const286 SkString GrAuditTrail::OpNode::toJson() const {
287     SkString json;
288     json.append("{");
289     json.appendf("\"ProxyID\": \"%u\",", fProxyUniqueID.asUInt());
290     skrect_to_json(&json, "Bounds", fBounds);
291     JsonifyTArray(&json, "Ops", fChildren, true);
292     json.append("}");
293     return json;
294 }
295