GrDrawOpAtlas.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. #ifndef GrDrawOpAtlas_DEFINED
  8. #define GrDrawOpAtlas_DEFINED
  9. #include <cmath>
  10. #include "include/core/SkSize.h"
  11. #include "include/private/SkTDArray.h"
  12. #include "src/core/SkGlyphRunPainter.h"
  13. #include "src/core/SkIPoint16.h"
  14. #include "src/core/SkTInternalLList.h"
  15. #include "src/gpu/ops/GrDrawOp.h"
  16. class GrOnFlushResourceProvider;
  17. class GrRectanizer;
  18. /**
  19. * This class manages one or more atlas textures on behalf of GrDrawOps. The draw ops that use the
  20. * atlas perform texture uploads when preparing their draws during flush. The class provides
  21. * facilities for using GrDrawOpUploadToken to detect data hazards. Op's uploads are performed in
  22. * "ASAP" mode until it is impossible to add data without overwriting texels read by draws that
  23. * have not yet executed on the gpu. At that point, the atlas will attempt to allocate a new
  24. * atlas texture (or "page") of the same size, up to a maximum number of textures, and upload
  25. * to that texture. If that's not possible, the uploads are performed "inline" between draws. If a
  26. * single draw would use enough subimage space to overflow the atlas texture then the atlas will
  27. * fail to add a subimage. This gives the op the chance to end the draw and begin a new one.
  28. * Additional uploads will then succeed in inline mode.
  29. *
  30. * When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e.,
  31. * it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using
  32. * excess space, periodic garbage collection is needed to shift data from the higher index pages to
  33. * the lower ones, and then eventually remove any pages that are no longer in use. "In use" is
  34. * determined by using the GrDrawUploadToken system: After a flush each subarea of the page
  35. * is checked to see whether it was used in that flush; if it is not, a counter is incremented.
  36. * Once that counter reaches a threshold that subarea is considered to be no longer in use.
  37. *
  38. * Garbage collection is initiated by the GrDrawOpAtlas's client via the compact() method. One
  39. * solution is to make the client a subclass of GrOnFlushCallbackObject, register it with the
  40. * GrContext via addOnFlushCallbackObject(), and the client's postFlush() method calls compact()
  41. * and passes in the given GrDrawUploadToken.
  42. */
  43. class GrDrawOpAtlas {
  44. private:
  45. static constexpr auto kMaxMultitexturePages = 4;
  46. public:
  47. /** Is the atlas allowed to use more than one texture? */
  48. enum class AllowMultitexturing : bool { kNo, kYes };
  49. static constexpr int kMaxPlots = 32; // restricted by the fPlotAlreadyUpdated bitfield
  50. // in BulkUseTokenUpdater
  51. /**
  52. * An AtlasID is an opaque handle which callers can use to determine if the atlas contains
  53. * a specific piece of data.
  54. */
  55. typedef uint64_t AtlasID;
  56. static const uint32_t kInvalidAtlasID = 0;
  57. static const uint64_t kInvalidAtlasGeneration = 0;
  58. /**
  59. * A function pointer for use as a callback during eviction. Whenever GrDrawOpAtlas evicts a
  60. * specific AtlasID, it will call all of the registered listeners so they can process the
  61. * eviction.
  62. */
  63. typedef void (*EvictionFunc)(GrDrawOpAtlas::AtlasID, void*);
  64. /**
  65. * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas
  66. * should only be used inside of GrMeshDrawOp::onPrepareDraws.
  67. * @param GrColorType The colorType which this atlas will store
  68. * @param width width in pixels of the atlas
  69. * @param height height in pixels of the atlas
  70. * @param numPlotsX The number of plots the atlas should be broken up into in the X
  71. * direction
  72. * @param numPlotsY The number of plots the atlas should be broken up into in the Y
  73. * direction
  74. * @param allowMultitexturing Can the atlas use more than one texture.
  75. * @param func An eviction function which will be called whenever the atlas has to
  76. * evict data
  77. * @param data User supplied data which will be passed into func whenever an
  78. * eviction occurs
  79. * @return An initialized GrDrawOpAtlas, or nullptr if creation fails
  80. */
  81. static std::unique_ptr<GrDrawOpAtlas> Make(GrProxyProvider*,
  82. const GrBackendFormat& format,
  83. GrColorType,
  84. int width, int height,
  85. int plotWidth, int plotHeight,
  86. AllowMultitexturing allowMultitexturing,
  87. GrDrawOpAtlas::EvictionFunc func, void* data);
  88. /**
  89. * Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns
  90. * the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if
  91. * the subimage cannot fit in the atlas without overwriting texels that will be read in the
  92. * current draw. This indicates that the op should end its current draw and begin another
  93. * before adding more data. Upon success, an upload of the provided image data will have
  94. * been added to the GrDrawOp::Target, in "asap" mode if possible, otherwise in "inline" mode.
  95. * Successive uploads in either mode may be consolidated.
  96. * 'kError' will be returned when some unrecoverable error was encountered while trying to
  97. * add the subimage. In this case the op being created should be discarded.
  98. *
  99. * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call
  100. * 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to
  101. * addToAtlas might cause the previous data to be overwritten before it has been read.
  102. */
  103. enum class ErrorCode {
  104. kError,
  105. kSucceeded,
  106. kTryAgain
  107. };
  108. ErrorCode addToAtlas(GrResourceProvider*, AtlasID*, GrDeferredUploadTarget*,
  109. int width, int height,
  110. const void* image, SkIPoint16* loc);
  111. const sk_sp<GrTextureProxy>* getProxies() const { return fProxies; }
  112. uint64_t atlasGeneration() const { return fAtlasGeneration; }
  113. inline bool hasID(AtlasID id) {
  114. if (kInvalidAtlasID == id) {
  115. return false;
  116. }
  117. uint32_t plot = GetPlotIndexFromID(id);
  118. SkASSERT(plot < fNumPlots);
  119. uint32_t page = GetPageIndexFromID(id);
  120. SkASSERT(page < fNumActivePages);
  121. return fPages[page].fPlotArray[plot]->genID() == GetGenerationFromID(id);
  122. }
  123. /** To ensure the atlas does not evict a given entry, the client must set the last use token. */
  124. inline void setLastUseToken(AtlasID id, GrDeferredUploadToken token) {
  125. SkASSERT(this->hasID(id));
  126. uint32_t plotIdx = GetPlotIndexFromID(id);
  127. SkASSERT(plotIdx < fNumPlots);
  128. uint32_t pageIdx = GetPageIndexFromID(id);
  129. SkASSERT(pageIdx < fNumActivePages);
  130. Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get();
  131. this->makeMRU(plot, pageIdx);
  132. plot->setLastUseToken(token);
  133. }
  134. inline void registerEvictionCallback(EvictionFunc func, void* userData) {
  135. EvictionData* data = fEvictionCallbacks.append();
  136. data->fFunc = func;
  137. data->fData = userData;
  138. }
  139. uint32_t numActivePages() { return fNumActivePages; }
  140. /**
  141. * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The
  142. * current max number of plots per page the GrDrawOpAtlas can handle is 32. If in the future
  143. * this is insufficient then we can move to a 64 bit int.
  144. */
  145. class BulkUseTokenUpdater {
  146. public:
  147. BulkUseTokenUpdater() {
  148. memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated));
  149. }
  150. BulkUseTokenUpdater(const BulkUseTokenUpdater& that)
  151. : fPlotsToUpdate(that.fPlotsToUpdate) {
  152. memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated));
  153. }
  154. bool add(AtlasID id) {
  155. int index = GrDrawOpAtlas::GetPlotIndexFromID(id);
  156. int pageIdx = GrDrawOpAtlas::GetPageIndexFromID(id);
  157. if (this->find(pageIdx, index)) {
  158. return false;
  159. }
  160. this->set(pageIdx, index);
  161. return true;
  162. }
  163. void reset() {
  164. fPlotsToUpdate.reset();
  165. memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated));
  166. }
  167. struct PlotData {
  168. PlotData(int pageIdx, int plotIdx) : fPageIndex(pageIdx), fPlotIndex(plotIdx) {}
  169. uint32_t fPageIndex;
  170. uint32_t fPlotIndex;
  171. };
  172. private:
  173. bool find(int pageIdx, int index) const {
  174. SkASSERT(index < kMaxPlots);
  175. return (fPlotAlreadyUpdated[pageIdx] >> index) & 1;
  176. }
  177. void set(int pageIdx, int index) {
  178. SkASSERT(!this->find(pageIdx, index));
  179. fPlotAlreadyUpdated[pageIdx] |= (1 << index);
  180. fPlotsToUpdate.push_back(PlotData(pageIdx, index));
  181. }
  182. static constexpr int kMinItems = 4;
  183. SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate;
  184. uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; // TODO: increase this to uint64_t
  185. // to allow more plots per page
  186. friend class GrDrawOpAtlas;
  187. };
  188. void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDeferredUploadToken token) {
  189. int count = updater.fPlotsToUpdate.count();
  190. for (int i = 0; i < count; i++) {
  191. const BulkUseTokenUpdater::PlotData& pd = updater.fPlotsToUpdate[i];
  192. // it's possible we've added a plot to the updater and subsequently the plot's page
  193. // was deleted -- so we check to prevent a crash
  194. if (pd.fPageIndex < fNumActivePages) {
  195. Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get();
  196. this->makeMRU(plot, pd.fPageIndex);
  197. plot->setLastUseToken(token);
  198. }
  199. }
  200. }
  201. void compact(GrDeferredUploadToken startTokenForNextFlush);
  202. static uint32_t GetPageIndexFromID(AtlasID id) {
  203. return id & 0xff;
  204. }
  205. void instantiate(GrOnFlushResourceProvider*);
  206. uint32_t maxPages() const {
  207. return fMaxPages;
  208. }
  209. int numAllocated_TestingOnly() const;
  210. void setMaxPages_TestingOnly(uint32_t maxPages);
  211. private:
  212. GrDrawOpAtlas(GrProxyProvider*, const GrBackendFormat& format, GrColorType, int width,
  213. int height, int plotWidth, int plotHeight,
  214. AllowMultitexturing allowMultitexturing);
  215. /**
  216. * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots
  217. * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its
  218. * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e.
  219. * there is no room for the new subimage according to the GrRectanizer), it can no longer be
  220. * used unless the last use of the Plot has already been flushed through to the gpu.
  221. */
  222. class Plot : public SkRefCnt {
  223. SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot);
  224. public:
  225. /** index() is a unique id for the plot relative to the owning GrAtlas and page. */
  226. uint32_t index() const { return fPlotIndex; }
  227. /**
  228. * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know
  229. * if a particular subimage is still present in the atlas.
  230. */
  231. uint64_t genID() const { return fGenID; }
  232. GrDrawOpAtlas::AtlasID id() const {
  233. SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID);
  234. return fID;
  235. }
  236. SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; })
  237. bool addSubImage(int width, int height, const void* image, SkIPoint16* loc);
  238. /**
  239. * To manage the lifetime of a plot, we use two tokens. We use the last upload token to
  240. * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to
  241. * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We
  242. * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use
  243. * has already flushed through the gpu then we can reuse the plot.
  244. */
  245. GrDeferredUploadToken lastUploadToken() const { return fLastUpload; }
  246. GrDeferredUploadToken lastUseToken() const { return fLastUse; }
  247. void setLastUploadToken(GrDeferredUploadToken token) { fLastUpload = token; }
  248. void setLastUseToken(GrDeferredUploadToken token) { fLastUse = token; }
  249. void uploadToTexture(GrDeferredTextureUploadWritePixelsFn&, GrTextureProxy*);
  250. void resetRects();
  251. int flushesSinceLastUsed() { return fFlushesSinceLastUse; }
  252. void resetFlushesSinceLastUsed() { fFlushesSinceLastUse = 0; }
  253. void incFlushesSinceLastUsed() { fFlushesSinceLastUse++; }
  254. private:
  255. Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY,
  256. int width, int height, GrColorType colorType);
  257. ~Plot() override;
  258. /**
  259. * Create a clone of this plot. The cloned plot will take the place of the current plot in
  260. * the atlas
  261. */
  262. Plot* clone() const {
  263. return new Plot(fPageIndex, fPlotIndex, fGenID + 1, fX, fY, fWidth, fHeight,
  264. fColorType);
  265. }
  266. static GrDrawOpAtlas::AtlasID CreateId(uint32_t pageIdx, uint32_t plotIdx,
  267. uint64_t generation) {
  268. SkASSERT(pageIdx < (1 << 8));
  269. SkASSERT(pageIdx < kMaxMultitexturePages);
  270. SkASSERT(plotIdx < (1 << 8));
  271. SkASSERT(generation < ((uint64_t)1 << 48));
  272. return generation << 16 | plotIdx << 8 | pageIdx;
  273. }
  274. GrDeferredUploadToken fLastUpload;
  275. GrDeferredUploadToken fLastUse;
  276. // the number of flushes since this plot has been last used
  277. int fFlushesSinceLastUse;
  278. struct {
  279. const uint32_t fPageIndex : 16;
  280. const uint32_t fPlotIndex : 16;
  281. };
  282. uint64_t fGenID;
  283. GrDrawOpAtlas::AtlasID fID;
  284. unsigned char* fData;
  285. const int fWidth;
  286. const int fHeight;
  287. const int fX;
  288. const int fY;
  289. GrRectanizer* fRects;
  290. const SkIPoint16 fOffset; // the offset of the plot in the backing texture
  291. const GrColorType fColorType;
  292. const size_t fBytesPerPixel;
  293. SkIRect fDirtyRect;
  294. SkDEBUGCODE(bool fDirty);
  295. friend class GrDrawOpAtlas;
  296. typedef SkRefCnt INHERITED;
  297. };
  298. typedef SkTInternalLList<Plot> PlotList;
  299. static uint32_t GetPlotIndexFromID(AtlasID id) {
  300. return (id >> 8) & 0xff;
  301. }
  302. // top 48 bits are reserved for the generation ID
  303. static uint64_t GetGenerationFromID(AtlasID id) {
  304. return (id >> 16) & 0xffffffffffff;
  305. }
  306. inline bool updatePlot(GrDeferredUploadTarget*, AtlasID*, Plot*);
  307. inline void makeMRU(Plot* plot, int pageIdx) {
  308. if (fPages[pageIdx].fPlotList.head() == plot) {
  309. return;
  310. }
  311. fPages[pageIdx].fPlotList.remove(plot);
  312. fPages[pageIdx].fPlotList.addToHead(plot);
  313. // No MRU update for pages -- since we will always try to add from
  314. // the front and remove from the back there is no need for MRU.
  315. }
  316. bool uploadToPage(unsigned int pageIdx, AtlasID* id, GrDeferredUploadTarget* target,
  317. int width, int height, const void* image, SkIPoint16* loc);
  318. bool createPages(GrProxyProvider*);
  319. bool activateNewPage(GrResourceProvider*);
  320. void deactivateLastPage();
  321. void processEviction(AtlasID);
  322. inline void processEvictionAndResetRects(Plot* plot) {
  323. this->processEviction(plot->id());
  324. plot->resetRects();
  325. }
  326. GrBackendFormat fFormat;
  327. GrColorType fColorType;
  328. int fTextureWidth;
  329. int fTextureHeight;
  330. int fPlotWidth;
  331. int fPlotHeight;
  332. unsigned int fNumPlots;
  333. uint64_t fAtlasGeneration;
  334. // nextTokenToFlush() value at the end of the previous flush
  335. GrDeferredUploadToken fPrevFlushToken;
  336. struct EvictionData {
  337. EvictionFunc fFunc;
  338. void* fData;
  339. };
  340. SkTDArray<EvictionData> fEvictionCallbacks;
  341. struct Page {
  342. // allocated array of Plots
  343. std::unique_ptr<sk_sp<Plot>[]> fPlotArray;
  344. // LRU list of Plots (MRU at head - LRU at tail)
  345. PlotList fPlotList;
  346. };
  347. // proxies kept separate to make it easier to pass them up to client
  348. sk_sp<GrTextureProxy> fProxies[kMaxMultitexturePages];
  349. Page fPages[kMaxMultitexturePages];
  350. uint32_t fMaxPages;
  351. uint32_t fNumActivePages;
  352. };
  353. // There are three atlases (A8, 565, ARGB) that are kept in relation with one another. In
  354. // general, the A8 dimensions are 2x the 565 and ARGB dimensions with the constraint that an atlas
  355. // size will always contain at least one plot. Since the ARGB atlas takes the most space, its
  356. // dimensions are used to size the other two atlases.
  357. class GrDrawOpAtlasConfig {
  358. public:
  359. // The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this
  360. // represents the largest they want a single atlas texture to be. Due to multitexturing, we
  361. // may expand temporarily to use more space as needed.
  362. GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes);
  363. // For testing only - make minimum sized atlases -- a single plot for ARGB, four for A8
  364. GrDrawOpAtlasConfig() : GrDrawOpAtlasConfig(kMaxAtlasDim, 0) {}
  365. SkISize atlasDimensions(GrMaskFormat type) const;
  366. SkISize plotDimensions(GrMaskFormat type) const;
  367. private:
  368. // On some systems texture coordinates are represented using half-precision floating point,
  369. // which limits the largest atlas dimensions to 2048x2048.
  370. // For simplicity we'll use this constraint for all of our atlas textures.
  371. // This can be revisited later if we need larger atlases.
  372. static constexpr int kMaxAtlasDim = 2048;
  373. SkISize fARGBDimensions;
  374. int fMaxTextureSize;
  375. };
  376. #endif