| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef GrDrawOpAtlas_DEFINED |
| #define GrDrawOpAtlas_DEFINED |
| |
| #include "SkPoint.h" |
| #include "SkTDArray.h" |
| #include "SkTInternalLList.h" |
| |
| #include "ops/GrDrawOp.h" |
| |
| class GrRectanizer; |
| |
| struct GrDrawOpAtlasConfig { |
| int numPlotsX() const { return fWidth / fPlotWidth; } |
| int numPlotsY() const { return fHeight / fPlotWidth; } |
| int fWidth; |
| int fHeight; |
| int fLog2Width; |
| int fLog2Height; |
| int fPlotWidth; |
| int fPlotHeight; |
| }; |
| |
| /** |
| * This class manages an atlas texture 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 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. |
| */ |
| class GrDrawOpAtlas { |
| public: |
| /** |
| * An AtlasID is an opaque handle which callers can use to determine if the atlas contains |
| * a specific piece of data. |
| */ |
| typedef uint64_t AtlasID; |
| static const uint32_t kInvalidAtlasID = 0; |
| static const uint64_t kInvalidAtlasGeneration = 0; |
| |
| /** |
| * A function pointer for use as a callback during eviction. Whenever GrDrawOpAtlas evicts a |
| * specific AtlasID, it will call all of the registered listeners so they can process the |
| * eviction. |
| */ |
| typedef void (*EvictionFunc)(GrDrawOpAtlas::AtlasID, void*); |
| |
| /** |
| * Returns a GrDrawOpAtlas. This function can be called anywhere, but the returned atlas |
| * should only be used inside of GrMeshDrawOp::onPrepareDraws. |
| * @param GrPixelConfig The pixel config which this atlas will store |
| * @param width width in pixels of the atlas |
| * @param height height in pixels of the atlas |
| * @param numPlotsX The number of plots the atlas should be broken up into in the X |
| * direction |
| * @param numPlotsY The number of plots the atlas should be broken up into in the Y |
| * direction |
| * @param func An eviction function which will be called whenever the atlas has to |
| * evict data |
| * @param data User supplied data which will be passed into func whenver an |
| * eviction occurs |
| * @return An initialized GrDrawOpAtlas, or nullptr if creation fails |
| */ |
| static std::unique_ptr<GrDrawOpAtlas> Make(GrContext*, GrPixelConfig, |
| int width, int height, |
| int numPlotsX, int numPlotsY, |
| GrDrawOpAtlas::EvictionFunc func, void* data); |
| |
| /** |
| * Adds a width x height subimage to the atlas. Upon success it returns an ID and the subimage's |
| * coordinates in the backing texture. False 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. |
| * NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call |
| * 'setUseToken' 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. |
| */ |
| bool addToAtlas(AtlasID*, GrDrawOp::Target*, int width, int height, const void* image, |
| SkIPoint16* loc); |
| |
| GrContext* context() const { return fContext; } |
| sk_sp<GrTextureProxy> getProxy() const { return fProxy; } |
| |
| uint64_t atlasGeneration() const { return fAtlasGeneration; } |
| |
| inline bool hasID(AtlasID id) { |
| uint32_t index = GetIndexFromID(id); |
| SkASSERT(index < fNumPlots); |
| return fPlotArray[index]->genID() == GetGenerationFromID(id); |
| } |
| |
| /** To ensure the atlas does not evict a given entry, the client must set the last use token. */ |
| inline void setLastUseToken(AtlasID id, GrDrawOpUploadToken token) { |
| SkASSERT(this->hasID(id)); |
| uint32_t index = GetIndexFromID(id); |
| SkASSERT(index < fNumPlots); |
| this->makeMRU(fPlotArray[index].get()); |
| fPlotArray[index]->setLastUseToken(token); |
| } |
| |
| inline void registerEvictionCallback(EvictionFunc func, void* userData) { |
| EvictionData* data = fEvictionCallbacks.append(); |
| data->fFunc = func; |
| data->fData = userData; |
| } |
| |
| /** |
| * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk. The |
| * current max number of plots the GrDrawOpAtlas can handle is 32. If in the future this is |
| * insufficient then we can move to a 64 bit int. |
| */ |
| class BulkUseTokenUpdater { |
| public: |
| BulkUseTokenUpdater() : fPlotAlreadyUpdated(0) {} |
| BulkUseTokenUpdater(const BulkUseTokenUpdater& that) |
| : fPlotsToUpdate(that.fPlotsToUpdate) |
| , fPlotAlreadyUpdated(that.fPlotAlreadyUpdated) { |
| } |
| |
| void add(AtlasID id) { |
| int index = GrDrawOpAtlas::GetIndexFromID(id); |
| if (!this->find(index)) { |
| this->set(index); |
| } |
| } |
| |
| void reset() { |
| fPlotsToUpdate.reset(); |
| fPlotAlreadyUpdated = 0; |
| } |
| |
| private: |
| bool find(int index) const { |
| SkASSERT(index < kMaxPlots); |
| return (fPlotAlreadyUpdated >> index) & 1; |
| } |
| |
| void set(int index) { |
| SkASSERT(!this->find(index)); |
| fPlotAlreadyUpdated = fPlotAlreadyUpdated | (1 << index); |
| fPlotsToUpdate.push_back(index); |
| } |
| |
| static const int kMinItems = 4; |
| static const int kMaxPlots = 32; |
| SkSTArray<kMinItems, int, true> fPlotsToUpdate; |
| uint32_t fPlotAlreadyUpdated; |
| |
| #if defined(COBALT) |
| // The non-Cobalt code also depends on this value, but it assumes |
| // that it is 32. |
| // |
| // |GrAtlasGlyphCache| needs to know the maximum number of plots, |
| // |kMaxPlots|, that can be used in a texture. |
| friend class GrAtlasGlyphCache; |
| #endif |
| |
| friend class GrDrawOpAtlas; |
| }; |
| |
| void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDrawOpUploadToken token) { |
| int count = updater.fPlotsToUpdate.count(); |
| for (int i = 0; i < count; i++) { |
| Plot* plot = fPlotArray[updater.fPlotsToUpdate[i]].get(); |
| this->makeMRU(plot); |
| plot->setLastUseToken(token); |
| } |
| } |
| |
| static const int kGlyphMaxDim = 256; |
| static bool GlyphTooLargeForAtlas(int width, int height) { |
| return width > kGlyphMaxDim || height > kGlyphMaxDim; |
| } |
| |
| private: |
| GrDrawOpAtlas(GrContext*, sk_sp<GrTextureProxy>, int numPlotsX, int numPlotsY); |
| |
| /** |
| * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots |
| * keep track of subimage placement via their GrRectanizer. A Plot manages the lifetime of its |
| * data using two tokens, a last use token and a last upload token. Once a Plot is "full" (i.e. |
| * there is no room for the new subimage according to the GrRectanizer), it can no longer be |
| * used unless the last use of the Plot has already been flushed through to the gpu. |
| */ |
| class Plot : public SkRefCnt { |
| SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot); |
| |
| public: |
| /** index() is a unique id for the plot relative to the owning GrAtlas. */ |
| uint32_t index() const { return fIndex; } |
| /** |
| * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know |
| * if a particular subimage is still present in the atlas. |
| */ |
| uint64_t genID() const { return fGenID; } |
| GrDrawOpAtlas::AtlasID id() const { |
| SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != fID); |
| return fID; |
| } |
| SkDEBUGCODE(size_t bpp() const { return fBytesPerPixel; }) |
| |
| bool addSubImage(int width, int height, const void* image, SkIPoint16* loc); |
| |
| /** |
| * To manage the lifetime of a plot, we use two tokens. We use the last upload token to |
| * know when we can 'piggy back' uploads, i.e. if the last upload hasn't been flushed to |
| * the gpu, we don't need to issue a new upload even if we update the cpu backing store. We |
| * use lastUse to determine when we can evict a plot from the cache, i.e. if the last use |
| * has already flushed through the gpu then we can reuse the plot. |
| */ |
| GrDrawOpUploadToken lastUploadToken() const { return fLastUpload; } |
| GrDrawOpUploadToken lastUseToken() const { return fLastUse; } |
| void setLastUploadToken(GrDrawOpUploadToken token) { fLastUpload = token; } |
| void setLastUseToken(GrDrawOpUploadToken token) { fLastUse = token; } |
| |
| void uploadToTexture(GrDrawOp::WritePixelsFn&, GrTexture* texture); |
| void resetRects(); |
| |
| private: |
| Plot(int index, uint64_t genID, int offX, int offY, int width, int height, |
| GrPixelConfig config); |
| |
| ~Plot() override; |
| |
| /** |
| * Create a clone of this plot. The cloned plot will take the place of the current plot in |
| * the atlas |
| */ |
| Plot* clone() const { |
| return new Plot(fIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig); |
| } |
| |
| static GrDrawOpAtlas::AtlasID CreateId(uint32_t index, uint64_t generation) { |
| SkASSERT(index < (1 << 16)); |
| SkASSERT(generation < ((uint64_t)1 << 48)); |
| return generation << 16 | index; |
| } |
| |
| GrDrawOpUploadToken fLastUpload; |
| GrDrawOpUploadToken fLastUse; |
| |
| const uint32_t fIndex; |
| uint64_t fGenID; |
| GrDrawOpAtlas::AtlasID fID; |
| unsigned char* fData; |
| const int fWidth; |
| const int fHeight; |
| const int fX; |
| const int fY; |
| GrRectanizer* fRects; |
| const SkIPoint16 fOffset; // the offset of the plot in the backing texture |
| const GrPixelConfig fConfig; |
| const size_t fBytesPerPixel; |
| SkIRect fDirtyRect; |
| SkDEBUGCODE(bool fDirty); |
| |
| friend class GrDrawOpAtlas; |
| |
| typedef SkRefCnt INHERITED; |
| }; |
| |
| typedef SkTInternalLList<Plot> PlotList; |
| |
| static uint32_t GetIndexFromID(AtlasID id) { |
| return id & 0xffff; |
| } |
| |
| // top 48 bits are reserved for the generation ID |
| static uint64_t GetGenerationFromID(AtlasID id) { |
| return (id >> 16) & 0xffffffffffff; |
| } |
| |
| inline bool updatePlot(GrDrawOp::Target*, AtlasID*, Plot*); |
| |
| inline void makeMRU(Plot* plot) { |
| if (fPlotList.head() == plot) { |
| return; |
| } |
| |
| fPlotList.remove(plot); |
| fPlotList.addToHead(plot); |
| } |
| |
| inline void processEviction(AtlasID); |
| |
| GrContext* fContext; |
| sk_sp<GrTextureProxy> fProxy; |
| int fPlotWidth; |
| int fPlotHeight; |
| SkDEBUGCODE(uint32_t fNumPlots;) |
| |
| uint64_t fAtlasGeneration; |
| |
| struct EvictionData { |
| EvictionFunc fFunc; |
| void* fData; |
| }; |
| |
| SkTDArray<EvictionData> fEvictionCallbacks; |
| // allocated array of Plots |
| std::unique_ptr<sk_sp<Plot>[]> fPlotArray; |
| // LRU list of Plots (MRU at head - LRU at tail) |
| PlotList fPlotList; |
| }; |
| |
| #endif |