Explorar el Código

Simplify and optimize Text code. If text does not change, do not look up glyphs & kerning each frame. Fix display of wordwrapped right-aligned texts.

Lasse Öörni hace 12 años
padre
commit
0c76216202
Se han modificado 4 ficheros con 130 adiciones y 168 borrados
  1. 114 164
      Source/Engine/UI/Text.cpp
  2. 12 4
      Source/Engine/UI/Text.h
  3. 2 0
      Source/Engine/UI/UIElement.cpp
  4. 2 0
      Source/Engine/UI/UIElement.h

+ 114 - 164
Source/Engine/UI/Text.cpp

@@ -53,6 +53,7 @@ Text::Text(Context* context) :
     textAlignment_(HA_LEFT),
     rowSpacing_(1.0f),
     wordWrap_(false),
+    charPositionsDirty_(true),
     selectionStart_(0),
     selectionLength_(0),
     selectionColor_(Color::TRANSPARENT),
@@ -160,119 +161,42 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
         FontFace* face = font_->GetFace(fontSize_);
         if (!face)
             return;
-
-        const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
+        if (charPositionsDirty_ || !fontFace_ || face != fontFace_)
+            UpdateCharPositions();
         
-        if (textures.Size() > 1)
-        {
-            // Only traversing thru the printText once regardless of number of textures/pages in the font
-            pageGlyphLocations_.Resize(textures.Size());
-            
-            unsigned rowIndex = 0;
-            int x = GetRowStartPosition(rowIndex);
-            int y = 0;
-            const FontGlyph* p = 0;
-            unsigned last = 0;
-
-            for (unsigned i = 0; i < printText_.Size(); ++i)
-            {
-                unsigned c = printText_[i];
-
-                if (c != '\n')
-                {
-                    // Optimize if same glyph requested several times in a row
-                    if (!p || c != last)
-                    {
-                        p = face->GetGlyph(c);
-                        last = c;
-                    }
-
-                    if (!p)
-                        continue;
-
-                    // 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_;
-                    if (i < printText_.Size() - 1)
-                        x += face->GetKerning(c, printText_[i + 1]);
-                }
-                else
-                {
-                    x = GetRowStartPosition(++rowIndex);
-                    y += rowHeight_;
-                }
-            }
-
-            for (unsigned n = 0; n < textures.Size(); ++n)
-            {
-                // One batch per texture/page
-                UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
-
-                const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
-
-                switch (textEffect_)
-                {
-                case TE_NONE:
-                    ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                    break;
-                    
-                case TE_SHADOW:
-                    ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                    break;
-                    
-                case TE_STROKE:
-                    ConstructBatch(pageBatch, pageGlyphLocation, -1, -1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 0, -1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 1, -1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, -1, 0, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 1, 0, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, -1, 1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 0, 1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
-                    ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
-                    break;
-                }
-
-                batches.Push(pageBatch);
-            }
-        }
-        else
+        const Vector<SharedPtr<Texture2D> >& textures = face->GetTextures();
+        for (unsigned n = 0; n < textures.Size() && n < pageGlyphLocations_.Size(); ++n)
         {
-            // If only one texture page, construct the UI batch directly
-            int x = GetRowStartPosition(0);
-            int y = 0;
+            // One batch per texture/page
+            UIBatch pageBatch(this, BLEND_ALPHA, currentScissor, textures[n], &vertexData);
 
-            UIBatch batch(this, BLEND_ALPHA, currentScissor, textures[0], &vertexData);
+            const PODVector<GlyphLocation>& pageGlyphLocation = pageGlyphLocations_[n];
 
             switch (textEffect_)
             {
             case TE_NONE:
-                ConstructBatch(batch, face, x, y);
+                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
                 break;
                 
             case TE_SHADOW:
-                ConstructBatch(batch, face, x, y, 1, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y);
+                ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
                 break;
                 
             case TE_STROKE:
-                ConstructBatch(batch, face, x, y, -1, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, 0, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, 1, -1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, -1, 0, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, 1, 0, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, -1, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, 0, 1, &effectColor_, effectDepthBias_);
-                ConstructBatch(batch, face, x, y, 1, 1, &effectColor_, effectDepthBias_);
-                
-                ConstructBatch(batch, face, x, y);
+                ConstructBatch(pageBatch, pageGlyphLocation, -1, -1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 0, -1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 1, -1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, -1, 0, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 1, 0, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, -1, 1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 0, 1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+                ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
                 break;
             }
 
-            UIBatch::AddOrMerge(batch, batches);
+            UIBatch::AddOrMerge(pageBatch, batches);
         }
     }
 
@@ -284,6 +208,13 @@ void Text::OnResize()
 {
     if (wordWrap_)
         UpdateText();
+    else
+        charPositionsDirty_ = true;
+}
+
+void Text::OnIndentSet()
+{
+    charPositionsDirty_ = true;
 }
 
 bool Text::SetFont(const String& fontName, int size)
@@ -328,7 +259,7 @@ void Text::SetTextAlignment(HorizontalAlignment align)
     if (align != textAlignment_)
     {
         textAlignment_ = align;
-        UpdateText();
+        charPositionsDirty_ = true;
     }
 }
 
@@ -388,6 +319,20 @@ void Text::SetEffectDepthBias(float bias)
     effectDepthBias_ = bias;
 }
 
+const PODVector<IntVector2>& Text::GetCharPositions()
+{
+    if (charPositionsDirty_)
+        UpdateCharPositions();
+    return charPositions_;
+}
+
+const PODVector<IntVector2>& Text::GetCharSizes()
+{
+    if (charPositionsDirty_)
+        UpdateCharPositions();
+    return charSizes_;
+}
+
 void Text::SetFontAttr(ResourceRef value)
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
@@ -419,14 +364,9 @@ bool Text::FilterImplicitAttributes(XMLElement& dest) const
 
 void Text::UpdateText()
 {
-    int width = 0;
-    int height = 0;
-
     rowWidths_.Clear();
     printText_.Clear();
-
-    PODVector<unsigned> printToText;
-
+    
     if (font_)
     {
         FontFace* face = font_->GetFace(fontSize_);
@@ -434,6 +374,9 @@ void Text::UpdateText()
             return;
 
         rowHeight_ = face->GetRowHeight();
+        
+        int width = 0;
+        int height = 0;
         int rowWidth = 0;
         int rowHeight = (int)(rowSpacing_ * rowHeight_);
 
@@ -441,15 +384,17 @@ void Text::UpdateText()
         if (!wordWrap_)
         {
             printText_ = unicodeText_;
-            printToText.Resize(printText_.Size());
+            printToText_.Resize(printText_.Size());
             for (unsigned i = 0; i < printText_.Size(); ++i)
-                printToText[i] = i;
+                printToText_[i] = i;
         }
         else
         {
             int maxWidth = GetWidth();
             unsigned nextBreak = 0;
             unsigned lineStart = 0;
+            printToText_.Clear();
+            
             for (unsigned i = 0; i < unicodeText_.Size(); ++i)
             {
                 unsigned j;
@@ -498,12 +443,18 @@ void Text::UpdateText()
                             while (i < j)
                             {
                                 printText_.Push(unicodeText_[i]);
-                                printToText.Push(i);
+                                printToText_.Push(i);
                                 ++i;
                             }
                         }
+                        // Eliminate spaces that have been copied before the forced break
+                        while (printText_.Size() && printText_.Back() == ' ')
+                        {
+                            printText_.Pop();
+                            printToText_.Pop();
+                        }
                         printText_.Push('\n');
-                        printToText.Push(Min((int)i, (int)unicodeText_.Size() - 1));
+                        printToText_.Push(Min((int)i, (int)unicodeText_.Size() - 1));
                         rowWidth = 0;
                         nextBreak = lineStart = i;
                     }
@@ -522,14 +473,14 @@ void Text::UpdateText()
                         if (rowWidth <= maxWidth)
                         {
                             printText_.Push(c);
-                            printToText.Push(i);
+                            printToText_.Push(i);
                         }
                     }
                 }
                 else
                 {
                     printText_.Push('\n');
-                    printToText.Push(Min((int)i, (int)unicodeText_.Size() - 1));
+                    printToText_.Push(Min((int)i, (int)unicodeText_.Size() - 1));
                     rowWidth = 0;
                     nextBreak = lineStart = i;
                 }
@@ -568,27 +519,70 @@ void Text::UpdateText()
             rowWidths_.Push(rowWidth);
         }
 
-        // Set row height even if text is empty
+        // Set at least one row height even if text is empty
         if (!height)
             height = rowHeight;
 
-        // Store position & size of each character
+        // Set minimum and current size according to the text size, but respect fixed width if set
+        if (!IsFixedWidth())
+        {
+            SetMinWidth(wordWrap_ ? 0 : width);
+            SetWidth(width);
+        }
+        SetFixedHeight(height);
+        
+        charPositionsDirty_ = true;
+    }
+    else
+    {
+        // No font, nothing to render
+        pageGlyphLocations_.Clear();
+    }
+}
+
+void Text::UpdateCharPositions()
+{
+    if (font_)
+    {
+        // Remember the font face to see if it's still valid when it's time to render
+        FontFace* face = font_->GetFace(fontSize_);
+        if (!face)
+            return;
+        fontFace_ = face;
+        
+        int rowHeight = (int)(rowSpacing_ * rowHeight_);
+        
+        // Store position & size of each character, and locations per texture page
         charPositions_.Resize(unicodeText_.Size() + 1);
         charSizes_.Resize(unicodeText_.Size());
+        pageGlyphLocations_.Resize(face->GetTextures().Size());
+        for (unsigned i = 0; i < pageGlyphLocations_.Size(); ++i)
+            pageGlyphLocations_[i].Clear();
 
         unsigned rowIndex = 0;
+        unsigned lastFilled = 0;
         int x = GetRowStartPosition(rowIndex);
         int y = 0;
         for (unsigned i = 0; i < printText_.Size(); ++i)
         {
-            charPositions_[printToText[i]] = IntVector2(x, y);
+            IntVector2 pos(x, y);
+            
+            // Fill gaps in case characters were skipped from printing
+            for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
+                charPositions_[j] = pos;
+            
             unsigned c = printText_[i];
             if (c != '\n')
             {
                 const FontGlyph* glyph = face->GetGlyph(c);
-                charSizes_[printToText[i]] = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+                IntVector2 size(glyph ? glyph->advanceX_ : 0, rowHeight_);
+                for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
+                    charSizes_[j] = size;
                 if (glyph)
                 {
+                    // Store glyph's location for rendering. Verify that glyph page is valid
+                    if (glyph->page_ < pageGlyphLocations_.Size())
+                        pageGlyphLocations_[glyph->page_].Push(GlyphLocation(x, y, glyph));
                     x += glyph->advanceX_;
                     if (i < printText_.Size() - 1)
                         x += face->GetKerning(c, printText_[i + 1]);
@@ -596,22 +590,19 @@ void Text::UpdateText()
             }
             else
             {
-                charSizes_[printToText[i]] = IntVector2::ZERO;
+                for (unsigned j = lastFilled; j <= printToText_[i]; ++j)
+                    charSizes_[j] = IntVector2::ZERO;
                 x = GetRowStartPosition(++rowIndex);
                 y += rowHeight;
             }
+            
+            lastFilled = printToText_[i] + 1;
         }
         // Store the ending position
         charPositions_[unicodeText_.Size()] = IntVector2(x, y);
+        
+        charPositionsDirty_ = false;
     }
-
-    // Set minimum and current size according to the text size, but respect fixed width if set
-    if (!IsFixedWidth())
-    {
-        SetMinWidth(wordWrap_ ? 0 : width);
-        SetWidth(width);
-    }
-    SetFixedHeight(height);
 }
 
 void Text::ValidateSelection()
@@ -682,45 +673,4 @@ void Text::ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pa
     }
 }
 
-void Text::ConstructBatch(UIBatch& batch, FontFace* face, int x, int y, int dx, int dy, Color* color, float depthBias)
-{
-    unsigned rowIndex = 0;
-    unsigned startDataSize = batch.vertexData_->Size();
-    
-    if (!color)
-        batch.SetDefaultColor();
-    else
-        batch.SetColor(*color);
-
-    for (unsigned i = 0; i < printText_.Size(); ++i)
-    {
-        unsigned c = printText_[i];
-
-        if (c != '\n')
-        {
-            const FontGlyph* p = face->GetGlyph(c);
-            if (!p)
-                continue;
-
-            batch.AddQuad(dx + x + p->offsetX_, dy + y + p->offsetY_, p->width_, p->height_, p->x_, p->y_);
-
-            x += p->advanceX_;
-            if (i < printText_.Size() - 1)
-                x += face->GetKerning(c, printText_[i + 1]);
-        }
-        else
-        {
-            x = GetRowStartPosition(++rowIndex);
-            y += rowHeight_;
-        }
-    }
-    
-    if (depthBias != 0.0f)
-    {
-        unsigned dataSize = batch.vertexData_->Size();
-        for (unsigned i = startDataSize; i < dataSize; i += UI_VERTEX_SIZE)
-            batch.vertexData_->At(i + 2) += depthBias;
-    }
-}
-
 }

+ 12 - 4
Source/Engine/UI/Text.h

@@ -81,6 +81,8 @@ public:
     virtual void GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
     /// React to resize.
     virtual void OnResize();
+    /// React to indent change.
+    virtual void OnIndentSet();
 
     /// Set font and font size.
     bool SetFont(const String& fontName, int size = DEFAULT_FONT_SIZE);
@@ -138,9 +140,9 @@ public:
     /// Return width of each row.
     const PODVector<int>& GetRowWidths() const { return rowWidths_; }
     /// Return position of each character.
-    const PODVector<IntVector2>& GetCharPositions() const { return charPositions_; }
+    const PODVector<IntVector2>& GetCharPositions();
     /// Return size of each character.
-    const PODVector<IntVector2>& GetCharSizes() const { return charSizes_; }
+    const PODVector<IntVector2>& GetCharSizes();
 
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     void SetEffectDepthBias(float bias);
@@ -156,17 +158,19 @@ protected:
     virtual bool FilterImplicitAttributes(XMLElement& dest) const;
     /// Update text when text, font or spacing changed.
     void UpdateText();
+    /// Update character positions.
+    void UpdateCharPositions();
     /// Validate text selection to be within the text.
     void ValidateSelection();
     /// Return row start X position.
     int GetRowStartPosition(unsigned rowIndex) const;
     /// Contruct batch.
     void ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, int dx = 0, int dy = 0, Color* color = 0, float depthBias = 0.0f);
-    /// Contruct batch.
-    void ConstructBatch(UIBatch& batch, FontFace* face, int x, int y, int dx = 0, int dy = 0, Color* color = 0, float depthBias = 0.0f);
 
     /// Font.
     SharedPtr<Font> font_;
+    /// Current face.
+    WeakPtr<FontFace> fontFace_;
     /// Font size.
     int fontSize_;
     /// UTF-8 encoded text.
@@ -175,12 +179,16 @@ protected:
     PODVector<unsigned> unicodeText_;
     /// Text modified into printed form.
     PODVector<unsigned> printText_;
+    /// Mapping of printed form back to original char indices.
+    PODVector<unsigned> printToText_;
     /// Row alignment.
     HorizontalAlignment textAlignment_;
     /// Row spacing.
     float rowSpacing_;
     /// Wordwrap mode.
     bool wordWrap_;
+    /// Char positions dirty flag.
+    bool charPositionsDirty_;
     /// Selection start.
     unsigned selectionStart_;
     /// Selection length.

+ 2 - 0
Source/Engine/UI/UIElement.cpp

@@ -923,6 +923,7 @@ void UIElement::SetIndent(int indent)
     if (parent_)
         parent_->UpdateLayout();
     UpdateLayout();
+    OnIndentSet();
 }
 
 void UIElement::SetIndentSpacing(int indentSpacing)
@@ -931,6 +932,7 @@ void UIElement::SetIndentSpacing(int indentSpacing)
     if (parent_)
         parent_->UpdateLayout();
     UpdateLayout();
+    OnIndentSet();
 }
 
 void UIElement::UpdateLayout()

+ 2 - 0
Source/Engine/UI/UIElement.h

@@ -175,6 +175,8 @@ public:
     virtual void OnPositionSet() {}
     /// React to editable status change.
     virtual void OnSetEditable() {}
+    /// React to indent change.
+    virtual void OnIndentSet() {}
 
     /// Load from an XML file. Return true if successful.
     bool LoadXML(Deserializer& source);