• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc.
3  * Copyright 2017 ARM Ltd.
4  *
5  * Use of this source code is governed by a BSD-style license that can be
6  * found in the LICENSE file.
7  */
8 
9 #include "src/gpu/ops/SmallPathRenderer.h"
10 
11 #include "include/core/SkPaint.h"
12 #include "src/core/SkAutoPixmapStorage.h"
13 #include "src/core/SkDistanceFieldGen.h"
14 #include "src/core/SkDraw.h"
15 #include "src/core/SkMatrixPriv.h"
16 #include "src/core/SkMatrixProvider.h"
17 #include "src/core/SkPointPriv.h"
18 #include "src/core/SkRasterClip.h"
19 #include "src/gpu/BufferWriter.h"
20 #include "src/gpu/GrBuffer.h"
21 #include "src/gpu/GrCaps.h"
22 #include "src/gpu/GrDistanceFieldGenFromVector.h"
23 #include "src/gpu/GrDrawOpTest.h"
24 #include "src/gpu/GrResourceProvider.h"
25 #include "src/gpu/effects/GrBitmapTextGeoProc.h"
26 #include "src/gpu/effects/GrDistanceFieldGeoProc.h"
27 #include "src/gpu/geometry/GrQuad.h"
28 #include "src/gpu/geometry/GrStyledShape.h"
29 #include "src/gpu/ops/GrMeshDrawOp.h"
30 #include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
31 #include "src/gpu/ops/SmallPathAtlasMgr.h"
32 #include "src/gpu/ops/SmallPathShapeData.h"
33 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
34 
35 namespace skgpu::v1 {
36 
37 namespace {
38 
39 // mip levels
40 static constexpr SkScalar kIdealMinMIP = 12;
41 static constexpr SkScalar kMaxMIP = 162;
42 
43 static constexpr SkScalar kMaxDim = 73;
44 static constexpr SkScalar kMinSize = SK_ScalarHalf;
45 static constexpr SkScalar kMaxSize = 2*kMaxMIP;
46 
47 ////////////////////////////////////////////////////////////////////////////////
48 
49 // padding around path bounds to allow for antialiased pixels
50 static const int kAntiAliasPad = 1;
51 
52 class SmallPathOp final : public GrMeshDrawOp {
53 private:
54     using Helper = GrSimpleMeshDrawOpHelperWithStencil;
55 
56 public:
57     DEFINE_OP_CLASS_ID
58 
Make(GrRecordingContext * context,GrPaint && paint,const GrStyledShape & shape,const SkMatrix & viewMatrix,bool gammaCorrect,const GrUserStencilSettings * stencilSettings)59     static GrOp::Owner Make(GrRecordingContext* context,
60                             GrPaint&& paint,
61                             const GrStyledShape& shape,
62                             const SkMatrix& viewMatrix,
63                             bool gammaCorrect,
64                             const GrUserStencilSettings* stencilSettings) {
65         return Helper::FactoryHelper<SmallPathOp>(context, std::move(paint), shape, viewMatrix,
66                                                   gammaCorrect, stencilSettings);
67     }
68 
SmallPathOp(GrProcessorSet * processorSet,const SkPMColor4f & color,const GrStyledShape & shape,const SkMatrix & viewMatrix,bool gammaCorrect,const GrUserStencilSettings * stencilSettings)69     SmallPathOp(GrProcessorSet* processorSet, const SkPMColor4f& color, const GrStyledShape& shape,
70                 const SkMatrix& viewMatrix, bool gammaCorrect,
71                 const GrUserStencilSettings* stencilSettings)
72             : INHERITED(ClassID())
73             , fHelper(processorSet, GrAAType::kCoverage, stencilSettings) {
74         SkASSERT(shape.hasUnstyledKey());
75         // Compute bounds
76         this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsHairline::kNo);
77 
78 #if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
79         fUsesDistanceField = true;
80 #else
81         // only use distance fields on desktop and Android framework to save space in the atlas
82         fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP;
83 #endif
84         // always use distance fields if in perspective
85         fUsesDistanceField = fUsesDistanceField || viewMatrix.hasPerspective();
86 
87         fShapes.emplace_back(Entry{color, shape, viewMatrix});
88 
89         fGammaCorrect = gammaCorrect;
90     }
91 
name() const92     const char* name() const override { return "SmallPathOp"; }
93 
visitProxies(const GrVisitProxyFunc & func) const94     void visitProxies(const GrVisitProxyFunc& func) const override {
95         fHelper.visitProxies(func);
96     }
97 
fixedFunctionFlags() const98     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
99 
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)100     GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
101                                       GrClampType clampType) override {
102         return fHelper.finalizeProcessors(caps, clip, clampType,
103                                           GrProcessorAnalysisCoverage::kSingleChannel,
104                                           &fShapes.front().fColor, &fWideColor);
105     }
106 
107 private:
108     struct FlushInfo {
109         sk_sp<const GrBuffer> fVertexBuffer;
110         sk_sp<const GrBuffer> fIndexBuffer;
111         GrGeometryProcessor*  fGeometryProcessor;
112         const GrSurfaceProxy** fPrimProcProxies;
113         int fVertexOffset;
114         int fInstancesToFlush;
115     };
116 
programInfo()117     GrProgramInfo* programInfo() override {
118         // TODO [PI]: implement
119         return nullptr;
120     }
121 
onCreateProgramInfo(const GrCaps *,SkArenaAlloc *,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip &&,const GrDstProxyView &,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)122     void onCreateProgramInfo(const GrCaps*,
123                              SkArenaAlloc*,
124                              const GrSurfaceProxyView& writeView,
125                              bool usesMSAASurface,
126                              GrAppliedClip&&,
127                              const GrDstProxyView&,
128                              GrXferBarrierFlags renderPassXferBarriers,
129                              GrLoadOp colorLoadOp) override {
130         // We cannot surface the SmallPathOp's programInfo at record time. As currently
131         // implemented, the GP is modified at flush time based on the number of pages in the
132         // atlas.
133     }
134 
onPrePrepareDraws(GrRecordingContext *,const GrSurfaceProxyView & writeView,GrAppliedClip *,const GrDstProxyView &,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)135     void onPrePrepareDraws(GrRecordingContext*,
136                            const GrSurfaceProxyView& writeView,
137                            GrAppliedClip*,
138                            const GrDstProxyView&,
139                            GrXferBarrierFlags renderPassXferBarriers,
140                            GrLoadOp colorLoadOp) override {
141         // TODO [PI]: implement
142     }
143 
onPrepareDraws(GrMeshDrawTarget * target)144     void onPrepareDraws(GrMeshDrawTarget* target) override {
145         int instanceCount = fShapes.count();
146 
147         auto atlasMgr = target->smallPathAtlasManager();
148         if (!atlasMgr) {
149             return;
150         }
151 
152         static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures;
153         static_assert(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures);
154 
155         FlushInfo flushInfo;
156         flushInfo.fPrimProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures);
157 
158         int numActiveProxies;
159         const GrSurfaceProxyView* views = atlasMgr->getViews(&numActiveProxies);
160         for (int i = 0; i < numActiveProxies; ++i) {
161             // This op does not know its atlas proxies when it is added to a OpsTasks, so the
162             // proxies don't get added during the visitProxies call. Thus we add them here.
163             flushInfo.fPrimProcProxies[i] = views[i].proxy();
164             target->sampledProxyArray()->push_back(views[i].proxy());
165         }
166 
167         // Setup GrGeometryProcessor
168         const SkMatrix& ctm = fShapes[0].fViewMatrix;
169         if (fUsesDistanceField) {
170             uint32_t flags = 0;
171             // Still need to key off of ctm to pick the right shader for the transformed quad
172             flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
173             flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
174             flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
175 
176             const SkMatrix* matrix;
177             SkMatrix invert;
178             if (ctm.hasPerspective()) {
179                 matrix = &ctm;
180             } else if (fHelper.usesLocalCoords()) {
181                 if (!ctm.invert(&invert)) {
182                     return;
183                 }
184                 matrix = &invert;
185             } else {
186                 matrix = &SkMatrix::I();
187             }
188             flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
189                     target->allocator(), *target->caps().shaderCaps(), *matrix, fWideColor,
190                     views, numActiveProxies, GrSamplerState::Filter::kLinear,
191                     flags);
192         } else {
193             SkMatrix invert;
194             if (fHelper.usesLocalCoords()) {
195                 if (!ctm.invert(&invert)) {
196                     return;
197                 }
198             }
199 
200             flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
201                     target->allocator(), *target->caps().shaderCaps(), this->color(), fWideColor,
202                     views, numActiveProxies, GrSamplerState::Filter::kNearest,
203                     kA8_GrMaskFormat, invert, false);
204         }
205 
206         // allocate vertices
207         const size_t kVertexStride = flushInfo.fGeometryProcessor->vertexStride();
208 
209         // We need to make sure we don't overflow a 32 bit int when we request space in the
210         // makeVertexSpace call below.
211         if (instanceCount > SK_MaxS32 / GrResourceProvider::NumVertsPerNonAAQuad()) {
212             return;
213         }
214         VertexWriter vertices{target->makeVertexSpace(
215             kVertexStride, GrResourceProvider::NumVertsPerNonAAQuad() * instanceCount,
216             &flushInfo.fVertexBuffer, &flushInfo.fVertexOffset)};
217 
218         flushInfo.fIndexBuffer = target->resourceProvider()->refNonAAQuadIndexBuffer();
219         if (!vertices || !flushInfo.fIndexBuffer) {
220             SkDebugf("Could not allocate vertices\n");
221             return;
222         }
223 
224         flushInfo.fInstancesToFlush = 0;
225         for (int i = 0; i < instanceCount; i++) {
226             const Entry& args = fShapes[i];
227 
228             skgpu::v1::SmallPathShapeData* shapeData;
229             if (fUsesDistanceField) {
230                 // get mip level
231                 SkScalar maxScale;
232                 const SkRect& bounds = args.fShape.bounds();
233                 if (args.fViewMatrix.hasPerspective()) {
234                     // approximate the scale since we can't get it from the matrix
235                     SkRect xformedBounds;
236                     args.fViewMatrix.mapRect(&xformedBounds, bounds);
237                     maxScale = SkScalarAbs(std::max(xformedBounds.width() / bounds.width(),
238                                                   xformedBounds.height() / bounds.height()));
239                 } else {
240                     maxScale = SkScalarAbs(args.fViewMatrix.getMaxScale());
241                 }
242                 SkScalar maxDim = std::max(bounds.width(), bounds.height());
243                 // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.)
244                 // In the majority of cases this will yield a crisper rendering.
245                 SkScalar mipScale = 1.0f;
246                 // Our mipscale is the maxScale clamped to the next highest power of 2
247                 if (maxScale <= SK_ScalarHalf) {
248                     SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
249                     mipScale = SkScalarPow(2, -log);
250                 } else if (maxScale > SK_Scalar1) {
251                     SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
252                     mipScale = SkScalarPow(2, log);
253                 }
254                 // Log2 isn't very precise at values close to a power of 2,
255                 // so add a little tolerance here. A little bit of scaling up is fine.
256                 SkASSERT(maxScale <= mipScale + SK_ScalarNearlyZero);
257 
258                 SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
259                 // For sizes less than kIdealMinMIP we want to use as large a distance field as we can
260                 // so we can preserve as much detail as possible. However, we can't scale down more
261                 // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
262                 // just bigger than the ideal, and then scale down until we are no more than 4x the
263                 // original mipsize.
264                 if (mipSize < kIdealMinMIP) {
265                     SkScalar newMipSize = mipSize;
266                     do {
267                         newMipSize *= 2;
268                     } while (newMipSize < kIdealMinMIP);
269                     while (newMipSize > 4 * mipSize) {
270                         newMipSize *= 0.25f;
271                     }
272                     mipSize = newMipSize;
273                 }
274 
275                 SkScalar desiredDimension = std::min(mipSize, kMaxMIP);
276                 int ceilDesiredDimension = SkScalarCeilToInt(desiredDimension);
277 
278                 // check to see if df path is cached
279                 shapeData = atlasMgr->findOrCreate(args.fShape, ceilDesiredDimension);
280                 if (!shapeData->fAtlasLocator.plotLocator().isValid()) {
281                     SkScalar scale = desiredDimension / maxDim;
282 
283                     if (!this->addDFPathToAtlas(target,
284                                                 &flushInfo,
285                                                 atlasMgr,
286                                                 shapeData,
287                                                 args.fShape,
288                                                 ceilDesiredDimension,
289                                                 scale)) {
290                         atlasMgr->deleteCacheEntry(shapeData);
291                         continue;
292                     }
293                 }
294             } else {
295                 // check to see if bitmap path is cached
296                 shapeData = atlasMgr->findOrCreate(args.fShape, args.fViewMatrix);
297                 if (!shapeData->fAtlasLocator.plotLocator().isValid()) {
298                     if (!this->addBMPathToAtlas(target,
299                                                 &flushInfo,
300                                                 atlasMgr,
301                                                 shapeData,
302                                                 args.fShape,
303                                                 args.fViewMatrix)) {
304                         atlasMgr->deleteCacheEntry(shapeData);
305                         continue;
306                     }
307                 }
308             }
309 
310             auto uploadTarget = target->deferredUploadTarget();
311             atlasMgr->setUseToken(shapeData, uploadTarget->tokenTracker()->nextDrawToken());
312 
313             this->writePathVertices(vertices, GrVertexColor(args.fColor, fWideColor),
314                                     args.fViewMatrix, shapeData);
315             flushInfo.fInstancesToFlush++;
316         }
317 
318         this->flush(target, &flushInfo);
319     }
320 
addToAtlasWithRetry(GrMeshDrawTarget * target,FlushInfo * flushInfo,skgpu::v1::SmallPathAtlasMgr * atlasMgr,int width,int height,const void * image,const SkRect & bounds,int srcInset,skgpu::v1::SmallPathShapeData * shapeData) const321     bool addToAtlasWithRetry(GrMeshDrawTarget* target,
322                              FlushInfo* flushInfo,
323                              skgpu::v1::SmallPathAtlasMgr* atlasMgr,
324                              int width, int height, const void* image,
325                              const SkRect& bounds, int srcInset,
326                              skgpu::v1::SmallPathShapeData* shapeData) const {
327         auto resourceProvider = target->resourceProvider();
328         auto uploadTarget = target->deferredUploadTarget();
329 
330         auto code = atlasMgr->addToAtlas(resourceProvider, uploadTarget, width, height,
331                                          image, &shapeData->fAtlasLocator);
332         if (GrDrawOpAtlas::ErrorCode::kError == code) {
333             return false;
334         }
335 
336         if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) {
337             this->flush(target, flushInfo);
338 
339             code = atlasMgr->addToAtlas(resourceProvider, uploadTarget, width, height,
340                                         image, &shapeData->fAtlasLocator);
341         }
342 
343         shapeData->fAtlasLocator.insetSrc(srcInset);
344         shapeData->fBounds = bounds;
345 
346         return GrDrawOpAtlas::ErrorCode::kSucceeded == code;
347     }
348 
addDFPathToAtlas(GrMeshDrawTarget * target,FlushInfo * flushInfo,skgpu::v1::SmallPathAtlasMgr * atlasMgr,skgpu::v1::SmallPathShapeData * shapeData,const GrStyledShape & shape,uint32_t dimension,SkScalar scale) const349     bool addDFPathToAtlas(GrMeshDrawTarget* target,
350                           FlushInfo* flushInfo,
351                           skgpu::v1::SmallPathAtlasMgr* atlasMgr,
352                           skgpu::v1::SmallPathShapeData* shapeData,
353                           const GrStyledShape& shape,
354                           uint32_t dimension,
355                           SkScalar scale) const {
356 
357         const SkRect& bounds = shape.bounds();
358 
359         // generate bounding rect for bitmap draw
360         SkRect scaledBounds = bounds;
361         // scale to mip level size
362         scaledBounds.fLeft *= scale;
363         scaledBounds.fTop *= scale;
364         scaledBounds.fRight *= scale;
365         scaledBounds.fBottom *= scale;
366         // subtract out integer portion of origin
367         // (SDF created will be placed with fractional offset burnt in)
368         SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft);
369         SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop);
370         scaledBounds.offset(-dx, -dy);
371         // get integer boundary
372         SkIRect devPathBounds;
373         scaledBounds.roundOut(&devPathBounds);
374         // place devBounds at origin with padding to allow room for antialiasing
375         int width = devPathBounds.width() + 2 * kAntiAliasPad;
376         int height = devPathBounds.height() + 2 * kAntiAliasPad;
377         devPathBounds = SkIRect::MakeWH(width, height);
378         SkScalar translateX = kAntiAliasPad - dx;
379         SkScalar translateY = kAntiAliasPad - dy;
380 
381         // draw path to bitmap
382         SkMatrix drawMatrix;
383         drawMatrix.setScale(scale, scale);
384         drawMatrix.postTranslate(translateX, translateY);
385 
386         SkASSERT(devPathBounds.fLeft == 0);
387         SkASSERT(devPathBounds.fTop == 0);
388         SkASSERT(devPathBounds.width() > 0);
389         SkASSERT(devPathBounds.height() > 0);
390 
391         // setup signed distance field storage
392         SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad);
393         width = dfBounds.width();
394         height = dfBounds.height();
395         // TODO We should really generate this directly into the plot somehow
396         SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
397 
398         SkPath path;
399         shape.asPath(&path);
400         // Generate signed distance field directly from SkPath
401         bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(),
402                                                        path, drawMatrix, width, height,
403                                                        width * sizeof(unsigned char));
404         if (!succeed) {
405             // setup bitmap backing
406             SkAutoPixmapStorage dst;
407             if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), devPathBounds.height()))) {
408                 return false;
409             }
410             sk_bzero(dst.writable_addr(), dst.computeByteSize());
411 
412             // rasterize path
413             SkPaint paint;
414             paint.setStyle(SkPaint::kFill_Style);
415             paint.setAntiAlias(true);
416 
417             SkDraw draw;
418 
419             SkRasterClip rasterClip;
420             rasterClip.setRect(devPathBounds);
421             draw.fRC = &rasterClip;
422             SkSimpleMatrixProvider matrixProvider(drawMatrix);
423             draw.fMatrixProvider = &matrixProvider;
424             draw.fDst = dst;
425 
426             draw.drawPathCoverage(path, paint);
427 
428             // Generate signed distance field
429             SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
430                                                (const unsigned char*)dst.addr(),
431                                                dst.width(), dst.height(), dst.rowBytes());
432         }
433 
434         SkRect drawBounds = SkRect::Make(devPathBounds).makeOffset(-translateX, -translateY);
435         drawBounds.fLeft /= scale;
436         drawBounds.fTop /= scale;
437         drawBounds.fRight /= scale;
438         drawBounds.fBottom /= scale;
439 
440         return this->addToAtlasWithRetry(target, flushInfo, atlasMgr,
441                                          width, height, dfStorage.get(),
442                                          drawBounds, SK_DistanceFieldPad, shapeData);
443     }
444 
addBMPathToAtlas(GrMeshDrawTarget * target,FlushInfo * flushInfo,skgpu::v1::SmallPathAtlasMgr * atlasMgr,skgpu::v1::SmallPathShapeData * shapeData,const GrStyledShape & shape,const SkMatrix & ctm) const445     bool addBMPathToAtlas(GrMeshDrawTarget* target,
446                           FlushInfo* flushInfo,
447                           skgpu::v1::SmallPathAtlasMgr* atlasMgr,
448                           skgpu::v1::SmallPathShapeData* shapeData,
449                           const GrStyledShape& shape,
450                           const SkMatrix& ctm) const {
451         const SkRect& bounds = shape.bounds();
452         if (bounds.isEmpty()) {
453             return false;
454         }
455         SkMatrix drawMatrix(ctm);
456         SkScalar tx = ctm.getTranslateX();
457         SkScalar ty = ctm.getTranslateY();
458         tx -= SkScalarFloorToScalar(tx);
459         ty -= SkScalarFloorToScalar(ty);
460         drawMatrix.set(SkMatrix::kMTransX, tx);
461         drawMatrix.set(SkMatrix::kMTransY, ty);
462         SkRect shapeDevBounds;
463         drawMatrix.mapRect(&shapeDevBounds, bounds);
464         SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft);
465         SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop);
466 
467         // get integer boundary
468         SkIRect devPathBounds;
469         shapeDevBounds.roundOut(&devPathBounds);
470         // place devBounds at origin with padding to allow room for antialiasing
471         int width = devPathBounds.width() + 2 * kAntiAliasPad;
472         int height = devPathBounds.height() + 2 * kAntiAliasPad;
473         devPathBounds = SkIRect::MakeWH(width, height);
474         SkScalar translateX = kAntiAliasPad - dx;
475         SkScalar translateY = kAntiAliasPad - dy;
476 
477         SkASSERT(devPathBounds.fLeft == 0);
478         SkASSERT(devPathBounds.fTop == 0);
479         SkASSERT(devPathBounds.width() > 0);
480         SkASSERT(devPathBounds.height() > 0);
481 
482         SkPath path;
483         shape.asPath(&path);
484         // setup bitmap backing
485         SkAutoPixmapStorage dst;
486         if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), devPathBounds.height()))) {
487             return false;
488         }
489         sk_bzero(dst.writable_addr(), dst.computeByteSize());
490 
491         // rasterize path
492         SkPaint paint;
493         paint.setStyle(SkPaint::kFill_Style);
494         paint.setAntiAlias(true);
495 
496         SkDraw draw;
497 
498         SkRasterClip rasterClip;
499         rasterClip.setRect(devPathBounds);
500         draw.fRC = &rasterClip;
501         drawMatrix.postTranslate(translateX, translateY);
502         SkSimpleMatrixProvider matrixProvider(drawMatrix);
503         draw.fMatrixProvider = &matrixProvider;
504         draw.fDst = dst;
505 
506         draw.drawPathCoverage(path, paint);
507 
508         SkRect drawBounds = SkRect::Make(devPathBounds).makeOffset(-translateX, -translateY);
509 
510         return this->addToAtlasWithRetry(target, flushInfo, atlasMgr,
511                                          dst.width(), dst.height(), dst.addr(),
512                                          drawBounds, 0, shapeData);
513     }
514 
writePathVertices(VertexWriter & vertices,const GrVertexColor & color,const SkMatrix & ctm,const skgpu::v1::SmallPathShapeData * shapeData) const515     void writePathVertices(VertexWriter& vertices,
516                            const GrVertexColor& color,
517                            const SkMatrix& ctm,
518                            const skgpu::v1::SmallPathShapeData* shapeData) const {
519         SkRect translatedBounds(shapeData->fBounds);
520         if (!fUsesDistanceField) {
521             translatedBounds.offset(SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransX)),
522                                     SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransY)));
523         }
524 
525         // set up texture coordinates
526         auto texCoords = VertexWriter::TriStripFromUVs(shapeData->fAtlasLocator.getUVs());
527 
528         if (fUsesDistanceField && !ctm.hasPerspective()) {
529             vertices.writeQuad(GrQuad::MakeFromRect(translatedBounds, ctm),
530                                color,
531                                texCoords);
532         } else {
533             vertices.writeQuad(VertexWriter::TriStripFromRect(translatedBounds),
534                                color,
535                                texCoords);
536         }
537     }
538 
flush(GrMeshDrawTarget * target,FlushInfo * flushInfo) const539     void flush(GrMeshDrawTarget* target, FlushInfo* flushInfo) const {
540         auto atlasMgr = target->smallPathAtlasManager();
541         if (!atlasMgr) {
542             return;
543         }
544 
545         int numActiveProxies;
546         const GrSurfaceProxyView* views = atlasMgr->getViews(&numActiveProxies);
547 
548         GrGeometryProcessor* gp = flushInfo->fGeometryProcessor;
549         if (gp->numTextureSamplers() != numActiveProxies) {
550             for (int i = gp->numTextureSamplers(); i < numActiveProxies; ++i) {
551                 flushInfo->fPrimProcProxies[i] = views[i].proxy();
552                 // This op does not know its atlas proxies when it is added to a OpsTasks, so the
553                 // proxies don't get added during the visitProxies call. Thus we add them here.
554                 target->sampledProxyArray()->push_back(views[i].proxy());
555             }
556             // During preparation the number of atlas pages has increased.
557             // Update the proxies used in the GP to match.
558             if (fUsesDistanceField) {
559                 reinterpret_cast<GrDistanceFieldPathGeoProc*>(gp)->addNewViews(
560                         views, numActiveProxies, GrSamplerState::Filter::kLinear);
561             } else {
562                 reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews(
563                         views, numActiveProxies, GrSamplerState::Filter::kNearest);
564             }
565         }
566 
567         if (flushInfo->fInstancesToFlush) {
568             GrSimpleMesh* mesh = target->allocMesh();
569             mesh->setIndexedPatterned(flushInfo->fIndexBuffer,
570                                       GrResourceProvider::NumIndicesPerNonAAQuad(),
571                                       flushInfo->fInstancesToFlush,
572                                       GrResourceProvider::MaxNumNonAAQuads(),
573                                       flushInfo->fVertexBuffer,
574                                       GrResourceProvider::NumVertsPerNonAAQuad(),
575                                       flushInfo->fVertexOffset);
576             target->recordDraw(flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fPrimProcProxies,
577                                GrPrimitiveType::kTriangles);
578             flushInfo->fVertexOffset += GrResourceProvider::NumVertsPerNonAAQuad() *
579                                         flushInfo->fInstancesToFlush;
580             flushInfo->fInstancesToFlush = 0;
581         }
582     }
583 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)584     void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
585         auto pipeline = fHelper.createPipeline(flushState);
586 
587         flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, pipeline,
588                                                         fHelper.stencilSettings());
589     }
590 
color() const591     const SkPMColor4f& color() const { return fShapes[0].fColor; }
usesDistanceField() const592     bool usesDistanceField() const { return fUsesDistanceField; }
593 
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)594     CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
595         SmallPathOp* that = t->cast<SmallPathOp>();
596         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
597             return CombineResult::kCannotCombine;
598         }
599 
600         if (this->usesDistanceField() != that->usesDistanceField()) {
601             return CombineResult::kCannotCombine;
602         }
603 
604         const SkMatrix& thisCtm = this->fShapes[0].fViewMatrix;
605         const SkMatrix& thatCtm = that->fShapes[0].fViewMatrix;
606 
607         if (thisCtm.hasPerspective() != thatCtm.hasPerspective()) {
608             return CombineResult::kCannotCombine;
609         }
610 
611         // We can position on the cpu unless we're in perspective,
612         // but also need to make sure local matrices are identical
613         if ((thisCtm.hasPerspective() || fHelper.usesLocalCoords()) &&
614             !SkMatrixPriv::CheapEqual(thisCtm, thatCtm)) {
615             return CombineResult::kCannotCombine;
616         }
617 
618         // Depending on the ctm we may have a different shader for SDF paths
619         if (this->usesDistanceField()) {
620             if (thisCtm.isScaleTranslate() != thatCtm.isScaleTranslate() ||
621                 thisCtm.isSimilarity() != thatCtm.isSimilarity()) {
622                 return CombineResult::kCannotCombine;
623             }
624         }
625 
626         fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
627         fWideColor |= that->fWideColor;
628         return CombineResult::kMerged;
629     }
630 
631 #if GR_TEST_UTILS
onDumpInfo() const632     SkString onDumpInfo() const override {
633         SkString string;
634         for (const auto& geo : fShapes) {
635             string.appendf("Color: 0x%08x\n", geo.fColor.toBytes_RGBA());
636         }
637         string += fHelper.dumpInfo();
638         return string;
639     }
640 #endif
641 
642     bool fUsesDistanceField;
643 
644     struct Entry {
645         SkPMColor4f   fColor;
646         GrStyledShape fShape;
647         SkMatrix      fViewMatrix;
648     };
649 
650     SkSTArray<1, Entry> fShapes;
651     Helper fHelper;
652     bool fGammaCorrect;
653     bool fWideColor;
654 
655     using INHERITED = GrMeshDrawOp;
656 };
657 
658 } // anonymous namespace
659 
660 ///////////////////////////////////////////////////////////////////////////////////////////////////
661 
onCanDrawPath(const CanDrawPathArgs & args) const662 PathRenderer::CanDrawPath SmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
663     if (!args.fCaps->shaderCaps()->shaderDerivativeSupport()) {
664         return CanDrawPath::kNo;
665     }
666     // If the shape has no key then we won't get any reuse.
667     if (!args.fShape->hasUnstyledKey()) {
668         return CanDrawPath::kNo;
669     }
670     // This only supports filled paths, however, the caller may apply the style to make a filled
671     // path and try again.
672     if (!args.fShape->style().isSimpleFill()) {
673         return CanDrawPath::kNo;
674     }
675     // This does non-inverse coverage-based antialiased fills.
676     if (GrAAType::kCoverage != args.fAAType) {
677         return CanDrawPath::kNo;
678     }
679     // TODO: Support inverse fill
680     if (args.fShape->inverseFilled()) {
681         return CanDrawPath::kNo;
682     }
683 
684     SkScalar scaleFactors[2] = { 1, 1 };
685     // TODO: handle perspective distortion
686     if (!args.fViewMatrix->hasPerspective() && !args.fViewMatrix->getMinMaxScales(scaleFactors)) {
687         return CanDrawPath::kNo;
688     }
689     // For affine transformations, too much shear can produce artifacts.
690     if (!scaleFactors[0] || scaleFactors[1]/scaleFactors[0] > 4) {
691         return CanDrawPath::kNo;
692     }
693     // Only support paths with bounds within kMaxDim by kMaxDim,
694     // scaled to have bounds within kMaxSize by kMaxSize.
695     // The goal is to accelerate rendering of lots of small paths that may be scaling.
696     SkRect bounds = args.fShape->styledBounds();
697     SkScalar minDim = std::min(bounds.width(), bounds.height());
698     SkScalar maxDim = std::max(bounds.width(), bounds.height());
699     SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]);
700     SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]);
701     if (maxDim > kMaxDim || kMinSize > minSize || maxSize > kMaxSize) {
702         return CanDrawPath::kNo;
703     }
704 
705     return CanDrawPath::kYes;
706 }
707 
onDrawPath(const DrawPathArgs & args)708 bool SmallPathRenderer::onDrawPath(const DrawPathArgs& args) {
709     GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
710                               "SmallPathRenderer::onDrawPath");
711 
712     // we've already bailed on inverse filled paths, so this is safe
713     SkASSERT(!args.fShape->isEmpty());
714     SkASSERT(args.fShape->hasUnstyledKey());
715 
716     GrOp::Owner op = SmallPathOp::Make(
717             args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix,
718             args.fGammaCorrect, args.fUserStencilSettings);
719     args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op));
720 
721     return true;
722 }
723 
724 } // namespace skgpu::v1
725 
726 #if GR_TEST_UTILS
727 
GR_DRAW_OP_TEST_DEFINE(SmallPathOp)728 GR_DRAW_OP_TEST_DEFINE(SmallPathOp) {
729     SkMatrix viewMatrix = GrTest::TestMatrix(random);
730     bool gammaCorrect = random->nextBool();
731 
732     // This path renderer only allows fill styles.
733     GrStyledShape shape(GrTest::TestPath(random), GrStyle::SimpleFill());
734     return skgpu::v1::SmallPathOp::Make(context, std::move(paint), shape, viewMatrix, gammaCorrect,
735                                         GrGetRandomStencil(random, context));
736 }
737 
738 #endif // GR_TEST_UTILS
739