diff --git a/src/modules/graphics/opengl/Font.cpp b/src/modules/graphics/opengl/Font.cpp index 40911ccf3..2473c4661 100644 --- a/src/modules/graphics/opengl/Font.cpp +++ b/src/modules/graphics/opengl/Font.cpp @@ -39,6 +39,9 @@ namespace graphics namespace opengl { +const int Font::TEXTURE_WIDTHS[] = {128, 256, 256, 512, 512, 1024, 1024}; +const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512, 1024}; + Font::Font(love::font::Rasterizer *r, const Image::Filter &filter) : rasterizer(r) , height(r->getHeight()) @@ -47,10 +50,37 @@ Font::Font(love::font::Rasterizer *r, const Image::Filter &filter) , filter(filter) { r->retain(); + love::font::GlyphData *gd = r->getGlyphData(32); type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA ? FONT_TRUETYPE : FONT_IMAGE); delete gd; - createTexture(); + + // try to find the best texture size match for the font size + // default to the largest texture size if no rough match is found + texture_size_index = NUM_TEXTURE_SIZES - 1; + for (int i = 0; i < NUM_TEXTURE_SIZES; i++) + { + // base our chosen texture width/height on a very rough guess of the total size taken up by the font's used glyphs + // the estimate is likely larger than the actual total size taken up, which is good since texture changes are expensive + if ((height * 0.8) * height * 95 <= TEXTURE_WIDTHS[i] * TEXTURE_HEIGHTS[i]) + { + texture_size_index = i; + break; + } + } + + texture_width = TEXTURE_WIDTHS[texture_size_index]; + texture_height = TEXTURE_HEIGHTS[texture_size_index]; + + try + { + createTexture(); + } + catch (love::Exception &e) + { + r->release(); + throw; + } } Font::~Font() @@ -59,9 +89,30 @@ Font::~Font() unloadVolatile(); } +bool Font::initializeTexture(GLint format) +{ + GLint internalformat = (format == GL_LUMINANCE_ALPHA) ? GL_LUMINANCE8_ALPHA8 : GL_RGBA8; + + // clear errors before initializing + while (glGetError() != GL_NO_ERROR); + + glTexImage2D(GL_TEXTURE_2D, + 0, + internalformat, + (GLsizei)texture_width, + (GLsizei)texture_height, + 0, + format, + GL_UNSIGNED_BYTE, + NULL); + + return glGetError() == GL_NO_ERROR; +} + void Font::createTexture() { texture_x = texture_y = rowHeight = TEXTURE_PADDING; + GLuint t; glGenTextures(1, &t); textures.push_back(t); @@ -74,65 +125,85 @@ void Font::createTexture() glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); GLint format = (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA); - // Initialize the texture - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGBA, - (GLsizei)TEXTURE_WIDTH, - (GLsizei)TEXTURE_HEIGHT, - 0, - format, - GL_UNSIGNED_BYTE, - NULL); + + + // try to initialize the texture, attempting smaller sizes if initialization fails + bool initialized = false; + while (texture_size_index >= 0) + { + texture_width = TEXTURE_WIDTHS[texture_size_index]; + texture_height = TEXTURE_HEIGHTS[texture_size_index]; + + initialized = initializeTexture(format); + + if (initialized || texture_size_index <= 0) + break; + + --texture_size_index; + } + + if (!initialized) + { + // cleanup before throwing + deleteTexture(t); + bindTexture(0); + textures.pop_back(); + + throw love::Exception("Could not create font texture!"); + } + // Fill the texture with transparent black - std::vector emptyData(TEXTURE_WIDTH * TEXTURE_HEIGHT * (type == FONT_TRUETYPE ? 2 : 4), 0); + std::vector emptyData(texture_width * texture_height * (type == FONT_TRUETYPE ? 2 : 4), 0); glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, - 0, - (GLsizei)TEXTURE_WIDTH, - (GLsizei)TEXTURE_HEIGHT, + 0, 0, + (GLsizei)texture_width, + (GLsizei)texture_height, format, GL_UNSIGNED_BYTE, &emptyData[0]); } -Font::Glyph *Font::addGlyph(int glyph) +Font::Glyph *Font::addGlyph(const int glyph) { love::font::GlyphData *gd = rasterizer->getGlyphData(glyph); int w = gd->getWidth(); int h = gd->getHeight(); - if (texture_x + w + TEXTURE_PADDING > TEXTURE_WIDTH) + if (texture_x + w + TEXTURE_PADDING > texture_width) { // out of space - new row! texture_x = TEXTURE_PADDING; texture_y += rowHeight; rowHeight = TEXTURE_PADDING; } - if (texture_y + h + TEXTURE_PADDING > TEXTURE_HEIGHT) + if (texture_y + h + TEXTURE_PADDING > texture_height) { // totally out of space - new texture! createTexture(); } Glyph *g = new Glyph; - g->list = g->texture = 0; + + g->texture = 0; g->spacing = gd->getAdvance(); + + memset(&g->quad, 0, sizeof(GlyphQuad)); // don't waste space for empty glyphs. also fixes a division by zero bug with ati drivers if (w > 0 && h > 0) { - g->list = glGenLists(1); - if (0 == g->list) - { - delete g; - return NULL; - } - - GLuint t = textures.back(); + const GLuint t = textures.back(); + bindTexture(t); - glTexSubImage2D(GL_TEXTURE_2D, 0, texture_x, texture_y, w, h, (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA), GL_UNSIGNED_BYTE, gd->getData()); + glTexSubImage2D(GL_TEXTURE_2D, + 0, + texture_x, + texture_y, + w, h, + (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA), + GL_UNSIGNED_BYTE, + gd->getData()); g->texture = t; @@ -141,25 +212,17 @@ Font::Glyph *Font::addGlyph(int glyph) v.y = (float) texture_y; v.w = (float) w; v.h = (float) h; - Quad *q = new Quad(v, (const float) TEXTURE_WIDTH, (const float) TEXTURE_HEIGHT); - const vertex *verts = q->getVertices(); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&verts[0].x); - glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&verts[0].s); - - glNewList(g->list, GL_COMPILE); - glPushMatrix(); - glTranslatef(static_cast(gd->getBearingX()), static_cast(-gd->getBearingY()), 0.0f); - glDrawArrays(GL_QUADS, 0, 4); - glPopMatrix(); - glEndList(); - - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - - delete q; + + Quad q = Quad(v, (const float) texture_width, (const float) texture_height); + const vertex *verts = q.getVertices(); + + // copy vertex data to the glyph and set proper bearing + for (int i = 0; i < 4; i++) + { + g->quad.vertices[i] = verts[i]; + g->quad.vertices[i].x += gd->getBearingX(); + g->quad.vertices[i].y -= gd->getBearingY(); + } } if (w > 0) @@ -168,7 +231,18 @@ Font::Glyph *Font::addGlyph(int glyph) rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); delete gd; + glyphs[glyph] = g; + + return g; +} + +Font::Glyph *Font::findGlyph(const int glyph) +{ + Glyph *g = glyphs[glyph]; + if (!g) + g = addGlyph(glyph); + return g; } @@ -177,61 +251,120 @@ float Font::getHeight() const return static_cast(height); } -void Font::print(std::string text, float x, float y, float letter_spacing, float angle, float sx, float sy, float ox, float oy, float kx, float ky) +void Font::print(const std::string &text, float x, float y, float letter_spacing, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { float dx = 0.0f; // spacing counter for newline handling + float dy = 0.0f; + + // keeps track of when we need to switch textures in our vertex array + std::vector glyphinfolist; + + std::vector glyphquads; + glyphquads.reserve(text.size()); // pre-allocate space for the maximum possible number of quads + + int quadindex = 0; + glPushMatrix(); Matrix t; t.setTransformation(ceil(x), ceil(y), angle, sx, sy, ox, oy, kx, ky); glMultMatrixf((const GLfloat *)t.getElements()); + try { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + while (i != end) { int g = *i++; + if (g == '\n') { // wrap newline, but do not print it - glTranslatef(-dx, floor(getHeight() * getLineHeight() + 0.5f), 0); + dy += floor(getHeight() * getLineHeight() + 0.5f); dx = 0.0f; continue; } - Glyph *glyph = glyphs[g]; - if (!glyph) glyph = addGlyph(g); - glPushMatrix(); - // 1.25 is magic line height for true type fonts - if (type == FONT_TRUETYPE) glTranslatef(0, floor(getHeight() / 1.25f + 0.5f), 0); - bindTexture(glyph->texture); - glCallList(glyph->list); - glPopMatrix(); - glTranslatef(static_cast(glyph->spacing + letter_spacing), 0, 0); + + Glyph *glyph = findGlyph(g); + + // we only care about the vertices of glyphs which have a texture + if (glyph->texture != 0) + { + // copy glyphquad (4 vertices) from original glyph to our current quad list + glyphquads.push_back(glyph->quad); + + // 1.25 is magic line height for true type fonts + float lineheight = (type == FONT_TRUETYPE) ? floor(getHeight() / 1.25f + 0.5f) : 0.0f; + + // set proper relative position + for (int i = 0; i < 4; i++) + { + glyphquads[quadindex].vertices[i].x += dx; + glyphquads[quadindex].vertices[i].y += dy + lineheight; + } + + size_t listsize = glyphinfolist.size(); + + // check if current glyph texture has changed since the previous iteration + if (listsize == 0 || glyphinfolist[listsize-1].texture != glyph->texture) + { + // keep track of each sub-section of the string whose glyphs use different textures than the previous section + GlyphArrayDrawInfo glyphdrawinfo; + glyphdrawinfo.startquad = quadindex; + glyphdrawinfo.numquads = 0; + glyphdrawinfo.texture = glyph->texture; + glyphinfolist.push_back(glyphdrawinfo); + } + + ++quadindex; + ++glyphinfolist[glyphinfolist.size()-1].numquads; + } + + // advance the x position for the next glyph dx += glyph->spacing + letter_spacing; } } - catch(utf8::exception &e) + catch (love::Exception &e) + { + glPopMatrix(); + throw; + } + catch (utf8::exception &e) { glPopMatrix(); throw love::Exception("%s", e.what()); } - glPopMatrix(); -} - -void Font::print(char character, float x, float y) -{ - Glyph *glyph = glyphs[character]; - if (!glyph) glyph = addGlyph(character); - - if (0 != glyph->texture) - { - glPushMatrix(); - glTranslatef(x, floor(y+getHeight() + 0.5f), 0.0f); - bindTexture(glyph->texture); - glCallList(glyph->list); - glPopMatrix(); + + if (quadindex > 0 && glyphinfolist.size() > 0) + { + // sort glyph draw info list by texture first, and quad position in memory second (using the struct's < operator) + std::sort(glyphinfolist.begin(), glyphinfolist.end()); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].x); + glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].s); + + // we need to draw a new vertex array for every section of the string that uses a different texture than the previous section + std::vector::const_iterator it; + for (it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it) + { + bindTexture(it->texture); + + int startvertex = it->startquad * 4; + int numvertices = it->numquads * 4; + + glDrawArrays(GL_QUADS, startvertex, numvertices); + } + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); } + + glPopMatrix(); } int Font::getWidth(const std::string &str) @@ -253,8 +386,7 @@ int Font::getWidth(const std::string &str) while (i != end) { int c = *i++; - g = glyphs[c]; - if (!g) g = addGlyph(c); + g = findGlyph(c); width += static_cast(g->spacing * mSpacing); } } @@ -277,12 +409,11 @@ int Font::getWidth(const char *str) int Font::getWidth(const char character) { - Glyph *g = glyphs[character]; - if (!g) g = addGlyph(character); + Glyph *g = findGlyph(character); return g->spacing; } -std::vector Font::getWrap(const std::string text, float wrap, int *max_width) +std::vector Font::getWrap(const std::string &text, float wrap, int *max_width) { using namespace std; const float width_space = static_cast(getWidth(' ')); @@ -376,7 +507,6 @@ void Font::unloadVolatile() while (it != glyphs.end()) { g = it->second; - glDeleteLists(g->list, 1); delete g; glyphs.erase(it++); } diff --git a/src/modules/graphics/opengl/Font.h b/src/modules/graphics/opengl/Font.h index 0dde331b6..c8cd4fb71 100644 --- a/src/modules/graphics/opengl/Font.h +++ b/src/modules/graphics/opengl/Font.h @@ -32,7 +32,6 @@ #include "graphics/Image.h" #include "OpenGL.h" -#include "GLee.h" namespace love { @@ -69,16 +68,7 @@ public: * @param kx Shear along the x axis. * @param ky Shear along the y axis. **/ - void print(std::string text, float x, float y, float letter_spacing = 0.0f, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f); - - /** - * Prints the character at the designated position. - * - * @param character A character. - * @param x The x-coordinate. - * @param y The y-coordinate. - **/ - void print(char character, float x, float y); + void print(const std::string &text, float x, float y, float letter_spacing = 0.0f, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f); /** * Returns the height of the font. @@ -109,7 +99,7 @@ public: * @param max_width Optional output of the maximum width * Returns a vector with the lines. **/ - std::vector getWrap(const std::string text, float wrap, int *max_width = 0); + std::vector getWrap(const std::string &text, float wrap, int *max_width = 0); /** * Sets the line height (which should be a number to multiply the font size by, @@ -139,6 +129,7 @@ public: // Implements Volatile. bool loadVolatile(); void unloadVolatile(); + private: enum FontType @@ -147,12 +138,35 @@ private: FONT_IMAGE, FONT_UNKNOWN }; + + // thin wrapper for an array of 4 vertices + struct GlyphQuad + { + vertex vertices[4]; + }; struct Glyph { - GLuint list; GLuint texture; int spacing; + GlyphQuad quad; + }; + + // used to determine when to change textures in the vertex array generated when printing text + struct GlyphArrayDrawInfo + { + GLuint texture; + int startquad, numquads; + + // used when sorting with std::sort + // sorts by texture first (binding textures is expensive) and relative position in memory second + bool operator < (const GlyphArrayDrawInfo &other) const + { + if (texture != other.texture) + return texture < other.texture; + else + return startquad < other.startquad; + }; }; love::font::Rasterizer *rasterizer; @@ -160,20 +174,29 @@ private: int height; float lineHeight; float mSpacing; // modifies the spacing by multiplying it with this value + + int texture_size_index; + int texture_width; + int texture_height; + std::vector textures; // vector of packed textures - std::map glyphs; // maps glyphs to display lists + std::map glyphs; // maps glyphs to quad information FontType type; Image::Filter filter; + + static const int NUM_TEXTURE_SIZES = 7; + static const int TEXTURE_WIDTHS[NUM_TEXTURE_SIZES]; + static const int TEXTURE_HEIGHTS[NUM_TEXTURE_SIZES]; - static const int TEXTURE_WIDTH = 512; - static const int TEXTURE_HEIGHT = 512; static const int TEXTURE_PADDING = 1; int texture_x, texture_y; int rowHeight; + bool initializeTexture(GLint format); void createTexture(); - Glyph *addGlyph(int glyph); + Glyph *addGlyph(const int glyph); + Glyph *findGlyph (const int glyph); }; // Font } // opengl diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 5bd68e7f2..9d434acc8 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -290,8 +290,16 @@ int w_newFont(lua_State *L) love::font::Rasterizer *rasterizer = luax_checktype(L, 1, "Rasterizer", FONT_RASTERIZER_T); - // Create the font. - Font *font = instance->newFont(rasterizer, instance->getDefaultImageFilter()); + Font *font = NULL; + try + { + // Create the font. + font = instance->newFont(rasterizer, instance->getDefaultImageFilter()); + } + catch (love::Exception &e) + { + return luaL_error(L, e.what()); + } if (font == 0) return luaL_error(L, "Could not load font.");