1 /*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "GrStencilAndCoverTextContext.h"
9 #include "GrAtlasTextContext.h"
10 #include "GrDrawTarget.h"
11 #include "GrGpu.h"
12 #include "GrPath.h"
13 #include "GrPathRange.h"
14 #include "GrResourceProvider.h"
15 #include "SkAutoKern.h"
16 #include "SkDraw.h"
17 #include "SkDrawProcs.h"
18 #include "SkGlyphCache.h"
19 #include "SkGpuDevice.h"
20 #include "SkPath.h"
21 #include "SkTextMapStateProc.h"
22 #include "SkTextFormatParams.h"
23
GrStencilAndCoverTextContext(GrContext * context,SkGpuDevice * gpuDevice,const SkDeviceProperties & properties)24 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(GrContext* context,
25 SkGpuDevice* gpuDevice,
26 const SkDeviceProperties& properties)
27 : GrTextContext(context, gpuDevice, properties)
28 , fStroke(SkStrokeRec::kFill_InitStyle)
29 , fQueuedGlyphCount(0)
30 , fFallbackGlyphsIdx(kGlyphBufferSize) {
31 }
32
33 GrStencilAndCoverTextContext*
Create(GrContext * context,SkGpuDevice * gpuDevice,const SkDeviceProperties & props)34 GrStencilAndCoverTextContext::Create(GrContext* context, SkGpuDevice* gpuDevice,
35 const SkDeviceProperties& props) {
36 GrStencilAndCoverTextContext* textContext = SkNEW_ARGS(GrStencilAndCoverTextContext,
37 (context, gpuDevice, props));
38 textContext->fFallbackTextContext = GrAtlasTextContext::Create(context, gpuDevice, props,
39 false);
40
41 return textContext;
42 }
43
~GrStencilAndCoverTextContext()44 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
45 }
46
canDraw(const GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix)47 bool GrStencilAndCoverTextContext::canDraw(const GrRenderTarget* rt,
48 const GrClip& clip,
49 const GrPaint& paint,
50 const SkPaint& skPaint,
51 const SkMatrix& viewMatrix) {
52 if (skPaint.getRasterizer()) {
53 return false;
54 }
55 if (skPaint.getMaskFilter()) {
56 return false;
57 }
58 if (skPaint.getPathEffect()) {
59 return false;
60 }
61
62 // No hairlines unless we can map the 1 px width to the object space.
63 if (skPaint.getStyle() == SkPaint::kStroke_Style
64 && skPaint.getStrokeWidth() == 0
65 && viewMatrix.hasPerspective()) {
66 return false;
67 }
68
69 // No color bitmap fonts.
70 SkScalerContext::Rec rec;
71 SkScalerContext::MakeRec(skPaint, &fDeviceProperties, NULL, &rec);
72 return rec.getFormat() != SkMask::kARGB32_Format;
73 }
74
onDrawText(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & regionClipBounds)75 void GrStencilAndCoverTextContext::onDrawText(GrRenderTarget* rt,
76 const GrClip& clip,
77 const GrPaint& paint,
78 const SkPaint& skPaint,
79 const SkMatrix& viewMatrix,
80 const char text[],
81 size_t byteLength,
82 SkScalar x, SkScalar y,
83 const SkIRect& regionClipBounds) {
84 SkASSERT(byteLength == 0 || text != NULL);
85
86 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
87 return;
88 }
89
90 // This is the slow path, mainly used by Skia unit tests. The other
91 // backends (8888, gpu, ...) use device-space dependent glyph caches. In
92 // order to match the glyph positions that the other code paths produce, we
93 // must also use device-space dependent glyph cache. This has the
94 // side-effect that the glyph shape outline will be in device-space,
95 // too. This in turn has the side-effect that NVPR can not stroke the paths,
96 // as the stroke in NVPR is defined in object-space.
97 // NOTE: here we have following coincidence that works at the moment:
98 // - When using the device-space glyphs, the transforms we pass to NVPR
99 // instanced drawing are the global transforms, and the view transform is
100 // identity. NVPR can not use non-affine transforms in the instanced
101 // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
102 // will turn off the use of device-space glyphs when perspective transforms
103 // are in use.
104
105 this->init(rt, clip, paint, skPaint, byteLength, kMaxAccuracy_RenderMode, viewMatrix,
106 regionClipBounds);
107
108 // Transform our starting point.
109 if (fUsingDeviceSpaceGlyphs) {
110 SkPoint loc;
111 fContextInitialMatrix.mapXY(x, y, &loc);
112 x = loc.fX;
113 y = loc.fY;
114 }
115
116 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
117
118 const char* stop = text + byteLength;
119
120 // Measure first if needed.
121 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
122 SkFixed stopX = 0;
123 SkFixed stopY = 0;
124
125 const char* textPtr = text;
126 while (textPtr < stop) {
127 // We don't need x, y here, since all subpixel variants will have the
128 // same advance.
129 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
130
131 stopX += glyph.fAdvanceX;
132 stopY += glyph.fAdvanceY;
133 }
134 SkASSERT(textPtr == stop);
135
136 SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
137 SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
138
139 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
140 alignX = SkScalarHalf(alignX);
141 alignY = SkScalarHalf(alignY);
142 }
143
144 x -= alignX;
145 y -= alignY;
146 }
147
148 SkAutoKern autokern;
149
150 SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
151
152 SkFixed fx = SkScalarToFixed(x);
153 SkFixed fy = SkScalarToFixed(y);
154 while (text < stop) {
155 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
156 fx += SkFixedMul(autokern.adjust(glyph), fixedSizeRatio);
157 if (glyph.fWidth) {
158 this->appendGlyph(glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy)));
159 }
160
161 fx += SkFixedMul(glyph.fAdvanceX, fixedSizeRatio);
162 fy += SkFixedMul(glyph.fAdvanceY, fixedSizeRatio);
163 }
164
165 this->finish();
166 }
167
onDrawPosText(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & regionClipBounds)168 void GrStencilAndCoverTextContext::onDrawPosText(GrRenderTarget* rt,
169 const GrClip& clip,
170 const GrPaint& paint,
171 const SkPaint& skPaint,
172 const SkMatrix& viewMatrix,
173 const char text[],
174 size_t byteLength,
175 const SkScalar pos[],
176 int scalarsPerPosition,
177 const SkPoint& offset,
178 const SkIRect& regionClipBounds) {
179 SkASSERT(byteLength == 0 || text != NULL);
180 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
181
182 // nothing to draw
183 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
184 return;
185 }
186
187 // This is the fast path. Here we do not bake in the device-transform to
188 // the glyph outline or the advances. This is because we do not need to
189 // position the glyphs at all, since the caller has done the positioning.
190 // The positioning is based on SkPaint::measureText of individual
191 // glyphs. That already uses glyph cache without device transforms. Device
192 // transform is not part of SkPaint::measureText API, and thus we use the
193 // same glyphs as what were measured.
194
195 this->init(rt, clip, paint, skPaint, byteLength, kMaxPerformance_RenderMode, viewMatrix,
196 regionClipBounds);
197
198 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
199
200 const char* stop = text + byteLength;
201
202 SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
203 SkTextAlignProc alignProc(fSkPaint.getTextAlign());
204 while (text < stop) {
205 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
206 if (glyph.fWidth) {
207 SkPoint tmsLoc;
208 tmsProc(pos, &tmsLoc);
209 SkPoint loc;
210 alignProc(tmsLoc, glyph, &loc);
211
212 this->appendGlyph(glyph, loc);
213 }
214 pos += scalarsPerPosition;
215 }
216
217 this->finish();
218 }
219
get_gr_glyphs(GrContext * ctx,const SkTypeface * typeface,const SkDescriptor * desc,const SkStrokeRec & stroke)220 static GrPathRange* get_gr_glyphs(GrContext* ctx,
221 const SkTypeface* typeface,
222 const SkDescriptor* desc,
223 const SkStrokeRec& stroke) {
224 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
225 GrUniqueKey key;
226 GrUniqueKey::Builder builder(&key, kDomain, 4);
227 struct GlyphKey {
228 uint32_t fChecksum;
229 uint32_t fTypeface;
230 uint64_t fStroke;
231 };
232 GlyphKey* glyphKey = reinterpret_cast<GlyphKey*>(&builder[0]);
233 glyphKey->fChecksum = desc ? desc->getChecksum() : 0;
234 glyphKey->fTypeface = typeface ? typeface->uniqueID() : 0;
235 glyphKey->fStroke = GrPath::ComputeStrokeKey(stroke);
236 builder.finish();
237
238 SkAutoTUnref<GrPathRange> glyphs(
239 static_cast<GrPathRange*>(ctx->resourceProvider()->findAndRefResourceByUniqueKey(key)));
240 if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
241 glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
242 ctx->resourceProvider()->assignUniqueKeyToResource(key, glyphs);
243 }
244
245 return glyphs.detach();
246 }
247
init(GrRenderTarget * rt,const GrClip & clip,const GrPaint & paint,const SkPaint & skPaint,size_t textByteLength,RenderMode renderMode,const SkMatrix & viewMatrix,const SkIRect & regionClipBounds)248 void GrStencilAndCoverTextContext::init(GrRenderTarget* rt,
249 const GrClip& clip,
250 const GrPaint& paint,
251 const SkPaint& skPaint,
252 size_t textByteLength,
253 RenderMode renderMode,
254 const SkMatrix& viewMatrix,
255 const SkIRect& regionClipBounds) {
256 GrTextContext::init(rt, clip, paint, skPaint, regionClipBounds);
257
258 fContextInitialMatrix = viewMatrix;
259 fViewMatrix = viewMatrix;
260 fLocalMatrix = SkMatrix::I();
261
262 const bool otherBackendsWillDrawAsPaths =
263 SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
264
265 fUsingDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
266 kMaxAccuracy_RenderMode == renderMode &&
267 SkToBool(fContextInitialMatrix.getType() &
268 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
269
270 if (fUsingDeviceSpaceGlyphs) {
271 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
272 SkASSERT(!fContextInitialMatrix.hasPerspective());
273
274 // The whole shape (including stroke) will be baked into the glyph outlines. Make
275 // NVPR just fill the baked shapes.
276 fStroke = SkStrokeRec(SkStrokeRec::kFill_InitStyle);
277
278 fTextRatio = fTextInverseRatio = 1.0f;
279
280 // Glyphs loaded by GPU path rendering have an inverted y-direction.
281 SkMatrix m;
282 m.setScale(1, -1);
283 fViewMatrix = m;
284
285 // Post-flip the initial matrix so we're left with just the flip after
286 // the paint preConcats the inverse.
287 m = fContextInitialMatrix;
288 m.postScale(1, -1);
289 if (!m.invert(&fLocalMatrix)) {
290 SkDebugf("Not invertible!\n");
291 return;
292 }
293
294 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix,
295 true /*ignoreGamma*/);
296 fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
297 &fGlyphCache->getDescriptor(), fStroke);
298 } else {
299 // Don't bake strokes into the glyph outlines. We will stroke the glyphs
300 // using the GPU instead. This is the fast path.
301 fStroke = SkStrokeRec(fSkPaint);
302 fSkPaint.setStyle(SkPaint::kFill_Style);
303
304 if (fStroke.isHairlineStyle()) {
305 // Approximate hairline stroke.
306 SkScalar strokeWidth = SK_Scalar1 /
307 (SkVector::Make(fContextInitialMatrix.getScaleX(),
308 fContextInitialMatrix.getSkewY()).length());
309 fStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
310
311 } else if (fSkPaint.isFakeBoldText() &&
312 #ifdef SK_USE_FREETYPE_EMBOLDEN
313 kMaxPerformance_RenderMode == renderMode &&
314 #endif
315 SkStrokeRec::kStroke_Style != fStroke.getStyle()) {
316
317 // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
318 SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
319 kStdFakeBoldInterpKeys,
320 kStdFakeBoldInterpValues,
321 kStdFakeBoldInterpLength);
322 SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
323 fStroke.setStrokeStyle(fStroke.needToApply() ? fStroke.getWidth() + extra : extra,
324 true /*strokeAndFill*/);
325
326 fSkPaint.setFakeBoldText(false);
327 }
328
329 bool canUseRawPaths;
330
331 if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
332 // We can draw the glyphs from canonically sized paths.
333 fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
334 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
335
336 // Compensate for the glyphs being scaled by fTextRatio.
337 if (!fStroke.isFillStyle()) {
338 fStroke.setStrokeStyle(fStroke.getWidth() / fTextRatio,
339 SkStrokeRec::kStrokeAndFill_Style == fStroke.getStyle());
340 }
341
342 fSkPaint.setLinearText(true);
343 fSkPaint.setLCDRenderText(false);
344 fSkPaint.setAutohinted(false);
345 fSkPaint.setHinting(SkPaint::kNo_Hinting);
346 fSkPaint.setSubpixelText(true);
347 fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
348
349 canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
350 0 == fSkPaint.getTextSkewX() &&
351 !fSkPaint.isFakeBoldText() &&
352 !fSkPaint.isVerticalText();
353 } else {
354 fTextRatio = fTextInverseRatio = 1.0f;
355 canUseRawPaths = false;
356 }
357
358 SkMatrix textMatrix;
359 // Glyphs loaded by GPU path rendering have an inverted y-direction.
360 textMatrix.setScale(fTextRatio, -fTextRatio);
361 fViewMatrix.preConcat(textMatrix);
362 fLocalMatrix = textMatrix;
363
364 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, true /*ignoreGamma*/);
365 fGlyphs = canUseRawPaths ?
366 get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, fStroke) :
367 get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
368 &fGlyphCache->getDescriptor(), fStroke);
369 }
370
371 fStateRestore.set(&fPipelineBuilder);
372
373 fPipelineBuilder.setFromPaint(fPaint, fRenderTarget, fClip);
374
375 GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
376 kZero_StencilOp,
377 kZero_StencilOp,
378 kNotEqual_StencilFunc,
379 0xffff,
380 0x0000,
381 0xffff);
382
383 *fPipelineBuilder.stencil() = kStencilPass;
384
385 SkASSERT(0 == fQueuedGlyphCount);
386 SkASSERT(kGlyphBufferSize == fFallbackGlyphsIdx);
387 }
388
mapToFallbackContext(SkMatrix * inverse)389 bool GrStencilAndCoverTextContext::mapToFallbackContext(SkMatrix* inverse) {
390 // The current view matrix is flipped because GPU path rendering glyphs have an
391 // inverted y-direction. Unflip the view matrix for the fallback context. If using
392 // device-space glyphs, we'll also need to restore the original view matrix since
393 // we moved that transfomation into our local glyph cache for this scenario. Also
394 // track the inverse operation so the caller can unmap the paint and glyph positions.
395 if (fUsingDeviceSpaceGlyphs) {
396 fViewMatrix = fContextInitialMatrix;
397 if (!fContextInitialMatrix.invert(inverse)) {
398 return false;
399 }
400 inverse->preScale(1, -1);
401 } else {
402 inverse->setScale(1, -1);
403 const SkMatrix& unflip = *inverse; // unflip is equal to its own inverse.
404 fViewMatrix.preConcat(unflip);
405 }
406 return true;
407 }
408
appendGlyph(const SkGlyph & glyph,const SkPoint & pos)409 inline void GrStencilAndCoverTextContext::appendGlyph(const SkGlyph& glyph, const SkPoint& pos) {
410 if (fQueuedGlyphCount >= fFallbackGlyphsIdx) {
411 SkASSERT(fQueuedGlyphCount == fFallbackGlyphsIdx);
412 this->flush();
413 }
414
415 // Stick the glyphs we can't draw at the end of the buffer, growing backwards.
416 int index = (SkMask::kARGB32_Format == glyph.fMaskFormat) ?
417 --fFallbackGlyphsIdx : fQueuedGlyphCount++;
418
419 fGlyphIndices[index] = glyph.getGlyphID();
420 fGlyphPositions[index].set(fTextInverseRatio * pos.x(), -fTextInverseRatio * pos.y());
421 }
422
get_xy_scalar_array(const SkPoint * pointArray)423 static const SkScalar* get_xy_scalar_array(const SkPoint* pointArray) {
424 GR_STATIC_ASSERT(2 * sizeof(SkScalar) == sizeof(SkPoint));
425 GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX));
426
427 return &pointArray[0].fX;
428 }
429
flush()430 void GrStencilAndCoverTextContext::flush() {
431 if (fQueuedGlyphCount > 0) {
432 SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(fPaint.getColor(),
433 fViewMatrix,
434 fLocalMatrix));
435 fDrawTarget->drawPaths(&fPipelineBuilder, pp, fGlyphs,
436 fGlyphIndices, GrPathRange::kU16_PathIndexType,
437 get_xy_scalar_array(fGlyphPositions),
438 GrPathRendering::kTranslate_PathTransformType,
439 fQueuedGlyphCount, GrPathRendering::kWinding_FillType);
440
441 fQueuedGlyphCount = 0;
442 }
443
444 if (fFallbackGlyphsIdx < kGlyphBufferSize) {
445 int fallbackGlyphCount = kGlyphBufferSize - fFallbackGlyphsIdx;
446
447 GrPaint paintFallback(fPaint);
448
449 SkPaint skPaintFallback(fSkPaint);
450 if (!fUsingDeviceSpaceGlyphs) {
451 fStroke.applyToPaint(&skPaintFallback);
452 }
453 skPaintFallback.setTextAlign(SkPaint::kLeft_Align); // Align has already been accounted for.
454 skPaintFallback.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
455
456 SkMatrix inverse;
457 if (this->mapToFallbackContext(&inverse)) {
458 inverse.mapPoints(&fGlyphPositions[fFallbackGlyphsIdx], fallbackGlyphCount);
459 }
460
461 fFallbackTextContext->drawPosText(fRenderTarget, fClip, paintFallback, skPaintFallback,
462 fViewMatrix, (char*)&fGlyphIndices[fFallbackGlyphsIdx],
463 2 * fallbackGlyphCount,
464 get_xy_scalar_array(&fGlyphPositions[fFallbackGlyphsIdx]),
465 2, SkPoint::Make(0, 0), fRegionClipBounds);
466
467 fFallbackGlyphsIdx = kGlyphBufferSize;
468 }
469 }
470
finish()471 void GrStencilAndCoverTextContext::finish() {
472 this->flush();
473
474 fGlyphs->unref();
475 fGlyphs = NULL;
476
477 SkGlyphCache::AttachCache(fGlyphCache);
478 fGlyphCache = NULL;
479
480 fPipelineBuilder.stencil()->setDisabled();
481 fStateRestore.set(NULL);
482 fViewMatrix = fContextInitialMatrix;
483 GrTextContext::finish();
484 }
485
486