UMG源码笔记2-渲染过程

1.UMG类视图

UMG 控件跟Unity UGUI不太一样,不是所有的控件节点,都能拥有子节点,为了区分这三种控件,整理了下他们基类:

  • UWidget : 所有UMG 控件的公共基类,不提供增加子节点功能
  • UPanelWidget : 提供了增加子节点功能,可以有多个子节点
  • UContentWidget : 继承于 UPanelWidget ,是 UPanelWidget 的一种特例,只能有一个子节点

UMG常用控件的继承关系如下图所示:

UPanelWidget 实现了可以增加节点的功能 AddChild ,然后提供了是否可以增加多个子节点的标记为,

UPanelSlot* UPanelWidget::AddChild(UWidget* Content)
{
    if ( Content == nullptr )
    {
        return nullptr;
    }

    if ( !bCanHaveMultipleChildren && GetChildrenCount() > 0 )
    {
        return nullptr;
    }

    Content->RemoveFromParent();

    EObjectFlags NewObjectFlags = RF_Transactional;
    if (HasAnyFlags(RF_Transient))
    {
        NewObjectFlags |= RF_Transient;
    }

    // 创建 Slot 
    // GetSlotClass  : 获取对应节点的Slot类

    UPanelSlot* PanelSlot = NewObject<UPanelSlot>(this, GetSlotClass(), NAME_None, NewObjectFlags);

    // Slot->Content : 子节点
    // Slot->Parent  : 父节点 
    PanelSlot->Parent = this;
    PanelSlot->Content = Content;

    Content->Slot = PanelSlot;

    Slots.Add(PanelSlot);

    OnSlotAdded(PanelSlot);

    InvalidateLayoutAndVolatility();

    return PanelSlot;
}

UContentWidget 的实现是将 bCanHaveMultipleChildren 设置 false ,达到只有一个子节点的功能。

UContentWidget::UContentWidget(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    bCanHaveMultipleChildren = false;
}

其中, GetSlotClass 返回对应控件的 Slot 的类型,下面的表格里给出了对应控件的 Slot 类型,即 GetSlotClass 返回的 Slot 的类型。创建后的 Slot 对象会分别指向父节点跟子节点。

控件Slot类
UWidget/UPanelWidget/UCheckBox/URetainerBoxUPanelSlot
USafeZoneUSafeZoneSlot
USizeBoxUSizeBoxSlot
UBorderUBorderSlot
UButtonUButtonSlot
UCanvasPanelUCanvasPanelSlot
UHorizontalBoxUHorizontalBoxSlot
UOverlayUOverlaySlot
UScrollBoxUScrollBoxSlot
UGridPanelUGridSlot

1.3 Slate控件类视图

Slate 中基础类是 SWidget ,这是个抽象类,不能实例化,此外还有三个继承 SWidget 的基础类,其他控件都是这个三个类的子类型。

  • SPanel : 有多个子节点,本身是个抽象类,子类需要定义子节点组织方式
  • SLeafWidget : 没有子节点,抽象类,子节点需要重写 Paint 方法
  • SCompoundWidget : 可以有一个子节点

class SCompoundWidget : public SWidget
{
    FSimpleSlot ChildSlot;
}

class SBoxPanel : public SPanel
{
    TPanelChildren<FSlot> Children;
}

template<typename SlotType>
class TPanelChildren : public FChildren, private TIndirectArray< SlotType >
{
}

TPanelChildren: 实际上是一个 Array<SWidget*>
FSimpleSlot : 持有一个 SWidget*

2 UMG渲染流程

渲染一个SImage的调用栈如下:

2.1 FSlateApplication 渲染

首先介绍下几个关键的类

  • UGameEngine : 全局对象 GEngine
  • FSlateApplication : 单例,游戏窗口类负责渲染 Slate
// 全局单例
class UGameEngine
{
    // 游戏窗口句柄
    TWeakPtr<class SWindow> GameViewportWindow;
    // 游戏视口
    UGameViewportClient* GameViewport;

    // 游戏GameInstance对象 :WorkingCellGameInstance
    UGameInstance* GameInstance;
}

// 全局单例
class FSlateApplication
{
public:
    static void Create();
   
private:
    FSlateApplication();

    // 保存所有窗口
    TArray< TSharedRef<SWindow> > SlateWindows;
}

// 游戏加载前闪屏
class FPreLoadScreenManager
{
    TWeakPtr<class SWindow> MainWindow;

    void Initialize((FSlateRenderer& InSlateRenderer);
    void PassPreLoadScreenWindowBackToGame();
}

FSlateApplication 是一个单例类,会在 FEngineLoop::PreInit 调用时创建:

/// 1
int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
{
    const int32 rv1 = PreInitPreStartupScreen(CmdLine);
}

/// 2
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
{
    // ...
    FSlateApplication::Create();
}

创建完 FSlateApplication 后,接下来会在 FEngineLoop::PreInitPreStartUpScreen 函数中调用 UGameEngine::CreateGameWindow() 创建游戏窗口

/// 1
TSharedRef<SWindow> UGameEngine::CreateGameWindow()
{
    TSharedRef<SWindow> Window = SNew(SWindow);

    FSlateApplication::Get().AddWindow( Window, bShowImmediately );
}

/// 2. 如果有**PreLoadScreenManager**,则会在其初始化函数 **Initialize** 
///    的时候创建,并赋值给**MainWindow**
void FPreLoadScreenManager::Initialize(FSlateRenderer& InSlateRenderer)
{
    TSharedRef<SWindow> GameWindow = (GameEngine && GameEngine->GameViewportWindow.IsValid()) ? 
    GameEngine->GameViewportWindow.Pin().ToSharedRef() : UGameEngine::CreateGameWindow();

    MainWindow = GameWindow;
}

/// 3. 然后在**PassPreLoadScreenWindowBackToGame**将窗口
///    赋值给**GameEngine->GameViewportWindow**
void FPreLoadScreenManager::PassPreLoadScreenWindowBackToGame() const
{
    GameEngine->GameViewportWindow = MainWindow;
}

创建的 SWindow会加到 FSlateApplicationSlateWindown 队列:

TSharedRef<SWindow> FSlateApplication::AddWindow( TSharedRef<SWindow> InSlateWindow,
 const bool bShowImmediately )
{
    
    FSlateWindowHelper::ArrangeWindowToFront(SlateWindows, InSlateWindow);
    
    return InSlateWindow;
}

渲染时,会遍历 SlateWindows 列表,依次渲染每个 Window

/// 1
void FSlateApplication::PrivateDrawWindows( TSharedPtr<SWindow> DrawOnlyThisWindow )
{
    for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); 
        CurrentWindowIt; ++CurrentWindowIt )
    {
        TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
        // Only draw visible windows or in off-screen rendering mode
        if (bRenderOffScreen || CurrentWindow->IsVisible() )
        {
            DrawWindowAndChildren( CurrentWindow, DrawWindowArgs );
        }
    }
}

/// 2
void FSlateApplication::DrawWindowAndChildren( const TSharedRef<SWindow>& WindowToDraw, 
    FDrawWindowArgs& DrawWindowArgs )
{
    MaxLayerId = WindowToDraw->PaintWindow(
        GetCurrentTime(),
        GetDeltaTime(),
        WindowElementList,
        FWidgetStyle(),
        WindowToDraw->IsEnabled());
}

UGameEngine 初始化时,还会创建 UGameViewportClient

void UGameEngine::Init(IEngineLoop* InEngineLoop)
{
    if(GIsClient)
    {
        ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass);
        ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance);
        GameViewport = ViewportClient;
        GameInstance->GetWorldContext()->GameViewport = ViewportClient;
    }
}

2.2 SWindow 渲染

SWindow 类的组成

class SLATECORE_API SWindow : public SCompoundWidget, public FSlateInvalidationRoot
{
    /// Slate 事件检测加速类
    TUniquePtr<FHittestGrid> HittestGrid;
    SVerticalBox::FSlot* ContentSlot;
    TWeakPtr<SWindow> ParentWindowPtr;
    TArray< TSharedRef<SWindow> > ChildWindows;
}

SWindow在初始化时,会在 ChildSlot里增加几个 SOverlay

void SWindow::ConstructWindowInternals()
{
    this->ChildSlot
    [
        SAssignNew(WindowOverlay, SOverlay)
        .Visibility(EVisibility::SelfHitTestInvisible)
        // window background
        + SOverlay::Slot()
        [
            WindowBackgroundImage.ToSharedRef()
        ]

        // window border
        + SOverlay::Slot()
        [
            WindowBorder.ToSharedRef()
        ]

        // window outline
        + SOverlay::Slot()
        [
            WindowOutline.ToSharedRef()
        ]
    ];
}

渲染时,从 SlateApplication 对象调用 SWindowPaintWindow 方法

/// 1
int32 SWindow::PaintWindow( double CurrentTime, float DeltaTime, 
    FSlateWindowElementList& OutDrawElements, const FWidgetStyle& InWidgetStyle, 
    bool bParentEnabled )
{
    FSlateInvalidationResult Result = PaintInvalidationRoot(Context);
}

/// 2
FSlateInvalidationResult FSlateInvalidationRoot::PaintInvalidationRoot(
    const FSlateInvalidationContext& Context)
{
    CachedMaxLayerId = PaintSlowPath(Context);
}

/// 3
int32 SWindow::PaintSlowPath(const FSlateInvalidationContext& Context)
{
    HittestGrid->Clear();

    const FSlateRect WindowCullingBounds = GetClippingRectangleInWindow();
    const int32 LayerId = 0;
    const FGeometry WindowGeometry = GetWindowGeometryInWindow();

    int32 MaxLayerId = 0;
      
    MaxLayerId = Paint(*Context.PaintArgs, WindowGeometry, WindowCullingBounds, 
        *Context.WindowElementList, LayerId, Context.WidgetStyle,
         Context.bParentEnabled);

    return MaxLayerId;
}

/// 4. 最终调用到基类 SWidget::Paint 函数
int32 SWidget::Paint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, 
    const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, 
    int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
    int32 NewLayerId = OnPaint(UpdatedArgs, AllottedGeometry, CullingBounds, 
        OutDrawElements, LayerId, ContentWidgetStyle, bParentEnabled);
}

/// 5
int32 SWindow::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, 
    const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, 
    int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
    int32 MaxLayer = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, 
        OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
    return MaxLayer;
}

不同的节点的OnPaint函数实现不一样
SCompoundWidget只有一个子节点,直接调用子节点的Paint函数

int32 SCompoundWidget::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, 
    const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, 
    const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
    if( ArrangedChildren.Num() > 0 )
    {
        check( ArrangedChildren.Num() == 1 );
        FArrangedWidget& TheChild = ArrangedChildren[0];
       
        int32 Layer = 0;
        {
            Layer = TheChild.Widget->Paint( Args.WithNewParent(this), 
                TheChild.Geometry, MyCullingRect, OutDrawElements, LayerId + 1,
                CompoundedWidgetStyle, ShouldBeEnabled( bParentEnabled ) );
        }
        return Layer;
    }
}

SPanel 有多个子节点,渲染接口如下:

for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
{
    const FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];

    if (!IsChildWidgetCulled(MyCullingRect, CurWidget))
    {
        const int32 CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, 
            CurWidget.Geometry, MyCullingRect, OutDrawElements, LayerId,
            InWidgetStyle, bShouldBeEnabled);

        MaxLayerId = FMath::Max(MaxLayerId, CurWidgetsMaxLayerId);
    }
}

最终渲染到可渲染的子节点上,例如SImage

/// 1
int32 SImage::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, 
    const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, 
    int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
    const FSlateBrush* ImageBrush = Image.GetImage().Get();

    if ((ImageBrush != nullptr) && (ImageBrush->DrawAs != ESlateBrushDrawType::NoDrawType))
    {
        const bool bIsEnabled = ShouldBeEnabled(bParentEnabled);
        const ESlateDrawEffect DrawEffects = bIsEnabled ? ESlateDrawEffect::None :
            ESlateDrawEffect::DisabledEffect;

        const FLinearColor FinalColorAndOpacity(
            InWidgetStyle.GetColorAndOpacityTint() * 
            ColorAndOpacity.Get().GetColor(InWidgetStyle) * 
            ImageBrush->GetTint( InWidgetStyle ) );

        if (bFlipForRightToLeftFlowDirection && GSlateFlowDirection == EFlowDirection::RightToLeft)
        {
            const FGeometry FlippedGeometry = AllottedGeometry.MakeChild
                (FSlateRenderTransform(FScale2D(-1, 1)));

            FSlateDrawElement::MakeBox(OutDrawElements, LayerId,
                FlippedGeometry.ToPaintGeometry(), ImageBrush, DrawEffects,
                FinalColorAndOpacity);
        }
        else
        {
            FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), 
                ImageBrush, DrawEffects, FinalColorAndOpacity);
        }
    }

    return LayerId;
}

/// 2
/// 最后调用 **FSlateDrawElement** 来生成对应的渲染元素
/// FSlateDrawElement::MakeBox -> FSlateDrawElement::MakeBoxInternal
FSlateDrawElement& FSlateDrawElement::MakeBoxInternal(
    FSlateWindowElementList& ElementList,
    uint32 InLayer,
    const FPaintGeometry& PaintGeometry,
    const FSlateBrush* InBrush,
    ESlateDrawEffect InDrawEffects,
    const FLinearColor& InTint
)

{
    /// 记住这个 ElementType,后面还有用到
    EElementType ElementType = (InBrush->DrawAs == ESlateBrushDrawType::Border) ? 
        EElementType::ET_Border : EElementType::ET_Box;

    FSlateDrawElement& Element = ElementList.AddUninitialized();
    FSlateBoxPayload& BoxPayload = ElementList.CreatePayload<FSlateBoxPayload>(Element);

    BoxPayload.SetTint(InTint);
    BoxPayload.SetBrush(InBrush);

    Element.Init(ElementList, ElementType, InLayer, PaintGeometry, InDrawEffects);

    return Element;
}

SlateApplication中的Renderer中有专门的渲染buffDrawBuffer,调用完OnPaint后都会将自己的渲染数据Element添加到 ElementListd队列中,给后面合批准备数据。

2.3 真正的Render

下面进入真正的渲染流程了

/// 1
void FSlateApplication::PrivateDrawWindows( TSharedPtr<SWindow> DrawOnlyThisWindow )
{
    /// 这是上面说的,依次 Paint 每个 SWindow  
    for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); 
        CurrentWindowIt; ++CurrentWindowIt )
    {
        TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
        // Only draw visible windows or in off-screen rendering mode
        if (bRenderOffScreen || CurrentWindow->IsVisible() )
        {
            DrawWindowAndChildren( CurrentWindow, DrawWindowArgs );
        }
    }

    /// 开始准备合批,并且生成 render 指令
    /// OutDrawBuffer : 之前所有Slate控件的Element数据都在这里
    Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer );
}

/// 2
void FSlateRHIRenderer::DrawWindows(FSlateDrawBuffer& WindowDrawBuffer)
{
    DrawWindows_Private(WindowDrawBuffer);
}

/// 3
void FSlateRHIRenderer::DrawWindows_Private(FSlateDrawBuffer& WindowDrawBuffer)
{
    /// SlateBlush资源合并图集
    if (DoesThreadOwnSlateRendering())
    {
        ResourceManager->UpdateTextureAtlases();
    }

    /// 按照 Window 处理 Element 数据
    for (int32 ListIndex = 0; ListIndex < WindowElementLists.Num(); ++ListIndex)
    {
        FSlateWindowElementList& ElementList = *WindowElementLists[ListIndex];

        ElementBatcher->AddElements(ElementList);
    }
}

/// 4. ElementBatcher 定义
class FSlateRHIRenderer : public FSlateRenderer
{
    TUniquePtr<FSlateElementBatcher> ElementBatcher;
}

/// 5 
void FSlateElementBatcher::AddElements(FSlateWindowElementList& WindowElementList)
{
    AddElementsInternal(WindowElementList.GetUncachedDrawElements(), ViewportSize);
}

/// 6. 之前 SImage 用到的 ElementType ET_Box/ET_Border

enum class EElementType : uint8
{
    ET_Box,
    ET_DebugQuad,
    ET_Text,
    ET_ShapedText,
    ET_Spline,
    ET_Line,
    ET_Gradient,
    ET_Viewport,
    ET_Border,
    ET_Custom,
    ET_CustomVerts,
    ET_PostProcessPass,
    /** Total number of draw commands */
    ET_Count,
};

void FSlateElementBatcher::AddElementsInternal(const FSlateDrawElementArray& DrawElements, 
    const FVector2D& ViewportSize)
{
    for (const FSlateDrawElement& DrawElement : DrawElements)
    {
        // Determine what type of element to add
        switch ( DrawElement.GetElementType() )
        {
        // 之前 SImage 用到的 ElementType ET_Box/ET_Border
        case EElementType::ET_Box:
        {
            SCOPED_NAMED_EVENT_TEXT("Slate::AddBoxElement", FColor::Magenta);
            STAT(ElementStat_Boxes++);
            DrawElement.IsPixelSnapped() ? AddBoxElement<ESlateVertexRounding::Enabled>(DrawElement) : 
                AddBoxElement<ESlateVertexRounding::Disabled>(DrawElement);
        }
            break;
        case EElementType::ET_Border:
        case EElementType::ET_Text:

        ///...

        }
    }
}

/// 7
template<ESlateVertexRounding Rounding>
void FSlateElementBatcher::AddBoxElement(const FSlateDrawElement& DrawElement)
{

    FSlateRenderBatch& RenderBatch = CreateRenderBatch( Layer, FShaderParams(), Resource, 
        ESlateDrawPrimitive::TriangleList, ESlateShader::Default, InDrawEffects, DrawFlags, 
        DrawElement);
    // Create 9 quads for the box element based on the following diagram
    //     ___LeftMargin    ___RightMargin
    //    /                /
    //  +--+-------------+--+
    //  |  |c1           |c2| ___TopMargin
    //  +--o-------------o--+
    //  |  |             |  |
    //  |  |c3           |c4|
    //  +--o-------------o--+
    //  |  |             |  | ___BottomMargin
    //  +--+-------------+--+

    /// 一共16个顶点数据
    RenderBatch.AddVertex( FSlateVertex::Make<Rounding>( RenderTransform, FVector2D( Position.X, Position.Y ),
       LocalSize, DrawScale, FVector4(StartUV, Tiling), Tint ) ); //0
    /// ...
    RenderBatch.AddVertex( FSlateVertex::Make<Rounding>( RenderTransform, FVector2D( EndPos.X, EndPos.Y ),
       LocalSize, DrawScale, FVector4(EndUV, Tiling), Tint ) ); //15

    // Top
    RenderBatch.AddIndex( IndexStart + 0 );
    /// ...
    RenderBatch.AddIndex( IndexStart + 15 );

}

/// 8. 所有的Element都会存储Layer Shader参数等信息,添加成待合批数据。
FSlateRenderBatch& FSlateElementBatcher::CreateRenderBatch(
    int32 Layer, 
    const FShaderParams& ShaderParams,
    const FSlateShaderResource* InResource,
    ESlateDrawPrimitive PrimitiveType,
    ESlateShader ShaderType,
    ESlateDrawEffect DrawEffects,
    ESlateBatchDrawFlag DrawFlags,
    const FSlateDrawElement& DrawElement)
{
    FSlateRenderBatch& NewBatch = CurrentCachedElementList
        ? CurrentCachedElementList->AddRenderBatch(Layer, ShaderParams, InResource,
            PrimitiveType, ShaderType, DrawEffects, DrawFlags, DrawElement.GetSceneIndex())

        : BatchData->AddRenderBatch(Layer, ShaderParams, InResource, PrimitiveType, 
            ShaderType, DrawEffects, DrawFlags, DrawElement.GetSceneIndex());

    NewBatch.ClippingState = ResolveClippingState(DrawElement);

    return NewBatch;
}

/// 9. 最后新建合批任务,在新建的线程里进行合批
void FSlateRHIRenderer::DrawWindows_Private(FSlateDrawBuffer& WindowDrawBuffer)
{
    if (GIsClient && !IsRunningCommandlet() && !GUsingNullRHI)
    {
        ENQUEUE_RENDER_COMMAND(SlateDrawWindowsCommand)(
            [Params, ViewInfo](FRHICommandListImmediate& RHICmdList)
            {
                /// 10. 切换到渲染线程
                Params.Renderer->DrawWindow_RenderThread(RHICmdList, *ViewInfo, 
                    *Params.WindowElementList, Params);
            }
        );
    }
}

/// 10. 切换到渲染线程
/** Draws windows from a FSlateDrawBuffer on the render thread */
void FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate& RHICmdList,
    FViewportInfo& ViewportInfo, FSlateWindowElementList& WindowElementList, 
    const struct FSlateDrawWindowCommandParams& DrawCommandParams)
{
    RenderingPolicy->BuildRenderingBuffers(RHICmdList, BatchData);
}

/// 11. 创建渲染Buffers 
void FSlateRHIRenderingPolicy::BuildRenderingBuffers(FRHICommandListImmediate& RHICmdList, 
    FSlateBatchData& InBatchData)
{
    /// 12. Slate 数据合批
    InBatchData.MergeRenderBatches();
}

/// 12. Slate 数据合批
void FSlateBatchData::MergeRenderBatches()
{
    for (int32 TestIndex = BatchIndex + 1; TestIndex < BatchIndices.Num(); ++TestIndex)
    {
        const TPair<int32, int32>& NextBatchIndexPair = BatchIndices[TestIndex];
        FSlateRenderBatch& TestBatch = RenderBatches[NextBatchIndexPair.Key];
        if (TestBatch.GetLayer() != CurBatch.GetLayer())
        {
            // none of the batches will be compatible since we encountered an incompatible layer
            break;
        }
        ///                                       13. 合批规则
        else if (!TestBatch.bIsMerged && CurBatch.IsBatchableWith(TestBatch))
        {
            CombineBatches(CurBatch, TestBatch, FinalVertexData, FinalIndexData);
        }
    }
}

/// 13. 合批规则
bool IsBatchableWith(const FSlateRenderBatch& Other) const
{
    return
        ShaderResource == Other.ShaderResource
        && DrawFlags == Other.DrawFlags
        && ShaderType == Other.ShaderType
        && DrawPrimitiveType == Other.DrawPrimitiveType
        && DrawEffects == Other.DrawEffects
        && ShaderParams == Other.ShaderParams
        && InstanceData == Other.InstanceData
        && InstanceCount == Other.InstanceCount
        && InstanceOffset == Other.InstanceOffset
        && DynamicOffset == Other.DynamicOffset
        && CustomDrawer == Other.CustomDrawer
        && SceneIndex == Other.SceneIndex
        && ClippingState == Other.ClippingState;
}


/// 14. 合批结束
void FSlateRHIRenderingPolicy::BuildRenderingBuffers(FRHICommandListImmediate& RHICmdList,
    FSlateBatchData& InBatchData)
{
    /// 10/11/12 Draw Element 合批
    InBatchData.MergeRenderBatches();

    /// 合批结束 开始发送渲染指令
    const FSlateVertexArray& FinalVertexData = InBatchData.GetFinalVertexData();
    const FSlateIndexArray& FinalIndexData = InBatchData.GetFinalIndexData();

    const int32 NumVertices = FinalVertexData.Num();
    const int32 NumIndices = FinalIndexData.Num();

    if (InBatchData.GetRenderBatches().Num() > 0 && NumVertices > 0 && NumIndices > 0)
    {
        bool bShouldShrinkResources = false;

        RHICmdList.EnqueueLambda([
            VertexBuffer = MasterVertexBuffer.VertexBufferRHI.GetReference(),
            IndexBuffer = MasterIndexBuffer.IndexBufferRHI.GetReference(),
            &InBatchData,
            bAbsoluteIndices
        ](FRHICommandListImmediate& InRHICmdList)
        {
            SCOPE_CYCLE_COUNTER(STAT_SlateUpdateBufferRTTimeLambda);

            // Note: Use "Lambda" prefix to prevent clang/gcc warnings of '-Wshadow' warning
            const FSlateVertexArray& LambdaFinalVertexData = InBatchData.GetFinalVertexData();
            const FSlateIndexArray& LambdaFinalIndexData = InBatchData.GetFinalIndexData();

            const int32 NumBatchedVertices = LambdaFinalVertexData.Num();
            const int32 NumBatchedIndices = LambdaFinalIndexData.Num();

            uint32 RequiredVertexBufferSize = NumBatchedVertices * 
                sizeof(FSlateVertex);
            uint8* VertexBufferData = (uint8*)InRHICmdList.LockVertexBuffer(VertexBuffer, 0, 
                RequiredVertexBufferSize, RLM_WriteOnly);

            uint32 RequiredIndexBufferSize = NumBatchedIndices * sizeof(SlateIndex);
            uint8* IndexBufferData = (uint8*)InRHICmdList.LockIndexBuffer(IndexBuffer, 0, 
                RequiredIndexBufferSize, RLM_WriteOnly);

            FMemory::Memcpy(VertexBufferData, LambdaFinalVertexData.GetData(), RequiredVertexBufferSize);
            FMemory::Memcpy(IndexBufferData, LambdaFinalIndexData.GetData(), RequiredIndexBufferSize);

            InRHICmdList.UnlockVertexBuffer(VertexBuffer);
            InRHICmdList.UnlockIndexBuffer(IndexBuffer);
        });
    }
}

/// 15.回到渲染线程
void FSlateRHIRenderer::DrawWindow_RenderThread(...)
{
    /// 12. Slate 数据合批
    InBatchData.MergeRenderBatches();

    ///
    const uint32 ViewportWidth = (ViewportRT) ? ViewportRT->GetSizeX() :
        ViewportInfo.Width;
    const uint32 ViewportHeight = (ViewportRT) ? ViewportRT->GetSizeY() : 
        ViewportInfo.Height;

    FSlateBackBuffer BackBufferTarget(BackBuffer, FIntPoint(ViewportWidth, 
        ViewportHeight));

    /// 16. DrawElements
    RenderingPolicy->DrawElements
    (
        RHICmdList,
        BackBufferTarget,
        BackBuffer,
        PostProcessBuffer,
        ViewportInfo.bRequiresStencilTest ? ViewportInfo.DepthStencil : EmptyTarget,
        BatchData.GetFirstRenderBatchIndex(),
        BatchData.GetRenderBatches(),
        RenderParams
    );
}

/// 16. DrawElements
void FSlateRHIRenderingPolicy::DrawElements(
    FRHICommandListImmediate& RHICmdList,
    FSlateBackBuffer& BackBuffer,
    FTexture2DRHIRef& ColorTarget,
    FTexture2DRHIRef& PostProcessTexture,
    FTexture2DRHIRef& DepthStencilTarget,
    int32 FirstBatchIndex,
    const TArray<FSlateRenderBatch>& RenderBatches,
    const FSlateRenderingParams& Params)
{
    while (NextRenderBatchIndex != INDEX_NONE)
    {
        VertexBufferPtr = &MasterVertexBuffer;
        IndexBufferPtr = &MasterIndexBuffer;

        // ...
        // for RHIs that can't handle VertexOffset, we need to offset 
        // the stream source each time
        RHICmdList.SetStreamSource(0, VertexBufferPtr->VertexBufferRHI, 
            RenderBatch.VertexOffset * sizeof(FSlateVertex));

        RHICmdList.DrawIndexedPrimitive(IndexBufferPtr->IndexBufferRHI, 0, 0, 
            RenderBatch.NumVertices, RenderBatch.IndexOffset, PrimitiveCount, 
            RenderBatch.InstanceCount);
    }
}

至此,就完成了Slate的渲染了。