blob: 95675e4fce6b35bc3489ce7af9db66dfec54b06d [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"
13#include "GrDrawState.h"
14#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
22#define ATLAS_TEXTURE_WIDTH 1024
23#define ATLAS_TEXTURE_HEIGHT 1024
jvanverthfa38a302014-10-06 05:59:05 -070024#define PLOT_WIDTH 256
25#define PLOT_HEIGHT 256
26
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
jvanverthfa38a302014-10-06 05:59:05 -070038////////////////////////////////////////////////////////////////////////////////
jvanverth6d22eca2014-10-28 11:10:48 -070039GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
40 : fContext(context)
41 , fAtlas(NULL)
42 , fEffectFlags(kInvalid_DistanceFieldEffectFlag) {
43}
44
jvanverthfa38a302014-10-06 05:59:05 -070045GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
46 PathDataList::Iter iter;
47 iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
48 PathData* pathData;
49 while ((pathData = iter.get())) {
50 iter.next();
51 fPathList.remove(pathData);
52 SkDELETE(pathData);
53 }
54
55 SkDELETE(fAtlas);
jvanverthb3eb6872014-10-24 07:12:51 -070056
57#ifdef DF_PATH_TRACKING
58 SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
59#endif
jvanverthfa38a302014-10-06 05:59:05 -070060}
61
62////////////////////////////////////////////////////////////////////////////////
63bool GrAADistanceFieldPathRenderer::canDrawPath(const SkPath& path,
64 const SkStrokeRec& stroke,
65 const GrDrawTarget* target,
66 bool antiAlias) const {
jvanverthb3eb6872014-10-24 07:12:51 -070067
jvanverthfa38a302014-10-06 05:59:05 -070068 // TODO: Support inverse fill
69 // TODO: Support strokes
70 if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType()
jvanverthb3eb6872014-10-24 07:12:51 -070071 || path.isVolatile() || SkStrokeRec::kFill_Style != stroke.getStyle()) {
jvanverthfa38a302014-10-06 05:59:05 -070072 return false;
73 }
74
75 // currently don't support perspective or scaling more than 3x
76 const GrDrawState& drawState = target->getDrawState();
77 const SkMatrix& vm = drawState.getViewMatrix();
78 if (vm.hasPerspective() || vm.getMaxScale() > 3.0f) {
79 return false;
80 }
81
82 // only support paths smaller than 64 x 64
83 const SkRect& bounds = path.getBounds();
84 return bounds.width() < 64.f && bounds.height() < 64.f;
85}
86
87
88GrPathRenderer::StencilSupport GrAADistanceFieldPathRenderer::onGetStencilSupport(
89 const SkPath&,
90 const SkStrokeRec&,
91 const GrDrawTarget*) const {
92 return GrPathRenderer::kNoSupport_StencilSupport;
93}
94
95////////////////////////////////////////////////////////////////////////////////
96
97// position + texture coord
98extern const GrVertexAttrib gSDFPathVertexAttribs[] = {
99 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
100 { kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding }
101};
102static const size_t kSDFPathVASize = 2 * sizeof(SkPoint);
103
104bool GrAADistanceFieldPathRenderer::onDrawPath(const SkPath& path,
105 const SkStrokeRec& stroke,
106 GrDrawTarget* target,
107 bool antiAlias) {
108 // we've already bailed on inverse filled paths, so this is safe
109 if (path.isEmpty()) {
110 return true;
111 }
112
113 SkASSERT(fContext);
114
115 // check to see if path is cached
116 // TODO: handle stroked vs. filled version of same path
117 PathData* pathData = fPathCache.find(path.getGenerationID());
118 if (NULL == pathData) {
119 pathData = this->addPathToAtlas(path, stroke, antiAlias);
120 if (NULL == pathData) {
121 return false;
122 }
123 }
124
125 // use signed distance field to render
126 return this->internalDrawPath(path, pathData, target);
127}
128
129// factor used to scale the path prior to building distance field
130const SkScalar kScaleFactor = 2.0f;
131// padding around path bounds to allow for antialiased pixels
132const SkScalar kAntiAliasPad = 1.0f;
133
134GrAADistanceFieldPathRenderer::PathData* GrAADistanceFieldPathRenderer::addPathToAtlas(
135 const SkPath& path,
136 const SkStrokeRec& stroke,
137 bool antiAlias) {
138
139 // generate distance field and add to atlas
140 if (NULL == fAtlas) {
141 SkISize textureSize = SkISize::Make(ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT);
142 fAtlas = SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kAlpha_8_GrPixelConfig,
bsalomonf2703d82014-10-28 14:33:06 -0700143 kNone_GrSurfaceFlags, textureSize,
jvanverthfa38a302014-10-06 05:59:05 -0700144 NUM_PLOTS_X, NUM_PLOTS_Y, false));
145 if (NULL == fAtlas) {
146 return NULL;
147 }
148 }
149
150 const SkRect& bounds = path.getBounds();
151
152 // generate bounding rect for bitmap draw
153 SkRect scaledBounds = bounds;
154 // scale up to improve maxification range
155 scaledBounds.fLeft *= kScaleFactor;
156 scaledBounds.fTop *= kScaleFactor;
157 scaledBounds.fRight *= kScaleFactor;
158 scaledBounds.fBottom *= kScaleFactor;
159 // move the origin to an integer boundary (gives better results)
160 SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
161 SkScalar dy = SkScalarFraction(scaledBounds.fTop);
162 scaledBounds.offset(-dx, -dy);
163 // get integer boundary
164 SkIRect devPathBounds;
165 scaledBounds.roundOut(&devPathBounds);
166 // pad to allow room for antialiasing
167 devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
168 // move origin to upper left corner
169 devPathBounds.offsetTo(0,0);
170
171 // draw path to bitmap
172 SkMatrix drawMatrix;
173 drawMatrix.setTranslate(-bounds.left(), -bounds.top());
174 drawMatrix.postScale(kScaleFactor, kScaleFactor);
175 drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
176 GrSWMaskHelper helper(fContext);
177
178 if (!helper.init(devPathBounds, &drawMatrix)) {
179 return NULL;
180 }
181 helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
182
183 // generate signed distance field
184 devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
185 int width = devPathBounds.width();
186 int height = devPathBounds.height();
187 SkAutoSMalloc<1024> dfStorage(width*height*sizeof(unsigned char));
188 helper.toSDF((unsigned char*) dfStorage.get());
189
190 // add to atlas
191 SkIPoint16 atlasLocation;
192 GrPlot* plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
193 &atlasLocation);
194
195 // if atlas full
196 if (NULL == plot) {
197 if (this->freeUnusedPlot()) {
198 plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
199 &atlasLocation);
200 if (plot) {
201 goto HAS_ATLAS;
202 }
203 }
204
205 if (c_DumpPathCache) {
206#ifdef SK_DEVELOPER
207 GrTexture* texture = fAtlas->getTexture();
208 texture->surfacePriv().savePixels("pathcache.png");
209#endif
210 }
211
212 // before we purge the cache, we must flush any accumulated draws
213 fContext->flush();
214
215 if (this->freeUnusedPlot()) {
216 plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
217 &atlasLocation);
218 if (plot) {
219 goto HAS_ATLAS;
220 }
221 }
222
223 return NULL;
224 }
225
226HAS_ATLAS:
227 // add to cache
228 PathData* pathData = SkNEW(PathData);
229 pathData->fGenID = path.getGenerationID();
230 pathData->fPlot = plot;
231 // change the scaled rect to match the size of the inset distance field
232 scaledBounds.fRight = scaledBounds.fLeft +
233 SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
234 scaledBounds.fBottom = scaledBounds.fTop +
235 SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
236 // shift the origin to the correct place relative to the distance field
237 // need to also restore the fractional translation
238 scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
239 -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
240 pathData->fBounds = scaledBounds;
241 // origin we render from is inset from distance field edge
242 atlasLocation.fX += SK_DistanceFieldInset;
243 atlasLocation.fY += SK_DistanceFieldInset;
244 pathData->fAtlasLocation = atlasLocation;
245
246 fPathCache.add(pathData);
247 fPathList.addToTail(pathData);
jvanverthb3eb6872014-10-24 07:12:51 -0700248#ifdef DF_PATH_TRACKING
249 ++g_NumCachedPaths;
250#endif
251
jvanverthfa38a302014-10-06 05:59:05 -0700252 return pathData;
253}
254
255bool GrAADistanceFieldPathRenderer::freeUnusedPlot() {
256 // find an unused plot
257 GrPlot* plot = fAtlas->getUnusedPlot();
258 if (NULL == plot) {
259 return false;
260 }
261 plot->resetRects();
262
263 // remove any paths that use this plot
264 PathDataList::Iter iter;
265 iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
266 PathData* pathData;
267 while ((pathData = iter.get())) {
268 iter.next();
269 if (plot == pathData->fPlot) {
270 fPathCache.remove(pathData->fGenID);
271 fPathList.remove(pathData);
272 SkDELETE(pathData);
jvanverthb3eb6872014-10-24 07:12:51 -0700273#ifdef DF_PATH_TRACKING
274 ++g_NumFreedPaths;
275#endif
jvanverthfa38a302014-10-06 05:59:05 -0700276 }
277 }
278
279 // tell the atlas to free the plot
280 GrAtlas::RemovePlot(&fPlotUsage, plot);
281
282 return true;
283}
284
285bool GrAADistanceFieldPathRenderer::internalDrawPath(const SkPath& path,
286 const PathData* pathData,
287 GrDrawTarget* target) {
jvanverthfa38a302014-10-06 05:59:05 -0700288 GrTexture* texture = fAtlas->getTexture();
289 GrDrawState* drawState = target->drawState();
jvanverthf19657f2014-10-07 08:04:58 -0700290 GrDrawState::AutoRestoreEffects are(drawState);
jvanverthfa38a302014-10-06 05:59:05 -0700291
292 SkASSERT(pathData->fPlot);
293 GrDrawTarget::DrawToken drawToken = target->getCurrentDrawToken();
294 pathData->fPlot->setDrawToken(drawToken);
295
296 // make me some vertices
297 drawState->setVertexAttribs<gSDFPathVertexAttribs>(SK_ARRAY_COUNT(gSDFPathVertexAttribs),
298 kSDFPathVASize);
299 void* vertices = NULL;
jvanverth6d22eca2014-10-28 11:10:48 -0700300 bool success = target->reserveVertexAndIndexSpace(4, 0, &vertices, NULL);
jvanverthfa38a302014-10-06 05:59:05 -0700301 GrAlwaysAssert(success);
302
303 SkScalar dx = pathData->fBounds.fLeft;
304 SkScalar dy = pathData->fBounds.fTop;
305 SkScalar width = pathData->fBounds.width();
306 SkScalar height = pathData->fBounds.height();
307
308 SkScalar invScale = 1.0f/kScaleFactor;
309 dx *= invScale;
310 dy *= invScale;
311 width *= invScale;
312 height *= invScale;
313
314 SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
315 SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
316 SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
317 SkFixed th = SkScalarToFixed(pathData->fBounds.height());
318
319 // vertex positions
320 SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
321 size_t vertSize = 2 * sizeof(SkPoint);
322 SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
323 positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertSize);
324
325 // vertex texture coords
326 intptr_t intPtr = reinterpret_cast<intptr_t>(positions);
327 SkPoint* textureCoords = reinterpret_cast<SkPoint*>(intPtr + vertSize - sizeof(SkPoint));
328 textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
329 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
330 SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
331 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
332 vertSize);
333
jvanverthfa38a302014-10-06 05:59:05 -0700334 // set up any flags
335 uint32_t flags = 0;
336 const SkMatrix& vm = drawState->getViewMatrix();
337 flags |= vm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
338
339 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
jvanverth6d22eca2014-10-28 11:10:48 -0700340 if (flags != fEffectFlags) {
341 fCachedGeometryProcessor.reset(GrDistanceFieldNoGammaTextureEffect::Create(texture,
342 params,
343 flags));
344 fEffectFlags = flags;
345 }
346 drawState->setGeometryProcessor(fCachedGeometryProcessor.get());
jvanverthfa38a302014-10-06 05:59:05 -0700347
348 vm.mapRect(&r);
jvanverth6d22eca2014-10-28 11:10:48 -0700349 target->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
jvanverthfa38a302014-10-06 05:59:05 -0700350 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6, &r);
351 target->resetVertexSource();
jvanverthfa38a302014-10-06 05:59:05 -0700352
353 return true;
354}
355