/* * Copyright 2010 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrAtlas.h" #include "GrContext.h" #include "GrGpu.h" #include "GrRectanizer.h" #if 0 #define GR_PLOT_WIDTH 8 #define GR_PLOT_HEIGHT 4 #define GR_ATLAS_WIDTH 256 #define GR_ATLAS_HEIGHT 256 #define GR_ATLAS_TEXTURE_WIDTH (GR_PLOT_WIDTH * GR_ATLAS_WIDTH) #define GR_ATLAS_TEXTURE_HEIGHT (GR_PLOT_HEIGHT * GR_ATLAS_HEIGHT) #else #define GR_ATLAS_TEXTURE_WIDTH 1024 #define GR_ATLAS_TEXTURE_HEIGHT 2048 #define GR_ATLAS_WIDTH 256 #define GR_ATLAS_HEIGHT 256 #define GR_PLOT_WIDTH (GR_ATLAS_TEXTURE_WIDTH / GR_ATLAS_WIDTH) #define GR_PLOT_HEIGHT (GR_ATLAS_TEXTURE_HEIGHT / GR_ATLAS_HEIGHT) #endif /////////////////////////////////////////////////////////////////////////////// #define BORDER 1 #ifdef SK_DEBUG static int gCounter; #endif // for testing #define FONT_CACHE_STATS 0 #if FONT_CACHE_STATS static int g_UploadCount = 0; #endif GrPlot::GrPlot() : fDrawToken(NULL, 0) , fNext(NULL) , fTexture(NULL) , fAtlasMgr(NULL) , fBytesPerPixel(1) { fRects = GrRectanizer::Factory(GR_ATLAS_WIDTH - BORDER, GR_ATLAS_HEIGHT - BORDER); fOffset.set(0, 0); } GrPlot::~GrPlot() { delete fRects; } static inline void adjust_for_offset(GrIPoint16* loc, const GrIPoint16& offset) { loc->fX += offset.fX * GR_ATLAS_WIDTH; loc->fY += offset.fY * GR_ATLAS_HEIGHT; } static inline uint8_t* zero_fill(uint8_t* ptr, size_t count) { sk_bzero(ptr, count); return ptr + count; } bool GrPlot::addSubImage(int width, int height, const void* image, GrIPoint16* loc) { if (!fRects->addRect(width + BORDER, height + BORDER, loc)) { return false; } SkAutoSMalloc<1024> storage; int dstW = width + 2*BORDER; int dstH = height + 2*BORDER; if (BORDER) { const size_t dstRB = dstW * fBytesPerPixel; uint8_t* dst = (uint8_t*)storage.reset(dstH * dstRB); sk_bzero(dst, dstRB); // zero top row dst += dstRB; for (int y = 0; y < height; y++) { dst = zero_fill(dst, fBytesPerPixel); // zero left edge memcpy(dst, image, width * fBytesPerPixel); dst += width * fBytesPerPixel; dst = zero_fill(dst, fBytesPerPixel); // zero right edge image = (const void*)((const char*)image + width * fBytesPerPixel); } sk_bzero(dst, dstRB); // zero bottom row image = storage.get(); } adjust_for_offset(loc, fOffset); GrContext* context = fTexture->getContext(); // We pass the flag that does not force a flush. We assume our caller is // smart and hasn't referenced the part of the texture we're about to update // since the last flush. context->writeTexturePixels(fTexture, loc->fX, loc->fY, dstW, dstH, fTexture->config(), image, 0, GrContext::kDontFlush_PixelOpsFlag); // now tell the caller to skip the top/left BORDER loc->fX += BORDER; loc->fY += BORDER; #if FONT_CACHE_STATS ++g_UploadCount; #endif return true; } /////////////////////////////////////////////////////////////////////////////// GrAtlasMgr::GrAtlasMgr(GrGpu* gpu, GrPixelConfig config) { fGpu = gpu; fPixelConfig = config; gpu->ref(); fTexture = NULL; // set up allocated plots size_t bpp = GrBytesPerPixel(fPixelConfig); fPlots = SkNEW_ARRAY(GrPlot, (GR_PLOT_WIDTH*GR_PLOT_HEIGHT)); fFreePlots = NULL; GrPlot* currPlot = fPlots; for (int y = GR_PLOT_HEIGHT-1; y >= 0; --y) { for (int x = GR_PLOT_WIDTH-1; x >= 0; --x) { currPlot->fAtlasMgr = this; currPlot->fOffset.set(x, y); currPlot->fBytesPerPixel = bpp; // add to free list currPlot->fNext = fFreePlots; fFreePlots = currPlot; ++currPlot; } } } GrAtlasMgr::~GrAtlasMgr() { SkSafeUnref(fTexture); SkDELETE_ARRAY(fPlots); fGpu->unref(); #if FONT_CACHE_STATS GrPrintf("Num uploads: %d\n", g_UploadCount); #endif } GrPlot* GrAtlasMgr::addToAtlas(GrAtlas* atlas, int width, int height, const void* image, GrIPoint16* loc) { // iterate through entire plot list, see if we can find a hole GrPlot* plotIter = atlas->fPlots; while (plotIter) { if (plotIter->addSubImage(width, height, image, loc)) { return plotIter; } plotIter = plotIter->fNext; } // If the above fails, then either we have no starting plot, or the current // plot list is full. Either way we need to allocate a new plot GrPlot* newPlot = this->allocPlot(); if (NULL == newPlot) { return NULL; } if (NULL == fTexture) { // TODO: Update this to use the cache rather than directly creating a texture. GrTextureDesc desc; desc.fFlags = kDynamicUpdate_GrTextureFlagBit; desc.fWidth = GR_ATLAS_TEXTURE_WIDTH; desc.fHeight = GR_ATLAS_TEXTURE_HEIGHT; desc.fConfig = fPixelConfig; fTexture = fGpu->createTexture(desc, NULL, 0); if (NULL == fTexture) { return NULL; } } // be sure to set texture for fast lookup newPlot->fTexture = fTexture; if (!newPlot->addSubImage(width, height, image, loc)) { this->freePlot(newPlot); return NULL; } // new plot, put at head newPlot->fNext = atlas->fPlots; atlas->fPlots = newPlot; return newPlot; } bool GrAtlasMgr::removeUnusedPlots(GrAtlas* atlas) { // GrPlot** is used so that the head element can be easily // modified when the first element is deleted GrPlot** plotRef = &atlas->fPlots; GrPlot* plot = atlas->fPlots; bool removed = false; while (NULL != plot) { if (plot->drawToken().isIssued()) { *plotRef = plot->fNext; this->freePlot(plot); plot = *plotRef; removed = true; } else { plotRef = &plot->fNext; plot = plot->fNext; } } return removed; } void GrAtlasMgr::deletePlotList(GrPlot* plot) { while (NULL != plot) { GrPlot* next = plot->fNext; this->freePlot(plot); plot = next; } } GrPlot* GrAtlasMgr::allocPlot() { if (NULL == fFreePlots) { return NULL; } else { GrPlot* alloc = fFreePlots; fFreePlots = alloc->fNext; #ifdef SK_DEBUG // GrPrintf(" GrPlot %p [%d %d] %d\n", this, alloc->fOffset.fX, alloc->fOffset.fY, gCounter); gCounter += 1; #endif return alloc; } } void GrAtlasMgr::freePlot(GrPlot* plot) { SkASSERT(this == plot->fAtlasMgr); plot->fRects->reset(); plot->fNext = fFreePlots; fFreePlots = plot; #ifdef SK_DEBUG --gCounter; // GrPrintf("~GrPlot %p [%d %d] %d\n", this, plot->fOffset.fX, plot->fOffset.fY, gCounter); #endif }