| /* | |
| * 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 "src/gpu/ganesh/GrAuditTrail.h" | |
| #include "src/gpu/ganesh/ops/GrOp.h" | |
| using namespace skia_private; | |
| 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->fOpsTaskID = kGrAuditTrailInvalidID; | |
| auditOp->fChildID = kGrAuditTrailInvalidID; | |
| // consume the current stack trace if any | |
| auditOp->fStackTrace = fCurrentStackTrace; | |
| fCurrentStackTrace.clear(); | |
| 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->fOpsTaskID = fOpsTask.size(); | |
| 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->fOpsTaskID); | |
| OpNode* opNode = new OpNode(proxyID); | |
| opNode->fBounds = op->bounds(); | |
| opNode->fChildren.push_back(auditOp); | |
| fOpsTask.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 < fOpsTask.size() && fOpsTask[index]); | |
| OpNode& consumerOp = *fOpsTask[index]; | |
| // Look up the op which will be glommed | |
| int* consumedPtr = fIDLookup.find(consumed->uniqueID()); | |
| SkASSERT(consumedPtr); | |
| int consumedIndex = *consumedPtr; | |
| SkASSERT(consumedIndex < fOpsTask.size() && fOpsTask[consumedIndex]); | |
| OpNode& consumedOp = *fOpsTask[consumedIndex]; | |
| // steal all of consumed's ops | |
| for (int i = 0; i < consumedOp.fChildren.size(); i++) { | |
| Op* childOp = consumedOp.fChildren[i]; | |
| // set the ids for the child op | |
| childOp->fOpsTaskID = index; | |
| childOp->fChildID = consumerOp.fChildren.size(); | |
| consumerOp.fChildren.push_back(childOp); | |
| } | |
| // Update the bounds for the combineWith node | |
| consumerOp.fBounds = consumer->bounds(); | |
| // remove the old node from our opsTask and clear the combinee's lookup | |
| // NOTE: because we can't change the shape of the oplist, we use a sentinel | |
| fOpsTask[consumedIndex].reset(nullptr); | |
| fIDLookup.remove(consumed->uniqueID()); | |
| } | |
| void GrAuditTrail::copyOutFromOpsTask(OpInfo* outOpInfo, int opsTaskID) { | |
| SkASSERT(opsTaskID < fOpsTask.size()); | |
| const OpNode* bn = fOpsTask[opsTaskID].get(); | |
| SkASSERT(bn); | |
| outOpInfo->fBounds = bn->fBounds; | |
| outOpInfo->fProxyUniqueID = bn->fProxyUniqueID; | |
| for (int j = 0; j < bn->fChildren.size(); 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(TArray<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 currentOpsTaskID = kGrAuditTrailInvalidID; | |
| for (int i = 0; i < (*opsLookup)->size(); 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 == currentOpsTaskID || op->fOpsTaskID != currentOpsTaskID) { | |
| 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->copyOutFromOpsTask(&outOpInfo, op->fOpsTaskID); | |
| } | |
| } | |
| } | |
| } | |
| void GrAuditTrail::getBoundsByOpsTaskID(OpInfo* outInfo, int opsTaskID) { | |
| this->copyOutFromOpsTask(outInfo, opsTaskID); | |
| } | |
| void GrAuditTrail::fullReset() { | |
| SkASSERT(fEnabled); | |
| fOpsTask.clear(); | |
| fIDLookup.reset(); | |
| // free all client ops | |
| fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; }); | |
| fClientIDLookup.reset(); | |
| fOpPool.clear(); // must be last, frees all of the memory | |
| } | |
| #ifdef SK_ENABLE_DUMP_GPU | |
| #include "src/utils/SkJSONWriter.h" | |
| template <typename T> | |
| void GrAuditTrail::JsonifyTArray(SkJSONWriter& writer, const char* name, const T& array) { | |
| if (array.size()) { | |
| writer.beginArray(name); | |
| for (int i = 0; i < array.size(); i++) { | |
| // Handle sentinel nullptrs | |
| if (array[i]) { | |
| array[i]->toJson(writer); | |
| } | |
| } | |
| writer.endArray(); | |
| } | |
| } | |
| void GrAuditTrail::toJson(SkJSONWriter& writer) const { | |
| writer.beginObject(); | |
| JsonifyTArray(writer, "Ops", fOpsTask); | |
| 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); | |
| writer.appendS32("ClientID", fClientID); | |
| writer.appendS32("OpsTaskID", fOpsTaskID); | |
| writer.appendS32("ChildID", fChildID); | |
| skrect_to_json(writer, "Bounds", fBounds); | |
| if (fStackTrace.size()) { | |
| writer.beginArray("Stack"); | |
| for (int i = 0; i < fStackTrace.size(); i++) { | |
| writer.appendString(fStackTrace[i]); | |
| } | |
| 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(); | |
| } | |
| #else | |
| template <typename T> | |
| void GrAuditTrail::JsonifyTArray(SkJSONWriter& writer, const char* name, const T& array) {} | |
| void GrAuditTrail::toJson(SkJSONWriter& writer) const {} | |
| void GrAuditTrail::toJson(SkJSONWriter& writer, int clientID) const {} | |
| void GrAuditTrail::Op::toJson(SkJSONWriter& writer) const {} | |
| void GrAuditTrail::OpNode::toJson(SkJSONWriter& writer) const {} | |
| #endif // SK_ENABLE_DUMP_GPU |