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
11const int GrAuditTrail::kGrAuditTrailInvalidID = -1;
12
13void GrAuditTrail::addOp(const GrOp* op, GrGpuResource::UniqueID renderTargetID) {
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(renderTargetID);
48    opNode->fBounds = op->bounds();
49    opNode->fChildren.push_back(auditOp);
50    fOpList.emplace_back(opNode);
51}
52
53void 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
87void 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->fRenderTargetUniqueID = bn->fRenderTargetUniqueID;
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
101void 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
124void GrAuditTrail::getBoundsByOpListID(OpInfo* outInfo, int opListID) {
125    this->copyOutFromOpList(outInfo, opListID);
126}
127
128void 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
138template <typename T>
139void 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.
162class PrettyPrintJson {
163public:
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    }
189private:
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
200    void tab() {
201        if (fFreshLine) {
202            for (int i = 0; i < fTabCount; i++) {
203                fPrettyJson += '\t';
204            }
205        }
206    }
207
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
221static SkString pretty_print_json(SkString json) {
222    class PrettyPrintJson prettyPrintJson;
223    return prettyPrintJson.prettify(json);
224}
225
226SkString 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
239SkString 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
255static 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
264SkString 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
286SkString GrAuditTrail::OpNode::toJson() const {
287    SkString json;
288    json.append("{");
289    json.appendf("\"RenderTarget\": \"%u\",", fRenderTargetUniqueID.asUInt());
290    skrect_to_json(&json, "Bounds", fBounds);
291    JsonifyTArray(&json, "Ops", fChildren, true);
292    json.append("}");
293    return json;
294}
295