1 /*
2 * Copyright 2018 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/ganesh/text/GrAtlasManager.h"
9
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkEncodedImageFormat.h"
12 #include "src/base/SkAutoMalloc.h"
13 #include "src/codec/SkMasks.h"
14 #include "src/core/SkDistanceFieldGen.h"
15 #include "src/gpu/ganesh/GrImageInfo.h"
16 #include "src/gpu/ganesh/GrMeshDrawTarget.h"
17 #include "src/text/gpu/Glyph.h"
18 #include "src/text/gpu/GlyphVector.h"
19 #include "src/text/gpu/StrikeCache.h"
20
21 using Glyph = sktext::gpu::Glyph;
22 using MaskFormat = skgpu::MaskFormat;
23
GrAtlasManager(GrProxyProvider * proxyProvider,size_t maxTextureBytes,GrDrawOpAtlas::AllowMultitexturing allowMultitexturing,bool supportBilerpAtlas)24 GrAtlasManager::GrAtlasManager(GrProxyProvider* proxyProvider,
25 size_t maxTextureBytes,
26 GrDrawOpAtlas::AllowMultitexturing allowMultitexturing,
27 bool supportBilerpAtlas)
28 : fAllowMultitexturing{allowMultitexturing}
29 , fSupportBilerpAtlas{supportBilerpAtlas}
30 , fProxyProvider{proxyProvider}
31 , fCaps{fProxyProvider->refCaps()}
32 , fAtlasConfig{fCaps->maxTextureSize(), maxTextureBytes} { }
33
34 GrAtlasManager::~GrAtlasManager() = default;
35
freeAll()36 void GrAtlasManager::freeAll() {
37 for (int i = 0; i < skgpu::kMaskFormatCount; ++i) {
38 fAtlases[i] = nullptr;
39 }
40 }
41
hasGlyph(MaskFormat format,Glyph * glyph)42 bool GrAtlasManager::hasGlyph(MaskFormat format, Glyph* glyph) {
43 SkASSERT(glyph);
44 return this->getAtlas(format)->hasID(glyph->fAtlasLocator.plotLocator());
45 }
46
47 template <typename INT_TYPE>
expand_bits(INT_TYPE * dst,const uint8_t * src,int width,int height,int dstRowBytes,int srcRowBytes)48 static void expand_bits(INT_TYPE* dst,
49 const uint8_t* src,
50 int width,
51 int height,
52 int dstRowBytes,
53 int srcRowBytes) {
54 for (int y = 0; y < height; ++y) {
55 int rowWritesLeft = width;
56 const uint8_t* s = src;
57 INT_TYPE* d = dst;
58 while (rowWritesLeft > 0) {
59 unsigned mask = *s++;
60 for (int x = 7; x >= 0 && rowWritesLeft; --x, --rowWritesLeft) {
61 *d++ = (mask & (1 << x)) ? (INT_TYPE)(~0UL) : 0;
62 }
63 }
64 dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes);
65 src += srcRowBytes;
66 }
67 }
68
get_packed_glyph_image(const SkGlyph & glyph,int dstRB,MaskFormat expectedMaskFormat,void * dst)69 static void get_packed_glyph_image(
70 const SkGlyph& glyph, int dstRB, MaskFormat expectedMaskFormat, void* dst) {
71 const int width = glyph.width();
72 const int height = glyph.height();
73 const void* src = glyph.image();
74 SkASSERT(src != nullptr);
75
76 MaskFormat maskFormat = Glyph::FormatFromSkGlyph(glyph.maskFormat());
77 if (maskFormat == expectedMaskFormat) {
78 int srcRB = glyph.rowBytes();
79 // Notice this comparison is with the glyphs raw mask format, and not its MaskFormat.
80 if (glyph.maskFormat() != SkMask::kBW_Format) {
81 if (srcRB != dstRB) {
82 const int bbp = MaskFormatBytesPerPixel(expectedMaskFormat);
83 for (int y = 0; y < height; y++) {
84 memcpy(dst, src, width * bbp);
85 src = (const char*) src + srcRB;
86 dst = (char*) dst + dstRB;
87 }
88 } else {
89 memcpy(dst, src, dstRB * height);
90 }
91 } else {
92 // Handle 8-bit format by expanding the mask to the expected format.
93 const uint8_t* bits = reinterpret_cast<const uint8_t*>(src);
94 switch (expectedMaskFormat) {
95 case MaskFormat::kA8: {
96 uint8_t* bytes = reinterpret_cast<uint8_t*>(dst);
97 expand_bits(bytes, bits, width, height, dstRB, srcRB);
98 break;
99 }
100 case MaskFormat::kA565: {
101 uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst);
102 expand_bits(rgb565, bits, width, height, dstRB, srcRB);
103 break;
104 }
105 default:
106 SK_ABORT("Invalid MaskFormat");
107 }
108 }
109 } else if (maskFormat == MaskFormat::kA565 &&
110 expectedMaskFormat == MaskFormat::kARGB) {
111 // Convert if the glyph uses a 565 mask format since it is using LCD text rendering
112 // but the expected format is 8888 (will happen on macOS with Metal since that
113 // combination does not support 565).
114 static constexpr SkMasks masks{
115 {0b1111'1000'0000'0000, 11, 5}, // Red
116 {0b0000'0111'1110'0000, 5, 6}, // Green
117 {0b0000'0000'0001'1111, 0, 5}, // Blue
118 {0, 0, 0} // Alpha
119 };
120 constexpr int a565Bpp = MaskFormatBytesPerPixel(MaskFormat::kA565);
121 constexpr int argbBpp = MaskFormatBytesPerPixel(MaskFormat::kARGB);
122 char* dstRow = (char*)dst;
123 for (int y = 0; y < height; y++) {
124 dst = dstRow;
125 for (int x = 0; x < width; x++) {
126 uint16_t color565 = 0;
127 memcpy(&color565, src, a565Bpp);
128 uint32_t colorRGBA = GrColorPackRGBA(masks.getRed(color565),
129 masks.getGreen(color565),
130 masks.getBlue(color565),
131 0xFF);
132 memcpy(dst, &colorRGBA, argbBpp);
133 src = (char*)src + a565Bpp;
134 dst = (char*)dst + argbBpp;
135 }
136 dstRow += dstRB;
137 }
138 } else {
139 SkUNREACHABLE;
140 }
141 }
142
143 // returns true if glyph successfully added to texture atlas, false otherwise.
addGlyphToAtlas(const SkGlyph & skGlyph,Glyph * glyph,int srcPadding,GrResourceProvider * resourceProvider,GrDeferredUploadTarget * uploadTarget)144 GrDrawOpAtlas::ErrorCode GrAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
145 Glyph* glyph,
146 int srcPadding,
147 GrResourceProvider* resourceProvider,
148 GrDeferredUploadTarget* uploadTarget) {
149 #if !defined(SK_DISABLE_SDF_TEXT)
150 SkASSERT(0 <= srcPadding && srcPadding <= SK_DistanceFieldInset);
151 #else
152 SkASSERT(0 <= srcPadding);
153 #endif
154
155 if (skGlyph.image() == nullptr) {
156 return GrDrawOpAtlas::ErrorCode::kError;
157 }
158 SkASSERT(glyph != nullptr);
159
160 MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
161 MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
162 int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
163
164 int padding;
165 switch (srcPadding) {
166 case 0:
167 // The direct mask/image case.
168 padding = 0;
169 if (fSupportBilerpAtlas) {
170 // Force direct masks (glyph with no padding) to have padding.
171 padding = 1;
172 srcPadding = 1;
173 }
174 break;
175 case 1:
176 // The transformed mask/image case.
177 padding = 1;
178 break;
179 #if !defined(SK_DISABLE_SDF_TEXT)
180 case SK_DistanceFieldInset:
181 // The SDFT case.
182 // If the srcPadding == SK_DistanceFieldInset (SDFT case) then the padding is built
183 // into the image on the glyph; no extra padding needed.
184 // TODO: can the SDFT glyph image in the cache be reduced by the padding?
185 padding = 0;
186 break;
187 #endif
188 default:
189 // The padding is not one of the know forms.
190 return GrDrawOpAtlas::ErrorCode::kError;
191 }
192
193 const int width = skGlyph.width() + 2*padding;
194 const int height = skGlyph.height() + 2*padding;
195 int rowBytes = width * bytesPerPixel;
196 size_t size = height * rowBytes;
197
198 // Temporary storage for normalizing glyph image.
199 SkAutoSMalloc<1024> storage(size);
200 void* dataPtr = storage.get();
201 if (padding > 0) {
202 sk_bzero(dataPtr, size);
203 // Advance in one row and one column.
204 dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel;
205 }
206
207 get_packed_glyph_image(skGlyph, rowBytes, expectedMaskFormat, dataPtr);
208
209 auto errorCode = this->addToAtlas(resourceProvider,
210 uploadTarget,
211 expectedMaskFormat,
212 width,
213 height,
214 storage.get(),
215 &glyph->fAtlasLocator);
216
217 if (errorCode == GrDrawOpAtlas::ErrorCode::kSucceeded) {
218 glyph->fAtlasLocator.insetSrc(srcPadding);
219 }
220
221 return errorCode;
222 }
223
224 // add to texture atlas that matches this format
addToAtlas(GrResourceProvider * resourceProvider,GrDeferredUploadTarget * target,MaskFormat format,int width,int height,const void * image,skgpu::AtlasLocator * atlasLocator)225 GrDrawOpAtlas::ErrorCode GrAtlasManager::addToAtlas(GrResourceProvider* resourceProvider,
226 GrDeferredUploadTarget* target,
227 MaskFormat format,
228 int width, int height, const void* image,
229 skgpu::AtlasLocator* atlasLocator) {
230 return this->getAtlas(format)->addToAtlas(resourceProvider, target, width, height, image,
231 atlasLocator);
232 }
233
addGlyphToBulkAndSetUseToken(skgpu::BulkUsePlotUpdater * updater,MaskFormat format,Glyph * glyph,skgpu::AtlasToken token)234 void GrAtlasManager::addGlyphToBulkAndSetUseToken(skgpu::BulkUsePlotUpdater* updater,
235 MaskFormat format, Glyph* glyph,
236 skgpu::AtlasToken token) {
237 SkASSERT(glyph);
238 if (updater->add(glyph->fAtlasLocator)) {
239 this->getAtlas(format)->setLastUseToken(glyph->fAtlasLocator, token);
240 }
241 }
242
243 #ifdef SK_DEBUG
244 #include "include/gpu/GrDirectContext.h"
245 #include "src/gpu/ganesh/GrDirectContextPriv.h"
246 #include "src/gpu/ganesh/GrSurfaceProxy.h"
247 #include "src/gpu/ganesh/GrTextureProxy.h"
248 #include "src/gpu/ganesh/SurfaceContext.h"
249
250 #include "include/core/SkBitmap.h"
251 #include "include/core/SkImageEncoder.h"
252 #include "include/core/SkStream.h"
253 #include <stdio.h>
254
255 /**
256 * Write the contents of the surface proxy to a PNG. Returns true if successful.
257 * @param filename Full path to desired file
258 */
save_pixels(GrDirectContext * dContext,GrSurfaceProxyView view,GrColorType colorType,const char * filename)259 static bool save_pixels(GrDirectContext* dContext, GrSurfaceProxyView view, GrColorType colorType,
260 const char* filename) {
261 if (!view.proxy()) {
262 return false;
263 }
264
265 auto ii = SkImageInfo::Make(view.proxy()->dimensions(), kRGBA_8888_SkColorType,
266 kPremul_SkAlphaType);
267 SkBitmap bm;
268 if (!bm.tryAllocPixels(ii)) {
269 return false;
270 }
271
272 auto sContext = dContext->priv().makeSC(std::move(view),
273 {colorType, kUnknown_SkAlphaType, nullptr});
274 if (!sContext || !sContext->asTextureProxy()) {
275 return false;
276 }
277
278 bool result = sContext->readPixels(dContext, bm.pixmap(), {0, 0});
279 if (!result) {
280 SkDebugf("------ failed to read pixels for %s\n", filename);
281 return false;
282 }
283
284 // remove any previous version of this file
285 remove(filename);
286
287 SkFILEWStream file(filename);
288 if (!file.isValid()) {
289 SkDebugf("------ failed to create file: %s\n", filename);
290 remove(filename); // remove any partial file
291 return false;
292 }
293
294 if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
295 SkDebugf("------ failed to encode %s\n", filename);
296 remove(filename); // remove any partial file
297 return false;
298 }
299
300 return true;
301 }
302
dump(GrDirectContext * context) const303 void GrAtlasManager::dump(GrDirectContext* context) const {
304 static int gDumpCount = 0;
305 for (int i = 0; i < skgpu::kMaskFormatCount; ++i) {
306 if (fAtlases[i]) {
307 const GrSurfaceProxyView* views = fAtlases[i]->getViews();
308 for (uint32_t pageIdx = 0; pageIdx < fAtlases[i]->numActivePages(); ++pageIdx) {
309 SkASSERT(views[pageIdx].proxy());
310 SkString filename;
311 #ifdef SK_BUILD_FOR_ANDROID
312 filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
313 #else
314 filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
315 #endif
316 SkColorType ct = MaskFormatToColorType(AtlasIndexToMaskFormat(i));
317 save_pixels(context, views[pageIdx], SkColorTypeToGrColorType(ct),
318 filename.c_str());
319 }
320 }
321 }
322 ++gDumpCount;
323 }
324 #endif
325
setAtlasDimensionsToMinimum_ForTesting()326 void GrAtlasManager::setAtlasDimensionsToMinimum_ForTesting() {
327 // Delete any old atlases.
328 // This should be safe to do as long as we are not in the middle of a flush.
329 for (int i = 0; i < skgpu::kMaskFormatCount; i++) {
330 fAtlases[i] = nullptr;
331 }
332
333 // Set all the atlas sizes to 1x1 plot each.
334 new (&fAtlasConfig) GrDrawOpAtlasConfig{};
335 }
336
initAtlas(MaskFormat format)337 bool GrAtlasManager::initAtlas(MaskFormat format) {
338 int index = MaskFormatToAtlasIndex(format);
339 if (fAtlases[index] == nullptr) {
340 SkColorType colorType = MaskFormatToColorType(format);
341 GrColorType grColorType = SkColorTypeToGrColorType(colorType);
342 SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format);
343 SkISize plotDimensions = fAtlasConfig.plotDimensions(format);
344
345 const GrBackendFormat backendFormat =
346 fCaps->getDefaultBackendFormat(grColorType, GrRenderable::kNo);
347
348 fAtlases[index] = GrDrawOpAtlas::Make(fProxyProvider, backendFormat,
349 GrColorTypeToSkColorType(grColorType),
350 GrColorTypeBytesPerPixel(grColorType),
351 atlasDimensions.width(), atlasDimensions.height(),
352 plotDimensions.width(), plotDimensions.height(),
353 this,
354 fAllowMultitexturing,
355 nullptr,
356 /*label=*/"TextAtlas");
357 if (!fAtlases[index]) {
358 return false;
359 }
360 }
361 return true;
362 }
363
364 ////////////////////////////////////////////////////////////////////////////////////////////////
365
366 namespace sktext::gpu {
367
regenerateAtlas(int begin,int end,MaskFormat maskFormat,int srcPadding,GrMeshDrawTarget * target)368 std::tuple<bool, int> GlyphVector::regenerateAtlas(int begin, int end,
369 MaskFormat maskFormat,
370 int srcPadding,
371 GrMeshDrawTarget* target) {
372 GrAtlasManager* atlasManager = target->atlasManager();
373 GrDeferredUploadTarget* uploadTarget = target->deferredUploadTarget();
374
375 uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
376
377 this->packedGlyphIDToGlyph(target->strikeCache());
378
379 if (fAtlasGeneration != currentAtlasGen) {
380 // Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
381 // is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls..
382 fBulkUseUpdater.reset();
383
384 SkBulkGlyphMetricsAndImages metricsAndImages{fTextStrike->strikeSpec()};
385
386 // Update the atlas information in the GrStrike.
387 auto tokenTracker = uploadTarget->tokenTracker();
388 auto glyphs = fGlyphs.subspan(begin, end - begin);
389 int glyphsPlacedInAtlas = 0;
390 bool success = true;
391 for (const Variant& variant : glyphs) {
392 Glyph* gpuGlyph = variant.glyph;
393 SkASSERT(gpuGlyph != nullptr);
394
395 if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
396 const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID);
397 auto code = atlasManager->addGlyphToAtlas(
398 skGlyph, gpuGlyph, srcPadding, target->resourceProvider(), uploadTarget);
399 if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
400 success = code != GrDrawOpAtlas::ErrorCode::kError;
401 break;
402 }
403 }
404 atlasManager->addGlyphToBulkAndSetUseToken(
405 &fBulkUseUpdater, maskFormat, gpuGlyph,
406 tokenTracker->nextDrawToken());
407 glyphsPlacedInAtlas++;
408 }
409
410 // Update atlas generation if there are no more glyphs to put in the atlas.
411 if (success && begin + glyphsPlacedInAtlas == SkCount(fGlyphs)) {
412 // Need to get the freshest value of the atlas' generation because
413 // updateTextureCoordinates may have changed it.
414 fAtlasGeneration = atlasManager->atlasGeneration(maskFormat);
415 }
416
417 return {success, glyphsPlacedInAtlas};
418 } else {
419 // The atlas hasn't changed, so our texture coordinates are still valid.
420 if (end == SkCount(fGlyphs)) {
421 // The atlas hasn't changed and the texture coordinates are all still valid. Update
422 // all the plots used to the new use token.
423 atlasManager->setUseTokenBulk(fBulkUseUpdater,
424 uploadTarget->tokenTracker()->nextDrawToken(),
425 maskFormat);
426 }
427 return {true, end - begin};
428 }
429 }
430
431 } // namespace sktext::gpu
432