/* * 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 "batches/GrBatch.h" const int GrAuditTrail::kGrAuditTrailInvalidID = -1; void GrAuditTrail::addBatch(const GrBatch* batch) { SkASSERT(fEnabled); Batch* auditBatch = new Batch; fBatchPool.emplace_back(auditBatch); auditBatch->fName = batch->name(); auditBatch->fBounds = batch->bounds(); auditBatch->fClientID = kGrAuditTrailInvalidID; auditBatch->fBatchListID = kGrAuditTrailInvalidID; auditBatch->fChildID = kGrAuditTrailInvalidID; // consume the current stack trace if any auditBatch->fStackTrace = fCurrentStackTrace; fCurrentStackTrace.reset(); if (fClientID != kGrAuditTrailInvalidID) { auditBatch->fClientID = fClientID; Batches** batchesLookup = fClientIDLookup.find(fClientID); Batches* batches = nullptr; if (!batchesLookup) { batches = new Batches; fClientIDLookup.set(fClientID, batches); } else { batches = *batchesLookup; } batches->push_back(auditBatch); } // Our algorithm doesn't bother to reorder inside of a BatchNode // so the ChildID will start at 0 auditBatch->fBatchListID = fBatchList.count(); auditBatch->fChildID = 0; // We use the batch pointer as a key to find the batchnode we are 'glomming' batches onto fIDLookup.set(batch->uniqueID(), auditBatch->fBatchListID); BatchNode* batchNode = new BatchNode; batchNode->fBounds = batch->bounds(); batchNode->fRenderTargetUniqueID = batch->renderTargetUniqueID(); batchNode->fChildren.push_back(auditBatch); fBatchList.emplace_back(batchNode); } void GrAuditTrail::batchingResultCombined(const GrBatch* consumer, const GrBatch* consumed) { // Look up the batch we are going to glom onto int* indexPtr = fIDLookup.find(consumer->uniqueID()); SkASSERT(indexPtr); int index = *indexPtr; SkASSERT(index < fBatchList.count() && fBatchList[index]); BatchNode& consumerBatch = *fBatchList[index]; // Look up the batch which will be glommed int* consumedPtr = fIDLookup.find(consumed->uniqueID()); SkASSERT(consumedPtr); int consumedIndex = *consumedPtr; SkASSERT(consumedIndex < fBatchList.count() && fBatchList[consumedIndex]); BatchNode& consumedBatch = *fBatchList[consumedIndex]; // steal all of consumed's batches for (int i = 0; i < consumedBatch.fChildren.count(); i++) { Batch* childBatch = consumedBatch.fChildren[i]; // set the ids for the child batch childBatch->fBatchListID = index; childBatch->fChildID = consumerBatch.fChildren.count(); consumerBatch.fChildren.push_back(childBatch); } // Update the bounds for the combineWith node consumerBatch.fBounds = consumer->bounds(); // remove the old node from our batchlist and clear the combinee's lookup // NOTE: because we can't change the shape of the batchlist, we use a sentinel fBatchList[consumedIndex].reset(nullptr); fIDLookup.remove(consumed->uniqueID()); } void GrAuditTrail::copyOutFromBatchList(BatchInfo* outBatchInfo, int batchListID) { SkASSERT(batchListID < fBatchList.count()); const BatchNode* bn = fBatchList[batchListID]; SkASSERT(bn); outBatchInfo->fBounds = bn->fBounds; outBatchInfo->fRenderTargetUniqueID = bn->fRenderTargetUniqueID; for (int j = 0; j < bn->fChildren.count(); j++) { BatchInfo::Batch& outBatch = outBatchInfo->fBatches.push_back(); const Batch* currentBatch = bn->fChildren[j]; outBatch.fBounds = currentBatch->fBounds; outBatch.fClientID = currentBatch->fClientID; } } void GrAuditTrail::getBoundsByClientID(SkTArray* outInfo, int clientID) { Batches** batchesLookup = fClientIDLookup.find(clientID); if (batchesLookup) { // We track which batchlistID we're currently looking at. If it changes, then we // need to push back a new batch info struct. We happen to know that batches are // in sequential order in the batchlist, otherwise we'd have to do more bookkeeping int currentBatchListID = kGrAuditTrailInvalidID; for (int i = 0; i < (*batchesLookup)->count(); i++) { const Batch* batch = (**batchesLookup)[i]; // Because we will copy out all of the batches associated with a given // batch list id everytime the id changes, we only have to update our struct // when the id changes. if (kGrAuditTrailInvalidID == currentBatchListID || batch->fBatchListID != currentBatchListID) { BatchInfo& outBatchInfo = outInfo->push_back(); // copy out all of the batches so the client can display them even if // they have a different clientID this->copyOutFromBatchList(&outBatchInfo, batch->fBatchListID); } } } } void GrAuditTrail::getBoundsByBatchListID(BatchInfo* outInfo, int batchListID) { this->copyOutFromBatchList(outInfo, batchListID); } void GrAuditTrail::fullReset() { SkASSERT(fEnabled); fBatchList.reset(); fIDLookup.reset(); // free all client batches fClientIDLookup.foreach([](const int&, Batches** batches) { delete *batches; }); fClientIDLookup.reset(); fBatchPool.reset(); // must be last, frees all of the memory } template 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, "Batches", fBatchList, 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("{"); Batches** batches = fClientIDLookup.find(clientID); if (batches) { JsonifyTArray(&json, "Batches", **batches, 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::Batch::toJson() const { SkString json; json.append("{"); json.appendf("\"Name\": \"%s\",", fName.c_str()); json.appendf("\"ClientID\": \"%d\",", fClientID); json.appendf("\"BatchListID\": \"%d\",", fBatchListID); 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::BatchNode::toJson() const { SkString json; json.append("{"); json.appendf("\"RenderTarget\": \"%u\",", fRenderTargetUniqueID); skrect_to_json(&json, "Bounds", fBounds); JsonifyTArray(&json, "Batches", fChildren, true); json.append("}"); return json; }