天天看點

UE4 渲染線程執行任務的流程

先列出相關的類: 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,如何給到渲染線程的,還需探索,待續未完。。。

繼續閱讀