Rename batch->op in GrAuditTrail.

Change-Id: I68670e5ceb06716e9928ee58485d63e157c7aca7
Reviewed-on: https://skia-review.googlesource.com/6345
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2016-12-20 16:14:45 -05:00 committed by Skia Commit-Bot
parent b44bb31137
commit 42ad83ac19
5 changed files with 136 additions and 146 deletions

View File

@ -49,31 +49,26 @@ public:
GrAuditTrail* fAuditTrail;
};
class AutoManageBatchList {
class AutoManageOpList {
public:
AutoManageBatchList(GrAuditTrail* auditTrail)
: fAutoEnable(auditTrail)
, fAuditTrail(auditTrail) {
}
AutoManageOpList(GrAuditTrail* auditTrail)
: fAutoEnable(auditTrail), fAuditTrail(auditTrail) {}
~AutoManageBatchList() {
fAuditTrail->fullReset();
}
~AutoManageOpList() { fAuditTrail->fullReset(); }
private:
AutoEnable fAutoEnable;
GrAuditTrail* fAuditTrail;
};
class AutoCollectBatches {
class AutoCollectOps {
public:
AutoCollectBatches(GrAuditTrail* auditTrail, int clientID)
: fAutoEnable(auditTrail)
, fAuditTrail(auditTrail) {
AutoCollectOps(GrAuditTrail* auditTrail, int clientID)
: fAutoEnable(auditTrail), fAuditTrail(auditTrail) {
fAuditTrail->setClientID(clientID);
}
~AutoCollectBatches() { fAuditTrail->setClientID(kGrAuditTrailInvalidID); }
~AutoCollectOps() { fAuditTrail->setClientID(kGrAuditTrailInvalidID); }
private:
AutoEnable fAutoEnable;
@ -85,19 +80,19 @@ public:
fCurrentStackTrace.push_back(SkString(framename));
}
void addBatch(const GrOp* batch);
void addOp(const GrOp*);
void batchingResultCombined(const GrOp* consumer, const GrOp* consumed);
void opsCombined(const GrOp* consumer, const GrOp* consumed);
// Because batching is heavily dependent on sequence of draw calls, these calls will only
// produce valid information for the given draw sequence which preceeded them.
// Specifically, future draw calls may change the batching and thus would invalidate
// the json. What this means is that for some sequence of draw calls N, the below toJson
// calls will only produce JSON which reflects N draw calls. This JSON may or may not be
// accurate for N + 1 or N - 1 draws depending on the actual batching algorithm used.
// Because op combining is heavily dependent on sequence of draw calls, these calls will only
// produce valid information for the given draw sequence which preceeded them. Specifically, ops
// of future draw calls may combine with previous ops and thus would invalidate the json. What
// this means is that for some sequence of draw calls N, the below toJson calls will only
// produce JSON which reflects N draw calls. This JSON may or may not be accurate for N + 1 or
// N - 1 draws depending on the actual combining algorithm used.
SkString toJson(bool prettyPrint = false) const;
// returns a json string of all of the batches associated with a given client id
// returns a json string of all of the ops associated with a given client id
SkString toJson(int clientID, bool prettyPrint = false) const;
bool isEnabled() { return fEnabled; }
@ -107,19 +102,19 @@ public:
// We could just return our internal bookkeeping struct if copying the data out becomes
// a performance issue, but until then its nice to decouple
struct BatchInfo {
SkRect fBounds;
struct OpInfo {
SkRect fBounds;
// TODO: switch over to GrSurfaceProxy::UniqueID
GrGpuResource::UniqueID fRenderTargetUniqueID;
struct Batch {
struct Op {
int fClientID;
SkRect fBounds;
};
SkTArray<Batch> fBatches;
SkTArray<Op> fOps;
};
void getBoundsByClientID(SkTArray<BatchInfo>* outInfo, int clientID);
void getBoundsByBatchListID(BatchInfo* outInfo, int batchListID);
void getBoundsByClientID(SkTArray<OpInfo>* outInfo, int clientID);
void getBoundsByOpListID(OpInfo* outInfo, int opListID);
void fullReset();
@ -127,41 +122,41 @@ public:
private:
// TODO if performance becomes an issue, we can move to using SkVarAlloc
struct Batch {
struct Op {
SkString toJson() const;
SkString fName;
SkTArray<SkString> fStackTrace;
SkRect fBounds;
int fClientID;
int fBatchListID;
int fOpListID;
int fChildID;
};
typedef SkTArray<std::unique_ptr<Batch>, true> BatchPool;
typedef SkTArray<std::unique_ptr<Op>, true> OpPool;
typedef SkTArray<Batch*> Batches;
typedef SkTArray<Op*> Ops;
struct BatchNode {
BatchNode(const GrGpuResource::UniqueID& id) : fRenderTargetUniqueID(id) { }
struct OpNode {
OpNode(const GrGpuResource::UniqueID& id) : fRenderTargetUniqueID(id) {}
SkString toJson() const;
SkRect fBounds;
Batches fChildren;
Ops fChildren;
const GrGpuResource::UniqueID fRenderTargetUniqueID;
};
typedef SkTArray<std::unique_ptr<BatchNode>, true> BatchList;
typedef SkTArray<std::unique_ptr<OpNode>, true> OpList;
void copyOutFromBatchList(BatchInfo* outBatchInfo, int batchListID);
void copyOutFromOpList(OpInfo* outOpInfo, int opListID);
template <typename T>
static void JsonifyTArray(SkString* json, const char* name, const T& array,
bool addComma);
BatchPool fBatchPool;
OpPool fOpPool;
SkTHashMap<uint32_t, int> fIDLookup;
SkTHashMap<int, Batches*> fClientIDLookup;
BatchList fBatchList;
SkTHashMap<int, Ops*> fClientIDLookup;
OpList fOpList;
SkTArray<SkString> fCurrentStackTrace;
// The client cas pass in an optional client ID which we will use to mark the batches
// The client can pass in an optional client ID which we will use to mark the ops
int fClientID;
bool fEnabled;
};
@ -177,13 +172,11 @@ private:
#define GR_AUDIT_TRAIL_RESET(audit_trail) \
//GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, fullReset);
#define GR_AUDIT_TRAIL_ADDBATCH(audit_trail, batch) \
GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, addBatch, batch);
#define GR_AUDIT_TRAIL_ADD_OP(audit_trail, op) GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, addOp, op);
#define GR_AUDIT_TRAIL_BATCHING_RESULT_COMBINED(audit_trail, combineWith, batch) \
GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, batchingResultCombined, combineWith, batch);
#define GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(audit_trail, combineWith, op) \
GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, opsCombined, combineWith, op);
#define GR_AUDIT_TRAIL_BATCHING_RESULT_NEW(audit_trail, batch) \
// Doesn't do anything now, one day...
#define GR_AUDIT_TRAIL_OP_RESULT_NEW(audit_trail, op) // Doesn't do anything now, one day...
#endif

View File

@ -10,132 +10,129 @@
const int GrAuditTrail::kGrAuditTrailInvalidID = -1;
void GrAuditTrail::addBatch(const GrOp* batch) {
void GrAuditTrail::addOp(const GrOp* op) {
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;
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
auditBatch->fStackTrace = fCurrentStackTrace;
auditOp->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);
auditOp->fClientID = fClientID;
Ops** opsLookup = fClientIDLookup.find(fClientID);
Ops* ops = nullptr;
if (!opsLookup) {
ops = new Ops;
fClientIDLookup.set(fClientID, ops);
} else {
batches = *batchesLookup;
ops = *opsLookup;
}
batches->push_back(auditBatch);
ops->push_back(auditOp);
}
// 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;
// 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 batch pointer as a key to find the batchnode we are 'glomming' batches onto
fIDLookup.set(batch->uniqueID(), auditBatch->fBatchListID);
BatchNode* batchNode = new BatchNode(batch->renderTargetUniqueID());
batchNode->fBounds = batch->bounds();
batchNode->fChildren.push_back(auditBatch);
fBatchList.emplace_back(batchNode);
// 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(op->renderTargetUniqueID());
opNode->fBounds = op->bounds();
opNode->fChildren.push_back(auditOp);
fOpList.emplace_back(opNode);
}
void GrAuditTrail::batchingResultCombined(const GrOp* consumer, const GrOp* consumed) {
// Look up the batch we are going to glom onto
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 < fBatchList.count() && fBatchList[index]);
BatchNode& consumerBatch = *fBatchList[index];
SkASSERT(index < fOpList.count() && fOpList[index]);
OpNode& consumerOp = *fOpList[index];
// Look up the batch which will be glommed
// Look up the op 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];
SkASSERT(consumedIndex < fOpList.count() && fOpList[consumedIndex]);
OpNode& consumedOp = *fOpList[consumedIndex];
// steal all of consumed's batches
for (int i = 0; i < consumedBatch.fChildren.count(); i++) {
Batch* childBatch = consumedBatch.fChildren[i];
// 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 batch
childBatch->fBatchListID = index;
childBatch->fChildID = consumerBatch.fChildren.count();
consumerBatch.fChildren.push_back(childBatch);
// 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
consumerBatch.fBounds = consumer->bounds();
consumerOp.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);
// 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::copyOutFromBatchList(BatchInfo* outBatchInfo, int batchListID) {
SkASSERT(batchListID < fBatchList.count());
const BatchNode* bn = fBatchList[batchListID].get();
void GrAuditTrail::copyOutFromOpList(OpInfo* outOpInfo, int opListID) {
SkASSERT(opListID < fOpList.count());
const OpNode* bn = fOpList[opListID].get();
SkASSERT(bn);
outBatchInfo->fBounds = bn->fBounds;
outBatchInfo->fRenderTargetUniqueID = bn->fRenderTargetUniqueID;
outOpInfo->fBounds = bn->fBounds;
outOpInfo->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;
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<BatchInfo>* 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];
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 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();
// 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 batches so the client can display them even if
// they have a different clientID
this->copyOutFromBatchList(&outBatchInfo, batch->fBatchListID);
// 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::getBoundsByBatchListID(BatchInfo* outInfo, int batchListID) {
this->copyOutFromBatchList(outInfo, batchListID);
void GrAuditTrail::getBoundsByOpListID(OpInfo* outInfo, int opListID) {
this->copyOutFromOpList(outInfo, opListID);
}
void GrAuditTrail::fullReset() {
SkASSERT(fEnabled);
fBatchList.reset();
fOpList.reset();
fIDLookup.reset();
// free all client batches
fClientIDLookup.foreach([](const int&, Batches** batches) { delete *batches; });
// free all client ops
fClientIDLookup.foreach ([](const int&, Ops** ops) { delete *ops; });
fClientIDLookup.reset();
fBatchPool.reset(); // must be last, frees all of the memory
fOpPool.reset(); // must be last, frees all of the memory
}
template <typename T>
@ -229,7 +226,7 @@ static SkString pretty_print_json(SkString json) {
SkString GrAuditTrail::toJson(bool prettyPrint) const {
SkString json;
json.append("{");
JsonifyTArray(&json, "Batches", fBatchList, false);
JsonifyTArray(&json, "Batches", fOpList, false);
json.append("}");
if (prettyPrint) {
@ -242,9 +239,9 @@ SkString GrAuditTrail::toJson(bool prettyPrint) const {
SkString GrAuditTrail::toJson(int clientID, bool prettyPrint) const {
SkString json;
json.append("{");
Batches** batches = fClientIDLookup.find(clientID);
if (batches) {
JsonifyTArray(&json, "Batches", **batches, false);
Ops** ops = fClientIDLookup.find(clientID);
if (ops) {
JsonifyTArray(&json, "Batches", **ops, false);
}
json.appendf("}");
@ -264,12 +261,12 @@ static void skrect_to_json(SkString* json, const char* name, const SkRect& rect)
json->append("}");
}
SkString GrAuditTrail::Batch::toJson() const {
SkString GrAuditTrail::Op::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("\"BatchListID\": \"%d\",", fOpListID);
json.appendf("\"ChildID\": \"%d\",", fChildID);
skrect_to_json(&json, "Bounds", fBounds);
if (fStackTrace.count()) {
@ -286,7 +283,7 @@ SkString GrAuditTrail::Batch::toJson() const {
return json;
}
SkString GrAuditTrail::BatchNode::toJson() const {
SkString GrAuditTrail::OpNode::toJson() const {
SkString json;
json.append("{");
json.appendf("\"RenderTarget\": \"%u\",", fRenderTargetUniqueID.asUInt());

View File

@ -463,7 +463,7 @@ GrOp* GrRenderTargetOpList::recordOp(sk_sp<GrOp> op, const SkRect& clippedBounds
// 1) check every op
// 2) intersect with something
// 3) find a 'blocker'
GR_AUDIT_TRAIL_ADDBATCH(fAuditTrail, op.get());
GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get());
GrOP_INFO("Re-Recording (%s, B%u)\n"
"\tBounds LRTB (%f, %f, %f, %f)\n",
op->name(),
@ -489,7 +489,7 @@ GrOp* GrRenderTargetOpList::recordOp(sk_sp<GrOp> op, const SkRect& clippedBounds
if (candidate->combineIfPossible(op.get(), *this->caps())) {
GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate->name(),
candidate->uniqueID());
GR_AUDIT_TRAIL_BATCHING_RESULT_COMBINED(fAuditTrail, candidate, op.get());
GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, candidate, op.get());
join(&fRecordedOps.fromBack(i).fClippedBounds,
fRecordedOps.fromBack(i).fClippedBounds, clippedBounds);
return candidate;
@ -510,7 +510,7 @@ GrOp* GrRenderTargetOpList::recordOp(sk_sp<GrOp> op, const SkRect& clippedBounds
} else {
GrOP_INFO("\t\tFirstOp\n");
}
GR_AUDIT_TRAIL_BATCHING_RESULT_NEW(fAuditTrail, op);
GR_AUDIT_TRAIL_OP_RESULT_NEW(fAuditTrail, op);
fRecordedOps.emplace_back(RecordedOp{std::move(op), clippedBounds});
fLastFullClearOp = nullptr;
return fRecordedOps.back().fOp.get();
@ -540,7 +540,7 @@ void GrRenderTargetOpList::forwardCombine() {
} else if (op->combineIfPossible(candidate, *this->caps())) {
GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate->name(),
candidate->uniqueID());
GR_AUDIT_TRAIL_BATCHING_RESULT_COMBINED(fAuditTrail, op, candidate);
GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, op, candidate);
fRecordedOps[j].fOp = std::move(fRecordedOps[i].fOp);
join(&fRecordedOps[j].fClippedBounds, fRecordedOps[j].fClippedBounds, opBounds);
break;

View File

@ -98,7 +98,7 @@ void GrTextureOpList::recordOp(sk_sp<GrOp> op) {
// A closed GrOpList should never receive new/more ops
SkASSERT(!this->isClosed());
GR_AUDIT_TRAIL_ADDBATCH(fAuditTrail, op.get());
GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get());
GrOP_INFO("Re-Recording (%s, B%u)\n"
"\tBounds LRTB (%f, %f, %f, %f)\n",
op->name(),
@ -106,7 +106,7 @@ void GrTextureOpList::recordOp(sk_sp<GrOp> op) {
op->bounds().fLeft, op->bounds().fRight,
op->bounds().fTop, op->bounds().fBottom);
GrOP_INFO(SkTabString(op->dumpInfo(), 1).c_str());
GR_AUDIT_TRAIL_BATCHING_RESULT_NEW(fAuditTrail, op.get());
GR_AUDIT_TRAIL_OP_RESULT_NEW(fAuditTrail, op.get());
fRecordedOps.emplace_back(std::move(op));
}

View File

@ -251,9 +251,9 @@ void SkDebugCanvas::drawTo(SkCanvas* canvas, int index, int m) {
// created, so if we allow them to combine, the audit trail will fail to find them.
canvas->flush();
GrAuditTrail::AutoCollectBatches* acb = nullptr;
GrAuditTrail::AutoCollectOps* acb = nullptr;
if (at) {
acb = new GrAuditTrail::AutoCollectBatches(at, i);
acb = new GrAuditTrail::AutoCollectOps(at, i);
}
#endif
@ -351,12 +351,12 @@ void SkDebugCanvas::drawTo(SkCanvas* canvas, int index, int m) {
GrGpuResource::UniqueID rtID = rtc->accessRenderTarget()->uniqueID();
// get the bounding boxes to draw
SkTArray<GrAuditTrail::BatchInfo> childrenBounds;
SkTArray<GrAuditTrail::OpInfo> childrenBounds;
if (m == -1) {
at->getBoundsByClientID(&childrenBounds, index);
} else {
// the client wants us to draw the mth batch
at->getBoundsByBatchListID(&childrenBounds.push_back(), m);
at->getBoundsByOpListID(&childrenBounds.push_back(), m);
}
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
@ -368,8 +368,8 @@ void SkDebugCanvas::drawTo(SkCanvas* canvas, int index, int m) {
}
paint.setColor(kTotalBounds);
canvas->drawRect(childrenBounds[i].fBounds, paint);
for (int j = 0; j < childrenBounds[i].fBatches.count(); j++) {
const GrAuditTrail::BatchInfo::Batch& batch = childrenBounds[i].fBatches[j];
for (int j = 0; j < childrenBounds[i].fOps.count(); j++) {
const GrAuditTrail::OpInfo::Op& batch = childrenBounds[i].fOps[j];
if (batch.fClientID != index) {
paint.setColor(kOtherBatchBounds);
} else {
@ -436,7 +436,7 @@ void SkDebugCanvas::drawAndCollectBatches(int n, SkCanvas* canvas) {
// loop over all of the commands and draw them, this is to collect reordering
// information
for (int i = 0; i < this->getSize() && i <= n; i++) {
GrAuditTrail::AutoCollectBatches enable(at, i);
GrAuditTrail::AutoCollectOps enable(at, i);
fCommandVector[i]->execute(canvas);
}
@ -495,7 +495,7 @@ Json::Value SkDebugCanvas::toJSONBatchList(int n, SkCanvas* canvas) {
#if SK_SUPPORT_GPU
GrAuditTrail* at = this->getAuditTrail(canvas);
if (at) {
GrAuditTrail::AutoManageBatchList enable(at);
GrAuditTrail::AutoManageOpList enable(at);
Json::Reader reader;
SkAssertResult(reader.parse(at->toJson().c_str(), parsedFromString));
}