UE4渲染任务的产生及入队

来源:互联网 发布:淘宝达人在哪里申请 编辑:程序博客网 时间:2024/06/05 10:40
渲染任务是如何产生并压入到渲染队列的呢
还记得ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER宏吗,该宏的作用就是生成渲染任务并压入渲染队列。
这是笔者知道的一种方式,应该还有其他方式。

ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER宏展开后会得到下面的核心代码,
它的作用是把创建任务并压入渲染队列:
TGraphTask<EURCMacroBeginDrawingCommand>::CreateTask().ConstructAndDispatchWhenReady(this);

先介绍TGraphTask:它是一个模板类,继承自FBaseGraphTask,里面维护了具体的任务,作用相当于具体任务的托管.
CreateTask() 创建了TGraphTask对象,同时也创建了具体的任务(EURCMacroBeginDrawingCommand).
下面展示的代码都是围绕渲染线程执行任务展开的,并且只是展示核心的代码。

看看ConstructAndDispatchWhenReady里做了什么
FGraphEventRef ConstructAndDispatchWhenReady(T&&... Args){new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);//这个Owner就是TGraphTask对象,这里Prerequisites和CurrentThreadIfKnown都为nullreturn Owner->Setup(Prerequisites, CurrentThreadIfKnown);}
Setup中调用了SetupPrereqs
void SetupPrereqs(const FGraphEventArray* Prerequisites, ENamedThreads::Type CurrentThreadIfKnown, bool bUnlock){//这个Task就是具体的任务,对应上面的EURCMacroBeginDrawingCommand实例TTask& Task = *(TTask*)&TaskStorage;//设置任务期望在哪个线程被执行,GetDesiredThread这里获取的值是ENamedThreads::RenderThread,即渲染线程!SetThreadToExecuteOn(Task.GetDesiredThread());int32 AlreadyCompletedPrerequisites = 0;PrerequisitesComplete(CurrentThreadIfKnown, AlreadyCompletedPrerequisites, bUnlock);}void PrerequisitesComplete(ENamedThreads::Type CurrentThread, int32 NumAlreadyFinishedPrequistes, bool bUnlock = true){QueueTask(CurrentThread);}

QueueTask里也很简单
void QueueTask(ENamedThreads::Type CurrentThreadIfKnown){//这三个实参分别为:入队的task,task期待被执行的线程Id(这里为渲染线程),当前的线程(这里为AnyThread)FTaskGraphInterface::Get().QueueTask(this, ThreadToExecuteOn, CurrentThreadIfKnown);}
FTaskGraphInterface::Get()得到子类FTaskGraphImplementation
可见最终会调用到FTaskGraphImplementation的QueueTask(),该接口的作用是把任务放入正确的队列
virtual void QueueTask(FBaseGraphTask* Task, ENamedThreads::Type ThreadToExecuteOn, ENamedThreads::Type InCurrentThreadIfKnown = ENamedThreads::AnyThread) final override{ENamedThreads::Type CurrentThreadIfKnown;if (ENamedThreads::GetThreadIndex(InCurrentThreadIfKnown) == ENamedThreads::AnyThread){//在这里会执行这一步,GetCurrentThread得到的是主线程CurrentThreadIfKnown = GetCurrentThread();}else{CurrentThreadIfKnown = ENamedThreads::GetThreadIndex(InCurrentThreadIfKnown);checkThreadGraph(CurrentThreadIfKnown == ENamedThreads::GetThreadIndex(GetCurrentThread()));}{int32 QueueToExecuteOn = ENamedThreads::GetQueueIndex(ThreadToExecuteOn);ThreadToExecuteOn = ENamedThreads::GetThreadIndex(ThreadToExecuteOn);FTaskThreadBase* Target = &Thread(ThreadToExecuteOn);//这里ThreadToExecuteOn是渲染线程,CurrentThreadIfKnown是主线程if (ThreadToExecuteOn == ENamedThreads::GetThreadIndex(CurrentThreadIfKnown)){//压入和期待被执行的线程相同Target->EnqueueFromThisThread(QueueToExecuteOn, Task);}else{//压入和期待被执行的线程不同,所以执行这里Target->EnqueueFromOtherThread(QueueToExecuteOn, Task);}}}

EnqueueFromOtherThread里主要是把task压入渲染线程的IncomingQueue队列里,IncomingQueue的优先级最低
还有两个队列,分别是PrivateQueueHiPri和PrivateQueue,PrivateQueueHiPri里的任务会优先执行
virtual bool EnqueueFromOtherThread(int32 QueueIndex, FBaseGraphTask* Task) override{//把任务放入IncomingQueue队列里       //从这里可以看出IncomingQueue队列存储的是压入线程不同于期待被执行的线程的taskQueue(QueueIndex).IncomingQueue.ReopenIfClosedAndPush(Task);}
至此,结合前面几篇博客,UE4的整个渲染系统框架的来龙去脉大致摸清楚了!
总结:
在game线程中每帧通过逻辑运算会产生很多渲染task, 其中一种方式是调用ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER,然后发送到渲染线程维护的队列里。之后,渲染线程不断调用ProcessTasksNamedThread处理任务,这里的任务大都是执行渲染指令。通过这种方式达到了渲染和逻辑运算分离并同时运行的目的(并不是同时运行同一帧,而是渲染线程跑第N帧,game线程跑N+1帧),这也就是我们常说的多线程渲染。以前的引擎架构大都数单线程的,先通过游戏逻辑算出需要渲染的数据,再把这些数据封装成渲染指令,之后进行渲染。这种架构优点是简单清晰,各种流程都易于控制;但缺点也很明显,无法利用cpu的多个核。而现在的手机,电脑cpu的核数不断在增加,主频却比以前低了不少,充分利用这些核是一种提高游戏效率不可或缺的途径,所以,多线程渲染必将成为游戏引擎的主流方式。











0 0
原创粉丝点击