ccpr: Tessellate fans for very large and/or simple paths
This increases CPU work, but reduces overdraw on the GPU as compared to
Redbook fanning.
TBR=bsalomon@google.com
Change-Id: I396b887075d4422531908c2361ee1e26f076d5c3
Reviewed-on: https://skia-review.googlesource.com/107141
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index 2a632d3..b19ffdf 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -16,9 +16,10 @@
#include "SkPathPriv.h"
#include "SkPoint.h"
#include "ccpr/GrCCGeometry.h"
+#include <stdlib.h>
-using TriangleInstance = GrCCCoverageProcessor::TriangleInstance;
-using CubicInstance = GrCCCoverageProcessor::CubicInstance;
+using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
+using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
GrCCPathParser::GrCCPathParser(int maxTotalPaths, int maxPathPoints, int numSkPoints,
int numSkVerbs)
@@ -32,7 +33,8 @@
// that "end" at the beginning of the data. These will not be drawn, but will only be be read by
// the first actual batch.
fScissorSubBatches.push_back() = {PrimitiveTallies(), SkIRect::MakeEmpty()};
- fCoverageCountBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count()};
+ fCoverageCountBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(),
+ PrimitiveTallies()};
}
void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds,
@@ -105,6 +107,7 @@
fCurrPathPointsIdx = fGeometry.points().count();
fCurrPathVerbsIdx = fGeometry.verbs().count();
fCurrPathPrimitiveCounts = PrimitiveTallies();
+ fCurrPathFillType = path.getFillType();
fGeometry.beginPath();
@@ -160,7 +163,81 @@
int16_t atlasOffsetX, int16_t atlasOffsetY) {
SkASSERT(fParsingPath);
- fPathsInfo.push_back() = {scissorMode, atlasOffsetX, atlasOffsetY};
+ fPathsInfo.emplace_back(scissorMode, atlasOffsetX, atlasOffsetY);
+
+ // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
+ int numVerbs = fGeometry.verbs().count() - fCurrPathVerbsIdx - 1;
+ int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N.
+ int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width();
+ if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100.
+ fCurrPathPrimitiveCounts.fTriangles =
+ fCurrPathPrimitiveCounts.fWoundTriangles = 0;
+
+ const SkTArray<GrCCGeometry::Verb, true>& verbs = fGeometry.verbs();
+ const SkTArray<SkPoint, true>& pts = fGeometry.points();
+ int ptsIdx = fCurrPathPointsIdx;
+
+ // Build an SkPath of the Redbook fan.
+ SkPath fan;
+ fan.setFillType(fCurrPathFillType);
+ SkASSERT(GrCCGeometry::Verb::kBeginPath == verbs[fCurrPathVerbsIdx]);
+ for (int i = fCurrPathVerbsIdx + 1; i < fGeometry.verbs().count(); ++i) {
+ switch (verbs[i]) {
+ case GrCCGeometry::Verb::kBeginPath:
+ SK_ABORT("Invalid GrCCGeometry");
+ continue;
+
+ case GrCCGeometry::Verb::kBeginContour:
+ fan.moveTo(pts[ptsIdx++]);
+ continue;
+
+ case GrCCGeometry::Verb::kLineTo:
+ fan.lineTo(pts[ptsIdx++]);
+ continue;
+
+ case GrCCGeometry::Verb::kMonotonicQuadraticTo:
+ fan.lineTo(pts[ptsIdx + 1]);
+ ptsIdx += 2;
+ continue;
+
+ case GrCCGeometry::Verb::kMonotonicCubicTo:
+ fan.lineTo(pts[ptsIdx + 2]);
+ ptsIdx += 3;
+ continue;
+
+ case GrCCGeometry::Verb::kEndClosedContour:
+ case GrCCGeometry::Verb::kEndOpenContour:
+ fan.close();
+ continue;
+ }
+ }
+ GrTessellator::WindingVertex* vertices = nullptr;
+ int count = GrTessellator::PathToVertices(fan, std::numeric_limits<float>::infinity(),
+ SkRect::Make(clippedDevIBounds), &vertices);
+ SkASSERT(0 == count % 3);
+ for (int i = 0; i < count; i += 3) {
+ SkASSERT(vertices[i].fWinding == vertices[i + 1].fWinding);
+ SkASSERT(vertices[i].fWinding == vertices[i + 2].fWinding);
+ if (1 == abs(vertices[i].fWinding)) {
+ // Ensure this triangle's points actually wind in the same direction as fWinding.
+ float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX;
+ float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY;
+ float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX;
+ float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY;
+ float wind = ay*bx - ax*by;
+ if ((wind > 0) != (vertices[i].fWinding > 0)) {
+ std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
+ }
+ ++fCurrPathPrimitiveCounts.fTriangles;
+ } else {
+ ++fCurrPathPrimitiveCounts.fWoundTriangles;
+ }
+ }
+
+ fPathsInfo.back().fFanTessellation.reset(vertices);
+ fPathsInfo.back().fFanTessellationCount = count;
+ }
+
fTotalPrimitiveCounts[(int)scissorMode] += fCurrPathPrimitiveCounts;
if (ScissorMode::kScissored == scissorMode) {
@@ -180,15 +257,23 @@
GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() {
SkASSERT(!fInstanceBuffer);
SkASSERT(!fCoverageCountBatches.empty());
+ const auto& lastBatch = fCoverageCountBatches.back();
+ const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1];
- int maxMeshes = 1 + fScissorSubBatches.count() -
- fCoverageCountBatches.back().fEndScissorSubBatchIdx;
- fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
+ PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored] -
+ lastBatch.fEndNonScissorIndices;
+ batchTotalCounts += fTotalPrimitiveCounts[(int)ScissorMode::kScissored] -
+ lastScissorSubBatch.fEndPrimitiveIndices;
fCoverageCountBatches.push_back() = {
fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored],
- fScissorSubBatches.count()
+ fScissorSubBatches.count(),
+ batchTotalCounts
};
+
+ int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
+ fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
+
return fCoverageCountBatches.count() - 1;
}
@@ -205,10 +290,10 @@
// elements past the end for this method to use as scratch space.
//
// Returns the next triangle instance after the final one emitted.
-static TriangleInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
+static TriPointInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
SkTArray<int32_t, true>& indices, int firstIndex,
int indexCount, const Sk2f& atlasOffset,
- TriangleInstance out[]) {
+ TriPointInstance out[]) {
if (indexCount < 3) {
return out;
}
@@ -232,6 +317,22 @@
return out;
}
+static void emit_tessellated_fan(const GrTessellator::WindingVertex* vertices, int numVertices,
+ const Sk2f& atlasOffset, TriPointInstance* triPointInstanceData,
+ QuadPointInstance* quadPointInstanceData,
+ GrCCGeometry::PrimitiveTallies* indices) {
+ for (int i = 0; i < numVertices; i += 3) {
+ if (1 == abs(vertices[i].fWinding)) {
+ triPointInstanceData[indices->fTriangles++].set(vertices[i].fPos, vertices[i + 1].fPos,
+ vertices[i + 2].fPos, atlasOffset);
+ } else {
+ quadPointInstanceData[indices->fWoundTriangles++].set(
+ vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, atlasOffset,
+ static_cast<float>(vertices[i].fWinding));
+ }
+ }
+}
+
bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) {
SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath().
SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
@@ -250,7 +351,7 @@
//
// We already know how big to make each of the 6 arrays from fTotalPrimitiveCounts, so layout is
// straightforward. Start with triangles and quadratics. They both view the instance buffer as
- // an array of TriangleInstance[], so we can begin at zero and lay them out one after the other.
+ // an array of TriPointInstance[], so we can begin at zero and lay them out one after the other.
fBaseInstances[0].fTriangles = 0;
fBaseInstances[1].fTriangles = fBaseInstances[0].fTriangles +
fTotalPrimitiveCounts[0].fTriangles;
@@ -260,85 +361,112 @@
fTotalPrimitiveCounts[0].fQuadratics;
int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics;
- // Cubics view the same instance buffer as an array of CubicInstance[]. So, reinterpreting the
- // instance data as CubicInstance[], we start them on the first index that will not overwrite
- // previous TriangleInstance data.
- int cubicBaseIdx =
- GR_CT_DIV_ROUND_UP(triEndIdx * sizeof(TriangleInstance), sizeof(CubicInstance));
- fBaseInstances[0].fCubics = cubicBaseIdx;
+ // Wound triangles and cubics both view the same instance buffer as an array of
+ // QuadPointInstance[]. So, reinterpreting the instance data as QuadPointInstance[], we start
+ // them on the first index that will not overwrite previous TriPointInstance data.
+ int quadBaseIdx =
+ GR_CT_DIV_ROUND_UP(triEndIdx * sizeof(TriPointInstance), sizeof(QuadPointInstance));
+ fBaseInstances[0].fWoundTriangles = quadBaseIdx;
+ fBaseInstances[1].fWoundTriangles = fBaseInstances[0].fWoundTriangles +
+ fTotalPrimitiveCounts[0].fWoundTriangles;
+ fBaseInstances[0].fCubics = fBaseInstances[1].fWoundTriangles +
+ fTotalPrimitiveCounts[1].fWoundTriangles;
fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics;
- int cubicEndIdx = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
+ int quadEndIdx = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
- cubicEndIdx * sizeof(CubicInstance));
+ quadEndIdx * sizeof(QuadPointInstance));
if (!fInstanceBuffer) {
return false;
}
- TriangleInstance* triangleInstanceData = static_cast<TriangleInstance*>(fInstanceBuffer->map());
- CubicInstance* cubicInstanceData = reinterpret_cast<CubicInstance*>(triangleInstanceData);
- SkASSERT(cubicInstanceData);
+ TriPointInstance* triPointInstanceData = static_cast<TriPointInstance*>(fInstanceBuffer->map());
+ QuadPointInstance* quadPointInstanceData =
+ reinterpret_cast<QuadPointInstance*>(triPointInstanceData);
+ SkASSERT(quadPointInstanceData);
- PathInfo* currPathInfo = fPathsInfo.begin();
+ PathInfo* nextPathInfo = fPathsInfo.begin();
float atlasOffsetX = 0.0, atlasOffsetY = 0.0;
Sk2f atlasOffset;
- int ptsIdx = -1;
PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]};
PrimitiveTallies* currIndices = nullptr;
SkSTArray<256, int32_t, true> currFan;
+ bool currFanIsTessellated = false;
const SkTArray<SkPoint, true>& pts = fGeometry.points();
+ int ptsIdx = -1;
// Expand the ccpr verbs into GPU instance buffers.
for (GrCCGeometry::Verb verb : fGeometry.verbs()) {
switch (verb) {
case GrCCGeometry::Verb::kBeginPath:
SkASSERT(currFan.empty());
- currIndices = &instanceIndices[(int)currPathInfo->fScissorMode];
- atlasOffsetX = static_cast<float>(currPathInfo->fAtlasOffsetX);
- atlasOffsetY = static_cast<float>(currPathInfo->fAtlasOffsetY);
+ currIndices = &instanceIndices[(int)nextPathInfo->fScissorMode];
+ atlasOffsetX = static_cast<float>(nextPathInfo->fAtlasOffsetX);
+ atlasOffsetY = static_cast<float>(nextPathInfo->fAtlasOffsetY);
atlasOffset = {atlasOffsetX, atlasOffsetY};
- ++currPathInfo;
+ currFanIsTessellated = nextPathInfo->fFanTessellation.get();
+ if (currFanIsTessellated) {
+ emit_tessellated_fan(nextPathInfo->fFanTessellation.get(),
+ nextPathInfo->fFanTessellationCount, atlasOffset,
+ triPointInstanceData, quadPointInstanceData, currIndices);
+ }
+ ++nextPathInfo;
continue;
case GrCCGeometry::Verb::kBeginContour:
SkASSERT(currFan.empty());
- currFan.push_back(++ptsIdx);
+ ++ptsIdx;
+ if (!currFanIsTessellated) {
+ currFan.push_back(ptsIdx);
+ }
continue;
case GrCCGeometry::Verb::kLineTo:
- SkASSERT(!currFan.empty());
- currFan.push_back(++ptsIdx);
+ ++ptsIdx;
+ if (!currFanIsTessellated) {
+ SkASSERT(!currFan.empty());
+ currFan.push_back(ptsIdx);
+ }
continue;
case GrCCGeometry::Verb::kMonotonicQuadraticTo:
- SkASSERT(!currFan.empty());
- triangleInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
- currFan.push_back(ptsIdx += 2);
+ triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
+ ptsIdx += 2;
+ if (!currFanIsTessellated) {
+ SkASSERT(!currFan.empty());
+ currFan.push_back(ptsIdx);
+ }
continue;
case GrCCGeometry::Verb::kMonotonicCubicTo:
- SkASSERT(!currFan.empty());
- cubicInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX,
- atlasOffsetY);
- currFan.push_back(ptsIdx += 3);
+ quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX,
+ atlasOffsetY);
+ ptsIdx += 3;
+ if (!currFanIsTessellated) {
+ SkASSERT(!currFan.empty());
+ currFan.push_back(ptsIdx);
+ }
continue;
case GrCCGeometry::Verb::kEndClosedContour: // endPt == startPt.
- SkASSERT(!currFan.empty());
- currFan.pop_back();
+ if (!currFanIsTessellated) {
+ SkASSERT(!currFan.empty());
+ currFan.pop_back();
+ }
// fallthru.
case GrCCGeometry::Verb::kEndOpenContour: // endPt != startPt.
- if (currFan.count() >= 3) {
+ SkASSERT(!currFanIsTessellated || currFan.empty());
+ if (!currFanIsTessellated && currFan.count() >= 3) {
int fanSize = currFan.count();
// Reserve space for emit_recursive_fan. Technically this can grow to
// fanSize + log3(fanSize), but we approximate with log2.
currFan.push_back_n(SkNextLog2(fanSize));
- SkDEBUGCODE(TriangleInstance* end =)
+ SkDEBUGCODE(TriPointInstance* end =)
emit_recursive_fan(pts, currFan, 0, fanSize, atlasOffset,
- triangleInstanceData + currIndices->fTriangles);
+ triPointInstanceData + currIndices->fTriangles);
currIndices->fTriangles += fanSize - 2;
- SkASSERT(triangleInstanceData + currIndices->fTriangles == end);
+ SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
}
currFan.reset();
continue;
@@ -347,14 +475,16 @@
fInstanceBuffer->unmap();
- SkASSERT(currPathInfo == fPathsInfo.end());
+ SkASSERT(nextPathInfo == fPathsInfo.end());
SkASSERT(ptsIdx == pts.count() - 1);
SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles);
SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics);
SkASSERT(instanceIndices[0].fQuadratics == fBaseInstances[1].fQuadratics);
SkASSERT(instanceIndices[1].fQuadratics == triEndIdx);
+ SkASSERT(instanceIndices[0].fWoundTriangles == fBaseInstances[1].fWoundTriangles);
+ SkASSERT(instanceIndices[1].fWoundTriangles == fBaseInstances[0].fCubics);
SkASSERT(instanceIndices[0].fCubics == fBaseInstances[1].fCubics);
- SkASSERT(instanceIndices[1].fCubics == cubicEndIdx);
+ SkASSERT(instanceIndices[1].fCubics == quadEndIdx);
fMeshesScratchBuffer.reserve(fMaxMeshesPerDraw);
fDynamicStatesScratchBuffer.reserve(fMaxMeshesPerDraw);
@@ -365,36 +495,56 @@
void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCountBatchID batchID,
const SkIRect& drawBounds) const {
using RenderPass = GrCCCoverageProcessor::RenderPass;
+ using WindMethod = GrCCCoverageProcessor::WindMethod;
SkASSERT(fInstanceBuffer);
+ const PrimitiveTallies& batchTotalCounts = fCoverageCountBatches[batchID].fTotalPrimitiveCounts;
+
GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled,
SkBlendMode::kPlus);
- // Triangles.
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
- &PrimitiveTallies::fTriangles, drawBounds);
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
- &PrimitiveTallies::fTriangles, drawBounds); // Might get skipped.
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
- &PrimitiveTallies::fTriangles, drawBounds);
+ if (batchTotalCounts.fTriangles) {
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles,
+ drawBounds); // Might get skipped.
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+ }
- // Quadratics.
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticHulls,
- &PrimitiveTallies::fQuadratics, drawBounds);
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticCorners,
- &PrimitiveTallies::fQuadratics, drawBounds);
+ if (batchTotalCounts.fWoundTriangles) {
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
+ WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+ drawBounds);
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
+ WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+ drawBounds); // Might get skipped.
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+ WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+ drawBounds);
+ }
- // Cubics.
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicHulls,
- &PrimitiveTallies::fCubics, drawBounds);
- this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicCorners,
- &PrimitiveTallies::fCubics, drawBounds);
+ if (batchTotalCounts.fQuadratics) {
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticHulls,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fQuadratics, drawBounds);
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticCorners,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fQuadratics, drawBounds);
+ }
+
+ if (batchTotalCounts.fCubics) {
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicHulls,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fCubics, drawBounds);
+ this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicCorners,
+ WindMethod::kCrossProduct, &PrimitiveTallies::fCubics, drawBounds);
+ }
}
void GrCCPathParser::drawRenderPass(GrOpFlushState* flushState, const GrPipeline& pipeline,
CoverageCountBatchID batchID,
GrCCCoverageProcessor::RenderPass renderPass,
+ GrCCCoverageProcessor::WindMethod windMethod,
int PrimitiveTallies::*instanceType,
const SkIRect& drawBounds) const {
SkASSERT(pipeline.getScissorState().enabled());
@@ -407,12 +557,13 @@
fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
fDynamicStatesScratchBuffer.pop_back_n(fDynamicStatesScratchBuffer.count());
- GrCCCoverageProcessor proc(flushState->resourceProvider(), renderPass, flushState->caps());
+ GrCCCoverageProcessor proc(flushState->resourceProvider(), renderPass, windMethod);
SkASSERT(batchID > 0);
SkASSERT(batchID < fCoverageCountBatches.count());
const CoverageCountBatch& previousBatch = fCoverageCountBatches[batchID - 1];
const CoverageCountBatch& batch = fCoverageCountBatches[batchID];
+ SkDEBUGCODE(int totalInstanceCount = 0);
if (int instanceCount = batch.fEndNonScissorIndices.*instanceType -
previousBatch.fEndNonScissorIndices.*instanceType) {
@@ -422,6 +573,7 @@
proc.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance, &fMeshesScratchBuffer);
fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, drawBounds.width(),
drawBounds.height());
+ SkDEBUGCODE(totalInstanceCount += instanceCount);
}
SkASSERT(previousBatch.fEndScissorSubBatchIdx > 0);
@@ -439,10 +591,12 @@
proc.appendMesh(fInstanceBuffer.get(), instanceCount,
baseScissorInstance + startIndex, &fMeshesScratchBuffer);
fDynamicStatesScratchBuffer.push_back().fScissorRect = scissorSubBatch.fScissor;
+ SkDEBUGCODE(totalInstanceCount += instanceCount);
}
SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
SkASSERT(fMeshesScratchBuffer.count() <= fMaxMeshesPerDraw);
+ SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
if (!fMeshesScratchBuffer.empty()) {
SkASSERT(flushState->rtCommandBuffer());