3 * Copyright 2014 Google Inc.
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
9 #include "GrAADistanceFieldPathRenderer.h"
12 #include "GrBatchTarget.h"
13 #include "GrBufferAllocPool.h"
14 #include "GrContext.h"
15 #include "GrPipelineBuilder.h"
16 #include "GrSurfacePriv.h"
17 #include "GrSWMaskHelper.h"
18 #include "GrTexturePriv.h"
19 #include "effects/GrDistanceFieldTextureEffect.h"
21 #include "SkDistanceFieldGen.h"
24 #define ATLAS_TEXTURE_WIDTH 1024
25 #define ATLAS_TEXTURE_HEIGHT 2048
26 #define PLOT_WIDTH 256
27 #define PLOT_HEIGHT 256
29 #define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
30 #define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
32 #ifdef DF_PATH_TRACKING
33 static int g_NumCachedPaths = 0;
34 static int g_NumFreedPaths = 0;
38 static const int kSmallMIP = 32;
39 static const int kMediumMIP = 78;
40 static const int kLargeMIP = 192;
42 // Callback to clear out internal path cache when eviction occurs
43 void GrAADistanceFieldPathRenderer::HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
44 GrAADistanceFieldPathRenderer* dfpr = (GrAADistanceFieldPathRenderer*)pr;
45 // remove any paths that use this plot
46 PathDataList::Iter iter;
47 iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
49 while ((pathData = iter.get())) {
51 if (id == pathData->fID) {
52 dfpr->fPathCache.remove(pathData->fKey);
53 dfpr->fPathList.remove(pathData);
55 #ifdef DF_PATH_TRACKING
62 ////////////////////////////////////////////////////////////////////////////////
63 GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
68 GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
69 PathDataList::Iter iter;
70 iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
72 while ((pathData = iter.get())) {
74 fPathList.remove(pathData);
79 #ifdef DF_PATH_TRACKING
80 SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
84 ////////////////////////////////////////////////////////////////////////////////
85 bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
86 const GrPipelineBuilder* pipelineBuilder,
87 const SkMatrix& viewMatrix,
89 const SkStrokeRec& stroke,
90 bool antiAlias) const {
92 // TODO: Support inverse fill
93 // TODO: Support strokes
94 if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType()
95 || path.isVolatile() || SkStrokeRec::kFill_Style != stroke.getStyle()) {
99 // currently don't support perspective
100 if (viewMatrix.hasPerspective()) {
104 // only support paths smaller than 64x64, scaled to less than 256x256
105 // the goal is to accelerate rendering of lots of small paths that may be scaling
106 SkScalar maxScale = viewMatrix.getMaxScale();
107 const SkRect& bounds = path.getBounds();
108 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
109 return maxDim < 64.f && maxDim * maxScale < 256.f;
113 GrPathRenderer::StencilSupport
114 GrAADistanceFieldPathRenderer::onGetStencilSupport(const GrDrawTarget*,
115 const GrPipelineBuilder*,
117 const SkStrokeRec&) const {
118 return GrPathRenderer::kNoSupport_StencilSupport;
121 ////////////////////////////////////////////////////////////////////////////////
123 // padding around path bounds to allow for antialiased pixels
124 static const SkScalar kAntiAliasPad = 1.0f;
126 class AADistanceFieldPathBatch : public GrBatch {
128 typedef GrAADistanceFieldPathRenderer::PathData PathData;
129 typedef SkTDynamicHash<PathData, PathData::Key> PathCache;
130 typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
133 Geometry(const SkStrokeRec& stroke) : fStroke(stroke) {}
140 static GrBatch* Create(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
141 GrBatchAtlas* atlas, PathCache* pathCache, PathDataList* pathList) {
142 return SkNEW_ARGS(AADistanceFieldPathBatch, (geometry, color, viewMatrix,
143 atlas, pathCache, pathList));
146 const char* name() const SK_OVERRIDE { return "AADistanceFieldPathBatch"; }
148 void getInvariantOutputColor(GrInitInvariantOutput* out) const SK_OVERRIDE {
149 out->setKnownFourComponents(fBatch.fColor);
152 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const SK_OVERRIDE {
153 out->setUnknownSingleComponent();
156 void initBatchTracker(const GrPipelineInfo& init) SK_OVERRIDE {
157 // Handle any color overrides
158 if (init.fColorIgnored) {
159 fBatch.fColor = GrColor_ILLEGAL;
160 } else if (GrColor_ILLEGAL != init.fOverrideColor) {
161 fBatch.fColor = init.fOverrideColor;
164 // setup batch properties
165 fBatch.fColorIgnored = init.fColorIgnored;
166 fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
167 fBatch.fCoverageIgnored = init.fCoverageIgnored;
170 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) SK_OVERRIDE {
171 int instanceCount = fGeoData.count();
174 if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
175 SkDebugf("Could not invert viewmatrix\n");
180 flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
182 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
184 // Setup GrGeometryProcessor
185 GrBatchAtlas* atlas = fAtlas;
186 SkAutoTUnref<GrGeometryProcessor> dfProcessor(
187 GrDistanceFieldNoGammaTextureEffect::Create(this->color(),
194 this->initDraw(batchTarget, dfProcessor, pipeline);
197 size_t vertexStride = dfProcessor->getVertexStride();
198 SkASSERT(vertexStride == 2 * sizeof(SkPoint));
200 int vertexCount = GrBatchTarget::kVertsPerRect * instanceCount;
202 const GrVertexBuffer* vertexBuffer;
205 void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
211 SkDebugf("Could not allocate vertices\n");
215 // We may have to flush while uploading path data to the atlas, so we set up the draw here
216 const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
217 int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
219 GrDrawTarget::DrawInfo drawInfo;
220 drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
221 drawInfo.setStartVertex(0);
222 drawInfo.setStartIndex(0);
223 drawInfo.setVerticesPerInstance(GrBatchTarget::kVertsPerRect);
224 drawInfo.setIndicesPerInstance(GrBatchTarget::kIndicesPerRect);
225 drawInfo.adjustStartVertex(firstVertex);
226 drawInfo.setVertexBuffer(vertexBuffer);
227 drawInfo.setIndexBuffer(quadIndexBuffer);
229 int instancesToFlush = 0;
230 for (int i = 0; i < instanceCount; i++) {
231 Geometry& args = fGeoData[i];
234 SkScalar maxScale = this->viewMatrix().getMaxScale();
235 const SkRect& bounds = args.fPath.getBounds();
236 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
237 SkScalar size = maxScale * maxDim;
238 uint32_t desiredDimension;
239 if (size <= kSmallMIP) {
240 desiredDimension = kSmallMIP;
241 } else if (size <= kMediumMIP) {
242 desiredDimension = kMediumMIP;
244 desiredDimension = kLargeMIP;
247 // check to see if path is cached
248 // TODO: handle stroked vs. filled version of same path
249 PathData::Key key = { args.fPath.getGenerationID(), desiredDimension };
250 args.fPathData = fPathCache->find(key);
251 if (NULL == args.fPathData || !atlas->hasID(args.fPathData->fID)) {
252 // Remove the stale cache entry
253 if (args.fPathData) {
254 fPathCache->remove(args.fPathData->fKey);
255 fPathList->remove(args.fPathData);
256 SkDELETE(args.fPathData);
258 SkScalar scale = desiredDimension/maxDim;
259 args.fPathData = SkNEW(PathData);
260 if (!this->addPathToAtlas(batchTarget,
273 SkDebugf("Can't rasterize path\n");
278 atlas->setLastRefToken(args.fPathData->fID, batchTarget->currentToken());
281 intptr_t offset = reinterpret_cast<intptr_t>(vertices);
282 offset += i * GrBatchTarget::kVertsPerRect * vertexStride;
283 SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
284 this->drawPath(batchTarget,
296 this->flush(batchTarget, dfProcessor, pipeline, &drawInfo, instancesToFlush,
297 maxInstancesPerDraw);
300 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
303 AADistanceFieldPathBatch(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
305 PathCache* pathCache, PathDataList* pathList) {
306 this->initClassID<AADistanceFieldPathBatch>();
307 fBatch.fColor = color;
308 fBatch.fViewMatrix = viewMatrix;
309 fGeoData.push_back(geometry);
310 fGeoData.back().fPathData = NULL;
313 fPathCache = pathCache;
314 fPathList = pathList;
317 bool addPathToAtlas(GrBatchTarget* batchTarget,
318 const GrGeometryProcessor* dfProcessor,
319 const GrPipeline* pipeline,
320 GrDrawTarget::DrawInfo* drawInfo,
321 int* instancesToFlush,
322 int maxInstancesPerDraw,
327 stroke, bool antiAlias,
330 const SkRect& bounds = path.getBounds();
332 // generate bounding rect for bitmap draw
333 SkRect scaledBounds = bounds;
334 // scale to mip level size
335 scaledBounds.fLeft *= scale;
336 scaledBounds.fTop *= scale;
337 scaledBounds.fRight *= scale;
338 scaledBounds.fBottom *= scale;
339 // move the origin to an integer boundary (gives better results)
340 SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
341 SkScalar dy = SkScalarFraction(scaledBounds.fTop);
342 scaledBounds.offset(-dx, -dy);
343 // get integer boundary
344 SkIRect devPathBounds;
345 scaledBounds.roundOut(&devPathBounds);
346 // pad to allow room for antialiasing
347 devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
348 // move origin to upper left corner
349 devPathBounds.offsetTo(0,0);
351 // draw path to bitmap
353 drawMatrix.setTranslate(-bounds.left(), -bounds.top());
354 drawMatrix.postScale(scale, scale);
355 drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
357 // setup bitmap backing
358 // Now translate so the bound's UL corner is at the origin
359 drawMatrix.postTranslate(-devPathBounds.fLeft * SK_Scalar1,
360 -devPathBounds.fTop * SK_Scalar1);
361 SkIRect pathBounds = SkIRect::MakeWH(devPathBounds.width(),
362 devPathBounds.height());
365 const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pathBounds.fRight,
367 if (!bmp.tryAllocPixels(bmImageInfo)) {
371 sk_bzero(bmp.getPixels(), bmp.getSafeSize());
375 if (stroke.isHairlineStyle()) {
376 paint.setStyle(SkPaint::kStroke_Style);
377 paint.setStrokeWidth(SK_Scalar1);
379 if (stroke.isFillStyle()) {
380 paint.setStyle(SkPaint::kFill_Style);
382 paint.setStyle(SkPaint::kStroke_Style);
383 paint.setStrokeJoin(stroke.getJoin());
384 paint.setStrokeCap(stroke.getCap());
385 paint.setStrokeWidth(stroke.getWidth());
388 paint.setAntiAlias(antiAlias);
391 sk_bzero(&draw, sizeof(draw));
393 SkRasterClip rasterClip;
394 rasterClip.setRect(pathBounds);
395 draw.fRC = &rasterClip;
396 draw.fClip = &rasterClip.bwRgn();
397 draw.fMatrix = &drawMatrix;
400 draw.drawPathCoverage(path, paint);
402 // generate signed distance field
403 devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
404 int width = devPathBounds.width();
405 int height = devPathBounds.height();
406 // TODO We should really generate this directly into the plot somehow
407 SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
409 // Generate signed distance field
411 SkAutoLockPixels alp(bmp);
413 SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
414 (const unsigned char*)bmp.getPixels(),
415 bmp.width(), bmp.height(), bmp.rowBytes());
419 SkIPoint16 atlasLocation;
420 GrBatchAtlas::AtlasID id;
421 bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(),
424 this->flush(batchTarget, dfProcessor, pipeline, drawInfo, *instancesToFlush,
425 maxInstancesPerDraw);
426 this->initDraw(batchTarget, dfProcessor, pipeline);
427 *instancesToFlush = 0;
429 SkDEBUGCODE(success =) atlas->addToAtlas(&id, batchTarget, width, height,
430 dfStorage.get(), &atlasLocation);
436 pathData->fKey.fGenID = path.getGenerationID();
437 pathData->fKey.fDimension = dimension;
438 pathData->fScale = scale;
440 // change the scaled rect to match the size of the inset distance field
441 scaledBounds.fRight = scaledBounds.fLeft +
442 SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
443 scaledBounds.fBottom = scaledBounds.fTop +
444 SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
445 // shift the origin to the correct place relative to the distance field
446 // need to also restore the fractional translation
447 scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
448 -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
449 pathData->fBounds = scaledBounds;
450 // origin we render from is inset from distance field edge
451 atlasLocation.fX += SK_DistanceFieldInset;
452 atlasLocation.fY += SK_DistanceFieldInset;
453 pathData->fAtlasLocation = atlasLocation;
455 fPathCache->add(pathData);
456 fPathList->addToTail(pathData);
457 #ifdef DF_PATH_TRACKING
463 void drawPath(GrBatchTarget* target,
465 const GrPipeline* pipeline,
466 const GrGeometryProcessor* gp,
469 const SkMatrix& viewMatrix,
471 const PathData* pathData) {
472 GrTexture* texture = atlas->getTexture();
474 SkScalar dx = pathData->fBounds.fLeft;
475 SkScalar dy = pathData->fBounds.fTop;
476 SkScalar width = pathData->fBounds.width();
477 SkScalar height = pathData->fBounds.height();
479 SkScalar invScale = 1.0f / pathData->fScale;
485 SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
486 SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
487 SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
488 SkFixed th = SkScalarToFixed(pathData->fBounds.height());
491 // TODO make the vertex attributes a struct
492 SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
493 positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride);
495 // vertex texture coords
496 SkPoint* textureCoords = positions + 1;
497 textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
498 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
499 SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
500 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
504 void initDraw(GrBatchTarget* batchTarget,
505 const GrGeometryProcessor* dfProcessor,
506 const GrPipeline* pipeline) {
507 batchTarget->initDraw(dfProcessor, pipeline);
509 // TODO remove this when batch is everywhere
511 init.fColorIgnored = fBatch.fColorIgnored;
512 init.fOverrideColor = GrColor_ILLEGAL;
513 init.fCoverageIgnored = fBatch.fCoverageIgnored;
514 init.fUsesLocalCoords = this->usesLocalCoords();
515 dfProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init);
518 void flush(GrBatchTarget* batchTarget,
519 const GrGeometryProcessor* dfProcessor,
520 const GrPipeline* pipeline,
521 GrDrawTarget::DrawInfo* drawInfo,
523 int maxInstancesPerDraw) {
524 while (instanceCount) {
525 drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
526 drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance());
527 drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance());
529 batchTarget->draw(*drawInfo);
531 drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount());
532 instanceCount -= drawInfo->instanceCount();
536 GrColor color() const { return fBatch.fColor; }
537 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
538 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
540 bool onCombineIfPossible(GrBatch* t) SK_OVERRIDE {
541 AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>();
543 // TODO we could actually probably do a bunch of this work on the CPU, ie map viewMatrix,
544 // maybe upload color via attribute
545 if (this->color() != that->color()) {
549 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
553 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
557 struct BatchTracker {
559 SkMatrix fViewMatrix;
560 bool fUsesLocalCoords;
562 bool fCoverageIgnored;
566 SkSTArray<1, Geometry, true> fGeoData;
567 GrBatchAtlas* fAtlas;
568 PathCache* fPathCache;
569 PathDataList* fPathList;
572 bool GrAADistanceFieldPathRenderer::onDrawPath(GrDrawTarget* target,
573 GrPipelineBuilder* pipelineBuilder,
575 const SkMatrix& viewMatrix,
577 const SkStrokeRec& stroke,
579 // we've already bailed on inverse filled paths, so this is safe
580 if (path.isEmpty()) {
587 // Create a new atlas
589 desc.fFlags = kNone_GrSurfaceFlags;
590 desc.fWidth = ATLAS_TEXTURE_WIDTH;
591 desc.fHeight = ATLAS_TEXTURE_HEIGHT;
592 desc.fConfig = kAlpha_8_GrPixelConfig;
594 // We don't want to flush the context so we claim we're in the middle of flushing so as to
595 // guarantee we do not recieve a texture with pending IO
596 GrTexture* texture = fContext->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch,
599 fAtlas = SkNEW_ARGS(GrBatchAtlas, (texture, NUM_PLOTS_X, NUM_PLOTS_Y));
603 fAtlas->registerEvictionCallback(&GrAADistanceFieldPathRenderer::HandleEviction,
607 AADistanceFieldPathBatch::Geometry geometry(stroke);
608 geometry.fPath = path;
609 geometry.fAntiAlias = antiAlias;
611 SkAutoTUnref<GrBatch> batch(AADistanceFieldPathBatch::Create(geometry, color, viewMatrix,
612 fAtlas, &fPathCache, &fPathList));
614 SkRect bounds = path.getBounds();
615 viewMatrix.mapRect(&bounds);
616 target->drawBatch(pipelineBuilder, batch, &bounds);