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