| /* |
| * 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" |
| #include "SkJSONWriter.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(SkJSONWriter& writer, const char* name, const T& array) { |
| if (array.count()) { |
| writer.beginArray(name); |
| for (int i = 0; i < array.count(); i++) { |
| // Handle sentinel nullptrs |
| if (array[i]) { |
| array[i]->toJson(writer); |
| } |
| } |
| writer.endArray(); |
| } |
| } |
| |
| void GrAuditTrail::toJson(SkJSONWriter& writer) const { |
| writer.beginObject(); |
| JsonifyTArray(writer, "Ops", fOpList); |
| writer.endObject(); |
| } |
| |
| void GrAuditTrail::toJson(SkJSONWriter& writer, int clientID) const { |
| writer.beginObject(); |
| Ops** ops = fClientIDLookup.find(clientID); |
| if (ops) { |
| JsonifyTArray(writer, "Ops", **ops); |
| } |
| writer.endObject(); |
| } |
| |
| static void skrect_to_json(SkJSONWriter& writer, const char* name, const SkRect& rect) { |
| writer.beginObject(name); |
| writer.appendFloat("Left", rect.fLeft); |
| writer.appendFloat("Right", rect.fRight); |
| writer.appendFloat("Top", rect.fTop); |
| writer.appendFloat("Bottom", rect.fBottom); |
| writer.endObject(); |
| } |
| |
| void GrAuditTrail::Op::toJson(SkJSONWriter& writer) const { |
| writer.beginObject(); |
| writer.appendString("Name", fName.c_str()); |
| writer.appendS32("ClientID", fClientID); |
| writer.appendS32("OpListID", fOpListID); |
| writer.appendS32("ChildID", fChildID); |
| skrect_to_json(writer, "Bounds", fBounds); |
| if (fStackTrace.count()) { |
| writer.beginArray("Stack"); |
| for (int i = 0; i < fStackTrace.count(); i++) { |
| writer.appendString(fStackTrace[i].c_str()); |
| } |
| writer.endArray(); |
| } |
| writer.endObject(); |
| } |
| |
| void GrAuditTrail::OpNode::toJson(SkJSONWriter& writer) const { |
| writer.beginObject(); |
| writer.appendU32("ProxyID", fProxyUniqueID.asUInt()); |
| skrect_to_json(writer, "Bounds", fBounds); |
| JsonifyTArray(writer, "Ops", fChildren); |
| writer.endObject(); |
| } |