CCPR: Rewrite path parsing

Creates a GrCCPRGeometry class that chops contours up into simple
segments that ccpr can render, and rewrites the GPU buffer creation to
be able to handle arbitrary lengths of ccpr geometry.

Bug: skia:
Change-Id: Iaa173a02729e177b0ed7ef7fbb9195d349be689d
Reviewed-on: https://skia-review.googlesource.com/41963
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2017-09-05 00:30:07 -06:00 committed by Skia Commit-Bot
parent 6a69593ea9
commit c1e59638b4
17 changed files with 1044 additions and 968 deletions

View File

@ -290,8 +290,8 @@ skia_gpu_sources = [
# coverage counting path renderer
"$_src/gpu/ccpr/GrCCPRAtlas.cpp",
"$_src/gpu/ccpr/GrCCPRAtlas.h",
"$_src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp",
"$_src/gpu/ccpr/GrCCPRCoverageOpsBuilder.h",
"$_src/gpu/ccpr/GrCCPRCoverageOp.cpp",
"$_src/gpu/ccpr/GrCCPRCoverageOp.h",
"$_src/gpu/ccpr/GrCCPRCoverageProcessor.cpp",
"$_src/gpu/ccpr/GrCCPRCoverageProcessor.h",
"$_src/gpu/ccpr/GrCCPRCubicProcessor.cpp",

View File

@ -25,15 +25,16 @@
#include "gl/GrGLGpu.cpp"
#include "ops/GrDrawOp.h"
using PrimitiveInstance = GrCCPRCoverageProcessor::PrimitiveInstance;
using TriangleInstance = GrCCPRCoverageProcessor::TriangleInstance;
using CurveInstance = GrCCPRCoverageProcessor::CurveInstance;
using Mode = GrCCPRCoverageProcessor::Mode;
static int num_points(Mode mode) {
return mode >= GrCCPRCoverageProcessor::Mode::kSerpentineInsets ? 4 : 3;
return mode >= Mode::kSerpentineInsets ? 4 : 3;
}
static int is_curve(Mode mode) {
return mode >= GrCCPRCoverageProcessor::Mode::kQuadraticHulls;
static int is_quadratic(Mode mode) {
return mode >= Mode::kQuadraticHulls && mode < Mode::kSerpentineInsets;
}
/**
@ -71,8 +72,9 @@ private:
{400.75f, 100.05f}
};
SkSTArray<16, SkPoint> fGpuPoints;
SkSTArray<3, PrimitiveInstance> fGpuInstances;
SkTArray<SkPoint> fGpuPoints;
SkTArray<int32_t> fInstanceData;
int fInstanceCount;
typedef SampleView INHERITED;
};
@ -111,7 +113,7 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
outline.moveTo(fPoints[0]);
if (4 == num_points(fMode)) {
outline.cubicTo(fPoints[1], fPoints[2], fPoints[3]);
} else if (is_curve(fMode)) {
} else if (is_quadratic(fMode)) {
outline.quadTo(fPoints[1], fPoints[3]);
} else {
outline.lineTo(fPoints[1]);
@ -158,7 +160,8 @@ void CCPRGeometryView::updateGpuData() {
int vertexCount = num_points(fMode);
fGpuPoints.reset();
fGpuInstances.reset();
fInstanceData.reset();
fInstanceCount = 0;
if (4 == vertexCount) {
double t[2], s[2];
@ -175,19 +178,15 @@ void CCPRGeometryView::updateGpuData() {
SkPoint chopped[10];
SkChopCubicAt(fPoints, chopped, chops.begin(), chops.count());
// Endpoints first, then control points.
for (int i = 0; i <= instanceCount; ++i) {
fGpuPoints.push_back(chopped[3*i]);
}
if (3 == instanceCount && SkCubicType::kLoop == type) {
fGpuPoints[2] = fGpuPoints[1]; // Account for floating point error.
}
fGpuPoints.push_back(chopped[0]);
for (int i = 0; i < instanceCount; ++i) {
fGpuPoints.push_back(chopped[3*i + 1]);
fGpuPoints.push_back(chopped[3*i + 2]);
// FIXME: we don't bother to send down the correct KLM t,s roots.
fGpuPoints.push_back({0, 0});
fGpuPoints.push_back({0, 0});
if (3 == instanceCount && SkCubicType::kLoop == type) {
fGpuPoints.push_back(chopped[3*i]); // Account for floating point error.
} else {
fGpuPoints.push_back(chopped[3*i + 3]);
}
}
if (fMode < Mode::kLoopInsets && SkCubicType::kLoop == type) {
@ -197,36 +196,36 @@ void CCPRGeometryView::updateGpuData() {
fMode = (Mode) ((int) fMode - 2);
}
int controlPointsIdx = instanceCount + 1;
for (int i = 0; i < instanceCount; ++i) {
fGpuInstances.push_back().fCubicData = {controlPointsIdx + i * 4, i};
fInstanceData.push_back(3*i);
fInstanceData.push_back(0); // Atlas offset.
++fInstanceCount;
}
} else if (is_curve(fMode)) {
SkPoint chopped[5];
fGpuPoints.push_back(fPoints[0]);
if (GrCCPRChopMonotonicQuadratics(fPoints[0], fPoints[1], fPoints[3], chopped)) {
// Endpoints.
fGpuPoints.push_back(chopped[2]);
fGpuPoints.push_back(chopped[4]);
// Control points.
fGpuPoints.push_back(chopped[1]);
fGpuPoints.push_back(chopped[3]);
fGpuInstances.push_back().fQuadraticData = {3, 0};
fGpuInstances.push_back().fQuadraticData = {4, 1};
} else {
fGpuPoints.push_back(fPoints[3]);
fGpuPoints.push_back(fPoints[1]);
fGpuInstances.push_back().fQuadraticData = {2, 0};
} else if (is_quadratic(fMode)) {
GrCCPRGeometry geometry;
geometry.beginContour(fPoints[0]);
geometry.quadraticTo(fPoints[1], fPoints[3]);
geometry.endContour();
fGpuPoints.push_back_n(geometry.points().count(), geometry.points().begin());
for (GrCCPRGeometry::Verb verb : geometry.verbs()) {
if (GrCCPRGeometry::Verb::kBeginContour == verb ||
GrCCPRGeometry::Verb::kEndOpenContour == verb ||
GrCCPRGeometry::Verb::kEndClosedContour == verb) {
continue;
}
SkASSERT(GrCCPRGeometry::Verb::kMonotonicQuadraticTo == verb);
fInstanceData.push_back(2 * fInstanceCount++); // Pts idx.
fInstanceData.push_back(0); // Atlas offset.
}
} else {
fGpuPoints.push_back(fPoints[0]);
fGpuPoints.push_back(fPoints[3]);
fGpuPoints.push_back(fPoints[1]);
fGpuInstances.push_back().fTriangleData = {0, 2, 1}; // Texel buffer has endpoints first.
}
for (PrimitiveInstance& instance : fGpuInstances) {
instance.fPackedAtlasOffset = 0;
fGpuPoints.push_back(fPoints[3]);
fInstanceData.push_back(0);
fInstanceData.push_back(1);
fInstanceData.push_back(2);
fInstanceData.push_back(0); // Atlas offset.
fInstanceCount = 1;
}
}
@ -246,11 +245,11 @@ void CCPRGeometryView::Op::onExecute(GrOpFlushState* state) {
return;
}
sk_sp<GrBuffer> instanceBuffer(rp->createBuffer(fView->fGpuInstances.count() * 4 * sizeof(int),
sk_sp<GrBuffer> instanceBuffer(rp->createBuffer(fView->fInstanceData.count() * sizeof(int),
kVertex_GrBufferType, kDynamic_GrAccessPattern,
GrResourceProvider::kNoPendingIO_Flag |
GrResourceProvider::kRequireGpuMemory_Flag,
fView->fGpuInstances.begin()));
fView->fInstanceData.begin()));
if (!instanceBuffer) {
return;
}
@ -262,7 +261,7 @@ void CCPRGeometryView::Op::onExecute(GrOpFlushState* state) {
SkDEBUGCODE(ccprProc.enableDebugVisualizations();)
GrMesh mesh(4 == vertexCount ? GrPrimitiveType::kLinesAdjacency : GrPrimitiveType::kTriangles);
mesh.setInstanced(instanceBuffer.get(), fView->fGpuInstances.count(), 0, vertexCount);
mesh.setInstanced(instanceBuffer.get(), fView->fInstanceCount, 0, vertexCount);
if (glGpu) {
glGpu->handleDirtyContext();

View File

@ -101,7 +101,7 @@ bool GrCCPRAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
}
sk_sp<GrRenderTargetContext> GrCCPRAtlas::finalize(GrOnFlushResourceProvider* onFlushRP,
std::unique_ptr<GrDrawOp> atlasOp) {
std::unique_ptr<GrDrawOp> atlasOp) {
SkASSERT(!fTextureProxy);
GrSurfaceDesc desc;

View File

@ -0,0 +1,467 @@
/*
* 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 "GrCCPRCoverageOp.h"
#include "GrGpuCommandBuffer.h"
#include "GrOnFlushResourceProvider.h"
#include "GrOpFlushState.h"
#include "SkMathPriv.h"
#include "SkPath.h"
#include "SkPathPriv.h"
#include "SkPoint.h"
#include "SkNx.h"
#include "ccpr/GrCCPRGeometry.h"
using TriangleInstance = GrCCPRCoverageProcessor::TriangleInstance;
using CurveInstance = GrCCPRCoverageProcessor::CurveInstance;
/**
* This is a view matrix that accumulates two bounding boxes as it maps points: device-space bounds
* and "45 degree" device-space bounds (| 1 -1 | * devCoords).
* | 1 1 |
*/
class AccumulatingViewMatrix {
public:
AccumulatingViewMatrix(const SkMatrix& m, const SkPoint& initialPoint);
SkPoint transform(const SkPoint& pt);
void getAccumulatedBounds(SkRect* devBounds, SkRect* devBounds45) const;
private:
Sk4f fX;
Sk4f fY;
Sk4f fT;
Sk4f fTopLeft;
Sk4f fBottomRight;
};
inline AccumulatingViewMatrix::AccumulatingViewMatrix(const SkMatrix& m,
const SkPoint& initialPoint) {
// m45 transforms into 45 degree space in order to find the octagon's diagonals. We could
// use SK_ScalarRoot2Over2 if we wanted an orthonormal transform, but this is irrelevant as
// long as the shader uses the correct inverse when coming back to device space.
SkMatrix m45;
m45.setSinCos(1, 1);
m45.preConcat(m);
fX = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
fY = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
fT = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());
Sk4f transformed = SkNx_fma(fY, Sk4f(initialPoint.y()), fT);
transformed = SkNx_fma(fX, Sk4f(initialPoint.x()), transformed);
fTopLeft = fBottomRight = transformed;
}
inline SkPoint AccumulatingViewMatrix::transform(const SkPoint& pt) {
Sk4f transformed = SkNx_fma(fY, Sk4f(pt.y()), fT);
transformed = SkNx_fma(fX, Sk4f(pt.x()), transformed);
fTopLeft = Sk4f::Min(fTopLeft, transformed);
fBottomRight = Sk4f::Max(fBottomRight, transformed);
// TODO: vst1_lane_f32? (Sk4f::storeLane?)
float data[4];
transformed.store(data);
return SkPoint::Make(data[0], data[1]);
}
inline void AccumulatingViewMatrix::getAccumulatedBounds(SkRect* devBounds,
SkRect* devBounds45) const {
float topLeft[4], bottomRight[4];
fTopLeft.store(topLeft);
fBottomRight.store(bottomRight);
devBounds->setLTRB(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]);
devBounds45->setLTRB(topLeft[2], topLeft[3], bottomRight[2], bottomRight[3]);
}
void GrCCPRCoverageOpsBuilder::parsePath(const SkMatrix& viewMatrix,
const SkPath& path, SkRect* devBounds,
SkRect* devBounds45) {
SkASSERT(!fParsingPath);
SkDEBUGCODE(fParsingPath = true);
fCurrPathPointsIdx = fGeometry.points().count();
fCurrPathVerbsIdx = fGeometry.verbs().count();
fCurrPathTallies = PrimitiveTallies();
fGeometry.beginPath();
const SkPoint* const pts = SkPathPriv::PointData(path);
int ptsIdx = 0;
bool insideContour = false;
SkASSERT(!path.isEmpty());
SkASSERT(path.countPoints() > 0);
AccumulatingViewMatrix m(viewMatrix, pts[0]);
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
switch (verb) {
case SkPath::kMove_Verb:
this->endContourIfNeeded(insideContour);
fGeometry.beginContour(m.transform(pts[ptsIdx++]));
insideContour = true;
continue;
case SkPath::kClose_Verb:
this->endContourIfNeeded(insideContour);
insideContour = false;
continue;
case SkPath::kLine_Verb:
fGeometry.lineTo(m.transform(pts[ptsIdx++]));
continue;
case SkPath::kQuad_Verb:
SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed.
fGeometry.quadraticTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]));
ptsIdx += 2;
continue;
case SkPath::kCubic_Verb:
SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed.
fGeometry.cubicTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]),
m.transform(pts[ptsIdx + 2]));
ptsIdx += 3;
continue;
case SkPath::kConic_Verb:
SK_ABORT("Conics are not supported.");
default:
SK_ABORT("Unexpected path verb.");
}
}
this->endContourIfNeeded(insideContour);
m.getAccumulatedBounds(devBounds, devBounds45);
}
void GrCCPRCoverageOpsBuilder::endContourIfNeeded(bool insideContour) {
if (insideContour) {
fCurrPathTallies += fGeometry.endContour();
}
}
void GrCCPRCoverageOpsBuilder::saveParsedPath(ScissorMode scissorMode,
const SkIRect& clippedDevIBounds,
int16_t atlasOffsetX, int16_t atlasOffsetY) {
SkASSERT(fParsingPath);
fPathsInfo.push_back() = {
scissorMode,
(atlasOffsetY << 16) | (atlasOffsetX & 0xffff),
std::move(fTerminatingOp)
};
fTallies[(int)scissorMode] += fCurrPathTallies;
if (ScissorMode::kScissored == scissorMode) {
fScissorBatches.push_back() = {
fCurrPathTallies,
clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY)
};
}
SkDEBUGCODE(fParsingPath = false);
}
void GrCCPRCoverageOpsBuilder::discardParsedPath() {
SkASSERT(fParsingPath);
// The code will still work whether or not the below assertion is true. It is just unlikely that
// the caller would want this, and probably indicative of of a mistake. (Why emit an
// intermediate Op (to switch to a new atlas?), just to then throw the path away?)
SkASSERT(!fTerminatingOp);
fGeometry.resize_back(fCurrPathPointsIdx, fCurrPathVerbsIdx);
SkDEBUGCODE(fParsingPath = false);
}
void GrCCPRCoverageOpsBuilder::emitOp(SkISize drawBounds) {
SkASSERT(!fTerminatingOp);
fTerminatingOp.reset(new GrCCPRCoverageOp(std::move(fScissorBatches), drawBounds));
SkASSERT(fScissorBatches.empty());
}
// 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 TriangleInstance* emit_recursive_fan(SkTArray<int32_t, true>& indices, int firstIndex,
int indexCount, int packedAtlasOffset,
TriangleInstance out[]) {
if (indexCount < 3) {
return out;
}
const int32_t oneThirdCount = indexCount / 3;
const int32_t twoThirdsCount = (2 * indexCount) / 3;
*out++ = {
indices[firstIndex],
indices[firstIndex + oneThirdCount],
indices[firstIndex + twoThirdsCount],
packedAtlasOffset
};
out = emit_recursive_fan(indices, firstIndex, oneThirdCount + 1, packedAtlasOffset, out);
out = emit_recursive_fan(indices, firstIndex + oneThirdCount,
twoThirdsCount - oneThirdCount + 1, packedAtlasOffset, out);
int endIndex = firstIndex + indexCount;
int32_t oldValue = indices[endIndex];
indices[endIndex] = indices[firstIndex];
out = emit_recursive_fan(indices, firstIndex + twoThirdsCount, indexCount - twoThirdsCount + 1,
packedAtlasOffset, out);
indices[endIndex] = oldValue;
return out;
}
bool GrCCPRCoverageOpsBuilder::finalize(GrOnFlushResourceProvider* onFlushRP,
SkTArray<std::unique_ptr<GrCCPRCoverageOp>>* ops) {
SkASSERT(!fParsingPath);
const SkTArray<SkPoint, true>& points = fGeometry.points();
sk_sp<GrBuffer> pointsBuffer = onFlushRP->makeBuffer(kTexel_GrBufferType,
points.count() * 2 * sizeof(float),
points.begin());
if (!pointsBuffer) {
return false;
}
// Configure the instance buffer layout.
PrimitiveTallies baseInstances[kNumScissorModes];
// int4 indices.
baseInstances[0].fTriangles = 0;
baseInstances[1].fTriangles = baseInstances[0].fTriangles + fTallies[0].fTriangles;
// int2 indices (curves index the buffer as int2 rather than int4).
baseInstances[0].fQuadratics = (baseInstances[1].fTriangles + fTallies[1].fTriangles) * 2;
baseInstances[1].fQuadratics = baseInstances[0].fQuadratics + fTallies[0].fQuadratics;
baseInstances[0].fSerpentines = baseInstances[1].fQuadratics + fTallies[1].fQuadratics;
baseInstances[1].fSerpentines = baseInstances[0].fSerpentines + fTallies[0].fSerpentines;
baseInstances[0].fLoops = baseInstances[1].fSerpentines + fTallies[1].fSerpentines;
baseInstances[1].fLoops = baseInstances[0].fLoops + fTallies[0].fLoops;
int instanceBufferSize = (baseInstances[1].fLoops + fTallies[1].fLoops) * sizeof(CurveInstance);
sk_sp<GrBuffer> instanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
instanceBufferSize);
if (!instanceBuffer) {
return false;
}
TriangleInstance* triangleInstanceData = static_cast<TriangleInstance*>(instanceBuffer->map());
CurveInstance* curveInstanceData = reinterpret_cast<CurveInstance*>(triangleInstanceData);
SkASSERT(curveInstanceData);
PathInfo* currPathInfo = fPathsInfo.begin();
int32_t packedAtlasOffset;
int ptsIdx = -1;
PrimitiveTallies instanceIndices[2] = {baseInstances[0], baseInstances[1]};
PrimitiveTallies* currIndices;
SkSTArray<256, int32_t, true> currFan;
#ifdef SK_DEBUG
int numScissoredPaths = 0;
int numScissorBatches = 0;
PrimitiveTallies initialBaseInstances[] = {baseInstances[0], baseInstances[1]};
#endif
// Expand the ccpr verbs into GPU instance buffers.
for (GrCCPRGeometry::Verb verb : fGeometry.verbs()) {
switch (verb) {
case GrCCPRGeometry::Verb::kBeginPath:
SkASSERT(currFan.empty());
currIndices = &instanceIndices[(int)currPathInfo->fScissorMode];
packedAtlasOffset = currPathInfo->fPackedAtlasOffset;
#ifdef SK_DEBUG
if (ScissorMode::kScissored == currPathInfo->fScissorMode) {
++numScissoredPaths;
}
#endif
if (auto op = std::move(currPathInfo->fTerminatingOp)) {
op->setBuffers(pointsBuffer, instanceBuffer, baseInstances, instanceIndices);
baseInstances[0] = instanceIndices[0];
baseInstances[1] = instanceIndices[1];
SkDEBUGCODE(numScissorBatches += op->fScissorBatches.count());
ops->push_back(std::move(op));
}
++currPathInfo;
continue;
case GrCCPRGeometry::Verb::kBeginContour:
SkASSERT(currFan.empty());
currFan.push_back(++ptsIdx);
continue;
case GrCCPRGeometry::Verb::kLineTo:
SkASSERT(!currFan.empty());
currFan.push_back(++ptsIdx);
continue;
case GrCCPRGeometry::Verb::kMonotonicQuadraticTo:
SkASSERT(!currFan.empty());
curveInstanceData[currIndices->fQuadratics++] = {ptsIdx, packedAtlasOffset};
currFan.push_back(ptsIdx += 2);
continue;
case GrCCPRGeometry::Verb::kConvexSerpentineTo:
SkASSERT(!currFan.empty());
curveInstanceData[currIndices->fSerpentines++] = {ptsIdx, packedAtlasOffset};
currFan.push_back(ptsIdx += 3);
continue;
case GrCCPRGeometry::Verb::kConvexLoopTo:
SkASSERT(!currFan.empty());
curveInstanceData[currIndices->fLoops++] = {ptsIdx, packedAtlasOffset};
currFan.push_back(ptsIdx += 3);
continue;
case GrCCPRGeometry::Verb::kEndClosedContour: // endPt == startPt.
SkASSERT(!currFan.empty());
currFan.pop_back();
// fallthru.
case GrCCPRGeometry::Verb::kEndOpenContour: // endPt != startPt.
if (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 =)
emit_recursive_fan(currFan, 0, fanSize, packedAtlasOffset,
triangleInstanceData + currIndices->fTriangles);
currIndices->fTriangles += fanSize - 2;
SkASSERT(triangleInstanceData + currIndices->fTriangles == end);
}
currFan.reset();
continue;
}
}
instanceBuffer->unmap();
if (auto op = std::move(fTerminatingOp)) {
op->setBuffers(std::move(pointsBuffer), std::move(instanceBuffer), baseInstances,
instanceIndices);
SkDEBUGCODE(numScissorBatches += op->fScissorBatches.count());
ops->push_back(std::move(op));
}
SkASSERT(currPathInfo == fPathsInfo.end());
SkASSERT(ptsIdx == points.count() - 1);
SkASSERT(numScissoredPaths == numScissorBatches);
SkASSERT(instanceIndices[0].fTriangles == initialBaseInstances[1].fTriangles);
SkASSERT(instanceIndices[1].fTriangles * 2 == initialBaseInstances[0].fQuadratics);
SkASSERT(instanceIndices[0].fQuadratics == initialBaseInstances[1].fQuadratics);
SkASSERT(instanceIndices[1].fQuadratics == initialBaseInstances[0].fSerpentines);
SkASSERT(instanceIndices[0].fSerpentines == initialBaseInstances[1].fSerpentines);
SkASSERT(instanceIndices[1].fSerpentines == initialBaseInstances[0].fLoops);
SkASSERT(instanceIndices[0].fLoops == initialBaseInstances[1].fLoops);
SkASSERT(instanceIndices[1].fLoops * (int) sizeof(CurveInstance) == instanceBufferSize);
return true;
}
void GrCCPRCoverageOp::setBuffers(sk_sp<GrBuffer> pointsBuffer, sk_sp<GrBuffer> instanceBuffer,
const PrimitiveTallies baseInstances[kNumScissorModes],
const PrimitiveTallies endInstances[kNumScissorModes]) {
fPointsBuffer = std::move(pointsBuffer);
fInstanceBuffer = std::move(instanceBuffer);
fBaseInstances[0] = baseInstances[0];
fBaseInstances[1] = baseInstances[1];
fInstanceCounts[0] = endInstances[0] - baseInstances[0];
fInstanceCounts[1] = endInstances[1] - baseInstances[1];
}
void GrCCPRCoverageOp::onExecute(GrOpFlushState* flushState) {
using Mode = GrCCPRCoverageProcessor::Mode;
SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fProxy));
SkASSERT(fPointsBuffer);
SkASSERT(fInstanceBuffer);
GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled,
SkBlendMode::kPlus);
fMeshesScratchBuffer.reserve(1 + fScissorBatches.count());
fDynamicStatesScratchBuffer.reserve(1 + fScissorBatches.count());
// Triangles.
auto constexpr kTrianglesGrPrimitiveType = GrCCPRCoverageProcessor::kTrianglesGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kCombinedTriangleHullsAndEdges,
kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleCorners,
kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
// Quadratics.
auto constexpr kQuadraticsGrPrimitiveType = GrCCPRCoverageProcessor::kQuadraticsGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticHulls,
kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticCorners,
kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
// Cubics.
auto constexpr kCubicsGrPrimitiveType = GrCCPRCoverageProcessor::kCubicsGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineInsets,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopInsets,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineBorders,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopBorders,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
}
void GrCCPRCoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
GrCCPRCoverageProcessor::Mode mode,
GrPrimitiveType primType, int vertexCount,
int PrimitiveTallies::* instanceType) const {
using ScissorMode = GrCCPRCoverageOpsBuilder::ScissorMode;
SkASSERT(pipeline.getScissorState().enabled());
fMeshesScratchBuffer.reset();
fDynamicStatesScratchBuffer.reset();
if (const int instanceCount = fInstanceCounts[(int)ScissorMode::kNonScissored].*instanceType) {
SkASSERT(instanceCount > 0);
const int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType;
GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType);
mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance, vertexCount);
fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, fDrawBounds.width(),
fDrawBounds.height());
}
if (fInstanceCounts[(int)ScissorMode::kScissored].*instanceType) {
int baseInstance = fBaseInstances[(int)ScissorMode::kScissored].*instanceType;
for (const ScissorBatch& batch : fScissorBatches) {
SkASSERT(this->bounds().contains(batch.fScissor));
const int instanceCount = batch.fInstanceCounts.*instanceType;
if (!instanceCount) {
continue;
}
SkASSERT(instanceCount > 0);
GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType);
mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance, vertexCount);
fDynamicStatesScratchBuffer.push_back().fScissorRect = batch.fScissor;
baseInstance += instanceCount;
}
}
SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
if (!fMeshesScratchBuffer.empty()) {
GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get());
SkASSERT(flushState->rtCommandBuffer());
flushState->rtCommandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(),
fDynamicStatesScratchBuffer.begin(),
fMeshesScratchBuffer.count(), this->bounds());
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrCCPRCoverageOp_DEFINED
#define GrCCPRCoverageOp_DEFINED
#include "GrMesh.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "ccpr/GrCCPRCoverageProcessor.h"
#include "ccpr/GrCCPRGeometry.h"
#include "ops/GrDrawOp.h"
class GrCCPRCoverageOp;
class GrOnFlushResourceProvider;
class SkMatrix;
class SkPath;
/**
* This class produces GrCCPRCoverageOps that render coverage count masks and atlases. A path is
* added to the current op in two steps:
*
* 1) parsePath(ScissorMode, viewMatrix, path, &devBounds, &devBounds45);
*
* <client decides where to put the mask within an atlas, if wanted>
*
* 2) saveParsedPath(offsetX, offsetY, clipBounds);
*
* The client can flush the currently saved paths to a GrCCPRCoverageOp by calling emitOp, and
* retrieve all emitted ops after calling finalize().
*/
class GrCCPRCoverageOpsBuilder {
public:
// Indicates whether a path should enforce a scissor clip when rendering its mask. (Specified
// as an int because these values get used directly as indices into arrays.)
enum class ScissorMode : int {
kNonScissored = 0,
kScissored = 1
};
static constexpr int kNumScissorModes = 2;
GrCCPRCoverageOpsBuilder(int maxTotalPaths, int numSkPoints, int numSkVerbs)
: fPathsInfo(maxTotalPaths)
, fGeometry(numSkPoints, numSkVerbs)
, fTallies{PrimitiveTallies(), PrimitiveTallies()}
, fScissorBatches(maxTotalPaths) {}
~GrCCPRCoverageOpsBuilder() {
// Enforce the contract that the client always calls saveParsedPath or discardParsedPath.
SkASSERT(!fParsingPath);
}
// Parses an SkPath into a temporary staging area. The path will not yet be included in the next
// Op unless there is a matching call to saveParsedPath. The user must complement this with a
// following call to either saveParsedPath or discardParsedPath.
//
// Returns two tight bounding boxes: device space and "45 degree" (| 1 -1 | * devCoords) space.
// | 1 1 |
void parsePath(const SkMatrix&, const SkPath&, SkRect* devBounds, SkRect* devBounds45);
// Commits the currently-parsed path from staging to the next Op, and specifies whether the mask
// should be rendered with a scissor clip in effect. Accepts an optional post-device-space
// translate for placement in an atlas.
void saveParsedPath(ScissorMode, const SkIRect& clippedDevIBounds,
int16_t atlasOffsetX, int16_t atlasOffsetY);
void discardParsedPath();
// Flushes all currently-saved paths internally to a GrCCPRCoverageOp.
//
// NOTE: if there is a parsed path in the staging area, it will not be included. But the client
// may still call saveParsedPath to include it in a future Op.
void emitOp(SkISize drawBounds);
// Builds GPU buffers and returns the list of GrCCPRCoverageOps as specified by calls to emitOp.
bool finalize(GrOnFlushResourceProvider*, SkTArray<std::unique_ptr<GrCCPRCoverageOp>>*);
private:
using PrimitiveTallies = GrCCPRGeometry::PrimitiveTallies;
// Every kBeginPath verb has a corresponding PathInfo entry.
struct PathInfo {
ScissorMode fScissorMode;
int32_t fPackedAtlasOffset; // (offsetY << 16) | (offsetX & 0xffff)
std::unique_ptr<GrCCPRCoverageOp> fTerminatingOp;
};
// Every PathInfo with a mode of kScissored has a corresponding ScissorBatch.
struct ScissorBatch {
PrimitiveTallies fInstanceCounts;
SkIRect fScissor;
};
void endContourIfNeeded(bool insideContour);
// Staging area for the path being parsed.
SkDEBUGCODE(int fParsingPath = false);
int fCurrPathPointsIdx;
int fCurrPathVerbsIdx;
PrimitiveTallies fCurrPathTallies;
SkSTArray<32, PathInfo, true> fPathsInfo;
GrCCPRGeometry fGeometry;
PrimitiveTallies fTallies[kNumScissorModes];
SkTArray<ScissorBatch, true> fScissorBatches;
std::unique_ptr<GrCCPRCoverageOp> fTerminatingOp;
friend class GrCCPRCoverageOp; // For ScissorBatch.
};
/**
* This Op renders coverage count masks and atlases. Create it using GrCCPRCoverageOpsBuilder.
*/
class GrCCPRCoverageOp : public GrDrawOp {
public:
DEFINE_OP_CLASS_ID
// GrDrawOp interface.
const char* name() const override { return "GrCCPRCoverageOp"; }
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override {
return RequiresDstTexture::kNo;
}
bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState*) override;
private:
static constexpr int kNumScissorModes = GrCCPRCoverageOpsBuilder::kNumScissorModes;
using PrimitiveTallies = GrCCPRGeometry::PrimitiveTallies;
using ScissorBatch = GrCCPRCoverageOpsBuilder::ScissorBatch;
GrCCPRCoverageOp(SkTArray<ScissorBatch, true>&& scissorBatches, const SkISize& drawBounds)
: INHERITED(ClassID())
, fScissorBatches(std::move(scissorBatches))
, fDrawBounds(drawBounds) {
this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
}
void setBuffers(sk_sp<GrBuffer> pointsBuffer, sk_sp<GrBuffer> instanceBuffer,
const PrimitiveTallies baseInstances[kNumScissorModes],
const PrimitiveTallies endInstances[kNumScissorModes]);
void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&, const GrCCPRCoverageProcessor::Mode,
GrPrimitiveType, int vertexCount,
int PrimitiveTallies::* instanceType) const;
sk_sp<GrBuffer> fPointsBuffer;
sk_sp<GrBuffer> fInstanceBuffer;
PrimitiveTallies fBaseInstances[kNumScissorModes];
PrimitiveTallies fInstanceCounts[kNumScissorModes];
const SkTArray<ScissorBatch, true> fScissorBatches;
const SkISize fDrawBounds;
mutable SkTArray<GrMesh> fMeshesScratchBuffer;
mutable SkTArray<GrPipeline::DynamicState> fDynamicStatesScratchBuffer;
friend class GrCCPRCoverageOpsBuilder;
typedef GrDrawOp INHERITED;
};
#endif

View File

@ -1,645 +0,0 @@
/*
* 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 "GrCCPRCoverageOpsBuilder.h"
#include "GrBuffer.h"
#include "GrGpuCommandBuffer.h"
#include "GrOnFlushResourceProvider.h"
#include "GrOpFlushState.h"
#include "SkGeometry.h"
#include "SkMakeUnique.h"
#include "SkMathPriv.h"
#include "SkPath.h"
#include "SkPathPriv.h"
#include "SkPoint.h"
#include "SkNx.h"
#include "ccpr/GrCCPRGeometry.h"
#include "ops/GrDrawOp.h"
#include "../pathops/SkPathOpsCubic.h"
#include <numeric>
class GrCCPRCoverageOpsBuilder::CoverageOp : public GrDrawOp {
public:
using PrimitiveTallies = GrCCPRCoverageOpsBuilder::PrimitiveTallies;
DEFINE_OP_CLASS_ID
CoverageOp(const SkISize& drawBounds, sk_sp<GrBuffer> pointsBuffer,
sk_sp<GrBuffer> trianglesBuffer,
const PrimitiveTallies baseInstances[kNumScissorModes],
const PrimitiveTallies endInstances[kNumScissorModes], SkTArray<ScissorBatch>&&);
// GrDrawOp interface.
const char* name() const override { return "GrCCPRCoverageOpsBuilder::CoverageOp"; }
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override {
return RequiresDstTexture::kNo;
}
bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState*) override;
private:
void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&, const GrCCPRCoverageProcessor::Mode,
GrPrimitiveType, int vertexCount,
int PrimitiveTallies::* instanceType) const;
const SkISize fDrawBounds;
const sk_sp<GrBuffer> fPointsBuffer;
const sk_sp<GrBuffer> fTrianglesBuffer;
const PrimitiveTallies fBaseInstances[GrCCPRCoverageOpsBuilder::kNumScissorModes];
const PrimitiveTallies fInstanceCounts[GrCCPRCoverageOpsBuilder::kNumScissorModes];
const SkTArray<ScissorBatch> fScissorBatches;
mutable SkTArray<GrMesh> fMeshesScratchBuffer;
mutable SkTArray<GrPipeline::DynamicState> fDynamicStatesScratchBuffer;
typedef GrDrawOp INHERITED;
};
/**
* This is a view matrix that accumulates two bounding boxes as it maps points: device-space bounds
* and "45 degree" device-space bounds (| 1 -1 | * devCoords).
* | 1 1 |
*/
class AccumulatingViewMatrix {
public:
AccumulatingViewMatrix(const SkMatrix& m, const SkPoint& initialPoint);
SkPoint transform(const SkPoint& pt);
void getAccumulatedBounds(SkRect* devBounds, SkRect* devBounds45) const;
private:
Sk4f fX;
Sk4f fY;
Sk4f fT;
Sk4f fTopLeft;
Sk4f fBottomRight;
};
static int num_pts(uint8_t verb) {
switch (verb) {
case SkPath::kClose_Verb:
case SkPath::kDone_Verb:
default:
SK_ABORT("Path verb does not have an endpoint.");
return 0;
case SkPath::kMove_Verb:
case SkPath::kLine_Verb:
return 1;
case SkPath::kQuad_Verb:
return 2;
case SkPath::kConic_Verb:
return 2;
case SkPath::kCubic_Verb:
return 3;
}
}
static SkPoint to_skpoint(double x, double y) {
return {static_cast<SkScalar>(x), static_cast<SkScalar>(y)};
}
static SkPoint to_skpoint(const SkDPoint& dpoint) {
return to_skpoint(dpoint.fX, dpoint.fY);
}
bool GrCCPRCoverageOpsBuilder::init(GrOnFlushResourceProvider* onFlushRP,
const MaxBufferItems& maxBufferItems) {
const int maxPoints = maxBufferItems.fMaxFanPoints + maxBufferItems.fMaxControlPoints;
fPointsBuffer = onFlushRP->makeBuffer(kTexel_GrBufferType, maxPoints * 2 * sizeof(float));
if (!fPointsBuffer) {
return false;
}
const MaxPrimitives* const maxPrimitives = maxBufferItems.fMaxPrimitives;
const int maxInstances = (maxPrimitives[0].sum() + maxPrimitives[1].sum());
fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, maxInstances * 4 * sizeof(int));
if (!fInstanceBuffer) {
fPointsBuffer.reset();
return false;
}
fFanPtsIdx = 0;
fControlPtsIdx = maxBufferItems.fMaxFanPoints;
SkDEBUGCODE(fMaxFanPoints = maxBufferItems.fMaxFanPoints);
SkDEBUGCODE(fMaxControlPoints = maxBufferItems.fMaxControlPoints);
int baseInstance = 0;
for (int i = 0; i < kNumScissorModes; ++i) {
fBaseInstances[i].fTriangles = baseInstance;
baseInstance += maxPrimitives[i].fMaxTriangles;
fBaseInstances[i].fQuadratics = baseInstance;
baseInstance += maxPrimitives[i].fMaxQuadratics;
fBaseInstances[i].fSerpentines = baseInstance;
baseInstance += maxPrimitives[i].fMaxCubics;
// Loops grow backwards.
fBaseInstances[i].fLoops = baseInstance;
fInstanceIndices[i] = fBaseInstances[i];
}
fPointsData = static_cast<SkPoint*>(fPointsBuffer->map());
SkASSERT(fPointsData);
GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
GR_STATIC_ASSERT(8 == sizeof(SkPoint));
fInstanceData = static_cast<PrimitiveInstance*>(fInstanceBuffer->map());
SkASSERT(fInstanceData);
return true;
}
using MaxBufferItems = GrCCPRCoverageOpsBuilder::MaxBufferItems;
void MaxBufferItems::countPathItems(GrCCPRCoverageOpsBuilder::ScissorMode scissorMode,
const SkPath& path) {
static constexpr int kMaxQuadraticSegments = 2;
static constexpr int kMaxCubicSegments = 3;
MaxPrimitives& maxPrimitives = fMaxPrimitives[(int)scissorMode];
int currFanPts = 0;
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
switch (verb) {
case SkPath::kMove_Verb:
case SkPath::kClose_Verb:
fMaxFanPoints += currFanPts;
maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2);
currFanPts = SkPath::kMove_Verb == verb ? 1 : 0;
continue;
case SkPath::kLine_Verb:
SkASSERT(currFanPts > 0);
++currFanPts;
continue;
case SkPath::kQuad_Verb:
SkASSERT(currFanPts > 0);
currFanPts += kMaxQuadraticSegments;
fMaxControlPoints += kMaxQuadraticSegments;
maxPrimitives.fMaxQuadratics += kMaxQuadraticSegments;
continue;
case SkPath::kCubic_Verb:
GR_STATIC_ASSERT(kMaxCubicSegments >= kMaxQuadraticSegments);
SkASSERT(currFanPts > 0);
// Over-allocate for the worst case when the cubic is chopped into 3 segments.
currFanPts += kMaxCubicSegments;
// Each cubic segment has two control points.
fMaxControlPoints += kMaxCubicSegments * 2;
maxPrimitives.fMaxCubics += kMaxCubicSegments;
// The cubic may also turn out to be a quadratic. While we over-allocate by a fair
// amount, this is still a relatively small amount of space compared to the atlas.
maxPrimitives.fMaxQuadratics += kMaxQuadraticSegments;
continue;
case SkPath::kConic_Verb:
SkASSERT(currFanPts > 0);
SK_ABORT("Conics are not supported.");
default:
SK_ABORT("Unexpected path verb.");
}
}
fMaxFanPoints += currFanPts;
maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2);
++fMaxPaths;
}
void GrCCPRCoverageOpsBuilder::parsePath(ScissorMode scissorMode, const SkMatrix& viewMatrix,
const SkPath& path, SkRect* devBounds,
SkRect* devBounds45) {
// Make sure they haven't called finalize yet (or not called init).
SkASSERT(fPointsData);
SkASSERT(fInstanceData);
fCurrScissorMode = scissorMode;
fCurrPathIndices = fInstanceIndices[(int)fCurrScissorMode];
fCurrContourStartIdx = fFanPtsIdx;
const SkPoint* const pts = SkPathPriv::PointData(path);
int ptsIdx = 0;
SkASSERT(!path.isEmpty());
SkASSERT(path.countPoints() > 0);
AccumulatingViewMatrix m(viewMatrix, pts[0]);
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
switch (verb) {
case SkPath::kMove_Verb:
this->startContour(m.transform(pts[ptsIdx++]));
continue;
case SkPath::kClose_Verb:
this->closeContour();
continue;
case SkPath::kLine_Verb:
this->fanTo(m.transform(pts[ptsIdx]));
break;
case SkPath::kQuad_Verb:
SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed.
this->quadraticTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]));
break;
case SkPath::kCubic_Verb:
SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed.
this->cubicTo(m.transform(pts[ptsIdx]), m.transform(pts[ptsIdx + 1]),
m.transform(pts[ptsIdx + 2]));
break;
case SkPath::kConic_Verb:
SK_ABORT("Conics are not supported.");
default:
SK_ABORT("Unexpected path verb.");
}
ptsIdx += num_pts(verb);
}
this->closeContour();
m.getAccumulatedBounds(devBounds, devBounds45);
SkDEBUGCODE(this->validate();)
}
void GrCCPRCoverageOpsBuilder::saveParsedPath(const SkIRect& clippedDevIBounds,
int16_t atlasOffsetX, int16_t atlasOffsetY) {
const PrimitiveTallies& baseIndices = fInstanceIndices[(int)fCurrScissorMode];
const int32_t packedAtlasOffset = (atlasOffsetY << 16) | (atlasOffsetX & 0xffff);
for (int i = baseIndices.fTriangles; i < fCurrPathIndices.fTriangles; ++i) {
fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset;
}
for (int i = baseIndices.fQuadratics; i < fCurrPathIndices.fQuadratics; ++i) {
fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset;
}
for (int i = baseIndices.fSerpentines; i < fCurrPathIndices.fSerpentines; ++i) {
fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset;
}
for (int i = baseIndices.fLoops - 1; i >= fCurrPathIndices.fLoops; --i) {
fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset;
}
if (ScissorMode::kScissored == fCurrScissorMode) {
fScissorBatches.push_back() = {
fCurrPathIndices - fInstanceIndices[(int)fCurrScissorMode],
clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY)
};
}
fInstanceIndices[(int)fCurrScissorMode] = fCurrPathIndices;
}
void GrCCPRCoverageOpsBuilder::startContour(const SkPoint& anchorPoint) {
this->closeContour();
fPointsData[fFanPtsIdx++] = fCurrAnchorPoint = fCurrFanPoint = anchorPoint;
SkASSERT(fCurrContourStartIdx == fFanPtsIdx - 1);
}
void GrCCPRCoverageOpsBuilder::fanTo(const SkPoint& pt) {
SkASSERT(fCurrContourStartIdx < fFanPtsIdx);
if (pt == fCurrAnchorPoint) {
this->startContour(pt);
return;
}
fPointsData[fFanPtsIdx++] = fCurrFanPoint = pt;
}
void GrCCPRCoverageOpsBuilder::quadraticTo(SkPoint controlPt, SkPoint endPt) {
SkASSERT(fCurrPathIndices.fQuadratics+2 <= fBaseInstances[(int)fCurrScissorMode].fSerpentines);
SkPoint chopped[5];
if (GrCCPRChopMonotonicQuadratics(fCurrFanPoint, controlPt, endPt, chopped)) {
this->fanTo(chopped[2]);
fPointsData[fControlPtsIdx++] = chopped[1];
fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = {
fControlPtsIdx - 1,
fFanPtsIdx - 2
};
controlPt = chopped[3];
SkASSERT(endPt == chopped[4]);
}
this->fanTo(endPt);
fPointsData[fControlPtsIdx++] = controlPt;
fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = {
fControlPtsIdx - 1,
fFanPtsIdx - 2
};
}
void GrCCPRCoverageOpsBuilder::cubicTo(SkPoint controlPt1, SkPoint controlPt2, SkPoint endPt) {
SkPoint P[4] = {fCurrFanPoint, controlPt1, controlPt2, endPt};
double t[2], s[2];
SkCubicType type = SkClassifyCubic(P, t, s);
if (SkCubicType::kLineOrPoint == type) {
this->fanTo(P[3]);
return;
}
if (SkCubicType::kQuadratic == type) {
SkScalar x1 = P[1].y() - P[0].y(), y1 = P[0].x() - P[1].x(),
k1 = x1 * P[0].x() + y1 * P[0].y();
SkScalar x2 = P[2].y() - P[3].y(), y2 = P[3].x() - P[2].x(),
k2 = x2 * P[3].x() + y2 * P[3].y();
SkScalar rdet = 1 / (x1*y2 - y1*x2);
this->quadraticTo({(y2*k1 - y1*k2) * rdet, (x1*k2 - x2*k1) * rdet}, P[3]);
return;
}
SkDCubic C;
C.set(P);
for (int x = 0; x <= 1; ++x) {
if (t[x] * s[x] <= 0) { // This is equivalent to tx/sx <= 0.
// This technically also gets taken if tx/sx = infinity, but the code still does
// the right thing in that edge case.
continue; // Don't increment x0.
}
if (fabs(t[x]) >= fabs(s[x])) { // tx/sx >= 1.
break;
}
const double chopT = double(t[x]) / double(s[x]);
SkASSERT(chopT >= 0 && chopT <= 1);
if (chopT <= 0 || chopT >= 1) { // floating-point error.
continue;
}
SkDCubicPair chopped = C.chopAt(chopT);
// Ensure the double points are identical if this is a loop (more workarounds for FP error).
if (SkCubicType::kLoop == type && 0 == t[0]) {
chopped.pts[3] = chopped.pts[0];
}
// (This might put ts0/ts1 out of order, but it doesn't matter anymore at this point.)
this->emitCubicSegment(type, chopped.first());
t[x] = 0;
s[x] = 1;
const double r = s[1 - x] * chopT;
t[1 - x] -= r;
s[1 - x] -= r;
C = chopped.second();
}
this->emitCubicSegment(type, C);
}
void GrCCPRCoverageOpsBuilder::emitCubicSegment(SkCubicType type, const SkDCubic& C) {
SkASSERT(fCurrPathIndices.fSerpentines < fCurrPathIndices.fLoops);
fPointsData[fControlPtsIdx++] = to_skpoint(C[1]);
fPointsData[fControlPtsIdx++] = to_skpoint(C[2]);
this->fanTo(to_skpoint(C[3]));
// Serpentines grow up from the front, and loops grow down from the back.
fInstanceData[SkCubicType::kLoop != type ?
fCurrPathIndices.fSerpentines++ : --fCurrPathIndices.fLoops].fCubicData = {
fControlPtsIdx - 2,
fFanPtsIdx - 2
};
}
void GrCCPRCoverageOpsBuilder::closeContour() {
int fanSize = fFanPtsIdx - fCurrContourStartIdx;
if (fanSize >= 3) {
// Technically this can grow to fanSize + log3(fanSize), but we approximate with log2.
SkAutoSTMalloc<300, int32_t> indices(fanSize + SkNextLog2(fanSize));
std::iota(indices.get(), indices.get() + fanSize, fCurrContourStartIdx);
this->emitHierarchicalFan(indices, fanSize);
}
// Reset the current contour.
fCurrContourStartIdx = fFanPtsIdx;
}
void GrCCPRCoverageOpsBuilder::emitHierarchicalFan(int32_t indices[], int count) {
if (count < 3) {
return;
}
const int32_t oneThirdPt = count / 3;
const int32_t twoThirdsPt = (2 * count) / 3;
SkASSERT(fCurrPathIndices.fTriangles < fBaseInstances[(int)fCurrScissorMode].fQuadratics);
fInstanceData[fCurrPathIndices.fTriangles++].fTriangleData = {
indices[0],
indices[oneThirdPt],
indices[twoThirdsPt]
};
this->emitHierarchicalFan(indices, oneThirdPt + 1);
this->emitHierarchicalFan(&indices[oneThirdPt], twoThirdsPt - oneThirdPt + 1);
int32_t oldIndex = indices[count];
indices[count] = indices[0];
this->emitHierarchicalFan(&indices[twoThirdsPt], count - twoThirdsPt + 1);
indices[count] = oldIndex;
}
std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::createIntermediateOp(SkISize drawBounds) {
auto op = skstd::make_unique<CoverageOp>(drawBounds, fPointsBuffer, fInstanceBuffer,
fBaseInstances, fInstanceIndices,
std::move(fScissorBatches));
SkASSERT(fScissorBatches.empty());
fBaseInstances[0] = fInstanceIndices[0];
fBaseInstances[1] = fInstanceIndices[1];
return std::move(op);
}
std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::finalize(SkISize drawBounds) {
fPointsBuffer->unmap();
SkDEBUGCODE(fPointsData = nullptr);
fInstanceBuffer->unmap();
SkDEBUGCODE(fInstanceData = nullptr);
return skstd::make_unique<CoverageOp>(drawBounds, std::move(fPointsBuffer),
std::move(fInstanceBuffer), fBaseInstances,
fInstanceIndices, std::move(fScissorBatches));
}
#ifdef SK_DEBUG
void GrCCPRCoverageOpsBuilder::validate() {
SkASSERT(fFanPtsIdx <= fMaxFanPoints);
SkASSERT(fControlPtsIdx <= fMaxFanPoints + fMaxControlPoints);
for (int i = 0; i < kNumScissorModes; ++i) {
SkASSERT(fInstanceIndices[i].fTriangles <= fBaseInstances[i].fQuadratics);
SkASSERT(fInstanceIndices[i].fQuadratics <= fBaseInstances[i].fSerpentines);
SkASSERT(fInstanceIndices[i].fSerpentines <= fInstanceIndices[i].fLoops);
}
}
#endif
using CoverageOp = GrCCPRCoverageOpsBuilder::CoverageOp;
GrCCPRCoverageOpsBuilder::CoverageOp::CoverageOp(const SkISize& drawBounds,
sk_sp<GrBuffer> pointsBuffer,
sk_sp<GrBuffer> trianglesBuffer,
const PrimitiveTallies baseInstances[kNumScissorModes],
const PrimitiveTallies endInstances[kNumScissorModes],
SkTArray<ScissorBatch>&& scissorBatches)
: INHERITED(ClassID())
, fDrawBounds(drawBounds)
, fPointsBuffer(std::move(pointsBuffer))
, fTrianglesBuffer(std::move(trianglesBuffer))
, fBaseInstances{baseInstances[0], baseInstances[1]}
, fInstanceCounts{endInstances[0] - baseInstances[0], endInstances[1] - baseInstances[1]}
, fScissorBatches(std::move(scissorBatches)) {
SkASSERT(fPointsBuffer);
SkASSERT(fTrianglesBuffer);
this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
}
void CoverageOp::onExecute(GrOpFlushState* flushState) {
using Mode = GrCCPRCoverageProcessor::Mode;
SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fProxy));
GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled,
SkBlendMode::kPlus);
fMeshesScratchBuffer.reserve(1 + fScissorBatches.count());
fDynamicStatesScratchBuffer.reserve(1 + fScissorBatches.count());
// Triangles.
auto constexpr kTrianglesGrPrimitiveType = GrCCPRCoverageProcessor::kTrianglesGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kCombinedTriangleHullsAndEdges,
kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleCorners,
kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
// Quadratics.
auto constexpr kQuadraticsGrPrimitiveType = GrCCPRCoverageProcessor::kQuadraticsGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticHulls,
kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticCorners,
kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
// Cubics.
auto constexpr kCubicsGrPrimitiveType = GrCCPRCoverageProcessor::kCubicsGrPrimitiveType;
this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineInsets,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopInsets,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineBorders,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopBorders,
kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
}
void CoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
GrCCPRCoverageProcessor::Mode mode, GrPrimitiveType primType,
int vertexCount, int PrimitiveTallies::* instanceType) const {
SkASSERT(pipeline.getScissorState().enabled());
fMeshesScratchBuffer.reset();
fDynamicStatesScratchBuffer.reset();
if (const int instanceCount = fInstanceCounts[(int)ScissorMode::kNonScissored].*instanceType) {
const int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType;
// Loops grow backwards, which is indicated by a negative instance count.
GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType);
mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount),
baseInstance + SkTMin(instanceCount, 0), vertexCount);
fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, fDrawBounds.width(),
fDrawBounds.height());
}
if (fInstanceCounts[(int)ScissorMode::kScissored].*instanceType) {
int baseInstance = fBaseInstances[(int)ScissorMode::kScissored].*instanceType;
for (const ScissorBatch& batch : fScissorBatches) {
SkASSERT(this->bounds().contains(batch.fScissor));
const int instanceCount = batch.fInstanceCounts.*instanceType;
if (!instanceCount) {
continue;
}
// Loops grow backwards, which is indicated by a negative instance count.
GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType);
mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount),
baseInstance + SkTMin(instanceCount,0), vertexCount);
fDynamicStatesScratchBuffer.push_back().fScissorRect = batch.fScissor;
baseInstance += instanceCount;
}
}
SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
if (!fMeshesScratchBuffer.empty()) {
GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get());
SkASSERT(flushState->rtCommandBuffer());
flushState->rtCommandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(),
fDynamicStatesScratchBuffer.begin(),
fMeshesScratchBuffer.count(), this->bounds());
}
}
using PrimitiveTallies = CoverageOp::PrimitiveTallies;
inline PrimitiveTallies PrimitiveTallies::operator+(const PrimitiveTallies& b) const {
return {fTriangles + b.fTriangles,
fQuadratics + b.fQuadratics,
fSerpentines + b.fSerpentines,
fLoops + b.fLoops};
}
inline PrimitiveTallies PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
return {fTriangles - b.fTriangles,
fQuadratics - b.fQuadratics,
fSerpentines - b.fSerpentines,
fLoops - b.fLoops};
}
inline int PrimitiveTallies::sum() const {
return fTriangles + fQuadratics + fSerpentines + fLoops;
}
inline AccumulatingViewMatrix::AccumulatingViewMatrix(const SkMatrix& m,
const SkPoint& initialPoint) {
// m45 transforms into 45 degree space in order to find the octagon's diagonals. We could
// use SK_ScalarRoot2Over2 if we wanted an orthonormal transform, but this is irrelevant as
// long as the shader uses the correct inverse when coming back to device space.
SkMatrix m45;
m45.setSinCos(1, 1);
m45.preConcat(m);
fX = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
fY = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
fT = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());
Sk4f transformed = SkNx_fma(fY, Sk4f(initialPoint.y()), fT);
transformed = SkNx_fma(fX, Sk4f(initialPoint.x()), transformed);
fTopLeft = fBottomRight = transformed;
}
inline SkPoint AccumulatingViewMatrix::transform(const SkPoint& pt) {
Sk4f transformed = SkNx_fma(fY, Sk4f(pt.y()), fT);
transformed = SkNx_fma(fX, Sk4f(pt.x()), transformed);
fTopLeft = Sk4f::Min(fTopLeft, transformed);
fBottomRight = Sk4f::Max(fBottomRight, transformed);
// TODO: vst1_lane_f32? (Sk4f::storeLane?)
float data[4];
transformed.store(data);
return SkPoint::Make(data[0], data[1]);
}
inline void AccumulatingViewMatrix::getAccumulatedBounds(SkRect* devBounds,
SkRect* devBounds45) const {
float topLeft[4], bottomRight[4];
fTopLeft.store(topLeft);
fBottomRight.store(bottomRight);
devBounds->setLTRB(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]);
devBounds45->setLTRB(topLeft[2], topLeft[3], bottomRight[2], bottomRight[3]);
}

View File

@ -1,166 +0,0 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrCCPRCoverageOpsBuilder_DEFINED
#define GrCCPRCoverageOpsBuilder_DEFINED
#include "GrBuffer.h"
#include "SkRefCnt.h"
#include "SkRect.h"
#include "ccpr/GrCCPRCoverageProcessor.h"
class GrCCPRCoverageOp;
class GrDrawOp;
class GrOnFlushResourceProvider;
class GrResourceProvider;
class SkMatrix;
class SkPath;
struct SkDCubic;
enum class SkCubicType;
/**
* This class produces GrDrawOps that render coverage count masks and atlases. A path is added to
* the current op in two steps:
*
* 1) parsePath(ScissorMode, viewMatrix, path, &devBounds, &devBounds45);
*
* <client decides where to put the mask within an atlas, if wanted>
*
* 2) saveParsedPath(offsetX, offsetY, clipBounds);
*
* The client can then produce a GrDrawOp for all currently saved paths by calling either
* createIntermediateOp() or finalize().
*/
class GrCCPRCoverageOpsBuilder {
public:
// Indicates whether a path should enforce a scissor clip when rendering its mask. (Specified
// as an int because these values get used directly as indices into arrays.)
enum class ScissorMode : int {
kNonScissored = 0,
kScissored = 1
};
static constexpr int kNumScissorModes = 2;
struct MaxPrimitives {
int fMaxTriangles = 0;
int fMaxQuadratics = 0;
int fMaxCubics = 0;
void operator+=(const MaxPrimitives&);
int sum() const;
};
struct MaxBufferItems {
int fMaxFanPoints = 0;
int fMaxControlPoints = 0;
MaxPrimitives fMaxPrimitives[kNumScissorModes];
int fMaxPaths = 0;
void operator+=(const MaxBufferItems&);
void countPathItems(ScissorMode, const SkPath&);
};
GrCCPRCoverageOpsBuilder() : fScissorBatches(512) {
SkDEBUGCODE(fPointsData = nullptr;)
SkDEBUGCODE(fInstanceData = nullptr;)
}
bool init(GrOnFlushResourceProvider*, const MaxBufferItems&);
// Parses an SkPath into a temporary staging area. The path will not yet be included in the next
// Op until there is a matching call to saveParsedPath.
//
// Returns two tight bounding boxes: device space and "45 degree" (| 1 -1 | * devCoords) space.
// | 1 1 |
void parsePath(ScissorMode, const SkMatrix&, const SkPath&, SkRect* devBounds,
SkRect* devBounds45);
// Commits the currently-parsed path from the staging area to the GPU buffers and next Op.
// Accepts an optional post-device-space translate for placement in an atlas.
void saveParsedPath(const SkIRect& clippedDevIBounds,
int16_t atlasOffsetX, int16_t atlasOffsetY);
// Flushes all currently-saved paths to a GrDrawOp and leaves the GPU buffers open to accept
// new paths (e.g. for when an atlas runs out of space).
// NOTE: if there is a parsed path in the staging area, it will not be included. But the client
// may still call saveParsedPath to include it in a future Op.
std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT createIntermediateOp(SkISize drawBounds);
// Flushes the remaining saved paths to a final GrDrawOp and closes off the GPU buffers. This
// must be called before attempting to draw any Ops produced by this class.
std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT finalize(SkISize drawBounds);
class CoverageOp;
private:
using PrimitiveInstance = GrCCPRCoverageProcessor::PrimitiveInstance;
struct PrimitiveTallies {
int fTriangles;
int fQuadratics;
int fSerpentines;
int fLoops;
PrimitiveTallies operator+(const PrimitiveTallies&) const;
PrimitiveTallies operator-(const PrimitiveTallies&) const;
int sum() const;
};
struct ScissorBatch {
PrimitiveTallies fInstanceCounts;
SkIRect fScissor;
};
void startContour(const SkPoint& anchorPoint);
void fanTo(const SkPoint& pt);
void quadraticTo(SkPoint controlPt, SkPoint endPt);
void cubicTo(SkPoint controlPt1, SkPoint controlPt2, SkPoint endPt);
void emitCubicSegment(SkCubicType, const SkDCubic&);
void closeContour();
void emitHierarchicalFan(int32_t indices[], int count);
SkDEBUGCODE(void validate();)
ScissorMode fCurrScissorMode;
PrimitiveTallies fCurrPathIndices;
int32_t fCurrContourStartIdx;
SkPoint fCurrAnchorPoint;
SkPoint fCurrFanPoint;
sk_sp<GrBuffer> fPointsBuffer;
SkPoint* fPointsData;
int32_t fFanPtsIdx;
int32_t fControlPtsIdx;
SkDEBUGCODE(int fMaxFanPoints;)
SkDEBUGCODE(int fMaxControlPoints;)
sk_sp<GrBuffer> fInstanceBuffer;
PrimitiveInstance* fInstanceData;
PrimitiveTallies fBaseInstances[kNumScissorModes];
PrimitiveTallies fInstanceIndices[kNumScissorModes];
SkTArray<ScissorBatch> fScissorBatches;
};
inline void GrCCPRCoverageOpsBuilder::MaxBufferItems::operator+=(const MaxBufferItems& b) {
fMaxFanPoints += b.fMaxFanPoints;
fMaxControlPoints += b.fMaxControlPoints;
fMaxPrimitives[0] += b.fMaxPrimitives[0];
fMaxPrimitives[1] += b.fMaxPrimitives[1];
fMaxPaths += b.fMaxPaths;
}
inline void GrCCPRCoverageOpsBuilder::MaxPrimitives::operator+=(const MaxPrimitives& b) {
fMaxTriangles += b.fMaxTriangles;
fMaxQuadratics += b.fMaxQuadratics;
fMaxCubics += b.fMaxCubics;
}
inline int GrCCPRCoverageOpsBuilder::MaxPrimitives::sum() const {
return fMaxTriangles + fMaxQuadratics + fMaxCubics;
}
#endif

View File

@ -45,7 +45,7 @@ const char* GrCCPRCoverageProcessor::GetProcessorName(Mode mode) {
GrCCPRCoverageProcessor::GrCCPRCoverageProcessor(Mode mode, GrBuffer* pointsBuffer)
: fMode(mode)
, fInstanceAttrib(this->addInstanceAttrib("instance", kVec4i_GrVertexAttribType,
, fInstanceAttrib(this->addInstanceAttrib("instance", InstanceArrayFormat(mode),
kHigh_GrSLPrecision)) {
fPointsBufferAccess.reset(kRG_float_GrPixelConfig, pointsBuffer, kVertex_GrShaderFlag);
this->addBufferAccess(&fPointsBufferAccess);
@ -121,7 +121,7 @@ void PrimitiveProcessor::emitVertexShader(const GrCCPRCoverageProcessor& proc,
GrGLSLVertexBuilder* v,
const TexelBufferHandle& pointsBuffer,
const char* rtAdjust, GrGPArgs* gpArgs) const {
v->codeAppendf("int packedoffset = %s.w;", proc.instanceAttrib());
v->codeAppendf("int packedoffset = %s[%i];", proc.instanceAttrib(), proc.atlasOffsetIdx());
v->codeAppend ("highp float2 atlasoffset = float2((packedoffset<<16) >> 16, "
"packedoffset >> 16);");

View File

@ -25,8 +25,8 @@ class GrGLSLFragmentBuilder;
* be used to draw the path (see GrCCPRPathProcessor).
*
* Caller provides the primitives' (x,y) points in an fp32x2 (RG) texel buffer, and an instance
* buffer with a single int32x4 attrib for each primitive (defined below). There are no vertex
* attribs.
* buffer with a single int32x4 attrib (for triangles) or int32x2 (for curves) defined below. There
* are no vertex attribs.
*
* Draw calls are instanced, with one vertex per bezier point (3 for triangles). They use the
* corresponding GrPrimitiveType as defined below.
@ -40,31 +40,21 @@ public:
static constexpr GrPrimitiveType kQuadraticsGrPrimitiveType = GrPrimitiveType::kTriangles;
static constexpr GrPrimitiveType kCubicsGrPrimitiveType = GrPrimitiveType::kLinesAdjacency;
struct PrimitiveInstance {
union {
struct {
int32_t fPt0Idx;
int32_t fPt1Idx;
int32_t fPt2Idx;
} fTriangleData;
struct {
int32_t fControlPtIdx;
int32_t fEndPtsIdx; // The endpoints (P0 and P2) are adjacent in the texel buffer.
} fQuadraticData;
struct {
int32_t fControlPtsKLMRootsIdx; // The control points (P1 and P2) are adjacent in
// the texel buffer, followed immediately by the
// homogenous KLM roots ({tl,sl}, {tm,sm}).
int32_t fEndPtsIdx; // The endpoints (P0 and P3) are adjacent in the texel buffer.
} fCubicData;
};
struct TriangleInstance {
int32_t fPt0Idx;
int32_t fPt1Idx;
int32_t fPt2Idx;
int32_t fPackedAtlasOffset; // (offsetY << 16) | (offsetX & 0xffff)
};
GR_STATIC_ASSERT(4 * 4 == sizeof(PrimitiveInstance));
GR_STATIC_ASSERT(4 * 4 == sizeof(TriangleInstance));
struct CurveInstance {
int32_t fPtsIdx;
int32_t fPackedAtlasOffset; // (offsetY << 16) | (offsetX & 0xffff)
};
GR_STATIC_ASSERT(2 * 4 == sizeof(CurveInstance));
enum class Mode {
// Triangles.
@ -83,11 +73,17 @@ public:
kLoopInsets,
kLoopBorders
};
static constexpr GrVertexAttribType InstanceArrayFormat(Mode mode) {
return mode < Mode::kQuadraticHulls ? kVec4i_GrVertexAttribType : kVec2i_GrVertexAttribType;
}
static const char* GetProcessorName(Mode);
GrCCPRCoverageProcessor(Mode, GrBuffer* pointsBuffer);
const char* instanceAttrib() const { return fInstanceAttrib.fName; }
int atlasOffsetIdx() const {
return kVec4i_GrVertexAttribType == InstanceArrayFormat(fMode) ? 3 : 1;
}
const char* name() const override { return GetProcessorName(fMode); }
SkString dumpInfo() const override {
return SkStringPrintf("%s\n%s", this->name(), this->INHERITED::dumpInfo().c_str());

View File

@ -24,7 +24,7 @@ void GrCCPRCubicProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& pro
#endif
// Fetch all 4 cubic bezier points.
v->codeAppendf("int4 indices = int4(%s.y, %s.x, %s.x + 1, %s.y + 1);",
v->codeAppendf("int4 indices = int4(%s.x, %s.x + 1, %s.x + 2, %s.x + 3);",
proc.instanceAttrib(), proc.instanceAttrib(), proc.instanceAttrib(),
proc.instanceAttrib());
v->codeAppend ("highp float4x2 bezierpts = float4x2(");

View File

@ -21,7 +21,7 @@ class GrGLSLGeometryBuilder;
*
* The caller is expected to chop cubics at the KLM roots (a.k.a. inflection points and loop
* intersection points, resulting in necessarily convex segments) before feeding them into this
* processor.
* processor. (Use GrCCPRGeometry.)
*
* The curves are rendered in two passes:
*

View File

@ -8,8 +8,9 @@
#include "GrCCPRGeometry.h"
#include "GrTypes.h"
#include "SkGeometry.h"
#include "SkPoint.h"
#include "SkNx.h"
#include "../pathops/SkPathOpsCubic.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
@ -19,6 +20,33 @@ GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint));
GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX));
void GrCCPRGeometry::beginPath() {
SkASSERT(!fBuildingContour);
fVerbs.push_back(Verb::kBeginPath);
}
void GrCCPRGeometry::beginContour(const SkPoint& devPt) {
SkASSERT(!fBuildingContour);
fCurrFanPoint = fCurrAnchorPoint = devPt;
// Store the current verb count in the fTriangles field for now. When we close the contour we
// will use this value to calculate the actual number of triangles in its fan.
fCurrContourTallies = {fVerbs.count(), 0, 0, 0};
fPoints.push_back(devPt);
fVerbs.push_back(Verb::kBeginContour);
SkDEBUGCODE(fBuildingContour = true;)
}
void GrCCPRGeometry::lineTo(const SkPoint& devPt) {
SkASSERT(fBuildingContour);
fCurrFanPoint = devPt;
fPoints.push_back(devPt);
fVerbs.push_back(Verb::kLineTo);
}
static inline Sk2f normalize(const Sk2f& n) {
Sk2f nn = n*n;
return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt();
@ -47,17 +75,20 @@ static inline Sk2f lerp(const Sk2f& a, const Sk2f& b, const Sk2f& t) {
return SkNx_fma(t, b - a, a);
}
bool GrCCPRChopMonotonicQuadratics(const SkPoint& startPt, const SkPoint& controlPt,
const SkPoint& endPt, SkPoint dst[5]) {
Sk2f p0 = Sk2f::Load(&startPt);
Sk2f p1 = Sk2f::Load(&controlPt);
Sk2f p2 = Sk2f::Load(&endPt);
void GrCCPRGeometry::quadraticTo(const SkPoint& devP0, const SkPoint& devP1) {
SkASSERT(fBuildingContour);
Sk2f p0 = Sk2f::Load(&fCurrFanPoint);
Sk2f p1 = Sk2f::Load(&devP0);
Sk2f p2 = Sk2f::Load(&devP1);
fCurrFanPoint = devP1;
Sk2f tan0 = p1 - p0;
Sk2f tan1 = p2 - p1;
// This should almost always be this case for well-behaved curves in the real world.
if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
return false;
this->appendMonotonicQuadratic(p1, p2);
return;
}
// Chop the curve into two segments with equal curvature. To do this we find the T value whose
@ -84,11 +115,111 @@ bool GrCCPRChopMonotonicQuadratics(const SkPoint& startPt, const SkPoint& contro
Sk2f p12 = SkNx_fma(t, tan1, p1);
Sk2f p012 = lerp(p01, p12, t);
p0.store(&dst[0]);
p01.store(&dst[1]);
p012.store(&dst[2]);
p12.store(&dst[3]);
p2.store(&dst[4]);
return true;
this->appendMonotonicQuadratic(p01, p012);
this->appendMonotonicQuadratic(p12, p2);
}
inline void GrCCPRGeometry::appendMonotonicQuadratic(const Sk2f& p1, const Sk2f& p2) {
p1.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
fVerbs.push_back(Verb::kMonotonicQuadraticTo);
++fCurrContourTallies.fQuadratics;
}
void GrCCPRGeometry::cubicTo(const SkPoint& devP1, const SkPoint& devP2, const SkPoint& devP3) {
SkASSERT(fBuildingContour);
SkPoint P[4] = {fCurrFanPoint, devP1, devP2, devP3};
double t[2], s[2];
SkCubicType type = SkClassifyCubic(P, t, s);
if (SkCubicType::kLineOrPoint == type) {
this->lineTo(P[3]);
return;
}
if (SkCubicType::kQuadratic == type) {
SkPoint quadP1 = (devP1 + devP2) * .75f - (fCurrFanPoint + devP3) * .25f;
this->quadraticTo(quadP1, devP3);
return;
}
fCurrFanPoint = devP3;
SkDCubic C;
C.set(P);
for (int x = 0; x <= 1; ++x) {
if (t[x] * s[x] <= 0) { // This is equivalent to tx/sx <= 0.
// This technically also gets taken if tx/sx = infinity, but the code still does
// the right thing in that edge case.
continue; // Don't increment x0.
}
if (fabs(t[x]) >= fabs(s[x])) { // tx/sx >= 1.
break;
}
const double chopT = double(t[x]) / double(s[x]);
SkASSERT(chopT >= 0 && chopT <= 1);
if (chopT <= 0 || chopT >= 1) { // floating-point error.
continue;
}
SkDCubicPair chopped = C.chopAt(chopT);
// Ensure the double points are identical if this is a loop (more workarounds for FP error).
if (SkCubicType::kLoop == type && 0 == t[0]) {
chopped.pts[3] = chopped.pts[0];
}
// (This might put ts0/ts1 out of order, but it doesn't matter anymore at this point.)
this->appendConvexCubic(type, chopped.first());
t[x] = 0;
s[x] = 1;
const double r = s[1 - x] * chopT;
t[1 - x] -= r;
s[1 - x] -= r;
C = chopped.second();
}
this->appendConvexCubic(type, C);
}
static SkPoint to_skpoint(const SkDPoint& dpoint) {
return {static_cast<SkScalar>(dpoint.fX), static_cast<SkScalar>(dpoint.fY)};
}
inline void GrCCPRGeometry::appendConvexCubic(SkCubicType type, const SkDCubic& C) {
fPoints.push_back(to_skpoint(C[1]));
fPoints.push_back(to_skpoint(C[2]));
fPoints.push_back(to_skpoint(C[3]));
if (SkCubicType::kLoop != type) {
fVerbs.push_back(Verb::kConvexSerpentineTo);
++fCurrContourTallies.fSerpentines;
} else {
fVerbs.push_back(Verb::kConvexLoopTo);
++fCurrContourTallies.fLoops;
}
}
GrCCPRGeometry::PrimitiveTallies GrCCPRGeometry::endContour() {
SkASSERT(fBuildingContour);
SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles);
// The fTriangles field currently contains this contour's starting verb index. We can now
// use it to calculate the size of the contour's fan.
int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles;
if (fCurrFanPoint == fCurrAnchorPoint) {
--fanSize;
fVerbs.push_back(Verb::kEndClosedContour);
} else {
fVerbs.push_back(Verb::kEndOpenContour);
}
fCurrContourTallies.fTriangles = SkTMax(fanSize - 2, 0);
SkDEBUGCODE(fBuildingContour = false;)
return fCurrContourTallies;
}

View File

@ -8,22 +8,106 @@
#ifndef GrGrCCPRGeometry_DEFINED
#define GrGrCCPRGeometry_DEFINED
#include "SkTypes.h"
#include "SkNx.h"
#include "SkPoint.h"
#include "SkTArray.h"
struct SkPoint;
struct SkDCubic;
enum class SkCubicType;
/*
* Ensures that a quadratic bezier is monotonic with respect to the vector between its endpoints
* [P2 - P0]. In the event that the curve is not monotonic, it is chopped into two segments that
* are. This should be rare for well-behaved curves in the real world.
/**
* This class chops device-space contours up into a series of segments that CCPR knows how to
* render. (See GrCCPRGeometry::Verb.)
*
* NOTE: This must be done in device space, since an affine transformation can change whether a
* curve is monotonic.
*
* Returns false if the curve was already monotonic.
* true if it was chopped into two monotonic segments, now contained in dst.
*/
bool GrCCPRChopMonotonicQuadratics(const SkPoint& startPt, const SkPoint& controlPt,
const SkPoint& endPt, SkPoint dst[5]);
class GrCCPRGeometry {
public:
// These are the verbs that CCPR knows how to draw. If a path has any segments that don't map to
// this list, then they are chopped into smaller ones that do. A list of these comprise a
// compact representation of what can later be expanded into GPU instance data.
enum class Verb : uint8_t {
kBeginPath, // Included only for caller convenience.
kBeginContour,
kLineTo,
kMonotonicQuadraticTo, // Monotonic relative to the vector between its endpoints [P2 - P0].
kConvexSerpentineTo,
kConvexLoopTo,
kEndClosedContour, // endPt == startPt.
kEndOpenContour // endPt != startPt.
};
// These tallies track numbers of CCPR primitives are required to draw a contour.
struct PrimitiveTallies {
int fTriangles; // Number of triangles in the contour's fan.
int fQuadratics;
int fSerpentines;
int fLoops;
void operator+=(const PrimitiveTallies&);
PrimitiveTallies operator-(const PrimitiveTallies&) const;
};
GrCCPRGeometry(int numSkPoints = 0, int numSkVerbs = 0)
: fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs.
, fVerbs(numSkVerbs * 3) {}
const SkTArray<SkPoint, true>& points() const { SkASSERT(!fBuildingContour); return fPoints; }
const SkTArray<Verb, true>& verbs() const { SkASSERT(!fBuildingContour); return fVerbs; }
void reset() {
SkASSERT(!fBuildingContour);
fPoints.reset();
fVerbs.reset();
}
// This is included in case the caller needs to discard previously added contours. It is up to
// the caller to track counts and ensure we don't pop back into the middle of a different
// contour.
void resize_back(int numPoints, int numVerbs) {
SkASSERT(!fBuildingContour);
fPoints.resize_back(numPoints);
fVerbs.resize_back(numVerbs);
SkASSERT(fVerbs.empty() || fVerbs.back() == Verb::kEndOpenContour ||
fVerbs.back() == Verb::kEndClosedContour);
}
void beginPath();
void beginContour(const SkPoint& devPt);
void lineTo(const SkPoint& devPt);
void quadraticTo(const SkPoint& devP1, const SkPoint& devP2);
void cubicTo(const SkPoint& devP1, const SkPoint& devP2, const SkPoint& devP3);
PrimitiveTallies endContour(); // Returns the numbers of primitives needed to draw the contour.
private:
inline void appendMonotonicQuadratic(const Sk2f& p1, const Sk2f& p2);
inline void appendConvexCubic(SkCubicType, const SkDCubic&);
// Transient state used while building a contour.
SkPoint fCurrAnchorPoint;
SkPoint fCurrFanPoint;
PrimitiveTallies fCurrContourTallies;
SkDEBUGCODE(bool fBuildingContour = false);
// TODO: These points could eventually be written directly to block-allocated GPU buffers.
SkSTArray<128, SkPoint, true> fPoints;
SkSTArray<128, Verb, true> fVerbs;
};
inline void GrCCPRGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) {
fTriangles += b.fTriangles;
fQuadratics += b.fQuadratics;
fSerpentines += b.fSerpentines;
fLoops += b.fLoops;
}
GrCCPRGeometry::PrimitiveTallies
inline GrCCPRGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
return {fTriangles - b.fTriangles,
fQuadratics - b.fQuadratics,
fSerpentines - b.fSerpentines,
fLoops - b.fLoops};
}
#endif

View File

@ -16,10 +16,9 @@ void GrCCPRQuadraticProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor&
const TexelBufferHandle& pointsBuffer,
const char* atlasOffset, const char* rtAdjust,
GrGPArgs* gpArgs) const {
v->codeAppendf("int3 indices = int3(%s.y, %s.x, %s.y + 1);",
proc.instanceAttrib(), proc.instanceAttrib(), proc.instanceAttrib());
v->codeAppend ("highp float2 self = ");
v->appendTexelFetch(pointsBuffer, "indices[sk_VertexID]");
v->appendTexelFetch(pointsBuffer,
SkStringPrintf("%s.x + sk_VertexID", proc.instanceAttrib()).c_str());
v->codeAppendf(".xy + %s;", atlasOffset);
gpArgs->fPositionVar.set(kVec2f_GrSLType, "self");
}

View File

@ -18,7 +18,7 @@
* https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
*
* The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
* Use GrPathUtils::chopMonotonicQuads.
* (Use GrCCPRGeometry.)
*/
class GrCCPRQuadraticProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor {
public:

View File

@ -72,6 +72,7 @@ GrCoverageCountingPathRenderer::DrawPathsOp::DrawPathsOp(GrCoverageCountingPathR
, fOwningRTPendingOps(nullptr) {
SkDEBUGCODE(fBaseInstance = -1);
SkDEBUGCODE(fDebugInstanceCount = 1;)
SkDEBUGCODE(fDebugSkippedInstances = 0;)
GrRenderTargetContext* const rtc = args.fRenderTargetContext;
@ -115,9 +116,12 @@ bool DrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
SkASSERT(owningRTPendingOps == fOwningRTPendingOps);
owningRTPendingOps->fOpList.remove(that);
} else {
// wasRecorded is not called when the op gets combined first. Count path items here instead.
SingleDraw& onlyDraw = that->getOnlyPathDraw();
fOwningRTPendingOps->fMaxBufferItems.countPathItems(onlyDraw.fScissorMode, onlyDraw.fPath);
// The Op is being combined immediately after creation, before a call to wasRecorded. In
// this case wasRecorded will not be called. So we count its path here instead.
const SingleDraw& onlyDraw = that->getOnlyPathDraw();
++fOwningRTPendingOps->fNumTotalPaths;
fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
}
fTailDraw->fNext = &fOwningRTPendingOps->fDrawsAllocator.push_back(that->fHeadDraw);
@ -132,21 +136,17 @@ bool DrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
void DrawPathsOp::wasRecorded(GrRenderTargetOpList* opList) {
SkASSERT(!fOwningRTPendingOps);
SingleDraw& onlyDraw = this->getOnlyPathDraw();
const SingleDraw& onlyDraw = this->getOnlyPathDraw();
fOwningRTPendingOps = &fCCPR->fRTPendingOpsMap[opList->uniqueID()];
++fOwningRTPendingOps->fNumTotalPaths;
fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
fOwningRTPendingOps->fOpList.addToTail(this);
fOwningRTPendingOps->fMaxBufferItems.countPathItems(onlyDraw.fScissorMode, onlyDraw.fPath);
}
void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
const uint32_t* opListIDs, int numOpListIDs,
SkTArray<sk_sp<GrRenderTargetContext>>* results) {
using PathInstance = GrCCPRPathProcessor::Instance;
SkASSERT(!fPerFlushIndexBuffer);
SkASSERT(!fPerFlushVertexBuffer);
SkASSERT(!fPerFlushInstanceBuffer);
SkASSERT(fPerFlushAtlases.empty());
SkASSERT(!fFlushing);
SkDEBUGCODE(fFlushing = true;)
@ -154,8 +154,29 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
return; // Nothing to draw.
}
this->setupPerFlushResources(onFlushRP, opListIDs, numOpListIDs, results);
// Erase these last, once we are done accessing data from the SingleDraw allocators.
for (int i = 0; i < numOpListIDs; ++i) {
fRTPendingOpsMap.erase(opListIDs[i]);
}
}
void GrCoverageCountingPathRenderer::setupPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
const uint32_t* opListIDs,
int numOpListIDs,
SkTArray<sk_sp<GrRenderTargetContext>>* results) {
using PathInstance = GrCCPRPathProcessor::Instance;
SkASSERT(!fPerFlushIndexBuffer);
SkASSERT(!fPerFlushVertexBuffer);
SkASSERT(!fPerFlushInstanceBuffer);
SkASSERT(fPerFlushAtlases.empty());
fPerFlushResourcesAreValid = false;
SkTInternalLList<DrawPathsOp> flushingOps;
GrCCPRCoverageOpsBuilder::MaxBufferItems maxBufferItems;
int maxTotalPaths = 0, numSkPoints = 0, numSkVerbs = 0;
for (int i = 0; i < numOpListIDs; ++i) {
auto it = fRTPendingOpsMap.find(opListIDs[i]);
@ -163,13 +184,15 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
RTPendingOps& rtPendingOps = it->second;
SkASSERT(!rtPendingOps.fOpList.isEmpty());
flushingOps.concat(std::move(rtPendingOps.fOpList));
maxBufferItems += rtPendingOps.fMaxBufferItems;
maxTotalPaths += rtPendingOps.fNumTotalPaths;
numSkPoints += rtPendingOps.fNumSkPoints;
numSkVerbs += rtPendingOps.fNumSkVerbs;
}
}
SkASSERT(flushingOps.isEmpty() == !maxBufferItems.fMaxPaths);
SkASSERT(flushingOps.isEmpty() == !maxTotalPaths);
if (flushingOps.isEmpty()) {
return; // Still nothing to draw.
return; // Nothing to draw.
}
fPerFlushIndexBuffer = GrCCPRPathProcessor::FindOrMakeIndexBuffer(onFlushRP);
@ -184,14 +207,8 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
return;
}
GrCCPRCoverageOpsBuilder atlasOpsBuilder;
if (!atlasOpsBuilder.init(onFlushRP, maxBufferItems)) {
SkDebugf("WARNING: failed to allocate buffers for coverage ops. No paths will be drawn.\n");
return;
}
fPerFlushInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
maxBufferItems.fMaxPaths * sizeof(PathInstance));
maxTotalPaths * sizeof(PathInstance));
if (!fPerFlushInstanceBuffer) {
SkDebugf("WARNING: failed to allocate path instance buffer. No paths will be drawn.\n");
return;
@ -201,29 +218,29 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
SkASSERT(pathInstanceData);
int pathInstanceIdx = 0;
GrCCPRCoverageOpsBuilder atlasOpsBuilder(maxTotalPaths, numSkPoints, numSkVerbs);
GrCCPRAtlas* atlas = nullptr;
SkDEBUGCODE(int skippedPaths = 0;)
SkDEBUGCODE(int skippedTotalPaths = 0;)
SkTInternalLList<DrawPathsOp>::Iter iter;
iter.init(flushingOps, SkTInternalLList<DrawPathsOp>::Iter::kHead_IterStart);
while (DrawPathsOp* op = iter.get()) {
SkASSERT(op->fDebugInstanceCount > 0);
SkASSERT(-1 == op->fBaseInstance);
op->fBaseInstance = pathInstanceIdx;
while (DrawPathsOp* drawPathOp = iter.get()) {
SkASSERT(drawPathOp->fDebugInstanceCount > 0);
SkASSERT(-1 == drawPathOp->fBaseInstance);
drawPathOp->fBaseInstance = pathInstanceIdx;
for (const DrawPathsOp::SingleDraw* draw = &op->fHeadDraw; draw; draw = draw->fNext) {
for (const auto* draw = &drawPathOp->fHeadDraw; draw; draw = draw->fNext) {
// parsePath gives us two tight bounding boxes: one in device space, as well as a second
// one rotated an additional 45 degrees. The path vertex shader uses these two bounding
// boxes to generate an octagon that circumscribes the path.
SkRect devBounds, devBounds45;
atlasOpsBuilder.parsePath(draw->fScissorMode, draw->fMatrix, draw->fPath, &devBounds,
&devBounds45);
atlasOpsBuilder.parsePath(draw->fMatrix, draw->fPath, &devBounds, &devBounds45);
SkRect clippedDevBounds = devBounds;
if (ScissorMode::kScissored == draw->fScissorMode &&
!clippedDevBounds.intersect(devBounds, SkRect::Make(draw->fClipBounds))) {
SkDEBUGCODE(--op->fDebugInstanceCount);
SkDEBUGCODE(++skippedPaths;)
SkDEBUGCODE(++drawPathOp->fDebugSkippedInstances);
atlasOpsBuilder.discardParsedPath();
continue;
}
@ -234,12 +251,9 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
SkIPoint16 atlasLocation;
if (atlas && !atlas->addRect(w, h, &atlasLocation)) {
// The atlas is out of room and can't grow any bigger.
auto atlasOp = atlasOpsBuilder.createIntermediateOp(atlas->drawBounds());
if (auto rtc = atlas->finalize(onFlushRP, std::move(atlasOp))) {
results->push_back(std::move(rtc));
}
if (pathInstanceIdx > op->fBaseInstance) {
op->addAtlasBatch(atlas, pathInstanceIdx);
atlasOpsBuilder.emitOp(atlas->drawBounds());
if (pathInstanceIdx > drawPathOp->fBaseInstance) {
drawPathOp->addAtlasBatch(atlas, pathInstanceIdx);
}
atlas = nullptr;
}
@ -262,34 +276,54 @@ void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlush
draw->fColor
};
atlasOpsBuilder.saveParsedPath(clippedDevIBounds, offsetX, offsetY);
atlasOpsBuilder.saveParsedPath(draw->fScissorMode, clippedDevIBounds, offsetX, offsetY);
}
SkASSERT(pathInstanceIdx == op->fBaseInstance + op->fDebugInstanceCount);
op->addAtlasBatch(atlas, pathInstanceIdx);
SkASSERT(pathInstanceIdx == drawPathOp->fBaseInstance + drawPathOp->fDebugInstanceCount -
drawPathOp->fDebugSkippedInstances);
if (pathInstanceIdx > drawPathOp->fBaseInstance) {
drawPathOp->addAtlasBatch(atlas, pathInstanceIdx);
}
iter.next();
SkDEBUGCODE(skippedTotalPaths += drawPathOp->fDebugSkippedInstances;)
}
SkASSERT(pathInstanceIdx == maxTotalPaths - skippedTotalPaths);
if (atlas) {
atlasOpsBuilder.emitOp(atlas->drawBounds());
}
SkASSERT(pathInstanceIdx == maxBufferItems.fMaxPaths - skippedPaths);
fPerFlushInstanceBuffer->unmap();
std::unique_ptr<GrDrawOp> atlasOp = atlasOpsBuilder.finalize(atlas->drawBounds());
if (auto rtc = atlas->finalize(onFlushRP, std::move(atlasOp))) {
results->push_back(std::move(rtc));
// Draw the coverage ops into their respective atlases.
SkSTArray<4, std::unique_ptr<GrCCPRCoverageOp>> atlasOps(fPerFlushAtlases.count());
if (!atlasOpsBuilder.finalize(onFlushRP, &atlasOps)) {
SkDebugf("WARNING: failed to allocate ccpr atlas buffers. No paths will be drawn.\n");
return;
}
SkASSERT(atlasOps.count() == fPerFlushAtlases.count());
// Erase these last, once we are done accessing data from the SingleDraw allocators.
for (int i = 0; i < numOpListIDs; ++i) {
fRTPendingOpsMap.erase(opListIDs[i]);
GrTAllocator<GrCCPRAtlas>::Iter atlasIter(&fPerFlushAtlases);
for (std::unique_ptr<GrCCPRCoverageOp>& atlasOp : atlasOps) {
SkAssertResult(atlasIter.next());
GrCCPRAtlas* atlas = atlasIter.get();
SkASSERT(atlasOp->bounds() == SkRect::MakeIWH(atlas->drawBounds().width(),
atlas->drawBounds().height()));
if (auto rtc = atlas->finalize(onFlushRP, std::move(atlasOp))) {
results->push_back(std::move(rtc));
}
}
SkASSERT(!atlasIter.next());
fPerFlushResourcesAreValid = true;
}
void DrawPathsOp::onExecute(GrOpFlushState* flushState) {
SkASSERT(fCCPR->fFlushing);
SkASSERT(flushState->rtCommandBuffer());
if (!fCCPR->fPerFlushInstanceBuffer) {
if (!fCCPR->fPerFlushResourcesAreValid) {
return; // Setup failed.
}
@ -323,7 +357,7 @@ void DrawPathsOp::onExecute(GrOpFlushState* flushState) {
flushState->rtCommandBuffer()->draw(pipeline, coverProc, &mesh, nullptr, 1, this->bounds());
}
SkASSERT(baseInstance == fBaseInstance + fDebugInstanceCount);
SkASSERT(baseInstance == fBaseInstance + fDebugInstanceCount - fDebugSkippedInstances);
}
void GrCoverageCountingPathRenderer::postFlush() {

View File

@ -13,7 +13,7 @@
#include "GrPathRenderer.h"
#include "SkTInternalLList.h"
#include "ccpr/GrCCPRAtlas.h"
#include "ccpr/GrCCPRCoverageOpsBuilder.h"
#include "ccpr/GrCCPRCoverageOp.h"
#include "ops/GrDrawOp.h"
#include <map>
@ -106,6 +106,7 @@ public:
RTPendingOps* fOwningRTPendingOps;
int fBaseInstance;
SkDEBUGCODE(int fDebugInstanceCount;)
SkDEBUGCODE(int fDebugSkippedInstances;)
SkSTArray<1, AtlasBatch, true> fAtlasBatches;
friend class GrCoverageCountingPathRenderer;
@ -116,9 +117,14 @@ public:
private:
GrCoverageCountingPathRenderer() = default;
void setupPerFlushResources(GrOnFlushResourceProvider*, const uint32_t* opListIDs,
int numOpListIDs, SkTArray<sk_sp<GrRenderTargetContext>>* results);
struct RTPendingOps {
SkTInternalLList<DrawPathsOp> fOpList;
GrCCPRCoverageOpsBuilder::MaxBufferItems fMaxBufferItems;
int fNumTotalPaths = 0;
int fNumSkPoints = 0;
int fNumSkVerbs = 0;
GrSTAllocator<256, DrawPathsOp::SingleDraw> fDrawsAllocator;
};
@ -129,6 +135,7 @@ private:
sk_sp<GrBuffer> fPerFlushVertexBuffer;
sk_sp<GrBuffer> fPerFlushInstanceBuffer;
GrSTAllocator<4, GrCCPRAtlas> fPerFlushAtlases;
bool fPerFlushResourcesAreValid;
SkDEBUGCODE(bool fFlushing = false;)
};