blob: dd362908d3fedc65a3459dc52bc0b88dcd250563 [file] [log] [blame]
/*
* 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
}