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