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/gpu/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/ops/GrAtlasTextOp.h"
19#include "src/gpu/text/GrSDFTOptions.h"
20#include "src/gpu/text/GrTextBlobCache.h"
21#endif
22
23#include "include/core/SkColorFilter.h"
24#include "include/core/SkMaskFilter.h"
25#include "include/core/SkPathEffect.h"
26#include "include/private/SkTDArray.h"
27#include "src/core/SkDevice.h"
28#include "src/core/SkDistanceFieldGen.h"
29#include "src/core/SkDraw.h"
30#include "src/core/SkEnumerate.h"
31#include "src/core/SkFontPriv.h"
32#include "src/core/SkRasterClip.h"
33#include "src/core/SkScalerCache.h"
34#include "src/core/SkStrikeCache.h"
35#include "src/core/SkStrikeForGPU.h"
36#include "src/core/SkStrikeSpec.h"
37#include "src/core/SkTraceEvent.h"
38
39#include <climits>
40
41// -- SkGlyphRunListPainter ------------------------------------------------------------------------
42SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
43 SkColorType colorType,
44 SkScalerContextFlags flags,
45 SkStrikeForGPUCacheInterface* strikeCache)
46 : fDeviceProps{props}
47 , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
48 , fColorType{colorType}, fScalerContextFlags{flags}
49 , fStrikeCache{strikeCache} {}
50
51// TODO: unify with code in GrSDFTOptions.cpp
52static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
53 // If we're doing linear blending, then we can disable the gamma hacks.
54 // Otherwise, leave them on. In either case, we still want the contrast boost:
55 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
56 if (cs && cs->gammaIsLinear()) {
57 return SkScalerContextFlags::kBoostContrast;
58 } else {
59 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
60 }
61}
62
63SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
64 SkColorType colorType,
65 SkColorSpace* cs,
66 SkStrikeForGPUCacheInterface* strikeCache)
67 : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}
68
69#if SK_SUPPORT_GPU
70SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, const GrColorInfo& csi)
71 : SkGlyphRunListPainter(props,
72 kUnknown_SkColorType,
73 compute_scaler_context_flags(csi.colorSpace()),
74 SkStrikeCache::GlobalStrikeCache()) {}
75
76SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
77 : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorInfo()} {}
78
79#endif
80
81void SkGlyphRunListPainter::drawForBitmapDevice(
82 const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
83 const BitmapDevicePainter* bitmapDevice) {
84 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
85
86 // TODO: fStrikeCache is only used for GPU, and some compilers complain about it during the no
87 // gpu build. Remove when SkGlyphRunListPainter is split into GPU and CPU version.
88 (void)fStrikeCache;
89
90 const SkPaint& runPaint = glyphRunList.paint();
91 // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
92 // convert the lcd text into A8 text. The props communicates this to the scaler.
93 auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver())
94 ? fDeviceProps
95 : fBitmapFallbackProps;
96
97 SkPoint drawOrigin = glyphRunList.origin();
98 for (auto& glyphRun : glyphRunList) {
99 const SkFont& runFont = glyphRun.font();
100
101 fRejects.setSource(glyphRun.source());
102
103 if (SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) {
104
105 SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath(
106 runFont, runPaint, props, fScalerContextFlags);
107
108 auto strike = strikeSpec.findOrCreateStrike();
109
110 fDrawable.startSource(fRejects.source());
111 strike->prepareForPathDrawing(&fDrawable, &fRejects);
112 fRejects.flipRejectsToSource();
113
114 // The paint we draw paths with must have the same anti-aliasing state as the runFont
115 // allowing the paths to have the same edging as the glyph masks.
116 SkPaint pathPaint = runPaint;
117 pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
118
119 bitmapDevice->paintPaths(
120 &fDrawable, strikeSpec.strikeToSourceRatio(), drawOrigin, pathPaint);
121 }
122 if (!fRejects.source().empty()) {
123 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
124 runFont, runPaint, props, fScalerContextFlags, deviceMatrix);
125
126 auto strike = strikeSpec.findOrCreateStrike();
127
128 fDrawable.startBitmapDevice(
129 fRejects.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
130 strike->prepareForDrawingMasksCPU(&fDrawable);
131 bitmapDevice->paintMasks(&fDrawable, runPaint);
132 }
133
134 // TODO: have the mask stage above reject the glyphs that are too big, and handle the
135 // rejects in a more sophisticated stage.
136 }
137}
138
139#if SK_SUPPORT_GPU
140void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList,
141 const SkMatrix& drawMatrix,
142 const SkSurfaceProps& props,
143 bool contextSupportsDistanceFieldText,
144 const GrSDFTOptions& options,
145 SkGlyphRunPainterInterface* process) {
146
147 SkPoint origin = glyphRunList.origin();
148 const SkPaint& runPaint = glyphRunList.paint();
149 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
150
151 for (const auto& glyphRun : glyphRunList) {
152 fRejects.setSource(glyphRun.source());
153 const SkFont& runFont = glyphRun.font();
154
155
156 bool useSDFT = options.canDrawAsDistanceFields(
157 runPaint, runFont, drawMatrix, props, contextSupportsDistanceFieldText);
158
159 bool usePaths =
160 useSDFT ? false : SkStrikeSpec::ShouldDrawAsPath(runPaint, runFont, drawMatrix);
161
162 if (useSDFT) {
163 // Process SDFT - This should be the .009% case.
164 SkScalar minScale, maxScale;
165 SkStrikeSpec strikeSpec;
166 std::tie(strikeSpec, minScale, maxScale) =
167 SkStrikeSpec::MakeSDFT(runFont, runPaint, fDeviceProps, drawMatrix, options);
168
169 if (!strikeSpec.isEmpty()) {
170 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
171
172 fDrawable.startSource(fRejects.source());
173 strike->prepareForSDFTDrawing(&fDrawable, &fRejects);
174 fRejects.flipRejectsToSource();
175
176 if (process && !fDrawable.drawableIsEmpty()) {
177 // processSourceSDFT must be called even if there are no glyphs to make sure
178 // runs are set correctly.
179 process->processSourceSDFT(
180 fDrawable.drawable(), strikeSpec, runFont, minScale, maxScale);
181 }
182 }
183 }
184
185 if (!usePaths && !fRejects.source().empty()) {
186 // Process masks including ARGB - this should be the 99.99% case.
187
188 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
189 runFont, runPaint, fDeviceProps, fScalerContextFlags, drawMatrix);
190
191 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
192
193 SkPoint residual = fDrawable.startGPUDevice(
194 fRejects.source(), origin, drawMatrix, strike->roundingSpec());
195 strike->prepareForMaskDrawing(&fDrawable, &fRejects);
196 fRejects.flipRejectsToSource();
197
198 if (process && !fDrawable.drawableIsEmpty()) {
199 // processDeviceMasks must be called even if there are no glyphs to make sure runs
200 // are set correctly.
201 process->processDeviceMasks(fDrawable.drawable(), strikeSpec, residual);
202 }
203 }
204
205 // Glyphs are generated in different scales relative to the source space. Masks are drawn
206 // in device space, and SDFT and Paths are draw in a fixed constant space. The
207 // maxDimensionInSourceSpace is used to calculate the factor from strike space to source
208 // space.
209 SkScalar maxDimensionInSourceSpace = 0.0;
210 if (!fRejects.source().empty()) {
211 // Path case - handle big things without color and that have a path.
212 SkStrikeSpec strikeSpec = SkStrikeSpec::MakePath(
213 runFont, runPaint, fDeviceProps, fScalerContextFlags);
214
215 if (!strikeSpec.isEmpty()) {
216 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
217
218 fDrawable.startSource(fRejects.source());
219 strike->prepareForPathDrawing(&fDrawable, &fRejects);
220 fRejects.flipRejectsToSource();
221 maxDimensionInSourceSpace =
222 fRejects.rejectedMaxDimension() * strikeSpec.strikeToSourceRatio();
223
224 if (process && !fDrawable.drawableIsEmpty()) {
225 // processSourcePaths must be called even if there are no glyphs to make sure
226 // runs are set correctly.
227 process->processSourcePaths(fDrawable.drawable(), runFont, strikeSpec);
228 }
229 }
230 }
231
232 if (!fRejects.source().empty() && maxDimensionInSourceSpace != 0) {
233 // Draw of last resort. Scale the bitmap to the screen.
234 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeSourceFallback(
235 runFont, runPaint, fDeviceProps,
236 fScalerContextFlags, maxDimensionInSourceSpace);
237
238 if (!strikeSpec.isEmpty()) {
239 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
240
241 fDrawable.startSource(fRejects.source());
242 strike->prepareForMaskDrawing(&fDrawable, &fRejects);
243 fRejects.flipRejectsToSource();
244 SkASSERT(fRejects.source().empty());
245
246 if (process && !fDrawable.drawableIsEmpty()) {
247 process->processSourceMasks(fDrawable.drawable(), strikeSpec);
248 }
249 }
250 }
251 } // For all glyph runs
252}
253#endif // SK_SUPPORT_GPU
254
255auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
256 size_t size = 0;
257 for (const SkGlyphRun& run : glyphRunList) {
258 size = std::max(run.runSize(), size);
259 }
260 return ScopedBuffers(this, size);
261}
262
263SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, size_t size)
264 : fPainter{painter} {
265 fPainter->fDrawable.ensureSize(size);
266}
267
268SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
269 fPainter->fDrawable.reset();
270 fPainter->fRejects.reset();
271}
272
273SkVector SkGlyphPositionRoundingSpec::HalfAxisSampleFreq(
274 bool isSubpixel, SkAxisAlignment axisAlignment) {
275 if (!isSubpixel) {
276 return {SK_ScalarHalf, SK_ScalarHalf};
277 } else {
278 switch (axisAlignment) {
279 case kX_SkAxisAlignment:
280 return {SkPackedGlyphID::kSubpixelRound, SK_ScalarHalf};
281 case kY_SkAxisAlignment:
282 return {SK_ScalarHalf, SkPackedGlyphID::kSubpixelRound};
283 case kNone_SkAxisAlignment:
284 return {SkPackedGlyphID::kSubpixelRound, SkPackedGlyphID::kSubpixelRound};
285 }
286 }
287
288 // Some compilers need this.
289 return {0, 0};
290}
291
292SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionMask(
293 bool isSubpixel, SkAxisAlignment axisAlignment) {
294 return SkIPoint::Make((!isSubpixel || axisAlignment == kY_SkAxisAlignment) ? 0 : ~0,
295 (!isSubpixel || axisAlignment == kX_SkAxisAlignment) ? 0 : ~0);
296}
297
298SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionFieldMask(bool isSubpixel,
299 SkAxisAlignment axisAlignment) {
300 SkIPoint ignoreMask = IgnorePositionMask(isSubpixel, axisAlignment);
301 SkIPoint answer{ignoreMask.x() & SkPackedGlyphID::kXYFieldMask.x(),
302 ignoreMask.y() & SkPackedGlyphID::kXYFieldMask.y()};
303 return answer;
304}
305
306SkGlyphPositionRoundingSpec::SkGlyphPositionRoundingSpec(
307 bool isSubpixel,SkAxisAlignment axisAlignment)
308 : halfAxisSampleFreq{HalfAxisSampleFreq(isSubpixel, axisAlignment)}
309 , ignorePositionMask{IgnorePositionMask(isSubpixel, axisAlignment)}
310 , ignorePositionFieldMask {IgnorePositionFieldMask(isSubpixel, axisAlignment)}{ }
311