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/SkAutoMalloc.h" |
13 | #include "src/core/SkAutoPixmapStorage.h" |
14 | #include "src/core/SkDistanceFieldGen.h" |
15 | #include "src/core/SkDraw.h" |
16 | #include "src/core/SkPointPriv.h" |
17 | #include "src/core/SkRasterClip.h" |
18 | #include "src/gpu/GrAuditTrail.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/ops/GrMeshDrawOp.h" |
30 | #include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h" |
31 | |
32 | static constexpr size_t kMaxAtlasTextureBytes = 2048 * 2048; |
33 | static constexpr size_t kPlotWidth = 512; |
34 | static constexpr size_t kPlotHeight = 256; |
35 | |
36 | #ifdef DF_PATH_TRACKING |
37 | static int g_NumCachedShapes = 0; |
38 | static int g_NumFreedShapes = 0; |
39 | #endif |
40 | |
41 | // mip levels |
42 | static constexpr SkScalar kIdealMinMIP = 12; |
43 | static constexpr SkScalar kMaxMIP = 162; |
44 | |
45 | static constexpr SkScalar kMaxDim = 73; |
46 | static constexpr SkScalar kMinSize = SK_ScalarHalf; |
47 | static constexpr SkScalar kMaxSize = 2*kMaxMIP; |
48 | |
49 | class ShapeDataKey { |
50 | public: |
51 | ShapeDataKey() {} |
52 | ShapeDataKey(const ShapeDataKey& that) { *this = that; } |
53 | ShapeDataKey(const GrShape& shape, uint32_t dim) { this->set(shape, dim); } |
54 | ShapeDataKey(const GrShape& shape, const SkMatrix& ctm) { this->set(shape, ctm); } |
55 | |
56 | ShapeDataKey& operator=(const ShapeDataKey& that) { |
57 | fKey.reset(that.fKey.count()); |
58 | memcpy(fKey.get(), that.fKey.get(), fKey.count() * sizeof(uint32_t)); |
59 | return *this; |
60 | } |
61 | |
62 | // for SDF paths |
63 | void set(const GrShape& shape, uint32_t dim) { |
64 | // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any |
65 | // relevant styling information. |
66 | SkASSERT(shape.style().isSimpleFill()); |
67 | SkASSERT(shape.hasUnstyledKey()); |
68 | int shapeKeySize = shape.unstyledKeySize(); |
69 | fKey.reset(1 + shapeKeySize); |
70 | fKey[0] = dim; |
71 | shape.writeUnstyledKey(&fKey[1]); |
72 | } |
73 | |
74 | // for bitmap paths |
75 | void set(const GrShape& shape, const SkMatrix& ctm) { |
76 | // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any |
77 | // relevant styling information. |
78 | SkASSERT(shape.style().isSimpleFill()); |
79 | SkASSERT(shape.hasUnstyledKey()); |
80 | // We require the upper left 2x2 of the matrix to match exactly for a cache hit. |
81 | SkScalar sx = ctm.get(SkMatrix::kMScaleX); |
82 | SkScalar sy = ctm.get(SkMatrix::kMScaleY); |
83 | SkScalar kx = ctm.get(SkMatrix::kMSkewX); |
84 | SkScalar ky = ctm.get(SkMatrix::kMSkewY); |
85 | SkScalar tx = ctm.get(SkMatrix::kMTransX); |
86 | SkScalar ty = ctm.get(SkMatrix::kMTransY); |
87 | // Allow 8 bits each in x and y of subpixel positioning. |
88 | tx -= SkScalarFloorToScalar(tx); |
89 | ty -= SkScalarFloorToScalar(ty); |
90 | SkFixed fracX = SkScalarToFixed(tx) & 0x0000FF00; |
91 | SkFixed fracY = SkScalarToFixed(ty) & 0x0000FF00; |
92 | int shapeKeySize = shape.unstyledKeySize(); |
93 | fKey.reset(5 + shapeKeySize); |
94 | fKey[0] = SkFloat2Bits(sx); |
95 | fKey[1] = SkFloat2Bits(sy); |
96 | fKey[2] = SkFloat2Bits(kx); |
97 | fKey[3] = SkFloat2Bits(ky); |
98 | fKey[4] = fracX | (fracY >> 8); |
99 | shape.writeUnstyledKey(&fKey[5]); |
100 | } |
101 | |
102 | bool operator==(const ShapeDataKey& that) const { |
103 | return fKey.count() == that.fKey.count() && |
104 | 0 == memcmp(fKey.get(), that.fKey.get(), sizeof(uint32_t) * fKey.count()); |
105 | } |
106 | |
107 | int count32() const { return fKey.count(); } |
108 | const uint32_t* data() const { return fKey.get(); } |
109 | |
110 | private: |
111 | // The key is composed of the GrShape's key, and either the dimensions of the DF |
112 | // generated for the path (32x32 max, 64x64 max, 128x128 max) if an SDF image or |
113 | // the matrix for the path with only fractional translation. |
114 | SkAutoSTArray<24, uint32_t> fKey; |
115 | }; |
116 | |
117 | class ShapeData { |
118 | public: |
119 | ShapeDataKey fKey; |
120 | SkRect fBounds; |
121 | GrDrawOpAtlas::AtlasLocator fAtlasLocator; |
122 | |
123 | SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeData); |
124 | |
125 | static inline const ShapeDataKey& GetKey(const ShapeData& data) { |
126 | return data.fKey; |
127 | } |
128 | |
129 | static inline uint32_t Hash(const ShapeDataKey& key) { |
130 | return SkOpts::hash(key.data(), sizeof(uint32_t) * key.count32()); |
131 | } |
132 | }; |
133 | |
134 | |
135 | |
136 | // Callback to clear out internal path cache when eviction occurs |
137 | void GrSmallPathRenderer::evict(GrDrawOpAtlas::PlotLocator plotLocator) { |
138 | // remove any paths that use this plot |
139 | ShapeDataList::Iter iter; |
140 | iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); |
141 | ShapeData* shapeData; |
142 | while ((shapeData = iter.get())) { |
143 | iter.next(); |
144 | if (plotLocator == shapeData->fAtlasLocator.plotLocator()) { |
145 | fShapeCache.remove(shapeData->fKey); |
146 | fShapeList.remove(shapeData); |
147 | delete shapeData; |
148 | #ifdef DF_PATH_TRACKING |
149 | ++g_NumFreedPaths; |
150 | #endif |
151 | } |
152 | } |
153 | } |
154 | |
155 | //////////////////////////////////////////////////////////////////////////////// |
156 | GrSmallPathRenderer::GrSmallPathRenderer() : fAtlas(nullptr) {} |
157 | |
158 | GrSmallPathRenderer::~GrSmallPathRenderer() { |
159 | ShapeDataList::Iter iter; |
160 | iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); |
161 | ShapeData* shapeData; |
162 | while ((shapeData = iter.get())) { |
163 | iter.next(); |
164 | delete shapeData; |
165 | } |
166 | |
167 | #ifdef DF_PATH_TRACKING |
168 | SkDebugf("Cached shapes: %d, freed shapes: %d\n" , g_NumCachedShapes, g_NumFreedShapes); |
169 | #endif |
170 | } |
171 | |
172 | //////////////////////////////////////////////////////////////////////////////// |
173 | GrPathRenderer::CanDrawPath GrSmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { |
174 | if (!args.fCaps->shaderCaps()->shaderDerivativeSupport()) { |
175 | return CanDrawPath::kNo; |
176 | } |
177 | // If the shape has no key then we won't get any reuse. |
178 | if (!args.fShape->hasUnstyledKey()) { |
179 | return CanDrawPath::kNo; |
180 | } |
181 | // This only supports filled paths, however, the caller may apply the style to make a filled |
182 | // path and try again. |
183 | if (!args.fShape->style().isSimpleFill()) { |
184 | return CanDrawPath::kNo; |
185 | } |
186 | // This does non-inverse coverage-based antialiased fills. |
187 | if (GrAAType::kCoverage != args.fAAType) { |
188 | return CanDrawPath::kNo; |
189 | } |
190 | // TODO: Support inverse fill |
191 | if (args.fShape->inverseFilled()) { |
192 | return CanDrawPath::kNo; |
193 | } |
194 | |
195 | // Only support paths with bounds within kMaxDim by kMaxDim, |
196 | // scaled to have bounds within kMaxSize by kMaxSize. |
197 | // The goal is to accelerate rendering of lots of small paths that may be scaling. |
198 | SkScalar scaleFactors[2] = { 1, 1 }; |
199 | if (!args.fViewMatrix->hasPerspective() && !args.fViewMatrix->getMinMaxScales(scaleFactors)) { |
200 | return CanDrawPath::kNo; |
201 | } |
202 | SkRect bounds = args.fShape->styledBounds(); |
203 | SkScalar minDim = std::min(bounds.width(), bounds.height()); |
204 | SkScalar maxDim = std::max(bounds.width(), bounds.height()); |
205 | SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]); |
206 | SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]); |
207 | if (maxDim > kMaxDim || kMinSize > minSize || maxSize > kMaxSize) { |
208 | return CanDrawPath::kNo; |
209 | } |
210 | |
211 | return CanDrawPath::kYes; |
212 | } |
213 | |
214 | //////////////////////////////////////////////////////////////////////////////// |
215 | |
216 | // padding around path bounds to allow for antialiased pixels |
217 | static const int kAntiAliasPad = 1; |
218 | |
219 | class GrSmallPathRenderer::SmallPathOp final : public GrMeshDrawOp { |
220 | private: |
221 | using Helper = GrSimpleMeshDrawOpHelperWithStencil; |
222 | |
223 | public: |
224 | DEFINE_OP_CLASS_ID |
225 | |
226 | using ShapeCache = SkTDynamicHash<ShapeData, ShapeDataKey>; |
227 | using ShapeDataList = GrSmallPathRenderer::ShapeDataList; |
228 | |
229 | static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, |
230 | GrPaint&& paint, |
231 | const GrShape& shape, |
232 | const SkMatrix& viewMatrix, |
233 | GrDrawOpAtlas* atlas, |
234 | ShapeCache* shapeCache, |
235 | ShapeDataList* shapeList, |
236 | bool gammaCorrect, |
237 | const GrUserStencilSettings* stencilSettings) { |
238 | return Helper::FactoryHelper<SmallPathOp>(context, std::move(paint), shape, viewMatrix, |
239 | atlas, shapeCache, shapeList, gammaCorrect, |
240 | stencilSettings); |
241 | } |
242 | |
243 | SmallPathOp(Helper::MakeArgs helperArgs, const SkPMColor4f& color, const GrShape& shape, |
244 | const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas, ShapeCache* shapeCache, |
245 | ShapeDataList* shapeList, bool gammaCorrect, |
246 | const GrUserStencilSettings* stencilSettings) |
247 | : INHERITED(ClassID()) |
248 | , fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) { |
249 | SkASSERT(shape.hasUnstyledKey()); |
250 | // Compute bounds |
251 | this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsHairline::kNo); |
252 | |
253 | #if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) |
254 | fUsesDistanceField = true; |
255 | #else |
256 | // only use distance fields on desktop and Android framework to save space in the atlas |
257 | fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP; |
258 | #endif |
259 | // always use distance fields if in perspective |
260 | fUsesDistanceField = fUsesDistanceField || viewMatrix.hasPerspective(); |
261 | |
262 | fShapes.emplace_back(Entry{color, shape, viewMatrix}); |
263 | |
264 | fAtlas = atlas; |
265 | fShapeCache = shapeCache; |
266 | fShapeList = shapeList; |
267 | fGammaCorrect = gammaCorrect; |
268 | } |
269 | |
270 | const char* name() const override { return "SmallPathOp" ; } |
271 | |
272 | void visitProxies(const VisitProxyFunc& func) const override { |
273 | fHelper.visitProxies(func); |
274 | |
275 | const GrSurfaceProxyView* views = fAtlas->getViews(); |
276 | for (uint32_t i = 0; i < fAtlas->numActivePages(); ++i) { |
277 | SkASSERT(views[i].proxy()); |
278 | func(views[i].proxy(), GrMipMapped::kNo); |
279 | } |
280 | } |
281 | |
282 | #ifdef SK_DEBUG |
283 | SkString dumpInfo() const override { |
284 | SkString string; |
285 | for (const auto& geo : fShapes) { |
286 | string.appendf("Color: 0x%08x\n" , geo.fColor.toBytes_RGBA()); |
287 | } |
288 | string += fHelper.dumpInfo(); |
289 | string += INHERITED::dumpInfo(); |
290 | return string; |
291 | } |
292 | #endif |
293 | |
294 | FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
295 | |
296 | GrProcessorSet::Analysis finalize( |
297 | const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, |
298 | GrClampType clampType) override { |
299 | return fHelper.finalizeProcessors( |
300 | caps, clip, hasMixedSampledCoverage, clampType, |
301 | GrProcessorAnalysisCoverage::kSingleChannel, &fShapes.front().fColor, &fWideColor); |
302 | } |
303 | |
304 | private: |
305 | struct FlushInfo { |
306 | sk_sp<const GrBuffer> fVertexBuffer; |
307 | sk_sp<const GrBuffer> fIndexBuffer; |
308 | GrGeometryProcessor* fGeometryProcessor; |
309 | const GrSurfaceProxy** fPrimProcProxies; |
310 | int fVertexOffset; |
311 | int fInstancesToFlush; |
312 | }; |
313 | |
314 | GrProgramInfo* programInfo() override { |
315 | // TODO [PI]: implement |
316 | return nullptr; |
317 | } |
318 | |
319 | void onCreateProgramInfo(const GrCaps*, |
320 | SkArenaAlloc*, |
321 | const GrSurfaceProxyView* writeView, |
322 | GrAppliedClip&&, |
323 | const GrXferProcessor::DstProxyView&) override { |
324 | // TODO [PI]: implement |
325 | } |
326 | |
327 | void onPrePrepareDraws(GrRecordingContext*, |
328 | const GrSurfaceProxyView* writeView, |
329 | GrAppliedClip*, |
330 | const GrXferProcessor::DstProxyView&) override { |
331 | // TODO [PI]: implement |
332 | } |
333 | |
334 | void onPrepareDraws(Target* target) override { |
335 | int instanceCount = fShapes.count(); |
336 | |
337 | static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures; |
338 | static_assert(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures); |
339 | |
340 | FlushInfo flushInfo; |
341 | flushInfo.fPrimProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures); |
342 | int numActiveProxies = fAtlas->numActivePages(); |
343 | const auto views = fAtlas->getViews(); |
344 | for (int i = 0; i < numActiveProxies; ++i) { |
345 | // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the |
346 | // proxies don't get added during the visitProxies call. Thus we add them here. |
347 | flushInfo.fPrimProcProxies[i] = views[i].proxy(); |
348 | target->sampledProxyArray()->push_back(views[i].proxy()); |
349 | } |
350 | |
351 | // Setup GrGeometryProcessor |
352 | const SkMatrix& ctm = fShapes[0].fViewMatrix; |
353 | if (fUsesDistanceField) { |
354 | uint32_t flags = 0; |
355 | // Still need to key off of ctm to pick the right shader for the transformed quad |
356 | flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0; |
357 | flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; |
358 | flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0; |
359 | |
360 | const SkMatrix* matrix; |
361 | SkMatrix invert; |
362 | if (ctm.hasPerspective()) { |
363 | matrix = &ctm; |
364 | } else if (fHelper.usesLocalCoords()) { |
365 | if (!ctm.invert(&invert)) { |
366 | return; |
367 | } |
368 | matrix = &invert; |
369 | } else { |
370 | matrix = &SkMatrix::I(); |
371 | } |
372 | flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make( |
373 | target->allocator(), *target->caps().shaderCaps(), *matrix, fWideColor, |
374 | fAtlas->getViews(), fAtlas->numActivePages(), GrSamplerState::Filter::kBilerp, |
375 | flags); |
376 | } else { |
377 | SkMatrix invert; |
378 | if (fHelper.usesLocalCoords()) { |
379 | if (!ctm.invert(&invert)) { |
380 | return; |
381 | } |
382 | } |
383 | |
384 | flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( |
385 | target->allocator(), *target->caps().shaderCaps(), this->color(), fWideColor, |
386 | fAtlas->getViews(), fAtlas->numActivePages(), GrSamplerState::Filter::kNearest, |
387 | kA8_GrMaskFormat, invert, false); |
388 | } |
389 | |
390 | // allocate vertices |
391 | const size_t kVertexStride = flushInfo.fGeometryProcessor->vertexStride(); |
392 | |
393 | // We need to make sure we don't overflow a 32 bit int when we request space in the |
394 | // makeVertexSpace call below. |
395 | if (instanceCount > SK_MaxS32 / GrResourceProvider::NumVertsPerNonAAQuad()) { |
396 | return; |
397 | } |
398 | GrVertexWriter vertices{ target->makeVertexSpace( |
399 | kVertexStride, GrResourceProvider::NumVertsPerNonAAQuad() * instanceCount, |
400 | &flushInfo.fVertexBuffer, &flushInfo.fVertexOffset)}; |
401 | |
402 | flushInfo.fIndexBuffer = target->resourceProvider()->refNonAAQuadIndexBuffer(); |
403 | if (!vertices.fPtr || !flushInfo.fIndexBuffer) { |
404 | SkDebugf("Could not allocate vertices\n" ); |
405 | return; |
406 | } |
407 | |
408 | flushInfo.fInstancesToFlush = 0; |
409 | for (int i = 0; i < instanceCount; i++) { |
410 | const Entry& args = fShapes[i]; |
411 | |
412 | ShapeData* shapeData; |
413 | if (fUsesDistanceField) { |
414 | // get mip level |
415 | SkScalar maxScale; |
416 | const SkRect& bounds = args.fShape.bounds(); |
417 | if (args.fViewMatrix.hasPerspective()) { |
418 | // approximate the scale since we can't get it from the matrix |
419 | SkRect xformedBounds; |
420 | args.fViewMatrix.mapRect(&xformedBounds, bounds); |
421 | maxScale = SkScalarAbs(std::max(xformedBounds.width() / bounds.width(), |
422 | xformedBounds.height() / bounds.height())); |
423 | } else { |
424 | maxScale = SkScalarAbs(args.fViewMatrix.getMaxScale()); |
425 | } |
426 | SkScalar maxDim = std::max(bounds.width(), bounds.height()); |
427 | // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.) |
428 | // In the majority of cases this will yield a crisper rendering. |
429 | SkScalar mipScale = 1.0f; |
430 | // Our mipscale is the maxScale clamped to the next highest power of 2 |
431 | if (maxScale <= SK_ScalarHalf) { |
432 | SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale))); |
433 | mipScale = SkScalarPow(2, -log); |
434 | } else if (maxScale > SK_Scalar1) { |
435 | SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale)); |
436 | mipScale = SkScalarPow(2, log); |
437 | } |
438 | SkASSERT(maxScale <= mipScale); |
439 | |
440 | SkScalar mipSize = mipScale*SkScalarAbs(maxDim); |
441 | // For sizes less than kIdealMinMIP we want to use as large a distance field as we can |
442 | // so we can preserve as much detail as possible. However, we can't scale down more |
443 | // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize |
444 | // just bigger than the ideal, and then scale down until we are no more than 4x the |
445 | // original mipsize. |
446 | if (mipSize < kIdealMinMIP) { |
447 | SkScalar newMipSize = mipSize; |
448 | do { |
449 | newMipSize *= 2; |
450 | } while (newMipSize < kIdealMinMIP); |
451 | while (newMipSize > 4 * mipSize) { |
452 | newMipSize *= 0.25f; |
453 | } |
454 | mipSize = newMipSize; |
455 | } |
456 | SkScalar desiredDimension = std::min(mipSize, kMaxMIP); |
457 | |
458 | // check to see if df path is cached |
459 | ShapeDataKey key(args.fShape, SkScalarCeilToInt(desiredDimension)); |
460 | shapeData = fShapeCache->find(key); |
461 | if (nullptr == shapeData || !fAtlas->hasID(shapeData->fAtlasLocator)) { |
462 | // Remove the stale cache entry |
463 | if (shapeData) { |
464 | fShapeCache->remove(shapeData->fKey); |
465 | fShapeList->remove(shapeData); |
466 | delete shapeData; |
467 | } |
468 | SkScalar scale = desiredDimension / maxDim; |
469 | |
470 | shapeData = new ShapeData; |
471 | if (!this->addDFPathToAtlas(target, |
472 | &flushInfo, |
473 | fAtlas, |
474 | shapeData, |
475 | args.fShape, |
476 | SkScalarCeilToInt(desiredDimension), |
477 | scale)) { |
478 | delete shapeData; |
479 | continue; |
480 | } |
481 | } |
482 | } else { |
483 | // check to see if bitmap path is cached |
484 | ShapeDataKey key(args.fShape, args.fViewMatrix); |
485 | shapeData = fShapeCache->find(key); |
486 | if (nullptr == shapeData || !fAtlas->hasID(shapeData->fAtlasLocator)) { |
487 | // Remove the stale cache entry |
488 | if (shapeData) { |
489 | fShapeCache->remove(shapeData->fKey); |
490 | fShapeList->remove(shapeData); |
491 | delete shapeData; |
492 | } |
493 | |
494 | shapeData = new ShapeData; |
495 | if (!this->addBMPathToAtlas(target, |
496 | &flushInfo, |
497 | fAtlas, |
498 | shapeData, |
499 | args.fShape, |
500 | args.fViewMatrix)) { |
501 | delete shapeData; |
502 | continue; |
503 | } |
504 | } |
505 | } |
506 | |
507 | auto uploadTarget = target->deferredUploadTarget(); |
508 | fAtlas->setLastUseToken( |
509 | shapeData->fAtlasLocator, uploadTarget->tokenTracker()->nextDrawToken()); |
510 | |
511 | this->writePathVertices(fAtlas, vertices, GrVertexColor(args.fColor, fWideColor), |
512 | args.fViewMatrix, shapeData); |
513 | flushInfo.fInstancesToFlush++; |
514 | } |
515 | |
516 | this->flush(target, &flushInfo); |
517 | } |
518 | |
519 | bool addToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas, |
520 | int width, int height, const void* image, |
521 | GrDrawOpAtlas::AtlasLocator* atlasLocator) const { |
522 | auto resourceProvider = target->resourceProvider(); |
523 | auto uploadTarget = target->deferredUploadTarget(); |
524 | |
525 | GrDrawOpAtlas::ErrorCode code = atlas->addToAtlas(resourceProvider, uploadTarget, |
526 | width, height, image, atlasLocator); |
527 | if (GrDrawOpAtlas::ErrorCode::kError == code) { |
528 | return false; |
529 | } |
530 | |
531 | if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) { |
532 | this->flush(target, flushInfo); |
533 | |
534 | code = atlas->addToAtlas(resourceProvider, uploadTarget, width, height, |
535 | image, atlasLocator); |
536 | } |
537 | |
538 | return GrDrawOpAtlas::ErrorCode::kSucceeded == code; |
539 | } |
540 | |
541 | bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, |
542 | GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape, |
543 | uint32_t dimension, SkScalar scale) const { |
544 | |
545 | const SkRect& bounds = shape.bounds(); |
546 | |
547 | // generate bounding rect for bitmap draw |
548 | SkRect scaledBounds = bounds; |
549 | // scale to mip level size |
550 | scaledBounds.fLeft *= scale; |
551 | scaledBounds.fTop *= scale; |
552 | scaledBounds.fRight *= scale; |
553 | scaledBounds.fBottom *= scale; |
554 | // subtract out integer portion of origin |
555 | // (SDF created will be placed with fractional offset burnt in) |
556 | SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft); |
557 | SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop); |
558 | scaledBounds.offset(-dx, -dy); |
559 | // get integer boundary |
560 | SkIRect devPathBounds; |
561 | scaledBounds.roundOut(&devPathBounds); |
562 | // place devBounds at origin with padding to allow room for antialiasing |
563 | int width = devPathBounds.width() + 2 * kAntiAliasPad; |
564 | int height = devPathBounds.height() + 2 * kAntiAliasPad; |
565 | devPathBounds = SkIRect::MakeWH(width, height); |
566 | SkScalar translateX = kAntiAliasPad - dx; |
567 | SkScalar translateY = kAntiAliasPad - dy; |
568 | |
569 | // draw path to bitmap |
570 | SkMatrix drawMatrix; |
571 | drawMatrix.setScale(scale, scale); |
572 | drawMatrix.postTranslate(translateX, translateY); |
573 | |
574 | SkASSERT(devPathBounds.fLeft == 0); |
575 | SkASSERT(devPathBounds.fTop == 0); |
576 | SkASSERT(devPathBounds.width() > 0); |
577 | SkASSERT(devPathBounds.height() > 0); |
578 | |
579 | // setup signed distance field storage |
580 | SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad); |
581 | width = dfBounds.width(); |
582 | height = dfBounds.height(); |
583 | // TODO We should really generate this directly into the plot somehow |
584 | SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char)); |
585 | |
586 | SkPath path; |
587 | shape.asPath(&path); |
588 | // Generate signed distance field directly from SkPath |
589 | bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(), |
590 | path, drawMatrix, |
591 | width, height, width * sizeof(unsigned char)); |
592 | if (!succeed) { |
593 | // setup bitmap backing |
594 | SkAutoPixmapStorage dst; |
595 | if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), |
596 | devPathBounds.height()))) { |
597 | return false; |
598 | } |
599 | sk_bzero(dst.writable_addr(), dst.computeByteSize()); |
600 | |
601 | // rasterize path |
602 | SkPaint paint; |
603 | paint.setStyle(SkPaint::kFill_Style); |
604 | paint.setAntiAlias(true); |
605 | |
606 | SkDraw draw; |
607 | |
608 | SkRasterClip rasterClip; |
609 | rasterClip.setRect(devPathBounds); |
610 | draw.fRC = &rasterClip; |
611 | draw.fMatrix = &drawMatrix; |
612 | draw.fDst = dst; |
613 | |
614 | draw.drawPathCoverage(path, paint); |
615 | |
616 | // Generate signed distance field |
617 | SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(), |
618 | (const unsigned char*)dst.addr(), |
619 | dst.width(), dst.height(), dst.rowBytes()); |
620 | } |
621 | |
622 | // add to atlas |
623 | if (!this->addToAtlas(target, flushInfo, atlas, width, height, dfStorage.get(), |
624 | &shapeData->fAtlasLocator)) { |
625 | return false; |
626 | } |
627 | |
628 | // add to cache |
629 | shapeData->fKey.set(shape, dimension); |
630 | |
631 | shapeData->fBounds = SkRect::Make(devPathBounds); |
632 | shapeData->fBounds.offset(-translateX, -translateY); |
633 | shapeData->fBounds.fLeft /= scale; |
634 | shapeData->fBounds.fTop /= scale; |
635 | shapeData->fBounds.fRight /= scale; |
636 | shapeData->fBounds.fBottom /= scale; |
637 | |
638 | fShapeCache->add(shapeData); |
639 | fShapeList->addToTail(shapeData); |
640 | #ifdef DF_PATH_TRACKING |
641 | ++g_NumCachedPaths; |
642 | #endif |
643 | return true; |
644 | } |
645 | |
646 | bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, |
647 | GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape, |
648 | const SkMatrix& ctm) const { |
649 | const SkRect& bounds = shape.bounds(); |
650 | if (bounds.isEmpty()) { |
651 | return false; |
652 | } |
653 | SkMatrix drawMatrix(ctm); |
654 | SkScalar tx = ctm.getTranslateX(); |
655 | SkScalar ty = ctm.getTranslateY(); |
656 | tx -= SkScalarFloorToScalar(tx); |
657 | ty -= SkScalarFloorToScalar(ty); |
658 | drawMatrix.set(SkMatrix::kMTransX, tx); |
659 | drawMatrix.set(SkMatrix::kMTransY, ty); |
660 | SkRect shapeDevBounds; |
661 | drawMatrix.mapRect(&shapeDevBounds, bounds); |
662 | SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft); |
663 | SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop); |
664 | |
665 | // get integer boundary |
666 | SkIRect devPathBounds; |
667 | shapeDevBounds.roundOut(&devPathBounds); |
668 | // place devBounds at origin with padding to allow room for antialiasing |
669 | int width = devPathBounds.width() + 2 * kAntiAliasPad; |
670 | int height = devPathBounds.height() + 2 * kAntiAliasPad; |
671 | devPathBounds = SkIRect::MakeWH(width, height); |
672 | SkScalar translateX = kAntiAliasPad - dx; |
673 | SkScalar translateY = kAntiAliasPad - dy; |
674 | |
675 | SkASSERT(devPathBounds.fLeft == 0); |
676 | SkASSERT(devPathBounds.fTop == 0); |
677 | SkASSERT(devPathBounds.width() > 0); |
678 | SkASSERT(devPathBounds.height() > 0); |
679 | |
680 | SkPath path; |
681 | shape.asPath(&path); |
682 | // setup bitmap backing |
683 | SkAutoPixmapStorage dst; |
684 | if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), |
685 | devPathBounds.height()))) { |
686 | return false; |
687 | } |
688 | sk_bzero(dst.writable_addr(), dst.computeByteSize()); |
689 | |
690 | // rasterize path |
691 | SkPaint paint; |
692 | paint.setStyle(SkPaint::kFill_Style); |
693 | paint.setAntiAlias(true); |
694 | |
695 | SkDraw draw; |
696 | |
697 | SkRasterClip rasterClip; |
698 | rasterClip.setRect(devPathBounds); |
699 | draw.fRC = &rasterClip; |
700 | drawMatrix.postTranslate(translateX, translateY); |
701 | draw.fMatrix = &drawMatrix; |
702 | draw.fDst = dst; |
703 | |
704 | draw.drawPathCoverage(path, paint); |
705 | |
706 | // add to atlas |
707 | if (!this->addToAtlas(target, flushInfo, atlas, dst.width(), dst.height(), dst.addr(), |
708 | &shapeData->fAtlasLocator)) { |
709 | return false; |
710 | } |
711 | |
712 | // add to cache |
713 | shapeData->fKey.set(shape, ctm); |
714 | |
715 | shapeData->fBounds = SkRect::Make(devPathBounds); |
716 | shapeData->fBounds.offset(-translateX, -translateY); |
717 | |
718 | fShapeCache->add(shapeData); |
719 | fShapeList->addToTail(shapeData); |
720 | #ifdef DF_PATH_TRACKING |
721 | ++g_NumCachedPaths; |
722 | #endif |
723 | return true; |
724 | } |
725 | |
726 | void writePathVertices(GrDrawOpAtlas* atlas, |
727 | GrVertexWriter& vertices, |
728 | const GrVertexColor& color, |
729 | const SkMatrix& ctm, |
730 | const ShapeData* shapeData) const { |
731 | SkRect translatedBounds(shapeData->fBounds); |
732 | if (!fUsesDistanceField) { |
733 | translatedBounds.offset(SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransX)), |
734 | SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransY))); |
735 | } |
736 | |
737 | // set up texture coordinates |
738 | auto texCoords = GrVertexWriter::TriStripFromUVs(shapeData->fAtlasLocator.getUVs( |
739 | fUsesDistanceField ? SK_DistanceFieldPad : 0)); |
740 | |
741 | if (fUsesDistanceField && !ctm.hasPerspective()) { |
742 | vertices.writeQuad(GrQuad::MakeFromRect(translatedBounds, ctm), |
743 | color, |
744 | texCoords); |
745 | } else { |
746 | vertices.writeQuad(GrVertexWriter::TriStripFromRect(translatedBounds), |
747 | color, |
748 | texCoords); |
749 | } |
750 | } |
751 | |
752 | void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const { |
753 | GrGeometryProcessor* gp = flushInfo->fGeometryProcessor; |
754 | int numAtlasTextures = SkToInt(fAtlas->numActivePages()); |
755 | const auto views = fAtlas->getViews(); |
756 | if (gp->numTextureSamplers() != numAtlasTextures) { |
757 | for (int i = gp->numTextureSamplers(); i < numAtlasTextures; ++i) { |
758 | flushInfo->fPrimProcProxies[i] = views[i].proxy(); |
759 | // This op does not know its atlas proxies when it is added to a GrOpsTasks, so the |
760 | // proxies don't get added during the visitProxies call. Thus we add them here. |
761 | target->sampledProxyArray()->push_back(views[i].proxy()); |
762 | } |
763 | // During preparation the number of atlas pages has increased. |
764 | // Update the proxies used in the GP to match. |
765 | if (fUsesDistanceField) { |
766 | reinterpret_cast<GrDistanceFieldPathGeoProc*>(gp)->addNewViews( |
767 | fAtlas->getViews(), fAtlas->numActivePages(), |
768 | GrSamplerState::Filter::kBilerp); |
769 | } else { |
770 | reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews( |
771 | fAtlas->getViews(), fAtlas->numActivePages(), |
772 | GrSamplerState::Filter::kNearest); |
773 | } |
774 | } |
775 | |
776 | if (flushInfo->fInstancesToFlush) { |
777 | GrSimpleMesh* mesh = target->allocMesh(); |
778 | mesh->setIndexedPatterned(flushInfo->fIndexBuffer, |
779 | GrResourceProvider::NumIndicesPerNonAAQuad(), |
780 | flushInfo->fInstancesToFlush, |
781 | GrResourceProvider::MaxNumNonAAQuads(), |
782 | flushInfo->fVertexBuffer, |
783 | GrResourceProvider::NumVertsPerNonAAQuad(), |
784 | flushInfo->fVertexOffset); |
785 | target->recordDraw(flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fPrimProcProxies, |
786 | GrPrimitiveType::kTriangles); |
787 | flushInfo->fVertexOffset += GrResourceProvider::NumVertsPerNonAAQuad() * |
788 | flushInfo->fInstancesToFlush; |
789 | flushInfo->fInstancesToFlush = 0; |
790 | } |
791 | } |
792 | |
793 | void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
794 | auto pipeline = fHelper.createPipelineWithStencil(flushState); |
795 | |
796 | flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, pipeline); |
797 | } |
798 | |
799 | const SkPMColor4f& color() const { return fShapes[0].fColor; } |
800 | bool usesDistanceField() const { return fUsesDistanceField; } |
801 | |
802 | CombineResult onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*, |
803 | const GrCaps& caps) override { |
804 | SmallPathOp* that = t->cast<SmallPathOp>(); |
805 | if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { |
806 | return CombineResult::kCannotCombine; |
807 | } |
808 | |
809 | if (this->usesDistanceField() != that->usesDistanceField()) { |
810 | return CombineResult::kCannotCombine; |
811 | } |
812 | |
813 | const SkMatrix& thisCtm = this->fShapes[0].fViewMatrix; |
814 | const SkMatrix& thatCtm = that->fShapes[0].fViewMatrix; |
815 | |
816 | if (thisCtm.hasPerspective() != thatCtm.hasPerspective()) { |
817 | return CombineResult::kCannotCombine; |
818 | } |
819 | |
820 | // We can position on the cpu unless we're in perspective, |
821 | // but also need to make sure local matrices are identical |
822 | if ((thisCtm.hasPerspective() || fHelper.usesLocalCoords()) && |
823 | !SkMatrixPriv::CheapEqual(thisCtm, thatCtm)) { |
824 | return CombineResult::kCannotCombine; |
825 | } |
826 | |
827 | // Depending on the ctm we may have a different shader for SDF paths |
828 | if (this->usesDistanceField()) { |
829 | if (thisCtm.isScaleTranslate() != thatCtm.isScaleTranslate() || |
830 | thisCtm.isSimilarity() != thatCtm.isSimilarity()) { |
831 | return CombineResult::kCannotCombine; |
832 | } |
833 | } |
834 | |
835 | fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin()); |
836 | fWideColor |= that->fWideColor; |
837 | return CombineResult::kMerged; |
838 | } |
839 | |
840 | bool fUsesDistanceField; |
841 | |
842 | struct Entry { |
843 | SkPMColor4f fColor; |
844 | GrShape fShape; |
845 | SkMatrix fViewMatrix; |
846 | }; |
847 | |
848 | SkSTArray<1, Entry> fShapes; |
849 | Helper fHelper; |
850 | GrDrawOpAtlas* fAtlas; |
851 | ShapeCache* fShapeCache; |
852 | ShapeDataList* fShapeList; |
853 | bool fGammaCorrect; |
854 | bool fWideColor; |
855 | |
856 | typedef GrMeshDrawOp INHERITED; |
857 | }; |
858 | |
859 | bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) { |
860 | GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), |
861 | "GrSmallPathRenderer::onDrawPath" ); |
862 | |
863 | // we've already bailed on inverse filled paths, so this is safe |
864 | SkASSERT(!args.fShape->isEmpty()); |
865 | SkASSERT(args.fShape->hasUnstyledKey()); |
866 | if (!fAtlas) { |
867 | const GrBackendFormat format = args.fContext->priv().caps()->getDefaultBackendFormat( |
868 | GrColorType::kAlpha_8, GrRenderable::kNo); |
869 | |
870 | GrDrawOpAtlasConfig atlasConfig(args.fContext->priv().caps()->maxTextureSize(), |
871 | kMaxAtlasTextureBytes); |
872 | SkISize size = atlasConfig.atlasDimensions(kA8_GrMaskFormat); |
873 | fAtlas = GrDrawOpAtlas::Make(args.fContext->priv().proxyProvider(), format, |
874 | GrColorType::kAlpha_8, size.width(), size.height(), |
875 | kPlotWidth, kPlotHeight, this, |
876 | GrDrawOpAtlas::AllowMultitexturing::kYes, this); |
877 | if (!fAtlas) { |
878 | return false; |
879 | } |
880 | } |
881 | |
882 | std::unique_ptr<GrDrawOp> op = SmallPathOp::Make( |
883 | args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, fAtlas.get(), |
884 | &fShapeCache, &fShapeList, args.fGammaCorrect, args.fUserStencilSettings); |
885 | args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); |
886 | |
887 | return true; |
888 | } |
889 | |
890 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
891 | |
892 | #if GR_TEST_UTILS |
893 | |
894 | struct GrSmallPathRenderer::PathTestStruct : public GrDrawOpAtlas::EvictionCallback, |
895 | public GrDrawOpAtlas::GenerationCounter { |
896 | PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {} |
897 | ~PathTestStruct() override { this->reset(); } |
898 | |
899 | void reset() { |
900 | ShapeDataList::Iter iter; |
901 | iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); |
902 | ShapeData* shapeData; |
903 | while ((shapeData = iter.get())) { |
904 | iter.next(); |
905 | fShapeList.remove(shapeData); |
906 | delete shapeData; |
907 | } |
908 | fAtlas = nullptr; |
909 | fShapeCache.reset(); |
910 | } |
911 | |
912 | void evict(GrDrawOpAtlas::PlotLocator plotLocator) override { |
913 | // remove any paths that use this plot |
914 | ShapeDataList::Iter iter; |
915 | iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart); |
916 | ShapeData* shapeData; |
917 | while ((shapeData = iter.get())) { |
918 | iter.next(); |
919 | if (plotLocator == shapeData->fAtlasLocator.plotLocator()) { |
920 | fShapeCache.remove(shapeData->fKey); |
921 | fShapeList.remove(shapeData); |
922 | delete shapeData; |
923 | } |
924 | } |
925 | } |
926 | |
927 | uint32_t fContextID; |
928 | std::unique_ptr<GrDrawOpAtlas> fAtlas; |
929 | ShapeCache fShapeCache; |
930 | ShapeDataList fShapeList; |
931 | }; |
932 | |
933 | std::unique_ptr<GrDrawOp> GrSmallPathRenderer::createOp_TestingOnly( |
934 | GrRecordingContext* context, |
935 | GrPaint&& paint, |
936 | const GrShape& shape, |
937 | const SkMatrix& viewMatrix, |
938 | GrDrawOpAtlas* atlas, |
939 | ShapeCache* shapeCache, |
940 | ShapeDataList* shapeList, |
941 | bool gammaCorrect, |
942 | const GrUserStencilSettings* stencil) { |
943 | |
944 | return GrSmallPathRenderer::SmallPathOp::Make(context, std::move(paint), shape, viewMatrix, |
945 | atlas, shapeCache, shapeList, gammaCorrect, |
946 | stencil); |
947 | |
948 | } |
949 | |
950 | GR_DRAW_OP_TEST_DEFINE(SmallPathOp) { |
951 | using PathTestStruct = GrSmallPathRenderer::PathTestStruct; |
952 | static PathTestStruct gTestStruct; |
953 | |
954 | if (context->priv().contextID() != gTestStruct.fContextID) { |
955 | gTestStruct.fContextID = context->priv().contextID(); |
956 | gTestStruct.reset(); |
957 | const GrBackendFormat format = context->priv().caps()->getDefaultBackendFormat( |
958 | GrColorType::kAlpha_8, GrRenderable::kNo); |
959 | GrDrawOpAtlasConfig atlasConfig(context->priv().caps()->maxTextureSize(), |
960 | kMaxAtlasTextureBytes); |
961 | SkISize size = atlasConfig.atlasDimensions(kA8_GrMaskFormat); |
962 | gTestStruct.fAtlas = |
963 | GrDrawOpAtlas::Make(context->priv().proxyProvider(), format, GrColorType::kAlpha_8, |
964 | size.width(), size.height(), kPlotWidth, kPlotHeight, |
965 | &gTestStruct, |
966 | GrDrawOpAtlas::AllowMultitexturing::kYes, &gTestStruct); |
967 | } |
968 | |
969 | SkMatrix viewMatrix = GrTest::TestMatrix(random); |
970 | bool gammaCorrect = random->nextBool(); |
971 | |
972 | // This path renderer only allows fill styles. |
973 | GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill()); |
974 | return GrSmallPathRenderer::createOp_TestingOnly( |
975 | context, |
976 | std::move(paint), shape, viewMatrix, |
977 | gTestStruct.fAtlas.get(), |
978 | &gTestStruct.fShapeCache, |
979 | &gTestStruct.fShapeList, |
980 | gammaCorrect, |
981 | GrGetRandomStencil(random, context)); |
982 | } |
983 | |
984 | #endif |
985 | |