1/*
2 * Copyright 2020 Google LLC.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "src/gpu/tessellate/GrStrokeTessellateOp.h"
9
10#include "src/core/SkPathPriv.h"
11#include "src/gpu/tessellate/GrStrokePatchBuilder.h"
12#include "src/gpu/tessellate/GrStrokeTessellateShader.h"
13
14static SkPMColor4f get_paint_constant_blended_color(const GrPaint& paint) {
15 SkPMColor4f constantColor;
16 // Patches can overlap, so until a stencil technique is implemented, the provided paints must be
17 // constant blended colors.
18 SkAssertResult(paint.isConstantBlendedColor(&constantColor));
19 return constantColor;
20}
21
22GrStrokeTessellateOp::GrStrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix,
23 const SkPath& path, const SkStrokeRec& stroke,
24 GrPaint&& paint)
25 : GrDrawOp(ClassID())
26 , fPathStrokes(path, stroke)
27 , fTotalCombinedVerbCnt(path.countVerbs())
28 , fAAType(aaType)
29 , fColor(get_paint_constant_blended_color(paint))
30 , fProcessors(std::move(paint)) {
31 SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples support yet.
32 if (stroke.getJoin() == SkPaint::kMiter_Join) {
33 float miter = stroke.getMiter();
34 if (miter <= 0) {
35 fPathStrokes.head().fStroke.setStrokeParams(stroke.getCap(), SkPaint::kBevel_Join, 0);
36 } else {
37 fMiterLimitOrZero = miter;
38 }
39 }
40 if (!(viewMatrix.getType() & ~SkMatrix::kScale_Mask) &&
41 viewMatrix.getScaleX() == viewMatrix.getScaleY()) {
42 fMatrixScale = viewMatrix.getScaleX();
43 fSkewMatrix = SkMatrix::I();
44 } else {
45 SkASSERT(!viewMatrix.hasPerspective()); // getMaxScale() doesn't work with perspective.
46 fMatrixScale = viewMatrix.getMaxScale();
47 float invScale = SkScalarInvert(fMatrixScale);
48 fSkewMatrix = viewMatrix;
49 fSkewMatrix.preScale(invScale, invScale);
50 }
51 SkASSERT(fMatrixScale >= 0);
52 SkRect devBounds = fPathStrokes.head().fPath.getBounds();
53 float inflationRadius = fPathStrokes.head().fStroke.getInflationRadius();
54 devBounds.outset(inflationRadius, inflationRadius);
55 viewMatrix.mapRect(&devBounds, devBounds);
56 this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
57}
58
59GrDrawOp::FixedFunctionFlags GrStrokeTessellateOp::fixedFunctionFlags() const {
60 auto flags = FixedFunctionFlags::kNone;
61 if (GrAAType::kNone != fAAType) {
62 flags |= FixedFunctionFlags::kUsesHWAA;
63 }
64 return flags;
65}
66
67GrProcessorSet::Analysis GrStrokeTessellateOp::finalize(const GrCaps& caps,
68 const GrAppliedClip* clip,
69 bool hasMixedSampledCoverage,
70 GrClampType clampType) {
71 return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip,
72 &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps,
73 clampType, &fColor);
74}
75
76GrOp::CombineResult GrStrokeTessellateOp::onCombineIfPossible(GrOp* grOp,
77 GrRecordingContext::Arenas* arenas,
78 const GrCaps&) {
79 auto* op = grOp->cast<GrStrokeTessellateOp>();
80 if (fColor != op->fColor ||
81 // TODO: When stroking is finished, we may want to consider whether a unique matrix scale
82 // can be stored with each PathStroke instead. This might improve batching.
83 fMatrixScale != op->fMatrixScale ||
84 fSkewMatrix != op->fSkewMatrix ||
85 fAAType != op->fAAType ||
86 ((fMiterLimitOrZero * op->fMiterLimitOrZero != 0) && // Are both non-zero?
87 fMiterLimitOrZero != op->fMiterLimitOrZero) ||
88 fProcessors != op->fProcessors) {
89 return CombineResult::kCannotCombine;
90 }
91
92 fPathStrokes.concat(std::move(op->fPathStrokes), arenas->recordTimeAllocator());
93 if (op->fMiterLimitOrZero != 0) {
94 SkASSERT(fMiterLimitOrZero == 0 || fMiterLimitOrZero == op->fMiterLimitOrZero);
95 fMiterLimitOrZero = op->fMiterLimitOrZero;
96 }
97 fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
98
99 return CombineResult::kMerged;
100}
101
102void GrStrokeTessellateOp::onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView* writeView,
103 GrAppliedClip*, const GrXferProcessor::DstProxyView&) {
104}
105
106void GrStrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
107 GrStrokePatchBuilder builder(flushState, &fVertexChunks, fMatrixScale, fTotalCombinedVerbCnt);
108 for (auto& [path, stroke] : fPathStrokes) {
109 builder.addPath(path, stroke);
110 }
111}
112
113void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
114 GrPipeline::InitArgs initArgs;
115 if (GrAAType::kNone != fAAType) {
116 initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
117 SkASSERT(flushState->proxy()->numSamples() > 1); // No mixed samples yet.
118 SkASSERT(fAAType != GrAAType::kCoverage); // No mixed samples yet.
119 }
120 initArgs.fCaps = &flushState->caps();
121 initArgs.fDstProxyView = flushState->drawOpArgs().dstProxyView();
122 initArgs.fWriteSwizzle = flushState->drawOpArgs().writeSwizzle();
123 GrPipeline pipeline(initArgs, std::move(fProcessors), flushState->detachAppliedClip());
124
125 GrStrokeTessellateShader strokeShader(fSkewMatrix, fColor, fMiterLimitOrZero);
126 GrPathShader::ProgramInfo programInfo(flushState->writeView(), &pipeline, &strokeShader);
127
128 SkASSERT(chainBounds == this->bounds());
129 flushState->bindPipelineAndScissorClip(programInfo, this->bounds());
130 flushState->bindTextures(strokeShader, nullptr, pipeline);
131
132 for (const auto& chunk : fVertexChunks) {
133 if (chunk.fVertexBuffer) {
134 flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fVertexBuffer));
135 flushState->draw(chunk.fVertexCount, chunk.fBaseVertex);
136 }
137 }
138}
139