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