虚幻Slate基本绘制流程主要包含两步:
预处理(SlatePrepass):预计算Slate绘制所需的信息。
绘制(Paint):为渲染层填充绘制数据。
在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;
}
在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来重新规划大小
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