/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrBatchAtlas.h" #include "GrBatchTarget.h" #include "GrGpu.h" #include "GrRectanizer.h" #include "GrTracing.h" // for testing #define ATLAS_STATS 0 #if ATLAS_STATS static int g_UploadCount = 0; #endif static inline void adjust_for_offset(SkIPoint16* loc, const SkIPoint16& offset) { loc->fX += offset.fX; loc->fY += offset.fY; } static GrBatchAtlas::AtlasID create_id(int index, int generation) { // Generation ID can roll over because we only check for equality SkASSERT(index < (1 << 16)); return generation << 16 | index; } // The backing GrTexture for a GrBatchAtlas is broken into a spatial grid of GrBatchPlots. // The GrBatchPlots keep track of subimage placement via their GrRectanizer. In turn, a GrBatchPlot // manages the lifetime of its data using two tokens, a last ref toke and a last upload token. // Once a GrBatchPlot 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 ref on the GrPlot has already been // flushed through to the gpu. class BatchPlot : public SkRefCnt { public: typedef GrBatchAtlas::BatchToken BatchToken; SK_DECLARE_INST_COUNT(BatchPlot); SK_DECLARE_INTERNAL_LLIST_INTERFACE(BatchPlot); // index() refers to the index of the plot in the owning GrAtlas's plot array. genID() is a // monotonically incrementing number which is bumped every time the cpu backing store is // wiped, or when the plot itself is evicted from the atlas(ie, there is continuity in genID() // across atlas spills) int index() const { return fIndex; } int genID() const { return fGenID; } GrBatchAtlas::AtlasID id() { return fID; } GrTexture* texture() const { return fTexture; } bool addSubImage(int width, int height, const void* image, SkIPoint16* loc, size_t rowBytes) { if (!fRects->addRect(width, height, loc)) { return false; } SkASSERT(fData); const unsigned char* imagePtr = (const unsigned char*)image; // point ourselves at the right starting spot unsigned char* dataPtr = fData; dataPtr += fBytesPerPixel * fWidth * loc->fY; dataPtr += fBytesPerPixel * loc->fX; // copy into the data buffer for (int i = 0; i < height; ++i) { memcpy(dataPtr, imagePtr, rowBytes); dataPtr += fBytesPerPixel * fWidth; imagePtr += rowBytes; } fDirtyRect.join(loc->fX, loc->fY, loc->fX + width, loc->fY + height); adjust_for_offset(loc, fOffset); SkDEBUGCODE(fDirty = true;) #if ATLAS_STATS ++g_UploadCount; #endif return true; } // to manage the lifetime of a plot, we use two tokens. We use last upload token to know when // we can 'piggy back' uploads, ie if the last upload hasn't been flushed to gpu, we don't need // to issue a new upload even if we update the cpu backing store. We use lastref to determine // when we can evict a plot from the cache, ie if the last ref has already flushed through // the gpu then we can reuse the plot BatchToken lastUploadToken() const { return fLastUpload; } BatchToken lastRefToken() const { return fLastRef; } void setLastUploadToken(BatchToken batchToken) { fLastUpload = batchToken; } void setLastRefToken(BatchToken batchToken) { fLastRef = batchToken; } void uploadToTexture(GrBatchTarget::TextureUploader uploader) { // We should only be issuing uploads if we are in fact dirty SkASSERT(fDirty); TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrBatchPlot::uploadToTexture"); SkASSERT(fTexture); size_t rowBytes = fBytesPerPixel * fRects->width(); const unsigned char* dataPtr = fData; dataPtr += rowBytes * fDirtyRect.fTop; dataPtr += fBytesPerPixel * fDirtyRect.fLeft; uploader.writeTexturePixels(fTexture, fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop, fDirtyRect.width(), fDirtyRect.height(), fTexture->config(), dataPtr, rowBytes); fDirtyRect.setEmpty(); SkDEBUGCODE(fDirty = false;) } void resetRects() { SkASSERT(fRects); fRects->reset(); fGenID++; fID = create_id(fIndex, fGenID); // zero out the plot SkASSERT(fData); memset(fData, 0, fBytesPerPixel * fWidth * fHeight); fDirtyRect.setEmpty(); SkDEBUGCODE(fDirty = false;) } int x() const { return fX; } int y() const { return fY; } private: BatchPlot() : fLastUpload(0) , fLastRef(0) , fIndex(-1) , fGenID(-1) , fID(0) , fData(NULL) , fWidth(0) , fHeight(0) , fX(0) , fY(0) , fTexture(NULL) , fRects(NULL) , fAtlas(NULL) , fBytesPerPixel(1) #ifdef SK_DEBUG , fDirty(false) #endif { fOffset.set(0, 0); } ~BatchPlot() { SkDELETE_ARRAY(fData); fData = NULL; delete fRects; } void init(GrBatchAtlas* atlas, GrTexture* texture, int index, uint32_t generation, int offX, int offY, int width, int height, size_t bpp) { fIndex = index; fGenID = generation; fID = create_id(index, generation); fWidth = width; fHeight = height; fX = offX; fY = offY; fRects = GrRectanizer::Factory(width, height); fAtlas = atlas; fOffset.set(offX * width, offY * height); fBytesPerPixel = bpp; fData = NULL; fDirtyRect.setEmpty(); SkDEBUGCODE(fDirty = false;) fTexture = texture; // allocate backing store fData = SkNEW_ARRAY(unsigned char, fBytesPerPixel * width * height); memset(fData, 0, fBytesPerPixel * width * height); } BatchToken fLastUpload; BatchToken fLastRef; uint32_t fIndex; uint32_t fGenID; GrBatchAtlas::AtlasID fID; unsigned char* fData; int fWidth; int fHeight; int fX; int fY; GrTexture* fTexture; GrRectanizer* fRects; GrBatchAtlas* fAtlas; SkIPoint16 fOffset; // the offset of the plot in the backing texture size_t fBytesPerPixel; SkIRect fDirtyRect; SkDEBUGCODE(bool fDirty;) friend class GrBatchAtlas; typedef SkRefCnt INHERITED; }; //////////////////////////////////////////////////////////////////////////////// class GrPlotUploader : public GrBatchTarget::Uploader { public: GrPlotUploader(BatchPlot* plot) : INHERITED(plot->lastUploadToken()) , fPlot(SkRef(plot)) { SkASSERT(plot); } void upload(GrBatchTarget::TextureUploader uploader) override { fPlot->uploadToTexture(uploader); } private: SkAutoTUnref fPlot; typedef GrBatchTarget::Uploader INHERITED; }; /////////////////////////////////////////////////////////////////////////////// GrBatchAtlas::GrBatchAtlas(GrTexture* texture, int numPlotsX, int numPlotsY) : fTexture(texture) , fNumPlotsX(numPlotsX) , fNumPlotsY(numPlotsY) , fPlotWidth(texture->width() / numPlotsX) , fPlotHeight(texture->height() / numPlotsY) , fAtlasGeneration(kInvalidAtlasGeneration + 1) { SkASSERT(fPlotWidth * fNumPlotsX == texture->width()); SkASSERT(fPlotHeight * fNumPlotsY == texture->height()); // We currently do not support compressed atlases... SkASSERT(!GrPixelConfigIsCompressed(texture->desc().fConfig)); // set up allocated plots fBPP = GrBytesPerPixel(texture->desc().fConfig); fPlotArray = SkNEW_ARRAY(SkAutoTUnref, (fNumPlotsX * fNumPlotsY)); SkAutoTUnref* currPlot = fPlotArray; for (int y = fNumPlotsY - 1, r = 0; y >= 0; --y, ++r) { for (int x = fNumPlotsX - 1, c = 0; x >= 0; --x, ++c) { int id = r * fNumPlotsX + c; currPlot->reset(SkNEW(BatchPlot)); (*currPlot)->init(this, texture, id, 1, x, y, fPlotWidth, fPlotHeight, fBPP); // build LRU list fPlotList.addToHead(currPlot->get()); ++currPlot; } } } GrBatchAtlas::~GrBatchAtlas() { SkSafeUnref(fTexture); SkDELETE_ARRAY(fPlotArray); #if ATLAS_STATS SkDebugf("Num uploads: %d\n", g_UploadCount); #endif } void GrBatchAtlas::processEviction(AtlasID id) { for (int i = 0; i < fEvictionCallbacks.count(); i++) { (*fEvictionCallbacks[i].fFunc)(id, fEvictionCallbacks[i].fData); } } void GrBatchAtlas::makeMRU(BatchPlot* plot) { if (fPlotList.head() == plot) { return; } fPlotList.remove(plot); fPlotList.addToHead(plot); } inline void GrBatchAtlas::updatePlot(GrBatchTarget* batchTarget, AtlasID* id, BatchPlot* plot) { this->makeMRU(plot); // If our most recent upload has already occurred then we have to insert a new // upload. Otherwise, we already have a scheduled upload that hasn't yet ocurred. // This new update will piggy back on that previously scheduled update. if (batchTarget->isIssued(plot->lastUploadToken())) { plot->setLastUploadToken(batchTarget->asapToken()); SkAutoTUnref uploader(SkNEW_ARGS(GrPlotUploader, (plot))); batchTarget->upload(uploader); } *id = plot->id(); } bool GrBatchAtlas::addToAtlas(AtlasID* id, GrBatchTarget* batchTarget, int width, int height, const void* image, SkIPoint16* loc) { // We should already have a texture, TODO clean this up SkASSERT(fTexture && width < fPlotWidth && height < fPlotHeight); // now look through all allocated plots for one we can share, in Most Recently Refed order GrBatchPlotList::Iter plotIter; plotIter.init(fPlotList, GrBatchPlotList::Iter::kHead_IterStart); BatchPlot* plot; while ((plot = plotIter.get())) { if (plot->addSubImage(width, height, image, loc, fBPP * width)) { this->updatePlot(batchTarget, id, plot); return true; } plotIter.next(); } // If the above fails, then see if the least recently refed plot has already been flushed to the // gpu plotIter.init(fPlotList, GrBatchPlotList::Iter::kTail_IterStart); plot = plotIter.get(); SkASSERT(plot); if (batchTarget->isIssued(plot->lastRefToken())) { this->processEviction(plot->id()); plot->resetRects(); SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc, fBPP * width); SkASSERT(verify); this->updatePlot(batchTarget, id, plot); fAtlasGeneration++; return true; } // The least recently refed plot hasn't been flushed to the gpu yet, however, if we have flushed // it to the batch target than we can reuse it. Our last ref token is guaranteed to be less // than or equal to the current token. If its 'less than' the current token, than we can spin // off the plot(ie let the batch target manage it) and create a new plot in its place in our // array. If it is equal to the currentToken, then the caller has to flush draws to the batch // target so we can spin off the plot if (plot->lastRefToken() == batchTarget->currentToken()) { return false; } // We take an extra ref here so our plot isn't deleted when we reset its index in the array. plot->ref(); int index = plot->index(); int x = plot->x(); int y = plot->y(); int generation = plot->genID(); this->processEviction(plot->id()); fPlotList.remove(plot); SkAutoTUnref& newPlot = fPlotArray[plot->index()]; newPlot.reset(SkNEW(BatchPlot)); newPlot->init(this, fTexture, index, ++generation, x, y, fPlotWidth, fPlotHeight, fBPP); fPlotList.addToHead(newPlot.get()); SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, loc, fBPP * width); SkASSERT(verify); newPlot->setLastUploadToken(batchTarget->currentToken()); SkAutoTUnref uploader(SkNEW_ARGS(GrPlotUploader, (newPlot))); batchTarget->upload(uploader); *id = newPlot->id(); plot->unref(); fAtlasGeneration++; return true; } bool GrBatchAtlas::hasID(AtlasID id) { int index = this->getIndexFromID(id); SkASSERT(index < fNumPlotsX * fNumPlotsY); return fPlotArray[index]->genID() == this->getGenerationFromID(id); } void GrBatchAtlas::setLastRefToken(AtlasID id, BatchToken batchToken) { SkASSERT(this->hasID(id)); int index = this->getIndexFromID(id); this->makeMRU(fPlotArray[index]); fPlotArray[index]->setLastRefToken(batchToken); }