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