2 * Copyright 2018 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "src/gpu/ganesh/text/GrAtlasManager.h"
10 #include "include/core/SkColorSpace.h"
11 #include "src/codec/SkMasks.h"
12 #include "src/core/SkAutoMalloc.h"
13 #include "src/core/SkDistanceFieldGen.h"
14 #include "src/gpu/ganesh/GrImageInfo.h"
15 #include "src/gpu/ganesh/GrMeshDrawTarget.h"
16 #include "src/text/gpu/Glyph.h"
17 #include "src/text/gpu/GlyphVector.h"
18 #include "src/text/gpu/StrikeCache.h"
20 using Glyph = sktext::gpu::Glyph;
21 using MaskFormat = skgpu::MaskFormat;
23 GrAtlasManager::GrAtlasManager(GrProxyProvider* proxyProvider,
24 size_t maxTextureBytes,
25 GrDrawOpAtlas::AllowMultitexturing allowMultitexturing,
26 bool supportBilerpAtlas)
27 : fAllowMultitexturing{allowMultitexturing}
28 , fSupportBilerpAtlas{supportBilerpAtlas}
29 , fProxyProvider{proxyProvider}
30 , fCaps{fProxyProvider->refCaps()}
31 , fAtlasConfig{fCaps->maxTextureSize(), maxTextureBytes} { }
33 GrAtlasManager::~GrAtlasManager() = default;
35 void GrAtlasManager::freeAll() {
36 for (int i = 0; i < skgpu::kMaskFormatCount; ++i) {
37 fAtlases[i] = nullptr;
41 bool GrAtlasManager::hasGlyph(MaskFormat format, Glyph* glyph) {
43 return this->getAtlas(format)->hasID(glyph->fAtlasLocator.plotLocator());
46 template <typename INT_TYPE>
47 static void expand_bits(INT_TYPE* dst,
53 for (int y = 0; y < height; ++y) {
54 int rowWritesLeft = width;
55 const uint8_t* s = src;
57 while (rowWritesLeft > 0) {
59 for (int x = 7; x >= 0 && rowWritesLeft; --x, --rowWritesLeft) {
60 *d++ = (mask & (1 << x)) ? (INT_TYPE)(~0UL) : 0;
63 dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes);
68 static void get_packed_glyph_image(
69 const SkGlyph& glyph, int dstRB, MaskFormat expectedMaskFormat, void* dst) {
70 const int width = glyph.width();
71 const int height = glyph.height();
72 const void* src = glyph.image();
73 SkASSERT(src != nullptr);
75 MaskFormat maskFormat = Glyph::FormatFromSkGlyph(glyph.maskFormat());
76 if (maskFormat == expectedMaskFormat) {
77 int srcRB = glyph.rowBytes();
78 // Notice this comparison is with the glyphs raw mask format, and not its MaskFormat.
79 if (glyph.maskFormat() != SkMask::kBW_Format) {
81 const int bbp = MaskFormatBytesPerPixel(expectedMaskFormat);
82 for (int y = 0; y < height; y++) {
83 memcpy(dst, src, width * bbp);
84 src = (const char*) src + srcRB;
85 dst = (char*) dst + dstRB;
88 memcpy(dst, src, dstRB * height);
91 // Handle 8-bit format by expanding the mask to the expected format.
92 const uint8_t* bits = reinterpret_cast<const uint8_t*>(src);
93 switch (expectedMaskFormat) {
94 case MaskFormat::kA8: {
95 uint8_t* bytes = reinterpret_cast<uint8_t*>(dst);
96 expand_bits(bytes, bits, width, height, dstRB, srcRB);
99 case MaskFormat::kA565: {
100 uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst);
101 expand_bits(rgb565, bits, width, height, dstRB, srcRB);
105 SK_ABORT("Invalid MaskFormat");
108 } else if (maskFormat == MaskFormat::kA565 &&
109 expectedMaskFormat == MaskFormat::kARGB) {
110 // Convert if the glyph uses a 565 mask format since it is using LCD text rendering
111 // but the expected format is 8888 (will happen on macOS with Metal since that
112 // combination does not support 565).
113 static constexpr SkMasks masks{
114 {0b1111'1000'0000'0000, 11, 5}, // Red
115 {0b0000'0111'1110'0000, 5, 6}, // Green
116 {0b0000'0000'0001'1111, 0, 5}, // Blue
119 constexpr int a565Bpp = MaskFormatBytesPerPixel(MaskFormat::kA565);
120 constexpr int argbBpp = MaskFormatBytesPerPixel(MaskFormat::kARGB);
121 for (int y = 0; y < height; y++) {
122 for (int x = 0; x < width; x++) {
123 uint16_t color565 = 0;
124 memcpy(&color565, src, a565Bpp);
125 uint32_t colorRGBA = GrColorPackRGBA(masks.getRed(color565),
126 masks.getGreen(color565),
127 masks.getBlue(color565),
129 memcpy(dst, &colorRGBA, argbBpp);
130 src = (char*)src + a565Bpp;
131 dst = (char*)dst + argbBpp;
136 // Retrieving the image from the cache can actually change the mask format. This case is
137 // very uncommon so for now we just draw a clear box for these glyphs.
138 const int bpp = MaskFormatBytesPerPixel(expectedMaskFormat);
139 for (int y = 0; y < height; y++) {
140 sk_bzero(dst, width * bpp);
141 dst = (char*)dst + dstRB;
146 // returns true if glyph successfully added to texture atlas, false otherwise. If the glyph's
147 // mask format has changed, then addGlyphToAtlas will draw a clear box. This will almost never
149 // TODO we can handle some of these cases if we really want to, but the long term solution is to
150 // get the actual glyph image itself when we get the glyph metrics.
151 GrDrawOpAtlas::ErrorCode GrAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
154 GrResourceProvider* resourceProvider,
155 GrDeferredUploadTarget* uploadTarget) {
156 SkASSERT(0 <= srcPadding && srcPadding <= SK_DistanceFieldInset);
158 if (skGlyph.image() == nullptr) {
159 return GrDrawOpAtlas::ErrorCode::kError;
161 SkASSERT(glyph != nullptr);
163 MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
164 MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
165 int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
168 switch (srcPadding) {
170 // The direct mask/image case.
172 if (fSupportBilerpAtlas) {
173 // Force direct masks (glyph with no padding) to have padding.
179 // The transformed mask/image case.
182 case SK_DistanceFieldInset:
184 // If the srcPadding == SK_DistanceFieldInset (SDFT case) then the padding is built
185 // into the image on the glyph; no extra padding needed.
186 // TODO: can the SDFT glyph image in the cache be reduced by the padding?
190 // The padding is not one of the know forms.
191 return GrDrawOpAtlas::ErrorCode::kError;
194 const int width = skGlyph.width() + 2*padding;
195 const int height = skGlyph.height() + 2*padding;
196 int rowBytes = width * bytesPerPixel;
197 size_t size = height * rowBytes;
199 // Temporary storage for normalizing glyph image.
200 SkAutoSMalloc<1024> storage(size);
201 void* dataPtr = storage.get();
203 sk_bzero(dataPtr, size);
204 // Advance in one row and one column.
205 dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel;
208 get_packed_glyph_image(skGlyph, rowBytes, expectedMaskFormat, dataPtr);
210 auto errorCode = this->addToAtlas(resourceProvider,
216 &glyph->fAtlasLocator);
218 if (errorCode == GrDrawOpAtlas::ErrorCode::kSucceeded) {
219 glyph->fAtlasLocator.insetSrc(srcPadding);
225 // add to texture atlas that matches this format
226 GrDrawOpAtlas::ErrorCode GrAtlasManager::addToAtlas(GrResourceProvider* resourceProvider,
227 GrDeferredUploadTarget* target,
229 int width, int height, const void* image,
230 skgpu::AtlasLocator* atlasLocator) {
231 return this->getAtlas(format)->addToAtlas(resourceProvider, target, width, height, image,
235 void GrAtlasManager::addGlyphToBulkAndSetUseToken(skgpu::BulkUsePlotUpdater* updater,
236 MaskFormat format, Glyph* glyph,
237 skgpu::DrawToken token) {
239 if (updater->add(glyph->fAtlasLocator)) {
240 this->getAtlas(format)->setLastUseToken(glyph->fAtlasLocator, token);
245 #include "include/gpu/GrDirectContext.h"
246 #include "src/gpu/ganesh/GrDirectContextPriv.h"
247 #include "src/gpu/ganesh/GrSurfaceProxy.h"
248 #include "src/gpu/ganesh/GrTextureProxy.h"
249 #include "src/gpu/ganesh/SurfaceContext.h"
251 #include "include/core/SkBitmap.h"
252 #include "include/core/SkImageEncoder.h"
253 #include "include/core/SkStream.h"
257 * Write the contents of the surface proxy to a PNG. Returns true if successful.
258 * @param filename Full path to desired file
260 static bool save_pixels(GrDirectContext* dContext, GrSurfaceProxyView view, GrColorType colorType,
261 const char* filename) {
266 auto ii = SkImageInfo::Make(view.proxy()->dimensions(), kRGBA_8888_SkColorType,
267 kPremul_SkAlphaType);
269 if (!bm.tryAllocPixels(ii)) {
273 auto sContext = dContext->priv().makeSC(std::move(view),
274 {colorType, kUnknown_SkAlphaType, nullptr});
275 if (!sContext || !sContext->asTextureProxy()) {
279 bool result = sContext->readPixels(dContext, bm.pixmap(), {0, 0});
281 SkDebugf("------ failed to read pixels for %s\n", filename);
285 // remove any previous version of this file
288 SkFILEWStream file(filename);
289 if (!file.isValid()) {
290 SkDebugf("------ failed to create file: %s\n", filename);
291 remove(filename); // remove any partial file
295 if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
296 SkDebugf("------ failed to encode %s\n", filename);
297 remove(filename); // remove any partial file
304 void GrAtlasManager::dump(GrDirectContext* context) const {
305 static int gDumpCount = 0;
306 for (int i = 0; i < skgpu::kMaskFormatCount; ++i) {
308 const GrSurfaceProxyView* views = fAtlases[i]->getViews();
309 for (uint32_t pageIdx = 0; pageIdx < fAtlases[i]->numActivePages(); ++pageIdx) {
310 SkASSERT(views[pageIdx].proxy());
312 #ifdef SK_BUILD_FOR_ANDROID
313 filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
315 filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
317 SkColorType ct = MaskFormatToColorType(AtlasIndexToMaskFormat(i));
318 save_pixels(context, views[pageIdx], SkColorTypeToGrColorType(ct),
327 void GrAtlasManager::setAtlasDimensionsToMinimum_ForTesting() {
328 // Delete any old atlases.
329 // This should be safe to do as long as we are not in the middle of a flush.
330 for (int i = 0; i < skgpu::kMaskFormatCount; i++) {
331 fAtlases[i] = nullptr;
334 // Set all the atlas sizes to 1x1 plot each.
335 new (&fAtlasConfig) GrDrawOpAtlasConfig{};
338 bool GrAtlasManager::initAtlas(MaskFormat format) {
339 int index = MaskFormatToAtlasIndex(format);
340 if (fAtlases[index] == nullptr) {
341 SkColorType colorType = MaskFormatToColorType(format);
342 GrColorType grColorType = SkColorTypeToGrColorType(colorType);
343 SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format);
344 SkISize plotDimensions = fAtlasConfig.plotDimensions(format);
346 const GrBackendFormat backendFormat =
347 fCaps->getDefaultBackendFormat(grColorType, GrRenderable::kNo);
349 fAtlases[index] = GrDrawOpAtlas::Make(fProxyProvider, backendFormat,
350 GrColorTypeToSkColorType(grColorType),
351 GrColorTypeBytesPerPixel(grColorType),
352 atlasDimensions.width(), atlasDimensions.height(),
353 plotDimensions.width(), plotDimensions.height(),
355 fAllowMultitexturing,
357 /*label=*/"TextAtlas");
358 if (!fAtlases[index]) {
365 ////////////////////////////////////////////////////////////////////////////////////////////////
367 namespace sktext::gpu {
369 std::tuple<bool, int> GlyphVector::regenerateAtlas(int begin, int end,
370 MaskFormat maskFormat,
372 GrMeshDrawTarget* target) {
373 GrAtlasManager* atlasManager = target->atlasManager();
374 GrDeferredUploadTarget* uploadTarget = target->deferredUploadTarget();
376 uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
378 this->packedGlyphIDToGlyph(target->strikeCache());
380 if (fAtlasGeneration != currentAtlasGen) {
381 // Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
382 // is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls..
383 fBulkUseUpdater.reset();
385 SkBulkGlyphMetricsAndImages metricsAndImages{fTextStrike->strikeSpec()};
387 // Update the atlas information in the GrStrike.
388 auto tokenTracker = uploadTarget->tokenTracker();
389 auto glyphs = fGlyphs.subspan(begin, end - begin);
390 int glyphsPlacedInAtlas = 0;
392 for (const Variant& variant : glyphs) {
393 Glyph* gpuGlyph = variant.glyph;
394 SkASSERT(gpuGlyph != nullptr);
396 if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
397 const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID);
398 auto code = atlasManager->addGlyphToAtlas(
399 skGlyph, gpuGlyph, srcPadding, target->resourceProvider(), uploadTarget);
400 if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
401 success = code != GrDrawOpAtlas::ErrorCode::kError;
405 atlasManager->addGlyphToBulkAndSetUseToken(
406 &fBulkUseUpdater, maskFormat, gpuGlyph,
407 tokenTracker->nextDrawToken());
408 glyphsPlacedInAtlas++;
411 // Update atlas generation if there are no more glyphs to put in the atlas.
412 if (success && begin + glyphsPlacedInAtlas == SkCount(fGlyphs)) {
413 // Need to get the freshest value of the atlas' generation because
414 // updateTextureCoordinates may have changed it.
415 fAtlasGeneration = atlasManager->atlasGeneration(maskFormat);
418 return {success, glyphsPlacedInAtlas};
420 // The atlas hasn't changed, so our texture coordinates are still valid.
421 if (end == SkCount(fGlyphs)) {
422 // The atlas hasn't changed and the texture coordinates are all still valid. Update
423 // all the plots used to the new use token.
424 atlasManager->setUseTokenBulk(fBulkUseUpdater,
425 uploadTarget->tokenTracker()->nextDrawToken(),
428 return {true, end - begin};
432 } // namespace sktext::gpu