• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
8 #include "src/gpu/text/GrTextContext.h"
9 
10 #include "include/core/SkGraphics.h"
11 #include "include/gpu/GrContext.h"
12 #include "include/private/SkTo.h"
13 #include "src/core/SkDistanceFieldGen.h"
14 #include "src/core/SkDraw.h"
15 #include "src/core/SkDrawProcs.h"
16 #include "src/core/SkGlyphRun.h"
17 #include "src/core/SkMaskFilterBase.h"
18 #include "src/core/SkPaintPriv.h"
19 #include "src/gpu/GrCaps.h"
20 #include "src/gpu/GrRecordingContextPriv.h"
21 #include "src/gpu/SkGr.h"
22 #include "src/gpu/ops/GrMeshDrawOp.h"
23 #include "src/gpu/text/GrSDFMaskFilter.h"
24 #include "src/gpu/text/GrTextBlobCache.h"
25 
26 // DF sizes and thresholds for usage of the small and medium sizes. For example, above
27 // kSmallDFFontLimit we will use the medium size. The large size is used up until the size at
28 // which we switch over to drawing as paths as controlled by Options.
29 static const int kSmallDFFontSize = 32;
30 static const int kSmallDFFontLimit = 32;
31 static const int kMediumDFFontSize = 72;
32 static const int kMediumDFFontLimit = 72;
33 static const int kLargeDFFontSize = 162;
34 #ifdef SK_BUILD_FOR_MAC
35 static const int kLargeDFFontLimit = 162;
36 static const int kExtraLargeDFFontSize = 256;
37 #endif
38 
39 static const int kDefaultMinDistanceFieldFontSize = 18;
40 #if defined(SK_BUILD_FOR_ANDROID)
41 static const int kDefaultMaxDistanceFieldFontSize = 384;
42 #elif defined(SK_BUILD_FOR_MAC)
43 static const int kDefaultMaxDistanceFieldFontSize = kExtraLargeDFFontSize;
44 #else
45 static const int kDefaultMaxDistanceFieldFontSize = 2 * kLargeDFFontSize;
46 #endif
47 
GrTextContext(const Options & options)48 GrTextContext::GrTextContext(const Options& options)
49         : fDistanceAdjustTable(new GrDistanceFieldAdjustTable), fOptions(options) {
50     SanitizeOptions(&fOptions);
51 }
52 
Make(const Options & options)53 std::unique_ptr<GrTextContext> GrTextContext::Make(const Options& options) {
54     return std::unique_ptr<GrTextContext>(new GrTextContext(options));
55 }
56 
ComputeCanonicalColor(const SkPaint & paint,bool lcd)57 SkColor GrTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
58     SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
59     if (lcd) {
60         // This is the correct computation, but there are tons of cases where LCD can be overridden.
61         // For now we just regenerate if any run in a textblob has LCD.
62         // TODO figure out where all of these overrides are and see if we can incorporate that logic
63         // at a higher level *OR* use sRGB
64         SkASSERT(false);
65         //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
66     } else {
67         // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
68         // gamma corrected masks anyways, nor color
69         U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
70                                        SkColorGetG(canonicalColor),
71                                        SkColorGetB(canonicalColor));
72         // reduce to our finite number of bits
73         canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
74     }
75     return canonicalColor;
76 }
77 
ComputeScalerContextFlags(const GrColorInfo & colorInfo)78 SkScalerContextFlags GrTextContext::ComputeScalerContextFlags(const GrColorInfo& colorInfo) {
79     // If we're doing linear blending, then we can disable the gamma hacks.
80     // Otherwise, leave them on. In either case, we still want the contrast boost:
81     // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
82     if (colorInfo.isLinearlyBlended()) {
83         return SkScalerContextFlags::kBoostContrast;
84     } else {
85         return SkScalerContextFlags::kFakeGammaAndBoostContrast;
86     }
87 }
88 
SanitizeOptions(Options * options)89 void GrTextContext::SanitizeOptions(Options* options) {
90     if (options->fMaxDistanceFieldFontSize < 0.f) {
91         options->fMaxDistanceFieldFontSize = kDefaultMaxDistanceFieldFontSize;
92     }
93     if (options->fMinDistanceFieldFontSize < 0.f) {
94         options->fMinDistanceFieldFontSize = kDefaultMinDistanceFieldFontSize;
95     }
96 }
97 
CanDrawAsDistanceFields(const SkPaint & paint,const SkFont & font,const SkMatrix & viewMatrix,const SkSurfaceProps & props,bool contextSupportsDistanceFieldText,const Options & options)98 bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& paint, const SkFont& font,
99                                             const SkMatrix& viewMatrix,
100                                             const SkSurfaceProps& props,
101                                             bool contextSupportsDistanceFieldText,
102                                             const Options& options) {
103     // mask filters modify alpha, which doesn't translate well to distance
104     if (paint.getMaskFilter() || !contextSupportsDistanceFieldText) {
105         return false;
106     }
107 
108     // TODO: add some stroking support
109     if (paint.getStyle() != SkPaint::kFill_Style) {
110         return false;
111     }
112 
113     if (viewMatrix.hasPerspective()) {
114         if (!options.fDistanceFieldVerticesAlwaysHaveW) {
115             return false;
116         }
117     } else {
118         SkScalar maxScale = viewMatrix.getMaxScale();
119         SkScalar scaledTextSize = maxScale * font.getSize();
120         // Hinted text looks far better at small resolutions
121         // Scaling up beyond 2x yields undesireable artifacts
122         if (scaledTextSize < options.fMinDistanceFieldFontSize ||
123             scaledTextSize > options.fMaxDistanceFieldFontSize) {
124             return false;
125         }
126 
127         bool useDFT = props.isUseDeviceIndependentFonts();
128 #if SK_FORCE_DISTANCE_FIELD_TEXT
129         useDFT = true;
130 #endif
131 
132         if (!useDFT && scaledTextSize < kLargeDFFontSize) {
133             return false;
134         }
135     }
136 
137     return true;
138 }
139 
scaled_text_size(const SkScalar textSize,const SkMatrix & viewMatrix)140 SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) {
141     SkScalar scaledTextSize = textSize;
142 
143     if (viewMatrix.hasPerspective()) {
144         // for perspective, we simply force to the medium size
145         // TODO: compute a size based on approximate screen area
146         scaledTextSize = kMediumDFFontLimit;
147     } else {
148         SkScalar maxScale = viewMatrix.getMaxScale();
149         // if we have non-unity scale, we need to choose our base text size
150         // based on the SkPaint's text size multiplied by the max scale factor
151         // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
152         if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
153             scaledTextSize *= maxScale;
154         }
155     }
156 
157     return scaledTextSize;
158 }
159 
InitDistanceFieldFont(const SkFont & font,const SkMatrix & viewMatrix,const Options & options,SkScalar * textRatio)160 SkFont GrTextContext::InitDistanceFieldFont(const SkFont& font,
161                                             const SkMatrix& viewMatrix,
162                                             const Options& options,
163                                             SkScalar* textRatio) {
164     SkScalar textSize = font.getSize();
165     SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
166 
167     SkFont dfFont{font};
168 
169     if (scaledTextSize <= kSmallDFFontLimit) {
170         *textRatio = textSize / kSmallDFFontSize;
171         dfFont.setSize(SkIntToScalar(kSmallDFFontSize));
172     } else if (scaledTextSize <= kMediumDFFontLimit) {
173         *textRatio = textSize / kMediumDFFontSize;
174         dfFont.setSize(SkIntToScalar(kMediumDFFontSize));
175 #ifdef SK_BUILD_FOR_MAC
176     } else if (scaledTextSize <= kLargeDFFontLimit) {
177         *textRatio = textSize / kLargeDFFontSize;
178         dfFont.setSize(SkIntToScalar(kLargeDFFontSize));
179     } else {
180         *textRatio = textSize / kExtraLargeDFFontSize;
181         dfFont.setSize(SkIntToScalar(kExtraLargeDFFontSize));
182     }
183 #else
184     } else {
185         *textRatio = textSize / kLargeDFFontSize;
186         dfFont.setSize(SkIntToScalar(kLargeDFFontSize));
187     }
188 #endif
189 
190     dfFont.setEdging(SkFont::Edging::kAntiAlias);
191     dfFont.setForceAutoHinting(false);
192     dfFont.setHinting(SkFontHinting::kNormal);
193 
194     // The sub-pixel position will always happen when transforming to the screen.
195     dfFont.setSubpixel(false);
196     return dfFont;
197 }
198 
InitDistanceFieldMinMaxScale(SkScalar textSize,const SkMatrix & viewMatrix,const GrTextContext::Options & options)199 std::pair<SkScalar, SkScalar> GrTextContext::InitDistanceFieldMinMaxScale(
200         SkScalar textSize,
201         const SkMatrix& viewMatrix,
202         const GrTextContext::Options& options) {
203 
204     SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
205 
206     // We have three sizes of distance field text, and within each size 'bucket' there is a floor
207     // and ceiling.  A scale outside of this range would require regenerating the distance fields
208     SkScalar dfMaskScaleFloor;
209     SkScalar dfMaskScaleCeil;
210     if (scaledTextSize <= kSmallDFFontLimit) {
211         dfMaskScaleFloor = options.fMinDistanceFieldFontSize;
212         dfMaskScaleCeil = kSmallDFFontLimit;
213     } else if (scaledTextSize <= kMediumDFFontLimit) {
214         dfMaskScaleFloor = kSmallDFFontLimit;
215         dfMaskScaleCeil = kMediumDFFontLimit;
216     } else {
217         dfMaskScaleFloor = kMediumDFFontLimit;
218         dfMaskScaleCeil = options.fMaxDistanceFieldFontSize;
219     }
220 
221     // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
222     // minMaxScale to make regeneration decisions.  Specifically, we want the maximum minimum scale
223     // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
224     // tolerate before we'd have to move to a large mip size.  When we actually test these values
225     // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
226     // against these values to decide if we can reuse or not(ie, will a given scale change our mip
227     // level)
228     SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
229 
230     return std::make_pair(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize);
231 }
232 
InitDistanceFieldPaint(const SkPaint & paint)233 SkPaint GrTextContext::InitDistanceFieldPaint(const SkPaint& paint) {
234     SkPaint dfPaint{paint};
235     dfPaint.setMaskFilter(GrSDFMaskFilter::Make());
236     return dfPaint;
237 }
238 
239 ///////////////////////////////////////////////////////////////////////////////////////////////////
240 
241 #if GR_TEST_UTILS
242 
243 #include "src/gpu/GrRenderTargetContext.h"
244 
GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp)245 GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) {
246     static uint32_t gContextID = SK_InvalidGenID;
247     static std::unique_ptr<GrTextContext> gTextContext;
248     static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
249 
250     if (context->priv().contextID() != gContextID) {
251         gContextID = context->priv().contextID();
252         gTextContext = GrTextContext::Make(GrTextContext::Options());
253     }
254 
255     // Setup dummy SkPaint / GrPaint / GrRenderTargetContext
256     auto rtc = GrRenderTargetContext::Make(
257             context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kApprox, {1024, 1024});
258 
259     SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
260 
261     SkPaint skPaint;
262     skPaint.setColor(random->nextU());
263 
264     SkFont font;
265     if (random->nextBool()) {
266         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
267     } else {
268         font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
269     }
270     font.setSubpixel(random->nextBool());
271 
272     const char* text = "The quick brown fox jumps over the lazy dog.";
273 
274     // create some random x/y offsets, including negative offsets
275     static const int kMaxTrans = 1024;
276     int xPos = (random->nextU() % 2) * 2 - 1;
277     int yPos = (random->nextU() % 2) * 2 - 1;
278     int xInt = (random->nextU() % kMaxTrans) * xPos;
279     int yInt = (random->nextU() % kMaxTrans) * yPos;
280 
281     return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(),
282                                               skPaint, font, viewMatrix, text, xInt, yInt);
283 }
284 
285 #endif
286