Line data Source code
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 "GrSmallPathRenderer.h"
10 :
11 : #include "GrBuffer.h"
12 : #include "GrContext.h"
13 : #include "GrDistanceFieldGenFromVector.h"
14 : #include "GrDrawOpTest.h"
15 : #include "GrOpFlushState.h"
16 : #include "GrPipelineBuilder.h"
17 : #include "GrResourceProvider.h"
18 : #include "GrSWMaskHelper.h"
19 : #include "GrSurfacePriv.h"
20 : #include "GrSurfaceProxyPriv.h"
21 : #include "GrTexturePriv.h"
22 : #include "effects/GrBitmapTextGeoProc.h"
23 : #include "effects/GrDistanceFieldGeoProc.h"
24 : #include "ops/GrMeshDrawOp.h"
25 :
26 : #include "SkAutoMalloc.h"
27 : #include "SkDistanceFieldGen.h"
28 : #include "SkPathOps.h"
29 :
30 : #define ATLAS_TEXTURE_WIDTH 2048
31 : #define ATLAS_TEXTURE_HEIGHT 2048
32 : #define PLOT_WIDTH 512
33 : #define PLOT_HEIGHT 256
34 :
35 : #define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
36 : #define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
37 :
38 : #ifdef DF_PATH_TRACKING
39 : static int g_NumCachedShapes = 0;
40 : static int g_NumFreedShapes = 0;
41 : #endif
42 :
43 : // mip levels
44 : static const SkScalar kIdealMinMIP = 12;
45 : static const SkScalar kMaxMIP = 162;
46 :
47 : static const SkScalar kMaxDim = 73;
48 : static const SkScalar kMinSize = SK_ScalarHalf;
49 : static const SkScalar kMaxSize = 2*kMaxMIP;
50 :
51 : // Callback to clear out internal path cache when eviction occurs
52 0 : void GrSmallPathRenderer::HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
53 0 : GrSmallPathRenderer* dfpr = (GrSmallPathRenderer*)pr;
54 : // remove any paths that use this plot
55 0 : ShapeDataList::Iter iter;
56 0 : iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
57 : ShapeData* shapeData;
58 0 : while ((shapeData = iter.get())) {
59 0 : iter.next();
60 0 : if (id == shapeData->fID) {
61 0 : dfpr->fShapeCache.remove(shapeData->fKey);
62 0 : dfpr->fShapeList.remove(shapeData);
63 0 : delete shapeData;
64 : #ifdef DF_PATH_TRACKING
65 : ++g_NumFreedPaths;
66 : #endif
67 : }
68 : }
69 0 : }
70 :
71 : ////////////////////////////////////////////////////////////////////////////////
72 0 : GrSmallPathRenderer::GrSmallPathRenderer() : fAtlas(nullptr) {}
73 :
74 0 : GrSmallPathRenderer::~GrSmallPathRenderer() {
75 0 : ShapeDataList::Iter iter;
76 0 : iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
77 : ShapeData* shapeData;
78 0 : while ((shapeData = iter.get())) {
79 0 : iter.next();
80 0 : delete shapeData;
81 : }
82 :
83 : #ifdef DF_PATH_TRACKING
84 : SkDebugf("Cached shapes: %d, freed shapes: %d\n", g_NumCachedShapes, g_NumFreedShapes);
85 : #endif
86 0 : }
87 :
88 : ////////////////////////////////////////////////////////////////////////////////
89 0 : bool GrSmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
90 0 : if (!args.fShaderCaps->shaderDerivativeSupport()) {
91 0 : return false;
92 : }
93 : // If the shape has no key then we won't get any reuse.
94 0 : if (!args.fShape->hasUnstyledKey()) {
95 0 : return false;
96 : }
97 : // This only supports filled paths, however, the caller may apply the style to make a filled
98 : // path and try again.
99 0 : if (!args.fShape->style().isSimpleFill()) {
100 0 : return false;
101 : }
102 : // This does non-inverse coverage-based antialiased fills.
103 0 : if (GrAAType::kCoverage != args.fAAType) {
104 0 : return false;
105 : }
106 : // TODO: Support inverse fill
107 0 : if (args.fShape->inverseFilled()) {
108 0 : return false;
109 : }
110 : // currently don't support perspective
111 0 : if (args.fViewMatrix->hasPerspective()) {
112 0 : return false;
113 : }
114 :
115 : // Only support paths with bounds within kMaxDim by kMaxDim,
116 : // scaled to have bounds within kMaxSize by kMaxSize.
117 : // The goal is to accelerate rendering of lots of small paths that may be scaling.
118 : SkScalar scaleFactors[2];
119 0 : if (!args.fViewMatrix->getMinMaxScales(scaleFactors)) {
120 0 : return false;
121 : }
122 0 : SkRect bounds = args.fShape->styledBounds();
123 0 : SkScalar minDim = SkMinScalar(bounds.width(), bounds.height());
124 0 : SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
125 0 : SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]);
126 0 : SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]);
127 :
128 0 : return maxDim <= kMaxDim && kMinSize <= minSize && maxSize <= kMaxSize;
129 : }
130 :
131 : ////////////////////////////////////////////////////////////////////////////////
132 :
133 : // padding around path bounds to allow for antialiased pixels
134 : static const SkScalar kAntiAliasPad = 1.0f;
135 :
136 0 : class SmallPathOp final : public GrLegacyMeshDrawOp {
137 : public:
138 0 : DEFINE_OP_CLASS_ID
139 :
140 : using ShapeData = GrSmallPathRenderer::ShapeData;
141 : using ShapeCache = SkTDynamicHash<ShapeData, ShapeData::Key>;
142 : using ShapeDataList = GrSmallPathRenderer::ShapeDataList;
143 :
144 0 : static std::unique_ptr<GrLegacyMeshDrawOp> Make(GrColor color, const GrShape& shape,
145 : const SkMatrix& viewMatrix,
146 : GrDrawOpAtlas* atlas, ShapeCache* shapeCache,
147 : ShapeDataList* shapeList, bool gammaCorrect) {
148 : return std::unique_ptr<GrLegacyMeshDrawOp>(new SmallPathOp(
149 0 : color, shape, viewMatrix, atlas, shapeCache, shapeList, gammaCorrect));
150 : }
151 :
152 0 : const char* name() const override { return "SmallPathOp"; }
153 :
154 0 : SkString dumpInfo() const override {
155 0 : SkString string;
156 0 : for (const auto& geo : fShapes) {
157 0 : string.appendf("Color: 0x%08x\n", geo.fColor);
158 : }
159 0 : string.append(DumpPipelineInfo(*this->pipeline()));
160 0 : string.append(INHERITED::dumpInfo());
161 0 : return string;
162 : }
163 :
164 : private:
165 0 : SmallPathOp(GrColor color, const GrShape& shape, const SkMatrix& viewMatrix,
166 : GrDrawOpAtlas* atlas, ShapeCache* shapeCache, ShapeDataList* shapeList,
167 : bool gammaCorrect)
168 0 : : INHERITED(ClassID()) {
169 0 : SkASSERT(shape.hasUnstyledKey());
170 : // Compute bounds
171 0 : this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
172 :
173 : #if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
174 : fUsesDistanceField = true;
175 : #else
176 : // only use distance fields on desktop and Android framework to save space in the atlas
177 0 : fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP;
178 : #endif
179 0 : fViewMatrix = viewMatrix;
180 0 : SkVector translate = SkVector::Make(0, 0);
181 0 : if (!fUsesDistanceField) {
182 : // In this case we don't apply a view matrix, so we need to remove the non-subpixel
183 : // translation and add it back when we generate the quad for the path
184 0 : SkScalar translateX = viewMatrix.getTranslateX();
185 0 : SkScalar translateY = viewMatrix.getTranslateY();
186 : translate = SkVector::Make(SkScalarFloorToScalar(translateX),
187 0 : SkScalarFloorToScalar(translateY));
188 : // Only store the fractional part of the translation in the view matrix
189 0 : fViewMatrix.setTranslateX(translateX - translate.fX);
190 0 : fViewMatrix.setTranslateY(translateY - translate.fY);
191 : }
192 :
193 0 : fShapes.emplace_back(Entry{color, shape, translate});
194 :
195 0 : fAtlas = atlas;
196 0 : fShapeCache = shapeCache;
197 0 : fShapeList = shapeList;
198 0 : fGammaCorrect = gammaCorrect;
199 :
200 0 : }
201 :
202 0 : void getProcessorAnalysisInputs(GrProcessorAnalysisColor* color,
203 : GrProcessorAnalysisCoverage* coverage) const override {
204 0 : color->setToConstant(fShapes[0].fColor);
205 0 : *coverage = GrProcessorAnalysisCoverage::kSingleChannel;
206 0 : }
207 :
208 0 : void applyPipelineOptimizations(const PipelineOptimizations& optimizations) override {
209 0 : optimizations.getOverrideColorIfSet(&fShapes[0].fColor);
210 0 : fUsesLocalCoords = optimizations.readsLocalCoords();
211 0 : }
212 :
213 0 : struct FlushInfo {
214 : sk_sp<const GrBuffer> fVertexBuffer;
215 : sk_sp<const GrBuffer> fIndexBuffer;
216 : sk_sp<GrGeometryProcessor> fGeometryProcessor;
217 : int fVertexOffset;
218 : int fInstancesToFlush;
219 : };
220 :
221 0 : void onPrepareDraws(Target* target) const override {
222 0 : int instanceCount = fShapes.count();
223 :
224 0 : const SkMatrix& ctm = this->viewMatrix();
225 :
226 0 : FlushInfo flushInfo;
227 :
228 : // Setup GrGeometryProcessor
229 0 : GrDrawOpAtlas* atlas = fAtlas;
230 0 : if (fUsesDistanceField) {
231 0 : GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
232 :
233 0 : uint32_t flags = 0;
234 0 : flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
235 0 : flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
236 0 : flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
237 :
238 0 : flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
239 : atlas->context()->resourceProvider(),
240 0 : this->color(), this->viewMatrix(), atlas->getProxy(), params, flags,
241 0 : this->usesLocalCoords());
242 : } else {
243 0 : GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
244 :
245 : SkMatrix invert;
246 0 : if (this->usesLocalCoords()) {
247 0 : if (!this->viewMatrix().invert(&invert)) {
248 0 : SkDebugf("Could not invert viewmatrix\n");
249 0 : return;
250 : }
251 : // for local coords, we need to add the translation back in that we removed
252 : // from the stored view matrix
253 0 : invert.preTranslate(-fShapes[0].fTranslate.fX, -fShapes[0].fTranslate.fY);
254 : }
255 :
256 0 : flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
257 : atlas->context()->resourceProvider(),
258 0 : this->color(), atlas->getProxy(), params, kA8_GrMaskFormat, invert,
259 0 : this->usesLocalCoords());
260 : }
261 :
262 : // allocate vertices
263 0 : size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
264 0 : SkASSERT(vertexStride == sizeof(SkPoint) + sizeof(GrColor) + 2*sizeof(uint16_t));
265 :
266 : const GrBuffer* vertexBuffer;
267 0 : void* vertices = target->makeVertexSpace(vertexStride,
268 : kVerticesPerQuad * instanceCount,
269 : &vertexBuffer,
270 0 : &flushInfo.fVertexOffset);
271 0 : flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
272 0 : flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
273 0 : if (!vertices || !flushInfo.fIndexBuffer) {
274 0 : SkDebugf("Could not allocate vertices\n");
275 0 : return;
276 : }
277 :
278 0 : flushInfo.fInstancesToFlush = 0;
279 : // Pointer to the next set of vertices to write.
280 0 : intptr_t offset = reinterpret_cast<intptr_t>(vertices);
281 0 : for (int i = 0; i < instanceCount; i++) {
282 0 : const Entry& args = fShapes[i];
283 :
284 : ShapeData* shapeData;
285 : SkScalar maxScale;
286 0 : if (fUsesDistanceField) {
287 : // get mip level
288 0 : maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
289 0 : const SkRect& bounds = args.fShape.bounds();
290 0 : SkScalar maxDim = SkMaxScalar(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 0 : SkScalar mipScale = 1.0f;
294 : // Our mipscale is the maxScale clamped to the next highest power of 2
295 0 : if (maxScale <= SK_ScalarHalf) {
296 0 : SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
297 0 : mipScale = SkScalarPow(2, -log);
298 0 : } else if (maxScale > SK_Scalar1) {
299 0 : SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
300 0 : mipScale = SkScalarPow(2, log);
301 : }
302 0 : SkASSERT(maxScale <= mipScale);
303 :
304 0 : SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
305 : // For sizes less than kIdealMinMIP we want to use as large a distance field as we can
306 : // so we can preserve as much detail as possible. However, we can't scale down more
307 : // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
308 : // just bigger than the ideal, and then scale down until we are no more than 4x the
309 : // original mipsize.
310 0 : if (mipSize < kIdealMinMIP) {
311 0 : SkScalar newMipSize = mipSize;
312 0 : do {
313 0 : newMipSize *= 2;
314 0 : } while (newMipSize < kIdealMinMIP);
315 0 : while (newMipSize > 4 * mipSize) {
316 0 : newMipSize *= 0.25f;
317 : }
318 0 : mipSize = newMipSize;
319 : }
320 0 : SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
321 :
322 : // check to see if df path is cached
323 0 : ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
324 0 : shapeData = fShapeCache->find(key);
325 0 : if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
326 : // Remove the stale cache entry
327 0 : if (shapeData) {
328 0 : fShapeCache->remove(shapeData->fKey);
329 0 : fShapeList->remove(shapeData);
330 0 : delete shapeData;
331 : }
332 0 : SkScalar scale = desiredDimension / maxDim;
333 :
334 0 : shapeData = new ShapeData;
335 0 : if (!this->addDFPathToAtlas(target,
336 : &flushInfo,
337 : atlas,
338 : shapeData,
339 : args.fShape,
340 0 : SkScalarCeilToInt(desiredDimension),
341 : scale)) {
342 0 : delete shapeData;
343 0 : SkDebugf("Can't rasterize path\n");
344 0 : continue;
345 : }
346 : }
347 : } else {
348 : // check to see if bitmap path is cached
349 0 : ShapeData::Key key(args.fShape, this->viewMatrix());
350 0 : shapeData = fShapeCache->find(key);
351 0 : if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
352 : // Remove the stale cache entry
353 0 : if (shapeData) {
354 0 : fShapeCache->remove(shapeData->fKey);
355 0 : fShapeList->remove(shapeData);
356 0 : delete shapeData;
357 : }
358 :
359 0 : shapeData = new ShapeData;
360 0 : if (!this->addBMPathToAtlas(target,
361 : &flushInfo,
362 : atlas,
363 : shapeData,
364 : args.fShape,
365 : this->viewMatrix())) {
366 0 : delete shapeData;
367 0 : SkDebugf("Can't rasterize path\n");
368 0 : continue;
369 : }
370 : }
371 0 : maxScale = 1;
372 : }
373 :
374 0 : atlas->setLastUseToken(shapeData->fID, target->nextDrawToken());
375 :
376 0 : this->writePathVertices(target,
377 : atlas,
378 : offset,
379 0 : args.fColor,
380 : vertexStride,
381 : maxScale,
382 : args.fTranslate,
383 0 : shapeData);
384 0 : offset += kVerticesPerQuad * vertexStride;
385 0 : flushInfo.fInstancesToFlush++;
386 : }
387 :
388 0 : this->flush(target, &flushInfo);
389 : }
390 :
391 0 : bool addDFPathToAtlas(GrLegacyMeshDrawOp::Target* target, FlushInfo* flushInfo,
392 : GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
393 : uint32_t dimension, SkScalar scale) const {
394 0 : const SkRect& bounds = shape.bounds();
395 :
396 : // generate bounding rect for bitmap draw
397 0 : SkRect scaledBounds = bounds;
398 : // scale to mip level size
399 0 : scaledBounds.fLeft *= scale;
400 0 : scaledBounds.fTop *= scale;
401 0 : scaledBounds.fRight *= scale;
402 0 : scaledBounds.fBottom *= scale;
403 : // subtract out integer portion of origin
404 : // (SDF created will be placed with fractional offset burnt in)
405 0 : SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft);
406 0 : SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop);
407 0 : scaledBounds.offset(-dx, -dy);
408 : // get integer boundary
409 : SkIRect devPathBounds;
410 0 : scaledBounds.roundOut(&devPathBounds);
411 : // pad to allow room for antialiasing
412 0 : const int intPad = SkScalarCeilToInt(kAntiAliasPad);
413 : // place devBounds at origin
414 0 : int width = devPathBounds.width() + 2*intPad;
415 0 : int height = devPathBounds.height() + 2*intPad;
416 0 : devPathBounds = SkIRect::MakeWH(width, height);
417 :
418 : // draw path to bitmap
419 : SkMatrix drawMatrix;
420 0 : drawMatrix.setScale(scale, scale);
421 0 : drawMatrix.postTranslate(intPad - dx, intPad - dy);
422 :
423 0 : SkASSERT(devPathBounds.fLeft == 0);
424 0 : SkASSERT(devPathBounds.fTop == 0);
425 0 : SkASSERT(devPathBounds.width() > 0);
426 0 : SkASSERT(devPathBounds.height() > 0);
427 :
428 : // setup signed distance field storage
429 0 : SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad);
430 0 : width = dfBounds.width();
431 0 : height = dfBounds.height();
432 : // TODO We should really generate this directly into the plot somehow
433 0 : SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
434 :
435 0 : SkPath path;
436 0 : shape.asPath(&path);
437 : #ifndef SK_USE_LEGACY_DISTANCE_FIELDS
438 : // Generate signed distance field directly from SkPath
439 : bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(),
440 : path, drawMatrix,
441 : width, height, width * sizeof(unsigned char));
442 : if (!succeed) {
443 : #endif
444 : // setup bitmap backing
445 0 : SkAutoPixmapStorage dst;
446 0 : if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
447 : devPathBounds.height()))) {
448 0 : return false;
449 : }
450 0 : sk_bzero(dst.writable_addr(), dst.getSafeSize());
451 :
452 : // rasterize path
453 0 : SkPaint paint;
454 0 : paint.setStyle(SkPaint::kFill_Style);
455 0 : paint.setAntiAlias(true);
456 :
457 0 : SkDraw draw;
458 0 : sk_bzero(&draw, sizeof(draw));
459 :
460 0 : SkRasterClip rasterClip;
461 0 : rasterClip.setRect(devPathBounds);
462 0 : draw.fRC = &rasterClip;
463 0 : draw.fMatrix = &drawMatrix;
464 0 : draw.fDst = dst;
465 :
466 0 : draw.drawPathCoverage(path, paint);
467 :
468 : // Generate signed distance field
469 0 : SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
470 0 : (const unsigned char*)dst.addr(),
471 0 : dst.width(), dst.height(), dst.rowBytes());
472 : #ifndef SK_USE_LEGACY_DISTANCE_FIELDS
473 : }
474 : #endif
475 :
476 : // add to atlas
477 : SkIPoint16 atlasLocation;
478 : GrDrawOpAtlas::AtlasID id;
479 0 : if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
480 0 : this->flush(target, flushInfo);
481 0 : if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
482 0 : return false;
483 : }
484 : }
485 :
486 : // add to cache
487 0 : shapeData->fKey.set(shape, dimension);
488 0 : shapeData->fID = id;
489 :
490 : // set the bounds rect to the original bounds
491 0 : shapeData->fBounds = bounds;
492 :
493 : // set up path to texture coordinate transform
494 0 : shapeData->fScale = scale;
495 0 : dx -= SK_DistanceFieldPad + kAntiAliasPad;
496 0 : dy -= SK_DistanceFieldPad + kAntiAliasPad;
497 0 : shapeData->fTranslate.fX = atlasLocation.fX - dx;
498 0 : shapeData->fTranslate.fY = atlasLocation.fY - dy;
499 :
500 0 : fShapeCache->add(shapeData);
501 0 : fShapeList->addToTail(shapeData);
502 : #ifdef DF_PATH_TRACKING
503 : ++g_NumCachedPaths;
504 : #endif
505 0 : return true;
506 : }
507 :
508 0 : bool addBMPathToAtlas(GrLegacyMeshDrawOp::Target* target, FlushInfo* flushInfo,
509 : GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
510 : const SkMatrix& ctm) const {
511 0 : const SkRect& bounds = shape.bounds();
512 0 : if (bounds.isEmpty()) {
513 0 : return false;
514 : }
515 0 : SkMatrix drawMatrix(ctm);
516 0 : drawMatrix.set(SkMatrix::kMTransX, SkScalarFraction(ctm.get(SkMatrix::kMTransX)));
517 0 : drawMatrix.set(SkMatrix::kMTransY, SkScalarFraction(ctm.get(SkMatrix::kMTransY)));
518 : SkRect shapeDevBounds;
519 0 : drawMatrix.mapRect(&shapeDevBounds, bounds);
520 0 : SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft);
521 0 : SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop);
522 :
523 : // get integer boundary
524 : SkIRect devPathBounds;
525 0 : shapeDevBounds.roundOut(&devPathBounds);
526 : // pad to allow room for antialiasing
527 0 : const int intPad = SkScalarCeilToInt(kAntiAliasPad);
528 : // place devBounds at origin
529 0 : int width = devPathBounds.width() + 2 * intPad;
530 0 : int height = devPathBounds.height() + 2 * intPad;
531 0 : devPathBounds = SkIRect::MakeWH(width, height);
532 0 : SkScalar translateX = intPad - dx;
533 0 : SkScalar translateY = intPad - dy;
534 :
535 0 : SkASSERT(devPathBounds.fLeft == 0);
536 0 : SkASSERT(devPathBounds.fTop == 0);
537 0 : SkASSERT(devPathBounds.width() > 0);
538 0 : SkASSERT(devPathBounds.height() > 0);
539 :
540 0 : SkPath path;
541 0 : shape.asPath(&path);
542 : // setup bitmap backing
543 0 : SkAutoPixmapStorage dst;
544 0 : if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
545 : devPathBounds.height()))) {
546 0 : return false;
547 : }
548 0 : sk_bzero(dst.writable_addr(), dst.getSafeSize());
549 :
550 : // rasterize path
551 0 : SkPaint paint;
552 0 : paint.setStyle(SkPaint::kFill_Style);
553 0 : paint.setAntiAlias(true);
554 :
555 0 : SkDraw draw;
556 0 : sk_bzero(&draw, sizeof(draw));
557 :
558 0 : SkRasterClip rasterClip;
559 0 : rasterClip.setRect(devPathBounds);
560 0 : draw.fRC = &rasterClip;
561 0 : drawMatrix.postTranslate(translateX, translateY);
562 0 : draw.fMatrix = &drawMatrix;
563 0 : draw.fDst = dst;
564 :
565 0 : draw.drawPathCoverage(path, paint);
566 :
567 : // add to atlas
568 : SkIPoint16 atlasLocation;
569 : GrDrawOpAtlas::AtlasID id;
570 0 : if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
571 : &atlasLocation)) {
572 0 : this->flush(target, flushInfo);
573 0 : if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
574 : &atlasLocation)) {
575 0 : return false;
576 : }
577 : }
578 :
579 : // add to cache
580 0 : shapeData->fKey.set(shape, ctm);
581 0 : shapeData->fID = id;
582 :
583 : // set the bounds rect to the original bounds
584 0 : shapeData->fBounds = SkRect::Make(devPathBounds);
585 0 : shapeData->fBounds.offset(-translateX, -translateY);
586 :
587 : // set up path to texture coordinate transform
588 0 : shapeData->fScale = SK_Scalar1;
589 0 : shapeData->fTranslate.fX = atlasLocation.fX + translateX;
590 0 : shapeData->fTranslate.fY = atlasLocation.fY + translateY;
591 :
592 0 : fShapeCache->add(shapeData);
593 0 : fShapeList->addToTail(shapeData);
594 : #ifdef DF_PATH_TRACKING
595 : ++g_NumCachedPaths;
596 : #endif
597 0 : return true;
598 : }
599 :
600 0 : void writePathVertices(GrDrawOp::Target* target,
601 : GrDrawOpAtlas* atlas,
602 : intptr_t offset,
603 : GrColor color,
604 : size_t vertexStride,
605 : SkScalar maxScale,
606 : const SkVector& preTranslate,
607 : const ShapeData* shapeData) const {
608 0 : SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
609 :
610 0 : SkRect bounds = shapeData->fBounds;
611 0 : if (fUsesDistanceField) {
612 : // outset bounds to include ~1 pixel of AA in device space
613 0 : SkScalar outset = SkScalarInvert(maxScale);
614 0 : bounds.outset(outset, outset);
615 : }
616 :
617 : // vertex positions
618 : // TODO make the vertex attributes a struct
619 0 : positions->setRectFan(bounds.left() + preTranslate.fX,
620 0 : bounds.top() + preTranslate.fY,
621 0 : bounds.right() + preTranslate.fX,
622 0 : bounds.bottom() + preTranslate.fY,
623 0 : vertexStride);
624 :
625 : // colors
626 0 : for (int i = 0; i < kVerticesPerQuad; i++) {
627 0 : GrColor* colorPtr = (GrColor*)(offset + sizeof(SkPoint) + i * vertexStride);
628 0 : *colorPtr = color;
629 : }
630 :
631 : // set up texture coordinates
632 0 : SkScalar texLeft = bounds.fLeft;
633 0 : SkScalar texTop = bounds.fTop;
634 0 : SkScalar texRight = bounds.fRight;
635 0 : SkScalar texBottom = bounds.fBottom;
636 :
637 : // transform original path's bounds to texture space
638 0 : SkScalar scale = shapeData->fScale;
639 0 : const SkVector& translate = shapeData->fTranslate;
640 0 : texLeft *= scale;
641 0 : texTop *= scale;
642 0 : texRight *= scale;
643 0 : texBottom *= scale;
644 0 : texLeft += translate.fX;
645 0 : texTop += translate.fY;
646 0 : texRight += translate.fX;
647 0 : texBottom += translate.fY;
648 :
649 : // convert texcoords to unsigned short format
650 0 : sk_sp<GrTextureProxy> proxy = atlas->getProxy();
651 :
652 : // The proxy must be functionally exact for this normalization to work correctly
653 0 : SkASSERT(GrResourceProvider::IsFunctionallyExact(proxy.get()));
654 0 : SkScalar uFactor = 65535.f / proxy->width();
655 0 : SkScalar vFactor = 65535.f / proxy->height();
656 0 : uint16_t l = (uint16_t)(texLeft*uFactor);
657 0 : uint16_t t = (uint16_t)(texTop*vFactor);
658 0 : uint16_t r = (uint16_t)(texRight*uFactor);
659 0 : uint16_t b = (uint16_t)(texBottom*vFactor);
660 :
661 : // set vertex texture coords
662 0 : intptr_t textureCoordOffset = offset + sizeof(SkPoint) + sizeof(GrColor);
663 0 : uint16_t* textureCoords = (uint16_t*) textureCoordOffset;
664 0 : textureCoords[0] = l;
665 0 : textureCoords[1] = t;
666 0 : textureCoordOffset += vertexStride;
667 0 : textureCoords = (uint16_t*)textureCoordOffset;
668 0 : textureCoords[0] = l;
669 0 : textureCoords[1] = b;
670 0 : textureCoordOffset += vertexStride;
671 0 : textureCoords = (uint16_t*)textureCoordOffset;
672 0 : textureCoords[0] = r;
673 0 : textureCoords[1] = b;
674 0 : textureCoordOffset += vertexStride;
675 0 : textureCoords = (uint16_t*)textureCoordOffset;
676 0 : textureCoords[0] = r;
677 0 : textureCoords[1] = t;
678 0 : }
679 :
680 0 : void flush(GrLegacyMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
681 0 : if (flushInfo->fInstancesToFlush) {
682 0 : GrMesh mesh;
683 : int maxInstancesPerDraw =
684 0 : static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
685 0 : mesh.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer.get(),
686 : flushInfo->fIndexBuffer.get(), flushInfo->fVertexOffset, kVerticesPerQuad,
687 0 : kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw);
688 0 : target->draw(flushInfo->fGeometryProcessor.get(), this->pipeline(), mesh);
689 0 : flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
690 0 : flushInfo->fInstancesToFlush = 0;
691 : }
692 0 : }
693 :
694 0 : GrColor color() const { return fShapes[0].fColor; }
695 0 : const SkMatrix& viewMatrix() const { return fViewMatrix; }
696 0 : bool usesLocalCoords() const { return fUsesLocalCoords; }
697 0 : bool usesDistanceField() const { return fUsesDistanceField; }
698 :
699 0 : bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
700 0 : SmallPathOp* that = t->cast<SmallPathOp>();
701 0 : if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
702 : that->bounds(), caps)) {
703 0 : return false;
704 : }
705 :
706 0 : if (this->usesDistanceField() != that->usesDistanceField()) {
707 0 : return false;
708 : }
709 :
710 : // TODO We can position on the cpu for distance field paths
711 0 : if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
712 0 : return false;
713 : }
714 :
715 0 : if (!this->usesDistanceField() && this->usesLocalCoords() &&
716 0 : !this->fShapes[0].fTranslate.equalsWithinTolerance(that->fShapes[0].fTranslate)) {
717 0 : return false;
718 : }
719 :
720 0 : fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
721 0 : this->joinBounds(*that);
722 0 : return true;
723 : }
724 :
725 : SkMatrix fViewMatrix;
726 : bool fUsesLocalCoords;
727 : bool fUsesDistanceField;
728 :
729 0 : struct Entry {
730 : GrColor fColor;
731 : GrShape fShape;
732 : SkVector fTranslate;
733 : };
734 :
735 : SkSTArray<1, Entry> fShapes;
736 : GrDrawOpAtlas* fAtlas;
737 : ShapeCache* fShapeCache;
738 : ShapeDataList* fShapeList;
739 : bool fGammaCorrect;
740 :
741 : typedef GrLegacyMeshDrawOp INHERITED;
742 : };
743 :
744 0 : bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) {
745 0 : GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
746 : "GrSmallPathRenderer::onDrawPath");
747 :
748 : // we've already bailed on inverse filled paths, so this is safe
749 0 : SkASSERT(!args.fShape->isEmpty());
750 0 : SkASSERT(args.fShape->hasUnstyledKey());
751 0 : if (!fAtlas) {
752 0 : fAtlas = GrDrawOpAtlas::Make(args.fContext,
753 : kAlpha_8_GrPixelConfig,
754 : ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
755 : NUM_PLOTS_X, NUM_PLOTS_Y,
756 : &GrSmallPathRenderer::HandleEviction,
757 0 : (void*)this);
758 0 : if (!fAtlas) {
759 0 : return false;
760 : }
761 : }
762 :
763 : std::unique_ptr<GrLegacyMeshDrawOp> op =
764 0 : SmallPathOp::Make(args.fPaint.getColor(), *args.fShape, *args.fViewMatrix, fAtlas.get(),
765 0 : &fShapeCache, &fShapeList, args.fGammaCorrect);
766 0 : GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
767 0 : pipelineBuilder.setUserStencil(args.fUserStencilSettings);
768 :
769 0 : args.fRenderTargetContext->addLegacyMeshDrawOp(std::move(pipelineBuilder), *args.fClip,
770 0 : std::move(op));
771 :
772 0 : return true;
773 : }
774 :
775 : ///////////////////////////////////////////////////////////////////////////////////////////////////
776 :
777 : #if GR_TEST_UTILS
778 :
779 : struct PathTestStruct {
780 : typedef GrSmallPathRenderer::ShapeCache ShapeCache;
781 : typedef GrSmallPathRenderer::ShapeData ShapeData;
782 : typedef GrSmallPathRenderer::ShapeDataList ShapeDataList;
783 0 : PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {}
784 0 : ~PathTestStruct() { this->reset(); }
785 :
786 0 : void reset() {
787 0 : ShapeDataList::Iter iter;
788 0 : iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
789 : ShapeData* shapeData;
790 0 : while ((shapeData = iter.get())) {
791 0 : iter.next();
792 0 : fShapeList.remove(shapeData);
793 0 : delete shapeData;
794 : }
795 0 : fAtlas = nullptr;
796 0 : fShapeCache.reset();
797 0 : }
798 :
799 0 : static void HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
800 0 : PathTestStruct* dfpr = (PathTestStruct*)pr;
801 : // remove any paths that use this plot
802 0 : ShapeDataList::Iter iter;
803 0 : iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
804 : ShapeData* shapeData;
805 0 : while ((shapeData = iter.get())) {
806 0 : iter.next();
807 0 : if (id == shapeData->fID) {
808 0 : dfpr->fShapeCache.remove(shapeData->fKey);
809 0 : dfpr->fShapeList.remove(shapeData);
810 0 : delete shapeData;
811 : }
812 : }
813 0 : }
814 :
815 : uint32_t fContextID;
816 : std::unique_ptr<GrDrawOpAtlas> fAtlas;
817 : ShapeCache fShapeCache;
818 : ShapeDataList fShapeList;
819 : };
820 :
821 0 : DRAW_OP_TEST_DEFINE(SmallPathOp) {
822 0 : static PathTestStruct gTestStruct;
823 :
824 0 : if (context->uniqueID() != gTestStruct.fContextID) {
825 0 : gTestStruct.fContextID = context->uniqueID();
826 0 : gTestStruct.reset();
827 0 : gTestStruct.fAtlas = GrDrawOpAtlas::Make(context, kAlpha_8_GrPixelConfig,
828 : ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
829 : NUM_PLOTS_X, NUM_PLOTS_Y,
830 : &PathTestStruct::HandleEviction,
831 0 : (void*)&gTestStruct);
832 : }
833 :
834 0 : SkMatrix viewMatrix = GrTest::TestMatrix(random);
835 0 : GrColor color = GrRandomColor(random);
836 0 : bool gammaCorrect = random->nextBool();
837 :
838 : // This path renderer only allows fill styles.
839 0 : GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill());
840 :
841 : return SmallPathOp::Make(color, shape, viewMatrix, gTestStruct.fAtlas.get(),
842 0 : &gTestStruct.fShapeCache, &gTestStruct.fShapeList, gammaCorrect);
843 : }
844 :
845 : #endif
|