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/DrawAtlas.h"
9
10 #include <memory>
11
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkStream.h"
14 #include "include/gpu/graphite/Recorder.h"
15 #include "include/private/base/SkTPin.h"
16 #include "src/core/SkColorData.h"
17
18 #include "src/base/SkMathPriv.h"
19 #include "src/core/SkTraceEvent.h"
20 #include "src/gpu/AtlasTypes.h"
21 #include "src/gpu/graphite/Caps.h"
22 #include "src/gpu/graphite/CommandTypes.h"
23 #include "src/gpu/graphite/ContextPriv.h"
24 #include "src/gpu/graphite/DrawContext.h"
25 #include "src/gpu/graphite/RecorderPriv.h"
26 #include "src/gpu/graphite/TextureProxy.h"
27
28 using namespace skia_private;
29
30 namespace skgpu::graphite {
31
32 #if defined(DUMP_ATLAS_DATA)
33 static const constexpr bool kDumpAtlasData = true;
34 #else
35 static const constexpr bool kDumpAtlasData = false;
36 #endif
37
38 #ifdef SK_DEBUG
validate(const AtlasLocator & atlasLocator) const39 void DrawAtlas::validate(const AtlasLocator& atlasLocator) const {
40 // Verify that the plotIndex stored in the PlotLocator is consistent with the glyph rectangle
41 int numPlotsX = fTextureWidth / fPlotWidth;
42 int numPlotsY = fTextureHeight / fPlotHeight;
43
44 int plotIndex = atlasLocator.plotIndex();
45 auto topLeft = atlasLocator.topLeft();
46 int plotX = topLeft.x() / fPlotWidth;
47 int plotY = topLeft.y() / fPlotHeight;
48 SkASSERT(plotIndex == (numPlotsY - plotY - 1) * numPlotsX + (numPlotsX - plotX - 1));
49 }
50 #endif
51
Make(SkColorType colorType,size_t bpp,int width,int height,int plotWidth,int plotHeight,AtlasGenerationCounter * generationCounter,AllowMultitexturing allowMultitexturing,UseStorageTextures useStorageTextures,PlotEvictionCallback * evictor,std::string_view label)52 std::unique_ptr<DrawAtlas> DrawAtlas::Make(SkColorType colorType, size_t bpp, int width,
53 int height, int plotWidth, int plotHeight,
54 AtlasGenerationCounter* generationCounter,
55 AllowMultitexturing allowMultitexturing,
56 UseStorageTextures useStorageTextures,
57 PlotEvictionCallback* evictor,
58 std::string_view label) {
59 std::unique_ptr<DrawAtlas> atlas(new DrawAtlas(colorType, bpp, width, height,
60 plotWidth, plotHeight, generationCounter,
61 allowMultitexturing, useStorageTextures, label));
62
63 if (evictor != nullptr) {
64 atlas->fEvictionCallbacks.emplace_back(evictor);
65 }
66 return atlas;
67 }
68
69 ///////////////////////////////////////////////////////////////////////////////
next_id()70 static uint32_t next_id() {
71 static std::atomic<uint32_t> nextID{1};
72 uint32_t id;
73 do {
74 id = nextID.fetch_add(1, std::memory_order_relaxed);
75 } while (id == SK_InvalidGenID);
76 return id;
77 }
DrawAtlas(SkColorType colorType,size_t bpp,int width,int height,int plotWidth,int plotHeight,AtlasGenerationCounter * generationCounter,AllowMultitexturing allowMultitexturing,UseStorageTextures useStorageTextures,std::string_view label)78 DrawAtlas::DrawAtlas(SkColorType colorType, size_t bpp, int width, int height,
79 int plotWidth, int plotHeight, AtlasGenerationCounter* generationCounter,
80 AllowMultitexturing allowMultitexturing,
81 UseStorageTextures useStorageTextures,
82 std::string_view label)
83 : fColorType(colorType)
84 , fBytesPerPixel(bpp)
85 , fTextureWidth(width)
86 , fTextureHeight(height)
87 , fPlotWidth(plotWidth)
88 , fPlotHeight(plotHeight)
89 , fUseStorageTextures(useStorageTextures)
90 , fLabel(label)
91 , fAtlasID(next_id())
92 , fGenerationCounter(generationCounter)
93 , fAtlasGeneration(fGenerationCounter->next())
94 , fPrevFlushToken(AtlasToken::InvalidToken())
95 , fFlushesSinceLastUse(0)
96 , fMaxPages(allowMultitexturing == AllowMultitexturing::kYes ?
97 PlotLocator::kMaxMultitexturePages : 1)
98 , fNumActivePages(0) {
99 int numPlotsX = width/plotWidth;
100 int numPlotsY = height/plotHeight;
101 SkASSERT(numPlotsX * numPlotsY <= PlotLocator::kMaxPlots);
102 SkASSERTF(fPlotWidth * numPlotsX == fTextureWidth,
103 "Invalid DrawAtlas. Plot width: %d, texture width %d", fPlotWidth, fTextureWidth);
104 SkASSERTF(fPlotHeight * numPlotsY == fTextureHeight,
105 "Invalid DrawAtlas. Plot height: %d, texture height %d", fPlotHeight, fTextureHeight);
106
107 fNumPlots = numPlotsX * numPlotsY;
108
109 this->createPages(generationCounter);
110 }
111
processEvictionAndResetRects(Plot * plot)112 inline void DrawAtlas::processEvictionAndResetRects(Plot* plot) {
113 // Process evictions
114 if (!plot->isEmpty()) {
115 const PlotLocator& plotLocator = plot->plotLocator();
116 for (PlotEvictionCallback* evictor : fEvictionCallbacks) {
117 evictor->evict(plotLocator);
118 }
119 fAtlasGeneration = fGenerationCounter->next();
120 }
121
122 plot->resetRects();
123 }
124
updatePlot(Plot * plot,AtlasLocator * atlasLocator)125 inline void DrawAtlas::updatePlot(Plot* plot, AtlasLocator* atlasLocator) {
126 int pageIdx = plot->pageIndex();
127 this->makeMRU(plot, pageIdx);
128
129 // The actual upload will be created in recordUploads().
130
131 atlasLocator->updatePlotLocator(plot->plotLocator());
132 SkDEBUGCODE(this->validate(*atlasLocator);)
133 }
134
addRectToPage(unsigned int pageIdx,int width,int height,AtlasLocator * atlasLocator)135 bool DrawAtlas::addRectToPage(unsigned int pageIdx, int width, int height,
136 AtlasLocator* atlasLocator) {
137 SkASSERT(fProxies[pageIdx]);
138
139 // look through all allocated plots for one we can share, in Most Recently Refed order
140 PlotList::Iter plotIter;
141 plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
142
143 for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
144 if (plot->addRect(width, height, atlasLocator)) {
145 this->updatePlot(plot, atlasLocator);
146 return true;
147 }
148 }
149
150 return false;
151 }
152
recordUploads(DrawContext * dc,Recorder * recorder)153 bool DrawAtlas::recordUploads(DrawContext* dc, Recorder* recorder) {
154 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
155 for (uint32_t pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
156 PlotList::Iter plotIter;
157 plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
158 for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
159 if (plot->needsUpload()) {
160 TextureProxy* proxy = fProxies[pageIdx].get();
161 SkASSERT(proxy);
162
163 const void* dataPtr;
164 SkIRect dstRect;
165 std::tie(dataPtr, dstRect) = plot->prepareForUpload();
166 if (dstRect.isEmpty()) {
167 continue;
168 }
169
170 std::vector<MipLevel> levels;
171 levels.push_back({dataPtr, fBytesPerPixel*fPlotWidth});
172
173 // Src and dst colorInfo are the same
174 SkColorInfo colorInfo(fColorType, kUnknown_SkAlphaType, nullptr);
175 if (!dc->recordUpload(recorder, sk_ref_sp(proxy), colorInfo, colorInfo, levels,
176 dstRect, /*ConditionalUploadContext=*/nullptr)) {
177 return false;
178 }
179 }
180 }
181 }
182 return true;
183 }
184
185 // Number of atlas-related flushes beyond which we consider a plot to no longer be in use.
186 //
187 // This value is somewhat arbitrary -- the idea is to keep it low enough that
188 // a page with unused plots will get removed reasonably quickly, but allow it
189 // to hang around for a bit in case it's needed. The assumption is that flushes
190 // are rare; i.e., we are not continually refreshing the frame.
191 static constexpr auto kPlotRecentlyUsedCount = 32;
192 static constexpr auto kAtlasRecentlyUsedCount = 128;
193
addRect(Recorder * recorder,int width,int height,AtlasLocator * atlasLocator)194 DrawAtlas::ErrorCode DrawAtlas::addRect(Recorder* recorder,
195 int width, int height,
196 AtlasLocator* atlasLocator) {
197 if (width > fPlotWidth || height > fPlotHeight || width < 0 || height < 0) {
198 return ErrorCode::kError;
199 }
200
201 // We permit zero-sized rects to allow inverse fills in the PathAtlases to work,
202 // but we don't want to enter them in the Rectanizer. So we handle this special case here.
203 // For text this should be caught at a higher level, but if not the only end result
204 // will be rendering a degenerate quad.
205 if (width == 0 || height == 0) {
206 if (fNumActivePages == 0) {
207 // Make sure we have a Page for the AtlasLocator to refer to
208 this->activateNewPage(recorder);
209 }
210 atlasLocator->updateRect(skgpu::IRect16::MakeXYWH(0, 0, 0, 0));
211 // Use the MRU Plot from the first Page
212 atlasLocator->updatePlotLocator(fPages[0].fPlotList.head()->plotLocator());
213 return ErrorCode::kSucceeded;
214 }
215
216 // Look through each page to see if we can upload without having to flush
217 // We prioritize this upload to the first pages, not the most recently used, to make it easier
218 // to remove unused pages in reverse page order.
219 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
220 if (this->addRectToPage(pageIdx, width, height, atlasLocator)) {
221 return ErrorCode::kSucceeded;
222 }
223 }
224
225 // If the above fails, then see if the least recently used plot per page has already been
226 // queued for upload if we're at max page allocation, or if the plot has aged out otherwise.
227 // We wait until we've grown to the full number of pages to begin evicting already queued
228 // plots so that we can maximize the opportunity for reuse.
229 // As before we prioritize this upload to the first pages, not the most recently used.
230 if (fNumActivePages == this->maxPages()) {
231 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
232 Plot* plot = fPages[pageIdx].fPlotList.tail();
233 SkASSERT(plot);
234 if (plot->lastUseToken() < recorder->priv().tokenTracker()->nextFlushToken()) {
235 this->processEvictionAndResetRects(plot);
236 SkDEBUGCODE(bool verify = )plot->addRect(width, height, atlasLocator);
237 SkASSERT(verify);
238 this->updatePlot(plot, atlasLocator);
239 return ErrorCode::kSucceeded;
240 }
241 }
242 } else {
243 // If we haven't activated all the available pages, try to create a new one and add to it
244 if (!this->activateNewPage(recorder)) {
245 return ErrorCode::kError;
246 }
247
248 if (this->addRectToPage(fNumActivePages-1, width, height, atlasLocator)) {
249 return ErrorCode::kSucceeded;
250 } else {
251 // If we fail to upload to a newly activated page then something has gone terribly
252 // wrong - return an error
253 return ErrorCode::kError;
254 }
255 }
256
257 if (!fNumActivePages) {
258 return ErrorCode::kError;
259 }
260
261 // All plots are currently in use by the current set of draws, so we need to fail. This
262 // gives the Device a chance to snap the current set of uploads and draws, advance the draw
263 // token, and call back into this function. The subsequent call will have plots available
264 // for fresh uploads.
265 return ErrorCode::kTryAgain;
266 }
267
addToAtlas(Recorder * recorder,int width,int height,const void * image,AtlasLocator * atlasLocator)268 DrawAtlas::ErrorCode DrawAtlas::addToAtlas(Recorder* recorder,
269 int width, int height, const void* image,
270 AtlasLocator* atlasLocator) {
271 ErrorCode ec = this->addRect(recorder, width, height, atlasLocator);
272 if (ec == ErrorCode::kSucceeded) {
273 Plot* plot = this->findPlot(*atlasLocator);
274 plot->copySubImage(*atlasLocator, image);
275 }
276
277 return ec;
278 }
279
prepForRender(const AtlasLocator & locator,SkAutoPixmapStorage * pixmap)280 SkIPoint DrawAtlas::prepForRender(const AtlasLocator& locator, SkAutoPixmapStorage* pixmap) {
281 Plot* plot = this->findPlot(locator);
282 return plot->prepForRender(locator, pixmap);
283 }
284
compact(AtlasToken startTokenForNextFlush)285 void DrawAtlas::compact(AtlasToken startTokenForNextFlush) {
286 if (fNumActivePages < 1) {
287 fPrevFlushToken = startTokenForNextFlush;
288 return;
289 }
290
291 // For all plots, reset number of flushes since used if used this frame.
292 PlotList::Iter plotIter;
293 bool atlasUsedThisFlush = false;
294 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
295 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
296 while (Plot* plot = plotIter.get()) {
297 // Reset number of flushes since used
298 if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
299 plot->resetFlushesSinceLastUsed();
300 atlasUsedThisFlush = true;
301 }
302
303 plotIter.next();
304 }
305 }
306
307 if (atlasUsedThisFlush) {
308 fFlushesSinceLastUse = 0;
309 } else {
310 ++fFlushesSinceLastUse;
311 }
312
313 // We only try to compact if the atlas was used in the recently completed flush or
314 // hasn't been used in a long time.
315 // This is to handle the case where a lot of text or path rendering has occurred but then just
316 // a blinking cursor is drawn.
317 if (atlasUsedThisFlush || fFlushesSinceLastUse > kAtlasRecentlyUsedCount) {
318 TArray<Plot*> availablePlots;
319 uint32_t lastPageIndex = fNumActivePages - 1;
320
321 // For all plots but the last one, update number of flushes since used, and check to see
322 // if there are any in the first pages that the last page can safely upload to.
323 for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
324 if constexpr (kDumpAtlasData) {
325 SkDebugf("page %u: ", pageIndex);
326 }
327
328 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
329 while (Plot* plot = plotIter.get()) {
330 // Update number of flushes since plot was last used
331 // We only increment the 'sinceLastUsed' count for flushes where the atlas was used
332 // to avoid deleting everything when we return to text drawing in the blinking
333 // cursor case
334 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
335 plot->incFlushesSinceLastUsed();
336 }
337
338 if constexpr (kDumpAtlasData) {
339 SkDebugf("%d ", plot->flushesSinceLastUsed());
340 }
341
342 // Count plots we can potentially upload to in all pages except the last one
343 // (the potential compactee).
344 if (plot->flushesSinceLastUsed() > kPlotRecentlyUsedCount) {
345 availablePlots.push_back() = plot;
346 }
347
348 plotIter.next();
349 }
350
351 if constexpr (kDumpAtlasData) {
352 SkDebugf("\n");
353 }
354 }
355
356 // Count recently used plots in the last page and evict any that are no longer in use.
357 // Since we prioritize uploading to the first pages, this will eventually
358 // clear out usage of this page unless we have a large need.
359 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
360 unsigned int usedPlots = 0;
361 if constexpr (kDumpAtlasData) {
362 SkDebugf("page %u: ", lastPageIndex);
363 }
364 while (Plot* plot = plotIter.get()) {
365 // Update number of flushes since plot was last used
366 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
367 plot->incFlushesSinceLastUsed();
368 }
369
370 if constexpr (kDumpAtlasData) {
371 SkDebugf("%d ", plot->flushesSinceLastUsed());
372 }
373
374 // If this plot was used recently
375 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
376 usedPlots++;
377 } else if (plot->lastUseToken() != AtlasToken::InvalidToken()) {
378 // otherwise if aged out just evict it.
379 this->processEvictionAndResetRects(plot);
380 }
381 plotIter.next();
382 }
383
384 if constexpr (kDumpAtlasData) {
385 SkDebugf("\n");
386 }
387
388 // If recently used plots in the last page are using less than a quarter of the page, try
389 // to evict them if there's available space in lower index pages. Since we prioritize
390 // uploading to the first pages, this will eventually clear out usage of this page unless
391 // we have a large need.
392 if (!availablePlots.empty() && usedPlots && usedPlots <= fNumPlots / 4) {
393 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
394 while (Plot* plot = plotIter.get()) {
395 // If this plot was used recently
396 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
397 // See if there's room in an lower index page and if so evict.
398 // We need to be somewhat harsh here so that a handful of plots that are
399 // consistently in use don't end up locking the page in memory.
400 if (!availablePlots.empty()) {
401 this->processEvictionAndResetRects(plot);
402 this->processEvictionAndResetRects(availablePlots.back());
403 availablePlots.pop_back();
404 --usedPlots;
405 }
406 if (usedPlots == 0 || availablePlots.empty()) {
407 break;
408 }
409 }
410 plotIter.next();
411 }
412 }
413
414 // If none of the plots in the last page have been used recently, delete it.
415 if (usedPlots == 0) {
416 if constexpr (kDumpAtlasData) {
417 SkDebugf("delete %u\n", fNumActivePages-1);
418 }
419
420 this->deactivateLastPage();
421 fFlushesSinceLastUse = 0;
422 }
423 }
424
425 fPrevFlushToken = startTokenForNextFlush;
426 }
427
createPages(AtlasGenerationCounter * generationCounter)428 bool DrawAtlas::createPages(AtlasGenerationCounter* generationCounter) {
429 SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
430
431 int numPlotsX = fTextureWidth/fPlotWidth;
432 int numPlotsY = fTextureHeight/fPlotHeight;
433
434 for (uint32_t i = 0; i < this->maxPages(); ++i) {
435 // Proxies are uncreated at first
436 fProxies[i] = nullptr;
437
438 // set up allocated plots
439 fPages[i].fPlotArray = std::make_unique<sk_sp<Plot>[]>(numPlotsX * numPlotsY);
440
441 sk_sp<Plot>* currPlot = fPages[i].fPlotArray.get();
442 for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) {
443 for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) {
444 uint32_t plotIndex = r * numPlotsX + c;
445 currPlot->reset(new Plot(
446 i, plotIndex, generationCounter, x, y, fPlotWidth, fPlotHeight, fColorType,
447 fBytesPerPixel));
448
449 // build LRU list
450 fPages[i].fPlotList.addToHead(currPlot->get());
451 ++currPlot;
452 }
453 }
454 }
455
456 return true;
457 }
458
activateNewPage(Recorder * recorder)459 bool DrawAtlas::activateNewPage(Recorder* recorder) {
460 SkASSERT(fNumActivePages < this->maxPages());
461 SkASSERT(!fProxies[fNumActivePages]);
462
463 const Caps* caps = recorder->priv().caps();
464 auto textureInfo = fUseStorageTextures == UseStorageTextures::kYes
465 ? caps->getDefaultStorageTextureInfo(fColorType)
466 : caps->getDefaultSampledTextureInfo(fColorType,
467 Mipmapped::kNo,
468 recorder->priv().isProtected(),
469 Renderable::kNo);
470 fProxies[fNumActivePages] = TextureProxy::Make(caps,
471 recorder->priv().resourceProvider(),
472 {fTextureWidth, fTextureHeight},
473 textureInfo,
474 fLabel,
475 skgpu::Budgeted::kYes);
476 if (!fProxies[fNumActivePages]) {
477 return false;
478 }
479
480 if constexpr (kDumpAtlasData) {
481 SkDebugf("activated page#: %u\n", fNumActivePages);
482 }
483
484 ++fNumActivePages;
485 return true;
486 }
487
deactivateLastPage()488 inline void DrawAtlas::deactivateLastPage() {
489 SkASSERT(fNumActivePages);
490
491 uint32_t lastPageIndex = fNumActivePages - 1;
492
493 int numPlotsX = fTextureWidth/fPlotWidth;
494 int numPlotsY = fTextureHeight/fPlotHeight;
495
496 fPages[lastPageIndex].fPlotList.reset();
497 for (int r = 0; r < numPlotsY; ++r) {
498 for (int c = 0; c < numPlotsX; ++c) {
499 uint32_t plotIndex = r * numPlotsX + c;
500
501 Plot* currPlot = fPages[lastPageIndex].fPlotArray[plotIndex].get();
502 this->processEvictionAndResetRects(currPlot);
503 currPlot->resetFlushesSinceLastUsed();
504
505 // rebuild the LRU list
506 SkDEBUGCODE(currPlot->resetListPtrs());
507 fPages[lastPageIndex].fPlotList.addToHead(currPlot);
508 }
509 }
510
511 // remove ref to the texture proxy
512 fProxies[lastPageIndex].reset();
513 --fNumActivePages;
514 }
515
markUsedPlotsAsFull()516 void DrawAtlas::markUsedPlotsAsFull() {
517 PlotList::Iter plotIter;
518 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
519 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
520 while (Plot* plot = plotIter.get()) {
521 plot->markFullIfUsed();
522 plotIter.next();
523 }
524 }
525 }
526
freeGpuResources(AtlasToken token)527 void DrawAtlas::freeGpuResources(AtlasToken token) {
528 PlotList::Iter plotIter;
529 for (int pageIndex = (int)(fNumActivePages)-1; pageIndex >= 0; --pageIndex) {
530 const Page& currPage = fPages[pageIndex];
531 plotIter.init(currPage.fPlotList, PlotList::Iter::kHead_IterStart);
532 while (Plot* plot = plotIter.get()) {
533 if (plot->lastUseToken().inInterval(fPrevFlushToken, token)) {
534 // This page is in use and we can only deactivate pages from high index
535 // to low index, so bail.
536 return;
537 }
538 plotIter.next();
539 }
540 this->deactivateLastPage();
541 }
542 }
543
evictAllPlots()544 void DrawAtlas::evictAllPlots() {
545 PlotList::Iter plotIter;
546 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
547 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
548 while (Plot* plot = plotIter.get()) {
549 this->processEvictionAndResetRects(plot);
550 plotIter.next();
551 }
552 }
553 }
554
DrawAtlasConfig(int maxTextureSize,size_t maxBytes)555 DrawAtlasConfig::DrawAtlasConfig(int maxTextureSize, size_t maxBytes) {
556 static const SkISize kARGBDimensions[] = {
557 {256, 256}, // maxBytes < 2^19
558 {512, 256}, // 2^19 <= maxBytes < 2^20
559 {512, 512}, // 2^20 <= maxBytes < 2^21
560 {1024, 512}, // 2^21 <= maxBytes < 2^22
561 {1024, 1024}, // 2^22 <= maxBytes < 2^23
562 {2048, 1024}, // 2^23 <= maxBytes
563 };
564
565 // Index 0 corresponds to maxBytes of 2^18, so start by dividing it by that
566 maxBytes >>= 18;
567 // Take the floor of the log to get the index
568 int index = maxBytes > 0
569 ? SkTPin<int>(SkPrevLog2(maxBytes), 0, std::size(kARGBDimensions) - 1)
570 : 0;
571
572 SkASSERT(kARGBDimensions[index].width() <= kMaxAtlasDim);
573 SkASSERT(kARGBDimensions[index].height() <= kMaxAtlasDim);
574 fARGBDimensions.set(std::min<int>(kARGBDimensions[index].width(), maxTextureSize),
575 std::min<int>(kARGBDimensions[index].height(), maxTextureSize));
576 fMaxTextureSize = std::min<int>(maxTextureSize, kMaxAtlasDim);
577 }
578
atlasDimensions(MaskFormat type) const579 SkISize DrawAtlasConfig::atlasDimensions(MaskFormat type) const {
580 if (MaskFormat::kA8 == type) {
581 // A8 is always 2x the ARGB dimensions, clamped to the max allowed texture size
582 return { std::min<int>(2 * fARGBDimensions.width(), fMaxTextureSize),
583 std::min<int>(2 * fARGBDimensions.height(), fMaxTextureSize) };
584 } else {
585 return fARGBDimensions;
586 }
587 }
588
plotDimensions(MaskFormat type) const589 SkISize DrawAtlasConfig::plotDimensions(MaskFormat type) const {
590 if (MaskFormat::kA8 == type) {
591 SkISize atlasDimensions = this->atlasDimensions(type);
592 // For A8 we want to grow the plots at larger texture sizes to accept more of the
593 // larger SDF glyphs. Since the largest SDF glyph can be 170x170 with padding, this
594 // allows us to pack 3 in a 512x256 plot, or 9 in a 512x512 plot.
595
596 // This will give us 512x256 plots for 2048x1024, 512x512 plots for 2048x2048,
597 // and 256x256 plots otherwise.
598 int plotWidth = atlasDimensions.width() >= 2048 ? 512 : 256;
599 int plotHeight = atlasDimensions.height() >= 2048 ? 512 : 256;
600
601 return { plotWidth, plotHeight };
602 } else {
603 // ARGB and LCD always use 256x256 plots -- this has been shown to be faster
604 return { 256, 256 };
605 }
606 }
607
608 } // namespace skgpu::graphite
609