Browse Source

Properly calculating the GUI update parent to avoid updating the entire widget when one element changes

Marko Pintera 10 years ago
parent
commit
3ce3fe428e

+ 29 - 14
BansheeEngine/Source/BsGUIElementBase.cpp

@@ -357,8 +357,21 @@ namespace BansheeEngine
 	{
 		GUIElementBase* updateParent = nullptr;
 		if (mParentElement != nullptr)
+		{
 			updateParent = mParentElement->findUpdateParent();
 
+			// If parent is a panel then we can do an optimization and only update
+			// one child instead of all of them, so change parent to that child.
+			if (updateParent != nullptr && updateParent->_getType() == GUIElementBase::Type::Panel)
+			{
+				GUIElementBase* optimizedUpdateParent = this;
+				while (optimizedUpdateParent->_getParent() != updateParent)
+					optimizedUpdateParent = optimizedUpdateParent->_getParent();
+
+				updateParent = optimizedUpdateParent;
+			}
+		}
+
 		GUIPanel* anchorParent = nullptr;
 		GUIElementBase* currentParent = mParentElement;
 		while (currentParent != nullptr)
@@ -385,20 +398,7 @@ namespace BansheeEngine
 			bool boundsDependOnChildren = !parentDimensions.fixedHeight() || !parentDimensions.fixedWidth();
 
 			if (!boundsDependOnChildren)
-			{
-				// If parent is a panel then we can do an optimization and only update
-				// one child instead of all of them, so change parent to that child.
-				if (currentElement->_getType() == GUIElementBase::Type::Panel)
-				{
-					GUIElementBase* optimizedUpdateParent = this;
-					while (optimizedUpdateParent != currentElement)
-						optimizedUpdateParent = optimizedUpdateParent->_getParent();
-
-					currentElement = optimizedUpdateParent;
-				}
-
 				return currentElement;
-			}
 
 			currentElement = currentElement->mParentElement;
 		}
@@ -411,7 +411,22 @@ namespace BansheeEngine
 		GUIElementBase* updateParent = findUpdateParent();
 
 		for (auto& child : mChildren)
-			child->setUpdateParent(updateParent);
+		{
+			GUIElementBase* childUpdateParent = updateParent;
+
+			// If parent is a panel then we can do an optimization and only update
+			// one child instead of all of them, so change parent to that child.
+			if (childUpdateParent != nullptr && childUpdateParent->_getType() == GUIElementBase::Type::Panel)
+			{
+				GUIElementBase* optimizedUpdateParent = child;
+				while (optimizedUpdateParent->_getParent() != childUpdateParent)
+					optimizedUpdateParent = optimizedUpdateParent->_getParent();
+
+				childUpdateParent = optimizedUpdateParent;
+			}
+
+			child->setUpdateParent(childUpdateParent);
+		}
 	}
 
 	void GUIElementBase::setAnchorParent(GUIPanel* anchorParent)

+ 1 - 5
BansheeEngine/Source/BsGUILayoutUtility.cpp

@@ -35,12 +35,9 @@ namespace BansheeEngine
 		parentArea.width = width;
 		parentArea.height = height;
 
-		gProfilerCPU().beginSample("actualSizeB");
 		layout->_getElementAreas(parentArea, elementAreas, numElements, layout->_getCachedChildSizeRanges(), layout->_getCachedSizeRange());
-		gProfilerCPU().endSample("actualSizeB");
-		Rect2I* actualAreas = elementAreas; // We re-use the same array
 
-		gProfilerCPU().beginSample("actualSizeC");
+		Rect2I* actualAreas = elementAreas; // We re-use the same array
 		for (UINT32 i = 0; i < numElements; i++)
 		{
 			GUIElementBase* child = layout->_getChild(i);
@@ -71,7 +68,6 @@ namespace BansheeEngine
 		if (elementAreas != nullptr)
 			bs_stack_free(elementAreas);
 
-		gProfilerCPU().endSample("actualSizeC");
 		return actualSize;
 	}
 }

+ 0 - 8
BansheeEngine/Source/BsGUILayoutX.cpp

@@ -47,8 +47,6 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 
-		gProfilerCPU().beginSample("OptX");
-
 		if(mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 
@@ -82,15 +80,11 @@ namespace BansheeEngine
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
-
-		gProfilerCPU().endSample("OptX");
 	}
 
 	void GUILayoutX::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
-		gProfilerCPU().beginSample("areasX");
-
 		assert(mChildren.size() == numElements);
 
 		UINT32 totalOptimalSize = mySizeRange.optimal.x;
@@ -380,8 +374,6 @@ namespace BansheeEngine
 
 		if (processedElements != nullptr)
 			bs_stack_free(processedElements);
-
-		gProfilerCPU().endSample("areasX");
 	}
 
 	void GUILayoutX::_updateLayoutInternal(const GUILayoutData& data)

+ 0 - 8
BansheeEngine/Source/BsGUILayoutY.cpp

@@ -47,8 +47,6 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 
-		gProfilerCPU().beginSample("OptY");
-
 		if(mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 
@@ -82,15 +80,11 @@ namespace BansheeEngine
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
-
-		gProfilerCPU().endSample("OptY");
 	}
 
 	void GUILayoutY::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
-		gProfilerCPU().beginSample("areasY");
-
 		assert(mChildren.size() == numElements);
 
 		UINT32 totalOptimalSize = mySizeRange.optimal.y;
@@ -378,8 +372,6 @@ namespace BansheeEngine
 			yOffset += elemHeight + child->_getPadding().bottom;
 			childIdx++;
 		}
-
-		gProfilerCPU().endSample("areasY");
 	}
 
 	void GUILayoutY::_updateLayoutInternal(const GUILayoutData& data)

+ 11 - 0
BansheeEngine/Source/BsGUIManager.cpp

@@ -357,6 +357,8 @@ namespace BansheeEngine
 			bool isDirty = renderData.isDirty;
 			renderData.isDirty = false;
 
+			gProfilerCPU().beginSample("updateRenderElements");
+
 			for(auto& widget : renderData.widgets)
 			{
 				if (widget->isDirty(true))
@@ -365,6 +367,8 @@ namespace BansheeEngine
 				}
 			}
 
+			gProfilerCPU().endSample("updateRenderElements");
+
 			if(!isDirty)
 				continue;
 
@@ -383,6 +387,8 @@ namespace BansheeEngine
 						(aDepth == bDepth && a.element == b.element && a.renderElement > b.renderElement); 
 				};
 
+				gProfilerCPU().beginSample("generateMatGroups");
+
 				FrameSet<GUIGroupElement, std::function<bool(const GUIGroupElement&, const GUIGroupElement&)>> allElements(elemComp);
 
 				for (auto& widget : renderData.widgets)
@@ -522,6 +528,9 @@ namespace BansheeEngine
 					}
 				}
 
+				gProfilerCPU().endSample("generateMatGroups");
+				gProfilerCPU().beginSample("updateMeshes");
+
 				UINT32 numMeshes = (UINT32)sortedGroups.size();
 				UINT32 oldNumMeshes = (UINT32)renderData.cachedMeshes.size();
 
@@ -591,6 +600,8 @@ namespace BansheeEngine
 
 					groupIdx++;
 				}
+
+				gProfilerCPU().endSample("updateMeshes");
 			}
 
 			bs_frame_clear();			

+ 0 - 7
BansheeEngine/Source/BsGUIPanel.cpp

@@ -67,7 +67,6 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 
-		gProfilerCPU().beginSample("OptP");
 		if (mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 
@@ -93,8 +92,6 @@ namespace BansheeEngine
 		}
 
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
-
-		gProfilerCPU().endSample("OptP");
 	}
 
 	void GUIPanel::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
@@ -114,8 +111,6 @@ namespace BansheeEngine
 
 	Rect2I GUIPanel::_getElementArea(const Rect2I& layoutArea, const GUIElementBase* element, const LayoutSizeRange& sizeRange) const
 	{
-		gProfilerCPU().beginSample("areasP");
-
 		const GUIDimensions& dimensions = element->_getDimensions();
 
 		Rect2I area;
@@ -163,8 +158,6 @@ namespace BansheeEngine
 			area.height = modifiedHeight;
 		}
 
-		gProfilerCPU().endSample("areasP");
-
 		return area;
 	}
 

+ 0 - 12
BansheeEngine/Source/BsGUIScrollArea.cpp

@@ -89,8 +89,6 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 
-		gProfilerCPU().beginSample("OptS");
-
 		if (mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 
@@ -102,8 +100,6 @@ namespace BansheeEngine
 		}
 
 		mSizeRange = mContentLayout->_getLayoutSizeRange();
-
-		gProfilerCPU().endSample("OptS");
 	}
 
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
@@ -116,8 +112,6 @@ namespace BansheeEngine
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, Vector2I& visibleSize, Vector2I& contentSize) const
 	{
-		gProfilerCPU().beginSample("areasS");
-
 		assert(mChildren.size() == numElements && numElements == 3);
 
 		UINT32 layoutIdx = 0;
@@ -242,14 +236,10 @@ namespace BansheeEngine
 		{
 			elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + layoutHeight, 0, 0);
 		}
-
-		gProfilerCPU().endSample("areasS");
 	}
 
 	void GUIScrollArea::_updateLayoutInternal(const GUILayoutData& data)
 	{
-		gProfilerCPU().beginSample("layoutS");
-
 		UINT32 numElements = (UINT32)mChildren.size();
 		Rect2I* elementAreas = nullptr;
 
@@ -343,8 +333,6 @@ namespace BansheeEngine
 
 		if (elementAreas != nullptr)
 			bs_stack_free(elementAreas);
-
-		gProfilerCPU().endSample("layoutS");
 	}
 
 	void GUIScrollArea::vertScrollUpdate(float scrollPos)

+ 2 - 6
TODO.txt

@@ -93,21 +93,17 @@ Moving the mouse over inspector seems to queue a rebuild for the entire GUIWidge
 		return nullptr;
 	}
 
-ScrollArea doesn't cache its own size range. This means whenever layouts (or other scroll areas) are performing optimal size updates I'm doing 
-another whole pass on all of the scroll area's children. While I'm modifying this I should modify how layouts determine whether to retrieve cached 
-size range or calculate it (right now I'm testing for exact type, but I need a more generic solution since ScrollArea doesn't qualify as its own type)
-
 Avoid fully recalculating layout for common operations like changing button states/toggles, and modifying input boxes.
  - Layout should be fully recalculated only if:
    - optimal size changes
    - padding changes
    - dimensions change (x/y position only relevant for for panels) (perhaps even only LayoutSizeRange)
+    - if its fixed height/width then even that might not matter
    - disabled state changes
    - when initially created
  - I should probably just handle this on a case by case basis (e.g. if changing the active button texture is the same size as previous one don't call contentDirty)
+ - ADDITIONALLY consider marking the mesh not dirty after layout update if size didn't change
 
-
-Test scroll areas after optimization pass
 Get rid of all the profiling calls
 
 Test inspector selection, selecting a resource and adding/removing component updates