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