1 /*
2 * Copyright 2015 Google Inc.
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/ganesh/ops/AALinearizingConvexPathRenderer.h"
9
10 #include "include/core/SkString.h"
11 #include "src/core/SkGeometry.h"
12 #include "src/core/SkPathPriv.h"
13 #include "src/core/SkTraceEvent.h"
14 #include "src/gpu/BufferWriter.h"
15 #include "src/gpu/ganesh/GrAuditTrail.h"
16 #include "src/gpu/ganesh/GrCaps.h"
17 #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h"
18 #include "src/gpu/ganesh/GrDrawOpTest.h"
19 #include "src/gpu/ganesh/GrGeometryProcessor.h"
20 #include "src/gpu/ganesh/GrOpFlushState.h"
21 #include "src/gpu/ganesh/GrProcessor.h"
22 #include "src/gpu/ganesh/GrProgramInfo.h"
23 #include "src/gpu/ganesh/GrStyle.h"
24 #include "src/gpu/ganesh/SurfaceDrawContext.h"
25 #include "src/gpu/ganesh/geometry/GrAAConvexTessellator.h"
26 #include "src/gpu/ganesh/geometry/GrPathUtils.h"
27 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
28 #include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
29 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
30
31 using namespace skia_private;
32
33 ///////////////////////////////////////////////////////////////////////////////
34 namespace skgpu::ganesh {
35
36 namespace {
37
38 static const int DEFAULT_BUFFER_SIZE = 100;
39
40 // The thicker the stroke, the harder it is to produce high-quality results using tessellation. For
41 // the time being, we simply drop back to software rendering above this stroke width.
42 static const SkScalar kMaxStrokeWidth = 20.0;
43
44 // extract the result vertices and indices from the GrAAConvexTessellator
extract_verts(const GrAAConvexTessellator & tess,const SkMatrix * localCoordsMatrix,void * vertData,const VertexColor & color,uint16_t firstIndex,uint16_t * idxs)45 void extract_verts(const GrAAConvexTessellator& tess,
46 const SkMatrix* localCoordsMatrix,
47 void* vertData,
48 const VertexColor& color,
49 uint16_t firstIndex,
50 uint16_t* idxs) {
51 VertexWriter verts{vertData};
52 for (int i = 0; i < tess.numPts(); ++i) {
53 SkPoint lc;
54 if (localCoordsMatrix) {
55 localCoordsMatrix->mapPoints(&lc, &tess.point(i), 1);
56 }
57 verts << tess.point(i) << color << VertexWriter::If(localCoordsMatrix, lc)
58 << tess.coverage(i);
59 }
60
61 for (int i = 0; i < tess.numIndices(); ++i) {
62 idxs[i] = tess.index(i) + firstIndex;
63 }
64 }
65
create_lines_only_gp(SkArenaAlloc * arena,bool tweakAlphaForCoverage,bool usesLocalCoords,bool wideColor)66 GrGeometryProcessor* create_lines_only_gp(SkArenaAlloc* arena,
67 bool tweakAlphaForCoverage,
68 bool usesLocalCoords,
69 bool wideColor) {
70 using namespace GrDefaultGeoProcFactory;
71
72 Coverage::Type coverageType =
73 tweakAlphaForCoverage ? Coverage::kAttributeTweakAlpha_Type : Coverage::kAttribute_Type;
74 LocalCoords::Type localCoordsType =
75 usesLocalCoords ? LocalCoords::kHasExplicit_Type : LocalCoords::kUnused_Type;
76 Color::Type colorType =
77 wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type;
78
79 return Make(arena, colorType, coverageType, localCoordsType, SkMatrix::I());
80 }
81
82 class AAFlatteningConvexPathOp final : public GrMeshDrawOp {
83 private:
84 using Helper = GrSimpleMeshDrawOpHelperWithStencil;
85
86 public:
87 DEFINE_OP_CLASS_ID
88
Make(GrRecordingContext * context,GrPaint && paint,const SkMatrix & viewMatrix,const SkPath & path,SkScalar strokeWidth,SkStrokeRec::Style style,SkPaint::Join join,SkScalar miterLimit,const GrUserStencilSettings * stencilSettings)89 static GrOp::Owner Make(GrRecordingContext* context,
90 GrPaint&& paint,
91 const SkMatrix& viewMatrix,
92 const SkPath& path,
93 SkScalar strokeWidth,
94 SkStrokeRec::Style style,
95 SkPaint::Join join,
96 SkScalar miterLimit,
97 const GrUserStencilSettings* stencilSettings) {
98 return Helper::FactoryHelper<AAFlatteningConvexPathOp>(context, std::move(paint),
99 viewMatrix, path,
100 strokeWidth, style, join, miterLimit,
101 stencilSettings);
102 }
103
AAFlatteningConvexPathOp(GrProcessorSet * processorSet,const SkPMColor4f & color,const SkMatrix & viewMatrix,const SkPath & path,SkScalar strokeWidth,SkStrokeRec::Style style,SkPaint::Join join,SkScalar miterLimit,const GrUserStencilSettings * stencilSettings)104 AAFlatteningConvexPathOp(GrProcessorSet* processorSet,
105 const SkPMColor4f& color,
106 const SkMatrix& viewMatrix,
107 const SkPath& path,
108 SkScalar strokeWidth,
109 SkStrokeRec::Style style,
110 SkPaint::Join join,
111 SkScalar miterLimit,
112 const GrUserStencilSettings* stencilSettings)
113 : INHERITED(ClassID()), fHelper(processorSet, GrAAType::kCoverage, stencilSettings) {
114 fPaths.emplace_back(
115 PathData{viewMatrix, path, color, strokeWidth, miterLimit, style, join});
116
117 // compute bounds
118 SkRect bounds = path.getBounds();
119 SkScalar w = strokeWidth;
120 if (w > 0) {
121 w /= 2;
122 SkScalar maxScale = viewMatrix.getMaxScale();
123 // We should not have a perspective matrix, thus we should have a valid scale.
124 SkASSERT(maxScale != -1);
125 if (SkPaint::kMiter_Join == join && w * maxScale > 1.f) {
126 w *= miterLimit;
127 }
128 bounds.outset(w, w);
129 }
130 this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kYes, IsHairline::kNo);
131 }
132
name() const133 const char* name() const override { return "AAFlatteningConvexPathOp"; }
134
visitProxies(const GrVisitProxyFunc & func) const135 void visitProxies(const GrVisitProxyFunc& func) const override {
136 if (fProgramInfo) {
137 fProgramInfo->visitFPProxies(func);
138 } else {
139 fHelper.visitProxies(func);
140 }
141 }
142
fixedFunctionFlags() const143 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
144
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)145 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
146 GrClampType clampType) override {
147 return fHelper.finalizeProcessors(caps, clip, clampType,
148 GrProcessorAnalysisCoverage::kSingleChannel,
149 &fPaths.back().fColor, &fWideColor);
150 }
151
152 private:
programInfo()153 GrProgramInfo* programInfo() override { return fProgramInfo; }
154
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)155 void onCreateProgramInfo(const GrCaps* caps,
156 SkArenaAlloc* arena,
157 const GrSurfaceProxyView& writeView,
158 bool usesMSAASurface,
159 GrAppliedClip&& appliedClip,
160 const GrDstProxyView& dstProxyView,
161 GrXferBarrierFlags renderPassXferBarriers,
162 GrLoadOp colorLoadOp) override {
163 GrGeometryProcessor* gp = create_lines_only_gp(arena,
164 fHelper.compatibleWithCoverageAsAlpha(),
165 fHelper.usesLocalCoords(),
166 fWideColor);
167 if (!gp) {
168 SkDebugf("Couldn't create a GrGeometryProcessor\n");
169 return;
170 }
171
172 fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView, usesMSAASurface,
173 std::move(appliedClip), dstProxyView,
174 gp, GrPrimitiveType::kTriangles,
175 renderPassXferBarriers, colorLoadOp);
176 }
177
recordDraw(GrMeshDrawTarget * target,int vertexCount,size_t vertexStride,void * vertices,int indexCount,uint16_t * indices)178 void recordDraw(GrMeshDrawTarget* target,
179 int vertexCount, size_t vertexStride, void* vertices,
180 int indexCount, uint16_t* indices) {
181 if (vertexCount == 0 || indexCount == 0) {
182 return;
183 }
184 sk_sp<const GrBuffer> vertexBuffer;
185 int firstVertex;
186 void* verts = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer,
187 &firstVertex);
188 if (!verts) {
189 SkDebugf("Could not allocate vertices\n");
190 return;
191 }
192 memcpy(verts, vertices, vertexCount * vertexStride);
193
194 sk_sp<const GrBuffer> indexBuffer;
195 int firstIndex;
196 uint16_t* idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex);
197 if (!idxs) {
198 SkDebugf("Could not allocate indices\n");
199 return;
200 }
201 memcpy(idxs, indices, indexCount * sizeof(uint16_t));
202 GrSimpleMesh* mesh = target->allocMesh();
203 mesh->setIndexed(std::move(indexBuffer), indexCount, firstIndex, 0, vertexCount - 1,
204 GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex);
205 fMeshes.push_back(mesh);
206 }
207
onPrepareDraws(GrMeshDrawTarget * target)208 void onPrepareDraws(GrMeshDrawTarget* target) override {
209 if (!fProgramInfo) {
210 this->createProgramInfo(target);
211 if (!fProgramInfo) {
212 return;
213 }
214 }
215
216 size_t vertexStride = fProgramInfo->geomProc().vertexStride();
217 int instanceCount = fPaths.size();
218
219 int64_t vertexCount = 0;
220 int64_t indexCount = 0;
221 int64_t maxVertices = DEFAULT_BUFFER_SIZE;
222 int64_t maxIndices = DEFAULT_BUFFER_SIZE;
223 uint8_t* vertices = (uint8_t*) sk_malloc_throw(maxVertices * vertexStride);
224 uint16_t* indices = (uint16_t*) sk_malloc_throw(maxIndices * sizeof(uint16_t));
225 for (int i = 0; i < instanceCount; i++) {
226 const PathData& args = fPaths[i];
227 GrAAConvexTessellator tess(args.fStyle, args.fStrokeWidth,
228 args.fJoin, args.fMiterLimit);
229
230 if (!tess.tessellate(args.fViewMatrix, args.fPath)) {
231 continue;
232 }
233
234 int currentVertices = tess.numPts();
235 if (vertexCount + currentVertices > static_cast<int>(UINT16_MAX)) {
236 // if we added the current instance, we would overflow the indices we can store in a
237 // uint16_t. Draw what we've got so far and reset.
238 this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices);
239 vertexCount = 0;
240 indexCount = 0;
241 }
242 if (vertexCount + currentVertices > maxVertices) {
243 maxVertices = std::max(vertexCount + currentVertices, maxVertices * 2);
244 if (maxVertices * vertexStride > SK_MaxS32) {
245 sk_free(vertices);
246 sk_free(indices);
247 return;
248 }
249 vertices = (uint8_t*) sk_realloc_throw(vertices, maxVertices * vertexStride);
250 }
251 int currentIndices = tess.numIndices();
252 if (indexCount + currentIndices > maxIndices) {
253 maxIndices = std::max(indexCount + currentIndices, maxIndices * 2);
254 if (maxIndices * sizeof(uint16_t) > SK_MaxS32) {
255 sk_free(vertices);
256 sk_free(indices);
257 return;
258 }
259 indices = (uint16_t*) sk_realloc_throw(indices, maxIndices * sizeof(uint16_t));
260 }
261
262 const SkMatrix* localCoordsMatrix = nullptr;
263 SkMatrix ivm;
264 if (fHelper.usesLocalCoords()) {
265 if (!args.fViewMatrix.invert(&ivm)) {
266 ivm = SkMatrix::I();
267 }
268 localCoordsMatrix = &ivm;
269 }
270
271 extract_verts(tess, localCoordsMatrix, vertices + vertexStride * vertexCount,
272 VertexColor(args.fColor, fWideColor), vertexCount, indices + indexCount);
273 vertexCount += currentVertices;
274 indexCount += currentIndices;
275 }
276 if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) {
277 this->recordDraw(target, vertexCount, vertexStride, vertices, indexCount, indices);
278 }
279 sk_free(vertices);
280 sk_free(indices);
281 }
282
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)283 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
284 if (!fProgramInfo || fMeshes.empty()) {
285 return;
286 }
287
288 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
289 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
290 for (int i = 0; i < fMeshes.size(); ++i) {
291 flushState->drawMesh(*fMeshes[i]);
292 }
293 }
294
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)295 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
296 AAFlatteningConvexPathOp* that = t->cast<AAFlatteningConvexPathOp>();
297 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
298 return CombineResult::kCannotCombine;
299 }
300
301 fPaths.push_back_n(that->fPaths.size(), that->fPaths.begin());
302 fWideColor |= that->fWideColor;
303 return CombineResult::kMerged;
304 }
305
306 #if defined(GR_TEST_UTILS)
onDumpInfo() const307 SkString onDumpInfo() const override {
308 SkString string;
309 for (const auto& path : fPaths) {
310 string.appendf(
311 "Color: 0x%08x, StrokeWidth: %.2f, Style: %d, Join: %d, "
312 "MiterLimit: %.2f\n",
313 path.fColor.toBytes_RGBA(), path.fStrokeWidth, path.fStyle, path.fJoin,
314 path.fMiterLimit);
315 }
316 string += fHelper.dumpInfo();
317 return string;
318 }
319 #endif
320
321 struct PathData {
322 SkMatrix fViewMatrix;
323 SkPath fPath;
324 SkPMColor4f fColor;
325 SkScalar fStrokeWidth;
326 SkScalar fMiterLimit;
327 SkStrokeRec::Style fStyle;
328 SkPaint::Join fJoin;
329 };
330
331 STArray<1, PathData, true> fPaths;
332 Helper fHelper;
333 bool fWideColor;
334
335 SkTDArray<GrSimpleMesh*> fMeshes;
336 GrProgramInfo* fProgramInfo = nullptr;
337
338 using INHERITED = GrMeshDrawOp;
339 };
340
341 } // anonymous namespace
342
343 ///////////////////////////////////////////////////////////////////////////////////////////////////
344
345 PathRenderer::CanDrawPath
onCanDrawPath(const CanDrawPathArgs & args) const346 AALinearizingConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
347 if (GrAAType::kCoverage != args.fAAType) {
348 return CanDrawPath::kNo;
349 }
350 if (!args.fShape->knownToBeConvex()) {
351 return CanDrawPath::kNo;
352 }
353 if (args.fShape->style().pathEffect()) {
354 return CanDrawPath::kNo;
355 }
356 if (args.fShape->inverseFilled()) {
357 return CanDrawPath::kNo;
358 }
359 if (args.fShape->bounds().width() <= 0 && args.fShape->bounds().height() <= 0) {
360 // Stroked zero length lines should draw, but this PR doesn't handle that case
361 return CanDrawPath::kNo;
362 }
363 const SkStrokeRec& stroke = args.fShape->style().strokeRec();
364
365 if (stroke.getStyle() == SkStrokeRec::kStroke_Style ||
366 stroke.getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
367 if (!args.fViewMatrix->isSimilarity()) {
368 return CanDrawPath::kNo;
369 }
370 SkScalar strokeWidth = args.fViewMatrix->getMaxScale() * stroke.getWidth();
371 if (strokeWidth < 1.0f && stroke.getStyle() == SkStrokeRec::kStroke_Style) {
372 return CanDrawPath::kNo;
373 }
374 if ((strokeWidth > kMaxStrokeWidth && !args.fShape->isRect()) ||
375 !args.fShape->knownToBeClosed() ||
376 stroke.getJoin() == SkPaint::Join::kRound_Join) {
377 return CanDrawPath::kNo;
378 }
379 return CanDrawPath::kYes;
380 }
381 if (stroke.getStyle() != SkStrokeRec::kFill_Style) {
382 return CanDrawPath::kNo;
383 }
384 // This can almost handle perspective. It would need to use 3 component explicit local coords
385 // when there are FPs that require them. This is difficult to test because AAConvexPathRenderer
386 // takes almost all filled paths that could get here. So just avoid perspective fills.
387 if (args.fViewMatrix->hasPerspective()) {
388 return CanDrawPath::kNo;
389 }
390 return CanDrawPath::kYes;
391 }
392
onDrawPath(const DrawPathArgs & args)393 bool AALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) {
394 GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
395 "AALinearizingConvexPathRenderer::onDrawPath");
396 SkASSERT(args.fSurfaceDrawContext->numSamples() <= 1);
397 SkASSERT(!args.fShape->isEmpty());
398 SkASSERT(!args.fShape->style().pathEffect());
399
400 SkPath path;
401 args.fShape->asPath(&path);
402 bool fill = args.fShape->style().isSimpleFill();
403 const SkStrokeRec& stroke = args.fShape->style().strokeRec();
404 SkScalar strokeWidth = fill ? -1.0f : stroke.getWidth();
405 SkPaint::Join join = fill ? SkPaint::Join::kMiter_Join : stroke.getJoin();
406 SkScalar miterLimit = stroke.getMiter();
407
408 GrOp::Owner op = AAFlatteningConvexPathOp::Make(
409 args.fContext, std::move(args.fPaint), *args.fViewMatrix, path, strokeWidth,
410 stroke.getStyle(), join, miterLimit, args.fUserStencilSettings);
411 args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op));
412 return true;
413 }
414
415 } // namespace skgpu::ganesh
416
417 #if defined(GR_TEST_UTILS)
418
GR_DRAW_OP_TEST_DEFINE(AAFlatteningConvexPathOp)419 GR_DRAW_OP_TEST_DEFINE(AAFlatteningConvexPathOp) {
420 SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random);
421 const SkPath& path = GrTest::TestPathConvex(random);
422
423 SkStrokeRec::Style styles[3] = { SkStrokeRec::kFill_Style,
424 SkStrokeRec::kStroke_Style,
425 SkStrokeRec::kStrokeAndFill_Style };
426
427 SkStrokeRec::Style style = styles[random->nextU() % 3];
428
429 SkScalar strokeWidth = -1.f;
430 SkPaint::Join join = SkPaint::kMiter_Join;
431 SkScalar miterLimit = 0.5f;
432
433 if (SkStrokeRec::kFill_Style != style) {
434 strokeWidth = random->nextRangeF(1.0f, 10.0f);
435 if (random->nextBool()) {
436 join = SkPaint::kMiter_Join;
437 } else {
438 join = SkPaint::kBevel_Join;
439 }
440 miterLimit = random->nextRangeF(0.5f, 2.0f);
441 }
442 const GrUserStencilSettings* stencilSettings = GrGetRandomStencil(random, context);
443 return skgpu::ganesh::AAFlatteningConvexPathOp::Make(context,
444 std::move(paint),
445 viewMatrix,
446 path,
447 strokeWidth,
448 style,
449 join,
450 miterLimit,
451 stencilSettings);
452 }
453
454 #endif
455