Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Member Functions | Friends | List of all members
GrDrawOpAtlas Class Reference

#include <GrDrawOpAtlas.h>

Public Types

enum class  AllowMultitexturing : bool { kNo , kYes }
 
enum class  ErrorCode { kError , kSucceeded , kTryAgain }
 

Public Member Functions

ErrorCode addToAtlas (GrResourceProvider *, GrDeferredUploadTarget *, int width, int height, const void *image, skgpu::AtlasLocator *)
 
const GrSurfaceProxyViewgetViews () const
 
uint64_t atlasGeneration () const
 
bool hasID (const skgpu::PlotLocator &plotLocator)
 
void setLastUseToken (const skgpu::AtlasLocator &atlasLocator, skgpu::AtlasToken token)
 
uint32_t numActivePages ()
 
void setLastUseTokenBulk (const skgpu::BulkUsePlotUpdater &updater, skgpu::AtlasToken token)
 
void compact (skgpu::AtlasToken startTokenForNextFlush)
 
void instantiate (GrOnFlushResourceProvider *)
 
uint32_t maxPages () const
 

Static Public Member Functions

static std::unique_ptr< GrDrawOpAtlasMake (GrProxyProvider *proxyProvider, const GrBackendFormat &format, SkColorType ct, size_t bpp, int width, int height, int plotWidth, int plotHeight, skgpu::AtlasGenerationCounter *generationCounter, AllowMultitexturing allowMultitexturing, skgpu::PlotEvictionCallback *evictor, std::string_view label)
 

Friends

class GrDrawOpAtlasTools
 

Detailed Description

This class manages one or more atlas textures on behalf of GrDrawOps. The draw ops that use the atlas perform texture uploads when preparing their draws during flush. The class provides facilities for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in "ASAP" mode until it is impossible to add data without overwriting texels read by draws that have not yet executed on the gpu. At that point, the atlas will attempt to allocate a new atlas texture (or "page") of the same size, up to a maximum number of textures, and upload to that texture. If that's not possible, the uploads are performed "inline" between draws. If a single draw would use enough subimage space to overflow the atlas texture then the atlas will fail to add a subimage. This gives the op the chance to end the draw and begin a new one. Additional uploads will then succeed in inline mode.

When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e., it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using excess space, periodic garbage collection is needed to shift data from the higher index pages to the lower ones, and then eventually remove any pages that are no longer in use. "In use" is determined by using the GrDrawUploadToken system: After a flush each subarea of the page is checked to see whether it was used in that flush. If less than a quarter of the plots have been used recently (within kPlotRecentlyUsedCount iterations) and there are available plots in lower index pages, the higher index page will be deactivated, and its glyphs will gradually migrate to other pages via the usual upload system.

Garbage collection is initiated by the GrDrawOpAtlas's client via the compact() method. One solution is to make the client a subclass of GrOnFlushCallbackObject, register it with the GrContext via addOnFlushCallbackObject(), and the client's postFlush() method calls compact() and passes in the given GrDrawUploadToken.

Definition at line 53 of file GrDrawOpAtlas.h.

Member Enumeration Documentation

◆ AllowMultitexturing

enum class GrDrawOpAtlas::AllowMultitexturing : bool
strong

Is the atlas allowed to use more than one texture?

Enumerator
kNo 
kYes 

Definition at line 56 of file GrDrawOpAtlas.h.

◆ ErrorCode

enum class GrDrawOpAtlas::ErrorCode
strong

Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if the subimage cannot fit in the atlas without overwriting texels that will be read in the current draw. This indicates that the op should end its current draw and begin another before adding more data. Upon success, an upload of the provided image data will have been added to the GrDrawOp::Target, in "asap" mode if possible, otherwise in "inline" mode. Successive uploads in either mode may be consolidated. 'kError' will be returned when some unrecoverable error was encountered while trying to add the subimage. In this case the op being created should be discarded.

NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call 'setLastUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to addToAtlas might cause the previous data to be overwritten before it has been read.

Enumerator
kError 
kSucceeded 
kTryAgain 

Definition at line 103 of file GrDrawOpAtlas.h.

Member Function Documentation

◆ addToAtlas()

GrDrawOpAtlas::ErrorCode GrDrawOpAtlas::addToAtlas ( GrResourceProvider resourceProvider,
GrDeferredUploadTarget target,
int  width,
int  height,
const void *  image,
skgpu::AtlasLocator atlasLocator 
)

Definition at line 209 of file GrDrawOpAtlas.cpp.

212 {
213 if (width > fPlotWidth || height > fPlotHeight) {
214 return ErrorCode::kError;
215 }
216
217 // Look through each page to see if we can upload without having to flush
218 // We prioritize this upload to the first pages, not the most recently used, to make it easier
219 // to remove unused pages in reverse page order.
220 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
221 if (this->uploadToPage(pageIdx, target, width, height, image, atlasLocator)) {
223 }
224 }
225
226 // If the above fails, then see if the least recently used plot per page has already been
227 // flushed to the gpu if we're at max page allocation, or if the plot has aged out otherwise.
228 // We wait until we've grown to the full number of pages to begin evicting already flushed
229 // plots so that we can maximize the opportunity for reuse.
230 // As before we prioritize this upload to the first pages, not the most recently used.
231 if (fNumActivePages == this->maxPages()) {
232 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
233 Plot* plot = fPages[pageIdx].fPlotList.tail();
234 SkASSERT(plot);
235 if (plot->lastUseToken() < target->tokenTracker()->nextFlushToken()) {
236 this->processEvictionAndResetRects(plot);
237 SkASSERT(GrBackendFormatBytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) ==
238 plot->bpp());
239 SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, atlasLocator);
240 SkASSERT(verify);
241 if (!this->updatePlot(target, atlasLocator, plot)) {
242 return ErrorCode::kError;
243 }
245 }
246 }
247 } else {
248 // If we haven't activated all the available pages, try to create a new one and add to it
249 if (!this->activateNewPage(resourceProvider)) {
250 return ErrorCode::kError;
251 }
252
253 if (this->uploadToPage(fNumActivePages-1, target, width, height, image, atlasLocator)) {
255 } else {
256 // If we fail to upload to a newly activated page then something has gone terribly
257 // wrong - return an error
258 return ErrorCode::kError;
259 }
260 }
261
262 if (!fNumActivePages) {
263 return ErrorCode::kError;
264 }
265
266 // Try to find a plot that we can perform an inline upload to.
267 // We prioritize this upload in reverse order of pages to counterbalance the order above.
268 Plot* plot = nullptr;
269 for (int pageIdx = ((int)fNumActivePages)-1; pageIdx >= 0; --pageIdx) {
270 Plot* currentPlot = fPages[pageIdx].fPlotList.tail();
271 if (currentPlot->lastUseToken() != target->tokenTracker()->nextDrawToken()) {
272 plot = currentPlot;
273 break;
274 }
275 }
276
277 // If we can't find a plot that is not used in a draw currently being prepared by an op, then
278 // we have to fail. This gives the op a chance to enqueue the draw, and call back into this
279 // function. When that draw is enqueued, the draw token advances, and the subsequent call will
280 // continue past this branch and prepare an inline upload that will occur after the enqueued
281 // draw which references the plot's pre-upload content.
282 if (!plot) {
284 }
285
286 this->processEviction(plot->plotLocator());
287 int pageIdx = plot->pageIndex();
288 fPages[pageIdx].fPlotList.remove(plot);
289 sk_sp<Plot>& newPlot = fPages[pageIdx].fPlotArray[plot->plotIndex()];
290 newPlot = plot->clone();
291
292 fPages[pageIdx].fPlotList.addToHead(newPlot.get());
293 SkASSERT(GrBackendFormatBytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) ==
294 newPlot->bpp());
295 SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, atlasLocator);
296 SkASSERT(verify);
297
298 // Note that this plot will be uploaded inline with the draws whereas the
299 // one it displaced most likely was uploaded ASAP.
300 // With c++14 we could move sk_sp into lambda to only ref once.
301 sk_sp<Plot> plotsp(SkRef(newPlot.get()));
302
303 GrTextureProxy* proxy = fViews[pageIdx].asTextureProxy();
304 SkASSERT(proxy && proxy->isInstantiated());
305
306 AtlasToken lastUploadToken = target->addInlineUpload(
307 [this, plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
308 this->uploadPlotToTexture(writePixels, proxy, plotsp.get());
309 });
310 newPlot->setLastUploadToken(lastUploadToken);
311
312 atlasLocator->updatePlotLocator(newPlot->plotLocator());
313 SkDEBUGCODE(this->validate(*atlasLocator);)
314
316}
size_t GrBackendFormatBytesPerPixel(const GrBackendFormat &format)
std::function< bool(GrTextureProxy *, SkIRect, GrColorType srcColorType, const void *, size_t rowBytes)> GrDeferredTextureUploadWritePixelsFn
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
static T * SkRef(T *obj)
Definition SkRefCnt.h:132
uint32_t maxPages() const
T * get() const
Definition SkRefCnt.h:303
void updatePlotLocator(PlotLocator p)
Definition AtlasTypes.h:337
skgpu::AtlasToken lastUseToken() const
Definition AtlasTypes.h:479
sk_sp< SkImage > image
Definition examples.cpp:29
uint32_t * target
static void plot(SkCanvas *canvas, const char *fn, float xMin, float xMax, float yMin, float yMax, const char *label=nullptr, bool requireES3=false)
int32_t height
int32_t width

◆ atlasGeneration()

uint64_t GrDrawOpAtlas::atlasGeneration ( ) const
inline

Definition at line 114 of file GrDrawOpAtlas.h.

114{ return fAtlasGeneration; }

◆ compact()

void GrDrawOpAtlas::compact ( skgpu::AtlasToken  startTokenForNextFlush)

Definition at line 318 of file GrDrawOpAtlas.cpp.

318 {
319 if (fNumActivePages < 1) {
320 fPrevFlushToken = startTokenForNextFlush;
321 return;
322 }
323
324 // For all plots, reset number of flushes since used if used this frame.
325 PlotList::Iter plotIter;
326 bool atlasUsedThisFlush = false;
327 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
328 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
329 while (Plot* plot = plotIter.get()) {
330 // Reset number of flushes since used
331 if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
332 plot->resetFlushesSinceLastUsed();
333 atlasUsedThisFlush = true;
334 }
335
336 plotIter.next();
337 }
338 }
339
340 if (atlasUsedThisFlush) {
341 fFlushesSinceLastUse = 0;
342 } else {
343 ++fFlushesSinceLastUse;
344 }
345
346 // We only try to compact if the atlas was used in the recently completed flush or
347 // hasn't been used in a long time.
348 // This is to handle the case where a lot of text or path rendering has occurred but then just
349 // a blinking cursor is drawn.
350 if (atlasUsedThisFlush || fFlushesSinceLastUse > kAtlasRecentlyUsedCount) {
351 TArray<Plot*> availablePlots;
352 uint32_t lastPageIndex = fNumActivePages - 1;
353
354 // For all plots but the last one, update number of flushes since used, and check to see
355 // if there are any in the first pages that the last page can safely upload to.
356 for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
357 if constexpr (kDumpAtlasData) {
358 SkDebugf("page %u: ", pageIndex);
359 }
360
361 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
362 while (Plot* plot = plotIter.get()) {
363 // Update number of flushes since plot was last used
364 // We only increment the 'sinceLastUsed' count for flushes where the atlas was used
365 // to avoid deleting everything when we return to text drawing in the blinking
366 // cursor case
367 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
368 plot->incFlushesSinceLastUsed();
369 }
370
371 if constexpr (kDumpAtlasData) {
372 SkDebugf("%d ", plot->flushesSinceLastUsed());
373 }
374
375 // Count plots we can potentially upload to in all pages except the last one
376 // (the potential compactee).
377 if (plot->flushesSinceLastUsed() > kPlotRecentlyUsedCount) {
378 availablePlots.push_back() = plot;
379 }
380
381 plotIter.next();
382 }
383
384 if constexpr (kDumpAtlasData) {
385 SkDebugf("\n");
386 }
387 }
388
389 // Count recently used plots in the last page and evict any that are no longer in use.
390 // Since we prioritize uploading to the first pages, this will eventually
391 // clear out usage of this page unless we have a large need.
392 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
393 unsigned int usedPlots = 0;
394 if constexpr (kDumpAtlasData) {
395 SkDebugf("page %u: ", lastPageIndex);
396 }
397
398 while (Plot* plot = plotIter.get()) {
399 // Update number of flushes since plot was last used
400 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
401 plot->incFlushesSinceLastUsed();
402 }
403
404 if constexpr (kDumpAtlasData) {
405 SkDebugf("%d ", plot->flushesSinceLastUsed());
406 }
407
408 // If this plot was used recently
409 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
410 usedPlots++;
411 } else if (plot->lastUseToken() != AtlasToken::InvalidToken()) {
412 // otherwise if aged out just evict it.
413 this->processEvictionAndResetRects(plot);
414 }
415 plotIter.next();
416 }
417
418 if constexpr (kDumpAtlasData) {
419 SkDebugf("\n");
420 }
421
422 // If recently used plots in the last page are using less than a quarter of the page, try
423 // to evict them if there's available space in earlier pages. Since we prioritize uploading
424 // to the first pages, this will eventually clear out usage of this page unless we have a
425 // large need.
426 if (!availablePlots.empty() && usedPlots && usedPlots <= fNumPlots / 4) {
427 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
428 while (Plot* plot = plotIter.get()) {
429 // If this plot was used recently
430 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
431 // See if there's room in an earlier page and if so evict.
432 // We need to be somewhat harsh here so that a handful of plots that are
433 // consistently in use don't end up locking the page in memory.
434 if (!availablePlots.empty()) {
435 this->processEvictionAndResetRects(plot);
436 this->processEvictionAndResetRects(availablePlots.back());
437 availablePlots.pop_back();
438 --usedPlots;
439 }
440 if (!usedPlots || availablePlots.empty()) {
441 break;
442 }
443 }
444 plotIter.next();
445 }
446 }
447
448 // If none of the plots in the last page have been used recently, delete it.
449 if (!usedPlots) {
450 if constexpr (kDumpAtlasData) {
451 SkDebugf("delete %u\n", fNumActivePages - 1);
452 }
453
454 this->deactivateLastPage();
455 fFlushesSinceLastUse = 0;
456 }
457 }
458
459 fPrevFlushToken = startTokenForNextFlush;
460}
static constexpr auto kPlotRecentlyUsedCount
static constexpr auto kAtlasRecentlyUsedCount
static const constexpr bool kDumpAtlasData
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
AtlasToken next() const
Definition AtlasTypes.h:185
static AtlasToken InvalidToken()
Definition AtlasTypes.h:153
bool empty() const
Definition SkTArray.h:194

◆ getViews()

const GrSurfaceProxyView * GrDrawOpAtlas::getViews ( ) const
inline

Definition at line 112 of file GrDrawOpAtlas.h.

112{ return fViews; }

◆ hasID()

bool GrDrawOpAtlas::hasID ( const skgpu::PlotLocator plotLocator)
inline

Definition at line 116 of file GrDrawOpAtlas.h.

116 {
117 if (!plotLocator.isValid()) {
118 return false;
119 }
120
121 uint32_t plot = plotLocator.plotIndex();
122 uint32_t page = plotLocator.pageIndex();
123 uint64_t plotGeneration = fPages[page].fPlotArray[plot]->genID();
124 uint64_t locatorGeneration = plotLocator.genID();
125 return plot < fNumPlots && page < fNumActivePages && plotGeneration == locatorGeneration;
126 }
uint64_t genID() const
Definition AtlasTypes.h:280
uint32_t plotIndex() const
Definition AtlasTypes.h:279
bool isValid() const
Definition AtlasTypes.h:262
uint32_t pageIndex() const
Definition AtlasTypes.h:278

◆ instantiate()

void GrDrawOpAtlas::instantiate ( GrOnFlushResourceProvider onFlushResourceProvider)

Definition at line 63 of file GrDrawOpAtlas.cpp.

63 {
64 for (uint32_t i = 0; i < fNumActivePages; ++i) {
65 // All the atlas pages are now instantiated at flush time in the activeNewPage method.
66 SkASSERT(fViews[i].proxy() && fViews[i].proxy()->isInstantiated());
67 }
68}

◆ Make()

std::unique_ptr< GrDrawOpAtlas > GrDrawOpAtlas::Make ( GrProxyProvider proxyProvider,
const GrBackendFormat format,
SkColorType  ct,
size_t  bpp,
int  width,
int  height,
int  plotWidth,
int  plotHeight,
skgpu::AtlasGenerationCounter generationCounter,
AllowMultitexturing  allowMultitexturing,
skgpu::PlotEvictionCallback evictor,
std::string_view  label 
)
static

Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas should only be used inside of GrMeshDrawOp::onPrepareDraws.

Parameters
proxyProviderUsed to create the atlas's texture proxies.
formatBackend format for the atlas's textures. Should be compatible with ct.
ctThe colorType which this atlas will store.
bppSize in bytes of each pixel.
widthWidth in pixels of the atlas.
heightHeight in pixels of the atlas.
plotWidthThe width of each plot. width/plotWidth should be an integer.
plotWidthThe height of each plot. height/plotHeight should be an integer.
generationCounterA pointer to the context's generation counter.
allowMultitexturingCan the atlas use more than one texture.
evictorA pointer to an eviction callback class.
labelA label for the atlas texture.
Returns
An initialized DrawAtlas, or nullptr if creation fails.

Definition at line 70 of file GrDrawOpAtlas.cpp.

77 {
78 if (!format.isValid()) {
79 return nullptr;
80 }
81
82 std::unique_ptr<GrDrawOpAtlas> atlas(new GrDrawOpAtlas(proxyProvider, format, colorType, bpp,
83 width, height, plotWidth, plotHeight,
84 generationCounter,
85 allowMultitexturing, label));
86 if (!atlas->createPages(proxyProvider, generationCounter) || !atlas->getViews()[0].proxy()) {
87 return nullptr;
88 }
89
90 if (evictor != nullptr) {
91 atlas->fEvictionCallbacks.emplace_back(evictor);
92 }
93 return atlas;
94}
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
uint32_t uint32_t * format
sk_sp< const SkImage > atlas
Definition SkRecords.h:331

◆ maxPages()

uint32_t GrDrawOpAtlas::maxPages ( ) const
inline

Definition at line 161 of file GrDrawOpAtlas.h.

161 {
162 return fMaxPages;
163 }

◆ numActivePages()

uint32_t GrDrawOpAtlas::numActivePages ( )
inline

Definition at line 140 of file GrDrawOpAtlas.h.

140{ return fNumActivePages; }

◆ setLastUseToken()

void GrDrawOpAtlas::setLastUseToken ( const skgpu::AtlasLocator atlasLocator,
skgpu::AtlasToken  token 
)
inline

To ensure the atlas does not evict a given entry, the client must set the last use token.

Definition at line 129 of file GrDrawOpAtlas.h.

129 {
130 SkASSERT(this->hasID(atlasLocator.plotLocator()));
131 uint32_t plotIdx = atlasLocator.plotIndex();
132 SkASSERT(plotIdx < fNumPlots);
133 uint32_t pageIdx = atlasLocator.pageIndex();
134 SkASSERT(pageIdx < fNumActivePages);
135 skgpu::Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get();
136 this->makeMRU(plot, pageIdx);
137 plot->setLastUseToken(token);
138 }
bool hasID(const skgpu::PlotLocator &plotLocator)
uint32_t plotIndex() const
Definition AtlasTypes.h:305
PlotLocator plotLocator() const
Definition AtlasTypes.h:301
uint32_t pageIndex() const
Definition AtlasTypes.h:303

◆ setLastUseTokenBulk()

void GrDrawOpAtlas::setLastUseTokenBulk ( const skgpu::BulkUsePlotUpdater updater,
skgpu::AtlasToken  token 
)
inline

Definition at line 142 of file GrDrawOpAtlas.h.

143 {
144 int count = updater.count();
145 for (int i = 0; i < count; i++) {
146 const skgpu::BulkUsePlotUpdater::PlotData& pd = updater.plotData(i);
147 // it's possible we've added a plot to the updater and subsequently the plot's page
148 // was deleted -- so we check to prevent a crash
149 if (pd.fPageIndex < fNumActivePages) {
150 skgpu::Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get();
151 this->makeMRU(plot, pd.fPageIndex);
152 plot->setLastUseToken(token);
153 }
154 }
155 }
int count
const PlotData & plotData(int index) const
Definition AtlasTypes.h:410

Friends And Related Symbol Documentation

◆ GrDrawOpAtlasTools

friend class GrDrawOpAtlasTools
friend

Definition at line 166 of file GrDrawOpAtlas.h.


The documentation for this class was generated from the following files: