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