Implement DashLinePathRenderer with a simple FP

An SDF of the dash is implemented as a linear gradient, which is then
converted to coverage by an inline runtime effect. Currently only
non-perspective, non-hairline, AA butt caps are supported by the new
FP. We can delete DashOp once the FP supports more types of strokes.

Bug: skia:12872
Change-Id: Ifb19fcc4a114aec1847de50aab7ee900f289ed33
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/505476
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Christopher Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2022-02-11 10:06:45 -07:00 committed by SkCQ
parent 1c0f1abd53
commit bbc4ee5e07
2 changed files with 209 additions and 2 deletions

View File

@ -94,6 +94,9 @@ public:
GrFragmentProcessor* getCoverageFragmentProcessor() const {
return fCoverageFragmentProcessor.get();
}
std::unique_ptr<GrFragmentProcessor> detachCoverageFragmentProcessor() {
return std::move(fCoverageFragmentProcessor);
}
bool usesLocalCoords() const {
// The sample coords for the top level FPs are implicitly the GP's local coords.
return (fColorFragmentProcessor && fColorFragmentProcessor->usesSampleCoords()) ||

View File

@ -7,21 +7,43 @@
#include "src/gpu/ops/DashLinePathRenderer.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/gpu/GrAuditTrail.h"
#include "src/gpu/GrGpu.h"
#include "src/gpu/effects/GrSkSLFP.h"
#include "src/gpu/geometry/GrStyledShape.h"
#include "src/gpu/gradients/GrGradientShader.h"
#include "src/gpu/ops/DashOp.h"
#include "src/gpu/ops/GrMeshDrawOp.h"
#include "src/gpu/v1/SurfaceDrawContext_v1.h"
namespace skgpu::v1 {
// Checks if our simple dash FP can work in the the given environment.
static bool can_use_dash_fp(const SkMatrix& viewMatrix,
GrAAType aaType,
const SkStrokeRec& stroke,
const GrCaps& caps) {
return !viewMatrix.hasPerspective() && // NOTE: for prespective, aaInverseRampWidth=fwidth(d).
aaType != GrAAType::kNone && // The simple FP doesn't have a non-aa variant.
stroke.getCap() == SkPaint::kButt_Cap && // Our FP doesn't support caps yet.
stroke.getWidth() > 0 && // drawStrokedLine doesn't support hairlines yet.
// If our gradient falls back on a texture, it needs to be floating point.
caps.getDefaultBackendFormat(GrColorType::kRGBA_F16, GrRenderable::kNo).isValid();
}
PathRenderer::CanDrawPath DashLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
SkPoint pts[2];
bool inverted;
if (args.fShape->style().isDashed() && args.fShape->asLine(pts, &inverted)) {
// We should never have an inverse dashed case.
SkASSERT(!inverted);
if (can_use_dash_fp(*args.fViewMatrix,
args.fAAType,
args.fShape->style().strokeRec(),
*args.fCaps)) {
return CanDrawPath::kYes;
}
if (!DashOp::CanDrawDashLine(pts, args.fShape->style(), *args.fViewMatrix)) {
return CanDrawPath::kNo;
}
@ -30,9 +52,193 @@ PathRenderer::CanDrawPath DashLinePathRenderer::onCanDrawPath(const CanDrawPathA
return CanDrawPath::kNo;
}
// Creates a coverage FP that implements the given dash pattern.
static std::unique_ptr<GrFragmentProcessor> make_dash_coverage_fp(
GrRecordingContext* ctx,
const SurfaceDrawContext* sdc,
const SkMatrix& viewMatrix,
SkPoint p0,
SkVector lineDirection,
const float intervals[],
int numIntervals,
float phase,
std::unique_ptr<GrFragmentProcessor>&& inputCoverage) {
// Build an SDF of the dash pattern as a linear gradient.
SkSTArray<8, float> stops(numIntervals + 2);
SkSTArray<8, SkColor4f> coverages(numIntervals + 2);
stops.push_back(0);
coverages.push_back({0,0,0,0});
float dashLength = 0;
for (int i = 0; i < numIntervals; ++i) {
float halfInterval = intervals[i] * .5f;
stops.push_back(dashLength + halfInterval);
float coverage = (i & 1) ? -halfInterval /*off*/ : +halfInterval /*on*/;
coverages.push_back({0, 0, 0, coverage});
dashLength += intervals[i];
}
float dashInvLength = 1/dashLength;
for (int i = 1; i < stops.count(); ++i) {
stops[i] *= dashInvLength;
}
stops.push_back(1);
coverages.push_back({0,0,0,0});
SkPoint gradPts[2] = {p0 + lineDirection * -phase,
p0 + lineDirection * (dashLength - phase)};
viewMatrix.mapPoints(gradPts, 2);
SkGradientShaderBase::Descriptor gradDesc;
gradDesc.fColors = coverages.data();
gradDesc.fPos = stops.data();
gradDesc.fCount = stops.count();
gradDesc.fTileMode = SkTileMode::kRepeat;
GrColorInfo gradColorInfo(GrColorType::kRGBA_F16, kPremul_SkAlphaType, nullptr);
auto dashSDF = GrGradientShader::MakeLinear(SkLinearGradient(gradPts, gradDesc),
GrFPArgs(ctx,
SkMatrixProvider(SkMatrix::I()),
&gradColorInfo));
// Convert the dash SDF to coverage.
static auto dashEffect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
uniform shader dashSDF;
uniform float aaInverseRampWidth;
half4 main(float2 coord, half4 inCoverage) {
// Use sk_FragCoord instead of the interpolated local "coord" input. This avoids
// precision issues interpolating across very long lines.
half d = dashSDF.eval(sk_FragCoord.xy).a;
half dashCoverage = saturate(d * aaInverseRampWidth + .5);
return inCoverage * dashCoverage;
}
)");
SkVector gradSpan = gradPts[1] - gradPts[0];
float gradLength = gradSpan.length();
float aaRampWidth = dashLength / gradLength;
if (sdc->alwaysAntialias()) {
// We are rendering to a DMSAA surface. Convert the AA ramp width from Euclidian to
// Manhattan. DMSAA uses FillRRectOp in drawStrokedLine, which does Manhattan AA.
aaRampWidth *= (fabsf(gradSpan.x()) + fabsf(gradSpan.y())) / gradLength;
}
return GrSkSLFP::Make(dashEffect,
"dash_fp",
std::move(inputCoverage),
GrSkSLFP::OptFlags::kNone,
"dashSDF", std::move(dashSDF),
"aaInverseRampWidth", 1/aaRampWidth);
}
// Trims off the ends of the given line if they begin or end in dashing "whitespace". This prevents
// us from picking up partial coverage from dashes that extend beyond the line.
static void trim_dashed_line(SkPoint lineDirection,
float lineLength,
const float intervals[],
int numIntervals,
float phase,
SkPoint trimmedPts[2]) {
SkASSERT(numIntervals > 0);
float dashLength = intervals[0];
for (int i = 1; i < numIntervals; ++i) {
dashLength += intervals[i];
}
{
// Trim the beginning of the line, if necessary.
float t0 = phase;
t0 = t0 - dashLength * floorf(t0/dashLength); // mod to [0, dashLength).
float intervalEnd = 0;
for (int i = 0; i < numIntervals; ++i) {
intervalEnd += intervals[i];
if (fabsf(intervalEnd - t0) < SK_ScalarNearlyZero) {
intervalEnd = t0;
}
if (t0 < intervalEnd) {
if (i & 1 /*off*/) {
// p0 is in an "off" interval. Trim the whitespace.
trimmedPts[0] += lineDirection * (intervalEnd - t0);
}
break;
}
}
}
{
// Trim the end of the line, if necessary.
float t1 = lineLength + phase;
t1 = dashLength * (1 - ceilf(t1/dashLength)) + t1; // mod to (0, dashLength].
float intervalBegin = dashLength;
for (int i = numIntervals - 1; i >= 0; --i) {
intervalBegin -= intervals[i];
if (fabsf(t1 - intervalBegin) < SK_ScalarNearlyZero) {
intervalBegin = t1;
}
if (t1 > intervalBegin) {
if (i & 1 /*off*/) {
// p1 is in an "off" interval. Trim the whitespace.
trimmedPts[1] -= lineDirection * (t1 - intervalBegin);
}
break;
}
}
}
}
bool DashLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
"DashLinePathRenderer::onDrawPath");
SkPoint pts[2];
SkAssertResult(args.fShape->asLine(pts, nullptr));
const auto& stroke = args.fShape->style().strokeRec();
// First check if we can draw the dash with a simple FP.
if (can_use_dash_fp(*args.fViewMatrix, args.fAAType, stroke, *args.fContext->priv().caps())) {
// Unpack dash params.
const float* intervals = args.fShape->style().dashIntervals();
int numIntervals = args.fShape->style().dashIntervalCnt();
if (numIntervals == 0) {
return true;
}
float phase = args.fShape->style().dashPhase();
SkVector lineDirection = pts[1] - pts[0];
float lineLength = lineDirection.length();
lineDirection *= 1/lineLength;
// Add a "dash" coverage processor to the paint.
auto dashFP = make_dash_coverage_fp(args.fContext,
args.fSurfaceDrawContext,
*args.fViewMatrix,
pts[0],
lineDirection,
intervals,
numIntervals,
phase,
args.fPaint.detachCoverageFragmentProcessor());
if (!dashFP) {
// The linear gradient in make_dash_coverage_fp will only fail in unexpected
// circumstances. (It shouldn't even have a limit in # of stops.)
SkDebugf("WARNING: failed to create FP for dash with %i intervals\n", numIntervals);
return false;
}
args.fPaint.setCoverageFragmentProcessor(std::move(dashFP));
// Trim off the ends of line if they begin or end in dashing "whitespace". This prevents
// us from picking up partial coverage from dashes that extend beyond the line.
SkPoint trimmedPts[2] = {pts[0], pts[1]};
trim_dashed_line(lineDirection, lineLength, intervals, numIntervals, phase, trimmedPts);
// Draw the dashed line.
args.fSurfaceDrawContext->drawStrokedLine(args.fClip,
std::move(args.fPaint),
GrAA::kYes,
*args.fViewMatrix,
trimmedPts,
stroke);
return true;
}
DashOp::AAMode aaMode;
switch (args.fAAType) {
case GrAAType::kNone:
@ -47,8 +253,6 @@ bool DashLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
aaMode = DashOp::AAMode::kCoverage;
break;
}
SkPoint pts[2];
SkAssertResult(args.fShape->asLine(pts, nullptr));
GrOp::Owner op = DashOp::MakeDashLineOp(args.fContext, std::move(args.fPaint),
*args.fViewMatrix, pts, aaMode, args.fShape->style(),
args.fUserStencilSettings);