1.Slate控件类型:
在Slate中,控件分为三种类型:
SLeafWidget不带子槽的控件,如TextBlock;
SPanel子槽数量为动态的控件,如SHorizontalBox,SVerticalBox;
SCompoundWidget子槽数量固定的控件如SDockTab,Border,Button等。
2.创建控件
/**
* Slate widgets are constructed through SNew and SAssignNew.
* e.g.
*
* TSharedRef<SButton> MyButton = SNew(SButton);
* or
* TSharedPtr<SButton> MyButton;
* SAssignNew( MyButton, SButton );
*
* Using SNew and SAssignNew ensures that widgets are populated
*/
SNew返回TSharedRef; SAssignNew 返回TSharedPtr.
在虚幻4引擎中,TSharedRef可以直接转换为TSharedPtr,但是这个转化不是双向的,TSharedPtr转化为TSharedRef需要用到AsShared() 函数。
3.总体的渲染流程
第一块是控件绘制,在主线程中,给每个控件分配LayerId,并从控件抽象出FSlateDrawElement。
第二块是绘制指令生成渲染指令,也是在主线程中,把FSlateDrawElement包装成FSlateRenderBatch,并根据控件的信息生成VertexBuffer。
第三块是合批并执行渲染,在渲染线程,把之前生成的FSlateRenderBatch按照LayerId从小到大排序,并尝试合批。最后把UI渲染到BackBuffer。
下面从widget源码分析整个渲染的流程
SWidget总共1800行左右,是Slate控件的基类,其中最主要的方法声明如下:
class SLATECORE_API SWidget : public FSlateControlledConstruction,
public TSharedFromThis<SWidget> // Enables 'this->AsShared()'
{
public:
/**
* Called to tell a widget to paint itself (and it's children).
*
* The widget should respond by populating the OutDrawElements array with FDrawElements
* that represent it and any of its children.
*
* @param Args All the arguments necessary to paint this widget (@todo umg: move all params into this struct)
* @param AllottedGeometry The FGeometry that describes an area in which the widget should appear.
* @param MyCullingRect The clipping rectangle allocated for this widget and its children.
* @param OutDrawElements A list of FDrawElements to populate with the output.
* @param LayerId The Layer onto which this widget should be rendered.
* @param InColorAndOpacity Color and Opacity to be applied to all the descendants of the widget being painted
* @param bParentEnabled True if the parent of this widget is enabled.
* @return The maximum layer ID attained by this widget or any of its children.
*/
int32 Paint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const;
/**
* Ticks this widget with Geometry. Override in derived classes, but always call the parent implementation.
*
* @param AllottedGeometry The space allotted for this widget
* @param InCurrentTime Current absolute real time
* @param InDeltaTime Real time passed since last tick
*/
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime);
private:
/**
* The widget should respond by populating the OutDrawElements array with FDrawElements
* that represent it and any of its children. Called by the non-virtual OnPaint to enforce pre/post conditions
* during OnPaint.
*
* @param Args All the arguments necessary to paint this widget (@todo umg: move all params into this struct)
* @param AllottedGeometry The FGeometry that describes an area in which the widget should appear.
* @param MyCullingRect The rectangle representing the bounds currently being used to completely cull widgets. Unless IsChildWidgetCulled(...) returns true, you should paint the widget.
* @param OutDrawElements A list of FDrawElements to populate with the output.
* @param LayerId The Layer onto which this widget should be rendered.
* @param InColorAndOpacity Color and Opacity to be applied to all the descendants of the widget being painted
* @param bParentEnabled True if the parent of this widget is enabled.
* @return The maximum layer ID attained by this widget or any of its children.
*/
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const = 0;
/**
* Compute the Geometry of all the children and add populate the ArrangedChildren list with their values.
* Each type of Layout panel should arrange children based on desired behavior.
*
* @param AllottedGeometry The geometry allotted for this widget by its parent.
* @param ArrangedChildren The array to which to add the WidgetGeometries that represent the arranged children.
*/
virtual void OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const = 0;
}
public中的Paint方法由SWidget实现,OnPaint方法需要继承SWidget的控件自己实现,OnArrangeChildren方法由带槽的控件实现。
每个控件在OnPaint中最终会调用FSlateDrawElement中的以下方法,并且返回layerId:
Unreal的控件绘制是从FSlateApplication::DrawWindows开始,从SWindow::Paint开始派发,LayerId初始为0。
整个控件的绘制过程,实际上是一个递归调用,所以Unreal的UI绘制是一个深度优先的遍历。递归过程如下:
- SWindow调用SWidget::Paint。
- 执行SWidget::Paint,并调用纯虚函数OnPaint,分发给各控件实现的OnPaint。
- 执行各控件的OnPaint,完成绘制,如果包含子控件则调用子控件的Paint,回到第二步。
- 直到所有控件绘制完。
SWidget中的Paint方法会调用OnPaint方法:
// Paint the geometry of this widget.
int32 NewLayerId = OnPaint(UpdatedArgs, AllottedGeometry, CullingBounds, OutDrawElements, LayerId, ContentWidgetStyle, bParentEnabled);
FSlateDrawElement类在DrawElements.h中定义,绘制类型如下:
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,
};
SImage绘制的是ET_Box类型。
以MakeText为例:
void FSlateDrawElement::MakeText( FSlateWindowElementList& ElementList, uint32 InLayer, const FPaintGeometry& PaintGeometry, const FString& InText, const FSlateFontInfo& InFontInfo, ESlateDrawEffect InDrawEffects, const FLinearColor& InTint )
{
SCOPE_CYCLE_COUNTER(STAT_SlateDrawElementMakeTime)
PaintGeometry.CommitTransformsIfUsingLegacyConstructor();
...
FSlateDrawElement& Element = ElementList.AddUninitialized();
FSlateTextPayload& DataPayload = ElementList.CreatePayload<FSlateTextPayload>(Element);
DataPayload.SetTint(InTint);
DataPayload.SetText(InText, InFontInfo);
Element.Init(ElementList, EElementType::ET_Text, InLayer, PaintGeometry, InDrawEffects);
}
同其它的MakeXXX方法一样,最终会创建一个FSlateDrawElement,调用Element.Init方法:
void FSlateDrawElement::Init(FSlateWindowElementList& ElementList, EElementType InElementType, uint32 InLayer, const FPaintGeometry& PaintGeometry, ESlateDrawEffect InDrawEffects)
{
RenderTransform = PaintGeometry.GetAccumulatedRenderTransform();
Position = PaintGeometry.DrawPosition;
Scale = PaintGeometry.DrawScale;
LocalSize = PaintGeometry.GetLocalSize();
ClipStateHandle.SetPreCachedClipIndex(ElementList.GetClippingIndex());
LayerId = InLayer;
ElementType = InElementType;
DrawEffects = InDrawEffects;
// Calculate the layout to render transform as this is needed by several calculations downstream.
const FSlateLayoutTransform InverseLayoutTransform(Inverse(FSlateLayoutTransform(Scale, Position)));
// This is a workaround because we want to keep track of the various Scenes
// in use throughout the UI. We keep a synchronized set with the render thread on the SlateRenderer and
// use indices to synchronize between them.
FSlateRenderer* Renderer = FSlateApplicationBase::Get().GetRenderer();
checkSlow(Renderer);
SceneIndex = Renderer->GetCurrentSceneIndex();
BatchFlags = ESlateBatchDrawFlag::None;
BatchFlags |= static_cast<ESlateBatchDrawFlag>(static_cast<uint32>(InDrawEffects) & static_cast<uint32>(ESlateDrawEffect::NoBlending | ESlateDrawEffect::PreMultipliedAlpha | ESlateDrawEffect::NoGamma | ESlateDrawEffect::InvertAlpha));
...
if ((InDrawEffects & ESlateDrawEffect::ReverseGamma) != ESlateDrawEffect::None)
{
BatchFlags |= ESlateBatchDrawFlag::ReverseGamma;
}
}
这里只是将绘制的相关的数据放在FSlateDrawElement中,比如:变换、位置、缩放等,实际上没有进行渲染调用。
SlateApplication.h
/**
* Draws slate windows, optionally only drawing the passed in window
*/
void FSlateApplication::PrivateDrawWindows( TSharedPtr<SWindow> DrawOnlyThisWindow )
{
check(Renderer.IsValid());
// Grab a scope lock around access to the resource proxy map, just to ensure we never cross over
// with the loading thread.
FScopeLock ScopeLock(Renderer->GetResourceCriticalSection());
FMemMark Mark(FMemStack::Get());
FWidgetPath WidgetsToVisualizeUnderCursor;
#if SLATE_HAS_WIDGET_REFLECTOR
// Is user expecting visual feedback from the Widget Reflector?
if (WidgetReflectorPtr.IsValid() && WidgetReflectorPtr.Pin()->IsVisualizingLayoutUnderCursor())
{
WidgetsToVisualizeUnderCursor = GetCursorUser()->GetLastWidgetsUnderCursor().ToWidgetPath();
}
#endif
// Prepass the window
DrawPrepass( DrawOnlyThisWindow );
FDrawWindowArgs DrawWindowArgs( Renderer->GetDrawBuffer(), WidgetsToVisualizeUnderCursor);
{
SCOPE_CYCLE_COUNTER( STAT_SlateDrawWindowTime );
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
if (ActiveModalWindow.IsValid())
{
DrawWindowAndChildren( ActiveModalWindow.ToSharedRef(), DrawWindowArgs );
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
{
const TSharedRef<SWindow>& CurrentWindow = *CurrentWindowIt;
if ( CurrentWindow->GetType() == EWindowType::ToolTip )
{
DrawWindowAndChildren(CurrentWindow, DrawWindowArgs);
}
}
TArray< TSharedRef<SWindow> > NotificationWindows;
FSlateNotificationManager::Get().GetWindows(NotificationWindows);
for( auto CurrentWindowIt( NotificationWindows.CreateIterator() ); CurrentWindowIt; ++CurrentWindowIt )
{
DrawWindowAndChildren(*CurrentWindowIt, DrawWindowArgs);
}
}
else if( DrawOnlyThisWindow.IsValid() )
{
DrawWindowAndChildren( DrawOnlyThisWindow.ToSharedRef(), DrawWindowArgs );
}
else
{
// Draw all windows
// Use of an old-style iterator is intentional here, as SlateWindows
// array may be mutated by user logic in draw calls. The iterator
// prevents us from reading off the end and only keeps an index
// internally:
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 );
}
}
}
}
// This is potentially dangerous on the movie playback thread that slate sometimes runs on
if(!IsInSlateThread())
{
// Some windows may have been destroyed/removed.
// Do not attempt to draw any windows that have been removed.
TArray<SWindow*> AllWindows = GatherAllDescendants(SlateWindows);
DrawWindowArgs.OutDrawBuffer.RemoveUnusedWindowElement(AllWindows);
}
Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer );
}
?在slate 独立窗口中应该绘制的是ActiveModalWindow:
DrawWindowAndChildren( ActiveModalWindow.ToSharedRef(), DrawWindowArgs );
最终会调用FSlateRenderer的DrawWindows方法,FSlateRenderer没有实现该方法,实际位置为FSlateRHIRenderer的DrawWindows:
/**
* Creates necessary resources to render a window and sends draw commands to the rendering thread
*
* @param WindowDrawBuffer The buffer containing elements to draw
*/
void FSlateRHIRenderer::DrawWindows_Private(FSlateDrawBuffer& WindowDrawBuffer)
{
...
// Iterate through each element list and set up an RHI window for it if needed
const TArray<TSharedRef<FSlateWindowElementList>>& WindowElementLists = WindowDrawBuffer.GetWindowElementLists();
for (int32 ListIndex = 0; ListIndex < WindowElementLists.Num(); ++ListIndex)
{
FSlateWindowElementList& ElementList = *WindowElementLists[ListIndex];
SWindow* Window = ElementList.GetRenderWindow();
if (Window)
{
// Add all elements for this window to the element batcher
ElementBatcher->AddElements(ElementList);
}
...
}
WindowDrawBuffer是从FSlateApplication的FDrawWindowArgs传递的:
FDrawWindowArgs DrawWindowArgs( Renderer->GetDrawBuffer(), WidgetsToVisualizeUnderCursor);
其中第一个参数就是要绘制的数据,我们看下Renderer是如何获取到DrawBuffer的,Renderer是在FSlateApplication中创建和初始化的
bool FSlateApplication::InitializeRenderer( TSharedRef<FSlateRenderer> InRenderer, bool bQuietMode )
{
Renderer = InRenderer;
bool bResult = Renderer->Initialize();
if (!bResult && !bQuietMode)
{
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *NSLOCTEXT("SlateD3DRenderer", "ProblemWithGraphicsCard", "There is a problem with your graphics card. Please ensure your card meets the minimum system requirements and that you have the latest drivers installed.").ToString(), *NSLOCTEXT("SlateD3DRenderer", "UnsupportedVideoCardErrorTitle", "Unsupported Graphics Card").ToString());
}
return bResult;
}
在FSlateOpenGLRenderer中GetDrawBuffer方法:
/** Returns a draw buffer that can be used by Slate windows to draw window elements */
FSlateDrawBuffer& FSlateOpenGLRenderer::GetDrawBuffer()
{
// Clear out the buffer each time its accessed
DrawBuffer.ClearBuffer();
return DrawBuffer;
}
可以看到获取的DrawBuffer是空的,接着分析DrawWindowArgs的调用,在上面代码中调用了5次DrawWindowAndChildren:
void FSlateApplication::DrawWindowAndChildren( const TSharedRef<SWindow>& WindowToDraw, FDrawWindowArgs& DrawWindowArgs )
{
// On Mac, where child windows can be on screen even if their parent is hidden or minimized, we want to always draw child windows.
// On other platforms we set bDrawChildWindows to true only if we draw the current window.
bool bDrawChildWindows = PLATFORM_MAC;
// Only draw visible windows or in off-screen rendering mode
if (bRenderOffScreen || (WindowToDraw->IsVisible() && (!WindowToDraw->IsWindowMinimized() || FApp::UseVRFocus())) )
{
...
// Draw Prep
FSlateWindowElementList& WindowElementList = DrawWindowArgs.OutDrawBuffer.AddWindowElementList(WindowToDraw);
...
}
}
查看FSlateDrawBuffer的AddWindowElementList方法:
FSlateWindowElementList& FSlateDrawBuffer::AddWindowElementList(TSharedRef<SWindow> ForWindow)
{
for ( int32 WindowIndex = 0; WindowIndex < WindowElementListsPool.Num(); ++WindowIndex )
{
TSharedRef<FSlateWindowElementList> ExistingElementList = WindowElementListsPool[WindowIndex];
if (ExistingElementList->GetPaintWindow() == &ForWindow.Get())
{
WindowElementLists.Add(ExistingElementList);
WindowElementListsPool.RemoveAtSwap(WindowIndex);
ExistingElementList->ResetElementList();
return *ExistingElementList;
}
}
TSharedRef<FSlateWindowElementList> WindowElements = MakeShared<FSlateWindowElementList>(ForWindow);
WindowElements->ResetElementList();
WindowElementLists.Add(WindowElements);
return *WindowElements;
}
最终返回了FSlateWindowElementList:
FSlateWindowElementList存储Slate的窗口中的所有图元信息。在SWindow执行Draw的时候,会向SlateDrawBuffer申请一个SlateWindowElementList,一个SWindow对应着一个List。在SWindow的子控件执行Paint或者OnPaint的时候,会传入这个FSlateWindowElementList引用,然后控件会将包含的图元信息向List的SlateDrawLayer中写入。
FSlateWindowElementList具有一个DrawLayer的Stack,在图元收集阶段使用。
在AddElements方法中,数据来源是:
const TArrayView<FSlateCachedElementData* const> CachedElementDataList = WindowElementList.GetCachedElementDataList();
最终定位到DrawElements.cpp文件中:
void FSlateWindowElementList::PushCachedElementData(FSlateCachedElementData& CachedElementData)
{
check(&CachedElementData);
const int32 Index = CachedElementDataList.AddUnique(&CachedElementData);
CachedElementDataListStack.Push(Index);
}
查看PushCachedElementData的调用,在SWindow的PaintWindow中,SWindow继承自FSlateInvalidationRoot,最终调用的是FSlateInvalidationRoot的GetCachedElements方法,返回FSlateCachedElementData数据:
OutDrawElements.PushCachedElementData(GetCachedElements());
FSlateCachedElementData的数据结构:
struct FSlateCachedElementData
{
friend class FSlateElementBatcher;
friend struct FSlateCachedElementsHandle;
void Empty();
FSlateCachedElementsHandle AddCache(const SWidget* Widget);
FSlateDrawElement& AddCachedElement(FSlateCachedElementsHandle& CacheHandle, const FSlateClippingManager& ParentClipManager, const SWidget* Widget);
FSlateRenderBatch& AddCachedRenderBatch(FSlateRenderBatch&& NewBatch, int32& OutIndex);
void RemoveCachedRenderBatches(const TArray<int32>& CachedRenderBatchIndices);
FSlateCachedClipState& FindOrAddCachedClipState(const FSlateClippingState* RefClipState);
void CleanupUnusedClipStates();
const TSparseArray<FSlateRenderBatch>& GetCachedBatches() const { return CachedBatches; }
void AddReferencedObjects(FReferenceCollector& Collector);
private:
/** Removes a cache node completely from the cache */
void RemoveList(FSlateCachedElementsHandle& CacheHandle);
private:
/** List of cached batches to submit for drawing */
TSparseArray<FSlateRenderBatch> CachedBatches;
TArray<TSharedPtr<FSlateCachedElementList>> CachedElementLists;
TArray<FSlateCachedElementList*, TInlineAllocator<50>> ListsWithNewData;
TArray<FSlateCachedClipState> CachedClipStates;
};
从接口定义就可以发现渲染的数据是通过AddCachedElement方法传入的:
FSlateDrawElement& FSlateWindowElementList::AddCachedElement()
{
FSlateCachedElementData* CurrentCachedElementData = GetCurrentCachedElementData();
check(CurrentCachedElementData);
FWidgetDrawElementState& CurrentWidgetState = WidgetDrawStack.Top();
check(!CurrentWidgetState.bIsVolatile);
if (!CurrentWidgetState.CacheHandle.IsValid())
{
CurrentWidgetState.CacheHandle = CurrentCachedElementData->AddCache(CurrentWidgetState.Widget);
}
return CurrentCachedElementData->AddCachedElement(CurrentWidgetState.CacheHandle, GetClippingManager(), CurrentWidgetState.Widget);
}
该方法返回了FSlateDrawElement引用。
添加widget到WidgetDrawStack中:
void FSlateWindowElementList::PushPaintingWidget(const SWidget& CurrentWidget, int32 StartingLayerId, FSlateCachedElementsHandle& CurrentCacheHandle)
{
FSlateCachedElementData* CurrentCachedElementData = GetCurrentCachedElementData();
if (CurrentCachedElementData)
{
// When a widget is pushed reset its draw elements. They are being recached or possibly going away
if (CurrentCacheHandle.IsValid())
{
#if WITH_SLATE_DEBUGGING
check(CurrentCacheHandle.IsOwnedByWidget(&CurrentWidget));
#endif
CurrentCacheHandle.ClearCachedElements();
}
WidgetDrawStack.Emplace(CurrentCacheHandle, CurrentWidget.IsVolatileIndirectly() || CurrentWidget.IsVolatile(), &CurrentWidget);
}
}
PushPaintingWidget是在SWidget中Paint方法调用的:
int32 SWidget::Paint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
...
OutDrawElements.PushPaintingWidget(*this, LayerId, PersistentState.CachedElementHandle);
...
}
在DrawWindows_Private中的数据填充分析完成了,接下来看下拿到数据后是如何渲染的:
void FSlateRHIRenderer::DrawWindows_Private(FSlateDrawBuffer& WindowDrawBuffer)
{
...
// Tell the rendering thread to draw the windows
{
FSlateDrawWindowCommandParams Params;
Params.Renderer = this;
Params.WindowElementList = &ElementList;
Params.Window = Window;
#if WANTS_DRAW_MESH_EVENTS
Params.WindowTitle = Window->GetTitle().ToString();
#endif
Params.bLockToVsync = bLockToVsync;
#if ALPHA_BLENDED_WINDOWS
Params.bClear = Window->GetTransparencySupport() == EWindowTransparency::PerPixel;
#else
Params.bClear = false;
#endif
Params.WorldTimeSeconds = FApp::GetCurrentTime() - GStartTime;
Params.DeltaTimeSeconds = FApp::GetDeltaTime();
Params.RealTimeSeconds = FPlatformTime::Seconds() - GStartTime;
// Skip the actual draw if we're in a headless execution environment
bool bLocalTakingAScreenShot = bTakingAScreenShot;
if (GIsClient && !IsRunningCommandlet() && !GUsingNullRHI)
{
ENQUEUE_RENDER_COMMAND(SlateDrawWindowsCommand)(
[Params, ViewInfo](FRHICommandListImmediate& RHICmdList)
{
Params.Renderer->DrawWindow_RenderThread(RHICmdList, *ViewInfo, *Params.WindowElementList, Params);
}
);
}
SlateWindowRendered.Broadcast(*Window, &ViewInfo->ViewportRHI);
if (bLocalTakingAScreenShot)
{
// immediately flush the RHI command list
FlushRenderingCommands();
}
}
...
}
ENQUEUE_RENDER_COMMAND定义如下:
#define ENQUEUE_RENDER_COMMAND(Type) \
struct Type##Name \
{ \
static const char* CStr() { return #Type; } \
static const TCHAR* TStr() { return TEXT(#Type); } \
}; \
EnqueueUniqueRenderCommand<Type##Name>
EnqueueUniqueRenderCommand:
template<typename TSTR, typename LAMBDA>
FORCEINLINE_DEBUGGABLE void EnqueueUniqueRenderCommand(LAMBDA&& Lambda)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_EnqueueUniqueRenderCommand);
typedef TEnqueueUniqueRenderCommandType<TSTR, LAMBDA> EURCType;
#if 0 // UE_SERVER && UE_BUILD_DEBUG
UE_LOG(LogRHI, Warning, TEXT("Render command '%s' is being executed on a dedicated server."), TSTR::TStr())
#endif
if (IsInRenderingThread())
{
FRHICommandListImmediate& RHICmdList = GetImmediateCommandList_ForRenderCommand();
Lambda(RHICmdList);
}
else
{
if (ShouldExecuteOnRenderThread())
{
CheckNotBlockedOnRenderThread();
TGraphTask<EURCType>::CreateTask().ConstructAndDispatchWhenReady(Forward<LAMBDA>(Lambda));
}
else
{
EURCType TempCommand(Forward<LAMBDA>(Lambda));
FScopeCycleCounter EURCMacro_Scope(TempCommand.GetStatId());
TempCommand.DoTask(ENamedThreads::GameThread, FGraphEventRef());
}
}
}
最终通过lambda表达式执行DrawWindow_RenderThread方法,该方法总共有500多行代码。
/** Draws windows from a FSlateDrawBuffer on the render thread */
void FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate& RHICmdList, FViewportInfo& ViewportInfo, FSlateWindowElementList& WindowElementList, const struct FSlateDrawWindowCommandParams& DrawCommandParams)
{
...
RenderingPolicy->DrawElements
(
RHICmdList,
BackBufferTarget,
BackBuffer,
PostProcessBuffer,
ViewportInfo.bRequiresStencilTest ? ViewportInfo.DepthStencil : EmptyTarget,
BatchData.GetFirstRenderBatchIndex(),
BatchData.GetRenderBatches(),
RenderParams
);
...
}
简单的看下去是调用shader进行渲染的
查看UMG中UImage的实现:
TSharedRef<SWidget> UImage::RebuildWidget()
{
MyImage = SNew(SImage)
.FlipForRightToLeftFlowDirection(bFlipForRightToLeftFlowDirection);
return MyImage.ToSharedRef();
}
UMG就是对Slate的一个简单封装。
于是现在一切流程清晰后,可以得到如下结论:
【1】如果想新建一个自己的UI类型,直接派生一个SWidget子类然后重载OnPaint,然后再在UMG层建一个与之对应的简单封装即可。
【2】如果想要在UI上做一些花样,比如动态UI,3D UI可以在渲染层的vertexshader或者pixleshader上做文章,可以自己给UI写一些特殊shader。
【3】想要优化UI,也是在渲染层做优化,不过unreal的UI已经自带batch,剔除等优化操作了。
参考资料:
typedef TArray<FSlateVertex, FSlateStatTrackingMemoryAllocator<FRenderingBufferStatTracker>> FSlateVertexArray;
typedef TArray<SlateIndex, FSlateStatTrackingMemoryAllocator<FRenderingBufferStatTracker>> FSlateIndexArray;
typedef TArray<FSlateDrawElement, FSlateStatTrackingMemoryAllocator<FDrawElementStatTracker>> FSlateDrawElementArray;