先列出相關的類: FTaskGraphInterface: 虛基類,提供了一組管理和操作線程的接口. class FTaskGraphImplementation : public FTaskGraphInterface 該子類實作了基類的接口,在構造函數裡建立了N個FRunnableThread和FTaskThreadBase. FTaskThreadBase:繼承自 FRunnable(定義了線程的執行函數),故它也可以作為線程的執行體,它還定義了諸多與處理任務相關的接口 class FNamedTaskThread : public FTaskThreadBase 該類維護了任務隊列,主線程發送的渲染任務最終就是進到這個隊列;實作了命名線程處理任務的接口,渲染線程最終也會調用這裡的接口來處理任務。
FTaskGraphImplementation(int32)
{
bCreatedHiPriorityThreads = !!ENamedThreads::bHasHighPriorityThreads;
bCreatedBackgroundPriorityThreads = !!ENamedThreads::bHasBackgroundThreads;
int32 MaxTaskThreads = MAX_THREADS;
int32 NumTaskThreads = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
MaxTaskThreads = 1;
NumTaskThreads = 1;
LastExternalThread = (ENamedThreads::Type)(ENamedThreads::ActualRenderingThread - 1);
//當啟動渲染線程時,NumNamedThreads 一般為2,第二個代表渲染線程.
NumNamedThreads = LastExternalThread + 1;
NumTaskThreadSets = 1 + bCreatedHiPriorityThreads + bCreatedBackgroundPriorityThreads;
NumThreads = FMath::Max<int32>(FMath::Min<int32>(NumTaskThreads * NumTaskThreadSets + NumNamedThreads, MAX_THREADS), NumNamedThreads + 1)
NumThreads = FMath::Min(NumThreads, NumNamedThreads + NumTaskThreads * NumTaskThreadSets);
//建立線程局部存儲
PerThreadIDTLSSlot = FPlatformTLS::AllocTlsSlot();
for (int32 ThreadIndex = 0; ThreadIndex < NumThreads; ThreadIndex++)
{
bool bAnyTaskThread = ThreadIndex >= NumNamedThreads;
if (bAnyTaskThread)
{
WorkerThreads[ThreadIndex].TaskGraphWorker = new FTaskThreadAnyThread(ThreadIndexToPriorityIndex(ThreadIndex));
}
else
{
//建立命名線程的Runnable, ThreadIndex = 1 是渲染線程的Runnable.
WorkerThreads[ThreadIndex].TaskGraphWorker = new FNamedTaskThread;
}
//設定線程局部存儲Id,用于存儲WorkerThreads[ThreadIndex],把PerThreadIDTLSSlot和WorkerThreads[ThreadIndex]關聯起來
WorkerThreads[ThreadIndex].TaskGraphWorker->Setup(ENamedThreads::Type(ThreadIndex), PerThreadIDTLSSlot, &WorkerThreads[ThreadIndex]);
}
//從匿名線程開始
for (int32 ThreadIndex = LastExternalThread + 1; ThreadIndex < NumThreads; ThreadIndex++)
{
uint32 StackSize = 256 * 1024;
//建立匿名的線程. 渲染線程不在此建立,因為在其他地方單獨建立了
WorkerThreads[ThreadIndex].RunnableThread = FRunnableThread::Create(&Thread(ThreadIndex), *Name, StackSize, ThreadPri, FPlatformAffinity::GetTaskGraphThreadMask());
WorkerThreads[ThreadIndex].bAttached = true;
}
}
下面介紹 渲染線程的建立:
//建立渲染線程,在FEngineLoop::PreInit中調用了該函數
void StartRenderingThread()
{
//建立一個Runnable,定義了線程執行的函數run
GRenderingThreadRunnable = new FRenderingThread();
//建立平台相關的線程
GRenderingThread = FRunnableThread::Create( GRenderingThreadRunnable, *BuildRenderingThreadName( ThreadCount ), 0, TPri_Normal, FPlatformAffinity::GetRenderingThreadMask());
//調用wait後所線上程挂起,即主線程挂起了
((FRenderingThread*)GRenderingThreadRunnable)->TaskGraphBoundSyncEvent->Wait();
}
GRenderingThreadRunnable.Run最終會調用 RenderingThreadMain,這個函數才是渲染線程的真正入口.
/** The rendering thread main loop */
void RenderingThreadMain( FEvent* TaskGraphBoundSyncEvent )
{
//把ENamedThreads::RenderThread對應的線程對象與渲染線程關聯起來
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::RenderThread);
//喚醒主線程
// Inform main thread that the render thread has been attached to the taskgraph and is ready to receive tasks
TaskGraphBoundSyncEvent->Trigger();
//開始處理任務了
FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RenderThread);
}
FTaskGraphInterface::Get()最終得到一個FTaskGraphImplementation執行個體
virtual void ProcessThreadUntilRequestReturn(ENamedThreads::Type CurrentThread) final override
{
//QueueIndex 表示隊列的索引,一般為0
int32 QueueIndex = ENamedThreads::GetQueueIndex(CurrentThread);
//Thread(CurrentThread)得到一個FTaskThreadBase子類對象
Thread(CurrentThread).ProcessTasksUntilQuit(QueueIndex);
}
這裡Thread(CurrentThread)擷取到的是WorkerThreads[CurrentThread].TaskGraphWorker,
而這裡的TaskGraphWorker是一個 FNamedTaskThread類的執行個體,是以直接到FNamedTaskThread裡找ProcessTasksUntilQuit接口
virtual void ProcessTasksUntilQuit(int32 QueueIndex) override
{
Queue(QueueIndex).QuitWhenIdle.Reset();
while (Queue(QueueIndex).QuitWhenIdle.GetValue() == 0)
{
//核心接口,處理衆多任務
ProcessTasksNamedThread(QueueIndex, true);
}
}
ProcessTasksNamedThread 先從高優先級的隊列取task, 沒有則把 IncomingQueue隊列的task插到PrivateQueueHiPri或者PrivateQueue隊列裡,之後再從PrivateQueueHiPri或者PrivateQueue隊列取task。 值得注意的是, IncomingQueue隊列裡的task是從其他線程發送過來的,而PrivateQueueHiPri和PrivateQueue隊列裡的task是在本線程發送過來的
void ProcessTasksNamedThread(int32 QueueIndex, bool bAllowStall)
{
while (1)
{
FBaseGraphTask* Task = NULL;
//先從高優先級的PrivateQueueHiPri隊列取task
Task = Queue(QueueIndex).PrivateQueueHiPri.Dequeue();
if (!Task)
{
if (Queue(QueueIndex).OutstandingHiPriTasks.GetValue())
{ //把IncomingQueue隊列的task插到PrivateQueueHiPri或者PrivateQueue隊列中
Queue(QueueIndex).IncomingQueue.PopAll(NewTasks);
if (NewTasks.Num())
{
for (int32 Index = NewTasks.Num() - 1; Index >= 0 ; Index--)
{
FBaseGraphTask* NewTask = NewTasks[Index];
if (ENamedThreads::GetTaskPriority(NewTask->ThreadToExecuteOn))
{
Queue(QueueIndex).PrivateQueueHiPri.Enqueue(NewTask);
Queue(QueueIndex).OutstandingHiPriTasks.Decrement();
}
else
{
Queue(QueueIndex).PrivateQueue.Enqueue(NewTask);
}
}
NewTasks.Reset();
Task = Queue(QueueIndex).PrivateQueueHiPri.Dequeue();
}
}
if (!Task)
{
Task = Queue(QueueIndex).PrivateQueue.Dequeue();
}
}
//這裡終于執行了任務
if (Task)
Task->Execute(NewTasks, ENamedThreads::Type(ThreadId | (QueueIndex << ENamedThreads::QueueIndexShift)));
else
break;
}
}
在這裡 Task->Execute(...)最終會調用到 TGraphTask類的 ExecuteTask接口
virtual void ExecuteTask(TArray<FBaseGraphTask*>& NewTasks, ENamedThreads::Type CurrentThread)
{
TTask& Task = *(TTask*)&TaskStorage;
//最終具體任務的DoTask接口得以執行
Task.DoTask(CurrentThread, Subsequents);
Task.~TTask();
}
在介紹 ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER 宏的時候便提到了DoTask接口,該DoTask接口就是在上面的代碼中被執行的. 渲染線程初始化及執行task的流程介紹完了,但是引擎是如何産生task,如何給到渲染線程的,還需探索,待續未完。。。