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