虚幻4 Task的创建和执行

来源:互联网 发布:无锡胡埭加工中心编程 编辑:程序博客网 时间:2024/05/22 03:08

今天看到把渲染人物放到人物列表的函数。

Engine\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp


// API inherited from FTaskGraphInterface/**  *Function to queue a task, called from a FBaseGraphTask *@paramTask; the task to queue *@paramThreadToExecuteOn; Either a named thread for a threadlocked task or ENamedThreads::AnyThread for a task that is to run on a worker thread *@paramCurrentThreadIfKnown; This should be the current thread if it is known, or otherwise use ENamedThreads::AnyThread and the current thread will be determined.**/virtual void QueueTask(FBaseGraphTask* Task, ENamedThreads::Type ThreadToExecuteOn, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread) override{TASKGRAPH_SCOPE_CYCLE_COUNTER(2, STAT_TaskGraph_QueueTask);TestRandomizedThreads();checkThreadGraph(NextUnnamedThreadMod);if (ENamedThreads::GetThreadIndex(CurrentThreadIfKnown) == ENamedThreads::AnyThread){ CurrentThreadIfKnown = GetCurrentThread();}else{CurrentThreadIfKnown = ENamedThreads::GetThreadIndex(CurrentThreadIfKnown);checkThreadGraph(CurrentThreadIfKnown == GetCurrentThread());}if (ENamedThreads::GetThreadIndex(ThreadToExecuteOn) == ENamedThreads::AnyThread){TASKGRAPH_SCOPE_CYCLE_COUNTER(3, STAT_TaskGraph_QueueTask_AnyThread);if (FPlatformProcess::SupportsMultithreading()){{TASKGRAPH_SCOPE_CYCLE_COUNTER(4, STAT_TaskGraph_QueueTask_IncomingAnyThreadTasks_Push);if (ENamedThreads::GetPriority(Task->ThreadToExecuteOn)){IncomingAnyThreadTasksHiPri.Push(Task);}else{IncomingAnyThreadTasks.Push(Task);}}FTaskThread* TempTarget;{TASKGRAPH_SCOPE_CYCLE_COUNTER(5, STAT_TaskGraph_QueueTask_StalledUnnamedThreads_Pop);TempTarget = StalledUnnamedThreads.Pop(); //@todo it is possible that a thread is in the process of stalling and we just missed it, non-fatal, but we could lose a whole task of potential parallelism.}if (TempTarget){ThreadToExecuteOn = TempTarget->GetThreadId();}else{ThreadToExecuteOn = ENamedThreads::Type((uint32(NextUnnamedThreadForTaskFromUnknownThread.Increment()) % uint32(NextUnnamedThreadMod)) + NumNamedThreads);}FTaskThread* Target = &Thread(ThreadToExecuteOn);if (ThreadToExecuteOn != CurrentThreadIfKnown){Target->EnqueueFromOtherThread(0, WakeUpBaseGraphTask);}return;}else{ThreadToExecuteOn = ENamedThreads::GameThread;}}{int32 QueueToExecuteOn = ENamedThreads::GetQueueIndex(ThreadToExecuteOn);ThreadToExecuteOn = ENamedThreads::GetThreadIndex(ThreadToExecuteOn);FTaskThread* Target = &Thread(ThreadToExecuteOn);if (ThreadToExecuteOn == CurrentThreadIfKnown){Target->EnqueueFromThisThread(QueueToExecuteOn, Task);}else{Target->EnqueueFromOtherThread(QueueToExecuteOn, Task);}}}

这个文件里还有执行一个Task的函数。

/**  *Process tasks until idle. May block if bAllowStall is true *@param QueueIndex, Queue to process tasks from *@param bAllowStall,  if true, the thread will block on the stall event when it runs out of tasks. **/void ProcessTasks(int32 QueueIndex, bool bAllowStall){TStatId StallStatId;bool bCountAsStall = false;#if STATSTStatId StatName;FCycleCounter ProcessingTasks;if (ThreadId == ENamedThreads::GameThread){StatName = GET_STATID(STAT_TaskGraph_GameTasks);StallStatId = GET_STATID(STAT_TaskGraph_GameStalls);bCountAsStall = true;}else if (ThreadId == ENamedThreads::RenderThread){if (QueueIndex > 0){StallStatId = GET_STATID(STAT_TaskGraph_RenderStalls);bCountAsStall = true;}// else StatName = none, we need to let the scope empty so that the render thread submits tasks in a timely manner. }else if (ThreadId != ENamedThreads::StatsThread){StatName = GET_STATID(STAT_TaskGraph_OtherTasks);StallStatId = GET_STATID(STAT_TaskGraph_OtherStalls);bCountAsStall = true;}bool bTasksOpen = false;#endifcheck(!Queue(QueueIndex).RecursionGuard.GetValue());Queue(QueueIndex).RecursionGuard.Increment();while (1){FBaseGraphTask* Task = NULL;Task = Queue(QueueIndex).PrivateQueueHiPri.Dequeue();if (!Task){if (!bAllowsStealsFromMe && Queue(QueueIndex).OutstandingHiPriTasks.GetValue()){Queue(QueueIndex).IncomingQueue.PopAll(NewTasks);if (NewTasks.Num()){for (int32 Index = NewTasks.Num() - 1; Index >= 0 ; Index--) // reverse the order since PopAll is implicitly backwards{FBaseGraphTask* NewTask = NewTasks[Index];if (ENamedThreads::GetPriority(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){if (!bAllowsStealsFromMe){// no steals so we will take all of the items, and this also ensures orderingfor (int32 Count = SPIN_COUNT + 1; Count && !NewTasks.Num() ; Count--){Queue(QueueIndex).IncomingQueue.PopAll(NewTasks);}if (FPlatformProcess::SupportsMultithreading()){for (int32 Count = SLEEP_COUNT; Count && !NewTasks.Num() ; Count--){FPlatformProcess::Sleep(0.0f);Queue(QueueIndex).IncomingQueue.PopAll(NewTasks);}}if (!NewTasks.Num()){if (bAllowStall){#if STATSif(bTasksOpen){ProcessingTasks.Stop();bTasksOpen = false;}#endifif (Stall(QueueIndex, StallStatId, bCountAsStall)){Queue(QueueIndex).IncomingQueue.PopAll(NewTasks);}}}if (NewTasks.Num()){for (int32 Index = NewTasks.Num() - 1; Index >= 0 ; Index--) // reverse the order since PopAll is implicitly backwards{FBaseGraphTask* NewTask = NewTasks[Index];if (ENamedThreads::GetPriority(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();}}}else{// because of stealing, we are only going to take one itemfor (int32 Count = SPIN_COUNT + 1; !Task && Count ; Count--){Task = Queue(QueueIndex).IncomingQueue.PopIfNotClosed();if (!Task){Task = FindWork();}}if (FPlatformProcess::SupportsMultithreading()){for (int32 Count = SLEEP_COUNT; !Task && Count ; Count--){FPlatformProcess::Sleep(0.0f);Task = Queue(QueueIndex).IncomingQueue.PopIfNotClosed();if (!Task){Task = FindWork();}}}if (!Task){if (bAllowStall){#if STATSif(bTasksOpen){ProcessingTasks.Stop();bTasksOpen = false;}#endifif (Stall(QueueIndex, StallStatId, bCountAsStall)){Task = Queue(QueueIndex).IncomingQueue.PopIfNotClosed();}}}}}if (Task){TestRandomizedThreads();#if STATSif (!bTasksOpen && FThreadStats::IsCollectingData(StatName)){bTasksOpen = true;ProcessingTasks.Start(StatName);}#endifif (Task != WakeUpBaseGraphTask){Task->Execute(NewTasks, ENamedThreads::Type(ThreadId | (QueueIndex << ENamedThreads::QueueIndexShift)));}TestRandomizedThreads();}else{break;}}#if STATSif(bTasksOpen){ProcessingTasks.Stop();bTasksOpen = false;}#endifQueue(QueueIndex).RecursionGuard.Decrement();check(!Queue(QueueIndex).RecursionGuard.GetValue());}


维护了四个列表:

TLockFreePointerList<FBaseGraphTask>IncomingAnyThreadTasks;TLockFreePointerList<FBaseGraphTask>IncomingAnyThreadTasksHiPri;TLockFreePointerList<FBaseGraphTask>SortedAnyThreadTasks;TLockFreePointerList<FBaseGraphTask>SortedAnyThreadTasksHiPri;


还有与一个线程对应的一个一个结构体

/** Grouping of the data for an individual queue. **/struct FThreadTaskQueue{/** A non-threas safe queue for the thread locked tasks of a named thread **/FTaskQueuePrivateQueue;/** A non-threas safe queue for the thread locked tasks of a named thread. These are high priority.**/FTaskQueuePrivateQueueHiPri;/** Used to signal pending hi pri tasks. **/FThreadSafeCounterOutstandingHiPriTasks;/**  *For named threads, this is a queue of thread locked tasks coming from other threads. They are not stealable. *For unnamed thread this is the public queue, subject to stealing. *In either case this queue is closely related to the stall event. Other threads that reopen the incoming queue must trigger the stall event to allow the thread to run.**/TReopenableLockFreePointerList<FBaseGraphTask>IncomingQueue;/** Used to signal the thread to quit when idle. **/FThreadSafeCounterQuitWhenIdle;/** We need to disallow reentry of the processing loop **/FThreadSafeCounterRecursionGuard;/** Event that this thread blocks on when it runs out of work. **/FEvent*StallRestartEvent;FThreadTaskQueue(): StallRestartEvent(FPlatformProcess::GetSynchEventFromPool(true)){}~FThreadTaskQueue(){FPlatformProcess::ReturnSynchEventToPool( StallRestartEvent );StallRestartEvent = nullptr;}};

有他的一个数组

/** Array of queues, only the first one is used for unnamed threads. **/FThreadTaskQueue Queues[ENamedThreads::NumQueues];



前两个是人物刚到的列表,后两个是被Sort之后的列表。

取人物执行的话只从Sort列表里面取,HiPri代表高优先级。


下面是查找任务的代码,执行人物要先查找。

/**  *Attempt to steal some work from another thread. *@paramThreadInNeed; Id of the thread requesting work. *@return Task that was stolen if any was found.**/FBaseGraphTask* FindWork(ENamedThreads::Type ThreadInNeed){TestRandomizedThreads();{FBaseGraphTask* Task = SortedAnyThreadTasksHiPri.Pop();if (Task){return Task;}}if (!IncomingAnyThreadTasksHiPri.IsEmpty()){do{FScopeLock ScopeLock(&CriticalSectionForSortingIncomingAnyThreadTasksHiPri);if (!IncomingAnyThreadTasksHiPri.IsEmpty() && SortedAnyThreadTasksHiPri.IsEmpty()){static TArray<FBaseGraphTask*> NewTasks;NewTasks.Reset();IncomingAnyThreadTasksHiPri.PopAll(NewTasks);check(NewTasks.Num());if (NewTasks.Num() > 1){TLockFreePointerList<FBaseGraphTask> TempSortedAnyThreadTasks;for (int32 Index = 0 ; Index < NewTasks.Num() - 1; Index++) // we are going to take the last one for ourselves{TempSortedAnyThreadTasks.Push(NewTasks[Index]);}verify(SortedAnyThreadTasksHiPri.ReplaceListIfEmpty(TempSortedAnyThreadTasks));}return NewTasks[NewTasks.Num() - 1];}{FBaseGraphTask* Task = SortedAnyThreadTasksHiPri.Pop();if (Task){return Task;}}} while (!IncomingAnyThreadTasksHiPri.IsEmpty() || !SortedAnyThreadTasksHiPri.IsEmpty());}{FBaseGraphTask* Task = SortedAnyThreadTasks.Pop();if (Task){return Task;}}do{FScopeLock ScopeLock(&CriticalSectionForSortingIncomingAnyThreadTasks);if (!IncomingAnyThreadTasks.IsEmpty() && SortedAnyThreadTasks.IsEmpty()){static TArray<FBaseGraphTask*> NewTasks;NewTasks.Reset();IncomingAnyThreadTasks.PopAll(NewTasks);check(NewTasks.Num());if (NewTasks.Num() > 1){TLockFreePointerList<FBaseGraphTask> TempSortedAnyThreadTasks;for (int32 Index = 0 ; Index < NewTasks.Num() - 1; Index++) // we are going to take the last one for ourselves{TempSortedAnyThreadTasks.Push(NewTasks[Index]);}verify(SortedAnyThreadTasks.ReplaceListIfEmpty(TempSortedAnyThreadTasks));}return NewTasks[NewTasks.Num() - 1];}{FBaseGraphTask* Task = SortedAnyThreadTasks.Pop();if (Task){return Task;}}} while (!IncomingAnyThreadTasks.IsEmpty() || !SortedAnyThreadTasks.IsEmpty());// this can be called before my constructor is finishedfor (int32 Pass = 0; Pass < 2; Pass++){for (int32 Test = ThreadInNeed - 1; Test >= NumNamedThreads; Test--){if (Pass || !Thread(Test).IsProbablyStalled()){FBaseGraphTask* Task = Thread(Test).RequestSteal();if (Task){return Task;}}}for (int32 Test = NumThreads - 1; Test > ThreadInNeed; Test--){if (Pass || !Thread(Test).IsProbablyStalled()){FBaseGraphTask* Task = Thread(Test).RequestSteal();if (Task){return Task;}}}}return NULL;}



关于渲染的代码那一句:

TGraphTask<EURCMacro_CleanupEditToolRenderData>::CreateTask().ConstructAndDispatchWhenReady(this);   

创建了一个GraphTask,然后创建了之后就把这个任务分发出去了。






渲染线程开始于:

D:\Projects\Program\Engine\Engine\Source\Runtime\RenderCore\Private\RenderingThread.cpp

virtual uint32 Run(void) override{FPlatformProcess::SetupGameOrRenderThread(true);#if PLATFORM_WINDOWSif ( !FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash ){#if !PLATFORM_SEH_EXCEPTIONS_DISABLED__try#endif{RenderingThreadMain( TaskGraphBoundSyncEvent );}#if !PLATFORM_SEH_EXCEPTIONS_DISABLED__except( ReportCrash( GetExceptionInformation() ) ){GRenderingThreadError = GErrorHist;// Use a memory barrier to ensure that the game thread sees the write to GRenderingThreadError before// the write to GIsRenderingThreadHealthy.FPlatformMisc::MemoryBarrier();GIsRenderingThreadHealthy = false;}#endif}else#endif // PLATFORM_WINDOWS{<span style="color:#ff0000;">RenderingThreadMain( TaskGraphBoundSyncEvent );</span>}return 0;}


/** The rendering thread main loop */void RenderingThreadMain( FEvent* TaskGraphBoundSyncEvent ){ENamedThreads::RenderThread = ENamedThreads::Type(ENamedThreads::ActualRenderingThread);ENamedThreads::RenderThread_Local = ENamedThreads::Type(ENamedThreads::ActualRenderingThread_Local);FTaskGraphInterface::Get().AttachToThread(ENamedThreads::RenderThread);FPlatformMisc::MemoryBarrier();// Inform main thread that the render thread has been attached to the taskgraph and is ready to receive tasksif( TaskGraphBoundSyncEvent != NULL ){TaskGraphBoundSyncEvent->Trigger();}// set the thread back to real time modeFPlatformProcess::SetRealTimeMode();#if STATSif (FThreadStats::WillEverCollectData()){FThreadStats::ExplicitFlush(); // flush the stats and set update the scope so we don't flush again until a frame update, this helps prevent fragmentation}#endifcheck(GIsThreadedRendering);<span style="color:#ff0000;">FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RenderThread);</span>FPlatformMisc::MemoryBarrier();check(!GIsThreadedRendering);ENamedThreads::RenderThread = ENamedThreads::GameThread;ENamedThreads::RenderThread_Local = ENamedThreads::GameThread_Local;FPlatformMisc::MemoryBarrier();}



Engine\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp

/** Used for named threads to start processing tasks until the thread is idle and RequestQuit has been called. **/void ProcessTasksUntilQuit(int32 QueueIndex){Queue(QueueIndex).QuitWhenIdle.Reset();while (Queue(QueueIndex).QuitWhenIdle.GetValue() == 0){<span style="color:#ff0000;">ProcessTasks(QueueIndex, true);</span>// @Hack - quit now when running with only one thread.if (!FPlatformProcess::SupportsMultithreading()){break;}}}


<span style="color: rgb(255, 0, 0);">FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RenderThread)</span>

可以看到传入了一个渲染线程的ID,任务队列也有一个数组,保存着一大堆的任务队列,而每个线程只能操作自己对应的任务队列。

任务队列的数组是:

FThreadTaskQueue Queues[ENamedThreads::NumQueues];




好了,到现在为止可以基本捋顺了渲染的过程,顺带着也捋了捋Task的执行过程。





0 0
原创粉丝点击