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