blob: 830539418e10cca5952f09d86ca24a75c7a40e21 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkGLTextCache.h"
#include "SkScalerContext.h"
#include "SkTSearch.h"
const GLenum gTextTextureFormat = GL_ALPHA;
const GLenum gTextTextureType = GL_UNSIGNED_BYTE;
SkGLTextCache::Strike::Strike(Strike* next, int width, int height) {
fStrikeWidth = SkNextPow2(SkMax32(kMinStrikeWidth, width));
fStrikeHeight = SkNextPow2(height);
fGlyphCount = 0;
fNextFreeOffsetX = 0;
fNext = next;
fStrikeWidthShift = SkNextLog2(fStrikeWidth);
fStrikeHeightShift = SkNextLog2(fStrikeHeight);
if (next) {
SkASSERT(next->fStrikeHeight == fStrikeHeight);
}
// create an empty texture to receive glyphs
fTexName = 0;
glGenTextures(1, &fTexName);
glBindTexture(GL_TEXTURE_2D, fTexName);
glTexImage2D(GL_TEXTURE_2D, 0, gTextTextureFormat,
fStrikeWidth, fStrikeHeight, 0,
gTextTextureFormat, gTextTextureType, NULL);
SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
SK_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
SkGLTextCache::Strike::~Strike() {
if (fTexName != 0) {
glDeleteTextures(1, &fTexName);
}
}
SkGLTextCache::Strike*
SkGLTextCache::Strike::findGlyph(const SkGlyph& glyph, int* offset) {
Strike* strike = this;
SkDEBUGCODE(const int height = SkNextPow2(glyph.fHeight);)
do {
SkASSERT(height == strike->fStrikeHeight);
int index = SkTSearch(strike->fGlyphIDArray, strike->fGlyphCount,
glyph.fID, sizeof(strike->fGlyphIDArray[0]));
if (index >= 0) {
if (offset) {
*offset = strike->fGlyphOffsetX[index];
}
return strike;
}
strike = strike->fNext;
} while (NULL != strike);
return NULL;
}
static void make_a_whole(void* buffer, int index, int count, size_t elemSize) {
SkASSERT(index >= 0 && index <= count);
size_t offset = index * elemSize;
memmove((char*)buffer + offset + elemSize,
(const char*)buffer + offset,
(count - index) * elemSize);
}
SkGLTextCache::Strike*
SkGLTextCache::Strike::addGlyphAndBind(const SkGlyph& glyph,
const uint8_t image[], int* offset) {
#ifdef SK_DEBUG
SkASSERT(this->findGlyph(glyph, NULL) == NULL);
const int height = SkNextPow2(glyph.fHeight);
SkASSERT(height <= fStrikeHeight && height > (fStrikeHeight >> 1));
#endif
int rowBytes = glyph.rowBytes();
SkASSERT(rowBytes >= glyph.fWidth);
Strike* strike;
if (fGlyphCount == kMaxGlyphCount ||
fNextFreeOffsetX + rowBytes >= fStrikeWidth) {
// this will bind the next texture for us
// SkDebugf("--- extend strike %p\n", this);
strike = SkNEW_ARGS(Strike, (this, rowBytes, glyph.fHeight));
} else {
glBindTexture(GL_TEXTURE_2D, fTexName);
strike = this;
}
uint32_t* idArray = strike->fGlyphIDArray;
uint16_t* offsetArray = strike->fGlyphOffsetX;
const int glyphCount = strike->fGlyphCount;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, strike->fNextFreeOffsetX, 0, rowBytes,
glyph.fHeight, gTextTextureFormat, gTextTextureType,
image);
// need to insert the offset
int index = SkTSearch(idArray, glyphCount, glyph.fID, sizeof(idArray[0]));
SkASSERT(index < 0);
index = ~index; // this is where we should insert it
make_a_whole(idArray, index, glyphCount, sizeof(idArray));
make_a_whole(offsetArray, index, glyphCount, sizeof(offsetArray[0]));
idArray[index] = glyph.fID;
offsetArray[index] = strike->fNextFreeOffsetX;
if (offset) {
*offset = strike->fNextFreeOffsetX;
}
#if 0
SkDebugf("--- strike %p glyph %x [%d %d] offset %d count %d\n",
strike, glyph.fID, glyph.fWidth, glyph.fHeight,
strike->fNextFreeOffsetX, glyphCount + 1);
#endif
// now update our header
strike->fGlyphCount = glyphCount + 1;
strike->fNextFreeOffsetX += glyph.fWidth;
return strike;
}
///////////////////////////////////////////////////////////////////////////////
SkGLTextCache::SkGLTextCache() {
sk_bzero(fStrikeList, sizeof(fStrikeList));
}
SkGLTextCache::~SkGLTextCache() {
this->deleteAllStrikes(true);
}
void SkGLTextCache::deleteAllStrikes(bool texturesAreValid) {
for (size_t i = 0; i < SK_ARRAY_COUNT(fStrikeList); i++) {
Strike* strike = fStrikeList[i];
while (strike != NULL) {
Strike* next = strike->fNext;
if (!texturesAreValid) {
strike->abandonTexture();
}
SkDELETE(strike);
strike = next;
}
}
sk_bzero(fStrikeList, sizeof(fStrikeList));
}
SkGLTextCache::Strike* SkGLTextCache::findGlyph(const SkGlyph& glyph,
int* offset) {
SkASSERT(glyph.fWidth != 0);
SkASSERT(glyph.fHeight != 0);
size_t index = SkNextLog2(glyph.fHeight);
if (index >= SK_ARRAY_COUNT(fStrikeList)) {
// too big for us to cache;
return NULL;
}
Strike* strike = fStrikeList[index];
if (strike) {
strike = strike->findGlyph(glyph, offset);
}
return strike;
}
SkGLTextCache::Strike* SkGLTextCache::addGlyphAndBind(const SkGlyph& glyph,
const uint8_t image[], int* offset) {
SkASSERT(image != NULL);
SkASSERT(glyph.fWidth != 0);
SkASSERT(glyph.fHeight != 0);
size_t index = SkNextLog2(glyph.fHeight);
if (index >= SK_ARRAY_COUNT(fStrikeList)) {
// too big for us to cache;
return NULL;
}
Strike* strike = fStrikeList[index];
if (NULL == strike) {
strike = SkNEW_ARGS(Strike, (NULL, glyph.rowBytes(), glyph.fHeight));
// SkDebugf("--- create strike [%d] %p cache %p\n", index, strike, this);
}
strike = strike->addGlyphAndBind(glyph, image, offset);
fStrikeList[index] = strike;
return strike;
}