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