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