summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-04-08 22:53:56 +0300
committerAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-04-09 20:48:34 +0300
commit5f91a3faf3ef0b9d0a6c3424fff119992dc0822a (patch)
tree31fe46c41da8a3f691cbc0e456f1b5f95f039a1a
parent130c50cf8d4de021f510b17fd02fcac88a67c5e6 (diff)
#5626 LLTextBase optimization
by caching string width
-rw-r--r--indra/llrender/llfontvertexbuffer.cpp77
-rw-r--r--indra/llrender/llfontvertexbuffer.h46
-rw-r--r--indra/llui/llscrollcontainer.cpp1
-rw-r--r--indra/llui/lltextbase.cpp29
-rw-r--r--indra/llui/lltextbase.h13
-rw-r--r--indra/newview/llexpandabletextbox.cpp2
-rw-r--r--indra/newview/llviewertexteditor.cpp12
-rw-r--r--indra/newview/pipeline.cpp8
8 files changed, 164 insertions, 24 deletions
diff --git a/indra/llrender/llfontvertexbuffer.cpp b/indra/llrender/llfontvertexbuffer.cpp
index a223509d30..2a0115265f 100644
--- a/indra/llrender/llfontvertexbuffer.cpp
+++ b/indra/llrender/llfontvertexbuffer.cpp
@@ -237,3 +237,80 @@ void LLFontVertexBuffer::renderBuffers()
gGL.popUIMatrix();
}
+// LLFontWidthBuffer
+bool LLFontWidthBuffer::sEnableBufferCollection = true;
+
+LLFontWidthBuffer::LLFontWidthBuffer()
+{
+}
+
+LLFontWidthBuffer::~LLFontWidthBuffer()
+{
+}
+
+void LLFontWidthBuffer::reset()
+{
+ mLastFont = nullptr;
+ mLastOffset = 0;
+ mLastMaxChars = 0;
+ mLastNoPadding = false;
+ mWidth = -1.f;
+ mLastScaleX = 1.f;
+ mLastScaleY = 1.f;
+ mLastVertDPI = 0.f;
+ mLastHorizDPI = 0.f;
+ mLastResGeneration = 0;
+ mLastFontCacheGen = 0;
+}
+
+F32 LLFontWidthBuffer::getWidth(
+ const LLFontGL* fontp,
+ const llwchar* wchars,
+ S32 begin_offset,
+ S32 max_chars,
+ bool no_padding)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+ if (!fontp || !wchars)
+ {
+ return 0.f;
+ }
+
+ if (!sEnableBufferCollection)
+ {
+ return fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding);
+ }
+
+ // Check if we can use cached width
+ bool needs_recalc = (mWidth < 0.f)
+ || (mLastFont != fontp)
+ || (mLastOffset != begin_offset)
+ || (mLastMaxChars != max_chars)
+ || (mLastNoPadding != no_padding)
+ || (mLastScaleX != LLFontGL::sScaleX)
+ || (mLastScaleY != LLFontGL::sScaleY)
+ || (mLastVertDPI != LLFontGL::sVertDPI)
+ || (mLastHorizDPI != LLFontGL::sHorizDPI)
+ || (mLastResGeneration != LLFontGL::sResolutionGeneration)
+ || (mLastFontCacheGen != fontp->getCacheGeneration());
+
+ if (needs_recalc)
+ {
+ // Calculate width using the font
+ mWidth = fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding);
+
+ // Cache the parameters
+ mLastFont = fontp;
+ mLastOffset = begin_offset;
+ mLastMaxChars = max_chars;
+ mLastNoPadding = no_padding;
+ mLastScaleX = LLFontGL::sScaleX;
+ mLastScaleY = LLFontGL::sScaleY;
+ mLastVertDPI = LLFontGL::sVertDPI;
+ mLastHorizDPI = LLFontGL::sHorizDPI;
+ mLastResGeneration = LLFontGL::sResolutionGeneration;
+ mLastFontCacheGen = fontp->getCacheGeneration();
+ }
+
+ return mWidth;
+}
diff --git a/indra/llrender/llfontvertexbuffer.h b/indra/llrender/llfontvertexbuffer.h
index a9e1e2337c..94b833d227 100644
--- a/indra/llrender/llfontvertexbuffer.h
+++ b/indra/llrender/llfontvertexbuffer.h
@@ -32,6 +32,11 @@
class LLVertexBufferData;
+// Rendering fonts is expensive, this class is intended to store
+// vertex buffers for rendered text, so that they can be reused.
+// LLFontVertexBuffer tracks font and rendering parameters, but
+// expects caller to track text changes and call reset() when
+// text changes.
class LLFontVertexBuffer
{
public:
@@ -127,4 +132,45 @@ private:
static bool sEnableBufferCollection;
};
+// Extracting width from a font is expensive, and due to
+// mechanics of font rendering, we need width separately
+// and usually before rendering.
+// LLFontWidthBuffer tracks font and rendering parameters,
+// but expects caller to track text changes and call reset()
+// when text changes.
+class LLFontWidthBuffer
+{
+public:
+ LLFontWidthBuffer();
+ ~LLFontWidthBuffer();
+
+ void reset();
+
+ F32 getWidth(const LLFontGL* fontp,
+ const llwchar* wchars,
+ S32 begin_offset,
+ S32 max_chars,
+ bool no_padding);
+
+ static void enableBufferCollection(bool enable) { sEnableBufferCollection = enable; }
+private:
+ const LLFontGL* mLastFont = nullptr;
+ S32 mLastOffset = 0;
+ S32 mLastMaxChars = 0;
+ bool mLastNoPadding = false;
+ F32 mWidth = -1.f;
+
+ // LLFontGL's values that affect width calculation
+ F32 mLastScaleX = 1.f;
+ F32 mLastScaleY = 1.f;
+ F32 mLastVertDPI = 0.f;
+ F32 mLastHorizDPI = 0.f;
+ S32 mLastResGeneration = 0;
+
+ // Cache generation tracking
+ S32 mLastFontCacheGen = 0;
+
+ static bool sEnableBufferCollection;
+};
+
#endif
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index df99c4f636..e36fd45bb4 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -480,6 +480,7 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height
void LLScrollContainer::draw()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0);
S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize);
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index c084b400f6..9fcf2a5f10 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -1640,7 +1640,7 @@ void LLTextBase::deselect()
bool LLTextBase::getSpellCheck() const
{
- return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
+ return (!mReadOnly) && (LLSpellChecker::getUseSpellCheck()) && (mSpellCheck);
}
const std::string& LLTextBase::getSuggestion(U32 index) const
@@ -2855,7 +2855,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round,
line_seg_iter != mSegments.end();
++line_seg_iter, line_seg_offset = 0)
{
- const LLTextSegmentPtr segmentp = *line_seg_iter;
+ LLTextSegmentPtr segmentp = *line_seg_iter;
S32 segment_line_start = segmentp->getStart() + line_seg_offset;
S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start;
@@ -2946,7 +2946,7 @@ LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const
while(line_seg_iter != mSegments.end())
{
- const LLTextSegmentPtr segmentp = *line_seg_iter;
+ LLTextSegmentPtr segmentp = *line_seg_iter;
if (line_seg_iter == cursor_seg_iter)
{
@@ -3497,8 +3497,8 @@ LLStyleSP LLTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source)
}
-bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; }
-bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const
+bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = 0; return false; }
+bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height)
{
F32 fwidth = 0;
bool result = getDimensionsF32(first_char, num_chars, fwidth, height);
@@ -3595,6 +3595,7 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
return draw_rect.mLeft;
}
@@ -3620,6 +3621,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
const LLFontGL* font = mStyle->getFont();
@@ -3851,17 +3853,19 @@ LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const
return new LLNormalTextSegment(sp, mStart, mEnd, target);
}
-bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
height = 0;
width = 0;
if (num_chars > 0 && (mStart + first_char >= 0))
{
height = mFontHeight;
- const LLWString &text = getWText();
- // if last character is a newline, then return true, forcing line break
- width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true);
+
+ const LLWString& text = getWText();
+ const LLFontGL* font = mStyle->getFont();
+ width += mFontWidthBuffer.getWidth(font, text.c_str(), mStart + first_char, num_chars, true);
}
+ // if last character is a newline, then return true, forcing line break
return false;
}
@@ -3943,6 +3947,7 @@ void LLNormalTextSegment::updateLayout(const class LLTextBase& editor)
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
void LLNormalTextSegment::dump() const
@@ -4094,7 +4099,7 @@ LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const
return nullptr;
}
-bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
if (first_char == 0 && num_chars == 0)
{
@@ -4186,7 +4191,7 @@ LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const
copy->mFontHeight = mFontHeight;
return copy;
}
-bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
width = 0;
height = mFontHeight;
@@ -4223,7 +4228,7 @@ LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const
static const S32 IMAGE_HPAD = 3;
// virtual
-bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
width = 0;
height = mStyle->getFont()->getLineHeight();
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index 21134fd7c9..9206b3facf 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -68,10 +68,10 @@ public:
virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); }
static LLStyleSP cloneStyle(LLTextBase& target, const LLStyle* source);
- bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const;
+ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height);
bool getPermitsEmoji() const { return mPermitsEmoji; };
- virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
/**
@@ -139,7 +139,7 @@ public:
virtual ~LLNormalTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
/*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
/*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
/*virtual*/ void updateLayout(const class LLTextBase& editor);
@@ -182,6 +182,7 @@ protected:
LLFontVertexBuffer mFontBufferPreSelection;
LLFontVertexBuffer mFontBufferSelection;
LLFontVertexBuffer mFontBufferPostSelection;
+ LLFontWidthBuffer mFontWidthBuffer;
S32 mLastGeneration = -1;
};
@@ -254,7 +255,7 @@ public:
~LLInlineViewSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
/*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
/*virtual*/ void updateLayout(const class LLTextBase& editor);
/*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
@@ -280,7 +281,7 @@ public:
LLLineBreakTextSegment(S32 pos);
~LLLineBreakTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
@@ -295,7 +296,7 @@ public:
~LLImageTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const;
F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp
index 5c46eb9d80..41ae47b645 100644
--- a/indra/newview/llexpandabletextbox.cpp
+++ b/indra/newview/llexpandabletextbox.cpp
@@ -52,7 +52,7 @@ public:
return copy;
}
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
// more label always spans width of text box
if (num_chars == 0)
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
index 210cd62d6f..95e34b4df1 100644
--- a/indra/newview/llviewertexteditor.cpp
+++ b/indra/newview/llviewertexteditor.cpp
@@ -189,7 +189,12 @@ public:
return new LLEmbeddedItemSegment(mStart, mImage, mItem, *editor);
}
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
+ {
+ return getSegmentDimensionsF32(first_char, num_chars, width, height);
+ }
+
+ inline bool getSegmentDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
{
if (num_chars == 0)
{
@@ -213,8 +218,9 @@ public:
}
else
{
- S32 width, height;
- getDimensions(mStart, 1, width, height);
+ F32 width;
+ S32 height;
+ getSegmentDimensionsF32(mStart, 1, width, height);
if (width > num_pixels)
{
return 0;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index c9d53bbcbc..4ef88f5deb 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -609,7 +609,9 @@ void LLPipeline::init()
{
cntrl_ptr->getCommitSignal()->connect([](LLControlVariable* control, const LLSD& value, const LLSD& previous)
{
- LLFontVertexBuffer::enableBufferCollection(control->getValue().asBoolean());
+ bool enable_buffers = control->getValue().asBoolean();
+ LLFontVertexBuffer::enableBufferCollection(enable_buffers);
+ LLFontWidthBuffer::enableBufferCollection(enable_buffers);
});
}
}
@@ -1146,7 +1148,9 @@ void LLPipeline::refreshCachedSettings()
LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors);
}
- LLFontVertexBuffer::enableBufferCollection(gSavedSettings.getBOOL("CollectFontVertexBuffers"));
+ bool enable_buffers = gSavedSettings.getBOOL("CollectFontVertexBuffers");
+ LLFontVertexBuffer::enableBufferCollection(enable_buffers);
+ LLFontWidthBuffer::enableBufferCollection(enable_buffers);
}
void LLPipeline::releaseGLBuffers()