UE4 Slate基本绘制流程

UE4 Slate基本绘制流程


1 概述


虚幻Slate基本绘制流程主要包含两步:

  • 预处理(SlatePrepass):预计算Slate绘制所需的信息。

  • 绘制(Paint):为渲染层填充绘制数据。


2 Slate预处理


2.1 缓存预计大小(CacheDesiredSize):

在SlatePrepass中至底向上预计算Widget的大小,官方示意图:

.    |<-----------22---------->|
.    +-------------------------+     
.    |     Horizontal Box      |
.    +-------------------------+     
.      +----=----+ +----=----+       
.      |    ?    | |    ?    |       
.      +----+----+ +----+----+       
.           ^           ^            
.           |           |            
.        +--+           +--+
.        |                 |         
.   +----+---------+  +----+--+    
.   |STextBlock    |  |SImage |
.   +---------+----+  +-------+  
.   |<---- 14----->|  |<--8-->|

关键代码块:

//UE4.18:
if ( bCanHaveChildren )
{
	// Cache child desired sizes first. This widget's desired size is
	FChildren* MyChildren = this->GetChildren();
	const int32 NumChildren = MyChildren->Num();
	for ( int32 ChildIndex=0; ChildIndex < NumChildren; ++ChildIndex )
	{
		const TSharedRef<SWidget>& ChildRef = MyChildren->GetChildAt(ChildIndex);
		SWidget* Child = &ChildRef.Get();
		if (Child->Visibility.Get() != EVisibility::Collapsed)
		{
			const float ChildLayoutScaleMultiplier = GetRelativeLayoutScale(MyChildren->GetSlotAt(ChildIndex), LayoutScaleMultiplier);
			// Recur: Descend down the widget tree.
			Child->SlatePrepass(LayoutScaleMultiplier * ChildLayoutScaleMultiplier);
		}
	}
}
#if SLATE_DEFERRED_DESIRED_SIZE
	// Invalidate this widget's desired size.
	InvalidateDesiredSize(LayoutScaleMultiplier);
#else
{
    // SCOPE_CYCLE_COUNTER(STAT_SlatePrepass_Cache);
    // Cache this widget's desired size.
    CacheDesiredSize(LayoutScaleMultiplier);
}
#endif

各类型Widget派生ComputeDesiredSize方法来计算预期大小,如:

FVector2D SImage::ComputeDesiredSize( float ) const
{
	const FSlateBrush* ImageBrush = Image.Get();
	if (ImageBrush != nullptr)
	{
		return ImageBrush->ImageSize;
	}
	return FVector2D::ZeroVector;
}

2.2 重新规划大小(ArrangeChildren)


在OnPaint时,根据父Widget的布局或限制(如填充模式),至顶向下重新分配子Widget大小:

.     |<---Allotted Space 25--->|
.     +-----------------+-------+
.     |     Horizontal Box      |
.     +-------------------------+
.       +----=----+ +----=-----+
.       |Auto Size| |Fill Width|
.       +----+----+ +----+-----+
.            |           |      
.         +--+           +--+   
.         |                 |   
.         v                 v   
.     +----+-----+----------+---+
.     |STextBlock| SImage       |
.     +----------+--------------+
.     |<---14--->|<-----11----->|

各类型Widget重载OnArrangeChildren方法来计算实际大小,如:

void SWindowTitleBarArea::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
{
	const EVisibility& MyCurrentVisibility = this->GetVisibility();
	if ( ArrangedChildren.Accepts( MyCurrentVisibility ) )
	{
		const FMargin SlotPadding(ChildSlot.SlotPadding.Get());
		AlignmentArrangeResult XAlignmentResult = AlignChild<Orient_Horizontal>( AllottedGeometry.GetLocalSize().X, ChildSlot, SlotPadding );
		AlignmentArrangeResult YAlignmentResult = AlignChild<Orient_Vertical>( AllottedGeometry.GetLocalSize().Y, ChildSlot, SlotPadding );

		ArrangedChildren.AddWidget(
			AllottedGeometry.MakeChild(
				ChildSlot.GetWidget(),
				FVector2D(XAlignmentResult.Offset, YAlignmentResult.Offset),
				FVector2D(XAlignmentResult.Size, YAlignmentResult.Size)
			)
		);
	}
}

各类型Widget重载OnPaint函数,调用ArrangeChildren来重新规划大小


3 Slate绘制


During the paint pass, Slate iterates over all the visible widgets and produces a list of draw elements which will be consumed by the rendering system. This list is produced anew for every frame.

在Slate绘制时,逻辑层每帧会为渲染层计算生成DrawElements列表,伪代码如下:

.   // An arranged child is a widget and its allotted geometry
.   struct ArrangedChild
.   {
.       Widget;
.       Geometry;
.   };
.   
.   OutputElements OnPaint( AllottedGeometry )
.   {
.       // Arrange all the children given our allotted geometry
.       Array ArrangedChildren = ArrangeChildrenGiven( AllottedGeometry );
.   
.       // Paint the children
.       for each ( Child in ArrangedChildren )
.       {
.           OutputElements.Append( Child.Widget.OnPaint( Child.Geometry ) );
.       }
.   
.       // Paint a border
.       OutputElements.Append( DrawBorder() );
.   }

虚幻早期版本都是简单计算,迭代Widget树上的所有元素进行计算,性能差强人意,后面的版本对这块进行了优化,不需要每帧重复计算,如重复计算变化的元素。


参考

[1] Unreal Engine 4 Slate Architecture


Tags: UE4 Slate Vistied:
Share: Twitter Facebook LinkedIn