• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ------------------------------------------------------------------------
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkScalerContextFlags flags,SkStrikeForGPUCacheInterface * strikeCache)41 SkGlyphRunListPainter::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
compute_scaler_context_flags(const SkColorSpace * cs)51 static 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 
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkColorSpace * cs,SkStrikeForGPUCacheInterface * strikeCache)62 SkGlyphRunListPainter::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
SkGlyphRunListPainter(const SkSurfaceProps & props,const GrColorInfo & csi)69 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, const GrColorInfo& csi)
70         : SkGlyphRunListPainter(props,
71                                 kUnknown_SkColorType,
72                                 compute_scaler_context_flags(csi.colorSpace()),
73                                 SkStrikeCache::GlobalStrikeCache()) {}
74 
SkGlyphRunListPainter(const GrRenderTargetContext & rtc)75 SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
76         : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorInfo()} {}
77 
78 #endif
79 
drawForBitmapDevice(const SkGlyphRunList & glyphRunList,const SkMatrix & deviceMatrix,const BitmapDevicePainter * bitmapDevice)80 void 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.findOrCreateExclusiveStrike();
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.findOrCreateExclusiveStrike();
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
processGlyphRunList(const SkGlyphRunList & glyphRunList,const SkMatrix & drawMatrix,const SkSurfaceProps & props,bool contextSupportsDistanceFieldText,const GrTextContext::Options & options,SkGlyphRunPainterInterface * process)138 void 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 
ensureBuffers(const SkGlyphRunList & glyphRunList)252 auto 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 --------------------------------------------------------------------------------
generate_filtered_color(const SkPaint & paint,const GrColorInfo & colorInfo)262 SkPMColor4f 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 
drawGlyphRunList(GrRecordingContext * context,GrTextTarget * target,const GrClip & clip,const SkMatrix & drawMatrix,const SkSurfaceProps & props,const SkGlyphRunList & glyphRunList)274 void 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     auto grStrikeCache = contextPriv.getGrStrikeCache();
284     GrTextBlobCache* textBlobCache = contextPriv.getTextBlobCache();
285 
286     // Get the first paint to use as the key paint.
287     const SkPaint& blobPaint = glyphRunList.paint();
288 
289     const GrColorInfo& colorInfo = target->colorInfo();
290     // This is the color the op will use to draw.
291     SkPMColor4f drawingColor = generate_filtered_color(blobPaint, colorInfo);
292     // When creating the a new blob, use the GrColor calculated from the drawingColor.
293     GrColor initialVertexColor = drawingColor.toBytes_RGBA();
294 
295     SkPoint drawOrigin = glyphRunList.origin();
296 
297     SkMaskFilterBase::BlurRec blurRec;
298     // It might be worth caching these things, but its not clear at this time
299     // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
300     const SkMaskFilter* mf = blobPaint.getMaskFilter();
301     bool canCache = glyphRunList.canCache() && !(blobPaint.getPathEffect() ||
302                                                 (mf && !as_MFB(mf)->asABlur(&blurRec)));
303     SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(colorInfo);
304 
305     sk_sp<GrTextBlob> cachedBlob;
306     GrTextBlob::Key key;
307     if (canCache) {
308         bool hasLCD = glyphRunList.anyRunsLCD();
309 
310         // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
311         SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
312                                         kUnknown_SkPixelGeometry;
313 
314         // TODO we want to figure out a way to be able to use the canonical color on LCD text,
315         // see the note on ComputeCanonicalColor above.  We pick a dummy value for LCD text to
316         // ensure we always match the same key
317         GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
318                                  ComputeCanonicalColor(blobPaint, hasLCD);
319 
320         key.fPixelGeometry = pixelGeometry;
321         key.fUniqueID = glyphRunList.uniqueID();
322         key.fStyle = blobPaint.getStyle();
323         key.fHasBlur = SkToBool(mf);
324         key.fCanonicalColor = canonicalColor;
325         key.fScalerContextFlags = scalerContextFlags;
326         cachedBlob = textBlobCache->find(key);
327     }
328 
329     bool forceW = fOptions.fDistanceFieldVerticesAlwaysHaveW;
330     bool supportsSDFT = context->priv().caps()->shaderCaps()->supportsDistanceFieldText();
331     SkGlyphRunListPainter* painter = target->glyphPainter();
332     if (cachedBlob) {
333         if (cachedBlob->mustRegenerate(blobPaint, glyphRunList.anyRunsSubpixelPositioned(),
334                                        blurRec, drawMatrix, drawOrigin)) {
335             // We have to remake the blob because changes may invalidate our masks.
336             // TODO we could probably get away reuse most of the time if the pointer is unique,
337             // but we'd have to clear the subrun information
338             textBlobCache->remove(cachedBlob.get());
339             cachedBlob = textBlobCache->makeCachedBlob(
340                     glyphRunList, grStrikeCache, key, blurRec, drawMatrix,
341                     initialVertexColor, forceW);
342 
343             painter->processGlyphRunList(
344                     glyphRunList, drawMatrix, props, supportsSDFT, fOptions, cachedBlob.get());
345         } else {
346             textBlobCache->makeMRU(cachedBlob.get());
347         }
348     } else {
349         if (canCache) {
350             cachedBlob = textBlobCache->makeCachedBlob(
351                     glyphRunList, grStrikeCache, key, blurRec, drawMatrix,
352                     initialVertexColor, forceW);
353         } else {
354             cachedBlob = textBlobCache->makeBlob(
355                     glyphRunList, grStrikeCache, drawMatrix, initialVertexColor, forceW);
356         }
357         painter->processGlyphRunList(
358                 glyphRunList, drawMatrix, props, supportsSDFT, fOptions, cachedBlob.get());
359     }
360 
361     cachedBlob->flush(target, props, fDistanceAdjustTable.get(), blobPaint, drawingColor,
362                       clip, drawMatrix, drawOrigin);
363 }
364 
365 #if GR_TEST_UTILS
366 
367 #include "src/gpu/GrRecordingContextPriv.h"
368 #include "src/gpu/GrRenderTargetContext.h"
369 
createOp_TestingOnly(GrRecordingContext * context,GrTextContext * textContext,GrRenderTargetContext * rtc,const SkPaint & skPaint,const SkFont & font,const SkMatrix & drawMatrix,const char * text,int x,int y)370 std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrRecordingContext* context,
371                                                               GrTextContext* textContext,
372                                                               GrRenderTargetContext* rtc,
373                                                               const SkPaint& skPaint,
374                                                               const SkFont& font,
375                                                               const SkMatrix& drawMatrix,
376                                                               const char* text,
377                                                               int x,
378                                                               int y) {
379     auto direct = context->priv().asDirectContext();
380     if (!direct) {
381         return nullptr;
382     }
383 
384     auto strikeCache = direct->priv().getGrStrikeCache();
385 
386     static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
387 
388     size_t textLen = (int)strlen(text);
389 
390     SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorInfo());
391     GrColor color = filteredColor.toBytes_RGBA();
392 
393     auto drawOrigin = SkPoint::Make(x, y);
394     SkGlyphRunBuilder builder;
395     builder.drawTextUTF8(skPaint, font, text, textLen, drawOrigin);
396 
397     auto glyphRunList = builder.useGlyphRunList();
398     sk_sp<GrTextBlob> blob;
399     if (!glyphRunList.empty()) {
400         blob = direct->priv().getTextBlobCache()->makeBlob(
401                 glyphRunList, strikeCache, drawMatrix, color, false);
402         SkGlyphRunListPainter* painter = rtc->textTarget()->glyphPainter();
403         painter->processGlyphRunList(
404                 glyphRunList, drawMatrix, surfaceProps,
405                 context->priv().caps()->shaderCaps()->supportsDistanceFieldText(),
406                 textContext->fOptions, blob.get());
407     }
408 
409     return blob->test_makeOp(textLen, drawMatrix, drawOrigin, skPaint, filteredColor, surfaceProps,
410                              textContext->dfAdjustTable(), rtc->textTarget());
411 }
412 
413 #endif  // GR_TEST_UTILS
414 #endif  // SK_SUPPORT_GPU
415 
ScopedBuffers(SkGlyphRunListPainter * painter,size_t size)416 SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, size_t size)
417         : fPainter{painter} {
418     fPainter->fDrawable.ensureSize(size);
419 }
420 
~ScopedBuffers()421 SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
422     fPainter->fDrawable.reset();
423     fPainter->fRejects.reset();
424 }
425 
HalfAxisSampleFreq(bool isSubpixel,SkAxisAlignment axisAlignment)426 SkVector SkGlyphPositionRoundingSpec::HalfAxisSampleFreq(
427         bool isSubpixel, SkAxisAlignment axisAlignment) {
428     if (!isSubpixel) {
429         return {SK_ScalarHalf, SK_ScalarHalf};
430     } else {
431         switch (axisAlignment) {
432             case kX_SkAxisAlignment:
433                 return {SkPackedGlyphID::kSubpixelRound, SK_ScalarHalf};
434             case kY_SkAxisAlignment:
435                 return {SK_ScalarHalf, SkPackedGlyphID::kSubpixelRound};
436             case kNone_SkAxisAlignment:
437                 return {SkPackedGlyphID::kSubpixelRound, SkPackedGlyphID::kSubpixelRound};
438         }
439     }
440 
441     // Some compilers need this.
442     return {0, 0};
443 }
444 
IgnorePositionMask(bool isSubpixel,SkAxisAlignment axisAlignment)445 SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionMask(
446         bool isSubpixel, SkAxisAlignment axisAlignment) {
447     return SkIPoint::Make((!isSubpixel || axisAlignment == kY_SkAxisAlignment) ? 0 : ~0,
448                           (!isSubpixel || axisAlignment == kX_SkAxisAlignment) ? 0 : ~0);
449 }
450 
IgnorePositionFieldMask(bool isSubpixel,SkAxisAlignment axisAlignment)451 SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionFieldMask(bool isSubpixel,
452                                                               SkAxisAlignment axisAlignment) {
453     SkIPoint ignoreMask = IgnorePositionMask(isSubpixel, axisAlignment);
454     SkIPoint answer{ignoreMask.x() & SkPackedGlyphID::kXYFieldMask.x(),
455                     ignoreMask.y() & SkPackedGlyphID::kXYFieldMask.y()};
456     return answer;
457 }
458 
SkGlyphPositionRoundingSpec(bool isSubpixel,SkAxisAlignment axisAlignment)459 SkGlyphPositionRoundingSpec::SkGlyphPositionRoundingSpec(
460         bool isSubpixel,SkAxisAlignment axisAlignment)
461     : halfAxisSampleFreq{HalfAxisSampleFreq(isSubpixel, axisAlignment)}
462     , ignorePositionMask{IgnorePositionMask(isSubpixel, axisAlignment)}
463     , ignorePositionFieldMask {IgnorePositionFieldMask(isSubpixel, axisAlignment)}{ }
464