blob: 9b79aac90c8d00a121d4ed4f1ed5bf9c3d3ab1fa [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "src/core/SkGeometry.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkPointPriv.h"
#include "src/gpu/GrAuditTrail.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrDrawOpTest.h"
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/GrProcessor.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/GrVertexWriter.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/geometry/GrStyledShape.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
#include "src/gpu/glsl/GrGLSLUniformHandler.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/ops/GrAAConvexPathRenderer.h"
#include "src/gpu/ops/GrMeshDrawOp.h"
#include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
GrAAConvexPathRenderer::GrAAConvexPathRenderer() {
}
struct Segment {
enum {
// These enum values are assumed in member functions below.
kLine = 0,
kQuad = 1,
} fType;
// line uses one pt, quad uses 2 pts
SkPoint fPts[2];
// normal to edge ending at each pt
SkVector fNorms[2];
// is the corner where the previous segment meets this segment
// sharp. If so, fMid is a normalized bisector facing outward.
SkVector fMid;
int countPoints() {
static_assert(0 == kLine && 1 == kQuad);
return fType + 1;
}
const SkPoint& endPt() const {
static_assert(0 == kLine && 1 == kQuad);
return fPts[fType];
}
const SkPoint& endNorm() const {
static_assert(0 == kLine && 1 == kQuad);
return fNorms[fType];
}
};
typedef SkTArray<Segment, true> SegmentArray;
static bool center_of_mass(const SegmentArray& segments, SkPoint* c) {
SkScalar area = 0;
SkPoint center = {0, 0};
int count = segments.count();
SkPoint p0 = {0, 0};
if (count > 2) {
// We translate the polygon so that the first point is at the origin.
// This avoids some precision issues with small area polygons far away
// from the origin.
p0 = segments[0].endPt();
SkPoint pi;
SkPoint pj;
// the first and last iteration of the below loop would compute
// zeros since the starting / ending point is (0,0). So instead we start
// at i=1 and make the last iteration i=count-2.
pj = segments[1].endPt() - p0;
for (int i = 1; i < count - 1; ++i) {
pi = pj;
pj = segments[i + 1].endPt() - p0;
SkScalar t = SkPoint::CrossProduct(pi, pj);
area += t;
center.fX += (pi.fX + pj.fX) * t;
center.fY += (pi.fY + pj.fY) * t;
}
}
// If the poly has no area then we instead return the average of
// its points.
if (SkScalarNearlyZero(area)) {
SkPoint avg;
avg.set(0, 0);
for (int i = 0; i < count; ++i) {
const SkPoint& pt = segments[i].endPt();
avg.fX += pt.fX;
avg.fY += pt.fY;
}
SkScalar denom = SK_Scalar1 / count;
avg.scale(denom);
*c = avg;
} else {
area *= 3;
area = SkScalarInvert(area);
center.scale(area);
// undo the translate of p0 to the origin.
*c = center + p0;
}
return !SkScalarIsNaN(c->fX) && !SkScalarIsNaN(c->fY) && c->isFinite();
}
static bool compute_vectors(SegmentArray* segments,
SkPoint* fanPt,
SkPathFirstDirection dir,
int* vCount,
int* iCount) {
if (!center_of_mass(*segments, fanPt)) {
return false;
}
int count = segments->count();
// Make the normals point towards the outside
SkPointPriv::Side normSide;
if (dir == SkPathFirstDirection::kCCW) {
normSide = SkPointPriv::kRight_Side;
} else {
normSide = SkPointPriv::kLeft_Side;
}
int64_t vCount64 = 0;
int64_t iCount64 = 0;
// compute normals at all points
for (int a = 0; a < count; ++a) {
Segment& sega = (*segments)[a];
int b = (a + 1) % count;
Segment& segb = (*segments)[b];
const SkPoint* prevPt = &sega.endPt();
int n = segb.countPoints();
for (int p = 0; p < n; ++p) {
segb.fNorms[p] = segb.fPts[p] - *prevPt;
segb.fNorms[p].normalize();
segb.fNorms[p] = SkPointPriv::MakeOrthog(segb.fNorms[p], normSide);
prevPt = &segb.fPts[p];
}
if (Segment::kLine == segb.fType) {
vCount64 += 5;
iCount64 += 9;
} else {
vCount64 += 6;
iCount64 += 12;
}
}
// compute mid-vectors where segments meet. TODO: Detect shallow corners
// and leave out the wedges and close gaps by stitching segments together.
for (int a = 0; a < count; ++a) {
const Segment& sega = (*segments)[a];
int b = (a + 1) % count;
Segment& segb = (*segments)[b];
segb.fMid = segb.fNorms[0] + sega.endNorm();
segb.fMid.normalize();
// corner wedges
vCount64 += 4;
iCount64 += 6;
}
if (vCount64 > SK_MaxS32 || iCount64 > SK_MaxS32) {
return false;
}
*vCount = vCount64;
*iCount = iCount64;
return true;
}
struct DegenerateTestData {
DegenerateTestData() { fStage = kInitial; }
bool isDegenerate() const { return kNonDegenerate != fStage; }
enum {
kInitial,
kPoint,
kLine,
kNonDegenerate
} fStage;
SkPoint fFirstPoint;
SkVector fLineNormal;
SkScalar fLineC;
};
static const SkScalar kClose = (SK_Scalar1 / 16);
static const SkScalar kCloseSqd = kClose * kClose;
static void update_degenerate_test(DegenerateTestData* data, const SkPoint& pt) {
switch (data->fStage) {
case DegenerateTestData::kInitial:
data->fFirstPoint = pt;
data->fStage = DegenerateTestData::kPoint;
break;
case DegenerateTestData::kPoint:
if (SkPointPriv::DistanceToSqd(pt, data->fFirstPoint) > kCloseSqd) {
data->fLineNormal = pt - data->fFirstPoint;
data->fLineNormal.normalize();
data->fLineNormal = SkPointPriv::MakeOrthog(data->fLineNormal);
data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
data->fStage = DegenerateTestData::kLine;
}
break;
case DegenerateTestData::kLine:
if (SkScalarAbs(data->fLineNormal.dot(pt) + data->fLineC) > kClose) {
data->fStage = DegenerateTestData::kNonDegenerate;
}
break;
case DegenerateTestData::kNonDegenerate:
break;
default:
SK_ABORT("Unexpected degenerate test stage.");
}
}
static inline bool get_direction(const SkPath& path, const SkMatrix& m,
SkPathFirstDirection* dir) {
// At this point, we've already returned true from canDraw(), which checked that the path's
// direction could be determined, so this should just be fetching the cached direction.
// However, if perspective is involved, we're operating on a transformed path, which may no
// longer have a computable direction.
*dir = SkPathPriv::ComputeFirstDirection(path);
if (*dir == SkPathFirstDirection::kUnknown) {
return false;
}
// check whether m reverses the orientation
SkASSERT(!m.hasPerspective());
SkScalar det2x2 = m.get(SkMatrix::kMScaleX) * m.get(SkMatrix::kMScaleY) -
m.get(SkMatrix::kMSkewX) * m.get(SkMatrix::kMSkewY);
if (det2x2 < 0) {
*dir = SkPathPriv::OppositeFirstDirection(*dir);
}
return true;
}
static inline void add_line_to_segment(const SkPoint& pt,
SegmentArray* segments) {
segments->push_back();
segments->back().fType = Segment::kLine;
segments->back().fPts[0] = pt;
}
static inline void add_quad_segment(const SkPoint pts[3],
SegmentArray* segments) {
if (SkPointPriv::DistanceToLineSegmentBetweenSqd(pts[1], pts[0], pts[2]) < kCloseSqd) {
if (pts[0] != pts[2]) {
add_line_to_segment(pts[2], segments);
}
} else {
segments->push_back();
segments->back().fType = Segment::kQuad;
segments->back().fPts[0] = pts[1];
segments->back().fPts[1] = pts[2];
}
}
static inline void add_cubic_segments(const SkPoint pts[4],
SkPathFirstDirection dir,
SegmentArray* segments) {
SkSTArray<15, SkPoint, true> quads;
GrPathUtils::convertCubicToQuadsConstrainToTangents(pts, SK_Scalar1, dir, &quads);
int count = quads.count();
for (int q = 0; q < count; q += 3) {
add_quad_segment(&quads[q], segments);
}
}
static bool get_segments(const SkPath& path,
const SkMatrix& m,
SegmentArray* segments,
SkPoint* fanPt,
int* vCount,
int* iCount) {
SkPath::Iter iter(path, true);
// This renderer over-emphasizes very thin path regions. We use the distance
// to the path from the sample to compute coverage. Every pixel intersected
// by the path will be hit and the maximum distance is sqrt(2)/2. We don't
// notice that the sample may be close to a very thin area of the path and
// thus should be very light. This is particularly egregious for degenerate
// line paths. We detect paths that are very close to a line (zero area) and
// draw nothing.
DegenerateTestData degenerateData;
SkPathFirstDirection dir;
if (!get_direction(path, m, &dir)) {
return false;
}
for (;;) {
SkPoint pts[4];
SkPath::Verb verb = iter.next(pts);
switch (verb) {
case SkPath::kMove_Verb:
m.mapPoints(pts, 1);
update_degenerate_test(&degenerateData, pts[0]);
break;
case SkPath::kLine_Verb: {
if (!SkPathPriv::AllPointsEq(pts, 2)) {
m.mapPoints(&pts[1], 1);
update_degenerate_test(&degenerateData, pts[1]);
add_line_to_segment(pts[1], segments);
}
break;
}
case SkPath::kQuad_Verb:
if (!SkPathPriv::AllPointsEq(pts, 3)) {
m.mapPoints(pts, 3);
update_degenerate_test(&degenerateData, pts[1]);
update_degenerate_test(&degenerateData, pts[2]);
add_quad_segment(pts, segments);
}
break;
case SkPath::kConic_Verb: {
if (!SkPathPriv::AllPointsEq(pts, 3)) {
m.mapPoints(pts, 3);
SkScalar weight = iter.conicWeight();
SkAutoConicToQuads converter;
const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f);
for (int i = 0; i < converter.countQuads(); ++i) {
update_degenerate_test(&degenerateData, quadPts[2*i + 1]);
update_degenerate_test(&degenerateData, quadPts[2*i + 2]);
add_quad_segment(quadPts + 2*i, segments);
}
}
break;
}
case SkPath::kCubic_Verb: {
if (!SkPathPriv::AllPointsEq(pts, 4)) {
m.mapPoints(pts, 4);
update_degenerate_test(&degenerateData, pts[1]);
update_degenerate_test(&degenerateData, pts[2]);
update_degenerate_test(&degenerateData, pts[3]);
add_cubic_segments(pts, dir, segments);
}
break;
}
case SkPath::kDone_Verb:
if (degenerateData.isDegenerate()) {
return false;
} else {
return compute_vectors(segments, fanPt, dir, vCount, iCount);
}
default:
break;
}
}
}
struct Draw {
Draw() : fVertexCnt(0), fIndexCnt(0) {}
int fVertexCnt;
int fIndexCnt;
};
typedef SkTArray<Draw, true> DrawArray;
static void create_vertices(const SegmentArray& segments,
const SkPoint& fanPt,
const GrVertexColor& color,
DrawArray* draws,
GrVertexWriter& verts,
uint16_t* idxs,
size_t vertexStride) {
Draw* draw = &draws->push_back();
// alias just to make vert/index assignments easier to read.
int* v = &draw->fVertexCnt;
int* i = &draw->fIndexCnt;
int count = segments.count();
for (int a = 0; a < count; ++a) {
const Segment& sega = segments[a];
int b = (a + 1) % count;
const Segment& segb = segments[b];
// Check whether adding the verts for this segment to the current draw would cause index
// values to overflow.
int vCount = 4;
if (Segment::kLine == segb.fType) {
vCount += 5;
} else {
vCount += 6;
}
if (draw->fVertexCnt + vCount > (1 << 16)) {
idxs += *i;
draw = &draws->push_back();
v = &draw->fVertexCnt;
i = &draw->fIndexCnt;
}
const SkScalar negOneDists[2] = { -SK_Scalar1, -SK_Scalar1 };
// FIXME: These tris are inset in the 1 unit arc around the corner
SkPoint p0 = sega.endPt();
// Position, Color, UV, D0, D1
verts.write(p0, color, SkPoint{0, 0}, negOneDists);
verts.write(p0 + sega.endNorm(), color, SkPoint{0, -SK_Scalar1}, negOneDists);
verts.write(p0 + segb.fMid, color, SkPoint{0, -SK_Scalar1}, negOneDists);
verts.write(p0 + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
idxs[*i + 0] = *v + 0;
idxs[*i + 1] = *v + 2;
idxs[*i + 2] = *v + 1;
idxs[*i + 3] = *v + 0;
idxs[*i + 4] = *v + 3;
idxs[*i + 5] = *v + 2;
*v += 4;
*i += 6;
if (Segment::kLine == segb.fType) {
// we draw the line edge as a degenerate quad (u is 0, v is the
// signed distance to the edge)
SkPoint v1Pos = sega.endPt();
SkPoint v2Pos = segb.fPts[0];
SkScalar dist = SkPointPriv::DistanceToLineBetween(fanPt, v1Pos, v2Pos);
verts.write(fanPt, color, SkPoint{0, dist}, negOneDists);
verts.write(v1Pos, color, SkPoint{0, 0}, negOneDists);
verts.write(v2Pos, color, SkPoint{0, 0}, negOneDists);
verts.write(v1Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
verts.write(v2Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
idxs[*i + 0] = *v + 3;
idxs[*i + 1] = *v + 1;
idxs[*i + 2] = *v + 2;
idxs[*i + 3] = *v + 4;
idxs[*i + 4] = *v + 3;
idxs[*i + 5] = *v + 2;
*i += 6;
// Draw the interior fan if it exists.
// TODO: Detect and combine colinear segments. This will ensure we catch every case
// with no interior, and that the resulting shared edge uses the same endpoints.
if (count >= 3) {
idxs[*i + 0] = *v + 0;
idxs[*i + 1] = *v + 2;
idxs[*i + 2] = *v + 1;
*i += 3;
}
*v += 5;
} else {
SkPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
SkScalar c0 = segb.fNorms[0].dot(qpts[0]);
SkScalar c1 = segb.fNorms[1].dot(qpts[2]);
// We must transform the positions into UV in cpu memory and then copy them to the gpu
// buffer. If we write the position first into the gpu buffer then calculate the UVs, it
// will cause us to read from the GPU buffer which can be very slow.
struct PosAndUV {
SkPoint fPos;
SkPoint fUV;
};
PosAndUV posAndUVPoints[6];
posAndUVPoints[0].fPos = fanPt;
posAndUVPoints[1].fPos = qpts[0];
posAndUVPoints[2].fPos = qpts[2];
posAndUVPoints[3].fPos = qpts[0] + segb.fNorms[0];
posAndUVPoints[4].fPos = qpts[2] + segb.fNorms[1];
SkVector midVec = segb.fNorms[0] + segb.fNorms[1];
midVec.normalize();
posAndUVPoints[5].fPos = qpts[1] + midVec;
GrPathUtils::QuadUVMatrix toUV(qpts);
toUV.apply(posAndUVPoints, 6, sizeof(PosAndUV), sizeof(SkPoint));
verts.write(posAndUVPoints[0].fPos, color, posAndUVPoints[0].fUV,
-segb.fNorms[0].dot(fanPt) + c0,
-segb.fNorms[1].dot(fanPt) + c1);
verts.write(posAndUVPoints[1].fPos, color, posAndUVPoints[1].fUV,
0.0f,
-segb.fNorms[1].dot(qpts[0]) + c1);
verts.write(posAndUVPoints[2].fPos, color, posAndUVPoints[2].fUV,
-segb.fNorms[0].dot(qpts[2]) + c0,
0.0f);
// We need a negative value that is very large that it won't effect results if it is
// interpolated with. However, the value can't be too large of a negative that it
// effects numerical precision on less powerful GPUs.
static const SkScalar kStableLargeNegativeValue = -SK_ScalarMax/1000000;
verts.write(posAndUVPoints[3].fPos, color, posAndUVPoints[3].fUV,
kStableLargeNegativeValue,
kStableLargeNegativeValue);
verts.write(posAndUVPoints[4].fPos, color, posAndUVPoints[4].fUV,
kStableLargeNegativeValue,
kStableLargeNegativeValue);
verts.write(posAndUVPoints[5].fPos, color, posAndUVPoints[5].fUV,
kStableLargeNegativeValue,
kStableLargeNegativeValue);
idxs[*i + 0] = *v + 3;
idxs[*i + 1] = *v + 1;
idxs[*i + 2] = *v + 2;
idxs[*i + 3] = *v + 4;
idxs[*i + 4] = *v + 3;
idxs[*i + 5] = *v + 2;
idxs[*i + 6] = *v + 5;
idxs[*i + 7] = *v + 3;
idxs[*i + 8] = *v + 4;
*i += 9;
// Draw the interior fan if it exists.
// TODO: Detect and combine colinear segments. This will ensure we catch every case
// with no interior, and that the resulting shared edge uses the same endpoints.
if (count >= 3) {
idxs[*i + 0] = *v + 0;
idxs[*i + 1] = *v + 2;
idxs[*i + 2] = *v + 1;
*i += 3;
}
*v += 6;
}
}
}
///////////////////////////////////////////////////////////////////////////////
/*
* Quadratic specified by 0=u^2-v canonical coords. u and v are the first
* two components of the vertex attribute. Coverage is based on signed
* distance with negative being inside, positive outside. The edge is specified in
* window space (y-down). If either the third or fourth component of the interpolated
* vertex coord is > 0 then the pixel is considered outside the edge. This is used to
* attempt to trim to a portion of the infinite quad.
* Requires shader derivative instruction support.
*/
class QuadEdgeEffect : public GrGeometryProcessor {
public:
static GrGeometryProcessor* Make(SkArenaAlloc* arena,
const SkMatrix& localMatrix,
bool usesLocalCoords,
bool wideColor) {
return arena->make([&](void* ptr) {
return new (ptr) QuadEdgeEffect(localMatrix, usesLocalCoords, wideColor);
});
}
~QuadEdgeEffect() override {}
const char* name() const override { return "QuadEdge"; }
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
GLSLProcessor() {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
const QuadEdgeEffect& qe = args.fGeomProc.cast<QuadEdgeEffect>();
GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
// emit attributes
varyingHandler->emitAttributes(qe);
// GL on iOS 14 needs more precision for the quadedge attributes
// We might as well enable it everywhere
GrGLSLVarying v(kFloat4_GrSLType);
varyingHandler->addVarying("QuadEdge", &v);
vertBuilder->codeAppendf("%s = %s;", v.vsOut(), qe.fInQuadEdge.name());
// Setup pass through color
fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
varyingHandler->addPassThroughAttribute(qe.fInColor, args.fOutputColor);
// Setup position
this->writeOutputPosition(vertBuilder, gpArgs, qe.fInPosition.name());
if (qe.fUsesLocalCoords) {
this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
qe.fInPosition.asShaderVar(), qe.fLocalMatrix,
&fLocalMatrixUniform);
}
fragBuilder->codeAppendf("half edgeAlpha;");
// keep the derivative instructions outside the conditional
fragBuilder->codeAppendf("half2 duvdx = half2(dFdx(%s.xy));", v.fsIn());
fragBuilder->codeAppendf("half2 duvdy = half2(dFdy(%s.xy));", v.fsIn());
fragBuilder->codeAppendf("if (%s.z > 0.0 && %s.w > 0.0) {", v.fsIn(), v.fsIn());
// today we know z and w are in device space. We could use derivatives
fragBuilder->codeAppendf("edgeAlpha = half(min(min(%s.z, %s.w) + 0.5, 1.0));", v.fsIn(),
v.fsIn());
fragBuilder->codeAppendf ("} else {");
fragBuilder->codeAppendf("half2 gF = half2(half(2.0*%s.x*duvdx.x - duvdx.y),"
" half(2.0*%s.x*duvdy.x - duvdy.y));",
v.fsIn(), v.fsIn());
fragBuilder->codeAppendf("edgeAlpha = half(%s.x*%s.x - %s.y);", v.fsIn(), v.fsIn(),
v.fsIn());
fragBuilder->codeAppendf("edgeAlpha = "
"saturate(0.5 - edgeAlpha / length(gF));}");
fragBuilder->codeAppendf("half4 %s = half4(edgeAlpha);", args.fOutputCoverage);
}
static inline void GenKey(const GrGeometryProcessor& gp,
const GrShaderCaps&,
GrProcessorKeyBuilder* b) {
const QuadEdgeEffect& qee = gp.cast<QuadEdgeEffect>();
b->addBool(qee.fUsesLocalCoords, "usesLocalCoords");
b->addBits(kMatrixKeyBits, ComputeMatrixKey(qee.fLocalMatrix), "localMatrixType");
}
void setData(const GrGLSLProgramDataManager& pdman,
const GrGeometryProcessor& geomProc) override {
const QuadEdgeEffect& qe = geomProc.cast<QuadEdgeEffect>();
this->setTransform(pdman, fLocalMatrixUniform, qe.fLocalMatrix, &fLocalMatrix);
}
private:
using INHERITED = GrGLSLGeometryProcessor;
SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
UniformHandle fLocalMatrixUniform;
};
void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
GLSLProcessor::GenKey(*this, caps, b);
}
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const override {
return new GLSLProcessor();
}
private:
QuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords, bool wideColor)
: INHERITED(kQuadEdgeEffect_ClassID)
, fLocalMatrix(localMatrix)
, fUsesLocalCoords(usesLocalCoords) {
fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
fInColor = MakeColorAttribute("inColor", wideColor);
// GL on iOS 14 needs more precision for the quadedge attributes
fInQuadEdge = {"inQuadEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
this->setVertexAttributes(&fInPosition, 3);
}
Attribute fInPosition;
Attribute fInColor;
Attribute fInQuadEdge;
SkMatrix fLocalMatrix;
bool fUsesLocalCoords;
GR_DECLARE_GEOMETRY_PROCESSOR_TEST
using INHERITED = GrGeometryProcessor;
};
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(QuadEdgeEffect);
#if GR_TEST_UTILS
GrGeometryProcessor* QuadEdgeEffect::TestCreate(GrProcessorTestData* d) {
// Doesn't work without derivative instructions.
return d->caps()->shaderCaps()->shaderDerivativeSupport()
? QuadEdgeEffect::Make(d->allocator(), GrTest::TestMatrix(d->fRandom),
d->fRandom->nextBool(), d->fRandom->nextBool())
: nullptr;
}
#endif
///////////////////////////////////////////////////////////////////////////////
GrPathRenderer::CanDrawPath
GrAAConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
// This check requires convexity and known direction, since the direction is used to build
// the geometry segments. Degenerate convex paths will fall through to some other path renderer.
if (args.fCaps->shaderCaps()->shaderDerivativeSupport() &&
(GrAAType::kCoverage == args.fAAType) && args.fShape->style().isSimpleFill() &&
!args.fShape->inverseFilled() && args.fShape->knownToBeConvex() &&
args.fShape->knownDirection()) {
return CanDrawPath::kYes;
}
return CanDrawPath::kNo;
}
namespace {
class AAConvexPathOp final : public GrMeshDrawOp {
private:
using Helper = GrSimpleMeshDrawOpHelperWithStencil;
public:
DEFINE_OP_CLASS_ID
static GrOp::Owner Make(GrRecordingContext* context,
GrPaint&& paint,
const SkMatrix& viewMatrix,
const SkPath& path,
const GrUserStencilSettings* stencilSettings) {
return Helper::FactoryHelper<AAConvexPathOp>(context, std::move(paint), viewMatrix, path,
stencilSettings);
}
AAConvexPathOp(GrProcessorSet* processorSet, const SkPMColor4f& color,
const SkMatrix& viewMatrix, const SkPath& path,
const GrUserStencilSettings* stencilSettings)
: INHERITED(ClassID()), fHelper(processorSet, GrAAType::kCoverage, stencilSettings) {
fPaths.emplace_back(PathData{viewMatrix, path, color});
this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes,
IsHairline::kNo);
}
const char* name() const override { return "AAConvexPathOp"; }
void visitProxies(const VisitProxyFunc& func) const override {
if (fProgramInfo) {
fProgramInfo->visitFPProxies(func);
} else {
fHelper.visitProxies(func);
}
}
FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
GrProcessorSet::Analysis finalize(
const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
GrClampType clampType) override {
return fHelper.finalizeProcessors(
caps, clip, hasMixedSampledCoverage, clampType,
GrProcessorAnalysisCoverage::kSingleChannel, &fPaths.back().fColor, &fWideColor);
}
private:
GrProgramInfo* programInfo() override { return fProgramInfo; }
void onCreateProgramInfo(const GrCaps* caps,
SkArenaAlloc* arena,
const GrSurfaceProxyView& writeView,
GrAppliedClip&& appliedClip,
const GrXferProcessor::DstProxyView& dstProxyView,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) override {
SkMatrix invert;
if (fHelper.usesLocalCoords() && !fPaths.back().fViewMatrix.invert(&invert)) {
return;
}
GrGeometryProcessor* quadProcessor = QuadEdgeEffect::Make(arena, invert,
fHelper.usesLocalCoords(),
fWideColor);
fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView,
std::move(appliedClip),
dstProxyView, quadProcessor,
GrPrimitiveType::kTriangles,
renderPassXferBarriers, colorLoadOp);
}
void onPrepareDraws(Target* target) override {
int instanceCount = fPaths.count();
if (!fProgramInfo) {
this->createProgramInfo(target);
if (!fProgramInfo) {
return;
}
}
const size_t kVertexStride = fProgramInfo->geomProc().vertexStride();
fDraws.reserve(instanceCount);
// TODO generate all segments for all paths and use one vertex buffer
for (int i = 0; i < instanceCount; i++) {
const PathData& args = fPaths[i];
// We use the fact that SkPath::transform path does subdivision based on
// perspective. Otherwise, we apply the view matrix when copying to the
// segment representation.
const SkMatrix* viewMatrix = &args.fViewMatrix;
// We avoid initializing the path unless we have to
const SkPath* pathPtr = &args.fPath;
SkTLazy<SkPath> tmpPath;
if (viewMatrix->hasPerspective()) {
SkPath* tmpPathPtr = tmpPath.init(*pathPtr);
tmpPathPtr->setIsVolatile(true);
tmpPathPtr->transform(*viewMatrix);
viewMatrix = &SkMatrix::I();
pathPtr = tmpPathPtr;
}
int vertexCount;
int indexCount;
enum {
kPreallocSegmentCnt = 512 / sizeof(Segment),
kPreallocDrawCnt = 4,
};
SkSTArray<kPreallocSegmentCnt, Segment, true> segments;
SkPoint fanPt;
if (!get_segments(*pathPtr, *viewMatrix, &segments, &fanPt, &vertexCount,
&indexCount)) {
continue;
}
sk_sp<const GrBuffer> vertexBuffer;
int firstVertex;
GrVertexWriter verts{target->makeVertexSpace(kVertexStride, vertexCount,
&vertexBuffer, &firstVertex)};
if (!verts.fPtr) {
SkDebugf("Could not allocate vertices\n");
return;
}
sk_sp<const GrBuffer> indexBuffer;
int firstIndex;
uint16_t *idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex);
if (!idxs) {
SkDebugf("Could not allocate indices\n");
return;
}
SkSTArray<kPreallocDrawCnt, Draw, true> draws;
GrVertexColor color(args.fColor, fWideColor);
create_vertices(segments, fanPt, color, &draws, verts, idxs, kVertexStride);
GrSimpleMesh* meshes = target->allocMeshes(draws.count());
for (int j = 0; j < draws.count(); ++j) {
const Draw& draw = draws[j];
meshes[j].setIndexed(indexBuffer, draw.fIndexCnt, firstIndex, 0,
draw.fVertexCnt - 1, GrPrimitiveRestart::kNo, vertexBuffer,
firstVertex);
firstIndex += draw.fIndexCnt;
firstVertex += draw.fVertexCnt;
}
fDraws.push_back({ meshes, draws.count() });
}
}
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
if (!fProgramInfo || fDraws.isEmpty()) {
return;
}
flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
for (int i = 0; i < fDraws.count(); ++i) {
for (int j = 0; j < fDraws[i].fMeshCount; ++j) {
flushState->drawMesh(fDraws[i].fMeshes[j]);
}
}
}
CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
AAConvexPathOp* that = t->cast<AAConvexPathOp>();
if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
return CombineResult::kCannotCombine;
}
if (fHelper.usesLocalCoords() &&
!SkMatrixPriv::CheapEqual(fPaths[0].fViewMatrix, that->fPaths[0].fViewMatrix)) {
return CombineResult::kCannotCombine;
}
fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
fWideColor |= that->fWideColor;
return CombineResult::kMerged;
}
#if GR_TEST_UTILS
SkString onDumpInfo() const override {
return SkStringPrintf("Count: %d\n%s", fPaths.count(), fHelper.dumpInfo().c_str());
}
#endif
struct PathData {
SkMatrix fViewMatrix;
SkPath fPath;
SkPMColor4f fColor;
};
Helper fHelper;
SkSTArray<1, PathData, true> fPaths;
bool fWideColor;
struct MeshDraw {
GrSimpleMesh* fMeshes;
int fMeshCount;
};
SkTDArray<MeshDraw> fDraws;
GrProgramInfo* fProgramInfo = nullptr;
using INHERITED = GrMeshDrawOp;
};
} // anonymous namespace
bool GrAAConvexPathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
"GrAAConvexPathRenderer::onDrawPath");
SkASSERT(args.fRenderTargetContext->numSamples() <= 1);
SkASSERT(!args.fShape->isEmpty());
SkPath path;
args.fShape->asPath(&path);
GrOp::Owner op = AAConvexPathOp::Make(args.fContext, std::move(args.fPaint),
*args.fViewMatrix,
path, args.fUserStencilSettings);
args.fRenderTargetContext->addDrawOp(args.fClip, std::move(op));
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#if GR_TEST_UTILS
GR_DRAW_OP_TEST_DEFINE(AAConvexPathOp) {
SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
const SkPath& path = GrTest::TestPathConvex(random);
const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context);
return AAConvexPathOp::Make(context, std::move(paint), viewMatrix, path, stencilSettings);
}
#endif