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