|  | /* | 
|  | * Copyright 2016 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "GrAuditTrail.h" | 
|  | #include "ops/GrOp.h" | 
|  |  | 
|  | const int GrAuditTrail::kGrAuditTrailInvalidID = -1; | 
|  |  | 
|  | void GrAuditTrail::addOp(const GrOp* op, GrRenderTargetProxy::UniqueID proxyID) { | 
|  | SkASSERT(fEnabled); | 
|  | Op* auditOp = new Op; | 
|  | fOpPool.emplace_back(auditOp); | 
|  | auditOp->fName = op->name(); | 
|  | auditOp->fBounds = op->bounds(); | 
|  | auditOp->fClientID = kGrAuditTrailInvalidID; | 
|  | auditOp->fOpListID = kGrAuditTrailInvalidID; | 
|  | auditOp->fChildID = kGrAuditTrailInvalidID; | 
|  |  | 
|  | // consume the current stack trace if any | 
|  | auditOp->fStackTrace = fCurrentStackTrace; | 
|  | fCurrentStackTrace.reset(); | 
|  |  | 
|  | if (fClientID != kGrAuditTrailInvalidID) { | 
|  | auditOp->fClientID = fClientID; | 
|  | Ops** opsLookup = fClientIDLookup.find(fClientID); | 
|  | Ops* ops = nullptr; | 
|  | if (!opsLookup) { | 
|  | ops = new Ops; | 
|  | fClientIDLookup.set(fClientID, ops); | 
|  | } else { | 
|  | ops = *opsLookup; | 
|  | } | 
|  |  | 
|  | ops->push_back(auditOp); | 
|  | } | 
|  |  | 
|  | // Our algorithm doesn't bother to reorder inside of an OpNode so the ChildID will start at 0 | 
|  | auditOp->fOpListID = fOpList.count(); | 
|  | auditOp->fChildID = 0; | 
|  |  | 
|  | // We use the op pointer as a key to find the OpNode we are 'glomming' ops onto | 
|  | fIDLookup.set(op->uniqueID(), auditOp->fOpListID); | 
|  | OpNode* opNode = new OpNode(proxyID); | 
|  | opNode->fBounds = op->bounds(); | 
|  | opNode->fChildren.push_back(auditOp); | 
|  | fOpList.emplace_back(opNode); | 
|  | } | 
|  |  | 
|  | void GrAuditTrail::opsCombined(const GrOp* consumer, const GrOp* consumed) { | 
|  | // Look up the op we are going to glom onto | 
|  | int* indexPtr = fIDLookup.find(consumer->uniqueID()); | 
|  | SkASSERT(indexPtr); | 
|  | int index = *indexPtr; | 
|  | SkASSERT(index < fOpList.count() && fOpList[index]); | 
|  | OpNode& consumerOp = *fOpList[index]; | 
|  |  | 
|  | // Look up the op which will be glommed | 
|  | int* consumedPtr = fIDLookup.find(consumed->uniqueID()); | 
|  | SkASSERT(consumedPtr); | 
|  | int consumedIndex = *consumedPtr; | 
|  | SkASSERT(consumedIndex < fOpList.count() && fOpList[consumedIndex]); | 
|  | OpNode& consumedOp = *fOpList[consumedIndex]; | 
|  |  | 
|  | // steal all of consumed's ops | 
|  | for (int i = 0; i < consumedOp.fChildren.count(); i++) { | 
|  | Op* childOp = consumedOp.fChildren[i]; | 
|  |  | 
|  | // set the ids for the child op | 
|  | childOp->fOpListID = index; | 
|  | childOp->fChildID = consumerOp.fChildren.count(); | 
|  | consumerOp.fChildren.push_back(childOp); | 
|  | } | 
|  |  | 
|  | // Update the bounds for the combineWith node | 
|  | consumerOp.fBounds = consumer->bounds(); | 
|  |  | 
|  | // remove the old node from our opList and clear the combinee's lookup | 
|  | // NOTE: because we can't change the shape of the oplist, we use a sentinel | 
|  | fOpList[consumedIndex].reset(nullptr); | 
|  | fIDLookup.remove(consumed->uniqueID()); | 
|  | } | 
|  |  | 
|  | void GrAuditTrail::copyOutFromOpList(OpInfo* outOpInfo, int opListID) { | 
|  | SkASSERT(opListID < fOpList.count()); | 
|  | const OpNode* bn = fOpList[opListID].get(); | 
|  | SkASSERT(bn); | 
|  | outOpInfo->fBounds = bn->fBounds; | 
|  | outOpInfo->fProxyUniqueID    = bn->fProxyUniqueID; | 
|  | for (int j = 0; j < bn->fChildren.count(); j++) { | 
|  | OpInfo::Op& outOp = outOpInfo->fOps.push_back(); | 
|  | const Op* currentOp = bn->fChildren[j]; | 
|  | outOp.fBounds = currentOp->fBounds; | 
|  | outOp.fClientID = currentOp->fClientID; | 
|  | } | 
|  | } | 
|  |  | 
|  | void GrAuditTrail::getBoundsByClientID(SkTArray<OpInfo>* outInfo, int clientID) { | 
|  | Ops** opsLookup = fClientIDLookup.find(clientID); | 
|  | if (opsLookup) { | 
|  | // We track which oplistID we're currently looking at.  If it changes, then we need to push | 
|  | // back a new op info struct.  We happen to know that ops are in sequential order in the | 
|  | // oplist, otherwise we'd have to do more bookkeeping | 
|  | int currentOpListID = kGrAuditTrailInvalidID; | 
|  | for (int i = 0; i < (*opsLookup)->count(); i++) { | 
|  | const Op* op = (**opsLookup)[i]; | 
|  |  | 
|  | // Because we will copy out all of the ops associated with a given op list id everytime | 
|  | // the id changes, we only have to update our struct when the id changes. | 
|  | if (kGrAuditTrailInvalidID == currentOpListID || op->fOpListID != currentOpListID) { | 
|  | OpInfo& outOpInfo = outInfo->push_back(); | 
|  |  | 
|  | // copy out all of the ops so the client can display them even if they have a | 
|  | // different clientID | 
|  | this->copyOutFromOpList(&outOpInfo, op->fOpListID); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void GrAuditTrail::getBoundsByOpListID(OpInfo* outInfo, int opListID) { | 
|  | this->copyOutFromOpList(outInfo, opListID); | 
|  | } | 
|  |  | 
|  | void GrAuditTrail::fullReset() { | 
|  | SkASSERT(fEnabled); | 
|  | fOpList.reset(); | 
|  | fIDLookup.reset(); | 
|  | // free all client ops | 
|  | fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; }); | 
|  | fClientIDLookup.reset(); | 
|  | fOpPool.reset();  // must be last, frees all of the memory | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void GrAuditTrail::JsonifyTArray(SkString* json, const char* name, const T& array, | 
|  | bool addComma) { | 
|  | if (array.count()) { | 
|  | if (addComma) { | 
|  | json->appendf(","); | 
|  | } | 
|  | json->appendf("\"%s\": [", name); | 
|  | const char* separator = ""; | 
|  | for (int i = 0; i < array.count(); i++) { | 
|  | // Handle sentinel nullptrs | 
|  | if (array[i]) { | 
|  | json->appendf("%s", separator); | 
|  | json->append(array[i]->toJson()); | 
|  | separator = ","; | 
|  | } | 
|  | } | 
|  | json->append("]"); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This will pretty print a very small subset of json | 
|  | // The parsing rules are straightforward, aside from the fact that we do not want an extra newline | 
|  | // before ',' and after '}', so we have a comma exception rule. | 
|  | class PrettyPrintJson { | 
|  | public: | 
|  | SkString prettify(const SkString& json) { | 
|  | fPrettyJson.reset(); | 
|  | fTabCount = 0; | 
|  | fFreshLine = false; | 
|  | fCommaException = false; | 
|  | for (size_t i = 0; i < json.size(); i++) { | 
|  | if ('[' == json[i] || '{' == json[i]) { | 
|  | this->newline(); | 
|  | this->appendChar(json[i]); | 
|  | fTabCount++; | 
|  | this->newline(); | 
|  | } else if (']' == json[i] || '}' == json[i]) { | 
|  | fTabCount--; | 
|  | this->newline(); | 
|  | this->appendChar(json[i]); | 
|  | fCommaException = true; | 
|  | } else if (',' == json[i]) { | 
|  | this->appendChar(json[i]); | 
|  | this->newline(); | 
|  | } else { | 
|  | this->appendChar(json[i]); | 
|  | } | 
|  | } | 
|  | return fPrettyJson; | 
|  | } | 
|  | private: | 
|  | void appendChar(char appendee) { | 
|  | if (fCommaException && ',' != appendee) { | 
|  | this->newline(); | 
|  | } | 
|  | this->tab(); | 
|  | fPrettyJson += appendee; | 
|  | fFreshLine = false; | 
|  | fCommaException = false; | 
|  | } | 
|  |  | 
|  | void tab() { | 
|  | if (fFreshLine) { | 
|  | for (int i = 0; i < fTabCount; i++) { | 
|  | fPrettyJson += '\t'; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void newline() { | 
|  | if (!fFreshLine) { | 
|  | fFreshLine = true; | 
|  | fPrettyJson += '\n'; | 
|  | } | 
|  | } | 
|  |  | 
|  | SkString fPrettyJson; | 
|  | int fTabCount; | 
|  | bool fFreshLine; | 
|  | bool fCommaException; | 
|  | }; | 
|  |  | 
|  | static SkString pretty_print_json(SkString json) { | 
|  | class PrettyPrintJson prettyPrintJson; | 
|  | return prettyPrintJson.prettify(json); | 
|  | } | 
|  |  | 
|  | SkString GrAuditTrail::toJson(bool prettyPrint) const { | 
|  | SkString json; | 
|  | json.append("{"); | 
|  | JsonifyTArray(&json, "Ops", fOpList, false); | 
|  | json.append("}"); | 
|  |  | 
|  | if (prettyPrint) { | 
|  | return pretty_print_json(json); | 
|  | } else { | 
|  | return json; | 
|  | } | 
|  | } | 
|  |  | 
|  | SkString GrAuditTrail::toJson(int clientID, bool prettyPrint) const { | 
|  | SkString json; | 
|  | json.append("{"); | 
|  | Ops** ops = fClientIDLookup.find(clientID); | 
|  | if (ops) { | 
|  | JsonifyTArray(&json, "Ops", **ops, false); | 
|  | } | 
|  | json.appendf("}"); | 
|  |  | 
|  | if (prettyPrint) { | 
|  | return pretty_print_json(json); | 
|  | } else { | 
|  | return json; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void skrect_to_json(SkString* json, const char* name, const SkRect& rect) { | 
|  | json->appendf("\"%s\": {", name); | 
|  | json->appendf("\"Left\": %f,", rect.fLeft); | 
|  | json->appendf("\"Right\": %f,", rect.fRight); | 
|  | json->appendf("\"Top\": %f,", rect.fTop); | 
|  | json->appendf("\"Bottom\": %f", rect.fBottom); | 
|  | json->append("}"); | 
|  | } | 
|  |  | 
|  | SkString GrAuditTrail::Op::toJson() const { | 
|  | SkString json; | 
|  | json.append("{"); | 
|  | json.appendf("\"Name\": \"%s\",", fName.c_str()); | 
|  | json.appendf("\"ClientID\": \"%d\",", fClientID); | 
|  | json.appendf("\"OpListID\": \"%d\",", fOpListID); | 
|  | json.appendf("\"ChildID\": \"%d\",", fChildID); | 
|  | skrect_to_json(&json, "Bounds", fBounds); | 
|  | if (fStackTrace.count()) { | 
|  | json.append(",\"Stack\": ["); | 
|  | for (int i = 0; i < fStackTrace.count(); i++) { | 
|  | json.appendf("\"%s\"", fStackTrace[i].c_str()); | 
|  | if (i < fStackTrace.count() - 1) { | 
|  | json.append(","); | 
|  | } | 
|  | } | 
|  | json.append("]"); | 
|  | } | 
|  | json.append("}"); | 
|  | return json; | 
|  | } | 
|  |  | 
|  | SkString GrAuditTrail::OpNode::toJson() const { | 
|  | SkString json; | 
|  | json.append("{"); | 
|  | json.appendf("\"ProxyID\": \"%u\",", fProxyUniqueID.asUInt()); | 
|  | skrect_to_json(&json, "Bounds", fBounds); | 
|  | JsonifyTArray(&json, "Ops", fChildren, true); | 
|  | json.append("}"); | 
|  | return json; | 
|  | } |