blob: bfad6dfb6914fe020ae69733776465f27f7668ed [file] [log] [blame]
robertphillips@google.comf4c2c522012-04-27 12:08:47 +00001
2/*
3 * Copyright 2012 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 "GrSoftwarePathRenderer.h"
robertphillips@google.comed4155d2012-05-01 14:30:24 +000010#include "GrPaint.h"
11#include "SkPaint.h"
12#include "GrRenderTarget.h"
13#include "GrContext.h"
14#include "SkDraw.h"
15#include "SkRasterClip.h"
robertphillips@google.comc82a8b72012-06-21 20:15:48 +000016#include "GrGpu.h"
robertphillips@google.comf4c2c522012-04-27 12:08:47 +000017
robertphillips@google.comed4155d2012-05-01 14:30:24 +000018////////////////////////////////////////////////////////////////////////////////
robertphillips@google.comf4c2c522012-04-27 12:08:47 +000019bool GrSoftwarePathRenderer::canDrawPath(const SkPath& path,
20 GrPathFill fill,
21 const GrDrawTarget* target,
22 bool antiAlias) const {
robertphillips@google.comed4155d2012-05-01 14:30:24 +000023 if (!antiAlias || NULL == fContext) {
24 // TODO: We could allow the SW path to also handle non-AA paths but
25 // this would mean that GrDefaultPathRenderer would never be called
26 // (since it appears after the SW renderer in the path renderer
27 // chain). Some testing would need to be done r.e. performance
28 // and consistency of the resulting images before removing
29 // the "!antiAlias" clause from the above test
robertphillips@google.comf4c2c522012-04-27 12:08:47 +000030 return false;
31 }
32
robertphillips@google.comed4155d2012-05-01 14:30:24 +000033 return true;
robertphillips@google.comf4c2c522012-04-27 12:08:47 +000034}
35
robertphillips@google.comed4155d2012-05-01 14:30:24 +000036namespace {
37
38////////////////////////////////////////////////////////////////////////////////
39SkPath::FillType gr_fill_to_sk_fill(GrPathFill fill) {
40 switch (fill) {
bsalomon@google.com47059542012-06-06 20:51:20 +000041 case kWinding_GrPathFill:
robertphillips@google.comed4155d2012-05-01 14:30:24 +000042 return SkPath::kWinding_FillType;
bsalomon@google.com47059542012-06-06 20:51:20 +000043 case kEvenOdd_GrPathFill:
robertphillips@google.comed4155d2012-05-01 14:30:24 +000044 return SkPath::kEvenOdd_FillType;
bsalomon@google.com47059542012-06-06 20:51:20 +000045 case kInverseWinding_GrPathFill:
robertphillips@google.comed4155d2012-05-01 14:30:24 +000046 return SkPath::kInverseWinding_FillType;
bsalomon@google.com47059542012-06-06 20:51:20 +000047 case kInverseEvenOdd_GrPathFill:
robertphillips@google.comed4155d2012-05-01 14:30:24 +000048 return SkPath::kInverseEvenOdd_FillType;
49 default:
50 GrCrash("Unexpected fill.");
51 return SkPath::kWinding_FillType;
52 }
53}
54
55////////////////////////////////////////////////////////////////////////////////
56// gets device coord bounds of path (not considering the fill) and clip. The
57// path bounds will be a subset of the clip bounds. returns false if
58// path bounds would be empty.
59bool get_path_and_clip_bounds(const GrDrawTarget* target,
60 const SkPath& path,
61 const GrVec* translate,
62 GrIRect* pathBounds,
63 GrIRect* clipBounds) {
64 // compute bounds as intersection of rt size, clip, and path
65 const GrRenderTarget* rt = target->getDrawState().getRenderTarget();
66 if (NULL == rt) {
67 return false;
68 }
69 *pathBounds = GrIRect::MakeWH(rt->width(), rt->height());
70 const GrClip& clip = target->getClip();
71 if (clip.hasConservativeBounds()) {
72 clip.getConservativeBounds().roundOut(clipBounds);
73 if (!pathBounds->intersect(*clipBounds)) {
74 return false;
75 }
76 } else {
77 // pathBounds is currently the rt extent, set clip bounds to that rect.
78 *clipBounds = *pathBounds;
79 }
80 GrRect pathSBounds = path.getBounds();
81 if (!pathSBounds.isEmpty()) {
82 if (NULL != translate) {
83 pathSBounds.offset(*translate);
84 }
85 target->getDrawState().getViewMatrix().mapRect(&pathSBounds,
86 pathSBounds);
87 GrIRect pathIBounds;
88 pathSBounds.roundOut(&pathIBounds);
89 if (!pathBounds->intersect(pathIBounds)) {
bsalomon@google.com276c1fa2012-06-19 13:22:45 +000090 // set the correct path bounds, as this would be used later.
91 *pathBounds = pathIBounds;
robertphillips@google.comed4155d2012-05-01 14:30:24 +000092 return false;
93 }
94 } else {
bsalomon@google.com276c1fa2012-06-19 13:22:45 +000095 *pathBounds = GrIRect::EmptyIRect();
robertphillips@google.comed4155d2012-05-01 14:30:24 +000096 return false;
97 }
98 return true;
99}
100
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000101
102/*
103 * Convert a boolean operation into a transfer mode code
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000104 */
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000105SkXfermode::Mode op_to_mode(SkRegion::Op op) {
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000106
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000107 static const SkXfermode::Mode modeMap[] = {
robertphillips@google.comfa662942012-05-17 12:20:22 +0000108 SkXfermode::kDstOut_Mode, // kDifference_Op
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000109 SkXfermode::kMultiply_Mode, // kIntersect_Op
110 SkXfermode::kSrcOver_Mode, // kUnion_Op
111 SkXfermode::kXor_Mode, // kXOR_Op
112 SkXfermode::kClear_Mode, // kReverseDifference_Op
113 SkXfermode::kSrc_Mode, // kReplace_Op
114 };
115
116 return modeMap[op];
117}
118
119}
120
121/**
122 * Draw a single rect element of the clip stack into the accumulation bitmap
123 */
124void GrSWMaskHelper::draw(const GrRect& clientRect, SkRegion::Op op,
robertphillips@google.comfa662942012-05-17 12:20:22 +0000125 bool antiAlias, GrColor color) {
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000126 SkPaint paint;
127
128 SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
129
130 paint.setXfermode(mode);
131 paint.setAntiAlias(antiAlias);
robertphillips@google.comfa662942012-05-17 12:20:22 +0000132 paint.setColor(color);
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000133
134 fDraw.drawRect(clientRect, paint);
135
136 SkSafeUnref(mode);
137}
138
139/**
140 * Draw a single path element of the clip stack into the accumulation bitmap
141 */
142void GrSWMaskHelper::draw(const SkPath& clientPath, SkRegion::Op op,
robertphillips@google.comfa662942012-05-17 12:20:22 +0000143 GrPathFill fill, bool antiAlias, GrColor color) {
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000144
145 SkPaint paint;
146 SkPath tmpPath;
147 const SkPath* pathToDraw = &clientPath;
bsalomon@google.com47059542012-06-06 20:51:20 +0000148 if (kHairLine_GrPathFill == fill) {
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000149 paint.setStyle(SkPaint::kStroke_Style);
150 paint.setStrokeWidth(SK_Scalar1);
151 } else {
152 paint.setStyle(SkPaint::kFill_Style);
153 SkPath::FillType skfill = gr_fill_to_sk_fill(fill);
154 if (skfill != pathToDraw->getFillType()) {
155 tmpPath = *pathToDraw;
156 tmpPath.setFillType(skfill);
157 pathToDraw = &tmpPath;
158 }
159 }
160 SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
161
162 paint.setXfermode(mode);
163 paint.setAntiAlias(antiAlias);
robertphillips@google.comfa662942012-05-17 12:20:22 +0000164 paint.setColor(color);
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000165
166 fDraw.drawPath(*pathToDraw, paint);
167
168 SkSafeUnref(mode);
169}
170
robertphillips@google.comfa662942012-05-17 12:20:22 +0000171bool GrSWMaskHelper::init(const GrIRect& pathDevBounds,
172 const GrPoint* translate,
173 bool useMatrix) {
174 if (useMatrix) {
175 fMatrix = fContext->getMatrix();
176 } else {
177 fMatrix.setIdentity();
178 }
179
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000180 if (NULL != translate) {
181 fMatrix.postTranslate(translate->fX, translate->fY);
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000182 }
183
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000184 fMatrix.postTranslate(-pathDevBounds.fLeft * SK_Scalar1,
robertphillips@google.comfa662942012-05-17 12:20:22 +0000185 -pathDevBounds.fTop * SK_Scalar1);
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000186 GrIRect bounds = GrIRect::MakeWH(pathDevBounds.width(),
robertphillips@google.comfa662942012-05-17 12:20:22 +0000187 pathDevBounds.height());
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000188
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000189 fBM.setConfig(SkBitmap::kA8_Config, bounds.fRight, bounds.fBottom);
190 if (!fBM.allocPixels()) {
191 return false;
192 }
193 sk_bzero(fBM.getPixels(), fBM.getSafeSize());
194
195 sk_bzero(&fDraw, sizeof(fDraw));
196 fRasterClip.setRect(bounds);
197 fDraw.fRC = &fRasterClip;
198 fDraw.fClip = &fRasterClip.bwRgn();
199 fDraw.fMatrix = &fMatrix;
200 fDraw.fBitmap = &fBM;
201 return true;
202}
203
204/**
205 * Get a texture (from the texture cache) of the correct size & format
206 */
207bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* tex) {
robertphillips@google.com75b3c962012-06-07 12:08:45 +0000208 GrTextureDesc desc;
209 desc.fWidth = fBM.width();
210 desc.fHeight = fBM.height();
211 desc.fConfig = kAlpha_8_GrPixelConfig;
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000212
213 tex->set(fContext, desc);
214 GrTexture* texture = tex->texture();
215
216 if (NULL == texture) {
217 return false;
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000218 }
219
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000220 return true;
221}
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000222
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000223/**
224 * Move the result of the software mask generation back to the gpu
225 */
robertphillips@google.comc82a8b72012-06-21 20:15:48 +0000226void GrSWMaskHelper::toTexture(GrTexture *texture, bool clearToWhite) {
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000227 SkAutoLockPixels alp(fBM);
robertphillips@google.comc82a8b72012-06-21 20:15:48 +0000228
229 // The destination texture is almost always larger than "fBM". Clear
230 // it appropriately so we don't get mask artifacts outside of the path's
231 // bounding box
232
233 // "texture" needs to be installed as the render target for the clear
234 // and the texture upload but cannot remain the render target upon
235 // returned. Callers typically use it as a texture and it would then
236 // be both source and dest.
237 GrDrawState::AutoRenderTargetRestore artr(fContext->getGpu()->drawState(),
238 texture->asRenderTarget());
239
240 if (clearToWhite) {
241 fContext->getGpu()->clear(NULL, SK_ColorWHITE);
242 } else {
243 fContext->getGpu()->clear(NULL, 0x00000000);
244 }
245
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000246 texture->writePixels(0, 0, fBM.width(), fBM.height(),
247 kAlpha_8_GrPixelConfig,
248 fBM.getPixels(), fBM.rowBytes());
249}
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000250
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000251namespace {
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000252////////////////////////////////////////////////////////////////////////////////
253/**
254 * sw rasterizes path to A8 mask using the context's matrix and uploads to a
255 * scratch texture.
256 */
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000257bool sw_draw_path_to_mask_texture(const SkPath& clientPath,
258 const GrIRect& pathDevBounds,
259 GrPathFill fill,
260 GrContext* context,
261 const GrPoint* translate,
262 GrAutoScratchTexture* tex,
263 bool antiAlias) {
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000264 GrSWMaskHelper helper(context);
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000265
robertphillips@google.comfa662942012-05-17 12:20:22 +0000266 if (!helper.init(pathDevBounds, translate, true)) {
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000267 return false;
268 }
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000269
robertphillips@google.comfa662942012-05-17 12:20:22 +0000270 helper.draw(clientPath, SkRegion::kReplace_Op,
271 fill, antiAlias, SK_ColorWHITE);
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000272
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000273 if (!helper.getTexture(tex)) {
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000274 return false;
275 }
robertphillips@google.com6f31a3b2012-05-14 17:37:05 +0000276
robertphillips@google.comc82a8b72012-06-21 20:15:48 +0000277 helper.toTexture(tex->texture(), false);
robertphillips@google.comb4f06d72012-05-15 12:10:05 +0000278
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000279 return true;
280}
281
282////////////////////////////////////////////////////////////////////////////////
283void draw_around_inv_path(GrDrawTarget* target,
284 GrDrawState::StageMask stageMask,
285 const GrIRect& clipBounds,
286 const GrIRect& pathBounds) {
287 GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask);
288 GrRect rect;
289 if (clipBounds.fTop < pathBounds.fTop) {
290 rect.iset(clipBounds.fLeft, clipBounds.fTop,
291 clipBounds.fRight, pathBounds.fTop);
292 target->drawSimpleRect(rect, NULL, stageMask);
293 }
294 if (clipBounds.fLeft < pathBounds.fLeft) {
295 rect.iset(clipBounds.fLeft, pathBounds.fTop,
296 pathBounds.fLeft, pathBounds.fBottom);
297 target->drawSimpleRect(rect, NULL, stageMask);
298 }
299 if (clipBounds.fRight > pathBounds.fRight) {
300 rect.iset(pathBounds.fRight, pathBounds.fTop,
301 clipBounds.fRight, pathBounds.fBottom);
302 target->drawSimpleRect(rect, NULL, stageMask);
303 }
304 if (clipBounds.fBottom > pathBounds.fBottom) {
305 rect.iset(clipBounds.fLeft, pathBounds.fBottom,
306 clipBounds.fRight, clipBounds.fBottom);
307 target->drawSimpleRect(rect, NULL, stageMask);
308 }
309}
310
311}
312
313////////////////////////////////////////////////////////////////////////////////
314// return true on success; false on failure
robertphillips@google.comf4c2c522012-04-27 12:08:47 +0000315bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path,
316 GrPathFill fill,
317 const GrVec* translate,
318 GrDrawTarget* target,
319 GrDrawState::StageMask stageMask,
320 bool antiAlias) {
321
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000322 if (NULL == fContext) {
323 return false;
324 }
325
326 GrAutoScratchTexture ast;
327 GrIRect pathBounds, clipBounds;
328 if (!get_path_and_clip_bounds(target, path, translate,
329 &pathBounds, &clipBounds)) {
bsalomon@google.com276c1fa2012-06-19 13:22:45 +0000330 if (GrIsFillInverted(fill)) {
331 draw_around_inv_path(target, stageMask,
332 clipBounds, pathBounds);
333 }
334 return true;
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000335 }
336 if (sw_draw_path_to_mask_texture(path, pathBounds,
337 fill, fContext,
338 translate, &ast, antiAlias)) {
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000339#if 1
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000340 GrTexture* texture = ast.texture();
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000341#else
342 SkAutoTUnref<GrTexture> texture(ast.detach());
343#endif
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000344 GrAssert(NULL != texture);
345 GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask);
346 enum {
robertphillips@google.com7ff2e372012-05-02 19:27:44 +0000347 // the SW path renderer shares this stage with glyph
348 // rendering (kGlyphMaskStage in GrBatchedTextContext)
349 kPathMaskStage = GrPaint::kTotalStages,
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000350 };
robertphillips@google.combeeb97c2012-05-09 21:15:28 +0000351 GrAssert(NULL == target->drawState()->getTexture(kPathMaskStage));
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000352 target->drawState()->setTexture(kPathMaskStage, texture);
353 target->drawState()->sampler(kPathMaskStage)->reset();
354 GrScalar w = GrIntToScalar(pathBounds.width());
355 GrScalar h = GrIntToScalar(pathBounds.height());
356 GrRect maskRect = GrRect::MakeWH(w / texture->width(),
357 h / texture->height());
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000358
robertphillips@google.comed4155d2012-05-01 14:30:24 +0000359 const GrRect* srcRects[GrDrawState::kNumStages] = {NULL};
360 srcRects[kPathMaskStage] = &maskRect;
361 stageMask |= 1 << kPathMaskStage;
362 GrRect dstRect = GrRect::MakeLTRB(
363 SK_Scalar1* pathBounds.fLeft,
364 SK_Scalar1* pathBounds.fTop,
365 SK_Scalar1* pathBounds.fRight,
366 SK_Scalar1* pathBounds.fBottom);
367 target->drawRect(dstRect, NULL, stageMask, srcRects, NULL);
368 target->drawState()->setTexture(kPathMaskStage, NULL);
369 if (GrIsFillInverted(fill)) {
370 draw_around_inv_path(target, stageMask,
371 clipBounds, pathBounds);
372 }
373 return true;
374 }
375
robertphillips@google.comf4c2c522012-04-27 12:08:47 +0000376 return false;
377}