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