Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Member Functions | List of all members
skgpu::graphite::DrawAtlas Class Reference

#include <DrawAtlas.h>

Public Types

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

Public Member Functions

ErrorCode addToAtlas (Recorder *, int width, int height, const void *image, AtlasLocator *)
 
ErrorCode addRect (Recorder *, int width, int height, AtlasLocator *)
 
SkIPoint prepForRender (const AtlasLocator &, SkAutoPixmapStorage *)
 
bool recordUploads (DrawContext *, Recorder *)
 
const sk_sp< TextureProxy > * getProxies () const
 
uint32_t atlasID () const
 
uint64_t atlasGeneration () const
 
uint32_t numActivePages () const
 
unsigned int numPlots () const
 
SkISize plotSize () const
 
bool hasID (const PlotLocator &plotLocator)
 
void setLastUseToken (const AtlasLocator &atlasLocator, AtlasToken token)
 
void setLastUseTokenBulk (const BulkUsePlotUpdater &updater, AtlasToken token)
 
void compact (AtlasToken startTokenForNextFlush)
 
void evictAllPlots ()
 
uint32_t maxPages () const
 
int numAllocated_TestingOnly () const
 
void setMaxPages_TestingOnly (uint32_t maxPages)
 

Static Public Member Functions

static std::unique_ptr< DrawAtlasMake (SkColorType ct, size_t bpp, int width, int height, int plotWidth, int plotHeight, AtlasGenerationCounter *generationCounter, AllowMultitexturing allowMultitexturing, PlotEvictionCallback *evictor, std::string_view label)
 

Detailed Description

TODO: the process described here is tentative, and this comment revised once locked down.

This class manages one or more atlas textures on behalf of primitive draws in Device. The drawing processes that use the atlas add preceding UploadTasks when generating RenderPassTasks. The class provides facilities for using DrawTokens to detect data hazards. Plots that need uploads are tracked until it is impossible to add data without overwriting texels read by draws that have not yet been snapped to a RenderPassTask. 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, then the atlas will fail to add a subimage. This gives the Device the chance to end the current draw, snap a RenderpassTask, and begin a new one. Additional uploads will then succeed.

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 AtlasToken system: After a DrawPass is snapped a subarea of the page, or "plot" is checked to see whether it was used in that DrawPass. 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 DrawAtlas's client via the compact() method.

Definition at line 53 of file DrawAtlas.h.

Member Enumeration Documentation

◆ AllowMultitexturing

Is the atlas allowed to use more than one texture?

Enumerator
kNo 
kYes 

Definition at line 56 of file DrawAtlas.h.

◆ ErrorCode

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 list of draws. This indicates that the Device should end its current draw, snap a DrawPass, and begin another before adding more data. 'kError' will be returned when some unrecoverable error was encountered while trying to add the subimage. In this case the draw being created should be discarded.

This tracking does not generate UploadTasks per se. Instead, when the RenderPassTask is ready to be snapped, recordUploads() will be called by the Device and that will generate the necessary UploadTasks. If the useCachedUploads argument in recordUploads() is true, this will generate uploads for the entire area of each Plot that has changed since the last eviction. Otherwise it will only generate uploads for newly added changes.

NOTE: When a draw that reads from the atlas is added to the DrawList, the client using this DrawAtlas must immediately call 'setLastUseToken' with the currentToken from the Recorder, 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 101 of file DrawAtlas.h.

Member Function Documentation

◆ addRect()

DrawAtlas::ErrorCode skgpu::graphite::DrawAtlas::addRect ( Recorder recorder,
int  width,
int  height,
AtlasLocator atlasLocator 
)

Definition at line 185 of file DrawAtlas.cpp.

187 {
188 if (width > fPlotWidth || height > fPlotHeight || width < 0 || height < 0) {
189 return ErrorCode::kError;
190 }
191
192 // We permit zero-sized rects to allow inverse fills in the PathAtlases to work,
193 // but we don't want to enter them in the Rectanizer. So we handle this special case here.
194 // For text this should be caught at a higher level, but if not the only end result
195 // will be rendering a degenerate quad.
196 if (width == 0 || height == 0) {
197 if (fNumActivePages == 0) {
198 // Make sure we have a Page for the AtlasLocator to refer to
199 this->activateNewPage(recorder);
200 }
201 atlasLocator->updateRect(skgpu::IRect16::MakeXYWH(0, 0, 0, 0));
202 // Use the MRU Plot from the first Page
203 atlasLocator->updatePlotLocator(fPages[0].fPlotList.head()->plotLocator());
205 }
206
207 // Look through each page to see if we can upload without having to flush
208 // We prioritize this upload to the first pages, not the most recently used, to make it easier
209 // to remove unused pages in reverse page order.
210 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
211 if (this->addRectToPage(pageIdx, width, height, atlasLocator)) {
213 }
214 }
215
216 // If the above fails, then see if the least recently used plot per page has already been
217 // queued for upload if we're at max page allocation, or if the plot has aged out otherwise.
218 // We wait until we've grown to the full number of pages to begin evicting already queued
219 // plots so that we can maximize the opportunity for reuse.
220 // As before we prioritize this upload to the first pages, not the most recently used.
221 if (fNumActivePages == this->maxPages()) {
222 for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
223 Plot* plot = fPages[pageIdx].fPlotList.tail();
224 SkASSERT(plot);
225 if (plot->lastUseToken() < recorder->priv().tokenTracker()->nextFlushToken()) {
226 this->processEvictionAndResetRects(plot);
227 SkDEBUGCODE(bool verify = )plot->addRect(width, height, atlasLocator);
228 SkASSERT(verify);
229 this->updatePlot(plot, atlasLocator);
230 return ErrorCode::kSucceeded;
231 }
232 }
233 } else {
234 // If we haven't activated all the available pages, try to create a new one and add to it
235 if (!this->activateNewPage(recorder)) {
236 return ErrorCode::kError;
237 }
238
239 if (this->addRectToPage(fNumActivePages-1, width, height, atlasLocator)) {
241 } else {
242 // If we fail to upload to a newly activated page then something has gone terribly
243 // wrong - return an error
244 return ErrorCode::kError;
245 }
246 }
247
248 if (!fNumActivePages) {
249 return ErrorCode::kError;
250 }
251
252 // All plots are currently in use by the current set of draws, so we need to fail. This
253 // gives the Device a chance to snap the current set of uploads and draws, advance the draw
254 // token, and call back into this function. The subsequent call will have plots available
255 // for fresh uploads.
257}
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
void updateRect(skgpu::IRect16 rect)
Definition AtlasTypes.h:345
void updatePlotLocator(PlotLocator p)
Definition AtlasTypes.h:337
AtlasToken nextFlushToken() const
Definition AtlasTypes.h:207
ErrorCode addRect(Recorder *, int width, int height, AtlasLocator *)
uint32_t maxPages() const
Definition DrawAtlas.h:158
TokenTracker * tokenTracker()
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
static IRect16 MakeXYWH(int16_t x, int16_t y, int16_t w, int16_t h)
Definition AtlasTypes.h:54

◆ addToAtlas()

DrawAtlas::ErrorCode skgpu::graphite::DrawAtlas::addToAtlas ( Recorder recorder,
int  width,
int  height,
const void *  image,
AtlasLocator atlasLocator 
)

Definition at line 259 of file DrawAtlas.cpp.

261 {
262 ErrorCode ec = this->addRect(recorder, width, height, atlasLocator);
263 if (ec == ErrorCode::kSucceeded) {
264 Plot* plot = this->findPlot(*atlasLocator);
265 plot->copySubImage(*atlasLocator, image);
266 }
267
268 return ec;
269}
sk_sp< SkImage > image
Definition examples.cpp:29

◆ atlasGeneration()

uint64_t skgpu::graphite::DrawAtlas::atlasGeneration ( ) const
inline

Definition at line 117 of file DrawAtlas.h.

117{ return fAtlasGeneration; }

◆ atlasID()

uint32_t skgpu::graphite::DrawAtlas::atlasID ( ) const
inline

Definition at line 116 of file DrawAtlas.h.

116{ return fAtlasID; }

◆ compact()

void skgpu::graphite::DrawAtlas::compact ( AtlasToken  startTokenForNextFlush)

Definition at line 276 of file DrawAtlas.cpp.

276 {
277 if (fNumActivePages < 1) {
278 fPrevFlushToken = startTokenForNextFlush;
279 return;
280 }
281
282 // For all plots, reset number of flushes since used if used this frame.
283 PlotList::Iter plotIter;
284 bool atlasUsedThisFlush = false;
285 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
286 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
287 while (Plot* plot = plotIter.get()) {
288 // Reset number of flushes since used
289 if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
290 plot->resetFlushesSinceLastUsed();
291 atlasUsedThisFlush = true;
292 }
293
294 plotIter.next();
295 }
296 }
297
298 if (atlasUsedThisFlush) {
299 fFlushesSinceLastUse = 0;
300 } else {
301 ++fFlushesSinceLastUse;
302 }
303
304 // We only try to compact if the atlas was used in the recently completed flush or
305 // hasn't been used in a long time.
306 // This is to handle the case where a lot of text or path rendering has occurred but then just
307 // a blinking cursor is drawn.
308 if (atlasUsedThisFlush || fFlushesSinceLastUse > kAtlasRecentlyUsedCount) {
309 TArray<Plot*> availablePlots;
310 uint32_t lastPageIndex = fNumActivePages - 1;
311
312 // For all plots but the last one, update number of flushes since used, and check to see
313 // if there are any in the first pages that the last page can safely upload to.
314 for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
315 if constexpr (kDumpAtlasData) {
316 SkDebugf("page %u: ", pageIndex);
317 }
318
319 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
320 while (Plot* plot = plotIter.get()) {
321 // Update number of flushes since plot was last used
322 // We only increment the 'sinceLastUsed' count for flushes where the atlas was used
323 // to avoid deleting everything when we return to text drawing in the blinking
324 // cursor case
325 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
326 plot->incFlushesSinceLastUsed();
327 }
328
329 if constexpr (kDumpAtlasData) {
330 SkDebugf("%d ", plot->flushesSinceLastUsed());
331 }
332
333 // Count plots we can potentially upload to in all pages except the last one
334 // (the potential compactee).
335 if (plot->flushesSinceLastUsed() > kPlotRecentlyUsedCount) {
336 availablePlots.push_back() = plot;
337 }
338
339 plotIter.next();
340 }
341
342 if constexpr (kDumpAtlasData) {
343 SkDebugf("\n");
344 }
345 }
346
347 // Count recently used plots in the last page and evict any that are no longer in use.
348 // Since we prioritize uploading to the first pages, this will eventually
349 // clear out usage of this page unless we have a large need.
350 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
351 unsigned int usedPlots = 0;
352 if constexpr (kDumpAtlasData) {
353 SkDebugf("page %u: ", lastPageIndex);
354 }
355 while (Plot* plot = plotIter.get()) {
356 // Update number of flushes since plot was last used
357 if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
358 plot->incFlushesSinceLastUsed();
359 }
360
361 if constexpr (kDumpAtlasData) {
362 SkDebugf("%d ", plot->flushesSinceLastUsed());
363 }
364
365 // If this plot was used recently
366 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
367 usedPlots++;
368 } else if (plot->lastUseToken() != AtlasToken::InvalidToken()) {
369 // otherwise if aged out just evict it.
370 this->processEvictionAndResetRects(plot);
371 }
372 plotIter.next();
373 }
374
375 if constexpr (kDumpAtlasData) {
376 SkDebugf("\n");
377 }
378
379 // If recently used plots in the last page are using less than a quarter of the page, try
380 // to evict them if there's available space in lower index pages. Since we prioritize
381 // uploading to the first pages, this will eventually clear out usage of this page unless
382 // we have a large need.
383 if (availablePlots.size() && usedPlots && usedPlots <= fNumPlots / 4) {
384 plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
385 while (Plot* plot = plotIter.get()) {
386 // If this plot was used recently
387 if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
388 // See if there's room in an lower index page and if so evict.
389 // We need to be somewhat harsh here so that a handful of plots that are
390 // consistently in use don't end up locking the page in memory.
391 if (availablePlots.size() > 0) {
392 this->processEvictionAndResetRects(plot);
393 this->processEvictionAndResetRects(availablePlots.back());
394 availablePlots.pop_back();
395 --usedPlots;
396 }
397 if (!usedPlots || !availablePlots.size()) {
398 break;
399 }
400 }
401 plotIter.next();
402 }
403 }
404
405 // If none of the plots in the last page have been used recently, delete it.
406 if (!usedPlots) {
407 if constexpr (kDumpAtlasData) {
408 SkDebugf("delete %u\n", fNumActivePages-1);
409 }
410
411 this->deactivateLastPage();
412 fFlushesSinceLastUse = 0;
413 }
414 }
415
416 fPrevFlushToken = startTokenForNextFlush;
417}
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
int size() const
Definition SkTArray.h:416
static constexpr auto kPlotRecentlyUsedCount
static const constexpr bool kDumpAtlasData
Definition DrawAtlas.cpp:35
static constexpr auto kAtlasRecentlyUsedCount

◆ evictAllPlots()

void skgpu::graphite::DrawAtlas::evictAllPlots ( )

Definition at line 505 of file DrawAtlas.cpp.

505 {
506 PlotList::Iter plotIter;
507 for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
508 plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
509 while (Plot* plot = plotIter.get()) {
510 this->processEvictionAndResetRects(plot);
511 plotIter.next();
512 }
513 }
514}

◆ getProxies()

const sk_sp< TextureProxy > * skgpu::graphite::DrawAtlas::getProxies ( ) const
inline

Definition at line 114 of file DrawAtlas.h.

114{ return fProxies; }

◆ hasID()

bool skgpu::graphite::DrawAtlas::hasID ( const PlotLocator plotLocator)
inline

Definition at line 122 of file DrawAtlas.h.

122 {
123 if (!plotLocator.isValid()) {
124 return false;
125 }
126
127 uint32_t plot = plotLocator.plotIndex();
128 uint32_t page = plotLocator.pageIndex();
129 uint64_t plotGeneration = fPages[page].fPlotArray[plot]->genID();
130 uint64_t locatorGeneration = plotLocator.genID();
131 return plot < fNumPlots && page < fNumActivePages && plotGeneration == locatorGeneration;
132 }
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

◆ Make()

std::unique_ptr< DrawAtlas > skgpu::graphite::DrawAtlas::Make ( SkColorType  ct,
size_t  bpp,
int  width,
int  height,
int  plotWidth,
int  plotHeight,
AtlasGenerationCounter generationCounter,
AllowMultitexturing  allowMultitexturing,
PlotEvictionCallback evictor,
std::string_view  label 
)
static

Returns a DrawAtlas.

Parameters
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.
atlasGenerationA pointer to the context's generation counter.
allowMultitexturingCan the atlas use more than one texture.
evictorA pointer to an eviction callback class.
Returns
An initialized DrawAtlas, or nullptr if creation fails.

Definition at line 52 of file DrawAtlas.cpp.

57 {
58 std::unique_ptr<DrawAtlas> atlas(new DrawAtlas(colorType, bpp, width, height,
59 plotWidth, plotHeight, generationCounter,
60 allowMultitexturing, label));
61
62 if (evictor != nullptr) {
63 atlas->fEvictionCallbacks.emplace_back(evictor);
64 }
65 return atlas;
66}
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
sk_sp< const SkImage > atlas
Definition SkRecords.h:331

◆ maxPages()

uint32_t skgpu::graphite::DrawAtlas::maxPages ( ) const
inline

Definition at line 158 of file DrawAtlas.h.

158 {
159 return fMaxPages;
160 }

◆ numActivePages()

uint32_t skgpu::graphite::DrawAtlas::numActivePages ( ) const
inline

Definition at line 118 of file DrawAtlas.h.

118{ return fNumActivePages; }

◆ numAllocated_TestingOnly()

int skgpu::graphite::DrawAtlas::numAllocated_TestingOnly ( ) const

◆ numPlots()

unsigned int skgpu::graphite::DrawAtlas::numPlots ( ) const
inline

Definition at line 119 of file DrawAtlas.h.

119{ return fNumPlots; }

◆ plotSize()

SkISize skgpu::graphite::DrawAtlas::plotSize ( ) const
inline

Definition at line 120 of file DrawAtlas.h.

120{ return {fPlotWidth, fPlotHeight}; }

◆ prepForRender()

SkIPoint skgpu::graphite::DrawAtlas::prepForRender ( const AtlasLocator locator,
SkAutoPixmapStorage pixmap 
)

Definition at line 271 of file DrawAtlas.cpp.

271 {
272 Plot* plot = this->findPlot(locator);
273 return plot->prepForRender(locator, pixmap);
274}

◆ recordUploads()

bool skgpu::graphite::DrawAtlas::recordUploads ( DrawContext dc,
Recorder recorder 
)

Definition at line 144 of file DrawAtlas.cpp.

144 {
145 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
146 for (uint32_t pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
147 PlotList::Iter plotIter;
148 plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
149 for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
150 if (plot->needsUpload()) {
151 TextureProxy* proxy = fProxies[pageIdx].get();
152 SkASSERT(proxy);
153
154 const void* dataPtr;
155 SkIRect dstRect;
156 std::tie(dataPtr, dstRect) = plot->prepareForUpload();
157 if (dstRect.isEmpty()) {
158 continue;
159 }
160
161 std::vector<MipLevel> levels;
162 levels.push_back({dataPtr, fBytesPerPixel*fPlotWidth});
163
164 // Src and dst colorInfo are the same
165 SkColorInfo colorInfo(fColorType, kUnknown_SkAlphaType, nullptr);
166 if (!dc->recordUpload(recorder, sk_ref_sp(proxy), colorInfo, colorInfo, levels,
167 dstRect, /*ConditionalUploadContext=*/nullptr)) {
168 return false;
169 }
170 }
171 }
172 }
173 return true;
174}
@ kUnknown_SkAlphaType
uninitialized
Definition SkAlphaType.h:27
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
#define TRACE_FUNC
bool isEmpty() const
Definition SkRect.h:202
#define TRACE_EVENT0(category_group, name)

◆ setLastUseToken()

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

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

Definition at line 135 of file DrawAtlas.h.

135 {
136 Plot* plot = this->findPlot(atlasLocator);
137 this->internalSetLastUseToken(plot, atlasLocator.pageIndex(), token);
138 }
uint32_t pageIndex() const
Definition AtlasTypes.h:303

◆ setLastUseTokenBulk()

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

Definition at line 140 of file DrawAtlas.h.

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

◆ setMaxPages_TestingOnly()

void skgpu::graphite::DrawAtlas::setMaxPages_TestingOnly ( uint32_t  maxPages)

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