blob: cbfa5ae7f28542ec6919621ba4653018ec80dd67 [file] [log] [blame]
/*
* 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/GrCCStroker.h"
#include "include/core/SkStrokeRec.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrOnFlushResourceProvider.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2;
using TriangleInstance = GrCCCoverageProcessor::TriPointInstance;
using ConicInstance = GrCCCoverageProcessor::QuadPointInstance;
namespace {
struct LinearStrokeInstance {
float fEndpoints[4];
float fStrokeRadius;
inline void set(const SkPoint[2], float dx, float dy, float strokeRadius);
};
inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) {
Sk2f X, Y;
Sk2f::Load2(P, &X, &Y);
Sk2f::Store2(fEndpoints, X + dx, Y + dy);
fStrokeRadius = strokeRadius;
}
struct CubicStrokeInstance {
float fX[4];
float fY[4];
float fStrokeRadius;
float fNumSegments;
inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments);
inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius,
int numSegments);
};
inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius,
int numSegments) {
Sk4f X, Y;
Sk4f::Load2(P, &X, &Y);
this->set(X, Y, dx, dy, strokeRadius, numSegments);
}
inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy,
float strokeRadius, int numSegments) {
(X + dx).store(&fX);
(Y + dy).store(&fY);
fStrokeRadius = strokeRadius;
fNumSegments = static_cast<float>(numSegments);
}
// This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body
// transforms can be achieved by transforming the line ahead of time and adjusting the stroke
// width. Skews of the stroke itself are not yet supported.
//
// Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important
// for seamless integration with the connecting geometry.
class LinearStrokeProcessor : public GrGeometryProcessor {
public:
LinearStrokeProcessor() : GrGeometryProcessor(kLinearStrokeProcessor_ClassID) {
this->setInstanceAttributes(kInstanceAttribs, 2);
#ifdef SK_DEBUG
using Instance = LinearStrokeInstance;
SkASSERT(this->instanceStride() == sizeof(Instance));
#endif
}
private:
const char* name() const override { return "LinearStrokeProcessor"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
static constexpr Attribute kInstanceAttribs[2] = {
{"endpts", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"stroke_radius", kFloat_GrVertexAttribType, kFloat_GrSLType}
};
class Impl : public GrGLSLGeometryProcessor {
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
FPCoordTransformIter&&) override {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
};
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
return new Impl();
}
};
void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
GrGLSLVertexBuilder* v = args.fVertBuilder;
v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);");
v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
// Outset the vertex position for AA butt caps.
v->codeAppend ("float2 outset = tan*nwidth/2;");
v->codeAppend ("float2 position = (sk_VertexID < 2) "
"? endpts.xy - outset : endpts.zw + outset;");
// Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and
// distance=-.5 on the outset edge.
GrGLSLVarying edgeDistances(kFloat4_GrSLType);
varyingHandler->addVarying("edge_distances", &edgeDistances);
v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);",
edgeDistances.vsOut());
v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;",
edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
// Outset the vertex position for stroke radius plus edge AA.
v->codeAppend ("outset = n * (stroke_radius + nwidth/2);");
v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;");
// Calculate Manhattan distance from both edges, where distance=0 on the actual edge and
// distance=-.5 on the outset.
v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut());
v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;",
edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
SkMatrix::I(), args.fFPCoordTransformHandler);
// Use the 4 edge distances to calculate coverage in the fragment shader.
GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
f->codeAppendf("half2 coverages = half2(min(%s.xy, .5) + min(%s.zw, .5));",
edgeDistances.fsIn(), edgeDistances.fsIn());
f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor);
// This shader doesn't use the built-in Ganesh coverage.
f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
}
constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[];
// This class draws stroked cubics in post-transform device space. Rigid-body transforms can be
// achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the
// stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics.
//
// This class works by finding stroke-width line segments orthogonal to the curve at a
// pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric
// sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise-
// winding triangles from the strip emit positive coverage, counter-clockwise triangles emit
// negative, and we use SkBlendMode::kPlus.
class CubicStrokeProcessor : public GrGeometryProcessor {
public:
CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) {
this->setInstanceAttributes(kInstanceAttribs, 3);
#ifdef SK_DEBUG
using Instance = CubicStrokeInstance;
SkASSERT(this->instanceStride() == sizeof(Instance));
#endif
}
private:
const char* name() const override { return "CubicStrokeProcessor"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
static constexpr Attribute kInstanceAttribs[3] = {
{"X", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"Y", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"stroke_info", kFloat2_GrVertexAttribType, kFloat2_GrSLType}
};
class Impl : public GrGLSLGeometryProcessor {
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
FPCoordTransformIter&&) override {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
};
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
return new Impl();
}
};
void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
GrGLSLVertexBuilder* v = args.fVertBuilder;
v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));");
v->codeAppend ("float stroke_radius = stroke_info[0];");
v->codeAppend ("float num_segments = stroke_info[1];");
// Find the parametric T value at which we will emit our orthogonal line segment. We emit two
// line segments at T=0 and double at T=1 as well for AA butt caps.
v->codeAppend ("float point_id = float(sk_VertexID/2);");
v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);");
v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;"); // In case x/x !== 1.
// Use De Casteljau's algorithm to find the position and tangent for our orthogonal line
// segment. De Casteljau's is more numerically stable than evaluating the curve and derivative
// directly.
v->codeAppend ("float2 ab = mix(P[0], P[1], T);");
v->codeAppend ("float2 bc = mix(P[1], P[2], T);");
v->codeAppend ("float2 cd = mix(P[2], P[3], T);");
v->codeAppend ("float2 abc = mix(ab, bc, T);");
v->codeAppend ("float2 bcd = mix(bc, cd, T);");
v->codeAppend ("float2 position = mix(abc, bcd, T);");
v->codeAppend ("float2 tan = bcd - abc;");
// Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't
// encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps
// control points to endpoints in curves where they are almost equal.)
v->codeAppend ("if (0 == T && P[0] == P[1]) {");
v->codeAppend ( "tan = P[2] - P[0];");
v->codeAppend ("}");
v->codeAppend ("if (1 == T && P[2] == P[3]) {");
v->codeAppend ( "tan = P[3] - P[1];");
v->codeAppend ("}");
v->codeAppend ("tan = normalize(tan);");
v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
// Outset the vertex position for stroke radius plus edge AA.
v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);");
v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;");
// Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and
// distance=-.5 on the outset.
GrGLSLVarying coverages(kFloat3_GrSLType);
varyingHandler->addVarying("coverages", &coverages);
v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut());
v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;",
coverages.vsOut(), coverages.vsOut(), coverages.vsOut());
// Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint
// at a Manhattan distance of .5 on either side.
v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {");
v->codeAppend ( "position -= tan*nwidth/2;");
v->codeAppend ("}");
v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {");
v->codeAppend ( "position += tan*nwidth/2;");
v->codeAppend ("}");
// Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner.
v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;",
coverages.vsOut());
gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
SkMatrix::I(), args.fFPCoordTransformHandler);
// Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
f->codeAppendf("half2 edge_coverages = min(half2(%s.xy), .5);", coverages.fsIn());
f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;");
f->codeAppendf("coverage *= half(%s.z);", coverages.fsIn()); // Butt cap AA.
// As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and
// counter-clockwise triangles emit negative.
f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor);
// This shader doesn't use the built-in Ganesh coverage.
f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
}
constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[];
} // anonymous namespace
void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts,
const SkStrokeRec& stroke, float strokeDevWidth,
GrScissorTest scissorTest,
const SkIRect& clippedDevIBounds,
const SkIVector& devToAtlasOffset) {
SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() ||
SkStrokeRec::kHairline_Style == stroke.getStyle());
SkASSERT(!fInstanceBuffer);
SkASSERT(!path.isEmpty());
if (!fHasOpenBatch) {
fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled],
fScissorSubBatches.count());
fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances;
fHasOpenBatch = true;
}
InstanceTallies* currStrokeEndIndices;
if (GrScissorTest::kEnabled == scissorTest) {
SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count());
fScissorSubBatches.emplace_back(&fTalliesAllocator,
*fInstanceCounts[(int)GrScissorTest::kEnabled],
clippedDevIBounds.makeOffset(devToAtlasOffset));
fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count();
fInstanceCounts[(int)GrScissorTest::kEnabled] =
currStrokeEndIndices = fScissorSubBatches.back().fEndInstances;
} else {
currStrokeEndIndices = fBatches.back().fNonScissorEndInstances;
}
fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices);
fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest};
int devPtsIdx = 0;
SkPath::Verb previousVerb = SkPath::kClose_Verb;
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
SkASSERT(SkPath::kDone_Verb != previousVerb);
const SkPoint* P = &deviceSpacePts[devPtsIdx - 1];
switch (verb) {
case SkPath::kMove_Verb:
if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
fGeometry.capContourAndExit();
}
fGeometry.moveTo(deviceSpacePts[devPtsIdx]);
++devPtsIdx;
break;
case SkPath::kClose_Verb:
SkASSERT(SkPath::kClose_Verb != previousVerb);
fGeometry.closeContour();
break;
case SkPath::kLine_Verb:
SkASSERT(SkPath::kClose_Verb != previousVerb);
fGeometry.lineTo(P[1]);
++devPtsIdx;
break;
case SkPath::kQuad_Verb:
SkASSERT(SkPath::kClose_Verb != previousVerb);
fGeometry.quadraticTo(P);
devPtsIdx += 2;
break;
case SkPath::kCubic_Verb: {
SkASSERT(SkPath::kClose_Verb != previousVerb);
fGeometry.cubicTo(P);
devPtsIdx += 3;
break;
}
case SkPath::kConic_Verb:
SkASSERT(SkPath::kClose_Verb != previousVerb);
SK_ABORT("Stroked conics not supported.");
break;
case SkPath::kDone_Verb:
break;
}
previousVerb = verb;
}
if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
fGeometry.capContourAndExit();
}
}
// This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry
// directly into GPU instance buffers.
class GrCCStroker::InstanceBufferBuilder {
public:
InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) {
memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances));
#ifdef SK_DEBUG
fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0];
fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1];
#endif
int endConicsIdx = stroker->fBaseInstances[1].fConics +
stroker->fInstanceCounts[1]->fConics;
fInstanceBuffer = onFlushRP->makeBuffer(GrGpuBufferType::kVertex,
endConicsIdx * sizeof(ConicInstance));
if (!fInstanceBuffer) {
SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n");
return;
}
fInstanceBufferData = fInstanceBuffer->map();
}
bool isMapped() const { return SkToBool(fInstanceBufferData); }
void updateCurrentInfo(const PathInfo& pathInfo) {
SkASSERT(this->isMapped());
fCurrDX = static_cast<float>(pathInfo.fDevToAtlasOffset.x());
fCurrDY = static_cast<float>(pathInfo.fDevToAtlasOffset.y());
fCurrStrokeRadius = pathInfo.fStrokeRadius;
fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest];
SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]);
}
void appendLinearStroke(const SkPoint endpts[2]) {
SkASSERT(this->isMapped());
this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius);
}
void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
SkASSERT(this->isMapped());
SkASSERT(numLinearSegmentsLog2 > 0);
Sk4f ptsT[2];
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
// Convert the quadratic to cubic.
Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0);
Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1);
Sk2f::Store4(ptsT, p0, c1, c2, p2);
this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
}
void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
SkASSERT(this->isMapped());
SkASSERT(numLinearSegmentsLog2 > 0);
this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
}
void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm,
const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) {
SkASSERT(this->isMapped());
Sk2f offset = Sk2f::Load(&center) + Sk2f(fCurrDX, fCurrDY);
Sk2f n0 = Sk2f::Load(&leftNorm);
Sk2f n1 = Sk2f::Load(&rightNorm);
// Identify the outer edge.
Sk2f cross = n0 * SkNx_shuffle<1,0>(n1);
if (cross[0] < cross[1]) {
Sk2f tmp = n0;
n0 = -n1;
n1 = -tmp;
}
if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) {
// Normal joins are a triangle that connects the outer corners of two adjoining strokes.
this->appendTriangleInstance().set(
n1 * fCurrStrokeRadius, Sk2f(0, 0), n0 * fCurrStrokeRadius, offset,
TriangleInstance::Ordering::kXYTransposed);
if (Verb::kBevelJoin == joinVerb) {
return;
}
} else {
// Internal joins are coverage-counted, self-intersecting quadrilaterals that tie the
// four corners of two adjoining strokes together a like a shoelace. Coverage is
// negative on the inside half. We implement this geometry with a pair of triangles.
this->appendTriangleInstance().set(
-n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius,
offset, TriangleInstance::Ordering::kXYTransposed);
if (Verb::kBevelJoin == joinVerb) {
return;
}
this->appendTriangleInstance().set(
-n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius, -n1 * fCurrStrokeRadius,
offset, TriangleInstance::Ordering::kXYTransposed);
if (Verb::kBevelJoin == joinVerb) {
return;
}
if (Verb::kInternalBevelJoin == joinVerb) {
return;
}
}
// For miter and round joins, we place an additional triangle cap on top of the bevel. This
// triangle is literal for miters and is conic control points for round joins.
SkASSERT(miterCapHeightOverWidth >= 0 || SkScalarIsNaN(miterCapHeightOverWidth));
Sk2f base = n1 - n0;
Sk2f baseNorm = Sk2f(base[1], -base[0]);
Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth;
if (Verb::kMiterJoin == joinVerb) {
this->appendTriangleInstance().set(
n0 * fCurrStrokeRadius, c * fCurrStrokeRadius, n1 * fCurrStrokeRadius, offset,
TriangleInstance::Ordering::kXYTransposed);
} else {
SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
n1 * fCurrStrokeRadius, offset, conicWeight);
if (Verb::kInternalRoundJoin == joinVerb) {
this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius,
-n0 * fCurrStrokeRadius, offset, conicWeight);
}
}
}
void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) {
SkASSERT(this->isMapped());
Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius;
Sk2f v = Sk2f(-n[1], n[0]);
Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY);
if (Verb::kSquareCap == capType) {
SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}};
this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius);
} else {
SkASSERT(Verb::kRoundCap == capType);
this->appendTriangleInstance().set(
n, v, -n, offset, TriangleInstance::Ordering::kXYTransposed);
this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2);
this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2);
}
}
sk_sp<GrGpuBuffer> finish() {
SkASSERT(this->isMapped());
SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
fInstanceBuffer->unmap();
fInstanceBufferData = nullptr;
SkASSERT(!this->isMapped());
return std::move(fInstanceBuffer);
}
private:
LinearStrokeInstance& appendLinearStrokeInstance() {
int instanceIdx = fCurrNextInstances->fStrokes[0]++;
SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]);
return reinterpret_cast<LinearStrokeInstance*>(fInstanceBufferData)[instanceIdx];
}
CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) {
SkASSERT(numLinearSegmentsLog2 > 0);
SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++;
SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]);
return reinterpret_cast<CubicStrokeInstance*>(fInstanceBufferData)[instanceIdx];
}
TriangleInstance& appendTriangleInstance() {
int instanceIdx = fCurrNextInstances->fTriangles++;
SkASSERT(instanceIdx < fCurrEndInstances->fTriangles);
return reinterpret_cast<TriangleInstance*>(fInstanceBufferData)[instanceIdx];
}
ConicInstance& appendConicInstance() {
int instanceIdx = fCurrNextInstances->fConics++;
SkASSERT(instanceIdx < fCurrEndInstances->fConics);
return reinterpret_cast<ConicInstance*>(fInstanceBufferData)[instanceIdx];
}
float fCurrDX, fCurrDY;
float fCurrStrokeRadius;
InstanceTallies* fCurrNextInstances;
SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
sk_sp<GrGpuBuffer> fInstanceBuffer;
void* fInstanceBufferData = nullptr;
InstanceTallies fNextInstances[2];
SkDEBUGCODE(InstanceTallies fEndInstances[2]);
};
GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() {
if (!fHasOpenBatch) {
return kEmptyBatchID;
}
int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch;
int end = fBatches.back().fEndScissorSubBatch;
fMaxNumScissorSubBatches = SkTMax(fMaxNumScissorSubBatches, end - start);
fHasOpenBatch = false;
return fBatches.count() - 1;
}
bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
SkASSERT(!fInstanceBuffer);
SkASSERT(!fHasOpenBatch); // Call closeCurrentBatch() first.
// Here we layout a single instance buffer to share with every internal batch.
//
// 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.
fBaseInstances[0].fStrokes[0] = 0;
fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0];
int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0];
int cubicStrokesIdx = GrSizeDivRoundUp(endLinearStrokesIdx * sizeof(LinearStrokeInstance),
sizeof(CubicStrokeInstance));
for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
for (int j = 0; j < kNumScissorModes; ++j) {
fBaseInstances[j].fStrokes[i] = cubicStrokesIdx;
cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i];
}
}
int trianglesIdx = GrSizeDivRoundUp(cubicStrokesIdx * sizeof(CubicStrokeInstance),
sizeof(TriangleInstance));
fBaseInstances[0].fTriangles = trianglesIdx;
fBaseInstances[1].fTriangles =
fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles;
int endTrianglesIdx =
fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles;
int conicsIdx =
GrSizeDivRoundUp(endTrianglesIdx * sizeof(TriangleInstance), sizeof(ConicInstance));
fBaseInstances[0].fConics = conicsIdx;
fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics;
InstanceBufferBuilder builder(onFlushRP, this);
if (!builder.isMapped()) {
return false; // Buffer allocation failed.
}
// Now parse the GrCCStrokeGeometry and expand it into the instance buffer.
int pathIdx = 0;
int ptsIdx = 0;
int paramsIdx = 0;
int normalsIdx = 0;
const SkTArray<GrCCStrokeGeometry::Parameter, true>& params = fGeometry.params();
const SkTArray<SkPoint, true>& pts = fGeometry.points();
const SkTArray<SkVector, true>& normals = fGeometry.normals();
float miterCapHeightOverWidth=0, conicWeight=0;
for (Verb verb : fGeometry.verbs()) {
switch (verb) {
case Verb::kBeginPath:
builder.updateCurrentInfo(fPathInfos[pathIdx]);
++pathIdx;
continue;
case Verb::kLinearStroke:
builder.appendLinearStroke(&pts[ptsIdx]);
++ptsIdx;
continue;
case Verb::kQuadraticStroke:
builder.appendQuadraticStroke(&pts[ptsIdx],
params[paramsIdx++].fNumLinearSegmentsLog2);
ptsIdx += 2;
++normalsIdx;
continue;
case Verb::kCubicStroke:
builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2);
ptsIdx += 3;
++normalsIdx;
continue;
case Verb::kRoundJoin:
case Verb::kInternalRoundJoin:
conicWeight = params[paramsIdx++].fConicWeight;
// fallthru
case Verb::kMiterJoin:
miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth;
// fallthru
case Verb::kBevelJoin:
case Verb::kInternalBevelJoin:
builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1],
miterCapHeightOverWidth, conicWeight);
++normalsIdx;
continue;
case Verb::kSquareCap:
case Verb::kRoundCap:
builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]);
continue;
case Verb::kEndContour:
++ptsIdx;
++normalsIdx;
continue;
}
SK_ABORT("Invalid CCPR stroke element.");
}
fInstanceBuffer = builder.finish();
SkASSERT(fPathInfos.count() == pathIdx);
SkASSERT(pts.count() == ptsIdx);
SkASSERT(normals.count() == normalsIdx);
fMeshesBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
fScissorsBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
return true;
}
void GrCCStroker::drawStrokes(GrOpFlushState* flushState, GrCCCoverageProcessor* proc,
BatchID batchID, const SkIRect& drawBounds) const {
using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
SkASSERT(fInstanceBuffer);
if (kEmptyBatchID == batchID) {
return;
}
const Batch& batch = fBatches[batchID];
int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch;
const InstanceTallies* startIndices[2];
startIndices[(int)GrScissorTest::kDisabled] = (!batchID)
? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances;
startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch)
? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances;
GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus,
flushState->drawOpArgs().outputSwizzle());
// Draw linear strokes.
this->appendStrokeMeshesToBuffers(0, batch, startIndices, startScissorSubBatch, drawBounds);
if (!fMeshesBuffer.empty()) {
LinearStrokeProcessor linearProc;
this->flushBufferedMeshesAsStrokes(linearProc, flushState, pipeline, drawBounds);
}
// Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.)
for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
this->appendStrokeMeshesToBuffers(i, batch, startIndices, startScissorSubBatch, drawBounds);
}
if (!fMeshesBuffer.empty()) {
CubicStrokeProcessor cubicProc;
this->flushBufferedMeshesAsStrokes(cubicProc, flushState, pipeline, drawBounds);
}
// Draw triangles.
proc->reset(PrimitiveType::kTriangles, flushState->resourceProvider());
this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
// Draw conics.
proc->reset(PrimitiveType::kConics, flushState->resourceProvider());
this->drawConnectingGeometry<&InstanceTallies::fConics>(
flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
}
void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch,
const InstanceTallies* startIndices[2],
int startScissorSubBatch,
const SkIRect& drawBounds) const {
// Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments"
// evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for
// AA butt caps. (i.e., 2 vertices * (numSegments + 3).)
int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2;
// Append non-scissored meshes.
int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2];
int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2];
int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2];
SkASSERT(endIdx >= startIdx);
if (int instanceCount = endIdx - startIdx) {
GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx,
numStripVertices);
fScissorsBuffer.push_back(drawBounds);
}
// Append scissored meshes.
baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2];
startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2];
for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
const ScissorSubBatch& subBatch = fScissorSubBatches[i];
endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2];
SkASSERT(endIdx >= startIdx);
if (int instanceCount = endIdx - startIdx) {
GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx,
numStripVertices);
fScissorsBuffer.push_back(subBatch.fScissor);
startIdx = endIdx;
}
}
}
void GrCCStroker::flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor& processor,
GrOpFlushState* flushState,
const GrPipeline& pipeline,
const SkIRect& drawBounds) const {
SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
GrPipeline::DynamicStateArrays dynamicStateArrays;
dynamicStateArrays.fScissorRects = fScissorsBuffer.begin();
GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
flushState->drawOpArgs().origin(),
pipeline,
processor,
nullptr,
&dynamicStateArrays, 0);
flushState->opsRenderPass()->draw(programInfo,
fMeshesBuffer.begin(), fMeshesBuffer.count(),
SkRect::Make(drawBounds));
// Don't call reset(), as that also resets the reserve count.
fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
}
template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline,
const GrCCCoverageProcessor& processor,
const Batch& batch, const InstanceTallies* startIndices[2],
int startScissorSubBatch,
const SkIRect& drawBounds) const {
// Append non-scissored meshes.
int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType;
int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType;
int endIdx = batch.fNonScissorEndInstances->*InstanceType;
SkASSERT(endIdx >= startIdx);
if (int instanceCount = endIdx - startIdx) {
processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx,
&fMeshesBuffer);
fScissorsBuffer.push_back(drawBounds);
}
// Append scissored meshes.
baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType;
startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType;
for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
const ScissorSubBatch& subBatch = fScissorSubBatches[i];
endIdx = subBatch.fEndInstances->*InstanceType;
SkASSERT(endIdx >= startIdx);
if (int instanceCount = endIdx - startIdx) {
processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx,
&fMeshesBuffer);
fScissorsBuffer.push_back(subBatch.fScissor);
startIdx = endIdx;
}
}
// Flush the geometry.
if (!fMeshesBuffer.empty()) {
SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
processor.draw(flushState, pipeline, fScissorsBuffer.begin(), fMeshesBuffer.begin(),
fMeshesBuffer.count(), SkRect::Make(drawBounds));
// Don't call reset(), as that also resets the reserve count.
fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
}
}