UE4隨筆——渲染線程使用

通常情況下UE4的渲染操作是在渲染線程下執(zhí)行的混蔼,這樣無(wú)疑可以提高系統(tǒng)的效率以及電腦CPU的利用率颓屑,當(dāng)然在某些情況下也可能在game線程下進(jìn)行渲染操作诉瓦,但一般都是渲染線程會(huì)比game線程晚一幀執(zhí)行十绑。下面將對(duì)渲染線程的調(diào)用流程進(jìn)行一個(gè)簡(jiǎn)單的介紹呼奢。

UE4隨筆

1 線程創(chuàng)建

將代碼定位到

int32 FEngineLoop::PreInit(const TCHAR* CmdLine)

其中有一行代碼為

// initialize task graph sub-system with potential multiple threads
   FTaskGraphInterface::Startup(FPlatformMisc::NumberOfCores());

這行代碼就是在引擎加載階段對(duì)線程進(jìn)行創(chuàng)建,具體過(guò)程請(qǐng)繼續(xù)往下看单料,

// Statics in FTaskGraphInterface

void FTaskGraphInterface::Startup(int32 NumThreads)
{
    // TaskGraphImplementationSingleton is actually set in the constructor because find work will be called before this returns.
    new FTaskGraphImplementation(NumThreads); 
}

在上述代碼中FTaskGraphInterface是一個(gè)單件埋凯,而FTaskGraphImplementation是FTaskGraphInterface的一個(gè)實(shí)現(xiàn)類,也就是創(chuàng)建這個(gè)單件扫尖,下面進(jìn)入FTaskGraphImplementation來(lái)看下這個(gè)單件實(shí)現(xiàn)類白对。

/** 
     *  Constructor - initializes the data structures, sets the singleton pointer and creates the internal threads.
     *  @param InNumThreads; total number of threads in the system, including named threads, unnamed threads, internal threads and external threads. Must be at least 1 + the number of named threads.
    **/
    FTaskGraphImplementation(int32)
    {
        bCreatedHiPriorityThreads = !!ENamedThreads::bHasHighPriorityThreads;
        bCreatedBackgroundPriorityThreads = !!ENamedThreads::bHasBackgroundThreads;

        int32 MaxTaskThreads = MAX_THREADS;
        int32 NumTaskThreads = FPlatformMisc::NumberOfWorkerThreadsToSpawn();

        // if we don't want any performance-based threads, then force the task graph to not create any worker threads, and run in game thread
        if (!FPlatformProcess::SupportsMultithreading())
        {
            // this is the logic that used to be spread over a couple of places, that will make the rest of this function disable a worker thread
            // @todo: it could probably be made simpler/clearer
            // this - 1 tells the below code there is no rendering thread
            MaxTaskThreads = 1;
            NumTaskThreads = 1;
            LastExternalThread = (ENamedThreads::Type)(ENamedThreads::ActualRenderingThread - 1);
            bCreatedHiPriorityThreads = false;
            bCreatedBackgroundPriorityThreads = false;
            ENamedThreads::bHasBackgroundThreads = 0;
            ENamedThreads::bHasHighPriorityThreads = 0;
        }
        else
        {
            LastExternalThread = ENamedThreads::ActualRenderingThread;
        }
        
        NumNamedThreads = LastExternalThread + 1;

        NumTaskThreadSets = 1 + bCreatedHiPriorityThreads + bCreatedBackgroundPriorityThreads;

        // if we don't have enough threads to allow all of the sets asked for, then we can't create what was asked for.
        check(NumTaskThreadSets == 1 || FMath::Min<int32>(NumTaskThreads * NumTaskThreadSets + NumNamedThreads, MAX_THREADS) == NumTaskThreads * NumTaskThreadSets + NumNamedThreads);
        NumThreads = FMath::Max<int32>(FMath::Min<int32>(NumTaskThreads * NumTaskThreadSets + NumNamedThreads, MAX_THREADS), NumNamedThreads + 1);

        // Cap number of extra threads to the platform worker thread count
        // if we don't have enough threads to allow all of the sets asked for, then we can't create what was asked for.
        check(NumTaskThreadSets == 1 || FMath::Min(NumThreads, NumNamedThreads + NumTaskThreads * NumTaskThreadSets) == NumThreads);
        NumThreads = FMath::Min(NumThreads, NumNamedThreads + NumTaskThreads * NumTaskThreadSets);

        NumTaskThreadsPerSet = (NumThreads - NumNamedThreads) / NumTaskThreadSets;
        check((NumThreads - NumNamedThreads) % NumTaskThreadSets == 0); // should be equal numbers of threads per priority set

        UE_LOG(LogTaskGraph, Log, TEXT("Started task graph with %d named threads and %d total threads with %d sets of task threads."), NumNamedThreads, NumThreads, NumTaskThreadSets);
        check(NumThreads - NumNamedThreads >= 1);  // need at least one pure worker thread
        check(NumThreads <= MAX_THREADS);
        check(!ReentrancyCheck.GetValue()); // reentrant?
        ReentrancyCheck.Increment(); // just checking for reentrancy
        PerThreadIDTLSSlot = FPlatformTLS::AllocTlsSlot();

        for (int32 ThreadIndex = 0; ThreadIndex < NumThreads; ThreadIndex++)
        {
            check(!WorkerThreads[ThreadIndex].bAttached); // reentrant?
            bool bAnyTaskThread = ThreadIndex >= NumNamedThreads;
            if (bAnyTaskThread)
            {
                WorkerThreads[ThreadIndex].TaskGraphWorker = new FTaskThreadAnyThread(ThreadIndexToPriorityIndex(ThreadIndex));
            }
            else
            {
                WorkerThreads[ThreadIndex].TaskGraphWorker = new FNamedTaskThread;
            }
            WorkerThreads[ThreadIndex].TaskGraphWorker->Setup(ENamedThreads::Type(ThreadIndex), PerThreadIDTLSSlot, &WorkerThreads[ThreadIndex]);
        }

        TaskGraphImplementationSingleton = this; // now reentrancy is ok

        for (int32 ThreadIndex = LastExternalThread + 1; ThreadIndex < NumThreads; ThreadIndex++)
        {
            FString Name;
            int32 Priority = ThreadIndexToPriorityIndex(ThreadIndex);
            EThreadPriority ThreadPri;
            uint64 Affinity = FPlatformAffinity::GetTaskGraphThreadMask();
            if (Priority == 1)
            {
                Name = FString::Printf(TEXT("TaskGraphThreadHP %d"), ThreadIndex - (LastExternalThread + 1));
                ThreadPri = TPri_SlightlyBelowNormal; // we want even hi priority tasks below the normal threads
            }
            else if (Priority == 2)
            {
                Name = FString::Printf(TEXT("TaskGraphThreadBP %d"), ThreadIndex - (LastExternalThread + 1));
                ThreadPri = TPri_Lowest;
                // If the platform defines FPlatformAffinity::GetTaskGraphBackgroundTaskMask then use it
                if ( FPlatformAffinity::GetTaskGraphBackgroundTaskMask() != 0xFFFFFFFFFFFFFFFF )
                {
                    Affinity = FPlatformAffinity::GetTaskGraphBackgroundTaskMask();
                }
            }
            else
            {
                Name = FString::Printf(TEXT("TaskGraphThreadNP %d"), ThreadIndex - (LastExternalThread + 1));
                ThreadPri = TPri_BelowNormal; // we want normal tasks below normal threads like the game thread
            }
#if WITH_EDITOR
            uint32 StackSize = 1024 * 1024;
#elif ( UE_BUILD_SHIPPING || UE_BUILD_TEST )
            uint32 StackSize = 384 * 1024;
#else
            uint32 StackSize = 512 * 1024;
#endif
            WorkerThreads[ThreadIndex].RunnableThread = FRunnableThread::Create(&Thread(ThreadIndex), *Name, StackSize, ThreadPri, Affinity); // these are below normal threads so that they sleep when the named threads are active
            WorkerThreads[ThreadIndex].bAttached = true;
        }
    }

縱觀以上代碼,說(shuō)白了就是計(jì)算需要?jiǎng)?chuàng)建線程個(gè)數(shù)换怖,并且創(chuàng)建的過(guò)程甩恼,線程分為FTaskThreadAnyThread和FNamedTaskThread兩種,創(chuàng)建的代碼如下:

            if (bAnyTaskThread)
            {
                WorkerThreads[ThreadIndex].TaskGraphWorker = new FTaskThreadAnyThread(ThreadIndexToPriorityIndex(ThreadIndex));
            }
            else
            {
                WorkerThreads[ThreadIndex].TaskGraphWorker = new FNamedTaskThread;
            }
            WorkerThreads[ThreadIndex].TaskGraphWorker->Setup(ENamedThreads::Type(ThreadIndex), PerThreadIDTLSSlot, &WorkerThreads[ThreadIndex]);

創(chuàng)建好的任務(wù)線程保存在WorkerThreads[]中沉颂,而且這些任務(wù)線程均繼承于FRunnable接口条摸,該接口是UE4自定義的線程,不過(guò)是一個(gè)假的線程铸屉,線程真正的創(chuàng)建在以下代碼中執(zhí)行钉蒲。

WorkerThreads[ThreadIndex].RunnableThread = FRunnableThread::Create(&Thread(ThreadIndex), *Name, StackSize, ThreadPri, Affinity); // these are below normal threads so that they sleep when the named threads are active

以上線程的創(chuàng)建并未包括渲染線程,渲染線程的創(chuàng)建如下:

void StartRenderingThread()
{
    static uint32 ThreadCount = 0;
    check(!GIsThreadedRendering && GUseThreadedRendering);

    check(!GRHIThread_InternalUseOnly && !GIsRunningRHIInSeparateThread_InternalUseOnly && !GIsRunningRHIInDedicatedThread_InternalUseOnly && !GIsRunningRHIInTaskThread_InternalUseOnly);

    if (GUseRHIThread_InternalUseOnly)
    {
        FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);        
        if (!FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::RHIThread))
        {
            FRHIThread::Get().Start();
        }
        DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread"), STAT_WaitForRHIThread, STATGROUP_TaskGraphTasks);

        FGraphEventRef CompletionEvent = TGraphTask<FOwnershipOfRHIThreadTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(true, GET_STATID(STAT_WaitForRHIThread));
        QUICK_SCOPE_CYCLE_COUNTER(STAT_StartRenderingThread);
        FTaskGraphInterface::Get().WaitUntilTaskCompletes(CompletionEvent, ENamedThreads::GameThread_Local);
        GRHIThread_InternalUseOnly = FRHIThread::Get().Thread;
        check(GRHIThread_InternalUseOnly);
        GIsRunningRHIInDedicatedThread_InternalUseOnly = true;
        GIsRunningRHIInSeparateThread_InternalUseOnly = true;
        GRHIThreadId = GRHIThread_InternalUseOnly->GetThreadID();
        GRHICommandList.LatchBypass();
    }
    else if (GUseRHITaskThreads_InternalUseOnly)
    {
        GIsRunningRHIInSeparateThread_InternalUseOnly = true;
        GIsRunningRHIInTaskThread_InternalUseOnly = true;
    }

    // Turn on the threaded rendering flag.
    GIsThreadedRendering = true;

    // Create the rendering thread.
    GRenderingThreadRunnable = new FRenderingThread();

    GRenderingThread = FRunnableThread::Create(GRenderingThreadRunnable, *BuildRenderingThreadName(ThreadCount), 0, FPlatformAffinity::GetRenderingThreadPriority(), FPlatformAffinity::GetRenderingThreadMask());

    // Wait for render thread to have taskgraph bound before we dispatch any tasks for it.
    ((FRenderingThread*)GRenderingThreadRunnable)->TaskGraphBoundSyncEvent->Wait();

    // register
    IConsoleManager::Get().RegisterThreadPropagation(GRenderingThread->GetThreadID(), &FConsoleRenderThreadPropagation::GetSingleton());

    // ensure the thread has actually started and is idling
    FRenderCommandFence Fence;
    Fence.BeginFence();
    Fence.Wait();

    GRunRenderingThreadHeartbeat = true;
    // Create the rendering thread heartbeat
    GRenderingThreadRunnableHeartbeat = new FRenderingThreadTickHeartbeat();

    GRenderingThreadHeartbeat = FRunnableThread::Create(GRenderingThreadRunnableHeartbeat, *FString::Printf(TEXT("RTHeartBeat %d"), ThreadCount), 16 * 1024, TPri_AboveNormal, FPlatformAffinity::GetRTHeartBeatMask());

    ThreadCount++;
}

該過(guò)程也是在以下代碼中調(diào)用彻坛。

int32 FEngineLoop::PreInit(const TCHAR* CmdLine)

2 線程調(diào)用

渲染線程創(chuàng)建完成之后便可以對(duì)其進(jìn)行使用了顷啼,下面以FPrimitiveSceneProxy::SetSelection_GameThread為例進(jìn)行講解,完整代碼如下所示昌屉。

void FPrimitiveSceneProxy::SetSelection_GameThread(const bool bInParentSelected, const bool bInIndividuallySelected)
{
    check(IsInGameThread());

    // Enqueue a message to the rendering thread containing the interaction to add.
    ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
        SetNewSelection,
        FPrimitiveSceneProxy*,PrimitiveSceneProxy,this,
        const bool,bNewParentSelection,bInParentSelected,
        const bool,bNewIndividuallySelected,bInIndividuallySelected,
    {
        PrimitiveSceneProxy->SetSelection_RenderThread(bNewParentSelection,bNewIndividuallySelected);
    });
}

該段代碼的意思是在game線程下向渲染線程傳遞SetSelection_RenderThread指令钙蒙,該過(guò)程是通過(guò)ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER宏來(lái)實(shí)現(xiàn)的,展開(kāi)這個(gè)宏可以看到如下代碼怠益。

#define ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(TypeName,ParamType1,ParamName1,ParamValue1,ParamType2,ParamName2,ParamValue2,ParamType3,ParamName3,ParamValue3,Code) \
    ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_DECLARE(TypeName,ParamType1,ParamName1,ParamValue1,ParamType2,ParamName2,ParamValue2,ParamType3,ParamName3,ParamValue3,Code) \
    ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_CREATE(TypeName,ParamType1,ParamValue1,ParamType2,ParamValue2,ParamType3,ParamValue3)

#define ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_DECLARE(TypeName,ParamType1,ParamName1,ParamValue1,ParamType2,ParamName2,ParamValue2,ParamType3,ParamName3,ParamValue3,Code) \
    ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_DECLARE_OPTTYPENAME(TypeName,ParamType1,ParamName1,ParamValue1,ParamType2,ParamName2,ParamValue2,ParamType3,ParamName3,ParamValue3,,Code)

/**
 * Declares a rendering command type with 3 parameters.
 */
#define ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_DECLARE_OPTTYPENAME(TypeName,ParamType1,ParamName1,ParamValue1,ParamType2,ParamName2,ParamValue2,ParamType3,ParamName3,ParamValue3,OptTypename,Code) \
    class EURCMacro_##TypeName : public FRenderCommand \
    { \
    public: \
        EURCMacro_##TypeName(OptTypename TCallTraits<ParamType1>::ParamType In##ParamName1,OptTypename TCallTraits<ParamType2>::ParamType In##ParamName2,OptTypename TCallTraits<ParamType3>::ParamType In##ParamName3): \
          ParamName1(In##ParamName1), \
          ParamName2(In##ParamName2), \
          ParamName3(In##ParamName3) \
        {} \
        TASK_FUNCTION(Code) \
        TASKNAME_FUNCTION(TypeName) \
    private: \
        ParamType1 ParamName1; \
        ParamType2 ParamName2; \
        ParamType3 ParamName3; \
    };

#define TASK_FUNCTION(Code) \
        void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) \
        { \
            FRHICommandListImmediate& RHICmdList = GetImmediateCommandList_ForRenderCommand(); \
            Code; \
        } 

#define TASKNAME_FUNCTION(TypeName) \
        FORCEINLINE TStatId GetStatId() const \
        { \
            RETURN_QUICK_DECLARE_CYCLE_STAT(TypeName, STATGROUP_RenderThreadCommands); \
        }
#define ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER_CREATE(TypeName,ParamType1,ParamValue1,ParamType2,ParamValue2,ParamType3,ParamValue3) \
    { \
        LogRenderCommand(TypeName); \
        if(ShouldExecuteOnRenderThread()) \
        { \
            CheckNotBlockedOnRenderThread(); \
            TGraphTask<EURCMacro_##TypeName>::CreateTask().ConstructAndDispatchWhenReady(ParamValue1,ParamValue2,ParamValue3); \
        } \
        else \
        { \
            EURCMacro_##TypeName TempCommand(ParamValue1,ParamValue2,ParamValue3); \
            FScopeCycleCounter EURCMacro_Scope(TempCommand.GetStatId()); \
            TempCommand.DoTask(ENamedThreads::GameThread, FGraphEventRef() ); \
        } \
    }

即創(chuàng)建了一個(gè)FRenderCommand的子類來(lái)保存?zhèn)魅脘秩揪€程的參數(shù)仪搔,以及一個(gè)DoTask實(shí)現(xiàn),來(lái)實(shí)現(xiàn)SetSelection_RenderThread指令蜻牢。然后通過(guò)ConstructAndDispatchWhenReady來(lái)講渲染線程的操作作為一個(gè)任務(wù)壓入渲染線程的隊(duì)列中烤咧,等待執(zhí)行偏陪。其實(shí)現(xiàn)如下:

template<typename...T>
        FGraphEventRef ConstructAndDispatchWhenReady(T&&... Args)
        {
            new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);
            return Owner->Setup(Prerequisites, CurrentThreadIfKnown);
        }
FGraphEventRef Setup(const FGraphEventArray* Prerequisites = NULL, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread)
    {
        FGraphEventRef ReturnedEventRef = Subsequents; // very important so that this doesn't get destroyed before we return
        SetupPrereqs(Prerequisites, CurrentThreadIfKnown, true);
        return ReturnedEventRef;
    }
void SetupPrereqs(const FGraphEventArray* Prerequisites, ENamedThreads::Type CurrentThreadIfKnown, bool bUnlock)
    {
        checkThreadGraph(!TaskConstructed);
        TaskConstructed = true;
        TTask& Task = *(TTask*)&TaskStorage;
        SetThreadToExecuteOn(Task.GetDesiredThread());
        int32 AlreadyCompletedPrerequisites = 0;
        if (Prerequisites)
        {
            for (int32 Index = 0; Index < Prerequisites->Num(); Index++)
            {
                check((*Prerequisites)[Index]);
                if (!(*Prerequisites)[Index]->AddSubsequent(this))
                {
                    AlreadyCompletedPrerequisites++;
                }
            }
        }
        PrerequisitesComplete(CurrentThreadIfKnown, AlreadyCompletedPrerequisites, bUnlock);
    }
void PrerequisitesComplete(ENamedThreads::Type CurrentThread, int32 NumAlreadyFinishedPrequistes, bool bUnlock = true)
    {
        checkThreadGraph(LifeStage.Increment() == int32(LS_PrequisitesSetup));
        int32 NumToSub = NumAlreadyFinishedPrequistes + (bUnlock ? 1 : 0); // the +1 is for the "lock" we set up in the constructor
        if (NumberOfPrerequistitesOutstanding.Subtract(NumToSub) == NumToSub) 
        {
            QueueTask(CurrentThread);
        }
    }
void QueueTask(ENamedThreads::Type CurrentThreadIfKnown)
    {
        checkThreadGraph(LifeStage.Increment() == int32(LS_Queued));
        FTaskGraphInterface::Get().QueueTask(this, ThreadToExecuteOn, CurrentThreadIfKnown);
    }
    virtual void QueueTask(FBaseGraphTask* Task, ENamedThreads::Type ThreadToExecuteOn, ENamedThreads::Type InCurrentThreadIfKnown = ENamedThreads::AnyThread) final override
    {
        TASKGRAPH_SCOPE_CYCLE_COUNTER(2, STAT_TaskGraph_QueueTask);

        if (ENamedThreads::GetThreadIndex(ThreadToExecuteOn) == ENamedThreads::AnyThread)
        {
            TASKGRAPH_SCOPE_CYCLE_COUNTER(3, STAT_TaskGraph_QueueTask_AnyThread);
            if (FPlatformProcess::SupportsMultithreading())
            {
                uint32 TaskPriority = ENamedThreads::GetTaskPriority(Task->ThreadToExecuteOn);
                int32 Priority = ENamedThreads::GetThreadPriorityIndex(Task->ThreadToExecuteOn);
                if (Priority == (ENamedThreads::BackgroundThreadPriority >> ENamedThreads::ThreadPriorityShift) && (!bCreatedBackgroundPriorityThreads || !ENamedThreads::bHasBackgroundThreads))
                {
                    Priority = ENamedThreads::NormalThreadPriority >> ENamedThreads::ThreadPriorityShift; // we don't have background threads, promote to normal
                    TaskPriority = ENamedThreads::NormalTaskPriority >> ENamedThreads::TaskPriorityShift; // demote to normal task pri
                }
                else if (Priority == (ENamedThreads::HighThreadPriority >> ENamedThreads::ThreadPriorityShift) && (!bCreatedHiPriorityThreads || !ENamedThreads::bHasHighPriorityThreads))
                {
                    Priority = ENamedThreads::NormalThreadPriority >> ENamedThreads::ThreadPriorityShift; // we don't have hi priority threads, demote to normal
                    TaskPriority = ENamedThreads::HighTaskPriority >> ENamedThreads::TaskPriorityShift; // promote to hi task pri
                }
                check(Priority >= 0 && Priority < MAX_THREAD_PRIORITIES);
                {
                    TASKGRAPH_SCOPE_CYCLE_COUNTER(4, STAT_TaskGraph_QueueTask_IncomingAnyThreadTasks_Push);
                    int32 IndexToStart = IncomingAnyThreadTasks[Priority].Push(Task, TaskPriority);
                    if (IndexToStart >= 0)
                    {
                        StartTaskThread(Priority, IndexToStart);
                    }
                }
                return;
            }
            else
            {
                ThreadToExecuteOn = ENamedThreads::GameThread;
            }
        }
        ENamedThreads::Type CurrentThreadIfKnown;
        if (ENamedThreads::GetThreadIndex(InCurrentThreadIfKnown) == ENamedThreads::AnyThread)
        {
            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);
            if (ThreadToExecuteOn == ENamedThreads::GetThreadIndex(CurrentThreadIfKnown))
            {
                Target->EnqueueFromThisThread(QueueToExecuteOn, Task);
            }
            else
            {
                Target->EnqueueFromOtherThread(QueueToExecuteOn, Task);
            }
        }
    }
virtual bool EnqueueFromOtherThread(int32 QueueIndex, FBaseGraphTask* Task) override
    {
        TestRandomizedThreads();
        checkThreadGraph(Task && Queue(QueueIndex).StallRestartEvent); // make sure we are started up

        uint32 PriIndex = ENamedThreads::GetTaskPriority(Task->ThreadToExecuteOn) ? 0 : 1;
        int32 ThreadToStart = Queue(QueueIndex).StallQueue.Push(Task, PriIndex);

        if (ThreadToStart >= 0)
        {
            checkThreadGraph(ThreadToStart == 0);
            TASKGRAPH_SCOPE_CYCLE_COUNTER(1, STAT_TaskGraph_EnqueueFromOtherThread_Trigger);
            Queue(QueueIndex).StallRestartEvent->Trigger();
            return true;
        }
        return false;
    }

以上是自己對(duì)渲染線程使用的一個(gè)簡(jiǎn)單總結(jié),因?yàn)闆](méi)有跟特別深煮嫌,相關(guān)代碼也沒(méi)全部過(guò)一遍笛谦,只能算是一個(gè)局部的個(gè)人理解,之后的使用過(guò)程中會(huì)進(jìn)一步地優(yōu)化這部分內(nèi)容昌阿,希望其他UE4使用者也可以多交流下這部分的知識(shí)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饥脑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懦冰,更是在濱河造成了極大的恐慌灶轰,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刷钢,死亡現(xiàn)場(chǎng)離奇詭異笋颤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)内地,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門伴澄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人阱缓,你說(shuō)我怎么就攤上這事非凌。” “怎么了荆针?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵敞嗡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我祭犯,道長(zhǎng)秸妥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任沃粗,我火速辦了婚禮,結(jié)果婚禮上键畴,老公的妹妹穿的比我還像新娘最盅。我一直安慰自己,他們只是感情好起惕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布涡贱。 她就那樣靜靜地躺著,像睡著了一般惹想。 火紅的嫁衣襯著肌膚如雪问词。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天嘀粱,我揣著相機(jī)與錄音激挪,去河邊找鬼辰狡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛垄分,可吹牛的內(nèi)容都是我干的宛篇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼薄湿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叫倍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起豺瘤,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吆倦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后坐求,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逼庞,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年瞻赶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赛糟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砸逊,死狀恐怖璧南,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情师逸,我是刑警寧澤司倚,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站篓像,受9級(jí)特大地震影響动知,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜员辩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一盒粮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奠滑,春花似錦丹皱、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至杰赛,卻和暖如春呢簸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工根时, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘦赫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓啸箫,卻偏偏與公主長(zhǎng)得像耸彪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忘苛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • OC語(yǔ)言基礎(chǔ) 1.類與對(duì)象 類方法 OC的類方法只有2種:靜態(tài)方法和實(shí)例方法兩種 在OC中蝉娜,只要方法聲明在@int...
    奇異果好補(bǔ)閱讀 4,276評(píng)論 0 11
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,190評(píng)論 25 707
  • 漫漫人生路 一路走來(lái) 有歌聲有歡笑 有哭泣有悲哀 夾雜著人生五味 累嗎? 肯定累 可是 你 沒(méi)有選擇的余地 路是自...
    千層云林閱讀 320評(píng)論 2 22
  • 林肯公園的新唱片中的一首歌扎唾,整個(gè)MV看了好多遍召川,男人傾向于暴力發(fā)泄,大喊大叫胸遇,摔東西自殘荧呐,而女人則表現(xiàn)的很平靜,雖...
    聶歡歡閱讀 357評(píng)論 0 0
  • 父親離開(kāi)我們就要整四年了纸镊,這是依照農(nóng)歷算的倍阐。按照我們老家的風(fēng)俗習(xí)慣,三年一過(guò)逗威,不再過(guò)忌日峰搪。但父親的音容笑貌,父親的...
    鄧文偉閱讀 527評(píng)論 0 0