1/*
2 * Copyright 2018 The Android Open Source Project
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 "src/core/SkGlyphRunPainter.h"
9
10#if SK_SUPPORT_GPU
11#include "include/private/GrRecordingContext.h"
12#include "src/gpu/GrCaps.h"
13#include "src/gpu/GrColorInfo.h"
14#include "src/gpu/GrContextPriv.h"
15#include "src/gpu/GrRecordingContextPriv.h"
16#include "src/gpu/GrRenderTargetContext.h"
17#include "src/gpu/SkGr.h"
18#include "src/gpu/text/GrTextBlobCache.h"
19#include "src/gpu/text/GrTextContext.h"
20#endif
21
22#include "include/core/SkColorFilter.h"
23#include "include/core/SkMaskFilter.h"
24#include "include/core/SkPathEffect.h"
25#include "include/private/SkTDArray.h"
26#include "src/core/SkDevice.h"
27#include "src/core/SkDistanceFieldGen.h"
28#include "src/core/SkDraw.h"
29#include "src/core/SkEnumerate.h"
30#include "src/core/SkFontPriv.h"
31#include "src/core/SkRasterClip.h"
32#include "src/core/SkScalerCache.h"
33#include "src/core/SkStrikeCache.h"
34#include "src/core/SkStrikeForGPU.h"
35#include "src/core/SkStrikeSpec.h"
36#include "src/core/SkTraceEvent.h"
37
38#include <climits>
39
40// -- SkGlyphRunListPainter ------------------------------------------------------------------------
41SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
42 SkColorType colorType,
43 SkScalerContextFlags flags,
44 SkStrikeForGPUCacheInterface* strikeCache)
45 : fDeviceProps{props}
46 , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
47 , fColorType{colorType}, fScalerContextFlags{flags}
48 , fStrikeCache{strikeCache} {}
49
50// TODO: unify with code in GrTextContext.cpp
51static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
52 // If we're doing linear blending, then we can disable the gamma hacks.
53 // Otherwise, leave them on. In either case, we still want the contrast boost:
54 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
55 if (cs && cs->gammaIsLinear()) {
56 return SkScalerContextFlags::kBoostContrast;
57 } else {
58 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
59 }
60}
61
62SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
63 SkColorType colorType,
64 SkColorSpace* cs,
65 SkStrikeForGPUCacheInterface* strikeCache)
66 : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}
67
68#if SK_SUPPORT_GPU
69SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, const GrColorInfo& csi)
70 : SkGlyphRunListPainter(props,
71 kUnknown_SkColorType,
72 compute_scaler_context_flags(csi.colorSpace()),
73 SkStrikeCache::GlobalStrikeCache()) {}
74
75SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
76 : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorInfo()} {}
77
78#endif
79
80void SkGlyphRunListPainter::drawForBitmapDevice(
81 const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
82 const BitmapDevicePainter* bitmapDevice) {
83 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
84
85 // TODO: fStrikeCache is only used for GPU, and some compilers complain about it during the no
86 // gpu build. Remove when SkGlyphRunListPainter is split into GPU and CPU version.
87 (void)fStrikeCache;
88
89 const SkPaint& runPaint = glyphRunList.paint();
90 // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
91 // convert the lcd text into A8 text. The props communicates this to the scaler.
92 auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver())
93 ? fDeviceProps
94 : fBitmapFallbackProps;
95
96 SkPoint drawOrigin = glyphRunList.origin();
97 for (auto& glyphRun : glyphRunList) {
98 const SkFont& runFont = glyphRun.font();
99
100 fRejects.setSource(glyphRun.source());
101
102 if (SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) {
103
104 SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath(
105 runFont, runPaint, props, fScalerContextFlags);
106
107 auto strike = strikeSpec.findOrCreateStrike();
108
109 fDrawable.startSource(fRejects.source(), drawOrigin);
110 strike->prepareForPathDrawing(&fDrawable, &fRejects);
111 fRejects.flipRejectsToSource();
112
113 // The paint we draw paths with must have the same anti-aliasing state as the runFont
114 // allowing the paths to have the same edging as the glyph masks.
115 SkPaint pathPaint = runPaint;
116 pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
117
118 bitmapDevice->paintPaths(&fDrawable, strikeSpec.strikeToSourceRatio(), pathPaint);
119 }
120 if (!fRejects.source().empty()) {
121 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
122 runFont, runPaint, props, fScalerContextFlags, deviceMatrix);
123
124 auto strike = strikeSpec.findOrCreateStrike();
125
126 fDrawable.startDevice(
127 fRejects.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
128 strike->prepareForDrawingMasksCPU(&fDrawable);
129 bitmapDevice->paintMasks(&fDrawable, runPaint);
130 }
131
132 // TODO: have the mask stage above reject the glyphs that are too big, and handle the
133 // rejects in a more sophisticated stage.
134 }
135}
136
137#if SK_SUPPORT_GPU
138void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList,
139 const SkMatrix& drawMatrix,
140 const SkSurfaceProps& props,
141 bool contextSupportsDistanceFieldText,
142 const GrTextContext::Options& options,
143 SkGlyphRunPainterInterface* process) {
144
145 SkPoint origin = glyphRunList.origin();
146 const SkPaint& runPaint = glyphRunList.paint();
147 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
148
149 for (const auto& glyphRun : glyphRunList) {
150 fRejects.setSource(glyphRun.source());
151 const SkFont& runFont = glyphRun.font();
152
153
154 bool useSDFT = GrTextContext::CanDrawAsDistanceFields(
155 runPaint, runFont, drawMatrix, props, contextSupportsDistanceFieldText, options);
156
157 bool usePaths =
158 useSDFT ? false : SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, drawMatrix);
159
160 if (useSDFT) {
161 // Process SDFT - This should be the .009% case.
162 SkScalar minScale, maxScale;
163 SkStrikeSpec strikeSpec;
164 std::tie(strikeSpec, minScale, maxScale) =
165 SkStrikeSpec::MakeSDFT(runFont, runPaint, fDeviceProps, drawMatrix, options);
166
167 if (!strikeSpec.isEmpty()) {
168 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
169
170 fDrawable.startSource(fRejects.source(), origin);
171 strike->prepareForSDFTDrawing(&fDrawable, &fRejects);
172 fRejects.flipRejectsToSource();
173
174 if (process) {
175 // processSourceSDFT must be called even if there are no glyphs to make sure
176 // runs are set correctly.
177 process->processSourceSDFT(
178 fDrawable.drawable(), strikeSpec, runFont, minScale, maxScale);
179 }
180 }
181 }
182
183 if (!usePaths && !fRejects.source().empty()) {
184 // Process masks including ARGB - this should be the 99.99% case.
185
186 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
187 runFont, runPaint, fDeviceProps, fScalerContextFlags, drawMatrix);
188
189 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
190
191 fDrawable.startDevice(fRejects.source(), origin, drawMatrix, strike->roundingSpec());
192 strike->prepareForMaskDrawing(&fDrawable, &fRejects);
193 fRejects.flipRejectsToSource();
194
195 if (process) {
196 // processDeviceMasks must be called even if there are no glyphs to make sure runs
197 // are set correctly.
198 process->processDeviceMasks(fDrawable.drawable(), strikeSpec);
199 }
200 }
201
202 // Glyphs are generated in different scales relative to the source space. Masks are drawn
203 // in device space, and SDFT and Paths are draw in a fixed constant space. The
204 // maxDimensionInSourceSpace is used to calculate the factor from strike space to source
205 // space.
206 SkScalar maxDimensionInSourceSpace = 0.0;
207 if (!fRejects.source().empty()) {
208 // Path case - handle big things without color and that have a path.
209 SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath(
210 runFont, runPaint, fDeviceProps, fScalerContextFlags);
211
212 if (!strikeSpec.isEmpty()) {
213 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
214
215 fDrawable.startPaths(fRejects.source());
216 strike->prepareForPathDrawing(&fDrawable, &fRejects);
217 fRejects.flipRejectsToSource();
218 maxDimensionInSourceSpace =
219 fRejects.rejectedMaxDimension() * strikeSpec.strikeToSourceRatio();
220
221 if (process) {
222 // processSourcePaths must be called even if there are no glyphs to make sure
223 // runs are set correctly.
224 process->processSourcePaths(fDrawable.drawable(), runFont, strikeSpec);
225 }
226 }
227 }
228
229 if (!fRejects.source().empty() && maxDimensionInSourceSpace != 0) {
230 // Draw of last resort. Scale the bitmap to the screen.
231 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeSourceFallback(
232 runFont, runPaint, fDeviceProps,
233 fScalerContextFlags, maxDimensionInSourceSpace);
234
235 if (!strikeSpec.isEmpty()) {
236 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
237
238 fDrawable.startSource(fRejects.source(), origin);
239 strike->prepareForMaskDrawing(&fDrawable, &fRejects);
240 fRejects.flipRejectsToSource();
241 SkASSERT(fRejects.source().empty());
242
243 if (process) {
244 process->processSourceMasks(fDrawable.drawable(), strikeSpec);
245 }
246 }
247 }
248 } // For all glyph runs
249}
250#endif // SK_SUPPORT_GPU
251
252auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
253 size_t size = 0;
254 for (const SkGlyphRun& run : glyphRunList) {
255 size = std::max(run.runSize(), size);
256 }
257 return ScopedBuffers(this, size);
258}
259
260#if SK_SUPPORT_GPU
261// -- GrTextContext --------------------------------------------------------------------------------
262SkPMColor4f generate_filtered_color(const SkPaint& paint, const GrColorInfo& colorInfo) {
263 SkColor4f filteredColor = paint.getColor4f();
264 if (auto* xform = colorInfo.colorSpaceXformFromSRGB()) {
265 filteredColor = xform->apply(filteredColor);
266 }
267 if (paint.getColorFilter() != nullptr) {
268 filteredColor = paint.getColorFilter()->filterColor4f(filteredColor, colorInfo.colorSpace(),
269 colorInfo.colorSpace());
270 }
271 return filteredColor.premul();
272}
273
274void GrTextContext::drawGlyphRunList(
275 GrRecordingContext* context, GrTextTarget* target, const GrClip& clip,
276 const SkMatrix& drawMatrix, const SkSurfaceProps& props,
277 const SkGlyphRunList& glyphRunList) {
278 auto contextPriv = context->priv();
279 // If we have been abandoned, then don't draw
280 if (contextPriv.abandoned()) {
281 return;
282 }
283 GrTextBlobCache* textBlobCache = contextPriv.getTextBlobCache();
284
285 // Get the first paint to use as the key paint.
286 const SkPaint& blobPaint = glyphRunList.paint();
287
288 const GrColorInfo& colorInfo = target->colorInfo();
289 // This is the color the op will use to draw.
290 SkPMColor4f drawingColor = generate_filtered_color(blobPaint, colorInfo);
291 // When creating the a new blob, use the GrColor calculated from the drawingColor.
292 GrColor initialVertexColor = drawingColor.toBytes_RGBA();
293
294 SkPoint drawOrigin = glyphRunList.origin();
295
296 SkMaskFilterBase::BlurRec blurRec;
297 // It might be worth caching these things, but its not clear at this time
298 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
299 const SkMaskFilter* mf = blobPaint.getMaskFilter();
300 bool canCache = glyphRunList.canCache() && !(blobPaint.getPathEffect() ||
301 (mf && !as_MFB(mf)->asABlur(&blurRec)));
302 SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(colorInfo);
303
304 sk_sp<GrTextBlob> cachedBlob;
305 GrTextBlob::Key key;
306 if (canCache) {
307 bool hasLCD = glyphRunList.anyRunsLCD();
308
309 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
310 SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
311 kUnknown_SkPixelGeometry;
312
313 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
314 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
315 // ensure we always match the same key
316 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
317 ComputeCanonicalColor(blobPaint, hasLCD);
318
319 key.fPixelGeometry = pixelGeometry;
320 key.fUniqueID = glyphRunList.uniqueID();
321 key.fStyle = blobPaint.getStyle();
322 key.fHasBlur = SkToBool(mf);
323 key.fCanonicalColor = canonicalColor;
324 key.fScalerContextFlags = scalerContextFlags;
325 cachedBlob = textBlobCache->find(key);
326 }
327
328 bool forceW = fOptions.fDistanceFieldVerticesAlwaysHaveW;
329 bool supportsSDFT = context->priv().caps()->shaderCaps()->supportsDistanceFieldText();
330 SkGlyphRunListPainter* painter = target->glyphPainter();
331 if (cachedBlob) {
332 if (cachedBlob->mustRegenerate(blobPaint, glyphRunList.anyRunsSubpixelPositioned(),
333 blurRec, drawMatrix, drawOrigin)) {
334 // We have to remake the blob because changes may invalidate our masks.
335 // TODO we could probably get away reuse most of the time if the pointer is unique,
336 // but we'd have to clear the subrun information
337 textBlobCache->remove(cachedBlob.get());
338 cachedBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, drawMatrix,
339 initialVertexColor, forceW);
340
341 painter->processGlyphRunList(
342 glyphRunList, drawMatrix, props, supportsSDFT, fOptions, cachedBlob.get());
343 } else {
344 textBlobCache->makeMRU(cachedBlob.get());
345 }
346 } else {
347 if (canCache) {
348 cachedBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, drawMatrix,
349 initialVertexColor, forceW);
350 } else {
351 cachedBlob = textBlobCache->makeBlob(glyphRunList, drawMatrix,
352 initialVertexColor, forceW);
353 }
354 painter->processGlyphRunList(
355 glyphRunList, drawMatrix, props, supportsSDFT, fOptions, cachedBlob.get());
356 }
357
358 cachedBlob->flush(target, props, blobPaint, drawingColor, clip, drawMatrix, drawOrigin);
359}
360
361#if GR_TEST_UTILS
362
363#include "src/gpu/GrRecordingContextPriv.h"
364#include "src/gpu/GrRenderTargetContext.h"
365
366std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrRecordingContext* context,
367 GrTextContext* textContext,
368 GrRenderTargetContext* rtc,
369 const SkPaint& skPaint,
370 const SkFont& font,
371 const SkMatrix& drawMatrix,
372 const char* text,
373 int x,
374 int y) {
375 auto direct = context->priv().asDirectContext();
376 if (!direct) {
377 return nullptr;
378 }
379
380 static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
381
382 size_t textLen = (int)strlen(text);
383
384 SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorInfo());
385 GrColor color = filteredColor.toBytes_RGBA();
386
387 auto drawOrigin = SkPoint::Make(x, y);
388 SkGlyphRunBuilder builder;
389 builder.drawTextUTF8(skPaint, font, text, textLen, drawOrigin);
390
391 auto glyphRunList = builder.useGlyphRunList();
392 sk_sp<GrTextBlob> blob;
393 if (!glyphRunList.empty()) {
394 blob = direct->priv().getTextBlobCache()->makeBlob(glyphRunList, drawMatrix, color, false);
395 SkGlyphRunListPainter* painter = rtc->textTarget()->glyphPainter();
396 painter->processGlyphRunList(
397 glyphRunList, drawMatrix, surfaceProps,
398 context->priv().caps()->shaderCaps()->supportsDistanceFieldText(),
399 textContext->fOptions, blob.get());
400 }
401
402 return blob->test_makeOp(textLen, drawMatrix, drawOrigin, skPaint, filteredColor, surfaceProps,
403 rtc->textTarget());
404}
405
406#endif // GR_TEST_UTILS
407#endif // SK_SUPPORT_GPU
408
409SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, size_t size)
410 : fPainter{painter} {
411 fPainter->fDrawable.ensureSize(size);
412}
413
414SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
415 fPainter->fDrawable.reset();
416 fPainter->fRejects.reset();
417}
418
419SkVector SkGlyphPositionRoundingSpec::HalfAxisSampleFreq(
420 bool isSubpixel, SkAxisAlignment axisAlignment) {
421 if (!isSubpixel) {
422 return {SK_ScalarHalf, SK_ScalarHalf};
423 } else {
424 switch (axisAlignment) {
425 case kX_SkAxisAlignment:
426 return {SkPackedGlyphID::kSubpixelRound, SK_ScalarHalf};
427 case kY_SkAxisAlignment:
428 return {SK_ScalarHalf, SkPackedGlyphID::kSubpixelRound};
429 case kNone_SkAxisAlignment:
430 return {SkPackedGlyphID::kSubpixelRound, SkPackedGlyphID::kSubpixelRound};
431 }
432 }
433
434 // Some compilers need this.
435 return {0, 0};
436}
437
438SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionMask(
439 bool isSubpixel, SkAxisAlignment axisAlignment) {
440 return SkIPoint::Make((!isSubpixel || axisAlignment == kY_SkAxisAlignment) ? 0 : ~0,
441 (!isSubpixel || axisAlignment == kX_SkAxisAlignment) ? 0 : ~0);
442}
443
444SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionFieldMask(bool isSubpixel,
445 SkAxisAlignment axisAlignment) {
446 SkIPoint ignoreMask = IgnorePositionMask(isSubpixel, axisAlignment);
447 SkIPoint answer{ignoreMask.x() & SkPackedGlyphID::kXYFieldMask.x(),
448 ignoreMask.y() & SkPackedGlyphID::kXYFieldMask.y()};
449 return answer;
450}
451
452SkGlyphPositionRoundingSpec::SkGlyphPositionRoundingSpec(
453 bool isSubpixel,SkAxisAlignment axisAlignment)
454 : halfAxisSampleFreq{HalfAxisSampleFreq(isSubpixel, axisAlignment)}
455 , ignorePositionMask{IgnorePositionMask(isSubpixel, axisAlignment)}
456 , ignorePositionFieldMask {IgnorePositionFieldMask(isSubpixel, axisAlignment)}{ }
457