/src/skia/src/gpu/graphite/text/TextAtlasManager.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2022 Google LLC |
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/graphite/text/TextAtlasManager.h" |
9 | | |
10 | | #include "include/core/SkColorSpace.h" |
11 | | #include "include/gpu/graphite/Recorder.h" |
12 | | #include "src/base/SkAutoMalloc.h" |
13 | | #include "src/core/SkDistanceFieldGen.h" |
14 | | #include "src/core/SkMasks.h" |
15 | | #include "src/gpu/graphite/AtlasProvider.h" |
16 | | #include "src/gpu/graphite/DrawAtlas.h" |
17 | | #include "src/gpu/graphite/RecorderPriv.h" |
18 | | #include "src/gpu/graphite/TextureProxy.h" |
19 | | #include "src/sksl/SkSLUtil.h" |
20 | | #include "src/text/gpu/Glyph.h" |
21 | | #include "src/text/gpu/GlyphVector.h" |
22 | | #include "src/text/gpu/StrikeCache.h" |
23 | | |
24 | | using Glyph = sktext::gpu::Glyph; |
25 | | |
26 | | namespace skgpu::graphite { |
27 | | |
28 | | TextAtlasManager::TextAtlasManager(Recorder* recorder) |
29 | | : fRecorder(recorder) |
30 | | , fSupportBilerpAtlas{recorder->priv().caps()->supportBilerpFromGlyphAtlas()} |
31 | | , fAtlasConfig{recorder->priv().caps()->maxTextureSize(), |
32 | 0 | recorder->priv().caps()->glyphCacheTextureMaximumBytes()} { |
33 | 0 | if (!recorder->priv().caps()->allowMultipleAtlasTextures() || |
34 | | // multitexturing supported only if range can represent the index + texcoords fully |
35 | 0 | !(recorder->priv().caps()->shaderCaps()->fFloatIs32Bits || |
36 | 0 | recorder->priv().caps()->shaderCaps()->fIntegerSupport)) { |
37 | 0 | fAllowMultitexturing = DrawAtlas::AllowMultitexturing::kNo; |
38 | 0 | } else { |
39 | 0 | fAllowMultitexturing = DrawAtlas::AllowMultitexturing::kYes; |
40 | 0 | } |
41 | 0 | } |
42 | | |
43 | 0 | TextAtlasManager::~TextAtlasManager() = default; |
44 | | |
45 | 0 | void TextAtlasManager::freeAll() { |
46 | 0 | for (int i = 0; i < kMaskFormatCount; ++i) { |
47 | 0 | fAtlases[i] = nullptr; |
48 | 0 | } |
49 | 0 | } |
50 | | |
51 | 0 | bool TextAtlasManager::hasGlyph(MaskFormat format, Glyph* glyph) { |
52 | 0 | SkASSERT(glyph); |
53 | 0 | return this->getAtlas(format)->hasID(glyph->fAtlasLocator.plotLocator()); |
54 | 0 | } Unexecuted instantiation: skgpu::graphite::TextAtlasManager::hasGlyph(skgpu::MaskFormat, sktext::gpu::Glyph*) Unexecuted instantiation: skgpu::graphite::TextAtlasManager::hasGlyph(skgpu::MaskFormat, sktext::gpu::Glyph*) |
55 | | |
56 | | template <typename INT_TYPE> |
57 | | static void expand_bits(INT_TYPE* dst, |
58 | | const uint8_t* src, |
59 | | int width, |
60 | | int height, |
61 | | int dstRowBytes, |
62 | 0 | int srcRowBytes) { |
63 | 0 | for (int y = 0; y < height; ++y) { |
64 | 0 | int rowWritesLeft = width; |
65 | 0 | const uint8_t* s = src; |
66 | 0 | INT_TYPE* d = dst; |
67 | 0 | while (rowWritesLeft > 0) { |
68 | 0 | unsigned mask = *s++; |
69 | 0 | for (int x = 7; x >= 0 && rowWritesLeft; --x, --rowWritesLeft) { |
70 | 0 | *d++ = (mask & (1 << x)) ? (INT_TYPE)(~0UL) : 0; |
71 | 0 | } |
72 | 0 | } |
73 | 0 | dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes); |
74 | 0 | src += srcRowBytes; |
75 | 0 | } |
76 | 0 | } Unexecuted instantiation: TextAtlasManager.cpp:void skgpu::graphite::expand_bits<unsigned char>(unsigned char*, unsigned char const*, int, int, int, int) Unexecuted instantiation: TextAtlasManager.cpp:void skgpu::graphite::expand_bits<unsigned short>(unsigned short*, unsigned char const*, int, int, int, int) |
77 | | |
78 | | static void get_packed_glyph_image( |
79 | 0 | const SkGlyph& glyph, int dstRB, MaskFormat expectedMaskFormat, void* dst) { |
80 | 0 | const int width = glyph.width(); |
81 | 0 | const int height = glyph.height(); |
82 | 0 | const void* src = glyph.image(); |
83 | 0 | SkASSERT(src != nullptr); |
84 | |
|
85 | 0 | MaskFormat maskFormat = Glyph::FormatFromSkGlyph(glyph.maskFormat()); |
86 | 0 | if (maskFormat == expectedMaskFormat) { |
87 | 0 | int srcRB = glyph.rowBytes(); |
88 | | // Notice this comparison is with the glyphs raw mask format, and not its MaskFormat. |
89 | 0 | if (glyph.maskFormat() != SkMask::kBW_Format) { |
90 | 0 | if (srcRB != dstRB) { |
91 | 0 | const int bbp = MaskFormatBytesPerPixel(expectedMaskFormat); |
92 | 0 | for (int y = 0; y < height; y++) { |
93 | 0 | memcpy(dst, src, width * bbp); |
94 | 0 | src = (const char*) src + srcRB; |
95 | 0 | dst = (char*) dst + dstRB; |
96 | 0 | } |
97 | 0 | } else { |
98 | 0 | memcpy(dst, src, dstRB * height); |
99 | 0 | } |
100 | 0 | } else { |
101 | | // Handle 8-bit format by expanding the mask to the expected format. |
102 | 0 | const uint8_t* bits = reinterpret_cast<const uint8_t*>(src); |
103 | 0 | switch (expectedMaskFormat) { |
104 | 0 | case MaskFormat::kA8: { |
105 | 0 | uint8_t* bytes = reinterpret_cast<uint8_t*>(dst); |
106 | 0 | expand_bits(bytes, bits, width, height, dstRB, srcRB); |
107 | 0 | break; |
108 | 0 | } |
109 | 0 | case MaskFormat::kA565: { |
110 | 0 | uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst); |
111 | 0 | expand_bits(rgb565, bits, width, height, dstRB, srcRB); |
112 | 0 | break; |
113 | 0 | } |
114 | 0 | default: |
115 | 0 | SK_ABORT("Invalid MaskFormat"); |
116 | 0 | } |
117 | 0 | } |
118 | 0 | } else if (maskFormat == MaskFormat::kA565 && |
119 | 0 | expectedMaskFormat == MaskFormat::kARGB) { |
120 | | // Convert if the glyph uses a 565 mask format since it is using LCD text rendering |
121 | | // but the expected format is 8888 (will happen on Intel MacOS with Metal since that |
122 | | // combination does not support 565). |
123 | 0 | static constexpr SkMasks masks{ |
124 | 0 | {0b1111'1000'0000'0000, 11, 5}, // Red |
125 | 0 | {0b0000'0111'1110'0000, 5, 6}, // Green |
126 | 0 | {0b0000'0000'0001'1111, 0, 5}, // Blue |
127 | 0 | {0, 0, 0} // Alpha |
128 | 0 | }; |
129 | 0 | constexpr int a565Bpp = MaskFormatBytesPerPixel(MaskFormat::kA565); |
130 | 0 | constexpr int argbBpp = MaskFormatBytesPerPixel(MaskFormat::kARGB); |
131 | 0 | constexpr bool kBGRAIsNative = kN32_SkColorType == kBGRA_8888_SkColorType; |
132 | 0 | char* dstRow = (char*)dst; |
133 | 0 | for (int y = 0; y < height; y++) { |
134 | 0 | dst = dstRow; |
135 | 0 | for (int x = 0; x < width; x++) { |
136 | 0 | uint16_t color565 = 0; |
137 | 0 | memcpy(&color565, src, a565Bpp); |
138 | 0 | uint32_t color8888; |
139 | | // On Windows (and possibly others), font data is stored as BGR. |
140 | | // So we need to swizzle the data to reflect that. |
141 | 0 | if (kBGRAIsNative) { |
142 | 0 | color8888 = masks.getBlue(color565) | |
143 | 0 | (masks.getGreen(color565) << 8) | |
144 | 0 | (masks.getRed(color565) << 16) | |
145 | 0 | (0xFF << 24); |
146 | 0 | } else { |
147 | 0 | color8888 = masks.getRed(color565) | |
148 | 0 | (masks.getGreen(color565) << 8) | |
149 | 0 | (masks.getBlue(color565) << 16) | |
150 | 0 | (0xFF << 24); |
151 | 0 | } |
152 | 0 | memcpy(dst, &color8888, argbBpp); |
153 | 0 | src = (const char*)src + a565Bpp; |
154 | 0 | dst = ( char*)dst + argbBpp; |
155 | 0 | } |
156 | 0 | dstRow += dstRB; |
157 | 0 | } |
158 | 0 | } else { |
159 | 0 | SkUNREACHABLE; |
160 | 0 | } |
161 | 0 | } Unexecuted instantiation: TextAtlasManager.cpp:skgpu::graphite::get_packed_glyph_image(SkGlyph const&, int, skgpu::MaskFormat, void*) Unexecuted instantiation: TextAtlasManager.cpp:skgpu::graphite::get_packed_glyph_image(SkGlyph const&, int, skgpu::MaskFormat, void*) |
162 | | |
163 | 0 | MaskFormat TextAtlasManager::resolveMaskFormat(MaskFormat format) const { |
164 | 0 | if (MaskFormat::kA565 == format && |
165 | 0 | !fRecorder->priv().caps()->getDefaultSampledTextureInfo(kRGB_565_SkColorType, |
166 | 0 | /*mipmapped=*/Mipmapped::kNo, |
167 | 0 | Protected::kNo, |
168 | 0 | Renderable::kNo).isValid()) { |
169 | 0 | format = MaskFormat::kARGB; |
170 | 0 | } |
171 | 0 | return format; |
172 | 0 | } |
173 | | |
174 | | // Returns kSucceeded if glyph successfully added to texture atlas, kTryAgain if a RenderPassTask |
175 | | // needs to be snapped before adding the glyph, and kError if it can't be added at all. |
176 | | DrawAtlas::ErrorCode TextAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph, |
177 | | Glyph* glyph, |
178 | 0 | int srcPadding) { |
179 | 0 | #if !defined(SK_DISABLE_SDF_TEXT) |
180 | 0 | SkASSERT(0 <= srcPadding && srcPadding <= SK_DistanceFieldInset); |
181 | | #else |
182 | | SkASSERT(0 <= srcPadding); |
183 | | #endif |
184 | |
|
185 | 0 | if (skGlyph.image() == nullptr) { |
186 | 0 | return DrawAtlas::ErrorCode::kError; |
187 | 0 | } |
188 | 0 | SkASSERT(glyph != nullptr); |
189 | |
|
190 | 0 | MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat()); |
191 | 0 | MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat); |
192 | 0 | int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat); |
193 | |
|
194 | 0 | int padding; |
195 | 0 | switch (srcPadding) { |
196 | 0 | case 0: |
197 | | // The direct mask/image case. |
198 | 0 | padding = 0; |
199 | 0 | if (fSupportBilerpAtlas) { |
200 | | // Force direct masks (glyph with no padding) to have padding. |
201 | 0 | padding = 1; |
202 | 0 | srcPadding = 1; |
203 | 0 | } |
204 | 0 | break; |
205 | 0 | case 1: |
206 | | // The transformed mask/image case. |
207 | 0 | padding = 1; |
208 | 0 | break; |
209 | 0 | #if !defined(SK_DISABLE_SDF_TEXT) |
210 | 0 | case SK_DistanceFieldInset: |
211 | | // The SDFT case. |
212 | | // If the srcPadding == SK_DistanceFieldInset (SDFT case) then the padding is built |
213 | | // into the image on the glyph; no extra padding needed. |
214 | | // TODO: can the SDFT glyph image in the cache be reduced by the padding? |
215 | 0 | padding = 0; |
216 | 0 | break; |
217 | 0 | #endif |
218 | 0 | default: |
219 | | // The padding is not one of the know forms. |
220 | 0 | return DrawAtlas::ErrorCode::kError; |
221 | 0 | } |
222 | | |
223 | 0 | const int width = skGlyph.width() + 2*padding; |
224 | 0 | const int height = skGlyph.height() + 2*padding; |
225 | 0 | int rowBytes = width * bytesPerPixel; |
226 | 0 | size_t size = height * rowBytes; |
227 | | |
228 | | // Temporary storage for normalizing glyph image. |
229 | 0 | SkAutoSMalloc<1024> storage(size); |
230 | 0 | void* dataPtr = storage.get(); |
231 | 0 | if (padding > 0) { |
232 | 0 | sk_bzero(dataPtr, size); |
233 | | // Advance in one row and one column. |
234 | 0 | dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel; |
235 | 0 | } |
236 | |
|
237 | 0 | get_packed_glyph_image(skGlyph, rowBytes, expectedMaskFormat, dataPtr); |
238 | |
|
239 | 0 | DrawAtlas* atlas = this->getAtlas(expectedMaskFormat); |
240 | 0 | auto errorCode = atlas->addToAtlas(fRecorder, |
241 | 0 | width, |
242 | 0 | height, |
243 | 0 | storage.get(), |
244 | 0 | &glyph->fAtlasLocator); |
245 | |
|
246 | 0 | if (errorCode == DrawAtlas::ErrorCode::kSucceeded) { |
247 | 0 | glyph->fAtlasLocator.insetSrc(srcPadding); |
248 | 0 | } |
249 | |
|
250 | 0 | return errorCode; |
251 | 0 | } Unexecuted instantiation: skgpu::graphite::TextAtlasManager::addGlyphToAtlas(SkGlyph const&, sktext::gpu::Glyph*, int) Unexecuted instantiation: skgpu::graphite::TextAtlasManager::addGlyphToAtlas(SkGlyph const&, sktext::gpu::Glyph*, int) |
252 | | |
253 | 0 | bool TextAtlasManager::recordUploads(DrawContext* dc) { |
254 | 0 | for (int i = 0; i < skgpu::kMaskFormatCount; i++) { |
255 | 0 | if (fAtlases[i] && !fAtlases[i]->recordUploads(dc, fRecorder)) { |
256 | 0 | return false; |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | 0 | return true; |
261 | 0 | } |
262 | | |
263 | | void TextAtlasManager::addGlyphToBulkAndSetUseToken(BulkUsePlotUpdater* updater, |
264 | | MaskFormat format, |
265 | | Glyph* glyph, |
266 | 0 | AtlasToken token) { |
267 | 0 | SkASSERT(glyph); |
268 | 0 | if (updater->add(glyph->fAtlasLocator)) { |
269 | 0 | this->getAtlas(format)->setLastUseToken(glyph->fAtlasLocator, token); |
270 | 0 | } |
271 | 0 | } Unexecuted instantiation: skgpu::graphite::TextAtlasManager::addGlyphToBulkAndSetUseToken(skgpu::BulkUsePlotUpdater*, skgpu::MaskFormat, sktext::gpu::Glyph*, skgpu::AtlasToken) Unexecuted instantiation: skgpu::graphite::TextAtlasManager::addGlyphToBulkAndSetUseToken(skgpu::BulkUsePlotUpdater*, skgpu::MaskFormat, sktext::gpu::Glyph*, skgpu::AtlasToken) |
272 | | |
273 | 0 | void TextAtlasManager::setAtlasDimensionsToMinimum_ForTesting() { |
274 | | // Delete any old atlases. |
275 | | // This should be safe to do as long as we are not in the middle of a flush. |
276 | 0 | for (int i = 0; i < skgpu::kMaskFormatCount; i++) { |
277 | 0 | fAtlases[i] = nullptr; |
278 | 0 | } |
279 | | |
280 | | // Set all the atlas sizes to 1x1 plot each. |
281 | 0 | new (&fAtlasConfig) DrawAtlasConfig{2048, 0}; |
282 | 0 | } |
283 | | |
284 | 0 | bool TextAtlasManager::initAtlas(MaskFormat format) { |
285 | 0 | int index = MaskFormatToAtlasIndex(format); |
286 | 0 | if (fAtlases[index] == nullptr) { |
287 | 0 | SkColorType colorType = MaskFormatToColorType(format); |
288 | 0 | SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format); |
289 | 0 | SkISize plotDimensions = fAtlasConfig.plotDimensions(format); |
290 | 0 | fAtlases[index] = DrawAtlas::Make(colorType, |
291 | 0 | SkColorTypeBytesPerPixel(colorType), |
292 | 0 | atlasDimensions.width(), atlasDimensions.height(), |
293 | 0 | plotDimensions.width(), plotDimensions.height(), |
294 | 0 | /*generationCounter=*/this, |
295 | 0 | fAllowMultitexturing, |
296 | 0 | DrawAtlas::UseStorageTextures::kNo, |
297 | 0 | /*evictor=*/nullptr, |
298 | 0 | /*label=*/"TextAtlas"); |
299 | 0 | if (!fAtlases[index]) { |
300 | 0 | return false; |
301 | 0 | } |
302 | 0 | } |
303 | 0 | return true; |
304 | 0 | } |
305 | | |
306 | 0 | void TextAtlasManager::compact(bool forceCompact) { |
307 | 0 | auto tokenTracker = fRecorder->priv().tokenTracker(); |
308 | 0 | for (int i = 0; i < kMaskFormatCount; ++i) { |
309 | 0 | if (fAtlases[i]) { |
310 | 0 | fAtlases[i]->compact(tokenTracker->nextFlushToken(), forceCompact); |
311 | 0 | } |
312 | 0 | } |
313 | 0 | } |
314 | | |
315 | | } // namespace skgpu::graphite |
316 | | |
317 | | //////////////////////////////////////////////////////////////////////////////////////////////// |
318 | | |
319 | | namespace sktext::gpu { |
320 | | |
321 | | using DrawAtlas = skgpu::graphite::DrawAtlas; |
322 | | |
323 | | std::tuple<bool, int> GlyphVector::regenerateAtlasForGraphite(int begin, |
324 | | int end, |
325 | | skgpu::MaskFormat maskFormat, |
326 | | int srcPadding, |
327 | 0 | skgpu::graphite::Recorder* recorder) { |
328 | 0 | auto atlasManager = recorder->priv().atlasProvider()->textAtlasManager(); |
329 | 0 | auto tokenTracker = recorder->priv().tokenTracker(); |
330 | | |
331 | | // TODO: this is not a great place for this -- need a better way to init atlases when needed |
332 | 0 | unsigned int numActiveProxies; |
333 | 0 | const sk_sp<skgpu::graphite::TextureProxy>* proxies = |
334 | 0 | atlasManager->getProxies(maskFormat, &numActiveProxies); |
335 | 0 | if (!proxies) { |
336 | 0 | SkDebugf("Could not allocate backing texture for atlas\n"); |
337 | 0 | return {false, 0}; |
338 | 0 | } |
339 | | |
340 | 0 | uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat); |
341 | |
|
342 | 0 | this->packedGlyphIDToGlyph(recorder->priv().strikeCache()); |
343 | |
|
344 | 0 | if (fAtlasGeneration != currentAtlasGen) { |
345 | | // Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration |
346 | | // is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls.. |
347 | 0 | fBulkUseUpdater.reset(); |
348 | |
|
349 | 0 | SkBulkGlyphMetricsAndImages metricsAndImages{fTextStrike->strikeSpec()}; |
350 | | |
351 | | // Update the atlas information in the GrStrike. |
352 | 0 | auto glyphs = fGlyphs.subspan(begin, end - begin); |
353 | 0 | int glyphsPlacedInAtlas = 0; |
354 | 0 | bool success = true; |
355 | 0 | for (const Variant& variant : glyphs) { |
356 | 0 | Glyph* gpuGlyph = variant.glyph; |
357 | 0 | SkASSERT(gpuGlyph != nullptr); |
358 | |
|
359 | 0 | if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) { |
360 | 0 | const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID); |
361 | 0 | auto code = atlasManager->addGlyphToAtlas(skGlyph, gpuGlyph, srcPadding); |
362 | 0 | if (code != DrawAtlas::ErrorCode::kSucceeded) { |
363 | 0 | success = code != DrawAtlas::ErrorCode::kError; |
364 | 0 | break; |
365 | 0 | } |
366 | 0 | } |
367 | 0 | atlasManager->addGlyphToBulkAndSetUseToken( |
368 | 0 | &fBulkUseUpdater, maskFormat, gpuGlyph, |
369 | 0 | tokenTracker->nextFlushToken()); |
370 | 0 | glyphsPlacedInAtlas++; |
371 | 0 | } |
372 | | |
373 | | // Update atlas generation if there are no more glyphs to put in the atlas. |
374 | 0 | if (success && begin + glyphsPlacedInAtlas == SkCount(fGlyphs)) { |
375 | | // Need to get the freshest value of the atlas' generation because |
376 | | // updateTextureCoordinates may have changed it. |
377 | 0 | fAtlasGeneration = atlasManager->atlasGeneration(maskFormat); |
378 | 0 | } |
379 | |
|
380 | 0 | return {success, glyphsPlacedInAtlas}; |
381 | 0 | } else { |
382 | | // The atlas hasn't changed, so our texture coordinates are still valid. |
383 | 0 | if (end == SkCount(fGlyphs)) { |
384 | | // The atlas hasn't changed and the texture coordinates are all still valid. Update |
385 | | // all the plots used to the new use token. |
386 | 0 | atlasManager->setUseTokenBulk(fBulkUseUpdater, |
387 | 0 | tokenTracker->nextFlushToken(), |
388 | 0 | maskFormat); |
389 | 0 | } |
390 | 0 | return {true, end - begin}; |
391 | 0 | } |
392 | 0 | } Unexecuted instantiation: sktext::gpu::GlyphVector::regenerateAtlasForGraphite(int, int, skgpu::MaskFormat, int, skgpu::graphite::Recorder*) Unexecuted instantiation: sktext::gpu::GlyphVector::regenerateAtlasForGraphite(int, int, skgpu::MaskFormat, int, skgpu::graphite::Recorder*) |
393 | | |
394 | | } // namespace sktext::gpu |