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 "src/gpu/GrAuditTrail.h" |
9 | #include "src/gpu/ops/GrOp.h" |
10 | #include "src/utils/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->fOpsTaskID = 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->fOpsTaskID = fOpsTask.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->fOpsTaskID); |
48 | OpNode* opNode = new OpNode(proxyID); |
49 | opNode->fBounds = op->bounds(); |
50 | opNode->fChildren.push_back(auditOp); |
51 | fOpsTask.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 < fOpsTask.count() && fOpsTask[index]); |
60 | OpNode& consumerOp = *fOpsTask[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 < fOpsTask.count() && fOpsTask[consumedIndex]); |
67 | OpNode& consumedOp = *fOpsTask[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->fOpsTaskID = 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 opsTask and clear the combinee's lookup |
83 | // NOTE: because we can't change the shape of the oplist, we use a sentinel |
84 | fOpsTask[consumedIndex].reset(nullptr); |
85 | fIDLookup.remove(consumed->uniqueID()); |
86 | } |
87 | |
88 | void GrAuditTrail::copyOutFromOpsTask(OpInfo* outOpInfo, int opsTaskID) { |
89 | SkASSERT(opsTaskID < fOpsTask.count()); |
90 | const OpNode* bn = fOpsTask[opsTaskID].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 currentOpsTaskID = 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 == currentOpsTaskID || op->fOpsTaskID != currentOpsTaskID) { |
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->copyOutFromOpsTask(&outOpInfo, op->fOpsTaskID); |
120 | } |
121 | } |
122 | } |
123 | } |
124 | |
125 | void GrAuditTrail::getBoundsByOpsTaskID(OpInfo* outInfo, int opsTaskID) { |
126 | this->copyOutFromOpsTask(outInfo, opsTaskID); |
127 | } |
128 | |
129 | void GrAuditTrail::fullReset() { |
130 | SkASSERT(fEnabled); |
131 | fOpsTask.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" , fOpsTask); |
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("OpsTaskID" , fOpsTaskID); |
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 | |