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 |
36 | static constexpr SkScalar kIdealMinMIP = 12; |
37 | static constexpr SkScalar kMaxMIP = 162; |
38 | |
39 | static constexpr SkScalar kMaxDim = 73; |
40 | static constexpr SkScalar kMinSize = SK_ScalarHalf; |
41 | static constexpr SkScalar kMaxSize = 2*kMaxMIP; |
42 | |
43 | GrSmallPathRenderer::GrSmallPathRenderer() {} |
44 | |
45 | GrSmallPathRenderer::~GrSmallPathRenderer() {} |
46 | |
47 | GrPathRenderer::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 |
91 | static const int kAntiAliasPad = 1; |
92 | |
93 | class GrSmallPathRenderer::SmallPathOp final : public GrMeshDrawOp { |
94 | private: |
95 | using Helper = GrSimpleMeshDrawOpHelperWithStencil; |
96 | |
97 | public: |
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 | |
149 | private: |
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 | |
693 | bool 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 |
712 | std::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 | |
724 | GR_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 | |