blob: f77c52e6f1628ff43f1b86c2ebacacdf7975fdf2 [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrCCPathParser.h"
#include "GrCaps.h"
#include "GrGpuCommandBuffer.h"
#include "GrOnFlushResourceProvider.h"
#include "GrOpFlushState.h"
#include "SkMathPriv.h"
#include "SkPath.h"
#include "SkPathPriv.h"
#include "SkPoint.h"
#include "ccpr/GrCCGeometry.h"
#include <stdlib.h>
using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
GrCCPathParser::GrCCPathParser(int maxTotalPaths, int maxPathPoints, int numSkPoints,
int numSkVerbs)
: fLocalDevPtsBuffer(maxPathPoints + 1) // Overallocate by one point to accomodate for
// overflow with Sk4f. (See parsePath.)
, fGeometry(numSkPoints, numSkVerbs)
, fPathsInfo(maxTotalPaths)
, fScissorSubBatches(maxTotalPaths)
, fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} {
// Batches decide what to draw by looking where the previous one ended. Define initial batches
// 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(),
PrimitiveTallies()};
}
void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds,
SkRect* devBounds45) {
const SkPoint* pts = SkPathPriv::PointData(path);
int numPts = path.countPoints();
SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count());
if (!numPts) {
devBounds->setEmpty();
devBounds45->setEmpty();
this->parsePath(path, nullptr);
return;
}
// m45 transforms path points into "45 degree" device space. A bounding box in this space gives
// the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal
// transform is not necessary as long as the shader uses the correct inverse.
SkMatrix m45;
m45.setSinCos(1, 1);
m45.preConcat(m);
// X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points:
// device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords).
// | 1 1 |
Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());
// Map the path's points to device space and accumulate bounding boxes.
Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T);
devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt);
Sk4f topLeft = devPt;
Sk4f bottomRight = devPt;
// Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two,
// and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must
// be at least one larger than the number of points.
devPt.store(&fLocalDevPtsBuffer[0]);
for (int i = 1; i < numPts; ++i) {
devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T);
devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt);
topLeft = Sk4f::Min(topLeft, devPt);
bottomRight = Sk4f::Max(bottomRight, devPt);
devPt.store(&fLocalDevPtsBuffer[i]);
}
SkPoint topLeftPts[2], bottomRightPts[2];
topLeft.store(topLeftPts);
bottomRight.store(bottomRightPts);
devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(),
bottomRightPts[0].y());
devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(),
bottomRightPts[1].y());
this->parsePath(path, fLocalDevPtsBuffer.get());
}
void GrCCPathParser::parseDeviceSpacePath(const SkPath& deviceSpacePath) {
this->parsePath(deviceSpacePath, SkPathPriv::PointData(deviceSpacePath));
}
void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts) {
SkASSERT(!fInstanceBuffer); // Can't call after finalize().
SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath() for the last one first.
SkDEBUGCODE(fParsingPath = true);
SkASSERT(path.isEmpty() || deviceSpacePts);
fCurrPathPointsIdx = fGeometry.points().count();
fCurrPathVerbsIdx = fGeometry.verbs().count();
fCurrPathPrimitiveCounts = PrimitiveTallies();
fGeometry.beginPath();
if (path.isEmpty()) {
return;
}
int ptsIdx = 0;
bool insideContour = false;
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
switch (verb) {
case SkPath::kMove_Verb:
this->endContourIfNeeded(insideContour);
fGeometry.beginContour(deviceSpacePts[ptsIdx]);
++ptsIdx;
insideContour = true;
continue;
case SkPath::kClose_Verb:
this->endContourIfNeeded(insideContour);
insideContour = false;
continue;
case SkPath::kLine_Verb:
fGeometry.lineTo(deviceSpacePts[ptsIdx]);
++ptsIdx;
continue;
case SkPath::kQuad_Verb:
fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]);
ptsIdx += 2;
continue;
case SkPath::kCubic_Verb:
fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]);
ptsIdx += 3;
continue;
case SkPath::kConic_Verb:
SK_ABORT("Conics are not supported.");
default:
SK_ABORT("Unexpected path verb.");
}
}
this->endContourIfNeeded(insideContour);
}
void GrCCPathParser::endContourIfNeeded(bool insideContour) {
if (insideContour) {
fCurrPathPrimitiveCounts += fGeometry.endContour();
}
}
void GrCCPathParser::saveParsedPath(ScissorMode scissorMode, const SkIRect& clippedDevIBounds,
int16_t atlasOffsetX, int16_t atlasOffsetY) {
SkASSERT(fParsingPath);
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.fWeightedTriangles = 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. We use "winding" fill type right now because we are
// producing a coverage count, and must fill in every region that has non-zero wind. The
// path processor will convert coverage count to the appropriate fill type later.
SkPath fan;
fan.setFillType(SkPath::kWinding_FillType);
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) {
int tessWinding = vertices[i].fWinding;
SkASSERT(tessWinding == vertices[i + 1].fWinding);
SkASSERT(tessWinding == vertices[i + 2].fWinding);
// Ensure this triangle's points actually wind in the same direction as tessWinding.
// CCPR shaders use the sign of wind to determine which direction to bloat, so even for
// "wound" triangles the winding sign and point ordering need to agree.
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 = ax*by - ay*bx;
if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense.
std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
}
if (1 == abs(tessWinding)) {
++fCurrPathPrimitiveCounts.fTriangles;
} else {
++fCurrPathPrimitiveCounts.fWeightedTriangles;
}
}
fPathsInfo.back().adoptFanTessellation(vertices, count);
}
fTotalPrimitiveCounts[(int)scissorMode] += fCurrPathPrimitiveCounts;
if (ScissorMode::kScissored == scissorMode) {
fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)ScissorMode::kScissored],
clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY)};
}
SkDEBUGCODE(fParsingPath = false);
}
void GrCCPathParser::discardParsedPath() {
SkASSERT(fParsingPath);
fGeometry.resize_back(fCurrPathPointsIdx, fCurrPathVerbsIdx);
SkDEBUGCODE(fParsingPath = false);
}
GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() {
SkASSERT(!fInstanceBuffer);
SkASSERT(!fCoverageCountBatches.empty());
const auto& lastBatch = fCoverageCountBatches.back();
int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1];
PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored] -
lastBatch.fEndNonScissorIndices;
batchTotalCounts += fTotalPrimitiveCounts[(int)ScissorMode::kScissored] -
lastScissorSubBatch.fEndPrimitiveIndices;
// This will invalidate lastBatch.
fCoverageCountBatches.push_back() = {
fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored],
fScissorSubBatches.count(),
batchTotalCounts
};
return fCoverageCountBatches.count() - 1;
}
// Emits a contour's triangle fan.
//
// Classic Redbook fanning would be the triangles: [0 1 2], [0 2 3], ..., [0 n-2 n-1].
//
// This function emits the triangle: [0 n/3 n*2/3], and then recurses on all three sides. The
// advantage to this approach is that for a convex-ish contour, it generates larger triangles.
// Classic fanning tends to generate long, skinny triangles, which are expensive to draw since they
// have a longer perimeter to rasterize and antialias.
//
// The indices array indexes the fan's points (think: glDrawElements), and must have at least log3
// elements past the end for this method to use as scratch space.
//
// Returns the next triangle instance after the final one emitted.
static TriPointInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
SkTArray<int32_t, true>& indices, int firstIndex,
int indexCount, const Sk2f& atlasOffset,
TriPointInstance out[]) {
if (indexCount < 3) {
return out;
}
int32_t oneThirdCount = indexCount / 3;
int32_t twoThirdsCount = (2 * indexCount) / 3;
out++->set(pts[indices[firstIndex]], pts[indices[firstIndex + oneThirdCount]],
pts[indices[firstIndex + twoThirdsCount]], atlasOffset);
out = emit_recursive_fan(pts, indices, firstIndex, oneThirdCount + 1, atlasOffset, out);
out = emit_recursive_fan(pts, indices, firstIndex + oneThirdCount,
twoThirdsCount - oneThirdCount + 1, atlasOffset, out);
int endIndex = firstIndex + indexCount;
int32_t oldValue = indices[endIndex];
indices[endIndex] = indices[firstIndex];
out = emit_recursive_fan(pts, indices, firstIndex + twoThirdsCount,
indexCount - twoThirdsCount + 1, atlasOffset, out);
indices[endIndex] = oldValue;
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->fWeightedTriangles++].setW(
vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, atlasOffset,
// Tessellator has opposite winding sense.
-static_cast<float>(vertices[i].fWinding));
}
}
}
bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) {
SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath().
SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored]);
SkASSERT(fCoverageCountBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count());
// Here we build a single instance buffer to share with every internal batch.
//
// CCPR processs 3 different types of primitives: triangles, quadratics, cubics. Each primitive
// type is further divided into instances that require a scissor and those that don't. This
// leaves us with 3*2 = 6 independent instance arrays to build for the GPU.
//
// Rather than place each instance array in its own GPU buffer, we allocate a single
// megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
// our draw calls to direct the GPU to the applicable elements within a given array.
//
// 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 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;
fBaseInstances[0].fQuadratics = fBaseInstances[1].fTriangles +
fTotalPrimitiveCounts[1].fTriangles;
fBaseInstances[1].fQuadratics = fBaseInstances[0].fQuadratics +
fTotalPrimitiveCounts[0].fQuadratics;
int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics;
// 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].fWeightedTriangles = quadBaseIdx;
fBaseInstances[1].fWeightedTriangles = fBaseInstances[0].fWeightedTriangles +
fTotalPrimitiveCounts[0].fWeightedTriangles;
fBaseInstances[0].fCubics = fBaseInstances[1].fWeightedTriangles +
fTotalPrimitiveCounts[1].fWeightedTriangles;
fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics;
int quadEndIdx = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
quadEndIdx * sizeof(QuadPointInstance));
if (!fInstanceBuffer) {
return false;
}
TriPointInstance* triPointInstanceData = static_cast<TriPointInstance*>(fInstanceBuffer->map());
QuadPointInstance* quadPointInstanceData =
reinterpret_cast<QuadPointInstance*>(triPointInstanceData);
SkASSERT(quadPointInstanceData);
PathInfo* nextPathInfo = fPathsInfo.begin();
float atlasOffsetX = 0.0, atlasOffsetY = 0.0;
Sk2f atlasOffset;
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)nextPathInfo->scissorMode()];
atlasOffsetX = static_cast<float>(nextPathInfo->atlasOffsetX());
atlasOffsetY = static_cast<float>(nextPathInfo->atlasOffsetY());
atlasOffset = {atlasOffsetX, atlasOffsetY};
currFanIsTessellated = nextPathInfo->hasFanTessellation();
if (currFanIsTessellated) {
emit_tessellated_fan(nextPathInfo->fanTessellation(),
nextPathInfo->fanTessellationCount(), atlasOffset,
triPointInstanceData, quadPointInstanceData, currIndices);
}
++nextPathInfo;
continue;
case GrCCGeometry::Verb::kBeginContour:
SkASSERT(currFan.empty());
++ptsIdx;
if (!currFanIsTessellated) {
currFan.push_back(ptsIdx);
}
continue;
case GrCCGeometry::Verb::kLineTo:
++ptsIdx;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case GrCCGeometry::Verb::kMonotonicQuadraticTo:
triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
ptsIdx += 2;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case GrCCGeometry::Verb::kMonotonicCubicTo:
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.
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.pop_back();
}
// fallthru.
case GrCCGeometry::Verb::kEndOpenContour: // endPt != startPt.
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(TriPointInstance* end =)
emit_recursive_fan(pts, currFan, 0, fanSize, atlasOffset,
triPointInstanceData + currIndices->fTriangles);
currIndices->fTriangles += fanSize - 2;
SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
}
currFan.reset();
continue;
}
}
fInstanceBuffer->unmap();
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].fWeightedTriangles == fBaseInstances[1].fWeightedTriangles);
SkASSERT(instanceIndices[1].fWeightedTriangles == fBaseInstances[0].fCubics);
SkASSERT(instanceIndices[0].fCubics == fBaseInstances[1].fCubics);
SkASSERT(instanceIndices[1].fCubics == quadEndIdx);
fMeshesScratchBuffer.reserve(fMaxMeshesPerDraw);
fDynamicStatesScratchBuffer.reserve(fMaxMeshesPerDraw);
return true;
}
void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCountBatchID batchID,
const SkIRect& drawBounds) const {
using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
SkASSERT(fInstanceBuffer);
const PrimitiveTallies& batchTotalCounts = fCoverageCountBatches[batchID].fTotalPrimitiveCounts;
GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled,
SkBlendMode::kPlus);
if (batchTotalCounts.fTriangles) {
this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kTriangles,
&PrimitiveTallies::fTriangles, drawBounds);
}
if (batchTotalCounts.fWeightedTriangles) {
this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kWeightedTriangles,
&PrimitiveTallies::fWeightedTriangles, drawBounds);
}
if (batchTotalCounts.fQuadratics) {
this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kQuadratics,
&PrimitiveTallies::fQuadratics, drawBounds);
}
if (batchTotalCounts.fCubics) {
this->drawPrimitives(flushState, pipeline, batchID, PrimitiveType::kCubics,
&PrimitiveTallies::fCubics, drawBounds);
}
}
void GrCCPathParser::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
CoverageCountBatchID batchID,
GrCCCoverageProcessor::PrimitiveType primitiveType,
int PrimitiveTallies::*instanceType,
const SkIRect& drawBounds) const {
SkASSERT(pipeline.getScissorState().enabled());
// Don't call reset(), as that also resets the reserve count.
fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
fDynamicStatesScratchBuffer.pop_back_n(fDynamicStatesScratchBuffer.count());
GrCCCoverageProcessor proc(flushState->resourceProvider(), primitiveType);
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) {
SkASSERT(instanceCount > 0);
int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType +
previousBatch.fEndNonScissorIndices.*instanceType;
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);
SkASSERT(batch.fEndScissorSubBatchIdx <= fScissorSubBatches.count());
int baseScissorInstance = fBaseInstances[(int)ScissorMode::kScissored].*instanceType;
for (int i = previousBatch.fEndScissorSubBatchIdx; i < batch.fEndScissorSubBatchIdx; ++i) {
const ScissorSubBatch& previousSubBatch = fScissorSubBatches[i - 1];
const ScissorSubBatch& scissorSubBatch = fScissorSubBatches[i];
int startIndex = previousSubBatch.fEndPrimitiveIndices.*instanceType;
int instanceCount = scissorSubBatch.fEndPrimitiveIndices.*instanceType - startIndex;
if (!instanceCount) {
continue;
}
SkASSERT(instanceCount > 0);
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()) {
proc.draw(flushState, pipeline, fMeshesScratchBuffer.begin(),
fDynamicStatesScratchBuffer.begin(), fMeshesScratchBuffer.count(),
SkRect::Make(drawBounds));
}
}