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