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