Line data Source code
1 : /*
2 : * Copyright 2015 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 : #include "GrAtlasTextContext.h"
8 : #include "GrContext.h"
9 : #include "GrRenderTargetContext.h"
10 : #include "GrTextBlobCache.h"
11 : #include "SkDraw.h"
12 : #include "SkDrawFilter.h"
13 : #include "SkGr.h"
14 : #include "ops/GrMeshDrawOp.h"
15 :
16 0 : GrAtlasTextContext::GrAtlasTextContext()
17 0 : : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
18 0 : }
19 :
20 0 : GrAtlasTextContext* GrAtlasTextContext::Create() {
21 0 : return new GrAtlasTextContext();
22 : }
23 :
24 0 : bool GrAtlasTextContext::canDraw(const SkPaint& skPaint,
25 : const SkMatrix& viewMatrix,
26 : const SkSurfaceProps& props,
27 : const GrShaderCaps& shaderCaps) {
28 0 : return GrTextUtils::CanDrawAsDistanceFields(skPaint, viewMatrix, props, shaderCaps) ||
29 0 : !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
30 : }
31 :
32 0 : SkColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
33 0 : SkColor canonicalColor = paint.computeLuminanceColor();
34 0 : if (lcd) {
35 : // This is the correct computation, but there are tons of cases where LCD can be overridden.
36 : // For now we just regenerate if any run in a textblob has LCD.
37 : // TODO figure out where all of these overrides are and see if we can incorporate that logic
38 : // at a higher level *OR* use sRGB
39 0 : SkASSERT(false);
40 : //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
41 : } else {
42 : // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
43 : // gamma corrected masks anyways, nor color
44 0 : U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
45 0 : SkColorGetG(canonicalColor),
46 0 : SkColorGetB(canonicalColor));
47 : // reduce to our finite number of bits
48 0 : canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
49 : }
50 0 : return canonicalColor;
51 : }
52 :
53 0 : uint32_t GrAtlasTextContext::ComputeScalerContextFlags(GrRenderTargetContext* rtc) {
54 : // If we're doing gamma-correct rendering, then we can disable the gamma hacks.
55 : // Otherwise, leave them on. In either case, we still want the contrast boost:
56 0 : if (rtc->isGammaCorrect()) {
57 0 : return SkPaint::kBoostContrast_ScalerContextFlag;
58 : } else {
59 0 : return SkPaint::kFakeGammaAndBoostContrast_ScalerContextFlags;
60 : }
61 : }
62 :
63 : // TODO if this function ever shows up in profiling, then we can compute this value when the
64 : // textblob is being built and cache it. However, for the time being textblobs mostly only have 1
65 : // run so this is not a big deal to compute here.
66 0 : bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
67 0 : SkTextBlobRunIterator it(blob);
68 0 : for (; !it.done(); it.next()) {
69 0 : if (it.isLCD()) {
70 0 : return true;
71 : }
72 : }
73 0 : return false;
74 : }
75 :
76 0 : void GrAtlasTextContext::drawTextBlob(GrContext* context, GrRenderTargetContext* rtc,
77 : const GrClip& clip, const SkPaint& skPaint,
78 : const SkMatrix& viewMatrix,
79 : const SkSurfaceProps& props, const SkTextBlob* blob,
80 : SkScalar x, SkScalar y,
81 : SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
82 : // If we have been abandoned, then don't draw
83 0 : if (context->abandoned()) {
84 0 : return;
85 : }
86 :
87 0 : sk_sp<GrAtlasTextBlob> cacheBlob;
88 : SkMaskFilter::BlurRec blurRec;
89 0 : GrAtlasTextBlob::Key key;
90 : // It might be worth caching these things, but its not clear at this time
91 : // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
92 0 : const SkMaskFilter* mf = skPaint.getMaskFilter();
93 0 : bool canCache = !(skPaint.getPathEffect() ||
94 0 : (mf && !mf->asABlur(&blurRec)) ||
95 0 : drawFilter);
96 0 : uint32_t scalerContextFlags = ComputeScalerContextFlags(rtc);
97 :
98 0 : GrTextBlobCache* cache = context->getTextBlobCache();
99 0 : if (canCache) {
100 0 : bool hasLCD = HasLCD(blob);
101 :
102 : // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
103 0 : SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
104 0 : kUnknown_SkPixelGeometry;
105 :
106 : // TODO we want to figure out a way to be able to use the canonical color on LCD text,
107 : // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
108 : // ensure we always match the same key
109 0 : GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
110 0 : ComputeCanonicalColor(skPaint, hasLCD);
111 :
112 0 : key.fPixelGeometry = pixelGeometry;
113 0 : key.fUniqueID = blob->uniqueID();
114 0 : key.fStyle = skPaint.getStyle();
115 0 : key.fHasBlur = SkToBool(mf);
116 0 : key.fCanonicalColor = canonicalColor;
117 0 : key.fScalerContextFlags = scalerContextFlags;
118 0 : cacheBlob = cache->find(key);
119 : }
120 :
121 0 : GrTextUtils::Paint paint(&skPaint);
122 0 : if (cacheBlob) {
123 0 : if (cacheBlob->mustRegenerate(paint, blurRec, viewMatrix, x, y)) {
124 : // We have to remake the blob because changes may invalidate our masks.
125 : // TODO we could probably get away reuse most of the time if the pointer is unique,
126 : // but we'd have to clear the subrun information
127 0 : cache->remove(cacheBlob.get());
128 0 : cacheBlob = cache->makeCachedBlob(blob, key, blurRec, skPaint);
129 0 : RegenerateTextBlob(cacheBlob.get(), context->getAtlasGlyphCache(),
130 0 : *context->caps()->shaderCaps(), paint, scalerContextFlags,
131 0 : viewMatrix, props, blob, x, y, drawFilter);
132 : } else {
133 0 : cache->makeMRU(cacheBlob.get());
134 :
135 : if (CACHE_SANITY_CHECK) {
136 : int glyphCount = 0;
137 : int runCount = 0;
138 : GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob);
139 : sk_sp<GrAtlasTextBlob> sanityBlob(cache->makeBlob(glyphCount, runCount));
140 : sanityBlob->setupKey(key, blurRec, skPaint);
141 : RegenerateTextBlob(sanityBlob.get(), context->getAtlasGlyphCache(),
142 : *context->caps()->shaderCaps(), paint, scalerContextFlags,
143 : viewMatrix, props, blob, x, y, drawFilter);
144 : GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
145 : }
146 : }
147 : } else {
148 0 : if (canCache) {
149 0 : cacheBlob = cache->makeCachedBlob(blob, key, blurRec, skPaint);
150 : } else {
151 0 : cacheBlob = cache->makeBlob(blob);
152 : }
153 0 : RegenerateTextBlob(cacheBlob.get(), context->getAtlasGlyphCache(),
154 0 : *context->caps()->shaderCaps(), paint, scalerContextFlags, viewMatrix,
155 0 : props, blob, x, y, drawFilter);
156 : }
157 :
158 0 : cacheBlob->flushCached(context, rtc, blob, props, fDistanceAdjustTable.get(), paint, drawFilter,
159 0 : clip, viewMatrix, clipBounds, x, y);
160 : }
161 :
162 0 : void GrAtlasTextContext::RegenerateTextBlob(GrAtlasTextBlob* cacheBlob,
163 : GrAtlasGlyphCache* fontCache,
164 : const GrShaderCaps& shaderCaps,
165 : const GrTextUtils::Paint& paint,
166 : uint32_t scalerContextFlags, const SkMatrix& viewMatrix,
167 : const SkSurfaceProps& props, const SkTextBlob* blob,
168 : SkScalar x, SkScalar y, SkDrawFilter* drawFilter) {
169 0 : cacheBlob->initReusableBlob(paint.filteredSkColor(), viewMatrix, x, y);
170 :
171 : // Regenerate textblob
172 0 : SkTextBlobRunIterator it(blob);
173 0 : GrTextUtils::RunPaint runPaint(&paint, drawFilter, props);
174 0 : for (int run = 0; !it.done(); it.next(), run++) {
175 0 : int glyphCount = it.glyphCount();
176 0 : size_t textLen = glyphCount * sizeof(uint16_t);
177 0 : const SkPoint& offset = it.offset();
178 0 : cacheBlob->push_back_run(run);
179 0 : if (!runPaint.modifyForRun(it)) {
180 0 : continue;
181 : }
182 0 : if (GrTextUtils::CanDrawAsDistanceFields(runPaint, viewMatrix, props, shaderCaps)) {
183 0 : switch (it.positioning()) {
184 : case SkTextBlob::kDefault_Positioning: {
185 0 : GrTextUtils::DrawDFText(cacheBlob, run, fontCache, props, runPaint,
186 : scalerContextFlags, viewMatrix,
187 0 : (const char*)it.glyphs(), textLen, x + offset.x(),
188 0 : y + offset.y());
189 0 : break;
190 : }
191 : case SkTextBlob::kHorizontal_Positioning: {
192 0 : SkPoint dfOffset = SkPoint::Make(x, y + offset.y());
193 0 : GrTextUtils::DrawDFPosText(
194 : cacheBlob, run, fontCache, props, runPaint, scalerContextFlags,
195 0 : viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 1, dfOffset);
196 0 : break;
197 : }
198 : case SkTextBlob::kFull_Positioning: {
199 0 : SkPoint dfOffset = SkPoint::Make(x, y);
200 0 : GrTextUtils::DrawDFPosText(
201 : cacheBlob, run, fontCache, props, runPaint, scalerContextFlags,
202 0 : viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 2, dfOffset);
203 0 : break;
204 : }
205 : }
206 0 : } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
207 0 : cacheBlob->setRunDrawAsPaths(run);
208 : } else {
209 0 : switch (it.positioning()) {
210 : case SkTextBlob::kDefault_Positioning:
211 0 : GrTextUtils::DrawBmpText(cacheBlob, run, fontCache, props, runPaint,
212 : scalerContextFlags, viewMatrix,
213 0 : (const char*)it.glyphs(), textLen, x + offset.x(),
214 0 : y + offset.y());
215 0 : break;
216 : case SkTextBlob::kHorizontal_Positioning:
217 0 : GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache, props, runPaint,
218 : scalerContextFlags, viewMatrix,
219 0 : (const char*)it.glyphs(), textLen, it.pos(), 1,
220 0 : SkPoint::Make(x, y + offset.y()));
221 0 : break;
222 : case SkTextBlob::kFull_Positioning:
223 0 : GrTextUtils::DrawBmpPosText(cacheBlob, run, fontCache, props, runPaint,
224 : scalerContextFlags, viewMatrix,
225 0 : (const char*)it.glyphs(), textLen, it.pos(), 2,
226 0 : SkPoint::Make(x, y));
227 0 : break;
228 : }
229 : }
230 : }
231 0 : }
232 :
233 : inline sk_sp<GrAtlasTextBlob>
234 0 : GrAtlasTextContext::MakeDrawTextBlob(GrTextBlobCache* blobCache,
235 : GrAtlasGlyphCache* fontCache,
236 : const GrShaderCaps& shaderCaps,
237 : const GrTextUtils::Paint& paint,
238 : uint32_t scalerContextFlags,
239 : const SkMatrix& viewMatrix,
240 : const SkSurfaceProps& props,
241 : const char text[], size_t byteLength,
242 : SkScalar x, SkScalar y) {
243 0 : int glyphCount = paint.skPaint().countText(text, byteLength);
244 :
245 0 : sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1);
246 0 : blob->initThrowawayBlob(viewMatrix, x, y);
247 :
248 0 : if (GrTextUtils::CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) {
249 0 : GrTextUtils::DrawDFText(blob.get(), 0, fontCache, props, paint, scalerContextFlags,
250 0 : viewMatrix, text, byteLength, x, y);
251 : } else {
252 0 : GrTextUtils::DrawBmpText(blob.get(), 0, fontCache, props, paint, scalerContextFlags,
253 0 : viewMatrix, text, byteLength, x, y);
254 : }
255 0 : return blob;
256 : }
257 :
258 : inline sk_sp<GrAtlasTextBlob>
259 0 : GrAtlasTextContext::MakeDrawPosTextBlob(GrTextBlobCache* blobCache,
260 : GrAtlasGlyphCache* fontCache,
261 : const GrShaderCaps& shaderCaps,
262 : const GrTextUtils::Paint& paint,
263 : uint32_t scalerContextFlags,
264 : const SkMatrix& viewMatrix,
265 : const SkSurfaceProps& props,
266 : const char text[], size_t byteLength,
267 : const SkScalar pos[], int scalarsPerPosition, const
268 : SkPoint& offset) {
269 0 : int glyphCount = paint.skPaint().countText(text, byteLength);
270 :
271 0 : sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1);
272 0 : blob->initThrowawayBlob(viewMatrix, offset.x(), offset.y());
273 :
274 0 : if (GrTextUtils::CanDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) {
275 0 : GrTextUtils::DrawDFPosText(blob.get(), 0, fontCache, props, paint, scalerContextFlags,
276 0 : viewMatrix, text, byteLength, pos, scalarsPerPosition, offset);
277 : } else {
278 0 : GrTextUtils::DrawBmpPosText(blob.get(), 0, fontCache, props, paint, scalerContextFlags,
279 0 : viewMatrix, text, byteLength, pos, scalarsPerPosition, offset);
280 : }
281 0 : return blob;
282 : }
283 :
284 0 : void GrAtlasTextContext::drawText(GrContext* context, GrRenderTargetContext* rtc,
285 : const GrClip& clip, const SkPaint& skPaint,
286 : const SkMatrix& viewMatrix, const SkSurfaceProps& props,
287 : const char text[], size_t byteLength, SkScalar x, SkScalar y,
288 : const SkIRect& regionClipBounds) {
289 0 : if (context->abandoned()) {
290 0 : return;
291 : }
292 0 : GrTextUtils::Paint paint(&skPaint);
293 0 : if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
294 : sk_sp<GrAtlasTextBlob> blob(
295 : MakeDrawTextBlob(context->getTextBlobCache(), context->getAtlasGlyphCache(),
296 0 : *context->caps()->shaderCaps(),
297 : paint, ComputeScalerContextFlags(rtc),
298 : viewMatrix, props,
299 0 : text, byteLength, x, y));
300 0 : blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), paint, clip,
301 0 : viewMatrix, regionClipBounds, x, y);
302 0 : return;
303 : }
304 :
305 : // fall back to drawing as a path
306 0 : GrTextUtils::DrawTextAsPath(context, rtc, clip, paint, viewMatrix, text, byteLength, x, y,
307 0 : regionClipBounds);
308 : }
309 :
310 0 : void GrAtlasTextContext::drawPosText(GrContext* context, GrRenderTargetContext* rtc,
311 : const GrClip& clip, const SkPaint& skPaint,
312 : const SkMatrix& viewMatrix, const SkSurfaceProps& props,
313 : const char text[], size_t byteLength, const SkScalar pos[],
314 : int scalarsPerPosition, const SkPoint& offset,
315 : const SkIRect& regionClipBounds) {
316 0 : GrTextUtils::Paint paint(&skPaint);
317 0 : if (context->abandoned()) {
318 0 : return;
319 0 : } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
320 : sk_sp<GrAtlasTextBlob> blob(
321 : MakeDrawPosTextBlob(context->getTextBlobCache(), context->getAtlasGlyphCache(),
322 0 : *context->caps()->shaderCaps(),
323 : paint, ComputeScalerContextFlags(rtc),
324 : viewMatrix, props,
325 : text, byteLength,
326 : pos, scalarsPerPosition,
327 0 : offset));
328 0 : blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), paint, clip,
329 0 : viewMatrix, regionClipBounds, offset.fX, offset.fY);
330 0 : return;
331 : }
332 :
333 : // fall back to drawing as a path
334 0 : GrTextUtils::DrawPosTextAsPath(context, rtc, props, clip, paint, viewMatrix, text, byteLength,
335 0 : pos, scalarsPerPosition, offset, regionClipBounds);
336 : }
337 :
338 : ///////////////////////////////////////////////////////////////////////////////////////////////////
339 :
340 : #if GR_TEST_UTILS
341 :
342 0 : DRAW_OP_TEST_DEFINE(TextBlobOp) {
343 : static uint32_t gContextID = SK_InvalidGenID;
344 : static GrAtlasTextContext* gTextContext = nullptr;
345 0 : static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
346 :
347 0 : if (context->uniqueID() != gContextID) {
348 0 : gContextID = context->uniqueID();
349 0 : delete gTextContext;
350 :
351 0 : gTextContext = GrAtlasTextContext::Create();
352 : }
353 :
354 : // Setup dummy SkPaint / GrPaint / GrRenderTargetContext
355 : sk_sp<GrRenderTargetContext> renderTargetContext(context->makeRenderTargetContext(
356 0 : SkBackingFit::kApprox, 1024, 1024, kRGBA_8888_GrPixelConfig, nullptr));
357 :
358 0 : SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
359 0 : SkPaint skPaint;
360 0 : skPaint.setColor(random->nextU());
361 0 : skPaint.setLCDRenderText(random->nextBool());
362 0 : skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
363 0 : skPaint.setSubpixelText(random->nextBool());
364 :
365 0 : const char* text = "The quick brown fox jumps over the lazy dog.";
366 0 : int textLen = (int)strlen(text);
367 :
368 : // create some random x/y offsets, including negative offsets
369 : static const int kMaxTrans = 1024;
370 0 : int xPos = (random->nextU() % 2) * 2 - 1;
371 0 : int yPos = (random->nextU() % 2) * 2 - 1;
372 0 : int xInt = (random->nextU() % kMaxTrans) * xPos;
373 0 : int yInt = (random->nextU() % kMaxTrans) * yPos;
374 0 : SkScalar x = SkIntToScalar(xInt);
375 0 : SkScalar y = SkIntToScalar(yInt);
376 :
377 0 : GrTextUtils::Paint paint(&skPaint);
378 : // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to
379 : // test the text op with this unit test, that is okay.
380 : sk_sp<GrAtlasTextBlob> blob(GrAtlasTextContext::MakeDrawTextBlob(
381 : context->getTextBlobCache(), context->getAtlasGlyphCache(),
382 0 : *context->caps()->shaderCaps(), paint,
383 : GrAtlasTextContext::kTextBlobOpScalerContextFlags, viewMatrix, gSurfaceProps, text,
384 0 : static_cast<size_t>(textLen), x, y));
385 :
386 : return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, paint, gSurfaceProps,
387 0 : gTextContext->dfAdjustTable(), context->getAtlasGlyphCache());
388 : }
389 :
390 : #endif
|