Browse Source

Adapted Aster Jian's mutable glyph mechanism. Globally switchable on from the UI subsystem. Configurable font texture max size in UI subsystem.

Lasse Öörni 12 years ago
parent
commit
267523a738

+ 6 - 0
Docs/LuaScriptAPI.dox

@@ -4886,8 +4886,10 @@ Methods:
 - bool SaveLayout(Serializer& dest, UIElement* element)
 - bool SaveLayout(Serializer& dest, UIElement* element)
 - void SetClipBoardText(const String text)
 - void SetClipBoardText(const String text)
 - void SetDoubleClickInterval(float interval)
 - void SetDoubleClickInterval(float interval)
+- void SetMaxFontTextureSize(int size)
 - void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 - void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 - void SetUseSystemClipBoard(bool enable)
 - void SetUseSystemClipBoard(bool enable)
+- void SetUseMutableGlyphs(bool enable)
 - UIElement* GetRoot() const
 - UIElement* GetRoot() const
 - UIElement* GetRootModalElement() const
 - UIElement* GetRootModalElement() const
 - Cursor* GetCursor() const
 - Cursor* GetCursor() const
@@ -4898,8 +4900,10 @@ Methods:
 - IntVector2 GetCursorPosition() const
 - IntVector2 GetCursorPosition() const
 - const String& GetClipBoardText() const
 - const String& GetClipBoardText() const
 - float GetDoubleClickInterval() const
 - float GetDoubleClickInterval() const
+- int GetMaxFontTextureSize() const
 - bool IsNonFocusedMouseWheel() const
 - bool IsNonFocusedMouseWheel() const
 - bool GetUseSystemClipBoard() const
 - bool GetUseSystemClipBoard() const
+- bool GetUseMutableGlyphs() const
 - bool HasModalElement() const
 - bool HasModalElement() const
 
 
 Properties:
 Properties:
@@ -4912,8 +4916,10 @@ Properties:
 - IntVector2 cursorPosition (readonly)
 - IntVector2 cursorPosition (readonly)
 - String& clipBoardText
 - String& clipBoardText
 - float doubleClickInterval
 - float doubleClickInterval
+- int maxFontTextureSize
 - bool nonFocusedMouseWheel
 - bool nonFocusedMouseWheel
 - bool useSystemClipBoard
 - bool useSystemClipBoard
+- bool useMutableGlyphs
 - bool modalElement (readonly)
 - bool modalElement (readonly)
 
 
 ### UIElement : Serializable
 ### UIElement : Serializable

+ 2 - 0
Docs/ScriptAPI.dox

@@ -6468,8 +6468,10 @@ Properties:
 - UIElement@ modalRoot (readonly)
 - UIElement@ modalRoot (readonly)
 - String clipBoardText
 - String clipBoardText
 - float doubleClickInterval
 - float doubleClickInterval
+- int maxFontTextureSize
 - bool nonFocusedMouseWheel
 - bool nonFocusedMouseWheel
 - bool useSystemClipBoard
 - bool useSystemClipBoard
+- bool useMutableGlyphs
 
 
 
 
 ### Controls
 ### Controls

+ 4 - 0
Source/Engine/Script/UIAPI.cpp

@@ -603,10 +603,14 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "const String& get_clipBoardText() const", asMETHOD(UI, GetClipBoardText), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "const String& get_clipBoardText() const", asMETHOD(UI, GetClipBoardText), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_doubleClickInterval(float)", asMETHOD(UI, SetDoubleClickInterval), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_doubleClickInterval(float)", asMETHOD(UI, SetDoubleClickInterval), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "float get_doubleClickInterval() const", asMETHOD(UI, GetDoubleClickInterval), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "float get_doubleClickInterval() const", asMETHOD(UI, GetDoubleClickInterval), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void set_maxFontTextureSize(int)", asMETHOD(UI, SetMaxFontTextureSize), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "int get_maxFontTextureSize() const", asMETHOD(UI, GetMaxFontTextureSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_nonFocusedMouseWheel(bool)", asMETHOD(UI, SetNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_nonFocusedMouseWheel(bool)", asMETHOD(UI, SetNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_nonFocusedMouseWheel() const", asMETHOD(UI, IsNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_nonFocusedMouseWheel() const", asMETHOD(UI, IsNonFocusedMouseWheel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_useSystemClipBoard(bool)", asMETHOD(UI, SetUseSystemClipBoard), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_useSystemClipBoard(bool)", asMETHOD(UI, SetUseSystemClipBoard), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_useSystemClipBoard() const", asMETHOD(UI, GetUseSystemClipBoard), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_useSystemClipBoard() const", asMETHOD(UI, GetUseSystemClipBoard), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void set_useMutableGlyphs(bool)", asMETHOD(UI, SetUseMutableGlyphs), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "bool get_useMutableGlyphs() const", asMETHOD(UI, GetUseMutableGlyphs), asCALL_THISCALL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);
 }
 }
 
 

+ 117 - 37
Source/Engine/UI/Font.cpp

@@ -32,6 +32,7 @@
 #include "ResourceCache.h"
 #include "ResourceCache.h"
 #include "StringUtils.h"
 #include "StringUtils.h"
 #include "Texture2D.h"
 #include "Texture2D.h"
+#include "UI.h"
 #include "XMLFile.h"
 #include "XMLFile.h"
 
 
 #include <ft2build.h>
 #include <ft2build.h>
@@ -73,6 +74,11 @@ private:
     FT_Library library_;
     FT_Library library_;
 };
 };
 
 
+MutableGlyph::MutableGlyph() :
+    glyphIndex_(M_MAX_UNSIGNED)
+{
+}
+
 FontGlyph::FontGlyph() :
 FontGlyph::FontGlyph() :
     used_(false),
     used_(false),
     page_(M_MAX_UNSIGNED)
     page_(M_MAX_UNSIGNED)
@@ -103,6 +109,10 @@ FontFace::~FontFace()
             totalTextureSize += textures_[i]->GetWidth() * textures_[i]->GetHeight();
             totalTextureSize += textures_[i]->GetWidth() * textures_[i]->GetHeight();
         font_->SetMemoryUse(font_->GetMemoryUse() - totalTextureSize);
         font_->SetMemoryUse(font_->GetMemoryUse() - totalTextureSize);
     }
     }
+    
+    for (List<MutableGlyph*>::Iterator i = mutableGlyphs_.Begin(); i != mutableGlyphs_.End(); ++i)
+        delete *i;
+    mutableGlyphs_.Clear();
 }
 }
 
 
 const FontGlyph* FontFace::GetGlyph(unsigned c)
 const FontGlyph* FontFace::GetGlyph(unsigned c)
@@ -111,11 +121,18 @@ const FontGlyph* FontFace::GetGlyph(unsigned c)
     if (i != glyphMapping_.End())
     if (i != glyphMapping_.End())
     {
     {
         FontGlyph& glyph = glyphs_[i->second_];
         FontGlyph& glyph = glyphs_[i->second_];
+        // Render glyph if not yet resident in a page texture
         if (glyph.page_ == M_MAX_UNSIGNED)
         if (glyph.page_ == M_MAX_UNSIGNED)
+            RenderGlyph(i->second_);
+        // If mutable glyphs in use, move to the front of the list
+        if (mutableGlyphs_.Size() && glyph.iterator_ != mutableGlyphs_.End())
         {
         {
-            if (!RenderGlyph(i->second_))
-                return 0;
+            MutableGlyph* mutableGlyph = *glyph.iterator_;
+            mutableGlyphs_.Erase(glyph.iterator_);
+            mutableGlyphs_.PushFront(mutableGlyph);
+            glyph.iterator_ = mutableGlyphs_.Begin();
         }
         }
+        
         glyph.used_ = true;
         glyph.used_ = true;
         return &glyph;
         return &glyph;
     }
     }
@@ -161,13 +178,11 @@ bool FontFace::IsDataLost() const
     return false;
     return false;
 }
 }
 
 
-bool FontFace::RenderAllGlyphs()
+bool FontFace::RenderAllGlyphs(int maxWidth, int maxHeight)
 {
 {
-    // RenderAllGlyphs() is valid only when no textures have yet been allocated
-    if (!font_ || !face_ || textures_.Size())
-        return false;
+    assert(font_ && face_ && textures_.Empty());
     
     
-    allocator_ = AreaAllocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MAX_SIZE, FONT_TEXTURE_MAX_SIZE);
+    allocator_ = AreaAllocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, maxWidth, maxHeight);
     
     
     for (unsigned i = 0; i < glyphs_.Size(); ++i)
     for (unsigned i = 0; i < glyphs_.Size(); ++i)
     {
     {
@@ -224,10 +239,9 @@ bool FontFace::RenderAllGlyphs()
     return true;
     return true;
 }
 }
 
 
-bool FontFace::RenderGlyph(unsigned index)
+void FontFace::RenderGlyph(unsigned index)
 {
 {
-    if (!font_ || !face_ || !textures_.Size())
-        return false;
+    assert(font_ && face_);
     
     
     FontGlyph& glyph = glyphs_[index];
     FontGlyph& glyph = glyphs_[index];
     
     
@@ -237,31 +251,57 @@ bool FontFace::RenderGlyph(unsigned index)
         glyph.x_ = 0;
         glyph.x_ = 0;
         glyph.y_ = 0;
         glyph.y_ = 0;
         glyph.page_ = textures_.Size() - 1;
         glyph.page_ = textures_.Size() - 1;
-        return true;
+        return;
     }
     }
     
     
-    // Try to allocate from current page, reserve next page if fails
-    int x, y;
-    if (!allocator_.Allocate(glyph.width_ + 1, glyph.height_ + 1, x, y))
+    if (!mutableGlyphs_.Size())
     {
     {
-        SetupNextTexture();
-        // This always succeeds, as it is the first allocation of an empty page
-        allocator_.Allocate(glyph.width_ + 1, glyph.height_ + 1, x, y);
+        // Not using mutable glyphs: try to allocate from current page, reserve next page if fails
+        int x, y;
+        if (!allocator_.Allocate(glyph.width_ + 1, glyph.height_ + 1, x, y))
+        {
+            SetupNextTexture(textures_[0]->GetWidth(), textures_[0]->GetHeight());
+            // This always succeeds, as it is the first allocation of an empty page
+            allocator_.Allocate(glyph.width_ + 1, glyph.height_ + 1, x, y);
+        }
+        
+        glyph.x_ = x;
+        glyph.y_ = y;
+        glyph.page_ = textures_.Size() - 1;
+        
+        if (!bitmap_ || (int)bitmapSize_ < glyph.width_ * glyph.height_)
+        {
+            bitmapSize_ = glyph.width_ * glyph.height_;
+            bitmap_ = new unsigned char[bitmapSize_];
+        }
+        
+        RenderGlyphBitmap(index, bitmap_.Get(), glyph.width_);
+        textures_.Back()->SetData(0, glyph.x_, glyph.y_, glyph.width_, glyph.height_, bitmap_.Get());
     }
     }
-    
-    glyph.x_ = x;
-    glyph.y_ = y;
-    glyph.page_ = textures_.Size() - 1;
-    
-    if (!bitmap_ || (int)bitmapSize_ < glyph.width_ * glyph.height_)
+    else
     {
     {
-        bitmapSize_ = glyph.width_ * glyph.height_;
-        bitmap_ = new unsigned char[bitmapSize_];
+        // Using mutable glyphs: overwrite the least recently used glyph
+        List<MutableGlyph*>::Iterator it = --mutableGlyphs_.End();
+        MutableGlyph* mutableGlyph = *it;
+        if (mutableGlyph->glyphIndex_ != M_MAX_UNSIGNED)
+            glyphs_[mutableGlyph->glyphIndex_].page_ = M_MAX_UNSIGNED;
+        glyph.x_ = mutableGlyph->x_;
+        glyph.y_ = mutableGlyph->y_;
+        glyph.page_ = 0;
+        glyph.iterator_ = it;
+        mutableGlyph->glyphIndex_ = index;
+        
+        if (!bitmap_)
+        {
+            bitmapSize_ = cellWidth_ * cellHeight_;
+            bitmap_ = new unsigned char[bitmapSize_];
+        }
+        
+        // Clear the cell bitmap before rendering to ensure padding
+        memset(bitmap_.Get(), 0, cellWidth_ * cellHeight_);
+        RenderGlyphBitmap(index, bitmap_.Get(), cellWidth_);
+        textures_[0]->SetData(0, glyph.x_, glyph.y_, cellWidth_, cellHeight_, bitmap_.Get());
     }
     }
-    
-    RenderGlyphBitmap(index, bitmap_.Get(), glyph.width_);
-    textures_.Back()->SetData(0, glyph.x_, glyph.y_, glyph.width_, glyph.height_, bitmap_.Get());
-    return true;
 }
 }
 
 
 void FontFace::RenderGlyphBitmap(unsigned index, unsigned char* dest, unsigned pitch)
 void FontFace::RenderGlyphBitmap(unsigned index, unsigned char* dest, unsigned pitch)
@@ -299,17 +339,45 @@ void FontFace::RenderGlyphBitmap(unsigned index, unsigned char* dest, unsigned p
     }
     }
 }
 }
 
 
-void FontFace::SetupNextTexture()
+void FontFace::SetupNextTexture(int width, int height)
 {
 {
     // If several dynamic textures are needed, use the maximum size to pack as many as possible to one texture
     // If several dynamic textures are needed, use the maximum size to pack as many as possible to one texture
-    allocator_ = AreaAllocator(FONT_TEXTURE_MAX_SIZE, FONT_TEXTURE_MAX_SIZE);
+    allocator_ = AreaAllocator(width, height);
 
 
     SharedPtr<Texture2D> texture = font_->CreateFaceTexture();
     SharedPtr<Texture2D> texture = font_->CreateFaceTexture();
-    texture->SetSize(FONT_TEXTURE_MAX_SIZE, FONT_TEXTURE_MAX_SIZE, Graphics::GetAlphaFormat());
+    texture->SetSize(width, height, Graphics::GetAlphaFormat());
+    SharedArrayPtr<unsigned char> emptyBitmap(new unsigned char[width * height]);
+    memset(emptyBitmap.Get(), 0, width * height);
+    texture->SetData(0, 0, 0, width, height, emptyBitmap.Get());
+    
     textures_.Push(texture);
     textures_.Push(texture);
-    font_->SetMemoryUse(font_->GetMemoryUse() + FONT_TEXTURE_MAX_SIZE * FONT_TEXTURE_MAX_SIZE);
+    font_->SetMemoryUse(font_->GetMemoryUse() + width * height);
     
     
-    LOGDEBUG(ToString("Font face %s (%dpt) is now using %d dynamic page textures", GetFileName(font_->GetName()).CString(), pointSize_, textures_.Size()));
+    LOGDEBUG(ToString("Font face %s (%dpt) is using %d dynamic page textures of size %dx%d", GetFileName(font_->GetName()).CString(),
+        pointSize_, textures_.Size(), width, height));
+}
+
+void FontFace::SetupMutableGlyphs(int textureWidth, int textureHeight, int maxWidth, int maxHeight)
+{
+    assert(mutableGlyphs_.Empty());
+    
+    SetupNextTexture(textureWidth, textureHeight);
+    
+    cellWidth_ = maxWidth + 1;
+    cellHeight_ = maxHeight + 1;
+    
+    // Allocate as many mutable glyphs as possible
+    int x, y;
+    while (allocator_.Allocate(cellWidth_, cellHeight_, x, y))
+    {
+        MutableGlyph* glyph = new MutableGlyph();
+        glyph->x_ = x;
+        glyph->y_ = y;
+        mutableGlyphs_.Push(glyph);
+    }
+    
+    LOGDEBUG(ToString("Font face %s (%dpt) is using %d mutable glyphs", GetFileName(font_->GetName()).CString(), pointSize_,
+        mutableGlyphs_.Size()));
 }
 }
 
 
 Font::Font(Context* context) :
 Font::Font(Context* context) :
@@ -536,6 +604,9 @@ FontFace* Font::GetFaceTTF(int pointSize)
     // Ensure the FreeType library is kept alive as long as TTF font resources exist
     // Ensure the FreeType library is kept alive as long as TTF font resources exist
     freeType_ = freeType;
     freeType_ = freeType;
     
     
+    UI* ui = GetSubsystem<UI>();
+    int maxTextureSize = ui->GetMaxFontTextureSize();
+    
     FT_Face face;
     FT_Face face;
     FT_Error error;
     FT_Error error;
     FT_Library library = freeType->GetLibrary();
     FT_Library library = freeType->GetLibrary();
@@ -585,6 +656,7 @@ FontFace* Font::GetFaceTTF(int pointSize)
     LOGDEBUG(ToString("Font face %s (%dpt) has %d glyphs", GetFileName(GetName()).CString(), pointSize, numGlyphs));
     LOGDEBUG(ToString("Font face %s (%dpt) has %d glyphs", GetFileName(GetName()).CString(), pointSize, numGlyphs));
     
     
     // Load each of the glyphs to see the sizes & store other information
     // Load each of the glyphs to see the sizes & store other information
+    int maxWidth = 0;
     int maxHeight = 0;
     int maxHeight = 0;
     FT_Pos ascender = face->size->metrics.ascender;
     FT_Pos ascender = face->size->metrics.ascender;
     
     
@@ -604,6 +676,7 @@ FontFace* Font::GetFaceTTF(int pointSize)
             newGlyph.offsetY_ = (short)((ascender - slot->metrics.horiBearingY) >> 6);
             newGlyph.offsetY_ = (short)((ascender - slot->metrics.horiBearingY) >> 6);
             newGlyph.advanceX_ = (short)((slot->metrics.horiAdvance) >> 6);
             newGlyph.advanceX_ = (short)((slot->metrics.horiAdvance) >> 6);
             
             
+            maxWidth = Max(maxWidth, newGlyph.width_);
             maxHeight = Max(maxHeight, newGlyph.height_);
             maxHeight = Max(maxHeight, newGlyph.height_);
         }
         }
         else
         else
@@ -640,13 +713,18 @@ FontFace* Font::GetFaceTTF(int pointSize)
     
     
     // Now try to pack into the smallest possible texture. If face does not fit into one texture, enable dynamic mode where
     // Now try to pack into the smallest possible texture. If face does not fit into one texture, enable dynamic mode where
     // glyphs are only created as necessary
     // glyphs are only created as necessary
-    if (newFace->RenderAllGlyphs())
+    if (newFace->RenderAllGlyphs(maxTextureSize, maxTextureSize))
     {
     {
         FT_Done_Face(face);
         FT_Done_Face(face);
         newFace->face_ = 0;
         newFace->face_ = 0;
     }
     }
     else
     else
-        newFace->SetupNextTexture();
+    {
+        if (ui->GetUseMutableGlyphs())
+            newFace->SetupMutableGlyphs(maxTextureSize, maxTextureSize, maxWidth, maxHeight);
+        else
+            newFace->SetupNextTexture(maxTextureSize, maxTextureSize);
+    }
     
     
     faces_[pointSize] = newFace;
     faces_[pointSize] = newFace;
     return newFace;
     return newFace;
@@ -791,6 +869,8 @@ SharedPtr<FontFace> Font::Pack(FontFace* fontFace)
     // Set parent font as null for the packed face so that it does not attempt to manage the font's total memory use
     // Set parent font as null for the packed face so that it does not attempt to manage the font's total memory use
     SharedPtr<FontFace> packedFontFace(new FontFace((Font*)0));
     SharedPtr<FontFace> packedFontFace(new FontFace((Font*)0));
     
     
+    int maxTextureSize = GetSubsystem<UI>()->GetMaxFontTextureSize();
+    
     // Clone properties
     // Clone properties
     packedFontFace->pointSize_ = fontFace->pointSize_;
     packedFontFace->pointSize_ = fontFace->pointSize_;
     packedFontFace->rowHeight_ = fontFace->rowHeight_;
     packedFontFace->rowHeight_ = fontFace->rowHeight_;
@@ -812,7 +892,7 @@ SharedPtr<FontFace> Font::Pack(FontFace* fontFace)
     HashMap<unsigned, unsigned>::ConstIterator i;
     HashMap<unsigned, unsigned>::ConstIterator i;
     while (startIter != fontFace->glyphMapping_.End())
     while (startIter != fontFace->glyphMapping_.End())
     {
     {
-        AreaAllocator allocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MAX_SIZE, FONT_TEXTURE_MAX_SIZE);
+        AreaAllocator allocator(FONT_TEXTURE_MIN_SIZE, FONT_TEXTURE_MIN_SIZE, maxTextureSize, maxTextureSize);
         for (i = startIter; i != fontFace->glyphMapping_.End(); ++i)
         for (i = startIter; i != fontFace->glyphMapping_.End(); ++i)
         {
         {
             FontGlyph glyph = fontFace->glyphs_[i->second_];
             FontGlyph glyph = fontFace->glyphs_[i->second_];

+ 32 - 8
Source/Engine/UI/Font.h

@@ -24,6 +24,7 @@
 
 
 #include "AreaAllocator.h"
 #include "AreaAllocator.h"
 #include "ArrayPtr.h"
 #include "ArrayPtr.h"
+#include "List.h"
 #include "Resource.h"
 #include "Resource.h"
 
 
 namespace Urho3D
 namespace Urho3D
@@ -36,9 +37,22 @@ class Image;
 class Texture2D;
 class Texture2D;
 
 
 static const int FONT_TEXTURE_MIN_SIZE = 128;
 static const int FONT_TEXTURE_MIN_SIZE = 128;
-static const int FONT_TEXTURE_MAX_SIZE = 2048;
 static const int FONT_DPI = 96;
 static const int FONT_DPI = 96;
 
 
+/// Mutable font glyph description.
+struct MutableGlyph
+{
+    /// Construct.
+    MutableGlyph();
+    
+    /// The actual glyph index that currently occupies this mutable slot. M_MAX_UNSIGNED if none.
+    unsigned glyphIndex_;
+    /// X position in texture.
+    short x_;
+    /// Y position in texture.
+    short y_;
+};
+
 /// %Font glyph description.
 /// %Font glyph description.
 struct FontGlyph
 struct FontGlyph
 {
 {
@@ -65,6 +79,8 @@ struct FontGlyph
     bool used_;
     bool used_;
     /// Kerning information.
     /// Kerning information.
     HashMap<unsigned, unsigned> kerning_;
     HashMap<unsigned, unsigned> kerning_;
+    /// Mutable glyph list iterator.
+    List<MutableGlyph*>::Iterator iterator_;
 };
 };
 
 
 /// %Font file type.
 /// %Font file type.
@@ -101,14 +117,16 @@ public:
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
     
     
 private:
 private:
-    /// Render all glyphs of the face into a single texture. Return true if could fit them. Called internally.
-    bool RenderAllGlyphs();
-    /// Render one glyph on demand into the current texture. Return true if successful. Called internally.
-    bool RenderGlyph(unsigned index);
-    /// Render a glyph bitmap into a memory buffer. Called internally.
+    /// Render all glyphs of the face into a single texture. Return true if could fit them. Called by Font.
+    bool RenderAllGlyphs(int maxWidth, int maxHeight);
+    /// Render one glyph on demand into the current texture.
+    void RenderGlyph(unsigned index);
+    /// Render a glyph bitmap into a memory buffer.
     void RenderGlyphBitmap(unsigned index, unsigned char* dest, unsigned pitch);
     void RenderGlyphBitmap(unsigned index, unsigned char* dest, unsigned pitch);
-    /// Setup next texture for dynamic glyph rendering. Called internally.
-    void SetupNextTexture();
+    /// Setup next texture for dynamic glyph rendering.
+    void SetupNextTexture(int width, int height);
+    /// Setup mutable glyph rendering, in which glyphs form a regular-sized grid.
+    void SetupMutableGlyphs(int textureWidth, int textureHeight, int maxGlyphWidth, int maxGlyphHeight);
     
     
     /// Parent font.
     /// Parent font.
     Font* font_;
     Font* font_;
@@ -120,10 +138,16 @@ private:
     HashMap<unsigned, unsigned> glyphMapping_;
     HashMap<unsigned, unsigned> glyphMapping_;
     /// Glyph texture pages.
     /// Glyph texture pages.
     Vector<SharedPtr<Texture2D> > textures_;
     Vector<SharedPtr<Texture2D> > textures_;
+    /// Mutable glyph list.
+    List<MutableGlyph*> mutableGlyphs_;
     /// Point size.
     /// Point size.
     int pointSize_;
     int pointSize_;
     /// Row height.
     /// Row height.
     int rowHeight_;
     int rowHeight_;
+    /// Mutable glyph cell width, including 1 pixel padding.
+    int cellWidth_;
+    /// Mutable glyph cell height, including 1 pixel padding.
+    int cellHeight_;
     /// Kerning flag.
     /// Kerning flag.
     bool hasKerning_;
     bool hasKerning_;
     /// Glyph area allocator.
     /// Glyph area allocator.

+ 3 - 1
Source/Engine/UI/Text.cpp

@@ -181,7 +181,9 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
                     if (!p)
                     if (!p)
                         continue;
                         continue;
 
 
-                    pageGlyphLocations[p->page_].Push(GlyphLocation(x, y, p));
+                    // Validate page because of possible glyph unallocations (should not happen, though)
+                    if (p->page_ < textures.Size())
+                        pageGlyphLocations[p->page_].Push(GlyphLocation(x, y, p));
 
 
                     x += p->advanceX_;
                     x += p->advanceX_;
                     if (i < printText_.Size() - 1)
                     if (i < printText_.Size() - 1)

+ 14 - 0
Source/Engine/UI/UI.cpp

@@ -66,6 +66,7 @@ const ShortStringHash VAR_ORIGINAL_CHILD_INDEX("OriginalChildIndex");
 const ShortStringHash VAR_PARENT_CHANGED("ParentChanged");
 const ShortStringHash VAR_PARENT_CHANGED("ParentChanged");
 
 
 const float DEFAULT_DOUBLECLICK_INTERVAL = 0.5f;
 const float DEFAULT_DOUBLECLICK_INTERVAL = 0.5f;
+const int DEFAULT_FONT_TEXTURE_MAX_SIZE = 2048;
 
 
 const char* UI_CATEGORY = "UI";
 const char* UI_CATEGORY = "UI";
 
 
@@ -75,6 +76,7 @@ UI::UI(Context* context) :
     rootModalElement_(new UIElement(context)),
     rootModalElement_(new UIElement(context)),
     mouseButtons_(0),
     mouseButtons_(0),
     qualifiers_(0),
     qualifiers_(0),
+    maxFontTextureSize_(DEFAULT_FONT_TEXTURE_MAX_SIZE),
     doubleClickInterval_(DEFAULT_DOUBLECLICK_INTERVAL),
     doubleClickInterval_(DEFAULT_DOUBLECLICK_INTERVAL),
     initialized_(false),
     initialized_(false),
     usingTouchInput_(false),
     usingTouchInput_(false),
@@ -84,6 +86,7 @@ UI::UI(Context* context) :
     nonFocusedMouseWheel_(true),     // Default Mac OS X and Linux behaviour
     nonFocusedMouseWheel_(true),     // Default Mac OS X and Linux behaviour
     #endif
     #endif
     useSystemClipBoard_(false),
     useSystemClipBoard_(false),
+    useMutableGlyphs_(false),
     nonModalBatchSize_(0)
     nonModalBatchSize_(0)
 {
 {
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
@@ -466,6 +469,12 @@ void UI::SetDoubleClickInterval(float interval)
     doubleClickInterval_ = Max(interval, 0.0f);
     doubleClickInterval_ = Max(interval, 0.0f);
 }
 }
 
 
+void UI::SetMaxFontTextureSize(int size)
+{
+    if (IsPowerOfTwo(size) && size >= FONT_TEXTURE_MIN_SIZE)
+        maxFontTextureSize_ = size;
+}
+
 void UI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 void UI::SetNonFocusedMouseWheel(bool nonFocusedMouseWheel)
 {
 {
     nonFocusedMouseWheel_ = nonFocusedMouseWheel;
     nonFocusedMouseWheel_ = nonFocusedMouseWheel;
@@ -476,6 +485,11 @@ void UI::SetUseSystemClipBoard(bool enable)
     useSystemClipBoard_ = enable;
     useSystemClipBoard_ = enable;
 }
 }
 
 
+void UI::SetUseMutableGlyphs(bool enable)
+{
+    useMutableGlyphs_ = enable;
+}
+
 IntVector2 UI::GetCursorPosition() const
 IntVector2 UI::GetCursorPosition() const
 {
 {
     return cursor_ ? cursor_->GetPosition() : GetSubsystem<Input>()->GetMousePosition();
     return cursor_ ? cursor_->GetPosition() : GetSubsystem<Input>()->GetMousePosition();

+ 13 - 1
Source/Engine/UI/UI.h

@@ -79,11 +79,15 @@ public:
     void SetClipBoardText(const String& text);
     void SetClipBoardText(const String& text);
     /// Set UI element double click interval in seconds.
     /// Set UI element double click interval in seconds.
     void SetDoubleClickInterval(float interval);
     void SetDoubleClickInterval(float interval);
+    /// Set maximum font face texture size. Must be a power of two. Default is 2048.
+    void SetMaxFontTextureSize(int size);
     /// Set whether mouse wheel can control also a non-focused element.
     /// Set whether mouse wheel can control also a non-focused element.
     void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
     void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
     /// Set whether to use system clipboard. Default false.
     /// Set whether to use system clipboard. Default false.
     void SetUseSystemClipBoard(bool enable);
     void SetUseSystemClipBoard(bool enable);
-
+    /// Set whether to use mutable (eraseable) glyphs to ensure a font face never expands to more than one texture. Default false.
+    void SetUseMutableGlyphs(bool enable);
+    
     /// Return root UI element.
     /// Return root UI element.
     UIElement* GetRoot() const { return rootElement_; }
     UIElement* GetRoot() const { return rootElement_; }
     /// Return root modal element.
     /// Return root modal element.
@@ -104,10 +108,14 @@ public:
     const String& GetClipBoardText() const;
     const String& GetClipBoardText() const;
     /// Return UI element double click interval in seconds.
     /// Return UI element double click interval in seconds.
     float GetDoubleClickInterval() const { return doubleClickInterval_; }
     float GetDoubleClickInterval() const { return doubleClickInterval_; }
+    /// Return font texture maximum size.
+    int GetMaxFontTextureSize() const { return maxFontTextureSize_; }
     /// Return whether mouse wheel can control also a non-focused element.
     /// Return whether mouse wheel can control also a non-focused element.
     bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
     bool IsNonFocusedMouseWheel() const { return nonFocusedMouseWheel_; }
     /// Return whether is using the system clipboard.
     /// Return whether is using the system clipboard.
     bool GetUseSystemClipBoard() const { return useSystemClipBoard_; }
     bool GetUseSystemClipBoard() const { return useSystemClipBoard_; }
+    /// Return whether is using mutable (eraseable) glyphs for fonts.
+    bool GetUseMutableGlyphs() const { return useMutableGlyphs_; }
     /// Return true when UI has modal element(s).
     /// Return true when UI has modal element(s).
     bool HasModalElement() const;
     bool HasModalElement() const;
 
 
@@ -211,6 +219,8 @@ private:
     int mouseButtons_;
     int mouseButtons_;
     /// Qualifier keys held down.
     /// Qualifier keys held down.
     int qualifiers_;
     int qualifiers_;
+    /// Font texture maximum size.
+    int maxFontTextureSize_;
     /// Initialized flag.
     /// Initialized flag.
     bool initialized_;
     bool initialized_;
     /// Touch used flag.
     /// Touch used flag.
@@ -219,6 +229,8 @@ private:
     bool nonFocusedMouseWheel_;
     bool nonFocusedMouseWheel_;
     /// Flag for using operating system clipboard instead of internal.
     /// Flag for using operating system clipboard instead of internal.
     bool useSystemClipBoard_;
     bool useSystemClipBoard_;
+    /// Flag for using mutable (eraseable) font glyphs.
+    bool useMutableGlyphs_;
     /// Non-modal batch size (used internally for rendering).
     /// Non-modal batch size (used internally for rendering).
     unsigned nonModalBatchSize_;
     unsigned nonModalBatchSize_;
     /// Timer used to trigger double click.
     /// Timer used to trigger double click.

+ 10 - 4
Source/Extras/LuaScript/pkgs/UI/UI.pkg

@@ -11,29 +11,33 @@ class UI : public Object
     void Render();
     void Render();
     void DebugDraw(UIElement* element);
     void DebugDraw(UIElement* element);
     bool SaveLayout(Serializer& dest, UIElement* element);
     bool SaveLayout(Serializer& dest, UIElement* element);
-    
+
     void SetClipBoardText(const String text);
     void SetClipBoardText(const String text);
 
 
     void SetDoubleClickInterval(float interval);
     void SetDoubleClickInterval(float interval);
+    void SetMaxFontTextureSize(int size);
     void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
     void SetNonFocusedMouseWheel(bool nonFocusedMouseWheel);
     void SetUseSystemClipBoard(bool enable);
     void SetUseSystemClipBoard(bool enable);
+    void SetUseMutableGlyphs(bool enable);
 
 
     UIElement* GetRoot() const;
     UIElement* GetRoot() const;
     UIElement* GetRootModalElement() const;
     UIElement* GetRootModalElement() const;
     Cursor* GetCursor() const;
     Cursor* GetCursor() const;
-    
+
     UIElement* GetElementAt(const IntVector2& position, bool enabledOnly = true);
     UIElement* GetElementAt(const IntVector2& position, bool enabledOnly = true);
     UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
     UIElement* GetElementAt(int x, int y, bool enabledOnly = true);
-    
+
     UIElement* GetFocusElement() const;
     UIElement* GetFocusElement() const;
     UIElement* GetFrontElement() const;
     UIElement* GetFrontElement() const;
     IntVector2 GetCursorPosition() const;
     IntVector2 GetCursorPosition() const;
     const String& GetClipBoardText() const;
     const String& GetClipBoardText() const;
     float GetDoubleClickInterval() const;
     float GetDoubleClickInterval() const;
+    int GetMaxFontTextureSize() const;
     bool IsNonFocusedMouseWheel() const;
     bool IsNonFocusedMouseWheel() const;
     bool GetUseSystemClipBoard() const;
     bool GetUseSystemClipBoard() const;
+    bool GetUseMutableGlyphs() const;
     bool HasModalElement() const;
     bool HasModalElement() const;
-    
+
     tolua_readonly tolua_property__get_set UIElement* root;
     tolua_readonly tolua_property__get_set UIElement* root;
     tolua_readonly tolua_property__get_set UIElement* rootModalElement;
     tolua_readonly tolua_property__get_set UIElement* rootModalElement;
     tolua_property__get_set Cursor* cursor;
     tolua_property__get_set Cursor* cursor;
@@ -42,7 +46,9 @@ class UI : public Object
     tolua_readonly tolua_property__get_set IntVector2 cursorPosition;
     tolua_readonly tolua_property__get_set IntVector2 cursorPosition;
     tolua_property__get_set String& clipBoardText;
     tolua_property__get_set String& clipBoardText;
     tolua_property__get_set float doubleClickInterval;
     tolua_property__get_set float doubleClickInterval;
+    tolua_property__get_set int maxFontTextureSize;
     tolua_property__is_set bool nonFocusedMouseWheel;
     tolua_property__is_set bool nonFocusedMouseWheel;
     tolua_property__get_set bool useSystemClipBoard;
     tolua_property__get_set bool useSystemClipBoard;
+    tolua_property__get_set bool useMutableGlyphs;
     tolua_readonly tolua_property__has_set bool modalElement;
     tolua_readonly tolua_property__has_set bool modalElement;
 };
 };