• 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 "GrSmallPathRenderer.h"
10 
11 #include "GrBuffer.h"
12 #include "GrContext.h"
13 #include "GrDistanceFieldGenFromVector.h"
14 #include "GrDrawOpTest.h"
15 #include "GrOpFlushState.h"
16 #include "GrResourceProvider.h"
17 #include "GrSWMaskHelper.h"
18 #include "GrSimpleMeshDrawOpHelper.h"
19 #include "SkAutoMalloc.h"
20 #include "SkDistanceFieldGen.h"
21 #include "SkPathOps.h"
22 #include "effects/GrBitmapTextGeoProc.h"
23 #include "effects/GrDistanceFieldGeoProc.h"
24 #include "ops/GrMeshDrawOp.h"
25 
26 #define ATLAS_TEXTURE_WIDTH 2048
27 #define ATLAS_TEXTURE_HEIGHT 2048
28 #define PLOT_WIDTH  512
29 #define PLOT_HEIGHT 256
30 
31 #define NUM_PLOTS_X   (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
32 #define NUM_PLOTS_Y   (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
33 
34 #ifdef DF_PATH_TRACKING
35 static int g_NumCachedShapes = 0;
36 static int g_NumFreedShapes = 0;
37 #endif
38 
39 // mip levels
40 static const SkScalar kIdealMinMIP = 12;
41 static const SkScalar kMaxMIP = 162;
42 
43 static const SkScalar kMaxDim = 73;
44 static const SkScalar kMinSize = SK_ScalarHalf;
45 static const SkScalar kMaxSize = 2*kMaxMIP;
46 
47 // Callback to clear out internal path cache when eviction occurs
HandleEviction(GrDrawOpAtlas::AtlasID id,void * pr)48 void GrSmallPathRenderer::HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
49     GrSmallPathRenderer* dfpr = (GrSmallPathRenderer*)pr;
50     // remove any paths that use this plot
51     ShapeDataList::Iter iter;
52     iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
53     ShapeData* shapeData;
54     while ((shapeData = iter.get())) {
55         iter.next();
56         if (id == shapeData->fID) {
57             dfpr->fShapeCache.remove(shapeData->fKey);
58             dfpr->fShapeList.remove(shapeData);
59             delete shapeData;
60 #ifdef DF_PATH_TRACKING
61             ++g_NumFreedPaths;
62 #endif
63         }
64     }
65 }
66 
67 ////////////////////////////////////////////////////////////////////////////////
GrSmallPathRenderer()68 GrSmallPathRenderer::GrSmallPathRenderer() : fAtlas(nullptr) {}
69 
~GrSmallPathRenderer()70 GrSmallPathRenderer::~GrSmallPathRenderer() {
71     ShapeDataList::Iter iter;
72     iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
73     ShapeData* shapeData;
74     while ((shapeData = iter.get())) {
75         iter.next();
76         delete shapeData;
77     }
78 
79 #ifdef DF_PATH_TRACKING
80     SkDebugf("Cached shapes: %d, freed shapes: %d\n", g_NumCachedShapes, g_NumFreedShapes);
81 #endif
82 }
83 
84 ////////////////////////////////////////////////////////////////////////////////
onCanDrawPath(const CanDrawPathArgs & args) const85 bool GrSmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
86     if (!args.fCaps->shaderCaps()->shaderDerivativeSupport()) {
87         return false;
88     }
89     // If the shape has no key then we won't get any reuse.
90     if (!args.fShape->hasUnstyledKey()) {
91         return false;
92     }
93     // This only supports filled paths, however, the caller may apply the style to make a filled
94     // path and try again.
95     if (!args.fShape->style().isSimpleFill()) {
96         return false;
97     }
98     // This does non-inverse coverage-based antialiased fills.
99     if (GrAAType::kCoverage != args.fAAType) {
100         return false;
101     }
102     // TODO: Support inverse fill
103     if (args.fShape->inverseFilled()) {
104         return false;
105     }
106     // currently don't support perspective
107     if (args.fViewMatrix->hasPerspective()) {
108         return false;
109     }
110 
111     // Only support paths with bounds within kMaxDim by kMaxDim,
112     // scaled to have bounds within kMaxSize by kMaxSize.
113     // The goal is to accelerate rendering of lots of small paths that may be scaling.
114     SkScalar scaleFactors[2];
115     if (!args.fViewMatrix->getMinMaxScales(scaleFactors)) {
116         return false;
117     }
118     SkRect bounds = args.fShape->styledBounds();
119     SkScalar minDim = SkMinScalar(bounds.width(), bounds.height());
120     SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
121     SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]);
122     SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]);
123 
124     return maxDim <= kMaxDim && kMinSize <= minSize && maxSize <= kMaxSize;
125 }
126 
127 ////////////////////////////////////////////////////////////////////////////////
128 
129 // padding around path bounds to allow for antialiased pixels
130 static const SkScalar kAntiAliasPad = 1.0f;
131 
132 class GrSmallPathRenderer::SmallPathOp final : public GrMeshDrawOp {
133 private:
134     using Helper = GrSimpleMeshDrawOpHelperWithStencil;
135 
136 public:
137     DEFINE_OP_CLASS_ID
138 
139     using ShapeData = GrSmallPathRenderer::ShapeData;
140     using ShapeCache = SkTDynamicHash<ShapeData, ShapeData::Key>;
141     using ShapeDataList = GrSmallPathRenderer::ShapeDataList;
142 
Make(GrPaint && paint,const GrShape & shape,const SkMatrix & viewMatrix,GrDrawOpAtlas * atlas,ShapeCache * shapeCache,ShapeDataList * shapeList,bool gammaCorrect,const GrUserStencilSettings * stencilSettings)143     static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const GrShape& shape,
144                                           const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas,
145                                           ShapeCache* shapeCache, ShapeDataList* shapeList,
146                                           bool gammaCorrect,
147                                           const GrUserStencilSettings* stencilSettings) {
148         return Helper::FactoryHelper<SmallPathOp>(std::move(paint), shape, viewMatrix, atlas,
149                                                   shapeCache, shapeList, gammaCorrect,
150                                                   stencilSettings);
151     }
152 
SmallPathOp(Helper::MakeArgs helperArgs,GrColor color,const GrShape & shape,const SkMatrix & viewMatrix,GrDrawOpAtlas * atlas,ShapeCache * shapeCache,ShapeDataList * shapeList,bool gammaCorrect,const GrUserStencilSettings * stencilSettings)153     SmallPathOp(Helper::MakeArgs helperArgs, GrColor color, const GrShape& shape,
154                 const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas, ShapeCache* shapeCache,
155                 ShapeDataList* shapeList, bool gammaCorrect,
156                 const GrUserStencilSettings* stencilSettings)
157             : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) {
158         SkASSERT(shape.hasUnstyledKey());
159         // Compute bounds
160         this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
161 
162 #if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
163         fUsesDistanceField = true;
164 #else
165         // only use distance fields on desktop and Android framework to save space in the atlas
166         fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP;
167 #endif
168         fViewMatrix = viewMatrix;
169         SkVector translate = SkVector::Make(0, 0);
170         if (!fUsesDistanceField) {
171             // In this case we don't apply a view matrix, so we need to remove the non-subpixel
172             // translation and add it back when we generate the quad for the path
173             SkScalar translateX = viewMatrix.getTranslateX();
174             SkScalar translateY = viewMatrix.getTranslateY();
175             translate = SkVector::Make(SkScalarFloorToScalar(translateX),
176                                        SkScalarFloorToScalar(translateY));
177             // Only store the fractional part of the translation in the view matrix
178             fViewMatrix.setTranslateX(translateX - translate.fX);
179             fViewMatrix.setTranslateY(translateY - translate.fY);
180         }
181 
182         fShapes.emplace_back(Entry{color, shape, translate});
183 
184         fAtlas = atlas;
185         fShapeCache = shapeCache;
186         fShapeList = shapeList;
187         fGammaCorrect = gammaCorrect;
188 
189     }
190 
name() const191     const char* name() const override { return "SmallPathOp"; }
192 
dumpInfo() const193     SkString dumpInfo() const override {
194         SkString string;
195         for (const auto& geo : fShapes) {
196             string.appendf("Color: 0x%08x\n", geo.fColor);
197         }
198         string += fHelper.dumpInfo();
199         string += INHERITED::dumpInfo();
200         return string;
201     }
202 
fixedFunctionFlags() const203     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
204 
finalize(const GrCaps & caps,const GrAppliedClip * clip)205     RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
206         return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel,
207                                             &fShapes.front().fColor);
208     }
209 
210 private:
211     struct FlushInfo {
212         sk_sp<const GrBuffer> fVertexBuffer;
213         sk_sp<const GrBuffer> fIndexBuffer;
214         sk_sp<GrGeometryProcessor>   fGeometryProcessor;
215         const GrPipeline* fPipeline;
216         int fVertexOffset;
217         int fInstancesToFlush;
218     };
219 
onPrepareDraws(Target * target) const220     void onPrepareDraws(Target* target) const override {
221         int instanceCount = fShapes.count();
222         const SkMatrix& ctm = this->viewMatrix();
223 
224         FlushInfo flushInfo;
225         flushInfo.fPipeline = fHelper.makePipeline(target);
226         // Setup GrGeometryProcessor
227         GrDrawOpAtlas* atlas = fAtlas;
228         if (fUsesDistanceField) {
229             GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
230 
231             uint32_t flags = 0;
232             flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
233             flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
234             flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
235 
236             flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
237                     this->color(), this->viewMatrix(), atlas->getProxy(), params, flags,
238                     fHelper.usesLocalCoords());
239         } else {
240             GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
241 
242             SkMatrix invert;
243             if (fHelper.usesLocalCoords()) {
244                 if (!this->viewMatrix().invert(&invert)) {
245                     SkDebugf("Could not invert viewmatrix\n");
246                     return;
247                 }
248                 // for local coords, we need to add the translation back in that we removed
249                 // from the stored view matrix
250                 invert.preTranslate(-fShapes[0].fTranslate.fX, -fShapes[0].fTranslate.fY);
251             }
252 
253             flushInfo.fGeometryProcessor =
254                     GrBitmapTextGeoProc::Make(this->color(), atlas->getProxy(), params,
255                                               kA8_GrMaskFormat, invert, fHelper.usesLocalCoords());
256         }
257 
258         // allocate vertices
259         size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
260         SkASSERT(vertexStride == sizeof(SkPoint) + sizeof(GrColor) + 2*sizeof(uint16_t));
261 
262         const GrBuffer* vertexBuffer;
263         void* vertices = target->makeVertexSpace(vertexStride,
264                                                  kVerticesPerQuad * instanceCount,
265                                                  &vertexBuffer,
266                                                  &flushInfo.fVertexOffset);
267         flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
268         flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
269         if (!vertices || !flushInfo.fIndexBuffer) {
270             SkDebugf("Could not allocate vertices\n");
271             return;
272         }
273 
274         flushInfo.fInstancesToFlush = 0;
275         // Pointer to the next set of vertices to write.
276         intptr_t offset = reinterpret_cast<intptr_t>(vertices);
277         for (int i = 0; i < instanceCount; i++) {
278             const Entry& args = fShapes[i];
279 
280             ShapeData* shapeData;
281             SkScalar maxScale;
282             if (fUsesDistanceField) {
283                 // get mip level
284                 maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
285                 const SkRect& bounds = args.fShape.bounds();
286                 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
287                 // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.)
288                 // In the majority of cases this will yield a crisper rendering.
289                 SkScalar mipScale = 1.0f;
290                 // Our mipscale is the maxScale clamped to the next highest power of 2
291                 if (maxScale <= SK_ScalarHalf) {
292                     SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
293                     mipScale = SkScalarPow(2, -log);
294                 } else if (maxScale > SK_Scalar1) {
295                     SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
296                     mipScale = SkScalarPow(2, log);
297                 }
298                 SkASSERT(maxScale <= mipScale);
299 
300                 SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
301                 // For sizes less than kIdealMinMIP we want to use as large a distance field as we can
302                 // so we can preserve as much detail as possible. However, we can't scale down more
303                 // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
304                 // just bigger than the ideal, and then scale down until we are no more than 4x the
305                 // original mipsize.
306                 if (mipSize < kIdealMinMIP) {
307                     SkScalar newMipSize = mipSize;
308                     do {
309                         newMipSize *= 2;
310                     } while (newMipSize < kIdealMinMIP);
311                     while (newMipSize > 4 * mipSize) {
312                         newMipSize *= 0.25f;
313                     }
314                     mipSize = newMipSize;
315                 }
316                 SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
317 
318                 // check to see if df path is cached
319                 ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
320                 shapeData = fShapeCache->find(key);
321                 if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
322                     // Remove the stale cache entry
323                     if (shapeData) {
324                         fShapeCache->remove(shapeData->fKey);
325                         fShapeList->remove(shapeData);
326                         delete shapeData;
327                     }
328                     SkScalar scale = desiredDimension / maxDim;
329 
330                     shapeData = new ShapeData;
331                     if (!this->addDFPathToAtlas(target,
332                                                 &flushInfo,
333                                                 atlas,
334                                                 shapeData,
335                                                 args.fShape,
336                                                 SkScalarCeilToInt(desiredDimension),
337                                                 scale)) {
338                         delete shapeData;
339                         continue;
340                     }
341                 }
342             } else {
343                 // check to see if bitmap path is cached
344                 ShapeData::Key key(args.fShape, this->viewMatrix());
345                 shapeData = fShapeCache->find(key);
346                 if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
347                     // Remove the stale cache entry
348                     if (shapeData) {
349                         fShapeCache->remove(shapeData->fKey);
350                         fShapeList->remove(shapeData);
351                         delete shapeData;
352                     }
353 
354                     shapeData = new ShapeData;
355                     if (!this->addBMPathToAtlas(target,
356                                               &flushInfo,
357                                               atlas,
358                                               shapeData,
359                                               args.fShape,
360                                               this->viewMatrix())) {
361                         delete shapeData;
362                         continue;
363                     }
364                 }
365                 maxScale = 1;
366             }
367 
368             atlas->setLastUseToken(shapeData->fID, target->nextDrawToken());
369 
370             this->writePathVertices(target,
371                                     atlas,
372                                     offset,
373                                     args.fColor,
374                                     vertexStride,
375                                     maxScale,
376                                     args.fTranslate,
377                                     shapeData);
378             offset += kVerticesPerQuad * vertexStride;
379             flushInfo.fInstancesToFlush++;
380         }
381 
382         this->flush(target, &flushInfo);
383     }
384 
addDFPathToAtlas(GrMeshDrawOp::Target * target,FlushInfo * flushInfo,GrDrawOpAtlas * atlas,ShapeData * shapeData,const GrShape & shape,uint32_t dimension,SkScalar scale) const385     bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo,
386                           GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
387                           uint32_t dimension, SkScalar scale) const {
388         const SkRect& bounds = shape.bounds();
389 
390         // generate bounding rect for bitmap draw
391         SkRect scaledBounds = bounds;
392         // scale to mip level size
393         scaledBounds.fLeft *= scale;
394         scaledBounds.fTop *= scale;
395         scaledBounds.fRight *= scale;
396         scaledBounds.fBottom *= scale;
397         // subtract out integer portion of origin
398         // (SDF created will be placed with fractional offset burnt in)
399         SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft);
400         SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop);
401         scaledBounds.offset(-dx, -dy);
402         // get integer boundary
403         SkIRect devPathBounds;
404         scaledBounds.roundOut(&devPathBounds);
405         // pad to allow room for antialiasing
406         const int intPad = SkScalarCeilToInt(kAntiAliasPad);
407         // place devBounds at origin
408         int width = devPathBounds.width() + 2*intPad;
409         int height = devPathBounds.height() + 2*intPad;
410         devPathBounds = SkIRect::MakeWH(width, height);
411 
412         // draw path to bitmap
413         SkMatrix drawMatrix;
414         drawMatrix.setScale(scale, scale);
415         drawMatrix.postTranslate(intPad - dx, intPad - dy);
416 
417         SkASSERT(devPathBounds.fLeft == 0);
418         SkASSERT(devPathBounds.fTop == 0);
419         SkASSERT(devPathBounds.width() > 0);
420         SkASSERT(devPathBounds.height() > 0);
421 
422         // setup signed distance field storage
423         SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad);
424         width = dfBounds.width();
425         height = dfBounds.height();
426         // TODO We should really generate this directly into the plot somehow
427         SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
428 
429         SkPath path;
430         shape.asPath(&path);
431 #ifndef SK_USE_LEGACY_DISTANCE_FIELDS
432         // Generate signed distance field directly from SkPath
433         bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(),
434                                         path, drawMatrix,
435                                         width, height, width * sizeof(unsigned char));
436         if (!succeed) {
437 #endif
438             // setup bitmap backing
439             SkAutoPixmapStorage dst;
440             if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
441                                                   devPathBounds.height()))) {
442                 return false;
443             }
444             sk_bzero(dst.writable_addr(), dst.getSafeSize());
445 
446             // rasterize path
447             SkPaint paint;
448             paint.setStyle(SkPaint::kFill_Style);
449             paint.setAntiAlias(true);
450 
451             SkDraw draw;
452             sk_bzero(&draw, sizeof(draw));
453 
454             SkRasterClip rasterClip;
455             rasterClip.setRect(devPathBounds);
456             draw.fRC = &rasterClip;
457             draw.fMatrix = &drawMatrix;
458             draw.fDst = dst;
459 
460             draw.drawPathCoverage(path, paint);
461 
462             // Generate signed distance field
463             SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
464                                                (const unsigned char*)dst.addr(),
465                                                dst.width(), dst.height(), dst.rowBytes());
466 #ifndef SK_USE_LEGACY_DISTANCE_FIELDS
467         }
468 #endif
469 
470         // add to atlas
471         SkIPoint16 atlasLocation;
472         GrDrawOpAtlas::AtlasID id;
473         if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
474             this->flush(target, flushInfo);
475             if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
476                 return false;
477             }
478         }
479 
480         // add to cache
481         shapeData->fKey.set(shape, dimension);
482         shapeData->fID = id;
483 
484         // set the bounds rect to the original bounds
485         shapeData->fBounds = bounds;
486 
487         // set up path to texture coordinate transform
488         shapeData->fScale = scale;
489         dx -= SK_DistanceFieldPad + kAntiAliasPad;
490         dy -= SK_DistanceFieldPad + kAntiAliasPad;
491         shapeData->fTranslate.fX = atlasLocation.fX - dx;
492         shapeData->fTranslate.fY = atlasLocation.fY - dy;
493 
494         fShapeCache->add(shapeData);
495         fShapeList->addToTail(shapeData);
496 #ifdef DF_PATH_TRACKING
497         ++g_NumCachedPaths;
498 #endif
499         return true;
500     }
501 
addBMPathToAtlas(GrMeshDrawOp::Target * target,FlushInfo * flushInfo,GrDrawOpAtlas * atlas,ShapeData * shapeData,const GrShape & shape,const SkMatrix & ctm) const502     bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo,
503                           GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
504                           const SkMatrix& ctm) const {
505         const SkRect& bounds = shape.bounds();
506         if (bounds.isEmpty()) {
507             return false;
508         }
509         SkMatrix drawMatrix(ctm);
510         drawMatrix.set(SkMatrix::kMTransX, SkScalarFraction(ctm.get(SkMatrix::kMTransX)));
511         drawMatrix.set(SkMatrix::kMTransY, SkScalarFraction(ctm.get(SkMatrix::kMTransY)));
512         SkRect shapeDevBounds;
513         drawMatrix.mapRect(&shapeDevBounds, bounds);
514         SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft);
515         SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop);
516 
517         // get integer boundary
518         SkIRect devPathBounds;
519         shapeDevBounds.roundOut(&devPathBounds);
520         // pad to allow room for antialiasing
521         const int intPad = SkScalarCeilToInt(kAntiAliasPad);
522         // place devBounds at origin
523         int width = devPathBounds.width() + 2 * intPad;
524         int height = devPathBounds.height() + 2 * intPad;
525         devPathBounds = SkIRect::MakeWH(width, height);
526         SkScalar translateX = intPad - dx;
527         SkScalar translateY = intPad - dy;
528 
529         SkASSERT(devPathBounds.fLeft == 0);
530         SkASSERT(devPathBounds.fTop == 0);
531         SkASSERT(devPathBounds.width() > 0);
532         SkASSERT(devPathBounds.height() > 0);
533 
534         SkPath path;
535         shape.asPath(&path);
536         // setup bitmap backing
537         SkAutoPixmapStorage dst;
538         if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
539                                               devPathBounds.height()))) {
540             return false;
541         }
542         sk_bzero(dst.writable_addr(), dst.getSafeSize());
543 
544         // rasterize path
545         SkPaint paint;
546         paint.setStyle(SkPaint::kFill_Style);
547         paint.setAntiAlias(true);
548 
549         SkDraw draw;
550         sk_bzero(&draw, sizeof(draw));
551 
552         SkRasterClip rasterClip;
553         rasterClip.setRect(devPathBounds);
554         draw.fRC = &rasterClip;
555         drawMatrix.postTranslate(translateX, translateY);
556         draw.fMatrix = &drawMatrix;
557         draw.fDst = dst;
558 
559         draw.drawPathCoverage(path, paint);
560 
561         // add to atlas
562         SkIPoint16 atlasLocation;
563         GrDrawOpAtlas::AtlasID id;
564         if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
565                                &atlasLocation)) {
566             this->flush(target, flushInfo);
567             if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
568                                    &atlasLocation)) {
569                 return false;
570             }
571         }
572 
573         // add to cache
574         shapeData->fKey.set(shape, ctm);
575         shapeData->fID = id;
576 
577         // set the bounds rect to the original bounds
578         shapeData->fBounds = SkRect::Make(devPathBounds);
579         shapeData->fBounds.offset(-translateX, -translateY);
580 
581         // set up path to texture coordinate transform
582         shapeData->fScale = SK_Scalar1;
583         shapeData->fTranslate.fX = atlasLocation.fX + translateX;
584         shapeData->fTranslate.fY = atlasLocation.fY + translateY;
585 
586         fShapeCache->add(shapeData);
587         fShapeList->addToTail(shapeData);
588 #ifdef DF_PATH_TRACKING
589         ++g_NumCachedPaths;
590 #endif
591         return true;
592     }
593 
writePathVertices(GrDrawOp::Target * target,GrDrawOpAtlas * atlas,intptr_t offset,GrColor color,size_t vertexStride,SkScalar maxScale,const SkVector & preTranslate,const ShapeData * shapeData) const594     void writePathVertices(GrDrawOp::Target* target,
595                            GrDrawOpAtlas* atlas,
596                            intptr_t offset,
597                            GrColor color,
598                            size_t vertexStride,
599                            SkScalar maxScale,
600                            const SkVector& preTranslate,
601                            const ShapeData* shapeData) const {
602         SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
603 
604         SkRect bounds = shapeData->fBounds;
605         if (fUsesDistanceField) {
606             // outset bounds to include ~1 pixel of AA in device space
607             SkScalar outset = SkScalarInvert(maxScale);
608             bounds.outset(outset, outset);
609         }
610 
611         // vertex positions
612         // TODO make the vertex attributes a struct
613         positions->setRectFan(bounds.left() + preTranslate.fX,
614                               bounds.top() + preTranslate.fY,
615                               bounds.right() + preTranslate.fX,
616                               bounds.bottom() + preTranslate.fY,
617                               vertexStride);
618 
619         // colors
620         for (int i = 0; i < kVerticesPerQuad; i++) {
621             GrColor* colorPtr = (GrColor*)(offset + sizeof(SkPoint) + i * vertexStride);
622             *colorPtr = color;
623         }
624 
625         // set up texture coordinates
626         SkScalar texLeft = bounds.fLeft;
627         SkScalar texTop = bounds.fTop;
628         SkScalar texRight = bounds.fRight;
629         SkScalar texBottom = bounds.fBottom;
630 
631         // transform original path's bounds to texture space
632         SkScalar scale = shapeData->fScale;
633         const SkVector& translate = shapeData->fTranslate;
634         texLeft *= scale;
635         texTop *= scale;
636         texRight *= scale;
637         texBottom *= scale;
638         texLeft += translate.fX;
639         texTop += translate.fY;
640         texRight += translate.fX;
641         texBottom += translate.fY;
642 
643         // convert texcoords to unsigned short format
644         sk_sp<GrTextureProxy> proxy = atlas->getProxy();
645 
646         // The proxy must be functionally exact for this normalization to work correctly
647         SkASSERT(GrResourceProvider::IsFunctionallyExact(proxy.get()));
648         SkScalar uFactor = 65535.f / proxy->width();
649         SkScalar vFactor = 65535.f / proxy->height();
650         uint16_t l = (uint16_t)(texLeft*uFactor);
651         uint16_t t = (uint16_t)(texTop*vFactor);
652         uint16_t r = (uint16_t)(texRight*uFactor);
653         uint16_t b = (uint16_t)(texBottom*vFactor);
654 
655         // set vertex texture coords
656         intptr_t textureCoordOffset = offset + sizeof(SkPoint) + sizeof(GrColor);
657         uint16_t* textureCoords = (uint16_t*) textureCoordOffset;
658         textureCoords[0] = l;
659         textureCoords[1] = t;
660         textureCoordOffset += vertexStride;
661         textureCoords = (uint16_t*)textureCoordOffset;
662         textureCoords[0] = l;
663         textureCoords[1] = b;
664         textureCoordOffset += vertexStride;
665         textureCoords = (uint16_t*)textureCoordOffset;
666         textureCoords[0] = r;
667         textureCoords[1] = b;
668         textureCoordOffset += vertexStride;
669         textureCoords = (uint16_t*)textureCoordOffset;
670         textureCoords[0] = r;
671         textureCoords[1] = t;
672     }
673 
flush(GrMeshDrawOp::Target * target,FlushInfo * flushInfo) const674     void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
675         if (flushInfo->fInstancesToFlush) {
676             GrMesh mesh(GrPrimitiveType::kTriangles);
677             int maxInstancesPerDraw =
678                 static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
679             mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerQuad,
680                                      kVerticesPerQuad, flushInfo->fInstancesToFlush,
681                                      maxInstancesPerDraw);
682             mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
683             target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh);
684             flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
685             flushInfo->fInstancesToFlush = 0;
686         }
687     }
688 
color() const689     GrColor color() const { return fShapes[0].fColor; }
viewMatrix() const690     const SkMatrix& viewMatrix() const { return fViewMatrix; }
usesDistanceField() const691     bool usesDistanceField() const { return fUsesDistanceField; }
692 
onCombineIfPossible(GrOp * t,const GrCaps & caps)693     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
694         SmallPathOp* that = t->cast<SmallPathOp>();
695         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
696             return false;
697         }
698 
699         if (this->usesDistanceField() != that->usesDistanceField()) {
700             return false;
701         }
702 
703         // TODO We can position on the cpu for distance field paths
704         if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
705             return false;
706         }
707 
708         if (!this->usesDistanceField() && fHelper.usesLocalCoords() &&
709             !this->fShapes[0].fTranslate.equalsWithinTolerance(that->fShapes[0].fTranslate)) {
710             return false;
711         }
712 
713         fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
714         this->joinBounds(*that);
715         return true;
716     }
717 
718     SkMatrix fViewMatrix;
719     bool fUsesDistanceField;
720 
721     struct Entry {
722         GrColor  fColor;
723         GrShape  fShape;
724         SkVector fTranslate;
725     };
726 
727     SkSTArray<1, Entry> fShapes;
728     Helper fHelper;
729     GrDrawOpAtlas* fAtlas;
730     ShapeCache* fShapeCache;
731     ShapeDataList* fShapeList;
732     bool fGammaCorrect;
733 
734     typedef GrMeshDrawOp INHERITED;
735 };
736 
onDrawPath(const DrawPathArgs & args)737 bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) {
738     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
739                               "GrSmallPathRenderer::onDrawPath");
740 
741     // we've already bailed on inverse filled paths, so this is safe
742     SkASSERT(!args.fShape->isEmpty());
743     SkASSERT(args.fShape->hasUnstyledKey());
744     if (!fAtlas) {
745         fAtlas = GrDrawOpAtlas::Make(args.fContext,
746                                      kAlpha_8_GrPixelConfig,
747                                      ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
748                                      NUM_PLOTS_X, NUM_PLOTS_Y,
749                                      &GrSmallPathRenderer::HandleEviction,
750                                      (void*)this);
751         if (!fAtlas) {
752             return false;
753         }
754     }
755 
756     std::unique_ptr<GrDrawOp> op = SmallPathOp::Make(
757             std::move(args.fPaint), *args.fShape, *args.fViewMatrix, fAtlas.get(), &fShapeCache,
758             &fShapeList, args.fGammaCorrect, args.fUserStencilSettings);
759     args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
760 
761     return true;
762 }
763 
764 ///////////////////////////////////////////////////////////////////////////////////////////////////
765 
766 #if GR_TEST_UTILS
767 
768 struct GrSmallPathRenderer::PathTestStruct {
PathTestStructGrSmallPathRenderer::PathTestStruct769     PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {}
~PathTestStructGrSmallPathRenderer::PathTestStruct770     ~PathTestStruct() { this->reset(); }
771 
resetGrSmallPathRenderer::PathTestStruct772     void reset() {
773         ShapeDataList::Iter iter;
774         iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
775         ShapeData* shapeData;
776         while ((shapeData = iter.get())) {
777             iter.next();
778             fShapeList.remove(shapeData);
779             delete shapeData;
780         }
781         fAtlas = nullptr;
782         fShapeCache.reset();
783     }
784 
HandleEvictionGrSmallPathRenderer::PathTestStruct785     static void HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
786         PathTestStruct* dfpr = (PathTestStruct*)pr;
787         // remove any paths that use this plot
788         ShapeDataList::Iter iter;
789         iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
790         ShapeData* shapeData;
791         while ((shapeData = iter.get())) {
792             iter.next();
793             if (id == shapeData->fID) {
794                 dfpr->fShapeCache.remove(shapeData->fKey);
795                 dfpr->fShapeList.remove(shapeData);
796                 delete shapeData;
797             }
798         }
799     }
800 
801     uint32_t fContextID;
802     std::unique_ptr<GrDrawOpAtlas> fAtlas;
803     ShapeCache fShapeCache;
804     ShapeDataList fShapeList;
805 };
806 
GR_DRAW_OP_TEST_DEFINE(SmallPathOp)807 GR_DRAW_OP_TEST_DEFINE(SmallPathOp) {
808     using PathTestStruct = GrSmallPathRenderer::PathTestStruct;
809     static PathTestStruct gTestStruct;
810 
811     if (context->uniqueID() != gTestStruct.fContextID) {
812         gTestStruct.fContextID = context->uniqueID();
813         gTestStruct.reset();
814         gTestStruct.fAtlas = GrDrawOpAtlas::Make(context, kAlpha_8_GrPixelConfig,
815                                                  ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
816                                                  NUM_PLOTS_X, NUM_PLOTS_Y,
817                                                  &PathTestStruct::HandleEviction,
818                                                  (void*)&gTestStruct);
819     }
820 
821     SkMatrix viewMatrix = GrTest::TestMatrix(random);
822     bool gammaCorrect = random->nextBool();
823 
824     // This path renderer only allows fill styles.
825     GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill());
826 
827     return GrSmallPathRenderer::SmallPathOp::Make(
828             std::move(paint), shape, viewMatrix, gTestStruct.fAtlas.get(), &gTestStruct.fShapeCache,
829             &gTestStruct.fShapeList, gammaCorrect, GrGetRandomStencil(random, context));
830 }
831 
832 #endif
833