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