Delete all low-level rendering from ccpr

This converts ccpr to just a poorly named atlas manager. The atlas
gets rendered by normal calls on its MSAA draw context:

  for (;;) {
      surfaceDrawContext->stencilPath();  // Stencil.
  }
  surfaceDrawContext->stencilRect(atlasBounds);  // Cover.

Bug: chromium:1158093
Change-Id: I758ffd372b2ed5bb8b370156b6f80f6204146700
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/381618
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2021-03-11 22:49:33 -07:00 committed by Skia Commit-Bot
parent 20c09f9290
commit 7d592cda58
31 changed files with 218 additions and 3367 deletions

View File

@ -627,34 +627,17 @@ skia_gl_gpu_sources = [
skia_ccpr_sources = [
# coverage counting path renderer
"$_src/gpu/ccpr/GrAutoMapVertexBuffer.h",
"$_src/gpu/ccpr/GrCCAtlas.cpp",
"$_src/gpu/ccpr/GrCCAtlas.h",
"$_src/gpu/ccpr/GrCCClipPath.cpp",
"$_src/gpu/ccpr/GrCCClipPath.h",
"$_src/gpu/ccpr/GrCCClipProcessor.cpp",
"$_src/gpu/ccpr/GrCCClipProcessor.h",
"$_src/gpu/ccpr/GrCCConicShader.cpp",
"$_src/gpu/ccpr/GrCCConicShader.h",
"$_src/gpu/ccpr/GrCCCoverageProcessor.cpp",
"$_src/gpu/ccpr/GrCCCoverageProcessor.h",
"$_src/gpu/ccpr/GrCCCubicShader.cpp",
"$_src/gpu/ccpr/GrCCCubicShader.h",
"$_src/gpu/ccpr/GrCCFillGeometry.cpp",
"$_src/gpu/ccpr/GrCCFillGeometry.h",
"$_src/gpu/ccpr/GrCCFiller.cpp",
"$_src/gpu/ccpr/GrCCFiller.h",
"$_src/gpu/ccpr/GrCCPerFlushResources.cpp",
"$_src/gpu/ccpr/GrCCPerFlushResources.h",
"$_src/gpu/ccpr/GrCCPerOpsTaskPaths.h",
"$_src/gpu/ccpr/GrCCQuadraticShader.cpp",
"$_src/gpu/ccpr/GrCCQuadraticShader.h",
"$_src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp",
"$_src/gpu/ccpr/GrCoverageCountingPathRenderer.h",
"$_src/gpu/ccpr/GrSampleMaskProcessor.cpp",
"$_src/gpu/ccpr/GrSampleMaskProcessor.h",
"$_src/gpu/ccpr/GrStencilAtlasOp.cpp",
"$_src/gpu/ccpr/GrStencilAtlasOp.h",
]
skia_nvpr_sources = [

View File

@ -8,6 +8,7 @@
#include "src/gpu/GrClipStack.h"
#include "include/core/SkMatrix.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRRectPriv.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkTaskGroup.h"

View File

@ -746,6 +746,43 @@ void GrSurfaceDrawContext::internalStencilClear(const SkIRect* scissor, bool ins
}
}
bool GrSurfaceDrawContext::stencilPath(const GrHardClip* clip,
GrAA doStencilMSAA,
const SkMatrix& viewMatrix,
const SkPath& path) {
SkIRect clipBounds = clip ? clip->getConservativeBounds()
: SkIRect::MakeSize(this->dimensions());
GrStyledShape shape(path, GrStyledShape::DoSimplify::kNo);
GrPathRenderer::CanDrawPathArgs canDrawArgs;
canDrawArgs.fCaps = fContext->priv().caps();
canDrawArgs.fProxy = this->asRenderTargetProxy();
canDrawArgs.fClipConservativeBounds = &clipBounds;
canDrawArgs.fViewMatrix = &viewMatrix;
canDrawArgs.fShape = &shape;
canDrawArgs.fPaint = nullptr;
canDrawArgs.fAAType = (doStencilMSAA == GrAA::kYes) ? GrAAType::kMSAA : GrAAType::kNone;
canDrawArgs.fHasUserStencilSettings = false;
canDrawArgs.fTargetIsWrappedVkSecondaryCB = this->wrapsVkSecondaryCB();
GrPathRenderer* pr = this->drawingManager()->getPathRenderer(
canDrawArgs, false, GrPathRendererChain::DrawType::kStencil);
if (!pr) {
SkDebugf("WARNING: No path renderer to stencil path.\n");
return false;
}
GrPathRenderer::StencilPathArgs args;
args.fContext = fContext;
args.fRenderTargetContext = this;
args.fClip = clip;
args.fClipConservativeBounds = &clipBounds;
args.fViewMatrix = &viewMatrix;
args.fShape = &shape;
args.fDoStencilMSAA = doStencilMSAA;
pr->stencilPath(args);
return true;
}
void GrSurfaceDrawContext::stencilPath(const GrHardClip* clip,
GrAA doStencilMSAA,
const SkMatrix& viewMatrix,

View File

@ -583,6 +583,15 @@ public:
this->drawFilledQuad(clip, std::move(paint), doStencilMSAA, &quad, ss);
}
// Fills the user stencil bits with a non-zero value at every sample inside the path. This will
// likely be implemented with a Redbook algorithm, but it is not guaranteed. The samples being
// rendered to must be zero initially.
bool stencilPath(const GrHardClip*,
GrAA doStencilMSAA,
const SkMatrix& viewMatrix,
const SkPath&);
// Same as for stencilPath, but for an NVPR path object.
void stencilPath(const GrHardClip*,
GrAA doStencilMSAA,
const SkMatrix& viewMatrix,

View File

@ -1477,39 +1477,3 @@ int GrTriangulator::polysToTriangles(Poly* polys, GrEagerVertexAllocator* vertex
vertexAllocator->unlock(actualCount);
return actualCount;
}
int GrTriangulator::PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
WindingVertex** verts) {
SkArenaAlloc alloc(kArenaDefaultChunkSize);
GrTriangulator triangulator(path, &alloc);
bool isLinear;
Poly* polys = triangulator.pathToPolys(tolerance, clipBounds, &isLinear);
int64_t count64 = CountPoints(polys, path.getFillType());
if (0 == count64 || count64 > SK_MaxS32) {
*verts = nullptr;
return 0;
}
int count = count64;
*verts = new WindingVertex[count];
WindingVertex* vertsEnd = *verts;
SkPoint* points = new SkPoint[count];
SkPoint* pointsEnd = points;
for (Poly* poly = polys; poly; poly = poly->fNext) {
if (apply_fill_type(path.getFillType(), poly)) {
SkPoint* start = pointsEnd;
pointsEnd = static_cast<SkPoint*>(triangulator.emitPoly(poly, pointsEnd));
while (start != pointsEnd) {
vertsEnd->fPos = *start;
vertsEnd->fWinding = poly->fWinding;
++start;
++vertsEnd;
}
}
}
int actualCount = static_cast<int>(vertsEnd - *verts);
SkASSERT(actualCount <= count);
SkASSERT(pointsEnd - points == actualCount);
delete[] points;
return actualCount;
}

View File

@ -36,21 +36,6 @@ public:
return count;
}
struct WindingVertex {
SkPoint fPos;
int fWinding;
};
// *DEPRECATED*: Once CCPR is removed this method will go away.
//
// Triangulates a path to an array of vertices. Each triangle is represented as a set of three
// WindingVertex entries, each of which contains the position and winding count (which is the
// same for all three vertices of a triangle). The 'verts' out parameter is set to point to the
// resultant vertex array. CALLER IS RESPONSIBLE for deleting this buffer to avoid a memory
// leak!
static int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
WindingVertex** verts);
// Enums used by GrTriangulator internals.
typedef enum { kLeft_Side, kRight_Side } Side;
enum class EdgeType { kInner, kOuter, kConnector };

View File

@ -1,75 +0,0 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrAutoMapVertexBuffer_DEFINED
#define GrAutoMapVertexBuffer_DEFINED
#include "include/private/SkNoncopyable.h"
#include "src/gpu/GrGpuBuffer.h"
#include "src/gpu/GrOnFlushResourceProvider.h"
// This class automatically allocates and maps a GPU vertex buffer, and polyfills the mapping
// functionality with a mirror buffer on CPU if it is not supported.
class GrAutoMapVertexBuffer : SkNoncopyable {
public:
~GrAutoMapVertexBuffer() {
if (this->isMapped()) {
this->unmapBuffer();
}
}
bool hasGpuBuffer() const { return SkToBool(fGpuBuffer.get()); }
sk_sp<const GrGpuBuffer> gpuBuffer() const { return fGpuBuffer; }
bool isMapped() const { return SkToBool(fData); }
void* data() const { SkASSERT(this->isMapped()); return fData; }
void resetAndMapBuffer(GrOnFlushResourceProvider* onFlushRP, size_t sizeInBytes) {
if (this->isMapped()) {
this->unmapBuffer();
}
fGpuBuffer = onFlushRP->makeBuffer(GrGpuBufferType::kVertex, sizeInBytes);
if (!fGpuBuffer) {
fSizeInBytes = 0;
fData = nullptr;
return;
}
fSizeInBytes = sizeInBytes;
fData = fGpuBuffer->map();
if (!fData) {
// Mapping failed. Allocate a mirror buffer on CPU.
fData = sk_malloc_throw(fSizeInBytes);
}
}
void unmapBuffer() {
SkASSERT(this->isMapped());
if (fGpuBuffer->isMapped()) {
fGpuBuffer->unmap();
} else {
// fData is a mirror buffer on CPU.
fGpuBuffer->updateData(fData, fSizeInBytes);
sk_free(fData);
}
fData = nullptr;
}
protected:
sk_sp<GrGpuBuffer> fGpuBuffer;
size_t fSizeInBytes = 0;
void* fData = nullptr;
};
template<typename T> class GrTAutoMapVertexBuffer : public GrAutoMapVertexBuffer {
public:
T& operator[](int idx) {
SkASSERT(this->isMapped());
SkASSERT(idx >= 0 && (size_t)idx < fSizeInBytes / sizeof(T));
return ((T*)fData)[idx];
}
};
#endif

View File

@ -47,15 +47,12 @@ void GrCCClipPath::init(const SkPath& deviceSpacePath, const SkIRect& accessRect
fAccessRect = accessRect;
}
void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* specs) const {
void GrCCClipPath::accountForOwnPath(GrCCAtlas::Specs* specs) const {
SkASSERT(this->isInitialized());
++specs->fNumClipPaths;
specs->fRenderedPathStats.statPath(fDeviceSpacePath);
SkIRect ibounds;
if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
specs->fRenderedAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height());
specs->accountForSpace(ibounds.width(), ibounds.height());
}
}
@ -64,7 +61,7 @@ void GrCCClipPath::renderPathInAtlas(GrCCPerFlushResources* resources,
SkASSERT(this->isInitialized());
SkASSERT(!fHasAtlas);
fAtlas = resources->renderDeviceSpacePathInAtlas(
fAccessRect, fDeviceSpacePath, fPathDevIBounds, GrFillRuleForSkPath(fDeviceSpacePath),
&fDevToAtlasOffset);
onFlushRP, fAccessRect, fDeviceSpacePath, fPathDevIBounds,
GrFillRuleForSkPath(fDeviceSpacePath), &fDevToAtlasOffset);
SkDEBUGCODE(fHasAtlas = true);
}

View File

@ -58,7 +58,7 @@ public:
return fPathDevIBounds;
}
void accountForOwnPath(GrCCPerFlushResourceSpecs*) const;
void accountForOwnPath(GrCCAtlas::Specs*) const;
void renderPathInAtlas(GrCCPerFlushResources*, GrOnFlushResourceProvider*);
const SkIVector& atlasTranslate() const {

View File

@ -1,72 +0,0 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ccpr/GrCCConicShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
void GrCCConicShader::emitSetupCode(
GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const {
// K is distance from the line P2 -> P0. L is distance from the line P0 -> P1, scaled by 2w.
// M is distance from the line P1 -> P2, scaled by 2w. We do this in a space where P1=0.
s->declareGlobal(fKLMMatrix);
s->codeAppendf("float x0 = %s[0].x - %s[1].x, x2 = %s[2].x - %s[1].x;", pts, pts, pts, pts);
s->codeAppendf("float y0 = %s[0].y - %s[1].y, y2 = %s[2].y - %s[1].y;", pts, pts, pts, pts);
s->codeAppendf("float w = %s[3].x;", pts);
s->codeAppendf("%s = float3x3(y2 - y0, x0 - x2, x2*y0 - x0*y2, "
"2*w * float2(+y0, -x0), 0, "
"2*w * float2(-y2, +x2), 0);", fKLMMatrix.c_str());
s->declareGlobal(fControlPoint);
s->codeAppendf("%s = %s[1];", fControlPoint.c_str(), pts);
// Scale KLM by the inverse Manhattan width of K, and make sure K is positive. This allows K to
// double as the flat opposite edge AA. kwidth will not be 0 because we cull degenerate conics
// on the CPU.
s->codeAppendf("float kwidth = 2*bloat * (abs(%s[0].x) + abs(%s[0].y)) * sign(%s[0].z);",
fKLMMatrix.c_str(), fKLMMatrix.c_str(), fKLMMatrix.c_str());
s->codeAppendf("%s *= 1/kwidth;", fKLMMatrix.c_str());
if (outHull4) {
// Clip the conic triangle by the tangent line at maximum height. Conics have the nice
// property that maximum height always occurs at T=.5. This is a simple application for
// De Casteljau's algorithm.
s->codeAppendf("float2 p1w = %s[1]*w;", pts);
s->codeAppend ("float r = 1 / (1 + w);");
s->codeAppend ("float2 conic_hull[4];");
s->codeAppendf("conic_hull[0] = %s[0];", pts);
s->codeAppendf("conic_hull[1] = (%s[0] + p1w) * r;", pts);
s->codeAppendf("conic_hull[2] = (p1w + %s[2]) * r;", pts);
s->codeAppendf("conic_hull[3] = %s[2];", pts);
*outHull4 = "conic_hull";
}
}
void GrCCConicShader::onEmitVaryings(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
code->appendf("float3 klm = float3(%s - %s, 1) * %s;",
position, fControlPoint.c_str(), fKLMMatrix.c_str());
fKLM_fWind.reset(kFloat3_GrSLType, scope);
varyingHandler->addVarying("klm", &fKLM_fWind);
code->appendf("%s.xyz = klm;", OutName(fKLM_fWind));
fGrad_fCorner.reset(cornerCoverage ? kFloat4_GrSLType : kFloat2_GrSLType, scope);
varyingHandler->addVarying((cornerCoverage) ? "grad_and_corner" : "grad", &fGrad_fCorner);
code->appendf("%s.xy = 2*bloat * (float3x2(%s) * float3(2*klm[0], -klm[2], -klm[1]));",
OutName(fGrad_fCorner), fKLMMatrix.c_str());
}
void GrCCConicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;",
fKLM_fWind.fsIn(), fKLM_fWind.fsIn(), fKLM_fWind.fsIn());
f->codeAppendf("float f = k*k - l*m;");
f->codeAppendf("float2 grad = %s;", fGrad_fCorner.fsIn());
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrCCConicShader_DEFINED
#define GrCCConicShader_DEFINED
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
/**
* This class renders the coverage of closed conic curves using the techniques outlined in
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
* Jim Blinn:
*
* 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 GrCCGeometry::conicTo().)
*/
class GrCCConicShader : public GrCCCoverageProcessor::Shader {
public:
bool calculatesOwnEdgeCoverage() const override { return true; }
void emitSetupCode(
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
void onEmitVaryings(
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
const char* coverage, const char* cornerCoverage, const char* wind) override;
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
private:
const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
const GrShaderVar fControlPoint{"control_point", kFloat2_GrSLType};
GrGLSLVarying fKLM_fWind;
GrGLSLVarying fGrad_fCorner;
};
#endif

View File

@ -1,189 +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 "src/gpu/ccpr/GrCCCoverageProcessor.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/ccpr/GrCCConicShader.h"
#include "src/gpu/ccpr/GrCCCubicShader.h"
#include "src/gpu/ccpr/GrCCQuadraticShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
class GrCCCoverageProcessor::TriangleShader : public GrCCCoverageProcessor::Shader {
void onEmitVaryings(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
const char* position, const char* coverage, const char* cornerCoverage,
const char* /*wind*/) override {
fCoverages.reset(kHalf_GrSLType, scope);
varyingHandler->addVarying("coverage", &fCoverages);
code->appendf("%s = %s;", OutName(fCoverages), coverage);
}
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override { return; }
GrGLSLVarying fCoverages;
};
void GrCCCoverageProcessor::Shader::CalcWind(const GrCCCoverageProcessor& proc,
GrGLSLVertexGeoBuilder* s, const char* pts,
const char* outputWind) {
if (3 == proc.numInputPoints()) {
s->codeAppendf("float2 a = %s[0] - %s[1], "
"b = %s[0] - %s[2];", pts, pts, pts, pts);
} else {
// All inputs are convex, so it's sufficient to just average the middle two input points.
SkASSERT(4 == proc.numInputPoints());
s->codeAppendf("float2 p12 = (%s[1] + %s[2]) * .5;", pts, pts);
s->codeAppendf("float2 a = %s[0] - p12, "
"b = %s[0] - %s[3];", pts, pts, pts);
}
s->codeAppend ("float area_x2 = determinant(float2x2(a, b));");
if (proc.isTriangles()) {
// We cull extremely thin triangles by zeroing wind. When a triangle gets too thin it's
// possible for FP round-off error to actually give us the wrong winding direction, causing
// rendering artifacts. The criteria we choose is "height <~ 1/1024". So we drop a triangle
// if the max effect it can have on any single pixel is <~ 1/1024, or 1/4 of a bit in 8888.
s->codeAppend ("float2 bbox_size = max(abs(a), abs(b));");
s->codeAppend ("float basewidth = max(bbox_size.x + bbox_size.y, 1);");
s->codeAppendf("%s = (abs(area_x2 * 1024) > basewidth) ? sign(half(area_x2)) : 0;",
outputWind);
} else {
// We already converted nearly-flat curves to lines on the CPU, so no need to worry about
// thin curve hulls at this point.
s->codeAppendf("%s = sign(half(area_x2));", outputWind);
}
}
void GrCCCoverageProcessor::Shader::CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder* s,
const char* leftPt,
const char* rightPt,
const char* rasterVertexDir,
const char* outputCoverage) {
// Here we find an edge's coverage at one corner of a conservative raster bloat box whose center
// falls on the edge in question. (A bloat box is axis-aligned and the size of one pixel.) We
// always set up coverage so it is -1 at the outermost corner, 0 at the innermost, and -.5 at
// the center. Interpolated, these coverage values convert jagged conservative raster edges into
// smooth antialiased edges.
//
// d1 == (P + sign(n) * bloat) dot n (Distance at the bloat box vertex whose
// == P dot n + (abs(n.x) + abs(n.y)) * bloatSize coverage=-1, where the bloat box is
// centered on P.)
//
// d0 == (P - sign(n) * bloat) dot n (Distance at the bloat box vertex whose
// == P dot n - (abs(n.x) + abs(n.y)) * bloatSize coverage=0, where the bloat box is
// centered on P.)
//
// d == (P + rasterVertexDir * bloatSize) dot n (Distance at the bloat box vertex whose
// == P dot n + (rasterVertexDir dot n) * bloatSize coverage we wish to calculate.)
//
// coverage == -(d - d0) / (d1 - d0) (coverage=-1 at d=d1; coverage=0 at d=d0)
//
// == (rasterVertexDir dot n) / (abs(n.x) + abs(n.y)) * -.5 - .5
//
s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
rightPt, leftPt, leftPt, rightPt);
s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
s->codeAppendf("float t = dot(%s, n);", rasterVertexDir);
// The below conditional guarantees we get exactly 1 on the divide when nwidth=t (in case the
// GPU divides by multiplying by the reciprocal?) It also guards against NaN when nwidth=0.
s->codeAppendf("%s = half(abs(t) != nwidth ? t / nwidth : sign(t)) * -.5 - .5;",
outputCoverage);
}
void GrCCCoverageProcessor::Shader::CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder* s,
const char* leftPt,
const char* rightPt,
const char* bloatDir1,
const char* bloatDir2,
const char* outputCoverages) {
// See comments in CalcEdgeCoverageAtBloatVertex.
s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
rightPt, leftPt, leftPt, rightPt);
s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
s->codeAppendf("float2 t = n * float2x2(%s, %s);", bloatDir1, bloatDir2);
s->codeAppendf("for (int i = 0; i < 2; ++i) {");
s->codeAppendf( "%s[i] = half(abs(t[i]) != nwidth ? t[i] / nwidth : sign(t[i])) * -.5 - .5;",
outputCoverages);
s->codeAppendf("}");
}
void GrCCCoverageProcessor::Shader::CalcCornerAttenuation(GrGLSLVertexGeoBuilder* s,
const char* leftDir, const char* rightDir,
const char* outputAttenuation) {
// obtuseness = cos(corner_angle) if corner_angle > 90 degrees
// 0 if corner_angle <= 90 degrees
//
// NOTE: leftDir and rightDir are normalized and point in the same direction the path was
// defined with, i.e., leftDir points into the corner and rightDir points away from the corner.
s->codeAppendf("half obtuseness = max(half(dot(%s, %s)), 0);", leftDir, rightDir);
// axis_alignedness = 1 - tan(angle_to_nearest_axis_from_corner_bisector)
// (i.e., 1 when the corner bisector is aligned with the x- or y-axis
// 0 when the corner bisector falls on a 45 degree angle
// 0..1 when the corner bisector falls somewhere in between
s->codeAppendf("half2 abs_bisect_maybe_transpose = abs((0 == obtuseness) ? half2(%s - %s) : "
"half2(%s + %s));",
leftDir, rightDir, leftDir, rightDir);
s->codeAppend ("half axis_alignedness = "
"1 - min(abs_bisect_maybe_transpose.y, abs_bisect_maybe_transpose.x) / "
"max(abs_bisect_maybe_transpose.x, abs_bisect_maybe_transpose.y);");
// ninety_degreesness = sin^2(corner_angle)
// sin^2 just because... it's always positive and the results looked better than plain sine... ?
s->codeAppendf("half ninety_degreesness = determinant(half2x2(%s, %s));", leftDir, rightDir);
s->codeAppend ("ninety_degreesness = ninety_degreesness * ninety_degreesness;");
// The below formula is not smart. It was just arrived at by considering the following
// observations:
//
// 1. 90-degree, axis-aligned corners have full attenuation along the bisector.
// (i.e. coverage = 1 - distance_to_corner^2)
// (i.e. outputAttenuation = 0)
//
// 2. 180-degree corners always have zero attenuation.
// (i.e. coverage = 1 - distance_to_corner)
// (i.e. outputAttenuation = 1)
//
// 3. 90-degree corners whose bisector falls on a 45 degree angle also do not attenuate.
// (i.e. outputAttenuation = 1)
s->codeAppendf("%s = max(obtuseness, axis_alignedness * ninety_degreesness);",
outputAttenuation);
}
GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const {
std::unique_ptr<Shader> shader;
switch (fPrimitiveType) {
case PrimitiveType::kTriangles:
case PrimitiveType::kWeightedTriangles:
shader = std::make_unique<TriangleShader>();
break;
case PrimitiveType::kQuadratics:
shader = std::make_unique<GrCCQuadraticShader>();
break;
case PrimitiveType::kCubics:
shader = std::make_unique<GrCCCubicShader>();
break;
case PrimitiveType::kConics:
shader = std::make_unique<GrCCConicShader>();
break;
}
return this->onCreateGLSLInstance(std::move(shader));
}
void GrCCCoverageProcessor::bindPipeline(GrOpFlushState* flushState, const GrPipeline& pipeline,
const SkRect& drawBounds,
const GrUserStencilSettings* stencil) const {
GrProgramInfo programInfo(flushState->writeView(), &pipeline, stencil, this,
this->primType(), 0, flushState->renderPassBarriers(),
flushState->colorLoadOp());
flushState->bindPipeline(programInfo, drawBounds);
}

View File

@ -1,289 +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 GrCCCoverageProcessor_DEFINED
#define GrCCCoverageProcessor_DEFINED
#include "include/private/SkNx.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/GrPipeline.h"
#include "src/gpu/GrShaderCaps.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
class GrGLSLFPFragmentBuilder;
class GrGLSLVertexGeoBuilder;
class GrOpFlushState;
/**
* This is the geometry processor for the simple convex primitive shapes (triangles and closed,
* convex bezier curves) from which ccpr paths are composed. The output is a single-channel alpha
* value, positive for clockwise shapes and negative for counter-clockwise, that indicates coverage.
*
* The caller is responsible to draw all primitives as produced by GrCCGeometry into a cleared,
* floating point, alpha-only render target using SkBlendMode::kPlus. Once all of a path's
* primitives have been drawn, the render target contains a composite coverage count that can then
* be used to draw the path (see GrCCPathProcessor).
*
* To draw primitives, use appendMesh() and draw() (defined below).
*/
class GrCCCoverageProcessor : public GrGeometryProcessor {
public:
enum class PrimitiveType {
kTriangles,
kWeightedTriangles, // Triangles (from the tessellator) whose winding magnitude > 1.
kQuadratics,
kCubics,
kConics
};
static const char* PrimitiveTypeName(PrimitiveType);
// Defines a single primitive shape with 3 input points (i.e. Triangles and Quadratics).
// X,Y point values are transposed.
struct TriPointInstance {
float fValues[6];
enum class Ordering : bool {
kXYTransposed,
kXYInterleaved,
};
void set(const SkPoint[3], const Sk2f& translate, Ordering);
void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& translate, Ordering);
void set(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering);
};
// Defines a single primitive shape with 4 input points, or 3 input points plus a "weight"
// parameter duplicated in both lanes of the 4th input (i.e. Cubics, Conics, and Triangles with
// a weighted winding number). X,Y point values are transposed.
struct QuadPointInstance {
float fX[4];
float fY[4];
void set(const SkPoint[4], float dx, float dy);
void setW(const SkPoint[3], const Sk2f& trans, float w);
void setW(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w);
};
PrimitiveType primitiveType() const { return fPrimitiveType; }
// Number of bezier points for curves, or 3 for triangles.
int numInputPoints() const { return PrimitiveType::kCubics == fPrimitiveType ? 4 : 3; }
bool isTriangles() const {
return PrimitiveType::kTriangles == fPrimitiveType ||
PrimitiveType::kWeightedTriangles == fPrimitiveType;
}
int hasInputWeight() const {
return PrimitiveType::kWeightedTriangles == fPrimitiveType ||
PrimitiveType::kConics == fPrimitiveType;
}
// GrPrimitiveProcessor overrides.
const char* name() const override { return PrimitiveTypeName(fPrimitiveType); }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
SkDEBUGCODE(this->getDebugBloatKey(b));
b->add32((int)fPrimitiveType);
}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
#ifdef SK_DEBUG
// Increases the 1/2 pixel AA bloat by a factor of debugBloat.
void enableDebugBloat(float debugBloat) { fDebugBloat = debugBloat; }
bool debugBloatEnabled() const { return fDebugBloat > 0; }
float debugBloat() const { SkASSERT(this->debugBloatEnabled()); return fDebugBloat; }
void getDebugBloatKey(GrProcessorKeyBuilder* b) const {
uint32_t bloatBits;
memcpy(&bloatBits, &fDebugBloat, 4);
b->add32(bloatBits);
}
#endif
// The caller uses these methods to actualy draw the coverage PrimitiveTypes. For each
// subpassIdx of each PrimitiveType, it calls reset/bind*/drawInstances.
virtual int numSubpasses() const = 0;
virtual void reset(PrimitiveType, int subpassIdx, GrResourceProvider*) = 0;
void bindPipeline(GrOpFlushState*, const GrPipeline&, const SkRect& drawBounds,
const GrUserStencilSettings* = &GrUserStencilSettings::kUnused) const;
virtual void bindBuffers(GrOpsRenderPass*, sk_sp<const GrBuffer> instanceBuffer) const = 0;
virtual void drawInstances(GrOpsRenderPass*, int instanceCount, int baseInstance) const = 0;
// The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
// provides details about shape-specific geometry.
class Shader {
public:
// Returns true if the Impl should not calculate the coverage argument for emitVaryings().
// If true, then "coverage" will have a signed magnitude of 1.
virtual bool calculatesOwnEdgeCoverage() const { return false; }
// Called before generating geometry. Subclasses may set up internal member variables during
// this time that will be needed during onEmitVaryings (e.g. transformation matrices).
//
// If the 'outHull4' parameter is provided, and there are not 4 input points, the subclass
// is required to fill it with the name of a 4-point hull around which the Impl can generate
// its geometry. If it is left unchanged, the Impl will use the regular input points.
virtual void emitSetupCode(
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4 = nullptr) const {
SkASSERT(!outHull4);
}
void emitVaryings(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
const char* position, const char* coverage, const char* cornerCoverage,
const char* wind) {
SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope);
this->onEmitVaryings(
varyingHandler, scope, code, position, coverage, cornerCoverage, wind);
}
// Assigns the built-in sample mask at the current pixel.
virtual void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const = 0;
// Calculates the winding direction of the input points (+1, -1, or 0). Wind for extremely
// thin triangles gets rounded to zero.
static void CalcWind(const GrCCCoverageProcessor&, GrGLSLVertexGeoBuilder*, const char* pts,
const char* outputWind);
// Calculates an edge's coverage at a conservative raster vertex. The edge is defined by two
// clockwise-ordered points, 'leftPt' and 'rightPt'. 'rasterVertexDir' is a pair of +/-1
// values that point in the direction of conservative raster bloat, starting from an
// endpoint.
//
// Coverage values ramp from -1 (completely outside the edge) to 0 (completely inside).
static void CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder*, const char* leftPt,
const char* rightPt, const char* rasterVertexDir,
const char* outputCoverage);
// Calculates an edge's coverage at two conservative raster vertices.
// (See CalcEdgeCoverageAtBloatVertex).
static void CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder*, const char* leftPt,
const char* rightPt, const char* bloatDir1,
const char* bloatDir2,
const char* outputCoverages);
// Corner boxes require an additional "attenuation" varying that is multiplied by the
// regular (linearly-interpolated) coverage. This function calculates the attenuation value
// to use in the single, outermost vertex. The remaining three vertices of the corner box
// all use an attenuation value of 1.
static void CalcCornerAttenuation(GrGLSLVertexGeoBuilder*, const char* leftDir,
const char* rightDir, const char* outputAttenuation);
virtual ~Shader() {}
protected:
// Here the subclass adds its internal varyings to the handler and produces code to
// initialize those varyings from a given position and coverage values.
//
// NOTE: the coverage values are signed appropriately for wind.
// 'coverage' will only be +1 or -1 on curves.
virtual void onEmitVaryings(
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
const char* coverage, const char* cornerCoverage, const char* wind) = 0;
// Returns the name of a Shader's internal varying at the point where where its value is
// assigned. This is intended to work whether called for a vertex or a geometry shader.
const char* OutName(const GrGLSLVarying& varying) const {
using Scope = GrGLSLVarying::Scope;
SkASSERT(Scope::kVertToGeo != varying.scope());
return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut();
}
// Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
};
protected:
// Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't
// accidentally bleed into neighbor pixels.
static constexpr float kAABloatRadius = 0.491111f;
GrCCCoverageProcessor(ClassID classID) : INHERITED(classID) {}
virtual GrPrimitiveType primType() const = 0;
virtual GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const = 0;
// Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
PrimitiveType fPrimitiveType;
SkDEBUGCODE(float fDebugBloat = 0);
class TriangleShader;
using INHERITED = GrGeometryProcessor;
};
inline const char* GrCCCoverageProcessor::PrimitiveTypeName(PrimitiveType type) {
switch (type) {
case PrimitiveType::kTriangles: return "kTriangles";
case PrimitiveType::kWeightedTriangles: return "kWeightedTriangles";
case PrimitiveType::kQuadratics: return "kQuadratics";
case PrimitiveType::kCubics: return "kCubics";
case PrimitiveType::kConics: return "kConics";
}
SK_ABORT("Invalid PrimitiveType");
}
inline void GrCCCoverageProcessor::TriPointInstance::set(
const SkPoint p[3], const Sk2f& translate, Ordering ordering) {
this->set(p[0], p[1], p[2], translate, ordering);
}
inline void GrCCCoverageProcessor::TriPointInstance::set(
const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, const Sk2f& translate,
Ordering ordering) {
Sk2f P0 = Sk2f::Load(&p0);
Sk2f P1 = Sk2f::Load(&p1);
Sk2f P2 = Sk2f::Load(&p2);
this->set(P0, P1, P2, translate, ordering);
}
inline void GrCCCoverageProcessor::TriPointInstance::set(
const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering ordering) {
if (Ordering::kXYTransposed == ordering) {
Sk2f::Store3(fValues, P0 + translate, P1 + translate, P2 + translate);
} else {
(P0 + translate).store(fValues);
(P1 + translate).store(fValues + 2);
(P2 + translate).store(fValues + 4);
}
}
inline void GrCCCoverageProcessor::QuadPointInstance::set(const SkPoint p[4], float dx, float dy) {
Sk4f X,Y;
Sk4f::Load2(p, &X, &Y);
(X + dx).store(&fX);
(Y + dy).store(&fY);
}
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint p[3], const Sk2f& trans,
float w) {
this->setW(p[0], p[1], p[2], trans, w);
}
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint& p0, const SkPoint& p1,
const SkPoint& p2, const Sk2f& trans,
float w) {
Sk2f P0 = Sk2f::Load(&p0);
Sk2f P1 = Sk2f::Load(&p1);
Sk2f P2 = Sk2f::Load(&p2);
this->setW(P0, P1, P2, trans, w);
}
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const Sk2f& P0, const Sk2f& P1,
const Sk2f& P2, const Sk2f& trans,
float w) {
Sk2f W = Sk2f(w);
Sk2f::Store4(this, P0 + trans, P1 + trans, P2 + trans, W);
}
#endif

View File

@ -1,107 +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 "src/gpu/ccpr/GrCCCubicShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
using Shader = GrCCCoverageProcessor::Shader;
void GrCCCubicShader::emitSetupCode(
GrGLSLVertexGeoBuilder* s, const char* pts, const char** /*outHull4*/) const {
// Find the cubic's power basis coefficients.
s->codeAppendf("float2x4 C = float4x4(-1, 3, -3, 1, "
" 3, -6, 3, 0, "
"-3, 3, 0, 0, "
" 1, 0, 0, 0) * transpose(%s);", pts);
// Find the cubic's inflection function.
s->codeAppend ("float D3 = +determinant(float2x2(C[0].yz, C[1].yz));");
s->codeAppend ("float D2 = -determinant(float2x2(C[0].xz, C[1].xz));");
s->codeAppend ("float D1 = +determinant(float2x2(C));");
// Shift the exponents in D so the largest magnitude falls somewhere in 1..2. This protects us
// from overflow while solving for roots and KLM functionals.
s->codeAppend ("float Dmax = max(max(abs(D1), abs(D2)), abs(D3));");
s->codeAppend ("float norm;");
if (s->getProgramBuilder()->shaderCaps()->fpManipulationSupport()) {
s->codeAppend ("int exp;");
s->codeAppend ("frexp(Dmax, exp);");
s->codeAppend ("norm = ldexp(1, 1 - exp);");
} else {
s->codeAppend ("norm = 1/Dmax;"); // Dmax will not be 0 because we cull line cubics on CPU.
}
s->codeAppend ("D3 *= norm;");
s->codeAppend ("D2 *= norm;");
s->codeAppend ("D1 *= norm;");
// Calculate the KLM matrix.
s->declareGlobal(fKLMMatrix);
s->codeAppend ("float discr = 3*D2*D2 - 4*D1*D3;");
s->codeAppend ("float x = discr >= 0 ? 3 : 1;");
s->codeAppend ("float q = sqrt(x * abs(discr));");
s->codeAppend ("q = x*D2 + (D2 >= 0 ? q : -q);");
s->codeAppend ("float2 l, m;");
s->codeAppend ("l.ts = float2(q, 2*x * D1);");
s->codeAppend ("m.ts = float2(2, q) * (discr >= 0 ? float2(D3, 1) "
": float2(D2*D2 - D3*D1, D1));");
s->codeAppend ("float4 K;");
s->codeAppend ("float4 lm = l.sstt * m.stst;");
s->codeAppend ("K = float4(0, lm.x, -lm.y - lm.z, lm.w);");
s->codeAppend ("float4 L, M;");
s->codeAppend ("lm.yz += 2*lm.zy;");
s->codeAppend ("L = float4(-1,x,-x,1) * l.sstt * (discr >= 0 ? l.ssst * l.sttt : lm);");
s->codeAppend ("M = float4(-1,x,-x,1) * m.sstt * (discr >= 0 ? m.ssst * m.sttt : lm.xzyw);");
s->codeAppend ("int middlerow = abs(D2) > abs(D1) ? 2 : 1;");
s->codeAppend ("float3x3 CI = inverse(float3x3(C[0][0], C[0][middlerow], C[0][3], "
"C[1][0], C[1][middlerow], C[1][3], "
" 0, 0, 1));");
s->codeAppendf("%s = CI * float3x3(K[0], K[middlerow], K[3], "
"L[0], L[middlerow], L[3], "
"M[0], M[middlerow], M[3]);", fKLMMatrix.c_str());
// Evaluate the cubic at T=.5 for a mid-ish point.
s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts);
// Orient the KLM matrix so L & M are both positive on the side of the curve we wish to fill.
s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
fKLMMatrix.c_str(), fKLMMatrix.c_str());
s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
"0, orientation[0], 0, "
"0, 0, orientation[1]);", fKLMMatrix.c_str());
}
void GrCCCubicShader::onEmitVaryings(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
fKLM_fEdge.reset(kFloat3_GrSLType, scope);
varyingHandler->addVarying("klm", &fKLM_fEdge);
code->appendf("%s = klm;", OutName(fKLM_fEdge));
fGradMatrix.reset(kFloat4_GrSLType, scope);
varyingHandler->addVarying("grad_matrix", &fGradMatrix);
code->appendf("%s.xy = 2*bloat * 3 * klm[0] * %s[0].xy;",
OutName(fGradMatrix), fKLMMatrix.c_str());
code->appendf("%s.zw = -2*bloat * (klm[1] * %s[2].xy + klm[2] * %s[1].xy);",
OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
}
void GrCCCubicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;",
fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn());
f->codeAppendf("float f = k*k*k - l*m;");
f->codeAppendf("float2x2 grad_matrix = float2x2(%s);", fGradMatrix.fsIn());
f->codeAppendf("float2 grad = grad_matrix * float2(k, 1);");
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
}

View File

@ -1,42 +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 GrCCCubicShader_DEFINED
#define GrCCCubicShader_DEFINED
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
/**
* This class renders the coverage of convex closed cubic segments using the techniques outlined in
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
* Jim Blinn:
*
* https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
*
* The provided curve segments must be convex, monotonic with respect to the vector of their closing
* edge [P3 - P0], and must not contain or be near any inflection points or loop intersections.
* (Use GrCCGeometry::cubicTo().)
*/
class GrCCCubicShader : public GrCCCoverageProcessor::Shader {
public:
void emitSetupCode(
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
void onEmitVaryings(
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
const char* coverage, const char* cornerCoverage, const char* wind) override;
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
private:
const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
GrGLSLVarying fKLM_fEdge;
GrGLSLVarying fGradMatrix;
GrGLSLVarying fCornerCoverage;
};
#endif

View File

@ -1,802 +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 "src/gpu/ccpr/GrCCFillGeometry.h"
#include "include/gpu/GrTypes.h"
#include "src/core/SkGeometry.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel.
void GrCCFillGeometry::beginPath() {
SkASSERT(!fBuildingContour);
fVerbs.push_back(Verb::kBeginPath);
}
void GrCCFillGeometry::beginContour(const SkPoint& pt) {
SkASSERT(!fBuildingContour);
// 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, 0};
fPoints.push_back(pt);
fVerbs.push_back(Verb::kBeginContour);
fCurrAnchorPoint = pt;
SkDEBUGCODE(fBuildingContour = true);
}
void GrCCFillGeometry::lineTo(const SkPoint P[2]) {
SkASSERT(fBuildingContour);
SkASSERT(P[0] == fPoints.back());
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
this->appendLine(p0, p1);
}
inline void GrCCFillGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) {
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
if ((p0 == p1).allTrue()) {
return;
}
p1.store(&fPoints.push_back());
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();
}
static inline float dot(const Sk2f& a, const Sk2f& b) {
float product[2];
(a * b).store(product);
return product[0] + product[1];
}
static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
float tolerance = kFlatnessThreshold) {
Sk2f l = p2 - p0; // Line from p0 -> p2.
// lwidth = Manhattan width of l.
Sk2f labs = l.abs();
float lwidth = labs[0] + labs[1];
// d = |p1 - p0| dot | l.y|
// |-l.x| = distance from p1 to l.
Sk2f dd = (p1 - p0) * SkNx_shuffle<1,0>(l);
float d = dd[0] - dd[1];
// We are collinear if a box with radius "tolerance", centered on p1, touches the line l.
// To decide this, we check if the distance from p1 to the line is less than the distance from
// p1 to the far corner of this imaginary box, along that same normal vector.
// The far corner of the box can be found at "p1 + sign(n) * tolerance", where n is normal to l:
//
// abs(dot(p1 - p0, n)) <= dot(sign(n) * tolerance, n)
//
// Which reduces to:
//
// abs(d) <= (n.x * sign(n.x) + n.y * sign(n.y)) * tolerance
// abs(d) <= (abs(n.x) + abs(n.y)) * tolerance
//
// Use "<=" in case l == 0.
return std::abs(d) <= lwidth * tolerance;
}
static inline bool are_collinear(const SkPoint P[4], float tolerance = kFlatnessThreshold) {
Sk4f Px, Py; // |Px Py| |p0 - p3|
Sk4f::Load2(P, &Px, &Py); // |. . | = |p1 - p3|
Px -= Px[3]; // |. . | |p2 - p3|
Py -= Py[3]; // |. . | | 0 |
// Find [lx, ly] = the line from p3 to the furthest-away point from p3.
Sk4f Pwidth = Px.abs() + Py.abs(); // Pwidth = Manhattan width of each point.
int lidx = Pwidth[0] > Pwidth[1] ? 0 : 1;
lidx = Pwidth[lidx] > Pwidth[2] ? lidx : 2;
float lx = Px[lidx], ly = Py[lidx];
float lwidth = Pwidth[lidx]; // lwidth = Manhattan width of [lx, ly].
// |Px Py|
// d = |. . | * | ly| = distances from each point to l (two of the distances will be zero).
// |. . | |-lx|
// |. . |
Sk4f d = Px*ly - Py*lx;
// We are collinear if boxes with radius "tolerance", centered on all 4 points all touch line l.
// (See the rationale for this formula in the above, 3-point version of this function.)
// Use "<=" in case l == 0.
return (d.abs() <= lwidth * tolerance).allTrue();
}
// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt].
static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& tan0,
const Sk2f& endPt, const Sk2f& tan1) {
Sk2f v = endPt - startPt;
float dot0 = dot(tan0, v);
float dot1 = dot(tan1, v);
// A small, negative tolerance handles floating-point error in the case when one tangent
// approaches 0 length, meaning the (convex) curve segment is effectively a flat line.
float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero;
return dot0 >= tolerance && dot1 >= tolerance;
}
template<int N> static inline SkNx<N,float> lerp(const SkNx<N,float>& a, const SkNx<N,float>& b,
const SkNx<N,float>& t) {
return SkNx_fma(t, b - a, a);
}
void GrCCFillGeometry::quadraticTo(const SkPoint P[3]) {
SkASSERT(fBuildingContour);
SkASSERT(P[0] == fPoints.back());
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
// Don't crunch on the curve if it is nearly flat (or just very small). Flat curves can break
// The monotonic chopping math.
if (are_collinear(p0, p1, p2)) {
this->appendLine(p0, p2);
return;
}
this->appendQuadratics(p0, p1, p2);
}
inline void GrCCFillGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
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)) {
this->appendMonotonicQuadratic(p0, p1, p2);
return;
}
// Chop the curve into two segments with equal curvature. To do this we find the T value whose
// tangent angle is halfway between tan0 and tan1.
Sk2f n = normalize(tan0) - normalize(tan1);
// The midtangent can be found where (dQ(t) dot n) = 0:
//
// 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n |
// | -2*p0 + 2*p1 | | . |
//
// = | 2*t 1 | * | tan1 - tan0 | * | n |
// | 2*tan0 | | . |
//
// = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n)
//
// t = (tan0 dot n) / ((tan0 - tan1) dot n)
Sk2f dQ1n = (tan0 - tan1) * n;
Sk2f dQ0n = tan0 * n;
Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n));
t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error.
Sk2f p01 = SkNx_fma(t, tan0, p0);
Sk2f p12 = SkNx_fma(t, tan1, p1);
Sk2f p012 = lerp(p01, p12, t);
this->appendMonotonicQuadratic(p0, p01, p012);
this->appendMonotonicQuadratic(p012, p12, p2);
}
inline void GrCCFillGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2) {
// Don't send curves to the GPU if we know they are nearly flat (or just very small).
if (are_collinear(p0, p1, p2)) {
this->appendLine(p0, p2);
return;
}
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
SkASSERT((p0 != p2).anyTrue());
p1.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
fVerbs.push_back(Verb::kMonotonicQuadraticTo);
++fCurrContourTallies.fQuadratics;
}
static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
Sk2f aa = a*a;
aa += SkNx_shuffle<1,0>(aa);
SkASSERT(aa[0] == aa[1]);
Sk2f bb = b*b;
bb += SkNx_shuffle<1,0>(bb);
SkASSERT(bb[0] == bb[1]);
return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
}
static inline void get_cubic_tangents(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, Sk2f* tan0, Sk2f* tan1) {
*tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
*tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
}
static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, const Sk2f& tan0, const Sk2f& tan1,
Sk2f* c) {
Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan1, p3);
*c = (c1 + c2) * .5f; // Hopefully optimized out if not used?
return ((c1 - c2).abs() <= 1).allTrue();
}
enum class ExcludedTerm : bool {
kQuadraticTerm,
kLinearTerm
};
// Finds where to chop a non-loop around its inflection points. The resulting cubic segments will be
// chopped such that a box of radius 'padRadius', centered at any point along the curve segment, is
// guaranteed to not cross the tangent lines at the inflection points (a.k.a lines L & M).
//
// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be
// drawn with flat lines instead of cubics.
//
// A serpentine cubic has two inflection points, so this method takes Sk2f and computes the padding
// for both in SIMD.
static inline void find_chops_around_inflection_points(float padRadius, Sk2f tl, Sk2f sl,
const Sk2f& C0, const Sk2f& C1,
ExcludedTerm skipTerm, float Cdet,
SkSTArray<4, float>* chops) {
SkASSERT(chops->empty());
SkASSERT(padRadius >= 0);
padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on.
// The homogeneous parametric functions for distance from lines L & M are:
//
// l(t,s) = (t*sl - s*tl)^3
// m(t,s) = (t*sm - s*tm)^3
//
// See "Resolution Independent Curve Rendering using Programmable Graphics Hardware",
// 4.3 Finding klmn:
//
// https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
//
// From here on we use Sk2f with "L" names, but the second lane will be for line M.
tl = (sl > 0).thenElse(tl, -tl); // Tl=tl/sl is the triple root of l(t,s). Normalize so s >= 0.
sl = sl.abs();
// Convert l(t,s), m(t,s) to power-basis form:
//
// | l3 m3 |
// |l(t,s) m(t,s)| = |t^3 t^2*s t*s^2 s^3| * | l2 m2 |
// | l1 m1 |
// | l0 m0 |
//
Sk2f l3 = sl*sl*sl;
Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sl*tl*-3 : sl*tl*tl*3;
// The equation for line L can be found as follows:
//
// L = C^-1 * (l excluding skipTerm)
//
// (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.)
// We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather
// than divide by determinant(C) here, we have already performed this divide on padRadius.
Sk2f Lx = C1[1]*l3 - C0[1]*l2or1;
Sk2f Ly = -C1[0]*l3 + C0[0]*l2or1;
// A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan
// with of L. (See rationale in are_collinear.)
Sk2f Lwidth = Lx.abs() + Ly.abs();
Sk2f pad = Lwidth * padRadius;
// Will T=(t + cbrt(pad))/s be greater than 0? No need to solve roots outside T=0..1.
Sk2f insideLeftPad = pad + tl*tl*tl;
// Will T=(t - cbrt(pad))/s be less than 1? No need to solve roots outside T=0..1.
Sk2f tms = tl - sl;
Sk2f insideRightPad = pad - tms*tms*tms;
// Solve for the T values where abs(l(T)) = pad.
if (insideLeftPad[0] > 0 && insideRightPad[0] > 0) {
float padT = cbrtf(pad[0]);
Sk2f pts = (tl[0] + Sk2f(-padT, +padT)) / sl[0];
pts.store(chops->push_back_n(2));
}
// Solve for the T values where abs(m(T)) = pad.
if (insideLeftPad[1] > 0 && insideRightPad[1] > 0) {
float padT = cbrtf(pad[1]);
Sk2f pts = (tl[1] + Sk2f(-padT, +padT)) / sl[1];
pts.store(chops->push_back_n(2));
}
}
static inline void swap_if_greater(float& a, float& b) {
if (a > b) {
std::swap(a, b);
}
}
// Finds where to chop a non-loop around its intersection point. The resulting cubic segments will
// be chopped such that a box of radius 'padRadius', centered at any point along the curve segment,
// is guaranteed to not cross the tangent lines at the intersection point (a.k.a lines L & M).
//
// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be
// drawn with quadratic splines instead of cubics.
//
// A loop intersection falls at two different T values, so this method takes Sk2f and computes the
// padding for both in SIMD.
static inline void find_chops_around_loop_intersection(float padRadius, Sk2f t2, Sk2f s2,
const Sk2f& C0, const Sk2f& C1,
ExcludedTerm skipTerm, float Cdet,
SkSTArray<4, float>* chops) {
SkASSERT(chops->empty());
SkASSERT(padRadius >= 0);
padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on.
// The parametric functions for distance from lines L & M are:
//
// l(T) = (T - Td)^2 * (T - Te)
// m(T) = (T - Td) * (T - Te)^2
//
// See "Resolution Independent Curve Rendering using Programmable Graphics Hardware",
// 4.3 Finding klmn:
//
// https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
Sk2f T2 = t2/s2; // T2 is the double root of l(T).
Sk2f T1 = SkNx_shuffle<1,0>(T2); // T1 is the other root of l(T).
// Convert l(T), m(T) to power-basis form:
//
// | 1 1 |
// |l(T) m(T)| = |T^3 T^2 T 1| * | l2 m2 |
// | l1 m1 |
// | l0 m0 |
//
// From here on we use Sk2f with "L" names, but the second lane will be for line M.
Sk2f l2 = SkNx_fma(Sk2f(-2), T2, -T1);
Sk2f l1 = T2 * SkNx_fma(Sk2f(2), T1, T2);
Sk2f l0 = -T2*T2*T1;
// The equation for line L can be found as follows:
//
// L = C^-1 * (l excluding skipTerm)
//
// (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.)
// We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather
// than divide by determinant(C) here, we have already performed this divide on padRadius.
Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? l2 : l1;
Sk2f Lx = -C0[1]*l2or1 + C1[1]; // l3 is always 1.
Sk2f Ly = C0[0]*l2or1 - C1[0];
// A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan
// with of L. (See rationale in are_collinear.)
Sk2f Lwidth = Lx.abs() + Ly.abs();
Sk2f pad = Lwidth * padRadius;
// Is l(T=0) outside the padding around line L?
Sk2f lT0 = l0; // l(T=0) = |0 0 0 1| dot |1 l2 l1 l0| = l0
Sk2f outsideT0 = lT0.abs() - pad;
// Is l(T=1) outside the padding around line L?
Sk2f lT1 = (Sk2f(1) + l2 + l1 + l0).abs(); // l(T=1) = |1 1 1 1| dot |1 l2 l1 l0|
Sk2f outsideT1 = lT1.abs() - pad;
// Values for solving the cubic.
Sk2f p, q, qqq, discr, numRoots, D;
bool hasDiscr = false;
// Values for calculating one root (rarely needed).
Sk2f R, QQ;
bool hasOneRootVals = false;
// Values for calculating three roots.
Sk2f P, cosTheta3;
bool hasThreeRootVals = false;
// Solve for the T values where l(T) = +pad and m(T) = -pad.
for (int i = 0; i < 2; ++i) {
float T = T2[i]; // T is the point we are chopping around.
if ((T < 0 && outsideT0[i] >= 0) || (T > 1 && outsideT1[i] >= 0)) {
// The padding around T is completely out of range. No point solving for it.
continue;
}
if (!hasDiscr) {
p = Sk2f(+.5f, -.5f) * pad;
q = (1.f/3) * (T2 - T1);
qqq = q*q*q;
discr = qqq*p*2 + p*p;
numRoots = (discr < 0).thenElse(3, 1);
D = T2 - q;
hasDiscr = true;
}
if (1 == numRoots[i]) {
if (!hasOneRootVals) {
Sk2f r = qqq + p;
Sk2f s = r.abs() + discr.sqrt();
R = (r > 0).thenElse(-s, s);
QQ = q*q;
hasOneRootVals = true;
}
float A = cbrtf(R[i]);
float B = A != 0 ? QQ[i]/A : 0;
// When there is only one root, ine L chops from root..1, line M chops from 0..root.
if (1 == i) {
chops->push_back(0);
}
chops->push_back(A + B + D[i]);
if (0 == i) {
chops->push_back(1);
}
continue;
}
if (!hasThreeRootVals) {
P = q.abs() * -2;
cosTheta3 = (q >= 0).thenElse(1, -1) + p / qqq.abs();
hasThreeRootVals = true;
}
static constexpr float k2PiOver3 = 2 * SK_ScalarPI / 3;
float theta = std::acos(cosTheta3[i]) * (1.f/3);
float roots[3] = {P[i] * std::cos(theta) + D[i],
P[i] * std::cos(theta + k2PiOver3) + D[i],
P[i] * std::cos(theta - k2PiOver3) + D[i]};
// Sort the three roots.
swap_if_greater(roots[0], roots[1]);
swap_if_greater(roots[1], roots[2]);
swap_if_greater(roots[0], roots[1]);
// Line L chops around the first 2 roots, line M chops around the second 2.
chops->push_back_n(2, &roots[i]);
}
}
void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
SkASSERT(fBuildingContour);
SkASSERT(P[0] == fPoints.back());
// Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small).
// Flat curves can break the math below.
if (are_collinear(P)) {
Sk2f p0 = Sk2f::Load(P);
Sk2f p3 = Sk2f::Load(P+3);
this->appendLine(p0, p3);
return;
}
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
Sk2f p3 = Sk2f::Load(P+3);
// Also detect near-quadratics ahead of time.
Sk2f tan0, tan1, c;
get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1);
if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c)) {
this->appendQuadratics(p0, c, p3);
return;
}
double tt[2], ss[2], D[4];
fCurrCubicType = SkClassifyCubic(P, tt, ss, D);
SkASSERT(!SkCubicIsDegenerate(fCurrCubicType));
Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1]));
Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1]));
ExcludedTerm skipTerm = (std::abs(D[2]) > std::abs(D[1]))
? ExcludedTerm::kQuadraticTerm
: ExcludedTerm::kLinearTerm;
Sk2f C0 = SkNx_fma(Sk2f(3), p1 - p2, p3 - p0);
Sk2f C1 = (ExcludedTerm::kLinearTerm == skipTerm
? SkNx_fma(Sk2f(-2), p1, p0 + p2)
: p1 - p0) * 3;
Sk2f C0x1 = C0 * SkNx_shuffle<1,0>(C1);
float Cdet = C0x1[0] - C0x1[1];
SkSTArray<4, float> chops;
if (SkCubicType::kLoop != fCurrCubicType) {
find_chops_around_inflection_points(inflectPad, t, s, C0, C1, skipTerm, Cdet, &chops);
} else {
find_chops_around_loop_intersection(loopIntersectPad, t, s, C0, C1, skipTerm, Cdet, &chops);
}
if (4 == chops.count() && chops[1] >= chops[2]) {
// This just the means the KLM roots are so close that their paddings overlap. We will
// approximate the entire middle section, but still have it chopped midway. For loops this
// chop guarantees the append code only sees convex segments. Otherwise, it means we are (at
// least almost) a cusp and the chop makes sure we get a sharp point.
Sk2f ts = t * SkNx_shuffle<1,0>(s);
chops[1] = chops[2] = (ts[0] + ts[1]) / (2*s[0]*s[1]);
}
#ifdef SK_DEBUG
for (int i = 1; i < chops.count(); ++i) {
SkASSERT(chops[i] >= chops[i - 1]);
}
#endif
this->appendCubics(AppendCubicMode::kLiteral, p0, p1, p2, p3, chops.begin(), chops.count());
}
static inline void chop_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, const Sk2f& p3,
float T, Sk2f* ab, Sk2f* abc, Sk2f* abcd, Sk2f* bcd, Sk2f* cd) {
Sk2f TT = T;
*ab = lerp(p0, p1, TT);
Sk2f bc = lerp(p1, p2, TT);
*cd = lerp(p2, p3, TT);
*abc = lerp(*ab, bc, TT);
*bcd = lerp(bc, *cd, TT);
*abcd = lerp(*abc, *bcd, TT);
}
void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2, const Sk2f& p3, const float chops[],
int numChops, float localT0, float localT1) {
if (numChops) {
SkASSERT(numChops > 0);
int midChopIdx = numChops/2;
float T = chops[midChopIdx];
// Chops alternate between literal and approximate mode.
AppendCubicMode rightMode = (AppendCubicMode)((bool)mode ^ (midChopIdx & 1) ^ 1);
if (T <= localT0) {
// T is outside 0..1. Append the right side only.
this->appendCubics(rightMode, p0, p1, p2, p3, &chops[midChopIdx + 1],
numChops - midChopIdx - 1, localT0, localT1);
return;
}
if (T >= localT1) {
// T is outside 0..1. Append the left side only.
this->appendCubics(mode, p0, p1, p2, p3, chops, midChopIdx, localT0, localT1);
return;
}
float localT = (T - localT0) / (localT1 - localT0);
Sk2f p01, p02, pT, p11, p12;
chop_cubic(p0, p1, p2, p3, localT, &p01, &p02, &pT, &p11, &p12);
this->appendCubics(mode, p0, p01, p02, pT, chops, midChopIdx, localT0, T);
this->appendCubics(rightMode, pT, p11, p12, p3, &chops[midChopIdx + 1],
numChops - midChopIdx - 1, T, localT1);
return;
}
this->appendCubics(mode, p0, p1, p2, p3);
}
void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) {
if (SkCubicType::kLoop != fCurrCubicType) {
// Serpentines and cusps are always monotonic after chopping around inflection points.
SkASSERT(!SkCubicIsDegenerate(fCurrCubicType));
if (AppendCubicMode::kApproximate == mode) {
// This section passes through an inflection point, so we can get away with a flat line.
// This can cause some curves to feel slightly more flat when inspected rigorously back
// and forth against another renderer, but for now this seems acceptable given the
// simplicity.
this->appendLine(p0, p3);
return;
}
} else {
Sk2f tan0, tan1;
get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1);
if (maxSubdivisions && !is_convex_curve_monotonic(p0, tan0, p3, tan1)) {
this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1,
maxSubdivisions - 1);
return;
}
if (AppendCubicMode::kApproximate == mode) {
Sk2f c;
if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c) && maxSubdivisions) {
this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1,
maxSubdivisions - 1);
return;
}
this->appendMonotonicQuadratic(p0, c, p3);
return;
}
}
// Don't send curves to the GPU if we know they are nearly flat (or just very small).
// Since the cubic segment is known to be convex at this point, our flatness check is simple.
if (are_collinear(p0, (p1 + p2) * .5f, p3)) {
this->appendLine(p0, p3);
return;
}
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
SkASSERT((p0 != p3).anyTrue());
p1.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
p3.store(&fPoints.push_back());
fVerbs.push_back(Verb::kMonotonicCubicTo);
++fCurrContourTallies.fCubics;
}
// Given a convex curve segment with the following order-2 tangent function:
//
// |C2x C2y|
// tan = some_scale * |dx/dt dy/dt| = |t^2 t 1| * |C1x C1y|
// |C0x C0y|
//
// This function finds the T value whose tangent angle is halfway between the tangents at T=0 and
// T=1 (tan0 and tan1).
static inline float find_midtangent(const Sk2f& tan0, const Sk2f& tan1,
const Sk2f& C2, const Sk2f& C1, const Sk2f& C0) {
// Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
// midtangent. 'n' will therefore bisect tan0 and -tan1, giving us the normal to the midtangent.
//
// n dot midtangent = 0
//
Sk2f n = normalize(tan0) - normalize(tan1);
// Find the T value at the midtangent. This is a simple quadratic equation:
//
// midtangent dot n = 0
//
// (|t^2 t 1| * C) dot n = 0
//
// |t^2 t 1| dot C*n = 0
//
// First find coeffs = C*n.
Sk4f C[2];
Sk2f::Store4(C, C2, C1, C0, 0);
Sk4f coeffs = C[0]*n[0] + C[1]*n[1];
// Now solve the quadratic.
float a = coeffs[0], b = coeffs[1], c = coeffs[2];
float discr = b*b - 4*a*c;
if (discr < 0) {
return 0; // This will only happen if the curve is a line.
}
// The roots are q/a and c/q. Pick the one closer to T=.5.
float q = -.5f * (b + copysignf(std::sqrt(discr), b));
float r = .5f*q*a;
return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q;
}
inline void GrCCFillGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0,
const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, const Sk2f& tan0,
const Sk2f& tan1,
int maxFutureSubdivisions) {
float midT = find_midtangent(tan0, tan1, p3 + (p1 - p2)*3 - p0,
(p0 - p1*2 + p2)*2,
p1 - p0);
// Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull
// near-flat cubics in cubicTo().)
if (!(midT > 0 && midT < 1)) {
// The cubic is flat. Otherwise there would be a real midtangent inside T=0..1.
this->appendLine(p0, p3);
return;
}
Sk2f p01, p02, pT, p11, p12;
chop_cubic(p0, p1, p2, p3, midT, &p01, &p02, &pT, &p11, &p12);
this->appendCubics(mode, p0, p01, p02, pT, maxFutureSubdivisions);
this->appendCubics(mode, pT, p11, p12, p3, maxFutureSubdivisions);
}
void GrCCFillGeometry::conicTo(const SkPoint P[3], float w) {
SkASSERT(fBuildingContour);
SkASSERT(P[0] == fPoints.back());
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
Sk2f tan0 = p1 - p0;
Sk2f tan1 = p2 - p1;
if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
// The derivative of a conic has a cumbersome order-4 denominator. However, this isn't
// necessary if we are only interested in a vector in the same *direction* as a given
// tangent line. Since the denominator scales dx and dy uniformly, we can throw it out
// completely after evaluating the derivative with the standard quotient rule. This leaves
// us with a simpler quadratic function that we use to find the midtangent.
float midT = find_midtangent(tan0, tan1, (w - 1) * (p2 - p0),
(p2 - p0) - 2*w*(p1 - p0),
w*(p1 - p0));
// Use positive logic since NaN fails comparisons. (However midT should not be NaN since we
// cull near-linear conics above. And while w=0 is flat, it's not a line and has valid
// midtangents.)
if (!(midT > 0 && midT < 1)) {
// The conic is flat. Otherwise there would be a real midtangent inside T=0..1.
this->appendLine(p0, p2);
return;
}
// Chop the conic at midtangent to produce two monotonic segments.
Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0);
Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w;
Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0);
Sk4f midT4 = midT;
Sk4f p3d01 = lerp(p3d0, p3d1, midT4);
Sk4f p3d12 = lerp(p3d1, p3d2, midT4);
Sk4f p3d012 = lerp(p3d01, p3d12, midT4);
Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2];
Sk2f ww = Sk2f(p3d01[2], p3d12[2]) * Sk2f(p3d012[2]).rsqrt();
this->appendMonotonicConic(p0, Sk2f(p3d01[0], p3d01[1]) / p3d01[2], midpoint, ww[0]);
this->appendMonotonicConic(midpoint, Sk2f(p3d12[0], p3d12[1]) / p3d12[2], p2, ww[1]);
return;
}
this->appendMonotonicConic(p0, p1, p2, w);
}
void GrCCFillGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
float w) {
SkASSERT(w >= 0);
Sk2f base = p2 - p0;
Sk2f baseAbs = base.abs();
float baseWidth = baseAbs[0] + baseAbs[1];
// Find the height of the curve. Max height always occurs at T=.5 for conics.
Sk2f d = (p1 - p0) * SkNx_shuffle<1,0>(base);
float h1 = std::abs(d[1] - d[0]); // Height of p1 above the base.
float ht = h1*w, hs = 1 + w; // Height of the conic = ht/hs.
// i.e. (ht/hs <= baseWidth * kFlatnessThreshold). Use "<=" in case base == 0.
if (ht <= (baseWidth*hs) * kFlatnessThreshold) {
// We are flat. (See rationale in are_collinear.)
this->appendLine(p0, p2);
return;
}
// i.e. (w > 1 && h1 - ht/hs < baseWidth).
if (w > 1 && h1*hs - ht < baseWidth*hs) {
// If we get within 1px of p1 when w > 1, we will pick up artifacts from the implicit
// function's reflection. Chop at max height (T=.5) and draw a triangle instead.
Sk2f p1w = p1*w;
Sk2f ab = p0 + p1w;
Sk2f bc = p1w + p2;
Sk2f highpoint = (ab + bc) / (2*(1 + w));
this->appendLine(p0, highpoint);
this->appendLine(highpoint, p2);
return;
}
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
SkASSERT((p0 != p2).anyTrue());
p1.store(&fPoints.push_back());
p2.store(&fPoints.push_back());
fConicWeights.push_back(w);
fVerbs.push_back(Verb::kMonotonicConicTo);
++fCurrContourTallies.fConics;
}
GrCCFillGeometry::PrimitiveTallies GrCCFillGeometry::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 (fPoints.back() == fCurrAnchorPoint) {
--fanSize;
fVerbs.push_back(Verb::kEndClosedContour);
} else {
fVerbs.push_back(Verb::kEndOpenContour);
}
fCurrContourTallies.fTriangles = std::max(fanSize - 2, 0);
SkDEBUGCODE(fBuildingContour = false);
return fCurrContourTallies;
}

View File

@ -1,143 +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 GrGrCCFillGeometry_DEFINED
#define GrGrCCFillGeometry_DEFINED
#include "include/core/SkPoint.h"
#include "include/private/SkNx.h"
#include "include/private/SkTArray.h"
#include "src/core/SkGeometry.h"
/**
* This class chops device-space contours up into a series of segments that CCPR knows how to
* fill. (See GrCCFillGeometry::Verb.)
*
* NOTE: This must be done in device space, since an affine transformation can change whether a
* curve is monotonic.
*/
class GrCCFillGeometry {
public:
// These are the verbs that CCPR knows how to fill. 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].
kMonotonicCubicTo,
kMonotonicConicTo,
kEndClosedContour, // endPt == startPt.
kEndOpenContour // endPt != startPt.
};
// These tallies track numbers of CCPR primitives that are required to draw a contour.
struct PrimitiveTallies {
int fTriangles; // Number of triangles in the contour's fan.
int fWeightedTriangles; // Triangles (from the tessellator) whose winding magnitude > 1.
int fQuadratics;
int fCubics;
int fConics;
void operator+=(const PrimitiveTallies&);
PrimitiveTallies operator-(const PrimitiveTallies&) const;
bool operator==(const PrimitiveTallies&);
};
GrCCFillGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0)
: fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs.
, fVerbs(numSkVerbs * 3)
, fConicWeights(numConicWeights * 3/2) {}
const SkTArray<SkPoint, true>& points() const { SkASSERT(!fBuildingContour); return fPoints; }
const SkTArray<Verb, true>& verbs() const { SkASSERT(!fBuildingContour); return fVerbs; }
float getConicWeight(int idx) const { SkASSERT(!fBuildingContour); return fConicWeights[idx]; }
void reset() {
SkASSERT(!fBuildingContour);
fPoints.reset();
fVerbs.reset();
}
void beginPath();
void beginContour(const SkPoint&);
void lineTo(const SkPoint P[2]);
void quadraticTo(const SkPoint[3]);
// We pass through inflection points and loop intersections using a line and quadratic(s)
// respectively. 'inflectPad' and 'loopIntersectPad' specify how close (in pixels) cubic
// segments are allowed to get to these points. For normal rendering you will want to use the
// default values, but these can be overridden for testing purposes.
//
// NOTE: loops do appear to require two full pixels of padding around the intersection point.
// With just one pixel-width of pad, we start to see bad pixels. Ultimately this has a
// minimal effect on the total amount of segments produced. Most sections that pass
// through the loop intersection can be approximated with a single quadratic anyway,
// regardless of whether we are use one pixel of pad or two (1.622 avg. quads per loop
// intersection vs. 1.489 on the tiger).
void cubicTo(const SkPoint[4], float inflectPad = 0.55f, float loopIntersectPad = 2);
void conicTo(const SkPoint[3], float w);
PrimitiveTallies endContour(); // Returns the numbers of primitives needed to draw the contour.
private:
inline void appendLine(const Sk2f& p0, const Sk2f& p1);
inline void appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2);
inline void appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2);
enum class AppendCubicMode : bool {
kLiteral,
kApproximate
};
void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, const float chops[], int numChops, float localT0 = 0,
float localT1 = 1);
void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
const Sk2f& p3, int maxSubdivisions = 2);
void chopAndAppendCubicAtMidTangent(AppendCubicMode, const Sk2f& p0, const Sk2f& p1,
const Sk2f& p2, const Sk2f& p3, const Sk2f& tan0,
const Sk2f& tan1, int maxFutureSubdivisions);
void appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w);
// Transient state used while building a contour.
SkPoint fCurrAnchorPoint;
PrimitiveTallies fCurrContourTallies;
SkCubicType fCurrCubicType;
SkDEBUGCODE(bool fBuildingContour = false);
SkSTArray<128, SkPoint, true> fPoints;
SkSTArray<128, Verb, true> fVerbs;
SkSTArray<32, float, true> fConicWeights;
};
inline void GrCCFillGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) {
fTriangles += b.fTriangles;
fWeightedTriangles += b.fWeightedTriangles;
fQuadratics += b.fQuadratics;
fCubics += b.fCubics;
fConics += b.fConics;
}
GrCCFillGeometry::PrimitiveTallies
inline GrCCFillGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
return {fTriangles - b.fTriangles,
fWeightedTriangles - b.fWeightedTriangles,
fQuadratics - b.fQuadratics,
fCubics - b.fCubics,
fConics - b.fConics};
}
inline bool GrCCFillGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) {
return fTriangles == b.fTriangles && fWeightedTriangles == b.fWeightedTriangles &&
fQuadratics == b.fQuadratics && fCubics == b.fCubics && fConics == b.fConics;
}
#endif

View File

@ -1,555 +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 "src/gpu/ccpr/GrCCFiller.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrOnFlushResourceProvider.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrProgramInfo.h"
#include <cstdlib>
using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
GrCCFiller::GrCCFiller(Algorithm algorithm, int numPaths, int numSkPoints, int numSkVerbs,
int numConicWeights)
: fAlgorithm(algorithm)
, fGeometry(numSkPoints, numSkVerbs, numConicWeights)
, fPathInfos(numPaths)
, fScissorSubBatches(numPaths)
, 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()};
fBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), PrimitiveTallies()};
}
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts,
GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
const SkIVector& devToAtlasOffset) {
SkASSERT(!fInstanceBuffer.hasGpuBuffer()); // Can't call after prepareToDraw().
SkASSERT(!path.isEmpty());
int currPathPointsIdx = fGeometry.points().count();
int currPathVerbsIdx = fGeometry.verbs().count();
PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies();
fGeometry.beginPath();
const float* conicWeights = SkPathPriv::ConicWeightData(path);
int ptsIdx = 0;
int conicWeightsIdx = 0;
bool insideContour = false;
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
switch (verb) {
case SkPath::kMove_Verb:
if (insideContour) {
currPathPrimitiveCounts += fGeometry.endContour();
}
fGeometry.beginContour(deviceSpacePts[ptsIdx]);
++ptsIdx;
insideContour = true;
continue;
case SkPath::kClose_Verb:
if (insideContour) {
currPathPrimitiveCounts += fGeometry.endContour();
}
insideContour = false;
continue;
case SkPath::kLine_Verb:
fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]);
++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:
fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]);
ptsIdx += 2;
++conicWeightsIdx;
continue;
default:
SK_ABORT("Unexpected path verb.");
}
}
SkASSERT(ptsIdx == path.countPoints());
SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));
if (insideContour) {
currPathPrimitiveCounts += fGeometry.endContour();
}
fPathInfos.emplace_back(scissorTest, devToAtlasOffset);
// Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 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.
fPathInfos.back().tessellateFan(
fAlgorithm, path, fGeometry, currPathVerbsIdx, currPathPointsIdx, clippedDevIBounds,
&currPathPrimitiveCounts);
}
fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts;
if (GrScissorTest::kEnabled == scissorTest) {
fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled],
clippedDevIBounds.makeOffset(devToAtlasOffset)};
}
}
void GrCCFiller::PathInfo::tessellateFan(
Algorithm algorithm, const SkPath& originalPath, const GrCCFillGeometry& geometry,
int verbsIdx, int ptsIdx, const SkIRect& clippedDevIBounds,
PrimitiveTallies* newTriangleCounts) {
using Verb = GrCCFillGeometry::Verb;
SkASSERT(-1 == fFanTessellationCount);
SkASSERT(!fFanTessellation);
const SkTArray<Verb, true>& verbs = geometry.verbs();
const SkTArray<SkPoint, true>& pts = geometry.points();
newTriangleCounts->fTriangles =
newTriangleCounts->fWeightedTriangles = 0;
// Build an SkPath of the Redbook fan.
SkPath fan;
// When counting winding numbers in the stencil buffer, it works to use even/odd for the fan
// tessellation (where applicable). But we need to strip out inverse fill info because
// inverse-ness gets accounted for later on.
fan.setFillType(SkPathFillType_ConvertToNonInverse(originalPath.getFillType()));
SkASSERT(Verb::kBeginPath == verbs[verbsIdx]);
for (int i = verbsIdx + 1; i < verbs.count(); ++i) {
switch (verbs[i]) {
case Verb::kBeginPath:
SK_ABORT("Invalid GrCCFillGeometry");
continue;
case Verb::kBeginContour:
fan.moveTo(pts[ptsIdx++]);
continue;
case Verb::kLineTo:
fan.lineTo(pts[ptsIdx++]);
continue;
case Verb::kMonotonicQuadraticTo:
case Verb::kMonotonicConicTo:
fan.lineTo(pts[ptsIdx + 1]);
ptsIdx += 2;
continue;
case Verb::kMonotonicCubicTo:
fan.lineTo(pts[ptsIdx + 2]);
ptsIdx += 3;
continue;
case Verb::kEndClosedContour:
case Verb::kEndOpenContour:
fan.close();
continue;
}
}
GrTriangulator::WindingVertex* vertices = nullptr;
SkASSERT(!fan.isInverseFillType());
fFanTessellationCount = GrTriangulator::PathToVertices(
fan, std::numeric_limits<float>::infinity(), SkRect::Make(clippedDevIBounds),
&vertices);
if (fFanTessellationCount <= 0) {
SkASSERT(0 == fFanTessellationCount);
SkASSERT(nullptr == vertices);
return;
}
SkASSERT(0 == fFanTessellationCount % 3);
for (int i = 0; i < fFanTessellationCount; i += 3) {
int weight = abs(vertices[i].fWinding);
if (SkPathFillType::kEvenOdd == fan.getFillType()) {
// The tessellator doesn't wrap weights modulo 2 when we request even/odd fill type.
SkASSERT(weight & 1);
weight = 1;
}
newTriangleCounts->fTriangles += weight;
vertices[i].fWinding = weight;
}
fFanTessellation.reset(vertices);
}
GrCCFiller::BatchID GrCCFiller::closeCurrentBatch() {
SkASSERT(!fInstanceBuffer.hasGpuBuffer());
SkASSERT(!fBatches.empty());
const auto& lastBatch = fBatches.back();
int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
fMaxMeshesPerDraw = std::max(fMaxMeshesPerDraw, maxMeshes);
const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1];
PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled] -
lastBatch.fEndNonScissorIndices;
batchTotalCounts += fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled] -
lastScissorSubBatch.fEndPrimitiveIndices;
// This will invalidate lastBatch.
fBatches.push_back() = {
fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled],
fScissorSubBatches.count(),
batchTotalCounts
};
return fBatches.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& devToAtlasOffset, TriPointInstance::Ordering ordering,
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]], devToAtlasOffset, ordering);
out = emit_recursive_fan(
pts, indices, firstIndex, oneThirdCount + 1, devToAtlasOffset, ordering, out);
out = emit_recursive_fan(
pts, indices, firstIndex + oneThirdCount, twoThirdsCount - oneThirdCount + 1,
devToAtlasOffset, ordering, 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,
devToAtlasOffset, ordering, out);
indices[endIndex] = oldValue;
return out;
}
void GrCCFiller::emitTessellatedFan(
const GrTriangulator::WindingVertex* vertices, int numVertices,
const Sk2f& devToAtlasOffset, TriPointInstance::Ordering ordering,
TriPointInstance* triPointInstanceData, QuadPointInstance* quadPointInstanceData,
GrCCFillGeometry::PrimitiveTallies* indices) {
for (int i = 0; i < numVertices; i += 3) {
int weight = vertices[i].fWinding;
SkASSERT(weight >= 1);
for (int j = 0; j < weight; ++j) {
// Unfortunately, there is not a way to increment stencil values by an amount larger
// than 1. Instead we draw the triangle 'weight' times.
triPointInstanceData[indices->fTriangles++].set(
vertices[i].fPos, vertices[i + 1].fPos, vertices[i + 2].fPos, devToAtlasOffset,
ordering);
}
}
}
bool GrCCFiller::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
using Verb = GrCCFillGeometry::Verb;
SkASSERT(!fInstanceBuffer.hasGpuBuffer());
SkASSERT(fBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled]);
SkASSERT(fBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count());
auto triangleOrdering = TriPointInstance::Ordering::kXYInterleaved;
// 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 =
GrSizeDivRoundUp(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;
fBaseInstances[0].fConics = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
fBaseInstances[1].fConics = fBaseInstances[0].fConics + fTotalPrimitiveCounts[0].fConics;
int quadEndIdx = fBaseInstances[1].fConics + fTotalPrimitiveCounts[1].fConics;
fInstanceBuffer.resetAndMapBuffer(onFlushRP, quadEndIdx * sizeof(QuadPointInstance));
if (!fInstanceBuffer.hasGpuBuffer()) {
SkDebugf("WARNING: failed to allocate CCPR fill instance buffer.\n");
return false;
}
auto triPointInstanceData = reinterpret_cast<TriPointInstance*>(fInstanceBuffer.data());
auto quadPointInstanceData = reinterpret_cast<QuadPointInstance*>(fInstanceBuffer.data());
SkASSERT(quadPointInstanceData);
PathInfo* nextPathInfo = fPathInfos.begin();
Sk2f devToAtlasOffset;
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;
int nextConicWeightIdx = 0;
// Expand the ccpr verbs into GPU instance buffers.
for (Verb verb : fGeometry.verbs()) {
switch (verb) {
case Verb::kBeginPath:
SkASSERT(currFan.empty());
currIndices = &instanceIndices[(int)nextPathInfo->scissorTest()];
devToAtlasOffset = Sk2f(static_cast<float>(nextPathInfo->devToAtlasOffset().fX),
static_cast<float>(nextPathInfo->devToAtlasOffset().fY));
currFanIsTessellated = nextPathInfo->hasFanTessellation();
if (currFanIsTessellated) {
this->emitTessellatedFan(
nextPathInfo->fanTessellation(), nextPathInfo->fanTessellationCount(),
devToAtlasOffset, triangleOrdering, triPointInstanceData,
quadPointInstanceData, currIndices);
}
++nextPathInfo;
continue;
case Verb::kBeginContour:
SkASSERT(currFan.empty());
++ptsIdx;
if (!currFanIsTessellated) {
currFan.push_back(ptsIdx);
}
continue;
case Verb::kLineTo:
++ptsIdx;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case Verb::kMonotonicQuadraticTo:
triPointInstanceData[currIndices->fQuadratics++].set(
&pts[ptsIdx], devToAtlasOffset, TriPointInstance::Ordering::kXYTransposed);
ptsIdx += 2;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case Verb::kMonotonicCubicTo:
quadPointInstanceData[currIndices->fCubics++].set(
&pts[ptsIdx], devToAtlasOffset[0], devToAtlasOffset[1]);
ptsIdx += 3;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case Verb::kMonotonicConicTo:
quadPointInstanceData[currIndices->fConics++].setW(
&pts[ptsIdx], devToAtlasOffset,
fGeometry.getConicWeight(nextConicWeightIdx));
ptsIdx += 2;
++nextConicWeightIdx;
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.push_back(ptsIdx);
}
continue;
case Verb::kEndClosedContour: // endPt == startPt.
if (!currFanIsTessellated) {
SkASSERT(!currFan.empty());
currFan.pop_back();
}
[[fallthrough]];
case 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, devToAtlasOffset, triangleOrdering,
triPointInstanceData + currIndices->fTriangles);
currIndices->fTriangles += fanSize - 2;
SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
}
currFan.reset();
continue;
}
}
fInstanceBuffer.unmapBuffer();
SkASSERT(nextPathInfo == fPathInfos.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 == fBaseInstances[0].fConics);
SkASSERT(instanceIndices[0].fConics == fBaseInstances[1].fConics);
SkASSERT(instanceIndices[1].fConics == quadEndIdx);
return true;
}
void GrCCFiller::drawFills(
GrOpFlushState* flushState, GrCCCoverageProcessor* proc, const GrPipeline& pipeline,
BatchID batchID, const SkIRect& drawBounds, const GrUserStencilSettings* stencil) const {
using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
SkASSERT(fInstanceBuffer.hasGpuBuffer());
GrResourceProvider* rp = flushState->resourceProvider();
const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts;
int numSubpasses = proc->numSubpasses();
if (batchTotalCounts.fTriangles) {
for (int i = 0; i < numSubpasses; ++i) {
proc->reset(PrimitiveType::kTriangles, i, rp);
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
&PrimitiveTallies::fTriangles, drawBounds);
}
}
if (batchTotalCounts.fWeightedTriangles) {
SkASSERT(Algorithm::kStencilWindingCount != fAlgorithm);
for (int i = 0; i < numSubpasses; ++i) {
proc->reset(PrimitiveType::kWeightedTriangles, i, rp);
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
&PrimitiveTallies::fWeightedTriangles, drawBounds);
}
}
if (batchTotalCounts.fQuadratics) {
for (int i = 0; i < numSubpasses; ++i) {
proc->reset(PrimitiveType::kQuadratics, i, rp);
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
&PrimitiveTallies::fQuadratics, drawBounds);
}
}
if (batchTotalCounts.fCubics) {
for (int i = 0; i < numSubpasses; ++i) {
proc->reset(PrimitiveType::kCubics, i, rp);
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
&PrimitiveTallies::fCubics, drawBounds);
}
}
if (batchTotalCounts.fConics) {
for (int i = 0; i < numSubpasses; ++i) {
proc->reset(PrimitiveType::kConics, i, rp);
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
&PrimitiveTallies::fConics, drawBounds);
}
}
}
void GrCCFiller::drawPrimitives(
GrOpFlushState* flushState, const GrCCCoverageProcessor& proc, const GrPipeline& pipeline,
const GrUserStencilSettings* stencil, BatchID batchID, int PrimitiveTallies::*instanceType,
const SkIRect& drawBounds) const {
SkASSERT(pipeline.isScissorTestEnabled());
GrOpsRenderPass* renderPass = flushState->opsRenderPass();
proc.bindPipeline(flushState, pipeline, SkRect::Make(drawBounds), stencil);
proc.bindBuffers(renderPass, fInstanceBuffer.gpuBuffer());
SkASSERT(batchID > 0);
SkASSERT(batchID < fBatches.count());
const Batch& previousBatch = fBatches[batchID - 1];
const Batch& batch = fBatches[batchID];
SkDEBUGCODE(int totalInstanceCount = 0);
if (int instanceCount = batch.fEndNonScissorIndices.*instanceType -
previousBatch.fEndNonScissorIndices.*instanceType) {
SkASSERT(instanceCount > 0);
int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*instanceType +
previousBatch.fEndNonScissorIndices.*instanceType;
renderPass->setScissorRect(SkIRect::MakeXYWH(0, 0, drawBounds.width(),
drawBounds.height()));
proc.drawInstances(renderPass, instanceCount, baseInstance);
SkDEBUGCODE(totalInstanceCount += instanceCount);
}
SkASSERT(previousBatch.fEndScissorSubBatchIdx > 0);
SkASSERT(batch.fEndScissorSubBatchIdx <= fScissorSubBatches.count());
int baseScissorInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*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);
renderPass->setScissorRect(scissorSubBatch.fScissor);
proc.drawInstances(renderPass, instanceCount, baseScissorInstance + startIndex);
SkDEBUGCODE(totalInstanceCount += instanceCount);
}
SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
}

View File

@ -1,126 +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 GrCCPathParser_DEFINED
#define GrCCPathParser_DEFINED
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrTriangulator.h"
#include "src/gpu/ccpr/GrAutoMapVertexBuffer.h"
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
#include "src/gpu/ccpr/GrCCFillGeometry.h"
#include "src/gpu/ops/GrDrawOp.h"
class GrOnFlushResourceProvider;
class SkMatrix;
class SkPath;
/**
* This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their
* coverage counts.
*/
class GrCCFiller {
public:
enum class Algorithm : bool {
kStencilWindingCount
};
GrCCFiller(Algorithm, int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights);
// Parses a device-space SkPath into the current batch, using the SkPath's original verbs and
// 'deviceSpacePts'. Accepts an optional post-device-space translate for placement in an atlas.
void parseDeviceSpaceFill(const SkPath&, const SkPoint* deviceSpacePts, GrScissorTest,
const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset);
using BatchID = int;
// Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to
// draw their fills in the future.
BatchID closeCurrentBatch();
// Builds internal GPU buffers and prepares for calls to drawFills(). Caller must close the
// current batch before calling this method, and cannot parse new paths afer.
bool prepareToDraw(GrOnFlushResourceProvider*);
// Called after prepareToDraw(). Draws the given batch of path fills.
void drawFills(GrOpFlushState*, GrCCCoverageProcessor*, const GrPipeline&, BatchID,
const SkIRect& drawBounds,
const GrUserStencilSettings* = &GrUserStencilSettings::kUnused) const;
private:
static constexpr int kNumScissorModes = 2;
using PrimitiveTallies = GrCCFillGeometry::PrimitiveTallies;
// Every kBeginPath verb has a corresponding PathInfo entry.
class PathInfo {
public:
PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset)
: fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {}
GrScissorTest scissorTest() const { return fScissorTest; }
const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; }
// An empty tessellation fan is also valid; we use negative count to denote not tessellated.
bool hasFanTessellation() const { return fFanTessellationCount >= 0; }
int fanTessellationCount() const {
SkASSERT(this->hasFanTessellation());
return fFanTessellationCount;
}
const GrTriangulator::WindingVertex* fanTessellation() const {
SkASSERT(this->hasFanTessellation());
return fFanTessellation.get();
}
void tessellateFan(
Algorithm, const SkPath& originalPath, const GrCCFillGeometry&, int verbsIdx,
int ptsIdx, const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts);
private:
GrScissorTest fScissorTest;
SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas.
int fFanTessellationCount = -1;
std::unique_ptr<const GrTriangulator::WindingVertex[]> fFanTessellation;
};
// Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous
// Batch in the list.
struct Batch {
PrimitiveTallies fEndNonScissorIndices;
int fEndScissorSubBatchIdx;
PrimitiveTallies fTotalPrimitiveCounts;
};
// Defines a sub-batch that will be drawn with the given scissor rect. Start indices are deduced
// by looking at the previous ScissorSubBatch in the list.
struct ScissorSubBatch {
PrimitiveTallies fEndPrimitiveIndices;
SkIRect fScissor;
};
void emitTessellatedFan(
const GrTriangulator::WindingVertex*, int numVertices, const Sk2f& devToAtlasOffset,
GrCCCoverageProcessor::TriPointInstance::Ordering,
GrCCCoverageProcessor::TriPointInstance*, GrCCCoverageProcessor::QuadPointInstance*,
GrCCFillGeometry::PrimitiveTallies*);
void drawPrimitives(GrOpFlushState*, const GrCCCoverageProcessor&, const GrPipeline&,
const GrUserStencilSettings*, BatchID,
int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const;
const Algorithm fAlgorithm;
GrCCFillGeometry fGeometry;
SkSTArray<32, PathInfo, true> fPathInfos;
SkSTArray<32, Batch, true> fBatches;
SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches;
PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes];
int fMaxMeshesPerDraw = 0;
GrAutoMapVertexBuffer fInstanceBuffer;
PrimitiveTallies fBaseInstances[kNumScissorModes];
};
#endif

View File

@ -8,118 +8,23 @@
#include "src/gpu/ccpr/GrCCPerFlushResources.h"
#include "include/gpu/GrRecordingContext.h"
#include "src/gpu/GrFixedClip.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrOnFlushResourceProvider.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/ccpr/GrSampleMaskProcessor.h"
#include "src/gpu/geometry/GrStyledShape.h"
#include <algorithm>
using FillBatchID = GrCCFiller::BatchID;
namespace {
// Base class for an Op that renders a CCPR atlas.
class AtlasOp : public GrDrawOp {
public:
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
bool hasMixedSampledCoverage, GrClampType) override {
return GrProcessorSet::EmptySetAnalysis();
}
CombineResult onCombineIfPossible(GrOp* other, SkArenaAlloc*, const GrCaps&) override {
// We will only make multiple copy ops if they have different source proxies.
// TODO: make use of texture chaining.
return CombineResult::kCannotCombine;
}
protected:
AtlasOp(uint32_t classID, sk_sp<const GrCCPerFlushResources> resources,
const SkISize& drawBounds)
: GrDrawOp(classID)
, fResources(std::move(resources)) {
this->setBounds(SkRect::MakeIWH(drawBounds.width(), drawBounds.height()),
GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo);
}
const sk_sp<const GrCCPerFlushResources> fResources;
private:
void onPrePrepare(GrRecordingContext*,
const GrSurfaceProxyView& writeView,
GrAppliedClip*,
const GrXferProcessor::DstProxyView&,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) final {}
void onPrepare(GrOpFlushState*) final {}
};
// Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser.
template<typename ProcessorType> class RenderAtlasOp : public AtlasOp {
public:
DEFINE_OP_CLASS_ID
static GrOp::Owner Make(
GrRecordingContext* context, sk_sp<const GrCCPerFlushResources> resources,
FillBatchID fillBatchID, const SkISize& drawBounds) {
return GrOp::Make<RenderAtlasOp>(context,
std::move(resources), fillBatchID, drawBounds);
}
// GrDrawOp interface.
const char* name() const override { return "RenderAtlasOp (CCPR)"; }
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
ProcessorType proc;
GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus,
flushState->drawOpArgs().writeView().swizzle());
fResources->filler().drawFills(flushState, &proc, pipeline, fFillBatchID, fDrawBounds);
}
private:
friend class ::GrOp; // for ctor
RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
const SkISize& drawBounds)
: AtlasOp(ClassID(), std::move(resources), drawBounds)
, fFillBatchID(fillBatchID)
, fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
}
const FillBatchID fFillBatchID;
const SkIRect fDrawBounds;
};
} // namespace
#include "src/gpu/ops/GrFillRectOp.h"
GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
const GrCCPerFlushResourceSpecs& specs)
: fFiller(GrCCFiller::Algorithm::kStencilWindingCount,
specs.fNumClipPaths,
specs.fRenderedPathStats.fNumTotalSkPoints,
specs.fRenderedPathStats.fNumTotalSkVerbs,
specs.fRenderedPathStats.fNumTotalConicWeights)
, fRenderedAtlasStack(specs.fRenderedAtlasSpecs, onFlushRP->caps()) {
int numRenderedPaths = specs.fNumClipPaths;
fStencilResolveBuffer.resetAndMapBuffer(
onFlushRP, numRenderedPaths * sizeof(GrStencilAtlasOp::ResolveRectInstance));
if (!fStencilResolveBuffer.hasGpuBuffer()) {
SkDebugf("WARNING: failed to allocate CCPR stencil resolve buffer. "
"No paths will be drawn.\n");
return;
}
SkDEBUGCODE(fEndStencilResolveInstance = numRenderedPaths);
const GrCCAtlas::Specs& specs)
: fRenderedAtlasStack(specs, onFlushRP->caps()) {
}
const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
GrFillRule fillRule, SkIVector* devToAtlasOffset) {
SkASSERT(this->isMapped());
GrOnFlushResourceProvider* onFlushRP, const SkIRect& clipIBounds, const SkPath& devPath,
const SkIRect& devPathIBounds, GrFillRule fillRule, SkIVector* devToAtlasOffset) {
if (devPath.isEmpty()) {
SkDEBUGCODE(--fEndStencilResolveInstance);
return nullptr;
}
@ -132,87 +37,155 @@ const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
enableScissorInAtlas = GrScissorTest::kEnabled;
} else {
// The clip and path bounds do not intersect. Draw nothing.
SkDEBUGCODE(--fEndStencilResolveInstance);
return nullptr;
}
this->placeRenderedPathInAtlas(clippedPathIBounds, enableScissorInAtlas, devToAtlasOffset);
fFiller.parseDeviceSpaceFill(devPath, SkPathPriv::PointData(devPath), enableScissorInAtlas,
clippedPathIBounds, *devToAtlasOffset);
this->placeRenderedPathInAtlas(onFlushRP, clippedPathIBounds, enableScissorInAtlas,
devToAtlasOffset);
// In MSAA mode we also record an internal draw instance that will be used to resolve stencil
// winding values to coverage when the atlas is generated.
this->recordStencilResolveInstance(clippedPathIBounds, *devToAtlasOffset, fillRule);
SkMatrix atlasMatrix = SkMatrix::Translate(devToAtlasOffset->fX, devToAtlasOffset->fY);
this->enqueueRenderedPath(devPath, fillRule, clippedPathIBounds, atlasMatrix,
enableScissorInAtlas, *devToAtlasOffset);
return &fRenderedAtlasStack.current();
}
void GrCCPerFlushResources::placeRenderedPathInAtlas(
const SkIRect& clippedPathIBounds, GrScissorTest scissorTest, SkIVector* devToAtlasOffset) {
GrOnFlushResourceProvider* onFlushRP, const SkIRect& clippedPathIBounds,
GrScissorTest scissorTest, SkIVector* devToAtlasOffset) {
if (GrCCAtlas* retiredAtlas =
fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
// We did not fit in the previous coverage count atlas and it was retired. Close the path
// parser's current batch (which does not yet include the path we just parsed). We will
// render this batch into the retired atlas during finalize().
retiredAtlas->setFillBatchID(fFiller.closeCurrentBatch());
retiredAtlas->setEndStencilResolveInstance(fNextStencilResolveInstanceIdx);
// We did not fit in the previous coverage count atlas and it was retired. Render the
// retired atlas.
this->flushRenderedPaths(onFlushRP, retiredAtlas);
}
}
void GrCCPerFlushResources::recordStencilResolveInstance(
const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule fillRule) {
SkASSERT(fNextStencilResolveInstanceIdx < fEndStencilResolveInstance);
SkIRect atlasIBounds = clippedPathIBounds.makeOffset(devToAtlasOffset);
if (GrFillRule::kEvenOdd == fillRule) {
// Make even/odd fills counterclockwise. The resolve draw uses two-sided stencil, with
// "nonzero" settings in front and "even/odd" settings in back.
std::swap(atlasIBounds.fLeft, atlasIBounds.fRight);
void GrCCPerFlushResources::enqueueRenderedPath(const SkPath& path, GrFillRule fillRule,
const SkIRect& clippedDevIBounds,
const SkMatrix& pathToAtlasMatrix,
GrScissorTest enableScissorInAtlas,
SkIVector devToAtlasOffset) {
SkPath* atlasPath;
if (enableScissorInAtlas == GrScissorTest::kDisabled) {
atlasPath = &fAtlasPaths[(int)fillRule].fUberPath;
} else {
auto& [scissoredPath, scissor] = fAtlasPaths[(int)fillRule].fScissoredPaths.push_back();
scissor = clippedDevIBounds.makeOffset(devToAtlasOffset);
atlasPath = &scissoredPath;
}
auto origin = clippedDevIBounds.topLeft() + devToAtlasOffset;
atlasPath->moveTo(origin.fX, origin.fY); // Implicit moveTo(0,0).
atlasPath->addPath(path, pathToAtlasMatrix);
}
static void draw_stencil_to_coverage(GrOnFlushResourceProvider* onFlushRP,
GrSurfaceDrawContext* surfaceDrawContext, SkRect&& rect) {
auto aaType = GrAAType::kMSAA;
auto fillRectFlags = GrSimpleMeshDrawOpHelper::InputFlags::kNone;
// This will be the final op in the surfaceDrawContext. So if Ganesh is planning to discard the
// stencil values anyway, then we might not actually need to reset the stencil values back to 0.
bool mustResetStencil = !onFlushRP->caps()->discardStencilValuesAfterRenderPass();
if (surfaceDrawContext->numSamples() == 1) {
// We are mixed sampled. We need to either enable conservative raster (preferred) or disable
// MSAA in order to avoid double blend artifacts. (Even if we disable MSAA for the cover
// geometry, the stencil test is still multisampled and will still produce smooth results.)
if (onFlushRP->caps()->conservativeRasterSupport()) {
fillRectFlags |= GrSimpleMeshDrawOpHelper::InputFlags::kConservativeRaster;
} else {
aaType = GrAAType::kNone;
}
mustResetStencil = true;
}
const GrUserStencilSettings* stencil;
if (mustResetStencil) {
constexpr static GrUserStencilSettings kTestAndResetStencil(
GrUserStencilSettings::StaticInit<
0x0000,
GrUserStencilTest::kNotEqual,
0xffff,
GrUserStencilOp::kZero,
GrUserStencilOp::kKeep,
0xffff>());
// Outset the cover rect in case there are T-junctions in the path bounds.
rect.outset(1, 1);
stencil = &kTestAndResetStencil;
} else {
constexpr static GrUserStencilSettings kTestStencil(
GrUserStencilSettings::StaticInit<
0x0000,
GrUserStencilTest::kNotEqual,
0xffff,
GrUserStencilOp::kKeep,
GrUserStencilOp::kKeep,
0xffff>());
stencil = &kTestStencil;
}
GrPaint paint;
paint.setColor4f(SK_PMColor4fWHITE);
GrQuad coverQuad(rect);
DrawQuad drawQuad{coverQuad, coverQuad, GrQuadAAFlags::kAll};
auto coverOp = GrFillRectOp::Make(surfaceDrawContext->recordingContext(), std::move(paint),
aaType, &drawQuad, stencil, fillRectFlags);
surfaceDrawContext->addDrawOp(nullptr, std::move(coverOp));
}
void GrCCPerFlushResources::flushRenderedPaths(GrOnFlushResourceProvider* onFlushRP,
GrCCAtlas* atlas) {
auto surfaceDrawContext = atlas->instantiate(onFlushRP);
if (!surfaceDrawContext) {
for (int i = 0; i < (int)SK_ARRAY_COUNT(fAtlasPaths); ++i) {
fAtlasPaths[i].fUberPath.reset();
fAtlasPaths[i].fScissoredPaths.reset();
}
return;
}
for (int i = 0; i < (int)SK_ARRAY_COUNT(fAtlasPaths); ++i) {
SkPathFillType fillType = (i == (int)GrFillRule::kNonzero) ? SkPathFillType::kWinding
: SkPathFillType::kEvenOdd;
SkPath& uberPath = fAtlasPaths[i].fUberPath;
if (!uberPath.isEmpty()) {
uberPath.setIsVolatile(true);
uberPath.setFillType(fillType);
surfaceDrawContext->stencilPath(nullptr, GrAA::kYes, SkMatrix::I(), uberPath);
uberPath.reset();
}
for (auto& [scissoredPath, scissor] : fAtlasPaths[i].fScissoredPaths) {
GrFixedClip fixedClip(
surfaceDrawContext->asRenderTargetProxy()->backingStoreDimensions(), scissor);
scissoredPath.setIsVolatile(true);
scissoredPath.setFillType(fillType);
surfaceDrawContext->stencilPath(&fixedClip, GrAA::kYes, SkMatrix::I(), scissoredPath);
}
fAtlasPaths[i].fScissoredPaths.reset();
}
draw_stencil_to_coverage(onFlushRP, surfaceDrawContext.get(),
SkRect::MakeSize(SkSize::Make(atlas->drawBounds())));
if (surfaceDrawContext->asSurfaceProxy()->requiresManualMSAAResolve()) {
onFlushRP->addTextureResolveTask(sk_ref_sp(surfaceDrawContext->asTextureProxy()),
GrSurfaceProxy::ResolveFlags::kMSAA);
}
fStencilResolveBuffer[fNextStencilResolveInstanceIdx++] = {
(int16_t)atlasIBounds.left(), (int16_t)atlasIBounds.top(),
(int16_t)atlasIBounds.right(), (int16_t)atlasIBounds.bottom()};
}
bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP) {
SkASSERT(this->isMapped());
SkASSERT(fNextStencilResolveInstanceIdx == fEndStencilResolveInstance);
if (fStencilResolveBuffer.hasGpuBuffer()) {
fStencilResolveBuffer.unmapBuffer();
}
if (!fRenderedAtlasStack.empty()) {
fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
fRenderedAtlasStack.current().setEndStencilResolveInstance(fNextStencilResolveInstanceIdx);
this->flushRenderedPaths(onFlushRP, &fRenderedAtlasStack.current());
}
// Build the GPU buffers to render path coverage counts. (This must not happen until after the
// final calls to fFiller/fStroker.closeCurrentBatch().)
if (!fFiller.prepareToDraw(onFlushRP)) {
return false;
#ifdef SK_DEBUG
// These paths should have been rendered and reset to empty by this point.
for (const auto& [uberPath, scissoredPaths] : fAtlasPaths) {
SkASSERT(uberPath.isEmpty());
SkASSERT(scissoredPaths.empty());
}
// Render the coverage count atlas(es).
int baseStencilResolveInstance = 0;
for (GrCCAtlas& atlas : fRenderedAtlasStack.atlases()) {
if (auto rtc = atlas.instantiate(onFlushRP)) {
GrOp::Owner op;
op = GrStencilAtlasOp::Make(
rtc->recordingContext(), sk_ref_sp(this), atlas.getFillBatchID(),
baseStencilResolveInstance,
atlas.getEndStencilResolveInstance(), atlas.drawBounds());
rtc->addDrawOp(nullptr, std::move(op));
if (rtc->asSurfaceProxy()->requiresManualMSAAResolve()) {
onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->asTextureProxy()),
GrSurfaceProxy::ResolveFlags::kMSAA);
}
}
SkASSERT(atlas.getEndStencilResolveInstance() >= baseStencilResolveInstance);
baseStencilResolveInstance = atlas.getEndStencilResolveInstance();
}
SkASSERT(baseStencilResolveInstance == fEndStencilResolveInstance);
#endif
return true;
}

View File

@ -9,42 +9,9 @@
#define GrCCPerFlushResources_DEFINED
#include "src/gpu/GrNonAtomicRef.h"
#include "src/gpu/ccpr/GrAutoMapVertexBuffer.h"
#include "src/gpu/ccpr/GrCCAtlas.h"
#include "src/gpu/ccpr/GrCCFiller.h"
#include "src/gpu/ccpr/GrStencilAtlasOp.h"
class GrCCPathCache;
class GrCCPathCacheEntry;
class GrOctoBounds;
class GrOnFlushResourceProvider;
class GrStyledShape;
/**
* This struct counts values that help us preallocate buffers for rendered path geometry.
*/
struct GrCCRenderedPathStats {
int fMaxPointsPerPath = 0;
int fNumTotalSkPoints = 0;
int fNumTotalSkVerbs = 0;
int fNumTotalConicWeights = 0;
void statPath(const SkPath&);
};
/**
* This struct encapsulates the minimum and desired requirements for the GPU resources required by
* CCPR in a given flush.
*/
struct GrCCPerFlushResourceSpecs {
int fNumClipPaths = 0;
GrCCRenderedPathStats fRenderedPathStats;
GrCCAtlas::Specs fRenderedAtlasSpecs;
bool isEmpty() const {
return 0 == fNumClipPaths;
}
};
/**
* This class wraps all the GPU resources that CCPR builds at flush time. It is allocated in CCPR's
@ -53,57 +20,39 @@ struct GrCCPerFlushResourceSpecs {
*/
class GrCCPerFlushResources : public GrNonAtomicRef<GrCCPerFlushResources> {
public:
GrCCPerFlushResources(
GrOnFlushResourceProvider*,const GrCCPerFlushResourceSpecs&);
bool isMapped() const { return fStencilResolveBuffer.isMapped(); }
GrCCPerFlushResources(GrOnFlushResourceProvider*, const GrCCAtlas::Specs&);
// Renders a path into an atlas.
const GrCCAtlas* renderDeviceSpacePathInAtlas(
const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
GrFillRule fillRule, SkIVector* devToAtlasOffset);
GrOnFlushResourceProvider*, const SkIRect& clipIBounds, const SkPath& devPath,
const SkIRect& devPathIBounds, GrFillRule fillRule, SkIVector* devToAtlasOffset);
// Finishes off the GPU buffers and renders the atlas(es).
bool finalize(GrOnFlushResourceProvider*);
// Accessors used by draw calls, once the resources have been finalized.
const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
sk_sp<const GrGpuBuffer> stencilResolveBuffer() const {
SkASSERT(!this->isMapped());
return fStencilResolveBuffer.gpuBuffer();
}
private:
void placeRenderedPathInAtlas(
const SkIRect& clippedPathIBounds, GrScissorTest, SkIVector* devToAtlasOffset);
GrOnFlushResourceProvider*, const SkIRect& clippedPathIBounds, GrScissorTest,
SkIVector* devToAtlasOffset);
// In MSAA mode we record an additional instance per path that draws a rectangle on top of its
// corresponding path in the atlas and resolves stencil winding values to coverage.
void recordStencilResolveInstance(
const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule);
// Enqueues the given path to be rendered during the next call to flushRenderedPaths().
void enqueueRenderedPath(const SkPath&, GrFillRule, const SkIRect& clippedDevIBounds,
const SkMatrix& pathToAtlasMatrix, GrScissorTest enableScissorInAtlas,
SkIVector devToAtlasOffset);
// Renders all enqueued paths into the given atlas and clears our path queue.
void flushRenderedPaths(GrOnFlushResourceProvider*, GrCCAtlas*);
GrCCFiller fFiller;
GrCCAtlasStack fRenderedAtlasStack;
// Used in MSAA mode make an intermediate draw that resolves stencil winding values to coverage.
GrTAutoMapVertexBuffer<GrStencilAtlasOp::ResolveRectInstance> fStencilResolveBuffer;
int fNextStencilResolveInstanceIdx = 0;
SkDEBUGCODE(int fEndStencilResolveInstance);
public:
#ifdef SK_DEBUG
void debugOnly_didReuseRenderedPath() {
--fEndStencilResolveInstance;
}
#endif
const GrTexture* testingOnly_frontRenderedAtlasTexture() const;
// Paths to be rendered in the atlas we are currently building.
struct AtlasPaths {
SkPath fUberPath; // Contains all contours from all non-scissored paths.
SkSTArray<32, std::tuple<SkPath, SkIRect>> fScissoredPaths;
};
static_assert((int)GrFillRule::kNonzero == 0);
static_assert((int)GrFillRule::kEvenOdd == 1);
AtlasPaths fAtlasPaths[2]; // One for "nonzero" fill rule and one for "even-odd".
};
inline void GrCCRenderedPathStats::statPath(const SkPath& path) {
fMaxPointsPerPath = std::max(fMaxPointsPerPath, path.countPoints());
fNumTotalSkPoints += path.countPoints();
fNumTotalSkVerbs += path.countVerbs();
fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
}
#endif

View File

@ -23,8 +23,6 @@ class GrCCPerFlushResources;
// DDL TODO: given the usage pattern in DDL mode, this could probably be non-atomic refcounting.
struct GrCCPerOpsTaskPaths : public SkRefCnt {
std::map<uint32_t, GrCCClipPath> fClipPaths;
SkSTArenaAlloc<10 * 1024> fAllocator{10 * 1024 * 2};
sk_sp<const GrCCPerFlushResources> fFlushResources;
};
#endif

View File

@ -1,51 +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 "src/gpu/ccpr/GrCCQuadraticShader.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
void GrCCQuadraticShader::emitSetupCode(
GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const {
s->declareGlobal(fQCoordMatrix);
s->codeAppendf("%s = float2x2(1, 1, .5, 0) * inverse(float2x2(%s[2] - %s[0], %s[1] - %s[0]));",
fQCoordMatrix.c_str(), pts, pts, pts, pts);
s->declareGlobal(fQCoord0);
s->codeAppendf("%s = %s[0];", fQCoord0.c_str(), pts);
if (outHull4) {
// Clip the bezier triangle by the tangent line at maximum height. Quadratics have the nice
// property that maximum height always occurs at T=.5. This is a simple application for
// De Casteljau's algorithm.
s->codeAppend ("float2 quadratic_hull[4];");
s->codeAppendf("quadratic_hull[0] = %s[0];", pts);
s->codeAppendf("quadratic_hull[1] = (%s[0] + %s[1]) * .5;", pts, pts);
s->codeAppendf("quadratic_hull[2] = (%s[1] + %s[2]) * .5;", pts, pts);
s->codeAppendf("quadratic_hull[3] = %s[2];", pts);
*outHull4 = "quadratic_hull";
}
}
void GrCCQuadraticShader::onEmitVaryings(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
fCoord_fGrad.reset(kFloat4_GrSLType, scope);
varyingHandler->addVarying("coord_and_grad", &fCoord_fGrad);
code->appendf("%s.xy = %s * (%s - %s);", // Quadratic coords.
OutName(fCoord_fGrad), fQCoordMatrix.c_str(), position, fQCoord0.c_str());
code->appendf("%s.zw = 2*bloat * float2(2 * %s.x, -1) * %s;", // Gradient.
OutName(fCoord_fGrad), OutName(fCoord_fGrad), fQCoordMatrix.c_str());
}
void GrCCQuadraticShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
f->codeAppendf("float x = %s.x, y = %s.y;", fCoord_fGrad.fsIn(), fCoord_fGrad.fsIn());
f->codeAppendf("float f = x*x - y;");
f->codeAppendf("float2 grad = %s.zw;", fCoord_fGrad.fsIn());
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
}

View File

@ -1,41 +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 GrCCQuadraticShader_DEFINED
#define GrCCQuadraticShader_DEFINED
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
/**
* This class renders the coverage of closed quadratic curves using the techniques outlined in
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
* Jim Blinn:
*
* 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 GrCCGeometry::quadraticTo().)
*/
class GrCCQuadraticShader : public GrCCCoverageProcessor::Shader {
public:
void emitSetupCode(
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
void onEmitVaryings(
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
const char* coverage, const char* cornerCoverage, const char* wind) override;
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
private:
const GrShaderVar fQCoordMatrix{"qcoord_matrix", kFloat2x2_GrSLType};
const GrShaderVar fQCoord0{"qcoord0", kFloat2_GrSLType};
GrGLSLVarying fCoord_fGrad;
GrGLSLVarying fEdge_fWind_fCorner;
};
#endif

View File

@ -101,10 +101,10 @@ void GrCoverageCountingPathRenderer::preFlush(
return; // Nothing to draw.
}
GrCCPerFlushResourceSpecs specs;
GrCCAtlas::Specs specs;
int maxPreferredRTSize = onFlushRP->caps()->maxPreferredRenderTargetSize();
specs.fRenderedAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize;
specs.fRenderedAtlasSpecs.fMinTextureSize = std::min(512, maxPreferredRTSize);
specs.fMaxPreferredTextureSize = maxPreferredRTSize;
specs.fMinTextureSize = std::min(512, maxPreferredRTSize);
// Move the per-opsTask paths that are about to be flushed from fPendingPaths to fFlushingPaths,
// and count them up so we can preallocate buffers.
@ -123,45 +123,26 @@ void GrCoverageCountingPathRenderer::preFlush(
}
}
if (specs.isEmpty()) {
return; // Nothing to draw.
}
fPerFlushResources = std::make_unique<GrCCPerFlushResources>(onFlushRP, specs);
auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
if (!resources->isMapped()) {
return; // Some allocation failed.
}
// Layout the atlas(es) and parse paths.
// Layout the atlas(es) and render paths.
for (const auto& flushingPaths : fFlushingPaths) {
for (auto& clipsIter : flushingPaths->fClipPaths) {
clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
clipsIter.second.renderPathInAtlas(fPerFlushResources.get(), onFlushRP);
}
}
// Allocate resources and then render the atlas(es).
if (!resources->finalize(onFlushRP)) {
return;
}
// Commit flushing paths to the resources once they are successfully completed.
for (auto& flushingPaths : fFlushingPaths) {
SkASSERT(!flushingPaths->fFlushResources);
flushingPaths->fFlushResources = resources;
}
fPerFlushResources->finalize(onFlushRP);
}
void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken,
SkSpan<const uint32_t> /* taskIDs */) {
SkASSERT(fFlushing);
if (!fFlushingPaths.empty()) {
// In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush
// resources manually.
for (auto& flushingPaths : fFlushingPaths) {
flushingPaths->fFlushResources = nullptr;
}
fPerFlushResources.reset();
if (!fFlushingPaths.empty()) {
// We wait to erase these until after flush, once Ops and FPs are done accessing their data.
fFlushingPaths.reset();
}

View File

@ -83,10 +83,9 @@ private:
// (It will only contain elements when fFlushing is true.)
SkSTArray<4, sk_sp<GrCCPerOpsTaskPaths>> fFlushingPaths;
SkDEBUGCODE(bool fFlushing = false);
std::unique_ptr<GrCCPerFlushResources> fPerFlushResources;
public:
const GrCCPerFlushResources* testingOnly_getCurrentFlushResources();
SkDEBUGCODE(bool fFlushing = false);
};
#endif

View File

@ -1,151 +0,0 @@
/*
* Copyright 2019 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ccpr/GrSampleMaskProcessor.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
class GrSampleMaskProcessor::Impl : public GrGLSLGeometryProcessor {
public:
Impl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
private:
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
void onEmitCode(EmitArgs&, GrGPArgs*) override;
const std::unique_ptr<Shader> fShader;
};
void GrSampleMaskProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
const GrSampleMaskProcessor& proc = args.fGP.cast<GrSampleMaskProcessor>();
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLVertexBuilder* v = args.fVertBuilder;
int numInputPoints = proc.numInputPoints();
int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
varyingHandler->emitAttributes(proc);
SkASSERT(!*args.fFPCoordTransformHandler);
if (PrimitiveType::kTriangles == proc.fPrimitiveType) {
SkASSERT(!proc.hasInstanceAttributes()); // Triangles are drawn with vertex arrays.
gpArgs->fPositionVar = proc.fInputAttribs.front().asShaderVar();
} else {
SkASSERT(!proc.hasVertexAttributes()); // Curves are drawn with instanced rendering.
// Shaders expect a global "bloat" variable when calculating gradients.
v->defineConstant("half", "bloat", ".5");
const char* swizzle = (4 == numInputPoints || proc.hasInputWeight()) ? "xyzw" : "xyz";
v->codeAppendf("float%ix2 pts = transpose(float2x%i(X.%s, Y.%s));",
inputWidth, inputWidth, swizzle, swizzle);
const char* hullPts = "pts";
fShader->emitSetupCode(v, "pts", &hullPts);
v->codeAppendf("float2 vertexpos = %s[sk_VertexID ^ (sk_VertexID >> 1)];", hullPts);
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag,
&AccessCodeString(v), "vertexpos", nullptr, nullptr, nullptr);
}
// Fragment shader.
fShader->emitSampleMaskCode(args.fFragBuilder);
}
void GrSampleMaskProcessor::reset(PrimitiveType primitiveType, int subpassIdx,
GrResourceProvider* rp) {
SkASSERT(subpassIdx == 0);
fPrimitiveType = primitiveType; // This will affect the return values for numInputPoints, etc.
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
this->resetCustomFeatures();
fInputAttribs.reset();
switch (fPrimitiveType) {
case PrimitiveType::kTriangles:
case PrimitiveType::kWeightedTriangles:
fInputAttribs.emplace_back("point", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
this->setVertexAttributes(fInputAttribs.begin(), 1);
this->setInstanceAttributes(nullptr, 0);
break;
case PrimitiveType::kQuadratics:
case PrimitiveType::kCubics:
case PrimitiveType::kConics: {
auto instanceAttribType = (PrimitiveType::kQuadratics == fPrimitiveType)
? kFloat3_GrVertexAttribType : kFloat4_GrVertexAttribType;
auto shaderVarType = (PrimitiveType::kQuadratics == fPrimitiveType)
? kFloat3_GrSLType : kFloat4_GrSLType;
fInputAttribs.emplace_back("X", instanceAttribType, shaderVarType);
fInputAttribs.emplace_back("Y", instanceAttribType, shaderVarType);
this->setVertexAttributes(nullptr, 0);
this->setInstanceAttributes(fInputAttribs.begin(), fInputAttribs.count());
this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
break;
}
}
}
GrPrimitiveType GrSampleMaskProcessor::primType() const {
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
switch (fPrimitiveType) {
case PrimitiveType::kTriangles:
case PrimitiveType::kWeightedTriangles:
return GrPrimitiveType::kTriangles;
case PrimitiveType::kQuadratics:
case PrimitiveType::kCubics:
case PrimitiveType::kConics:
return GrPrimitiveType::kTriangleStrip;
default:
return GrPrimitiveType::kTriangleStrip;
}
}
void GrSampleMaskProcessor::bindBuffers(GrOpsRenderPass* renderPass,
sk_sp<const GrBuffer> instanceBuffer) const {
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
switch (fPrimitiveType) {
case PrimitiveType::kTriangles:
case PrimitiveType::kWeightedTriangles: {
renderPass->bindBuffers(nullptr, nullptr, std::move(instanceBuffer));
break;
}
case PrimitiveType::kQuadratics:
case PrimitiveType::kCubics:
case PrimitiveType::kConics: {
renderPass->bindBuffers(nullptr, std::move(instanceBuffer), nullptr);
break;
}
}
}
void GrSampleMaskProcessor::drawInstances(GrOpsRenderPass* renderPass, int instanceCount,
int baseInstance) const {
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
switch (fPrimitiveType) {
case PrimitiveType::kTriangles:
case PrimitiveType::kWeightedTriangles: {
renderPass->draw(instanceCount * 3, baseInstance * 3);
break;
}
case PrimitiveType::kQuadratics:
case PrimitiveType::kCubics:
case PrimitiveType::kConics: {
renderPass->drawInstanced(instanceCount, baseInstance, 4, 0);
break;
}
}
}
GrGLSLPrimitiveProcessor* GrSampleMaskProcessor::onCreateGLSLInstance(
std::unique_ptr<Shader> shader) const {
return new Impl(std::move(shader));
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2019 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrSampleMaskProcessor_DEFINED
#define GrSampleMaskProcessor_DEFINED
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
/**
* This class implements GrCCCoverageProcessor with MSAA using the sample mask.
*/
class GrSampleMaskProcessor : public GrCCCoverageProcessor {
public:
GrSampleMaskProcessor() : GrCCCoverageProcessor(kGrSampleMaskProcessor_ClassID) {}
private:
GrPrimitiveType primType() const final;
int numSubpasses() const override { return 1; }
void reset(PrimitiveType, int subpassIdx, GrResourceProvider*) override;
void bindBuffers(GrOpsRenderPass*, sk_sp<const GrBuffer> instanceBuffer) const override;
void drawInstances(GrOpsRenderPass*, int instanceCount, int baseInstance) const override;
GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const override;
SkSTArray<2, Attribute> fInputAttribs;
class Impl;
};
#endif

View File

@ -1,191 +0,0 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ccpr/GrStencilAtlasOp.h"
#include "include/gpu/GrRecordingContext.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/ccpr/GrCCPerFlushResources.h"
#include "src/gpu/ccpr/GrSampleMaskProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
namespace {
class StencilResolveProcessor : public GrGeometryProcessor {
public:
StencilResolveProcessor() : INHERITED(kStencilResolveProcessor_ClassID) {
static constexpr Attribute kIBounds = {
"ibounds", kShort4_GrVertexAttribType, kShort4_GrSLType};
this->setInstanceAttributes(&kIBounds, 1);
SkASSERT(this->instanceStride() == sizeof(GrStencilAtlasOp::ResolveRectInstance));
}
private:
const char* name() const final { return "StencilResolveProcessor"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
class Impl;
using INHERITED = GrGeometryProcessor;
};
// This processor draws pixel-aligned rectangles directly on top of every path in the atlas.
// The caller should have set up the instance data such that "Nonzero" paths get clockwise
// rectangles (l < r) and "even/odd" paths get counter-clockwise (r < l). Its purpose
// is to convert winding counts in the stencil buffer to A8 coverage in the color buffer.
class StencilResolveProcessor::Impl : public GrGLSLGeometryProcessor {
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
args.fVaryingHandler->emitAttributes(args.fGP.cast<StencilResolveProcessor>());
GrGLSLVertexBuilder* v = args.fVertBuilder;
v->codeAppendf("short2 devcoord;");
v->codeAppendf("devcoord.x = (0 == (sk_VertexID & 1)) ? ibounds.x : ibounds.z;");
v->codeAppendf("devcoord.y = (sk_VertexID < 2) ? ibounds.y : ibounds.w;");
v->codeAppendf("float2 atlascoord = float2(devcoord);");
gpArgs->fPositionVar.set(kFloat2_GrSLType, "atlascoord");
// Just output "1" for coverage. This will be modulated by the MSAA stencil test.
GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
f->codeAppendf("const half4 %s = half4(1), %s = half4(1);",
args.fOutputColor, args.fOutputCoverage);
}
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
};
GrGLSLPrimitiveProcessor* StencilResolveProcessor::createGLSLInstance(const GrShaderCaps&) const {
return new Impl();
}
} // namespace
GrOp::Owner GrStencilAtlasOp::Make(
GrRecordingContext* context, sk_sp<const GrCCPerFlushResources> resources,
FillBatchID fillBatchID, int baseStencilResolveInstance,
int endStencilResolveInstance, const SkISize& drawBounds) {
return GrOp::Make<GrStencilAtlasOp>(
context, std::move(resources), fillBatchID, baseStencilResolveInstance,
endStencilResolveInstance, drawBounds);
}
// Increments clockwise triangles and decrements counterclockwise. We use the same incr/decr
// settings regardless of fill rule; fill rule is accounted for during the resolve step.
static constexpr GrUserStencilSettings kIncrDecrStencil(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0000,
GrUserStencilTest::kNever, GrUserStencilTest::kNever,
0xffff, 0xffff,
GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
0xffff, 0xffff>()
);
// Resolves stencil winding counts to A8 coverage. Leaves stencil values untouched.
// NOTE: For the CCW face we intentionally use "1 == (stencil & 1)" because the contrapositive logic
// (i.e. 0 != ...) causes bugs on Adreno Vulkan. http://skbug.com/9643
static constexpr GrUserStencilSettings kResolveStencilCoverage(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0001,
GrUserStencilTest::kNotEqual, GrUserStencilTest::kEqual,
0xffff, 0x0001,
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
0xffff, 0xffff>()
);
// Same as above, but also resets stencil values to zero. This is better for non-tilers
// where we prefer to not clear the stencil buffer at the beginning of every render pass.
static constexpr GrUserStencilSettings kResolveStencilCoverageAndReset(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0000,
GrUserStencilTest::kNotEqual, GrUserStencilTest::kNotEqual,
0xffff, 0x0001,
GrUserStencilOp::kZero, GrUserStencilOp::kZero,
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
0xffff, 0xffff>()
);
// Same as above, but done in two passes for D3D, which doesn't support mismatched refs or masks on
// dual sided stencil settings.
static constexpr GrUserStencilSettings kResolveWindingCoverageAndReset(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0000,
GrUserStencilTest::kNotEqual, GrUserStencilTest::kNever,
0xffff, 0xffff,
GrUserStencilOp::kZero, GrUserStencilOp::kKeep,
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
0xffff, 0xffff>()
);
static constexpr GrUserStencilSettings kResolveEvenOddCoverageAndReset(
GrUserStencilSettings::StaticInitSeparate<
0x0000, 0x0000,
GrUserStencilTest::kNever, GrUserStencilTest::kNotEqual,
0x0001, 0x0001,
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
0xffff, 0xffff>()
);
void GrStencilAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
SkIRect drawBoundsRect = SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height());
GrPipeline pipeline(GrScissorTest::kEnabled, GrDisableColorXPFactory::MakeXferProcessor(),
flushState->drawOpArgs().writeView().swizzle(),
GrPipeline::InputFlags::kHWAntialias);
GrSampleMaskProcessor sampleMaskProc;
fResources->filler().drawFills(
flushState, &sampleMaskProc, pipeline, fFillBatchID, drawBoundsRect, &kIncrDecrStencil);
// We resolve the stencil coverage to alpha by drawing pixel-aligned boxes. Fine raster is
// not necessary, and will even cause artifacts if using mixed samples.
constexpr auto noHWAA = GrPipeline::InputFlags::kNone;
GrPipeline resolvePipeline(GrScissorTest::kEnabled, SkBlendMode::kSrc,
flushState->drawOpArgs().writeView().swizzle(), noHWAA);
StencilResolveProcessor primProc;
if (!flushState->caps().twoSidedStencilRefsAndMasksMustMatch()) {
const GrUserStencilSettings* stencil =
(flushState->caps().discardStencilValuesAfterRenderPass()) ?
&kResolveStencilCoverage : &kResolveStencilCoverageAndReset;
this->drawResolve(flushState, resolvePipeline, stencil, primProc, drawBoundsRect);
return;
}
// If this ever becomes true then we should add new per-fill-type stencil settings that also
// don't reset back to zero.
SkASSERT(!flushState->caps().discardStencilValuesAfterRenderPass());
this->drawResolve(flushState, resolvePipeline, &kResolveWindingCoverageAndReset, primProc,
drawBoundsRect);
this->drawResolve(flushState, resolvePipeline, &kResolveEvenOddCoverageAndReset, primProc,
drawBoundsRect);
}
void GrStencilAtlasOp::drawResolve(GrOpFlushState* flushState, const GrPipeline& resolvePipeline,
const GrUserStencilSettings* stencil,
const GrPrimitiveProcessor& primProc,
const SkIRect& drawBounds) const {
GrProgramInfo programInfo(flushState->writeView(), &resolvePipeline, stencil,
&primProc, GrPrimitiveType::kTriangleStrip, 0,
flushState->renderPassBarriers(),
flushState->colorLoadOp());
flushState->bindPipeline(programInfo, SkRect::Make(drawBounds));
flushState->setScissorRect(drawBounds);
flushState->bindBuffers(nullptr, fResources->stencilResolveBuffer(), nullptr);
flushState->drawInstanced(fEndStencilResolveInstance - fBaseStencilResolveInstance,
fBaseStencilResolveInstance, 4, 0);
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 2019 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrStencilAtlasOp_DEFINED
#define GrStencilAtlasOp_DEFINED
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/ccpr/GrCCFiller.h"
#include "src/gpu/ops/GrDrawOp.h"
class GrCCPerFlushResources;
// Renders literal A8 coverage to a CCPR atlas using an intermediate MSAA stencil buffer.
class GrStencilAtlasOp : public GrDrawOp {
public:
DEFINE_OP_CLASS_ID
using FillBatchID = GrCCFiller::BatchID;
// Once all the paths in an atlas have been drawn to the stencil buffer, we make a final pass
// where we draw "resolve" rects over each path whose purpose is to convert winding counts to A8
// coverage.
struct ResolveRectInstance {
int16_t l, t, r, b;
};
// GrDrawOp interface.
const char* name() const override { return "StencilAtlasOp (CCPR)"; }
FixedFunctionFlags fixedFunctionFlags() const override {
return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil;
}
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
bool hasMixedSampledCoverage, GrClampType) override {
return GrProcessorSet::EmptySetAnalysis();
}
CombineResult onCombineIfPossible(GrOp* other, SkArenaAlloc*, const GrCaps&) override {
// We will only make multiple copy ops if they have different source proxies.
// TODO: make use of texture chaining.
return CombineResult::kCannotCombine;
}
static GrOp::Owner Make(
GrRecordingContext*, sk_sp<const GrCCPerFlushResources>, FillBatchID,
int baseStencilResolveInstance, int endStencilResolveInstance,
const SkISize& drawBounds);
private:
void onPrePrepare(GrRecordingContext*,
const GrSurfaceProxyView& writeView,
GrAppliedClip*,
const GrXferProcessor::DstProxyView&,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) override {}
void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
void drawResolve(GrOpFlushState*, const GrPipeline&, const GrUserStencilSettings*,
const GrPrimitiveProcessor&, const SkIRect& drawBounds) const;
friend class ::GrOp; // for ctor
GrStencilAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
int baseStencilResolveInstance,
int endStencilResolveInstance, const SkISize& drawBounds)
: GrDrawOp(ClassID())
, fResources(std::move(resources))
, fFillBatchID(fillBatchID)
, fBaseStencilResolveInstance(baseStencilResolveInstance)
, fEndStencilResolveInstance(endStencilResolveInstance)
, fDrawBounds(drawBounds) {
this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo);
}
const sk_sp<const GrCCPerFlushResources> fResources;
const FillBatchID fFillBatchID;
const int fBaseStencilResolveInstance;
const int fEndStencilResolveInstance;
const SkISize fDrawBounds;
int fResolveBaseVertex;
};
#endif

View File

@ -51,32 +51,6 @@ int GrResourceCache::countUniqueKeysWithTag(const char* tag) const {
//////////////////////////////////////////////////////////////////////////////
const GrCCPerFlushResources*
GrCoverageCountingPathRenderer::testingOnly_getCurrentFlushResources() {
SkASSERT(fFlushing);
if (fFlushingPaths.empty()) {
return nullptr;
}
// All pending paths should share the same resources.
const GrCCPerFlushResources* resources = fFlushingPaths.front()->fFlushResources.get();
#ifdef SK_DEBUG
for (const auto& flushingPaths : fFlushingPaths) {
SkASSERT(flushingPaths->fFlushResources.get() == resources);
}
#endif
return resources;
}
const GrTexture* GrCCPerFlushResources::testingOnly_frontRenderedAtlasTexture() const {
if (fRenderedAtlasStack.empty()) {
return nullptr;
}
const GrTextureProxy* proxy = fRenderedAtlasStack.front().textureProxy();
return (proxy) ? proxy->peekTexture() : nullptr;
}
//////////////////////////////////////////////////////////////////////////////
#define DRAW_OP_TEST_EXTERN(Op) \
extern GrOp::Owner Op##__Test(GrPaint&&, SkRandom*, \
GrRecordingContext*, int numSamples)