Kelly Washington avatar Kelly Washington committed 4918b15

MAINT-1897 Poor performance viewing large group member lists
* Improve draw performance of scroll lists by only drawing what is visible
* Reduce frequency of expensive column width calculations
Fixes by Richard

Comments (0)

Files changed (7)

indra/llui/llcombobox.cpp

 	S32 min_width = getRect().getWidth();
 	S32 max_width = llmax(min_width, MAX_COMBO_WIDTH);
 	// make sure we have up to date content width metrics
-	mList->calcColumnWidths();
-	S32 list_width = llclamp(mList->getMaxContentWidth(), min_width, max_width);
+	S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width, max_width);
 
 	if (mListPosition == BELOW)
 	{

indra/llui/llscrolllistcolumn.cpp

 	if (canResize() && mResizeBar->getRect().pointInRect(x, y))
 	{
 		// reshape column to max content width
+		mColumn->mParentCtrl->calcMaxContentWidth();
 		LLRect column_rect = getRect();
 		column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
 		setShape(column_rect, true);
 
 	LLRect snap_rect = getSnapRect();
 
+	mColumn->mParentCtrl->calcMaxContentWidth();
+
 	S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();
 
 	// x coord growing means column growing, so same signs mean we're going in right direction

indra/llui/llscrolllistctrl.cpp

 	mMouseWheelOpaque(p.mouse_wheel_opaque),
 	mPageLines(p.page_lines),
 	mMaxSelectable(0),
-	mAllowKeyboardMovement(TRUE),
+	mAllowKeyboardMovement(true),
 	mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
-	mCommitOnSelectionChange(FALSE),
-	mSelectionChanged(FALSE),
-	mNeedsScroll(FALSE),
-	mCanSelect(TRUE),
-	mColumnsDirty(FALSE),
+	mCommitOnSelectionChange(false),
+	mSelectionChanged(false),
+	mNeedsScroll(false),
+	mCanSelect(true),
+	mColumnsDirty(false),
 	mMaxItemCount(INT_MAX), 
-	mMaxContentWidth(0),
 	mBorderThickness( 2 ),
 	mOnDoubleClickCallback( NULL ),
 	mOnMaximumSelectCallback( NULL ),
 	mTotalStaticColumnWidth(0),
 	mTotalColumnPadding(0),
 	mSorted(false),
-	mDirty(FALSE),
+	mDirty(false),
 	mOriginalSelection(-1),
 	mLastSelected(NULL),
 	mHeadingHeight(p.heading_height),
 	mScrollLines = 0;
 	mLastSelected = NULL;
 	updateLayout();
-	mDirty = FALSE; 
+	mDirty = false; 
 }
 
 
 // NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
 //  while receiving a long list of names.
 // *TODO: Use bookkeeping to make this an incramental cost with item additions
-void LLScrollListCtrl::calcColumnWidths()
+S32 LLScrollListCtrl::calcMaxContentWidth()
 {
 	const S32 HEADING_TEXT_PADDING = 25;
 	const S32 COLUMN_TEXT_PADDING = 10;
 
-	mMaxContentWidth = 0;
-
 	S32 max_item_width = 0;
 
 	ordered_columns_t::iterator column_itor;
 		LLScrollListColumn* column = *column_itor;
 		if (!column) continue;
 
+		if (mColumnWidthsDirty)
+		{
+			mColumnWidthsDirty = false;
+			// update max content width for this column, by looking at all items
+			column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
+			item_list::iterator iter;
+			for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+			{
+				LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
+				if (!cellp) continue;
+
+				column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+			}
+		}
+		max_item_width += column->mMaxContentWidth;
+	}
+
+	return max_item_width;
+}
+
+bool LLScrollListCtrl::updateColumnWidths()
+{
+	bool width_changed = false;
+	ordered_columns_t::iterator column_itor;
+	for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
+	{
+		LLScrollListColumn* column = *column_itor;
+		if (!column) continue;
+
 		// update column width
 		S32 new_width = column->getWidth();
 		if (column->mRelWidth >= 0)
 			new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
 		}
 
-		column->setWidth(new_width);
-
-		// update max content width for this column, by looking at all items
-		column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
-		item_list::iterator iter;
-		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+		if (column->getWidth() != new_width)
 		{
-			LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
-			if (!cellp) continue;
-
-			column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+			column->setWidth(new_width);
+			width_changed = true;
 		}
-
-		max_item_width += column->mMaxContentWidth;
 	}
-
-	mMaxContentWidth = max_item_width;
+	return width_changed;
 }
 
 const S32 SCROLL_LIST_ROW_PAD = 2;
 
 void LLScrollListCtrl::updateColumns()
 {
-	calcColumnWidths();
+	if (!mColumnsDirty)
+		return;
+
+	mColumnsDirty = false;
+
+	bool columns_changed_width = updateColumnWidths();
 
 	// update column headers
 	std::vector<LLScrollListColumn*>::iterator column_ordered_it;
 	}
 
 	// propagate column widths to individual cells
-	item_list::iterator iter;
-	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+	if (columns_changed_width)
 	{
-		LLScrollListItem *itemp = *iter;
-		S32 num_cols = itemp->getNumColumns();
-		S32 i = 0;
-		for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+		item_list::iterator iter;
+		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 		{
-			if (i >= (S32)mColumnsIndexed.size()) break;
-
-			cell->setWidth(mColumnsIndexed[i]->getWidth());
+			LLScrollListItem *itemp = *iter;
+			S32 num_cols = itemp->getNumColumns();
+			S32 i = 0;
+			for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+			{
+				if (i >= (S32)mColumnsIndexed.size()) break;
+
+				cell->setWidth(mColumnsIndexed[i]->getWidth());
+			}
 		}
 	}
-
 }
 
 void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
 		{
 			deselectItem(itemp);
 		}
-		first_item = FALSE;
+		first_item = false;
 	}
 	if (mCommitOnSelectionChange)
 	{
 
 		S32 cur_y = y;
 		
-		S32 line = 0;
 		S32 max_columns = 0;
 
 		LLColor4 highlight_color = LLColor4::white;
 		static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
 		highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
 
+		S32 first_line = mScrollLines;
+		S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage());
+
+		if (first_line >= mItemList.size())
+		{
+			return;
+		}
 		item_list::iterator iter;
-		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+		for (S32 line = first_line; line <= last_line; line++)
 		{
-			LLScrollListItem* item = *iter;
+			LLScrollListItem* item = mItemList[line];
 			
 			item_rect.setOriginAndSize( 
 				x, 
 
 				cur_y -= mLineHeight;
 			}
-			line++;
 		}
 	}
 }
 	if (mNeedsScroll)
 	{
 		scrollToShowSelected();
-		mNeedsScroll = FALSE;
+		mNeedsScroll = false;
 	}
 	LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
 	// Draw background
 		gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha );
 	}
 
-	if (mColumnsDirty)
-	{
-		updateColumns();
-		mColumnsDirty = FALSE;
-	}
+	updateColumns();
 
 	getChildView("comment_text")->setVisible(mItemList.empty());
 
 		setFocus(TRUE);
 
 		// clear selection changed flag because user is starting a selection operation
-		mSelectionChanged = FALSE;
+		mSelectionChanged = false;
 
 		handleClick(x, y, mask);
 	}
 		if(mask == MASK_NONE)
 		{
 			selectItemAt(x, y, mask);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 	}
 
 	// always commit when mouse operation is completed inside list
 	if (mItemListRect.pointInRect(x,y))
 	{
-		mDirty |= mSelectionChanged;
-		mSelectionChanged = FALSE;
+		mDirty = mDirty || mSelectionChanged;
+		mSelectionChanged = false;
 		onCommit();
 	}
 
 		{
 			selectItemAt(x, y, mask);
 			gFocusMgr.setMouseCapture(this);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 		
 		// propagate state of cell to rest of selected column
 		// treat this as a normal single item selection
 		selectItemAt(x, y, mask);
 		gFocusMgr.setMouseCapture(this);
-		mNeedsScroll = TRUE;
+		mNeedsScroll = true;
 		// do not eat click (allow double click callback)
 		return FALSE;
 	}
 		if(mask == MASK_NONE)
 		{
 			selectItemAt(x, y, mask);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 	}
 	else 
 				{
 					// commit implicit in call
 					selectPrevItem(FALSE);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					handled = TRUE;
 				}
 				break;
 				{
 					// commit implicit in call
 					selectNextItem(FALSE);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					handled = TRUE;
 				}
 				break;
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectFirstItem();
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getItemCount() - 1);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
 				}
 				else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
 				{
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					// update search string only on successful match
 					mSearchTimer.reset();
 
 	if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
 	{
 		// update search string only on successful match
-		mNeedsScroll = TRUE;
+		mNeedsScroll = true;
 		mSearchString += uni_char;
 		mSearchTimer.reset();
 
 				if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
 				{
 					selectItem(item);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					cellp->highlightText(0, 1);
 					mSearchTimer.reset();
 
 		}
 		itemp->setSelected(TRUE);
 		mLastSelected = itemp;
-		mSelectionChanged = TRUE;
+		mSelectionChanged = true;
 	}
 }
 
 		{
 			cellp->highlightText(0, 0);	
 		}
-		mSelectionChanged = TRUE;
+		mSelectionChanged = true;
 	}
 }
 
 {
 	if (mSelectionChanged)
 	{
-		mDirty = TRUE;
+		mDirty = true;
 		mSelectionChanged = FALSE;
 		onCommit();
 	}
 
 void LLScrollListCtrl::dirtyColumns() 
 { 
-	mColumnsDirty = TRUE; 
+	mColumnsDirty = true; 
+	mColumnWidthsDirty = true;
 
 	// need to keep mColumnsIndexed up to date
 	// just in case someone indexes into it immediately
 void LLScrollListCtrl::onFocusReceived()
 {
 	// forget latent selection changes when getting focus
-	mSelectionChanged = FALSE;
+	mSelectionChanged = false;
 	LLUICtrl::onFocusReceived();
 }
 

indra/llui/llscrolllistctrl.h

 	static void onClickColumn(void *userdata);
 
 	virtual void updateColumns();
-	void calcColumnWidths();
-	S32 getMaxContentWidth() { return mMaxContentWidth; }
+	S32 calcMaxContentWidth();
+	bool updateColumnWidths();
 
 	void setHeadingHeight(S32 heading_height);
 	/**
 	S32				mHeadingHeight;	// the height of the column header buttons, if visible
 	U32				mMaxSelectable; 
 	LLScrollbar*	mScrollbar;
-	BOOL 			mAllowMultipleSelection;
-	BOOL			mAllowKeyboardMovement;
-	BOOL			mCommitOnKeyboardMovement;
-	BOOL			mCommitOnSelectionChange;
-	BOOL			mSelectionChanged;
-	BOOL			mNeedsScroll;
-	BOOL			mMouseWheelOpaque;
-	BOOL			mCanSelect;
-	const BOOL		mDisplayColumnHeaders;
-	BOOL			mColumnsDirty;
+	bool 			mAllowMultipleSelection;
+	bool			mAllowKeyboardMovement;
+	bool			mCommitOnKeyboardMovement;
+	bool			mCommitOnSelectionChange;
+	bool			mSelectionChanged;
+	bool			mNeedsScroll;
+	bool			mMouseWheelOpaque;
+	bool			mCanSelect;
+	bool			mDisplayColumnHeaders;
+	bool			mColumnsDirty;
+	bool			mColumnWidthsDirty;
 
 	mutable item_list	mItemList;
 
 	S32				mMaxItemCount; 
 
 	LLRect			mItemListRect;
-	S32				mMaxContentWidth;
 	S32             mColumnPadding;
 
 	BOOL			mBackgroundVisible;
 	typedef std::map<std::string, LLScrollListColumn*> column_map_t;
 	column_map_t mColumns;
 
-	BOOL			mDirty;
+	bool			mDirty;
 	S32				mOriginalSelection;
 
 	ContextMenuType mContextMenuType;

indra/llui/llview.cpp

 
 void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const
 {
-	*screen_x = local_x + getRect().mLeft;
-	*screen_y = local_y + getRect().mBottom;
+	*screen_x = local_x;
+	*screen_y = local_y;
 
 	const LLView* cur = this;
-	while( cur->mParentView )
+	do
 	{
+		LLRect cur_rect = cur->getRect();
+		*screen_x += cur_rect.mLeft;
+		*screen_y += cur_rect.mBottom;
 		cur = cur->mParentView;
-		*screen_x += cur->getRect().mLeft;
-		*screen_y += cur->getRect().mBottom;
 	}
+	while( cur );
 }
 
 void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const

indra/newview/llnamelistctrl.cpp

 {}
 
 // public
-void LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos,
+LLScrollListItem* LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos,
 								 BOOL enabled, const std::string& suffix)
 {
 	//llinfos << "LLNameListCtrl::addNameItem " << agent_id << llendl;
 	item.enabled = enabled;
 	item.target = INDIVIDUAL;
 
-	addNameItemRow(item, pos, suffix);
+	return addNameItemRow(item, pos, suffix);
 }
 
 // virtual, public
 	addNameItemRow(item, pos);
 }
 
-void LLNameListCtrl::addNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos)
+LLScrollListItem* LLNameListCtrl::addNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos)
 {
 	item.target = INDIVIDUAL;
-	addNameItemRow(item, pos);
+	return addNameItemRow(item, pos);
 }
 
 LLScrollListItem* LLNameListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata)

indra/newview/llnamelistctrl.h

 public:
 	// Add a user to the list by name.  It will be added, the name
 	// requested from the cache, and updated as necessary.
-	void addNameItem(const LLUUID& agent_id, EAddPosition pos = ADD_BOTTOM,
+	LLScrollListItem* addNameItem(const LLUUID& agent_id, EAddPosition pos = ADD_BOTTOM,
 					 BOOL enabled = TRUE, const std::string& suffix = LLStringUtil::null);
-	void addNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM);
+	LLScrollListItem* addNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM);
 
 	/*virtual*/ LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL);
 	LLScrollListItem* addNameItemRow(const NameItem& value, EAddPosition pos = ADD_BOTTOM, const std::string& suffix = LLStringUtil::null);
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.