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