blob: 7c1c29c38019b1ae166f05d6edff5e91ee038477 [file] [log] [blame]
/*
* 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/tessellate/GrTessellateStrokeOp.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/tessellate/GrStrokeGeometry.h"
#include "src/gpu/tessellate/GrTessellateStrokeShader.h"
static SkPath transform_path(const SkMatrix& viewMatrix, const SkPath& path) {
SkPath devPath;
// The provided matrix must be a similarity matrix for the time being. This is so we can
// bootstrap this Op on top of GrStrokeGeometry with minimal modifications.
SkASSERT(viewMatrix.isSimilarity());
path.transform(viewMatrix, &devPath);
return devPath;
}
static SkStrokeRec transform_stroke(const SkMatrix& viewMatrix, const SkStrokeRec& stroke) {
SkStrokeRec devStroke = stroke;
// kStrokeAndFill_Style is not yet supported.
SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
stroke.getStyle() == SkStrokeRec::kHairline_Style);
float strokeWidth = (stroke.getStyle() == SkStrokeRec::kHairline_Style) ?
1 : viewMatrix.getMaxScale() * stroke.getWidth();
devStroke.setStrokeStyle(strokeWidth, /*strokeAndFill=*/false);
return devStroke;
}
static SkPMColor4f get_paint_constant_blended_color(const GrPaint& paint) {
SkPMColor4f constantColor;
// Patches can overlap, so until a stencil technique is implemented, the provided paints must be
// constant blended colors.
SkAssertResult(paint.isConstantBlendedColor(&constantColor));
return constantColor;
}
GrTessellateStrokeOp::GrTessellateStrokeOp(const SkMatrix& viewMatrix, const SkPath& path,
const SkStrokeRec& stroke, GrPaint&& paint,
GrAAType aaType)
: GrDrawOp(ClassID())
, fDevPath(transform_path(viewMatrix, path))
, fDevStroke(transform_stroke(viewMatrix, stroke))
, fAAType(aaType)
, fColor(get_paint_constant_blended_color(paint))
, fProcessors(std::move(paint)) {
SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples support yet.
SkRect devBounds = fDevPath.getBounds();
float inflationRadius = fDevStroke.getInflationRadius();
devBounds.outset(inflationRadius, inflationRadius);
this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
}
GrProcessorSet::Analysis GrTessellateStrokeOp::finalize(const GrCaps& caps,
const GrAppliedClip* clip,
bool hasMixedSampledCoverage,
GrClampType clampType) {
return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip,
&GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps,
clampType, &fColor);
}
GrDrawOp::FixedFunctionFlags GrTessellateStrokeOp::fixedFunctionFlags() const {
auto flags = FixedFunctionFlags::kNone;
if (GrAAType::kNone != fAAType) {
flags |= FixedFunctionFlags::kUsesHWAA;
}
return flags;
}
void GrTessellateStrokeOp::onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView* writeView,
GrAppliedClip*, const GrXferProcessor::DstProxyView&) {
}
static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
return (b - a) * T + a;
}
static void write_line(SkPoint* patch, const SkPoint& p0, const SkPoint& p1) {
patch[0] = p0;
patch[1] = lerp(p0, p1, 1/3.f);
patch[2] = lerp(p0, p1, 2/3.f);
patch[3] = p1;
}
static void write_quadratic(SkPoint* patch, const SkPoint pts[]) {
patch[0] = pts[0];
patch[1] = lerp(pts[0], pts[1], 2/3.f);
patch[2] = lerp(pts[1], pts[2], 1/3.f);
patch[3] = pts[2];
}
static void write_loop(SkPoint* patch, const SkPoint& intersectionPoint,
const SkPoint lastControlPt, const SkPoint& nextControlPt) {
patch[0] = intersectionPoint;
patch[1] = lastControlPt;
patch[2] = nextControlPt;
patch[3] = intersectionPoint;
}
static void write_square_cap(SkPoint* patch, const SkPoint& endPoint,
const SkPoint controlPoint, float strokeRadius) {
SkVector v = (endPoint - controlPoint);
v.normalize();
SkPoint capPoint = endPoint + v*strokeRadius;
// Construct a line that incorporates controlPoint so we get a water tight edge with the rest of
// the stroke. The cubic will technically step outside the cap, but we will force it to only
// have one segment, giving edges only at the endpoints.
patch[0] = endPoint;
patch[1] = controlPoint;
// Straddle the midpoint of the cap because the tessellated geometry emits a center point at
// T=.5, and we need to ensure that point stays inside the cap.
patch[2] = endPoint + capPoint - controlPoint;
patch[3] = capPoint;
}
void GrTessellateStrokeOp::onPrepare(GrOpFlushState* flushState) {
float strokeRadius = fDevStroke.getWidth() * .5f;
// Rebuild the stroke using GrStrokeGeometry.
GrStrokeGeometry strokeGeometry(flushState->caps().shaderCaps()->maxTessellationSegments(),
fDevPath.countPoints(), fDevPath.countVerbs());
GrStrokeGeometry::InstanceTallies tallies = GrStrokeGeometry::InstanceTallies();
strokeGeometry.beginPath(fDevStroke, strokeRadius * 2, &tallies);
SkPathVerb previousVerb = SkPathVerb::kClose;
for (auto [verb, pts, w] : SkPathPriv::Iterate(fDevPath)) {
switch (verb) {
case SkPathVerb::kMove:
if (previousVerb != SkPathVerb::kClose) {
strokeGeometry.capContourAndExit();
}
strokeGeometry.moveTo(pts[0]);
break;
case SkPathVerb::kClose:
strokeGeometry.closeContour();
break;
case SkPathVerb::kLine:
strokeGeometry.lineTo(pts[1]);
break;
case SkPathVerb::kQuad:
strokeGeometry.quadraticTo(pts);
break;
case SkPathVerb::kCubic:
strokeGeometry.cubicTo(pts);
break;
case SkPathVerb::kConic:
SkUNREACHABLE;
}
previousVerb = verb;
}
if (previousVerb != SkPathVerb::kClose) {
strokeGeometry.capContourAndExit();
}
auto vertexData = static_cast<SkPoint*>(flushState->makeVertexSpace(
sizeof(SkPoint), strokeGeometry.verbs().count() * 2 * 5, &fVertexBuffer, &fBaseVertex));
if (!vertexData) {
return;
}
using Verb = GrStrokeGeometry::Verb;
// Dump GrStrokeGeometry into tessellation patches.
//
// This loop is only a temporary adapter for GrStrokeGeometry so we can bootstrap the
// tessellation shaders. Once the shaders are landed and tested, we will overhaul
// GrStrokeGeometry and remove this loop.
int i = 0;
const SkTArray<SkPoint, true>& pathPts = strokeGeometry.points();
auto pendingJoin = Verb::kEndContour;
SkPoint firstJoinControlPoint = {0, 0};
SkPoint lastJoinControlPoint = {0, 0};
bool hasFirstControlPoint = false;
for (auto verb : strokeGeometry.verbs()) {
SkPoint patch[4];
float overrideNumSegments = 0;
switch (verb) {
case Verb::kBeginPath:
continue;
case Verb::kRoundJoin:
case Verb::kInternalRoundJoin:
case Verb::kMiterJoin:
case Verb::kBevelJoin:
case Verb::kInternalBevelJoin:
pendingJoin = verb;
continue;
case Verb::kLinearStroke:
write_line(patch, pathPts[i], pathPts[i+1]);
++i;
break;
case Verb::kQuadraticStroke:
write_quadratic(patch, &pathPts[i]);
i += 2;
break;
case Verb::kCubicStroke:
memcpy(patch, &pathPts[i], sizeof(SkPoint) * 4);
i += 3;
break;
case Verb::kRotate:
write_loop(patch, pathPts[i], pathPts[i+1], pathPts[i]*2 - pathPts[i+1]);
i += 2;
break;
case Verb::kSquareCap: {
SkASSERT(pendingJoin == Verb::kEndContour);
write_square_cap(patch, pathPts[i], lastJoinControlPoint, strokeRadius);
// This cubic steps outside the cap, but if we force it to only have one segment, we
// will just get the rectangular cap.
overrideNumSegments = 1;
break;
}
case Verb::kRoundCap:
// A round cap is the same thing as a 180-degree round join.
SkASSERT(pendingJoin == Verb::kEndContour);
pendingJoin = Verb::kRoundJoin;
write_loop(patch, pathPts[i], lastJoinControlPoint, lastJoinControlPoint);
break;
case Verb::kEndContour:
// Final join
write_loop(patch, pathPts[i], firstJoinControlPoint, lastJoinControlPoint);
++i;
break;
}
SkPoint c1 = (patch[1] == patch[0]) ? patch[2] : patch[1];
SkPoint c2 = (patch[2] == patch[3]) ? patch[1] : patch[2];
if (pendingJoin != Verb::kEndContour) {
vertexData[0] = patch[0];
vertexData[1] = lastJoinControlPoint;
vertexData[2] = c1;
vertexData[3] = patch[0];
switch (pendingJoin) {
case Verb::kBevelJoin:
vertexData[4].set(1, strokeRadius);
break;
case Verb::kMiterJoin:
vertexData[4].set(2, strokeRadius);
break;
case Verb::kRoundJoin:
vertexData[4].set(3, strokeRadius);
break;
case Verb::kInternalRoundJoin:
case Verb::kInternalBevelJoin:
default:
vertexData[4].set(4, strokeRadius);
break;
}
vertexData += 5;
fVertexCount += 5;
pendingJoin = Verb::kEndContour;
}
if (verb != Verb::kRoundCap) {
if (!hasFirstControlPoint) {
firstJoinControlPoint = c1;
hasFirstControlPoint = true;
}
lastJoinControlPoint = c2;
}
if (verb == Verb::kEndContour) {
// Temporary hack for this adapter in case the next contour is a round cap.
lastJoinControlPoint = firstJoinControlPoint;
hasFirstControlPoint = false;
} else if (verb != Verb::kRotate && verb != Verb::kRoundCap) {
memcpy(vertexData, patch, sizeof(SkPoint) * 4);
vertexData[4].set(-overrideNumSegments, strokeRadius);
vertexData += 5;
fVertexCount += 5;
}
}
SkASSERT(fVertexCount <= strokeGeometry.verbs().count() * 2 * 5);
flushState->putBackVertices(strokeGeometry.verbs().count() * 2 * 5 - fVertexCount,
sizeof(SkPoint));
}
void GrTessellateStrokeOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
if (!fVertexBuffer) {
return;
}
GrPipeline::InitArgs initArgs;
if (GrAAType::kNone != fAAType) {
initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
SkASSERT(flushState->proxy()->numSamples() > 1); // No mixed samples yet.
SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples yet.
}
initArgs.fCaps = &flushState->caps();
initArgs.fDstProxyView = flushState->drawOpArgs().dstProxyView();
initArgs.fWriteSwizzle = flushState->drawOpArgs().writeSwizzle();
GrPipeline pipeline(initArgs, std::move(fProcessors), flushState->detachAppliedClip());
GrTessellateStrokeShader strokeShader(SkMatrix::I(), fDevStroke.getMiter(), fColor);
GrPathShader::ProgramInfo programInfo(flushState->writeView(), &pipeline, &strokeShader);
flushState->bindPipelineAndScissorClip(programInfo, this->bounds() /*chainBounds??*/);
flushState->bindTextures(strokeShader, nullptr, pipeline);
flushState->bindBuffers(nullptr, nullptr, fVertexBuffer.get());
flushState->draw(fVertexCount, fBaseVertex);
}