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