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 "GrDrawOpAtlas.h"
9 
10 #include "GrContext.h"
11 #include "GrContextPriv.h"
12 #include "GrOnFlushResourceProvider.h"
13 #include "GrOpFlushState.h"
14 #include "GrRectanizer.h"
15 #include "GrProxyProvider.h"
16 #include "GrResourceProvider.h"
17 #include "GrSurfaceProxyPriv.h"
18 #include "GrTexture.h"
19 #include "GrTracing.h"
20 
21 // When proxy allocation is deferred until flush time the proxies acting as atlases require
22 // special handling. This is because the usage that can be determined from the ops themselves
23 // isn't sufficient. Independent of the ops there will be ASAP and inline uploads to the
24 // atlases. Extending the usage interval of any op that uses an atlas to the start of the
25 // flush (as is done for proxies that are used for sw-generated masks) also won't work because
26 // the atlas persists even beyond the last use in an op - for a given flush. Given this, atlases
27 // must explicitly manage the lifetime of their backing proxies via the onFlushCallback system
28 // (which calls this method).
29 void GrDrawOpAtlas::instantiate(GrOnFlushResourceProvider* onFlushResourceProvider) {
30     for (uint32_t i = 0; i < fNumActivePages; ++i) {
31         // All the atlas pages are now instantiated at flush time in the activeNewPage method.
32         SkASSERT(fProxies[i] && fProxies[i]->priv().isInstantiated());
33     }
34 }
35 
36 std::unique_ptr<GrDrawOpAtlas> GrDrawOpAtlas::Make(GrProxyProvider* proxyProvider,
37                                                    GrPixelConfig config, int width,
38                                                    int height, int numPlotsX, int numPlotsY,
39                                                    AllowMultitexturing allowMultitexturing,
40                                                    GrDrawOpAtlas::EvictionFunc func, void* data) {
41     std::unique_ptr<GrDrawOpAtlas> atlas(new GrDrawOpAtlas(proxyProvider, config, width, height,
42                                                            numPlotsX, numPlotsY,
43                                                            allowMultitexturing));
44     if (!atlas->getProxies()[0]) {
45         return nullptr;
46     }
47 
48     atlas->registerEvictionCallback(func, data);
49     return atlas;
50 }
51 
52 #ifdef DUMP_ATLAS_DATA
53 static bool gDumpAtlasData = false;
54 #endif
55 
56 ////////////////////////////////////////////////////////////////////////////////
57 GrDrawOpAtlas::Plot::Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY,
58                           int width, int height, GrPixelConfig config)
59         : fLastUpload(GrDeferredUploadToken::AlreadyFlushedToken())
60         , fLastUse(GrDeferredUploadToken::AlreadyFlushedToken())
61         , fFlushesSinceLastUse(0)
62         , fPageIndex(pageIndex)
63         , fPlotIndex(plotIndex)
64         , fGenID(genID)
65         , fID(CreateId(fPageIndex, fPlotIndex, fGenID))
66         , fData(nullptr)
67         , fWidth(width)
68         , fHeight(height)
69         , fX(offX)
70         , fY(offY)
71         , fRects(nullptr)
72         , fOffset(SkIPoint16::Make(fX * fWidth, fY * fHeight))
73         , fConfig(config)
74         , fBytesPerPixel(GrBytesPerPixel(config))
75 #ifdef SK_DEBUG
76         , fDirty(false)
77 #endif
78 {
79     // We expect the allocated dimensions to be a multiple of 4 bytes
80     SkASSERT(((width*fBytesPerPixel) & 0x3) == 0);
81     // The padding for faster uploads only works for 1, 2 and 4 byte texels
82     SkASSERT(fBytesPerPixel != 3 && fBytesPerPixel <= 4);
83     fDirtyRect.setEmpty();
84 }
85 
86 GrDrawOpAtlas::Plot::~Plot() {
87     sk_free(fData);
88     delete fRects;
89 }
90 
91 bool GrDrawOpAtlas::Plot::addSubImage(int width, int height, const void* image, SkIPoint16* loc) {
92     SkASSERT(width <= fWidth && height <= fHeight);
93 
94     if (!fRects) {
95         fRects = GrRectanizer::Factory(fWidth, fHeight);
96     }
97 
98     if (!fRects->addRect(width, height, loc)) {
99         return false;
100     }
101 
102     if (!fData) {
103         fData = reinterpret_cast<unsigned char*>(sk_calloc_throw(fBytesPerPixel * fWidth *
104                                                                  fHeight));
105     }
106     size_t rowBytes = width * fBytesPerPixel;
107     const unsigned char* imagePtr = (const unsigned char*)image;
108     // point ourselves at the right starting spot
109     unsigned char* dataPtr = fData;
110     dataPtr += fBytesPerPixel * fWidth * loc->fY;
111     dataPtr += fBytesPerPixel * loc->fX;
112     // copy into the data buffer, swizzling as we go if this is ARGB data
113     if (4 == fBytesPerPixel && kSkia8888_GrPixelConfig == kBGRA_8888_GrPixelConfig) {
114         for (int i = 0; i < height; ++i) {
115             SkOpts::RGBA_to_BGRA(reinterpret_cast<uint32_t*>(dataPtr), imagePtr, width);
116             dataPtr += fBytesPerPixel * fWidth;
117             imagePtr += rowBytes;
118         }
119     } else {
120         for (int i = 0; i < height; ++i) {
121             memcpy(dataPtr, imagePtr, rowBytes);
122             dataPtr += fBytesPerPixel * fWidth;
123             imagePtr += rowBytes;
124         }
125     }
126 
127     fDirtyRect.join(loc->fX, loc->fY, loc->fX + width, loc->fY + height);
128 
129     loc->fX += fOffset.fX;
130     loc->fY += fOffset.fY;
131     SkDEBUGCODE(fDirty = true;)
132 
133     return true;
134 }
135 
136 void GrDrawOpAtlas::Plot::uploadToTexture(GrDeferredTextureUploadWritePixelsFn& writePixels,
137                                           GrTextureProxy* proxy) {
138     // We should only be issuing uploads if we are in fact dirty
139     SkASSERT(fDirty && fData && proxy && proxy->priv().peekTexture());
140     TRACE_EVENT0("skia.gpu", TRACE_FUNC);
141     size_t rowBytes = fBytesPerPixel * fWidth;
142     const unsigned char* dataPtr = fData;
143     // Clamp to 4-byte aligned boundaries
144     unsigned int clearBits = 0x3 / fBytesPerPixel;
145     fDirtyRect.fLeft &= ~clearBits;
146     fDirtyRect.fRight += clearBits;
147     fDirtyRect.fRight &= ~clearBits;
148     SkASSERT(fDirtyRect.fRight <= fWidth);
149     // Set up dataPtr
150     dataPtr += rowBytes * fDirtyRect.fTop;
151     dataPtr += fBytesPerPixel * fDirtyRect.fLeft;
152     // TODO: Make GrDrawOpAtlas store a GrColorType rather than GrPixelConfig.
153     auto colorType = GrPixelConfigToColorType(fConfig);
154     writePixels(proxy, fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop,
155                 fDirtyRect.width(), fDirtyRect.height(), colorType, dataPtr, rowBytes);
156     fDirtyRect.setEmpty();
157     SkDEBUGCODE(fDirty = false;)
158 }
159 
160 void GrDrawOpAtlas::Plot::resetRects() {
161     if (fRects) {
162         fRects->reset();
163     }
164 
165     fGenID++;
166     fID = CreateId(fPageIndex, fPlotIndex, fGenID);
167     fLastUpload = GrDeferredUploadToken::AlreadyFlushedToken();
168     fLastUse = GrDeferredUploadToken::AlreadyFlushedToken();
169 
170     // zero out the plot
171     if (fData) {
172         sk_bzero(fData, fBytesPerPixel * fWidth * fHeight);
173     }
174 
175     fDirtyRect.setEmpty();
176     SkDEBUGCODE(fDirty = false;)
177 }
178 
179 ///////////////////////////////////////////////////////////////////////////////
180 
181 GrDrawOpAtlas::GrDrawOpAtlas(GrProxyProvider* proxyProvider,
182                              GrPixelConfig config, int width, int height,
183                              int numPlotsX, int numPlotsY, AllowMultitexturing allowMultitexturing)
184         : fPixelConfig(config)
185         , fTextureWidth(width)
186         , fTextureHeight(height)
187         , fAtlasGeneration(kInvalidAtlasGeneration + 1)
188         , fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken())
189         , fAllowMultitexturing(allowMultitexturing)
190         , fNumActivePages(0) {
191     fPlotWidth = fTextureWidth / numPlotsX;
192     fPlotHeight = fTextureHeight / numPlotsY;
193     SkASSERT(numPlotsX * numPlotsY <= BulkUseTokenUpdater::kMaxPlots);
194     SkASSERT(fPlotWidth * numPlotsX == fTextureWidth);
195     SkASSERT(fPlotHeight * numPlotsY == fTextureHeight);
196 
197     fNumPlots = numPlotsX * numPlotsY;
198 
199     this->createPages(proxyProvider);
200 }
201 
202 inline void GrDrawOpAtlas::processEviction(AtlasID id) {
203     for (int i = 0; i < fEvictionCallbacks.count(); i++) {
204         (*fEvictionCallbacks[i].fFunc)(id, fEvictionCallbacks[i].fData);
205     }
206     ++fAtlasGeneration;
207 }
208 
209 inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target, AtlasID* id, Plot* plot) {
210     int pageIdx = GetPageIndexFromID(plot->id());
211     this->makeMRU(plot, pageIdx);
212 
213     // If our most recent upload has already occurred then we have to insert a new
214     // upload. Otherwise, we already have a scheduled upload that hasn't yet ocurred.
215     // This new update will piggy back on that previously scheduled update.
216     if (plot->lastUploadToken() < target->tokenTracker()->nextTokenToFlush()) {
217         // With c+14 we could move sk_sp into lamba to only ref once.
218         sk_sp<Plot> plotsp(SkRef(plot));
219 
220         GrTextureProxy* proxy = fProxies[pageIdx].get();
221         SkASSERT(proxy->priv().isInstantiated());  // This is occurring at flush time
222 
223         GrDeferredUploadToken lastUploadToken = target->addASAPUpload(
224                 [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
225                     plotsp->uploadToTexture(writePixels, proxy);
226                 });
227         plot->setLastUploadToken(lastUploadToken);
228     }
229     *id = plot->id();
230     return true;
231 }
232 
233 // Number of atlas-related flushes beyond which we consider a plot to no longer be in use.
234 //
235 // This value is somewhat arbitrary -- the idea is to keep it low enough that
236 // a page with unused plots will get removed reasonably quickly, but allow it
237 // to hang around for a bit in case it's needed. The assumption is that flushes
238 // are rare; i.e., we are not continually refreshing the frame.
239 static constexpr auto kRecentlyUsedCount = 256;
240 
241 bool GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider,
242                                AtlasID* id, GrDeferredUploadTarget* target,
243                                int width, int height, const void* image, SkIPoint16* loc) {
244     if (width > fPlotWidth || height > fPlotHeight) {
245         return false;
246     }
247 
248     // Look through each page to see if we can upload without having to flush
249     // We prioritize this upload to the first pages, not the most recently used, to make it easier
250     // to remove unused pages in reverse page order.
251     for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
252         SkASSERT(fProxies[pageIdx]);
253         // look through all allocated plots for one we can share, in Most Recently Refed order
254         PlotList::Iter plotIter;
255         plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
256         Plot* plot;
257         while ((plot = plotIter.get())) {
258             SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
259             if (plot->addSubImage(width, height, image, loc)) {
260                 return this->updatePlot(target, id, plot);
261             }
262             plotIter.next();
263         }
264     }
265 
266     // If the above fails, then see if the least recently used plot per page has already been
267     // flushed to the gpu if we're at max page allocation, or if the plot has aged out otherwise.
268     // We wait until we've grown to the full number of pages to begin evicting already flushed
269     // plots so that we can maximize the opportunity for reuse.
270     // As before we prioritize this upload to the first pages, not the most recently used.
271     for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
272         Plot* plot = fPages[pageIdx].fPlotList.tail();
273         SkASSERT(plot);
274         if ((fNumActivePages == this->maxPages() &&
275              plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush()) ||
276             plot->flushesSinceLastUsed() >= kRecentlyUsedCount) {
277             this->processEvictionAndResetRects(plot);
278             SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
279             SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc);
280             SkASSERT(verify);
281             if (!this->updatePlot(target, id, plot)) {
282                 return false;
283             }
284             return true;
285         }
286     }
287 
288     // If the simple cases fail, try to create a new page and add to it
289     if (this->activateNewPage(resourceProvider)) {
290         unsigned int pageIdx = fNumActivePages-1;
291         SkASSERT(fProxies[pageIdx] && fProxies[pageIdx]->priv().isInstantiated());
292 
293         Plot* plot = fPages[pageIdx].fPlotList.head();
294         SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
295         if (plot->addSubImage(width, height, image, loc)) {
296             return this->updatePlot(target, id, plot);
297         }
298 
299         // we shouldn't get here -- if so, something has gone terribly wrong
300         SkASSERT(false);
301         return false;
302     }
303 
304     // Try to find a plot that we can perform an inline upload to.
305     // We prioritize this upload in reverse order of pages to counterbalance the order above.
306     Plot* plot = nullptr;
307     for (int pageIdx = ((int)fNumActivePages)-1; pageIdx >= 0; --pageIdx) {
308         Plot* currentPlot = fPages[pageIdx].fPlotList.tail();
309         if (currentPlot->lastUseToken() != target->tokenTracker()->nextDrawToken()) {
310             plot = currentPlot;
311             break;
312         }
313     }
314 
315     // If we can't find a plot that is not used in a draw currently being prepared by an op, then
316     // we have to fail. This gives the op a chance to enqueue the draw, and call back into this
317     // function. When that draw is enqueued, the draw token advances, and the subsequent call will
318     // continue past this branch and prepare an inline upload that will occur after the enqueued
319     //draw which references the plot's pre-upload content.
320     if (!plot) {
321         return false;
322     }
323 
324     this->processEviction(plot->id());
325     int pageIdx = GetPageIndexFromID(plot->id());
326     fPages[pageIdx].fPlotList.remove(plot);
327     sk_sp<Plot>& newPlot = fPages[pageIdx].fPlotArray[plot->index()];
328     newPlot.reset(plot->clone());
329 
330     fPages[pageIdx].fPlotList.addToHead(newPlot.get());
331     SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == newPlot->bpp());
332     SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, loc);
333     SkASSERT(verify);
334 
335     // Note that this plot will be uploaded inline with the draws whereas the
336     // one it displaced most likely was uploaded ASAP.
337     // With c+14 we could move sk_sp into lambda to only ref once.
338     sk_sp<Plot> plotsp(SkRef(newPlot.get()));
339 
340     GrTextureProxy* proxy = fProxies[pageIdx].get();
341     SkASSERT(proxy->priv().isInstantiated());
342 
343     GrDeferredUploadToken lastUploadToken = target->addInlineUpload(
344             [plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
345                 plotsp->uploadToTexture(writePixels, proxy);
346             });
347     newPlot->setLastUploadToken(lastUploadToken);
348 
349     *id = newPlot->id();
350 
351     return true;
352 }
353 
354 void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) {
355     if (fNumActivePages <= 1) {
356         fPrevFlushToken = startTokenForNextFlush;
357         return;
358     }
359 
360     // For all plots, reset number of flushes since used if used this frame.
361     PlotList::Iter plotIter;
362     bool atlasUsedThisFlush = false;
363     for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
364         plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
365         while (Plot* plot = plotIter.get()) {
366             // Reset number of flushes since used
367             if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
368                 plot->resetFlushesSinceLastUsed();
369                 atlasUsedThisFlush = true;
370             }
371 
372             plotIter.next();
373         }
374     }
375 
376     // We only try to compact if the atlas was used in the recently completed flush.
377     // This is to handle the case where a lot of text or path rendering has occurred but then just
378     // a blinking cursor is drawn.
379     // TODO: consider if we should also do this if it's been a long time since the last atlas use
380     if (atlasUsedThisFlush) {
381         SkTArray<Plot*> availablePlots;
382         uint32_t lastPageIndex = fNumActivePages - 1;
383 
384         // For all plots but the last one, update number of flushes since used, and check to see
385         // if there are any in the first pages that the last page can safely upload to.
386         for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
387 #ifdef DUMP_ATLAS_DATA
388             if (gDumpAtlasData) {
389                 SkDebugf("page %d: ", pageIndex);
390             }
391 #endif
392             plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
393             while (Plot* plot = plotIter.get()) {
394                 // Update number of flushes since plot was last used
395                 // We only increment the 'sinceLastUsed' count for flushes where the atlas was used
396                 // to avoid deleting everything when we return to text drawing in the blinking
397                 // cursor case
398                 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
399                     plot->incFlushesSinceLastUsed();
400                 }
401 
402 #ifdef DUMP_ATLAS_DATA
403                 if (gDumpAtlasData) {
404                     SkDebugf("%d ", plot->flushesSinceLastUsed());
405                 }
406 #endif
407                 // Count plots we can potentially upload to in all pages except the last one
408                 // (the potential compactee).
409                 if (plot->flushesSinceLastUsed() > kRecentlyUsedCount) {
410                     availablePlots.push_back() = plot;
411                 }
412 
413                 plotIter.next();
414             }
415 #ifdef DUMP_ATLAS_DATA
416             if (gDumpAtlasData) {
417                 SkDebugf("\n");
418             }
419 #endif
420         }
421 
422         // Count recently used plots in the last page and evict any that are no longer in use.
423         // Since we prioritize uploading to the first pages, this will eventually
424         // clear out usage of this page unless we have a large need.
425         plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
426         unsigned int usedPlots = 0;
427 #ifdef DUMP_ATLAS_DATA
428         if (gDumpAtlasData) {
429             SkDebugf("page %d: ", lastPageIndex);
430         }
431 #endif
432         while (Plot* plot = plotIter.get()) {
433             // Update number of flushes since plot was last used
434             if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
435                 plot->incFlushesSinceLastUsed();
436             }
437 
438 #ifdef DUMP_ATLAS_DATA
439             if (gDumpAtlasData) {
440                 SkDebugf("%d ", plot->flushesSinceLastUsed());
441             }
442 #endif
443             // If this plot was used recently
444             if (plot->flushesSinceLastUsed() <= kRecentlyUsedCount) {
445                 usedPlots++;
446             } else if (plot->lastUseToken() != GrDeferredUploadToken::AlreadyFlushedToken()) {
447                 // otherwise if aged out just evict it.
448                 this->processEvictionAndResetRects(plot);
449             }
450             plotIter.next();
451         }
452 #ifdef DUMP_ATLAS_DATA
453         if (gDumpAtlasData) {
454             SkDebugf("\n");
455         }
456 #endif
457 
458         // If recently used plots in the last page are using less than a quarter of the page, try
459         // to evict them if there's available space in earlier pages. Since we prioritize uploading
460         // to the first pages, this will eventually clear out usage of this page unless we have a
461         // large need.
462         if (availablePlots.count() && usedPlots && usedPlots <= fNumPlots / 4) {
463             plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
464             while (Plot* plot = plotIter.get()) {
465                 // If this plot was used recently
466                 if (plot->flushesSinceLastUsed() <= kRecentlyUsedCount) {
467                     // See if there's room in an earlier page and if so evict.
468                     // We need to be somewhat harsh here so that a handful of plots that are
469                     // consistently in use don't end up locking the page in memory.
470                     if (availablePlots.count() > 0) {
471                         this->processEvictionAndResetRects(plot);
472                         this->processEvictionAndResetRects(availablePlots.back());
473                         availablePlots.pop_back();
474                         --usedPlots;
475                     }
476                     if (!usedPlots || !availablePlots.count()) {
477                         break;
478                     }
479                 }
480                 plotIter.next();
481             }
482         }
483 
484         // If none of the plots in the last page have been used recently, delete it.
485         if (!usedPlots) {
486 #ifdef DUMP_ATLAS_DATA
487             if (gDumpAtlasData) {
488                 SkDebugf("delete %d\n", fNumPages-1);
489             }
490 #endif
491             this->deactivateLastPage();
492         }
493     }
494 
495     fPrevFlushToken = startTokenForNextFlush;
496 }
497 
498 bool GrDrawOpAtlas::createPages(GrProxyProvider* proxyProvider) {
499     SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
500 
501     GrSurfaceDesc desc;
502     desc.fFlags = kNone_GrSurfaceFlags;
503     desc.fOrigin = kTopLeft_GrSurfaceOrigin;
504     desc.fWidth = fTextureWidth;
505     desc.fHeight = fTextureHeight;
506     desc.fConfig = fPixelConfig;
507 
508     int numPlotsX = fTextureWidth/fPlotWidth;
509     int numPlotsY = fTextureHeight/fPlotHeight;
510 
511     for (uint32_t i = 0; i < this->maxPages(); ++i) {
512         fProxies[i] = proxyProvider->createProxy(desc, SkBackingFit::kExact, SkBudgeted::kYes,
513                                                  GrResourceProvider::kNoPendingIO_Flag);
514         if (!fProxies[i]) {
515             return false;
516         }
517 
518         // set up allocated plots
519         fPages[i].fPlotArray.reset(new sk_sp<Plot>[ numPlotsX * numPlotsY ]);
520 
521         sk_sp<Plot>* currPlot = fPages[i].fPlotArray.get();
522         for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) {
523             for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) {
524                 uint32_t plotIndex = r * numPlotsX + c;
525                 currPlot->reset(new Plot(i, plotIndex, 1, x, y, fPlotWidth, fPlotHeight,
526                                          fPixelConfig));
527 
528                 // build LRU list
529                 fPages[i].fPlotList.addToHead(currPlot->get());
530                 ++currPlot;
531             }
532         }
533 
534     }
535 
536     return true;
537 }
538 
539 
540 bool GrDrawOpAtlas::activateNewPage(GrResourceProvider* resourceProvider) {
541     if (fNumActivePages >= this->maxPages()) {
542         return false;
543     }
544 
545     if (!fProxies[fNumActivePages]->instantiate(resourceProvider)) {
546         return false;
547     }
548 
549 #ifdef DUMP_ATLAS_DATA
550     if (gDumpAtlasData) {
551         SkDebugf("activated page#: %d\n", fNumActivePages);
552     }
553 #endif
554 
555     ++fNumActivePages;
556     return true;
557 }
558 
559 
560 inline void GrDrawOpAtlas::deactivateLastPage() {
561     SkASSERT(fNumActivePages);
562 
563     uint32_t lastPageIndex = fNumActivePages - 1;
564 
565     int numPlotsX = fTextureWidth/fPlotWidth;
566     int numPlotsY = fTextureHeight/fPlotHeight;
567 
568     fPages[lastPageIndex].fPlotList.reset();
569     for (int r = 0; r < numPlotsY; ++r) {
570         for (int c = 0; c < numPlotsX; ++c) {
571             uint32_t plotIndex = r * numPlotsX + c;
572 
573             Plot* currPlot = fPages[lastPageIndex].fPlotArray[plotIndex].get();
574             currPlot->resetRects();
575             currPlot->resetFlushesSinceLastUsed();
576 
577             // rebuild the LRU list
578             SkDEBUGCODE(currPlot->fPrev = currPlot->fNext = nullptr);
579             SkDEBUGCODE(currPlot->fList = nullptr);
580             fPages[lastPageIndex].fPlotList.addToHead(currPlot);
581         }
582     }
583 
584     // remove ref to the backing texture
585     fProxies[lastPageIndex]->deInstantiate();
586     --fNumActivePages;
587 }
588