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(FMath::Min(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& NewTasks, ENamedThreads::Type CurrentThread)
{
    TTask& Task = *(TTask*)&TaskStorage; 
    //最终具体任务的DoTask接口得以执行
    Task.DoTask(CurrentThread, Subsequents);
    Task.~TTask();
}

在介绍 ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER 宏的时候便提到了DoTask接口,该DoTask接口就是在上面的代码中被执行的.
渲染线程初始化及执行task的流程介绍完了,但是引擎是如何产生task,如何给到渲染线程的,还需探索,待续未完。。。

你可能感兴趣的:(Unreal,Engine,4)