blob: d50af9fbd85829e0b2c9235301f96da65cf7d036 [file] [log] [blame]
jvanverthfa38a302014-10-06 05:59:05 -07001
2/*
3 * Copyright 2014 Google Inc.
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 "GrAADistanceFieldPathRenderer.h"
10
11#include "GrAtlas.h"
12#include "GrContext.h"
egdaniel8dd688b2015-01-22 10:16:09 -080013#include "GrPipelineBuilder.h"
jvanverthfa38a302014-10-06 05:59:05 -070014#include "GrSurfacePriv.h"
15#include "GrSWMaskHelper.h"
16#include "GrTexturePriv.h"
17#include "effects/GrDistanceFieldTextureEffect.h"
18
19#include "SkDistanceFieldGen.h"
20#include "SkRTConf.h"
21
reede4ef1ca2015-02-17 18:38:38 -080022#define ATLAS_TEXTURE_WIDTH 1024
jvanverthb61283f2014-10-30 05:57:21 -070023#define ATLAS_TEXTURE_HEIGHT 2048
reede4ef1ca2015-02-17 18:38:38 -080024#define PLOT_WIDTH 256
25#define PLOT_HEIGHT 256
jvanverthfa38a302014-10-06 05:59:05 -070026
27#define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
28#define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
29
30SK_CONF_DECLARE(bool, c_DumpPathCache, "gpu.dumpPathCache", false,
31 "Dump the contents of the path cache before every purge.");
32
jvanverthb3eb6872014-10-24 07:12:51 -070033#ifdef DF_PATH_TRACKING
34static int g_NumCachedPaths = 0;
35static int g_NumFreedPaths = 0;
36#endif
37
jvanverthb61283f2014-10-30 05:57:21 -070038// mip levels
39static const int kSmallMIP = 32;
jvanverthada68ef2014-11-03 14:00:24 -080040static const int kMediumMIP = 78;
41static const int kLargeMIP = 192;
jvanverthb61283f2014-10-30 05:57:21 -070042
jvanverthfa38a302014-10-06 05:59:05 -070043////////////////////////////////////////////////////////////////////////////////
jvanverth6d22eca2014-10-28 11:10:48 -070044GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
45 : fContext(context)
46 , fAtlas(NULL)
47 , fEffectFlags(kInvalid_DistanceFieldEffectFlag) {
48}
49
jvanverthfa38a302014-10-06 05:59:05 -070050GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
51 PathDataList::Iter iter;
52 iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
53 PathData* pathData;
54 while ((pathData = iter.get())) {
55 iter.next();
56 fPathList.remove(pathData);
57 SkDELETE(pathData);
58 }
59
60 SkDELETE(fAtlas);
jvanverthb3eb6872014-10-24 07:12:51 -070061
62#ifdef DF_PATH_TRACKING
63 SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
64#endif
jvanverthfa38a302014-10-06 05:59:05 -070065}
66
67////////////////////////////////////////////////////////////////////////////////
joshualitt9853cce2014-11-17 14:22:48 -080068bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
egdaniel8dd688b2015-01-22 10:16:09 -080069 const GrPipelineBuilder* pipelineBuilder,
joshualitt8059eb92014-12-29 15:10:07 -080070 const SkMatrix& viewMatrix,
joshualitt9853cce2014-11-17 14:22:48 -080071 const SkPath& path,
jvanverthfa38a302014-10-06 05:59:05 -070072 const SkStrokeRec& stroke,
jvanverthfa38a302014-10-06 05:59:05 -070073 bool antiAlias) const {
jvanverthb3eb6872014-10-24 07:12:51 -070074
jvanverthfa38a302014-10-06 05:59:05 -070075 // TODO: Support inverse fill
76 // TODO: Support strokes
77 if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType()
jvanverthb3eb6872014-10-24 07:12:51 -070078 || path.isVolatile() || SkStrokeRec::kFill_Style != stroke.getStyle()) {
jvanverthfa38a302014-10-06 05:59:05 -070079 return false;
80 }
81
jvanverthb61283f2014-10-30 05:57:21 -070082 // currently don't support perspective
joshualitt8059eb92014-12-29 15:10:07 -080083 if (viewMatrix.hasPerspective()) {
jvanverthfa38a302014-10-06 05:59:05 -070084 return false;
85 }
86
jvanverthb61283f2014-10-30 05:57:21 -070087 // only support paths smaller than 64x64, scaled to less than 256x256
88 // the goal is to accelerate rendering of lots of small paths that may be scaling
joshualitt8059eb92014-12-29 15:10:07 -080089 SkScalar maxScale = viewMatrix.getMaxScale();
jvanverthfa38a302014-10-06 05:59:05 -070090 const SkRect& bounds = path.getBounds();
jvanverthb61283f2014-10-30 05:57:21 -070091 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
92 return maxDim < 64.f && maxDim*maxScale < 256.f;
jvanverthfa38a302014-10-06 05:59:05 -070093}
94
95
joshualitt9853cce2014-11-17 14:22:48 -080096GrPathRenderer::StencilSupport
97GrAADistanceFieldPathRenderer::onGetStencilSupport(const GrDrawTarget*,
egdaniel8dd688b2015-01-22 10:16:09 -080098 const GrPipelineBuilder*,
joshualitt9853cce2014-11-17 14:22:48 -080099 const SkPath&,
100 const SkStrokeRec&) const {
jvanverthfa38a302014-10-06 05:59:05 -0700101 return GrPathRenderer::kNoSupport_StencilSupport;
102}
103
104////////////////////////////////////////////////////////////////////////////////
105
joshualitt9853cce2014-11-17 14:22:48 -0800106bool GrAADistanceFieldPathRenderer::onDrawPath(GrDrawTarget* target,
egdaniel8dd688b2015-01-22 10:16:09 -0800107 GrPipelineBuilder* pipelineBuilder,
joshualitt2e3b3e32014-12-09 13:31:14 -0800108 GrColor color,
joshualitt8059eb92014-12-29 15:10:07 -0800109 const SkMatrix& viewMatrix,
joshualitt9853cce2014-11-17 14:22:48 -0800110 const SkPath& path,
jvanverthfa38a302014-10-06 05:59:05 -0700111 const SkStrokeRec& stroke,
jvanverthfa38a302014-10-06 05:59:05 -0700112 bool antiAlias) {
113 // we've already bailed on inverse filled paths, so this is safe
114 if (path.isEmpty()) {
115 return true;
116 }
117
118 SkASSERT(fContext);
119
jvanverthb61283f2014-10-30 05:57:21 -0700120 // get mip level
joshualitt8059eb92014-12-29 15:10:07 -0800121 SkScalar maxScale = viewMatrix.getMaxScale();
jvanverthb61283f2014-10-30 05:57:21 -0700122 const SkRect& bounds = path.getBounds();
123 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
124 SkScalar size = maxScale*maxDim;
125 uint32_t desiredDimension;
126 if (size <= kSmallMIP) {
127 desiredDimension = kSmallMIP;
128 } else if (size <= kMediumMIP) {
129 desiredDimension = kMediumMIP;
130 } else {
131 desiredDimension = kLargeMIP;
132 }
133
jvanverthfa38a302014-10-06 05:59:05 -0700134 // check to see if path is cached
135 // TODO: handle stroked vs. filled version of same path
jvanverthb61283f2014-10-30 05:57:21 -0700136 PathData::Key key = { path.getGenerationID(), desiredDimension };
137 PathData* pathData = fPathCache.find(key);
jvanverthfa38a302014-10-06 05:59:05 -0700138 if (NULL == pathData) {
jvanverthb61283f2014-10-30 05:57:21 -0700139 SkScalar scale = desiredDimension/maxDim;
140 pathData = this->addPathToAtlas(path, stroke, antiAlias, desiredDimension, scale);
jvanverthfa38a302014-10-06 05:59:05 -0700141 if (NULL == pathData) {
142 return false;
143 }
144 }
145
146 // use signed distance field to render
egdaniel8dd688b2015-01-22 10:16:09 -0800147 return this->internalDrawPath(target, pipelineBuilder, color, viewMatrix, path, pathData);
jvanverthfa38a302014-10-06 05:59:05 -0700148}
149
jvanverthfa38a302014-10-06 05:59:05 -0700150// padding around path bounds to allow for antialiased pixels
151const SkScalar kAntiAliasPad = 1.0f;
152
joshualitt9491f7f2015-02-11 11:33:38 -0800153inline bool GrAADistanceFieldPathRenderer::uploadPath(GrPlot** plot, SkIPoint16* atlasLocation,
154 int width, int height, void* dfStorage) {
155 *plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage, atlasLocation);
156
157 // if atlas full
158 if (NULL == *plot) {
159 if (this->freeUnusedPlot()) {
160 *plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage, atlasLocation);
161 if (*plot) {
162 return true;
163 }
164 }
165
166 if (c_DumpPathCache) {
167#ifdef SK_DEVELOPER
168 GrTexture* texture = fAtlas->getTexture();
169 texture->surfacePriv().savePixels("pathcache.png");
170#endif
171 }
172
173 // before we purge the cache, we must flush any accumulated draws
174 fContext->flush();
175
176 if (this->freeUnusedPlot()) {
177 *plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage, atlasLocation);
178 if (*plot) {
179 return true;
180 }
181 }
182 return false;
183 }
184 return true;
185}
186
jvanverthfa38a302014-10-06 05:59:05 -0700187GrAADistanceFieldPathRenderer::PathData* GrAADistanceFieldPathRenderer::addPathToAtlas(
188 const SkPath& path,
189 const SkStrokeRec& stroke,
jvanverthb61283f2014-10-30 05:57:21 -0700190 bool antiAlias,
191 uint32_t dimension,
192 SkScalar scale) {
jvanverthfa38a302014-10-06 05:59:05 -0700193
194 // generate distance field and add to atlas
195 if (NULL == fAtlas) {
196 SkISize textureSize = SkISize::Make(ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT);
197 fAtlas = SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kAlpha_8_GrPixelConfig,
bsalomonf2703d82014-10-28 14:33:06 -0700198 kNone_GrSurfaceFlags, textureSize,
jvanverthfa38a302014-10-06 05:59:05 -0700199 NUM_PLOTS_X, NUM_PLOTS_Y, false));
200 if (NULL == fAtlas) {
201 return NULL;
202 }
203 }
204
205 const SkRect& bounds = path.getBounds();
206
207 // generate bounding rect for bitmap draw
208 SkRect scaledBounds = bounds;
jvanverthb61283f2014-10-30 05:57:21 -0700209 // scale to mip level size
210 scaledBounds.fLeft *= scale;
211 scaledBounds.fTop *= scale;
212 scaledBounds.fRight *= scale;
213 scaledBounds.fBottom *= scale;
jvanverthfa38a302014-10-06 05:59:05 -0700214 // move the origin to an integer boundary (gives better results)
215 SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
216 SkScalar dy = SkScalarFraction(scaledBounds.fTop);
217 scaledBounds.offset(-dx, -dy);
218 // get integer boundary
219 SkIRect devPathBounds;
220 scaledBounds.roundOut(&devPathBounds);
221 // pad to allow room for antialiasing
222 devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
223 // move origin to upper left corner
224 devPathBounds.offsetTo(0,0);
225
226 // draw path to bitmap
227 SkMatrix drawMatrix;
228 drawMatrix.setTranslate(-bounds.left(), -bounds.top());
jvanverthb61283f2014-10-30 05:57:21 -0700229 drawMatrix.postScale(scale, scale);
jvanverthfa38a302014-10-06 05:59:05 -0700230 drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
231 GrSWMaskHelper helper(fContext);
232
233 if (!helper.init(devPathBounds, &drawMatrix)) {
234 return NULL;
235 }
236 helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
237
238 // generate signed distance field
239 devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
240 int width = devPathBounds.width();
241 int height = devPathBounds.height();
242 SkAutoSMalloc<1024> dfStorage(width*height*sizeof(unsigned char));
243 helper.toSDF((unsigned char*) dfStorage.get());
244
245 // add to atlas
joshualitt9491f7f2015-02-11 11:33:38 -0800246 GrPlot* plot;
jvanverthfa38a302014-10-06 05:59:05 -0700247 SkIPoint16 atlasLocation;
joshualitt9491f7f2015-02-11 11:33:38 -0800248 if (!this->uploadPath(&plot, &atlasLocation, width, height, dfStorage.get())) {
jvanverthfa38a302014-10-06 05:59:05 -0700249 return NULL;
250 }
joshualitt9491f7f2015-02-11 11:33:38 -0800251
jvanverthfa38a302014-10-06 05:59:05 -0700252 // add to cache
253 PathData* pathData = SkNEW(PathData);
jvanverthb61283f2014-10-30 05:57:21 -0700254 pathData->fKey.fGenID = path.getGenerationID();
255 pathData->fKey.fDimension = dimension;
256 pathData->fScale = scale;
jvanverthfa38a302014-10-06 05:59:05 -0700257 pathData->fPlot = plot;
258 // change the scaled rect to match the size of the inset distance field
259 scaledBounds.fRight = scaledBounds.fLeft +
260 SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
261 scaledBounds.fBottom = scaledBounds.fTop +
262 SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
263 // shift the origin to the correct place relative to the distance field
264 // need to also restore the fractional translation
265 scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
266 -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
267 pathData->fBounds = scaledBounds;
268 // origin we render from is inset from distance field edge
269 atlasLocation.fX += SK_DistanceFieldInset;
270 atlasLocation.fY += SK_DistanceFieldInset;
271 pathData->fAtlasLocation = atlasLocation;
272
273 fPathCache.add(pathData);
274 fPathList.addToTail(pathData);
jvanverthb3eb6872014-10-24 07:12:51 -0700275#ifdef DF_PATH_TRACKING
276 ++g_NumCachedPaths;
277#endif
278
jvanverthfa38a302014-10-06 05:59:05 -0700279 return pathData;
280}
281
282bool GrAADistanceFieldPathRenderer::freeUnusedPlot() {
283 // find an unused plot
284 GrPlot* plot = fAtlas->getUnusedPlot();
285 if (NULL == plot) {
286 return false;
287 }
288 plot->resetRects();
289
290 // remove any paths that use this plot
291 PathDataList::Iter iter;
292 iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
293 PathData* pathData;
294 while ((pathData = iter.get())) {
295 iter.next();
296 if (plot == pathData->fPlot) {
jvanverthb61283f2014-10-30 05:57:21 -0700297 fPathCache.remove(pathData->fKey);
jvanverthfa38a302014-10-06 05:59:05 -0700298 fPathList.remove(pathData);
299 SkDELETE(pathData);
jvanverthb3eb6872014-10-24 07:12:51 -0700300#ifdef DF_PATH_TRACKING
301 ++g_NumFreedPaths;
302#endif
jvanverthfa38a302014-10-06 05:59:05 -0700303 }
304 }
305
306 // tell the atlas to free the plot
307 GrAtlas::RemovePlot(&fPlotUsage, plot);
308
309 return true;
310}
311
joshualitt9853cce2014-11-17 14:22:48 -0800312bool GrAADistanceFieldPathRenderer::internalDrawPath(GrDrawTarget* target,
egdaniel8dd688b2015-01-22 10:16:09 -0800313 GrPipelineBuilder* pipelineBuilder,
joshualitt2e3b3e32014-12-09 13:31:14 -0800314 GrColor color,
joshualitt8059eb92014-12-29 15:10:07 -0800315 const SkMatrix& viewMatrix,
joshualitt9853cce2014-11-17 14:22:48 -0800316 const SkPath& path,
317 const PathData* pathData) {
jvanverthfa38a302014-10-06 05:59:05 -0700318 GrTexture* texture = fAtlas->getTexture();
bsalomon6be6f7c2015-02-26 13:05:21 -0800319 GrPipelineBuilder::AutoRestoreFragmentProcessors arfp(pipelineBuilder);
jvanverthfa38a302014-10-06 05:59:05 -0700320
321 SkASSERT(pathData->fPlot);
322 GrDrawTarget::DrawToken drawToken = target->getCurrentDrawToken();
323 pathData->fPlot->setDrawToken(drawToken);
324
joshualitt2dd1ae02014-12-03 06:24:10 -0800325 // set up any flags
326 uint32_t flags = 0;
joshualitt8059eb92014-12-29 15:10:07 -0800327 flags |= viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
joshualitt2dd1ae02014-12-03 06:24:10 -0800328
329 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
joshualitt8059eb92014-12-29 15:10:07 -0800330 if (flags != fEffectFlags || fCachedGeometryProcessor->color() != color ||
331 !fCachedGeometryProcessor->viewMatrix().cheapEqualTo(viewMatrix)) {
joshualitt2e3b3e32014-12-09 13:31:14 -0800332 fCachedGeometryProcessor.reset(GrDistanceFieldNoGammaTextureEffect::Create(color,
joshualitt8059eb92014-12-29 15:10:07 -0800333 viewMatrix,
joshualitt2e3b3e32014-12-09 13:31:14 -0800334 texture,
joshualitt2dd1ae02014-12-03 06:24:10 -0800335 params,
joshualitt56995b52014-12-11 15:44:02 -0800336 flags,
337 false));
joshualitt2dd1ae02014-12-03 06:24:10 -0800338 fEffectFlags = flags;
339 }
joshualitt2dd1ae02014-12-03 06:24:10 -0800340
jvanverthfa38a302014-10-06 05:59:05 -0700341 void* vertices = NULL;
joshualitt2dd1ae02014-12-03 06:24:10 -0800342 bool success = target->reserveVertexAndIndexSpace(4,
343 fCachedGeometryProcessor->getVertexStride(),
344 0, &vertices, NULL);
reede4ef1ca2015-02-17 18:38:38 -0800345 SkASSERT(fCachedGeometryProcessor->getVertexStride() == 2 * sizeof(SkPoint));
jvanverthfa38a302014-10-06 05:59:05 -0700346 GrAlwaysAssert(success);
347
348 SkScalar dx = pathData->fBounds.fLeft;
349 SkScalar dy = pathData->fBounds.fTop;
reede4ef1ca2015-02-17 18:38:38 -0800350 SkScalar width = pathData->fBounds.width();
351 SkScalar height = pathData->fBounds.height();
jvanverthfa38a302014-10-06 05:59:05 -0700352
jvanverthb61283f2014-10-30 05:57:21 -0700353 SkScalar invScale = 1.0f/pathData->fScale;
jvanverthfa38a302014-10-06 05:59:05 -0700354 dx *= invScale;
355 dy *= invScale;
356 width *= invScale;
357 height *= invScale;
reede4ef1ca2015-02-17 18:38:38 -0800358
359 SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
360 SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
361 SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
362 SkFixed th = SkScalarToFixed(pathData->fBounds.height());
363
jvanverthfa38a302014-10-06 05:59:05 -0700364 // vertex positions
365 SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
reede4ef1ca2015-02-17 18:38:38 -0800366 size_t vertSize = 2 * sizeof(SkPoint);
jvanverthfa38a302014-10-06 05:59:05 -0700367 SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
368 positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertSize);
369
370 // vertex texture coords
reede4ef1ca2015-02-17 18:38:38 -0800371 intptr_t intPtr = reinterpret_cast<intptr_t>(positions);
372 SkPoint* textureCoords = reinterpret_cast<SkPoint*>(intPtr + vertSize - sizeof(SkPoint));
373 textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
374 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
375 SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
376 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
377 vertSize);
jvanverthfa38a302014-10-06 05:59:05 -0700378
joshualitt8059eb92014-12-29 15:10:07 -0800379 viewMatrix.mapRect(&r);
jvanverth6d22eca2014-10-28 11:10:48 -0700380 target->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
egdaniel8dd688b2015-01-22 10:16:09 -0800381 target->drawIndexedInstances(pipelineBuilder, fCachedGeometryProcessor.get(),
joshualitt56995b52014-12-11 15:44:02 -0800382 kTriangles_GrPrimitiveType, 1, 4, 6, &r);
jvanverthfa38a302014-10-06 05:59:05 -0700383 target->resetVertexSource();
jvanverthfa38a302014-10-06 05:59:05 -0700384
385 return true;
386}
387