1/*
2 * Copyright 2015 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/GrDrawOpAtlas.h"
9
10#include <memory>
11
12#include "src/core/SkOpts.h"
13#include "src/gpu/GrOnFlushResourceProvider.h"
14#include "src/gpu/GrOpFlushState.h"
15#include "src/gpu/GrProxyProvider.h"
16#include "src/gpu/GrResourceProvider.h"
17#include "src/gpu/GrResourceProviderPriv.h"
18#include "src/gpu/GrSurfaceProxyPriv.h"
19#include "src/gpu/GrTexture.h"
20#include "src/gpu/GrTracing.h"
21
22#ifdef DUMP_ATLAS_DATA
23static bool gDumpAtlasData = false;
24#endif
25
26std::array<uint16_t, 4> GrDrawOpAtlas::AtlasLocator::getUVs() const {
27
28 // We pack the 2bit page index in the low bit of the u and v texture coords
29 uint32_t pageIndex = this->pageIndex();
30 auto [left, top] = PackIndexInTexCoords(fRect.fLeft, fRect.fTop, pageIndex);
31 auto [right, bottom] = PackIndexInTexCoords(fRect.fRight, fRect.fBottom, pageIndex);
32 return { left, top, right, bottom };
33}
34
35#ifdef SK_DEBUG
36void GrDrawOpAtlas::AtlasLocator::validate(const GrDrawOpAtlas* drawOpAtlas) const {
37 // Verify that the plotIndex stored in the PlotLocator is consistent with the glyph rectangle
38 int numPlotsX = drawOpAtlas->fTextureWidth / drawOpAtlas->fPlotWidth;
39 int numPlotsY = drawOpAtlas->fTextureHeight / drawOpAtlas->fPlotHeight;
40
41 int plotIndex = this->plotIndex();
42 int plotX = fRect.fLeft / drawOpAtlas->fPlotWidth;
43 int plotY = fRect.fTop / drawOpAtlas->fPlotHeight;
44 SkASSERT(plotIndex == (numPlotsY - plotY - 1) * numPlotsX + (numPlotsX - plotX - 1));
45}
46#endif
47
48// When proxy allocation is deferred until flush time the proxies acting as atlases require
49// special handling. This is because the usage that can be determined from the ops themselves
50// isn't sufficient. Independent of the ops there will be ASAP and inline uploads to the
51// atlases. Extending the usage interval of any op that uses an atlas to the start of the
52// flush (as is done for proxies that are used for sw-generated masks) also won't work because
53// the atlas persists even beyond the last use in an op - for a given flush. Given this, atlases
54// must explicitly manage the lifetime of their backing proxies via the onFlushCallback system
55// (which calls this method).
56void GrDrawOpAtlas::instantiate(GrOnFlushResourceProvider* onFlushResourceProvider) {
57 for (uint32_t i = 0; i < fNumActivePages; ++i) {
58 // All the atlas pages are now instantiated at flush time in the activeNewPage method.
59 SkASSERT(fViews[i].proxy() && fViews[i].proxy()->isInstantiated());
60 }
61}
62
63std::unique_ptr<GrDrawOpAtlas> GrDrawOpAtlas::Make(GrProxyProvider* proxyProvider,
64 const GrBackendFormat& format,
65 GrColorType colorType, int width,
66 int height, int plotWidth, int plotHeight,
67 GenerationCounter* generationCounter,
68 AllowMultitexturing allowMultitexturing,
69 EvictionCallback* evictor) {
70 if (!format.isValid()) {
71 return nullptr;
72 }
73
74 std::unique_ptr<GrDrawOpAtlas> atlas(new GrDrawOpAtlas(proxyProvider, format, colorType,
75 width, height, plotWidth, plotHeight,
76 generationCounter,
77 allowMultitexturing));
78 if (!atlas->getViews()[0].proxy()) {
79 return nullptr;
80 }
81
82 if (evictor != nullptr) {
83 atlas->fEvictionCallbacks.emplace_back(evictor);
84 }
85 return atlas;
86}
87
88// The two bits that make up the texture index are packed into the lower bits of the u and v
89// coordinate respectively.
90std::pair<uint16_t, uint16_t> GrDrawOpAtlas::PackIndexInTexCoords(uint16_t u, uint16_t v,
91 int pageIndex) {
92 SkASSERT(pageIndex >= 0 && pageIndex < 4);
93 uint16_t uBit = (pageIndex >> 1u) & 0x1u;
94 uint16_t vBit = pageIndex & 0x1u;
95 u <<= 1u;
96 u |= uBit;
97 v <<= 1u;
98 v |= vBit;
99 return std::make_pair(u, v);
100}
101
102std::tuple<uint16_t, uint16_t, int> GrDrawOpAtlas::UnpackIndexFromTexCoords(uint16_t u,
103 uint16_t v) {
104 int pageIndex = 0;
105 if (u & 0x1) {
106 pageIndex |= 0x2;
107 }
108 if (v & 0x1) {
109 pageIndex |= 0x1;
110 }
111 return std::make_tuple(u >> 1, v >> 1, pageIndex);
112}
113
114////////////////////////////////////////////////////////////////////////////////
115GrDrawOpAtlas::Plot::Plot(int pageIndex, int plotIndex, GenerationCounter* generationCounter,
116 int offX, int offY, int width, int height, GrColorType colorType)
117 : fLastUpload(GrDeferredUploadToken::AlreadyFlushedToken())
118 , fLastUse(GrDeferredUploadToken::AlreadyFlushedToken())
119 , fFlushesSinceLastUse(0)
120 , fPageIndex(pageIndex)
121 , fPlotIndex(plotIndex)
122 , fGenerationCounter(generationCounter)
123 , fGenID(fGenerationCounter->next())
124 , fPlotLocator(fPageIndex, fPlotIndex, fGenID)
125 , fData(nullptr)
126 , fWidth(width)
127 , fHeight(height)
128 , fX(offX)
129 , fY(offY)
130 , fRectanizer(width, height)
131 , fOffset(SkIPoint16::Make(fX * fWidth, fY * fHeight))
132 , fColorType(colorType)
133 , fBytesPerPixel(GrColorTypeBytesPerPixel(colorType))
134#ifdef SK_DEBUG
135 , fDirty(false)
136#endif
137{
138 // We expect the allocated dimensions to be a multiple of 4 bytes
139 SkASSERT(((width*fBytesPerPixel) & 0x3) == 0);
140 // The padding for faster uploads only works for 1, 2 and 4 byte texels
141 SkASSERT(fBytesPerPixel != 3 && fBytesPerPixel <= 4);
142 fDirtyRect.setEmpty();
143}
144
145GrDrawOpAtlas::Plot::~Plot() {
146 sk_free(fData);
147}
148
149bool GrDrawOpAtlas::Plot::addSubImage(int width, int height, const void* image, GrIRect16* rect) {
150 SkASSERT(width <= fWidth && height <= fHeight);
151
152 SkIPoint16 loc;
153 if (!fRectanizer.addRect(width, height, &loc)) {
154 return false;
155 }
156
157 *rect = GrIRect16::MakeXYWH(loc.fX, loc.fY, width, height);
158
159 if (!fData) {
160 fData = reinterpret_cast<unsigned char*>(sk_calloc_throw(fBytesPerPixel * fWidth *
161 fHeight));
162 }
163 size_t rowBytes = width * fBytesPerPixel;
164 const unsigned char* imagePtr = (const unsigned char*)image;
165 // point ourselves at the right starting spot
166 unsigned char* dataPtr = fData;
167 dataPtr += fBytesPerPixel * fWidth * rect->fTop;
168 dataPtr += fBytesPerPixel * rect->fLeft;
169 // copy into the data buffer, swizzling as we go if this is ARGB data
170 if (4 == fBytesPerPixel && kN32_SkColorType == kBGRA_8888_SkColorType) {
171 for (int i = 0; i < height; ++i) {
172 SkOpts::RGBA_to_BGRA((uint32_t*)dataPtr, (const uint32_t*)imagePtr, width);
173 dataPtr += fBytesPerPixel * fWidth;
174 imagePtr += rowBytes;
175 }
176 } else {
177 for (int i = 0; i < height; ++i) {
178 memcpy(dataPtr, imagePtr, rowBytes);
179 dataPtr += fBytesPerPixel * fWidth;
180 imagePtr += rowBytes;
181 }
182 }
183
184 fDirtyRect.join({rect->fLeft, rect->fTop, rect->fRight, rect->fBottom});
185
186 rect->offset(fOffset.fX, fOffset.fY);
187 SkDEBUGCODE(fDirty = true;)
188
189 return true;
190}
191
192void GrDrawOpAtlas::Plot::uploadToTexture(GrDeferredTextureUploadWritePixelsFn& writePixels,
193 GrTextureProxy* proxy) {
194 // We should only be issuing uploads if we are in fact dirty
195 SkASSERT(fDirty && fData && proxy && proxy->peekTexture());
196 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
197 size_t rowBytes = fBytesPerPixel * fWidth;
198 const unsigned char* dataPtr = fData;
199 // Clamp to 4-byte aligned boundaries
200 unsigned int clearBits = 0x3 / fBytesPerPixel;
201 fDirtyRect.fLeft &= ~clearBits;
202 fDirtyRect.fRight += clearBits;
203 fDirtyRect.fRight &= ~clearBits;
204 SkASSERT(fDirtyRect.fRight <= fWidth);
205 // Set up dataPtr
206 dataPtr += rowBytes * fDirtyRect.fTop;
207 dataPtr += fBytesPerPixel * fDirtyRect.fLeft;
208
209 writePixels(proxy, fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop,
210 fDirtyRect.width(), fDirtyRect.height(), fColorType, dataPtr, rowBytes);
211 fDirtyRect.setEmpty();
212 SkDEBUGCODE(fDirty = false;)
213}
214
215void GrDrawOpAtlas::Plot::resetRects() {
216 fRectanizer.reset();
217
218 fGenID = fGenerationCounter->next();
219 fPlotLocator = PlotLocator(fPageIndex, fPlotIndex, fGenID);
220 fLastUpload = GrDeferredUploadToken::AlreadyFlushedToken();
221 fLastUse = GrDeferredUploadToken::AlreadyFlushedToken();
222
223 // zero out the plot
224 if (fData) {
225 sk_bzero(fData, fBytesPerPixel * fWidth * fHeight);
226 }
227
228 fDirtyRect.setEmpty();
229 SkDEBUGCODE(fDirty = false;)
230}
231
232///////////////////////////////////////////////////////////////////////////////
233
234GrDrawOpAtlas::GrDrawOpAtlas(GrProxyProvider* proxyProvider, const GrBackendFormat& format,
235 GrColorType colorType, int width, int height,
236 int plotWidth, int plotHeight, GenerationCounter* generationCounter,
237 AllowMultitexturing allowMultitexturing)
238 : fFormat(format)
239 , fColorType(colorType)
240 , fTextureWidth(width)
241 , fTextureHeight(height)
242 , fPlotWidth(plotWidth)
243 , fPlotHeight(plotHeight)
244 , fGenerationCounter(generationCounter)
245 , fAtlasGeneration(fGenerationCounter->next())
246 , fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken())
247 , fFlushesSinceLastUse(0)
248 , fMaxPages(AllowMultitexturing::kYes == allowMultitexturing ? kMaxMultitexturePages : 1)
249 , fNumActivePages(0) {
250 int numPlotsX = width/plotWidth;
251 int numPlotsY = height/plotHeight;
252 SkASSERT(numPlotsX * numPlotsY <= GrDrawOpAtlas::kMaxPlots);
253 SkASSERT(fPlotWidth * numPlotsX == fTextureWidth);
254 SkASSERT(fPlotHeight * numPlotsY == fTextureHeight);
255
256 fNumPlots = numPlotsX * numPlotsY;
257
258 this->createPages(proxyProvider, generationCounter);
259}
260
261inline void GrDrawOpAtlas::processEviction(PlotLocator plotLocator) {
262 for (EvictionCallback* evictor : fEvictionCallbacks) {
263 evictor->evict(plotLocator);
264 }
265
266 fAtlasGeneration = fGenerationCounter->next();
267}
268
269inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target,
270 AtlasLocator* atlasLocator, Plot* plot) {
271 int pageIdx = plot->pageIndex();
272 this->makeMRU(plot, pageIdx);
273
274 // If our most recent upload has already occurred then we have to insert a new
275 // upload. Otherwise, we already have a scheduled upload that hasn't yet ocurred.
276 // This new update will piggy back on that previously scheduled update.
277 if (plot->lastUploadToken() < target->tokenTracker()->nextTokenToFlush()) {
278 // With c+14 we could move sk_sp into lamba to only ref once.
279 sk_sp<Plot> plotsp(SkRef(plot));
280
281 GrTextureProxy* proxy = fViews[pageIdx].asTextureProxy();
282 SkASSERT(proxy && proxy->isInstantiated()); // This is occurring at flush time
283
284 GrDeferredUploadToken lastUploadToken = target->addASAPUpload(
285 [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
286 plotsp->uploadToTexture(writePixels, proxy);
287 });
288 plot->setLastUploadToken(lastUploadToken);
289 }
290 atlasLocator->fPlotLocator = plot->plotLocator();
291 SkDEBUGCODE(atlasLocator->validate(this);)
292 return true;
293}
294
295bool GrDrawOpAtlas::uploadToPage(const GrCaps& caps, unsigned int pageIdx,
296 GrDeferredUploadTarget* target, int width, int height,
297 const void* image, AtlasLocator* atlasLocator) {
298 SkASSERT(fViews[pageIdx].proxy() && fViews[pageIdx].proxy()->isInstantiated());
299
300 // look through all allocated plots for one we can share, in Most Recently Refed order
301 PlotList::Iter plotIter;
302 plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
303
304 for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
305 SkASSERT(caps.bytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) == plot->bpp());
306
307 if (plot->addSubImage(width, height, image, &atlasLocator->fRect)) {
308 return this->updatePlot(target, atlasLocator, plot);
309 }
310 }
311
312 return false;
313}
314
315// Number of atlas-related flushes beyond which we consider a plot to no longer be in use.
316//
317// This value is somewhat arbitrary -- the idea is to keep it low enough that
318// a page with unused plots will get removed reasonably quickly, but allow it
319// to hang around for a bit in case it's needed. The assumption is that flushes
320// are rare; i.e., we are not continually refreshing the frame.
321static constexpr auto kPlotRecentlyUsedCount = 32;
322static constexpr auto kAtlasRecentlyUsedCount = 128;
323
324GrDrawOpAtlas::ErrorCode GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider,
325 GrDeferredUploadTarget* target,
326 int width, int height, const void* image,
327 AtlasLocator* atlasLocator) {
328 if (width > fPlotWidth || height > fPlotHeight) {
329 return ErrorCode::kError;
330 }
331
332 const GrCaps& caps = *resourceProvider->caps();
333
334 // Look through each page to see if we can upload without having to flush
335 // We prioritize this upload to the first pages, not the most recently used, to make it easier
336 // to remove unused pages in reverse page order.
337 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
338 if (this->uploadToPage(caps, pageIdx, target, width, height, image, atlasLocator)) {
339 return ErrorCode::kSucceeded;
340 }
341 }
342
343 // If the above fails, then see if the least recently used plot per page has already been
344 // flushed to the gpu if we're at max page allocation, or if the plot has aged out otherwise.
345 // We wait until we've grown to the full number of pages to begin evicting already flushed
346 // plots so that we can maximize the opportunity for reuse.
347 // As before we prioritize this upload to the first pages, not the most recently used.
348 if (fNumActivePages == this->maxPages()) {
349 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
350 Plot* plot = fPages[pageIdx].fPlotList.tail();
351 SkASSERT(plot);
352 if (plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush()) {
353 this->processEvictionAndResetRects(plot);
354 SkASSERT(caps.bytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) ==
355 plot->bpp());
356 SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image,
357 &atlasLocator->fRect);
358 SkASSERT(verify);
359 if (!this->updatePlot(target, atlasLocator, plot)) {
360 return ErrorCode::kError;
361 }
362 return ErrorCode::kSucceeded;
363 }
364 }
365 } else {
366 // If we haven't activated all the available pages, try to create a new one and add to it
367 if (!this->activateNewPage(resourceProvider)) {
368 return ErrorCode::kError;
369 }
370
371 if (this->uploadToPage(caps, fNumActivePages-1, target, width, height, image,
372 atlasLocator)) {
373 return ErrorCode::kSucceeded;
374 } else {
375 // If we fail to upload to a newly activated page then something has gone terribly
376 // wrong - return an error
377 return ErrorCode::kError;
378 }
379 }
380
381 if (!fNumActivePages) {
382 return ErrorCode::kError;
383 }
384
385 // Try to find a plot that we can perform an inline upload to.
386 // We prioritize this upload in reverse order of pages to counterbalance the order above.
387 Plot* plot = nullptr;
388 for (int pageIdx = ((int)fNumActivePages)-1; pageIdx >= 0; --pageIdx) {
389 Plot* currentPlot = fPages[pageIdx].fPlotList.tail();
390 if (currentPlot->lastUseToken() != target->tokenTracker()->nextDrawToken()) {
391 plot = currentPlot;
392 break;
393 }
394 }
395
396 // If we can't find a plot that is not used in a draw currently being prepared by an op, then
397 // we have to fail. This gives the op a chance to enqueue the draw, and call back into this
398 // function. When that draw is enqueued, the draw token advances, and the subsequent call will
399 // continue past this branch and prepare an inline upload that will occur after the enqueued
400 // draw which references the plot's pre-upload content.
401 if (!plot) {
402 return ErrorCode::kTryAgain;
403 }
404
405 this->processEviction(plot->plotLocator());
406 int pageIdx = plot->pageIndex();
407 fPages[pageIdx].fPlotList.remove(plot);
408 sk_sp<Plot>& newPlot = fPages[pageIdx].fPlotArray[plot->plotIndex()];
409 newPlot.reset(plot->clone());
410
411 fPages[pageIdx].fPlotList.addToHead(newPlot.get());
412 SkASSERT(caps.bytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) == newPlot->bpp());
413 SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, &atlasLocator->fRect);
414 SkASSERT(verify);
415
416 // Note that this plot will be uploaded inline with the draws whereas the
417 // one it displaced most likely was uploaded ASAP.
418 // With c++14 we could move sk_sp into lambda to only ref once.
419 sk_sp<Plot> plotsp(SkRef(newPlot.get()));
420
421 GrTextureProxy* proxy = fViews[pageIdx].asTextureProxy();
422 SkASSERT(proxy && proxy->isInstantiated());
423
424 GrDeferredUploadToken lastUploadToken = target->addInlineUpload(
425 [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
426 plotsp->uploadToTexture(writePixels, proxy);
427 });
428 newPlot->setLastUploadToken(lastUploadToken);
429
430 atlasLocator->fPlotLocator = newPlot->plotLocator();
431 SkDEBUGCODE(atlasLocator->validate(this);)
432
433 return ErrorCode::kSucceeded;
434}
435
436void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) {
437 if (fNumActivePages < 1) {
438 fPrevFlushToken = startTokenForNextFlush;
439 return;
440 }
441
442 // For all plots, reset number of flushes since used if used this frame.
443 PlotList::Iter plotIter;
444 bool atlasUsedThisFlush = false;
445 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
446 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
447 while (Plot* plot = plotIter.get()) {
448 // Reset number of flushes since used
449 if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
450 plot->resetFlushesSinceLastUsed();
451 atlasUsedThisFlush = true;
452 }
453
454 plotIter.next();
455 }
456 }
457
458 if (atlasUsedThisFlush) {
459 fFlushesSinceLastUse = 0;
460 } else {
461 ++fFlushesSinceLastUse;
462 }
463
464 // We only try to compact if the atlas was used in the recently completed flush or
465 // hasn't been used in a long time.
466 // This is to handle the case where a lot of text or path rendering has occurred but then just
467 // a blinking cursor is drawn.
468 if (atlasUsedThisFlush || fFlushesSinceLastUse > kAtlasRecentlyUsedCount) {
469 SkTArray<Plot*> availablePlots;
470 uint32_t lastPageIndex = fNumActivePages - 1;
471
472 // For all plots but the last one, update number of flushes since used, and check to see
473 // if there are any in the first pages that the last page can safely upload to.
474 for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
475#ifdef DUMP_ATLAS_DATA
476 if (gDumpAtlasData) {
477 SkDebugf("page %d: ", pageIndex);
478 }
479#endif
480 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
481 while (Plot* plot = plotIter.get()) {
482 // Update number of flushes since plot was last used
483 // We only increment the 'sinceLastUsed' count for flushes where the atlas was used
484 // to avoid deleting everything when we return to text drawing in the blinking
485 // cursor case
486 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
487 plot->incFlushesSinceLastUsed();
488 }
489
490#ifdef DUMP_ATLAS_DATA
491 if (gDumpAtlasData) {
492 SkDebugf("%d ", plot->flushesSinceLastUsed());
493 }
494#endif
495 // Count plots we can potentially upload to in all pages except the last one
496 // (the potential compactee).
497 if (plot->flushesSinceLastUsed() > kPlotRecentlyUsedCount) {
498 availablePlots.push_back() = plot;
499 }
500
501 plotIter.next();
502 }
503#ifdef DUMP_ATLAS_DATA
504 if (gDumpAtlasData) {
505 SkDebugf("\n");
506 }
507#endif
508 }
509
510 // Count recently used plots in the last page and evict any that are no longer in use.
511 // Since we prioritize uploading to the first pages, this will eventually
512 // clear out usage of this page unless we have a large need.
513 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
514 unsigned int usedPlots = 0;
515#ifdef DUMP_ATLAS_DATA
516 if (gDumpAtlasData) {
517 SkDebugf("page %d: ", lastPageIndex);
518 }
519#endif
520 while (Plot* plot = plotIter.get()) {
521 // Update number of flushes since plot was last used
522 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
523 plot->incFlushesSinceLastUsed();
524 }
525
526#ifdef DUMP_ATLAS_DATA
527 if (gDumpAtlasData) {
528 SkDebugf("%d ", plot->flushesSinceLastUsed());
529 }
530#endif
531 // If this plot was used recently
532 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
533 usedPlots++;
534 } else if (plot->lastUseToken() != GrDeferredUploadToken::AlreadyFlushedToken()) {
535 // otherwise if aged out just evict it.
536 this->processEvictionAndResetRects(plot);
537 }
538 plotIter.next();
539 }
540#ifdef DUMP_ATLAS_DATA
541 if (gDumpAtlasData) {
542 SkDebugf("\n");
543 }
544#endif
545
546 // If recently used plots in the last page are using less than a quarter of the page, try
547 // to evict them if there's available space in earlier pages. Since we prioritize uploading
548 // to the first pages, this will eventually clear out usage of this page unless we have a
549 // large need.
550 if (availablePlots.count() && usedPlots && usedPlots <= fNumPlots / 4) {
551 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
552 while (Plot* plot = plotIter.get()) {
553 // If this plot was used recently
554 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
555 // See if there's room in an earlier page and if so evict.
556 // We need to be somewhat harsh here so that a handful of plots that are
557 // consistently in use don't end up locking the page in memory.
558 if (availablePlots.count() > 0) {
559 this->processEvictionAndResetRects(plot);
560 this->processEvictionAndResetRects(availablePlots.back());
561 availablePlots.pop_back();
562 --usedPlots;
563 }
564 if (!usedPlots || !availablePlots.count()) {
565 break;
566 }
567 }
568 plotIter.next();
569 }
570 }
571
572 // If none of the plots in the last page have been used recently, delete it.
573 if (!usedPlots) {
574#ifdef DUMP_ATLAS_DATA
575 if (gDumpAtlasData) {
576 SkDebugf("delete %d\n", fNumActivePages-1);
577 }
578#endif
579 this->deactivateLastPage();
580 fFlushesSinceLastUse = 0;
581 }
582 }
583
584 fPrevFlushToken = startTokenForNextFlush;
585}
586
587bool GrDrawOpAtlas::createPages(
588 GrProxyProvider* proxyProvider, GenerationCounter* generationCounter) {
589 SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
590
591 SkISize dims = {fTextureWidth, fTextureHeight};
592
593 int numPlotsX = fTextureWidth/fPlotWidth;
594 int numPlotsY = fTextureHeight/fPlotHeight;
595
596 for (uint32_t i = 0; i < this->maxPages(); ++i) {
597 GrSwizzle swizzle = proxyProvider->caps()->getReadSwizzle(fFormat, fColorType);
598 sk_sp<GrSurfaceProxy> proxy = proxyProvider->createProxy(
599 fFormat, dims, GrRenderable::kNo, 1, GrMipmapped::kNo, SkBackingFit::kExact,
600 SkBudgeted::kYes, GrProtected::kNo, GrInternalSurfaceFlags::kNone,
601 GrSurfaceProxy::UseAllocator::kNo);
602 if (!proxy) {
603 return false;
604 }
605 fViews[i] = GrSurfaceProxyView(std::move(proxy), kTopLeft_GrSurfaceOrigin, swizzle);
606
607 // set up allocated plots
608 fPages[i].fPlotArray = std::make_unique<sk_sp<Plot>[]>(numPlotsX * numPlotsY);
609
610 sk_sp<Plot>* currPlot = fPages[i].fPlotArray.get();
611 for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) {
612 for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) {
613 uint32_t plotIndex = r * numPlotsX + c;
614 currPlot->reset(new Plot(
615 i, plotIndex, generationCounter, x, y, fPlotWidth, fPlotHeight, fColorType));
616
617 // build LRU list
618 fPages[i].fPlotList.addToHead(currPlot->get());
619 ++currPlot;
620 }
621 }
622
623 }
624
625 return true;
626}
627
628
629bool GrDrawOpAtlas::activateNewPage(GrResourceProvider* resourceProvider) {
630 SkASSERT(fNumActivePages < this->maxPages());
631
632 if (!fViews[fNumActivePages].proxy()->instantiate(resourceProvider)) {
633 return false;
634 }
635
636#ifdef DUMP_ATLAS_DATA
637 if (gDumpAtlasData) {
638 SkDebugf("activated page#: %d\n", fNumActivePages);
639 }
640#endif
641
642 ++fNumActivePages;
643 return true;
644}
645
646
647inline void GrDrawOpAtlas::deactivateLastPage() {
648 SkASSERT(fNumActivePages);
649
650 uint32_t lastPageIndex = fNumActivePages - 1;
651
652 int numPlotsX = fTextureWidth/fPlotWidth;
653 int numPlotsY = fTextureHeight/fPlotHeight;
654
655 fPages[lastPageIndex].fPlotList.reset();
656 for (int r = 0; r < numPlotsY; ++r) {
657 for (int c = 0; c < numPlotsX; ++c) {
658 uint32_t plotIndex = r * numPlotsX + c;
659
660 Plot* currPlot = fPages[lastPageIndex].fPlotArray[plotIndex].get();
661 currPlot->resetRects();
662 currPlot->resetFlushesSinceLastUsed();
663
664 // rebuild the LRU list
665 SkDEBUGCODE(currPlot->fPrev = currPlot->fNext = nullptr);
666 SkDEBUGCODE(currPlot->fList = nullptr);
667 fPages[lastPageIndex].fPlotList.addToHead(currPlot);
668 }
669 }
670
671 // remove ref to the backing texture
672 fViews[lastPageIndex].proxy()->deinstantiate();
673 --fNumActivePages;
674}
675
676GrDrawOpAtlasConfig::GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes) {
677 static const SkISize kARGBDimensions[] = {
678 {256, 256}, // maxBytes < 2^19
679 {512, 256}, // 2^19 <= maxBytes < 2^20
680 {512, 512}, // 2^20 <= maxBytes < 2^21
681 {1024, 512}, // 2^21 <= maxBytes < 2^22
682 {1024, 1024}, // 2^22 <= maxBytes < 2^23
683 {2048, 1024}, // 2^23 <= maxBytes
684 };
685
686 // Index 0 corresponds to maxBytes of 2^18, so start by dividing it by that
687 maxBytes >>= 18;
688 // Take the floor of the log to get the index
689 int index = maxBytes > 0
690 ? SkTPin<int>(SkPrevLog2(maxBytes), 0, SK_ARRAY_COUNT(kARGBDimensions) - 1)
691 : 0;
692
693 SkASSERT(kARGBDimensions[index].width() <= kMaxAtlasDim);
694 SkASSERT(kARGBDimensions[index].height() <= kMaxAtlasDim);
695 fARGBDimensions.set(std::min<int>(kARGBDimensions[index].width(), maxTextureSize),
696 std::min<int>(kARGBDimensions[index].height(), maxTextureSize));
697 fMaxTextureSize = std::min<int>(maxTextureSize, kMaxAtlasDim);
698}
699
700SkISize GrDrawOpAtlasConfig::atlasDimensions(GrMaskFormat type) const {
701 if (kA8_GrMaskFormat == type) {
702 // A8 is always 2x the ARGB dimensions, clamped to the max allowed texture size
703 return { std::min<int>(2 * fARGBDimensions.width(), fMaxTextureSize),
704 std::min<int>(2 * fARGBDimensions.height(), fMaxTextureSize) };
705 } else {
706 return fARGBDimensions;
707 }
708}
709
710SkISize GrDrawOpAtlasConfig::plotDimensions(GrMaskFormat type) const {
711 if (kA8_GrMaskFormat == type) {
712 SkISize atlasDimensions = this->atlasDimensions(type);
713 // For A8 we want to grow the plots at larger texture sizes to accept more of the
714 // larger SDF glyphs. Since the largest SDF glyph can be 170x170 with padding, this
715 // allows us to pack 3 in a 512x256 plot, or 9 in a 512x512 plot.
716
717 // This will give us 512x256 plots for 2048x1024, 512x512 plots for 2048x2048,
718 // and 256x256 plots otherwise.
719 int plotWidth = atlasDimensions.width() >= 2048 ? 512 : 256;
720 int plotHeight = atlasDimensions.height() >= 2048 ? 512 : 256;
721
722 return { plotWidth, plotHeight };
723 } else {
724 // ARGB and LCD always use 256x256 plots -- this has been shown to be faster
725 return { 256, 256 };
726 }
727}
728
729constexpr int GrDrawOpAtlasConfig::kMaxAtlasDim;
730