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