UE4引擎主流程框架


游戲啟動初始化 

ue4引擎通過傳入不同命令行參數來充當很多角色。它可以是編輯器,用於集成、制作、編輯、管理游戲資源。可以是ds服務器,用於管理玩家連接,同步游戲狀態,校驗玩家操作等

可以是游戲客戶端,接受輸入,呈現精美的畫面,給玩家帶來全方面的游戲體驗。還也可以是commandlet工具,提供cook、打包、分發版本等自動化能力

// 各種平台的main,流程大同小異,注意ios, android會在這里創建UE4游戲線程, UE4游戲線程不是App的主線程
  GEngineLoop.PreInit // FEngineLoop::PreInit(const TCHAR* CmdLine) 解析CmdLine,初始化是否編輯器
 FEngineLoop::PreInitPreStartupScreen
      GLog->AddOutputDevice(GScopedStdOut.Get())
      LLM(FLowLevelMemTracker::Get().ProcessCommandLine(CmdLine)) // LLM初始化
      GError = FPlatformApplicationMisc::GetErrorOutputDevice()
      GWarn = FPlatformApplicationMisc::GetFeedbackContext()
      IFileManager::Get().ProcessCommandLineOptions() // 初始化文件系統
      FTaskGraphInterface::Startup() // TaskGraph初始化,並根據當前機器cpu的核數來創建工作線程
      FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread)  // 將GameThread加入到TaskGraph中
      FThreadStats::StartThread()
      LoadCoreModules: CoreUObject
      FQueuedThreadPool: GThreadPool GBackgroundPriorityThreadPool GLargeThreadPool創建和初始化
      GLogConsole = GScopedLogConsole.Get() // 帶-log參數顯示出來的命令行窗口
      LoadPreInitModules: Engine Renderer AnimGraphRuntime 平台RHI SlateRHIRenderer Landscape RenderCore
      FCsvProfiler::Get()->Init()
      AppLifetimeEventCapture::Init()
      FTracingProfiler::Get()->Init()
      AppInit()
        BeginInitTextLocalization()
        FPlatformMisc::PlatformPreInit()
        FPlatformApplicationMisc::PreInit()
        FConfigCacheIni::InitializeConfigSystem()
        ProjectManager.LoadModulesForProject:  ELoadingPhase::EarliestPossible
        ELoadingPhase::EarliestPossible
        ProjectManager.CheckModuleCompatibility
        ProjectManager.LoadModulesForProject:  ELoadingPhase::PostConfigInit
      FQueuedThreadPool: GIOThreadPool創建和初始化
      GSystemSettings.Initialize
      Scalability::InitScalabilitySystem()
      FConfigCacheIni::LoadConsoleVariablesFromINI // 讀取%EngineDir%\Engine\Config\ConsoleVariables.ini中Startup標簽下的命令並執行
      FPlatformMisc::PlatformInit()
      FPlatformApplicationMisc::Init()
      FPlatformMemory::Init()
      InitializeStdOutDevice()
        GLog->AddOutputDevice(GScopedStdOut.Get())
      InitGamePhys()
      InitEngineTextLocalization()
      FSlateApplication::Create()
      FShaderParametersMetadata::InitializeAllUniformBufferStructs()
      RHIInit(bHasEditorToken)
      RenderUtilsInit()
      FShaderCodeLibrary::InitForRuntime
      FShaderPipelineCache::Initialize
      CreateMoviePlayer()
      PostInitRHI()
      StartRenderingThread()
      FSlateApplication: InitializeRenderer
      LoadModulesForProject: ELoadingPhase::PostSplashScreen
      FPreLoadScreenManager初始化和播放

 FEngineloop:PreInitPostStartupScreen
      GetMoviePlayer()->SetupLoadingScreenFromIni()
      LoadModulesForProject: ELoadingPhase::PreEarlyLoadingScreen
      GetMoviePlayer()->Initialize
      GetMoviePlayer()->PlayEarlyStartupMovies() // 播放啟動視頻
      FPlatformMisc::PlatformHandleSplashScreen(true)
      FCoreDelegates::OnMountAllPakFiles.Execute(PakFolders) // 掛載pak
      LoadModulesForEnabledPlugins: ELoadingPhase::PreEarlyLoadingScreen
      FShaderCodeLibrary::OpenLibrary
      FShaderPipelineCache::OpenPipelineFileCache
      InitGameTextLocalization()
      LoadModule: AssetRegistry
      ProcessNewlyLoadedUObjects() // 注冊所有UObject類和初始化缺省屬性
      UMaterialInterface::InitDefaultMaterials() // 加載缺省材質
      IStreamingManager::Get() // 初始化texture streaming系統
      FModuleManager::Get().StartProcessingNewlyLoadedObjects() 
      LoadStartupCoreModules() // Core Networking Messaging SlateCore Slate UMG等
      LoadModulesForProject: ELoadingPhase::PreLoadingScreen
      FPreLoadScreenManager::Get()->Initialize(*Renderer)
      PostInitRHI()
      StartRenderingThread()
      FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen
      FPlatformMisc::PlatformHandleSplashScreen // 顯示splash screen
      LoadStartupModules
        LoadModulesForProject: ELoadingPhase::PreDefault
        LoadModulesForProject: ELoadingPhase::Default
        LoadModulesForProject: ELoadingPhase::PostDefault
      if (!bIsRegularClient)  // 非客戶端時
        GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass) // 創建GEngine
        GEngine->ParseCommandline()
        GEngine->Init(this)
        FCoreDelegates::OnPostEngineInit.Broadcast()
        LoadModulesForProject: ELoadingPhase::PostEngineInit
        LoadModulesForEnabledPlugins: ELoadingPhase::PostEngineInit
        UCommandlet* Commandlet = NewObject<UCommandlet>(GetTransientPackage(), CommandletClass) // 創建Commandlet
        Commandlet->AddToRoot()
        Commandlet->ParseParms( CommandletCommandLine ) // 執行Commadlet
      GetHighResScreenshotConfig().Init() // 初始化高清截圖系統
      InitEngineTextLocalization()
      InitGameTextLocalization()
      FPlatformApplicationMisc::PostInit()
      FAutomationTestFramework::Get().RunSmokeTests()
      PreInitContext.Cleanup()

 GEngineLoop.Init // FEngineLoop::Init()
    GEngine = NewObject<>(GetTransientPackage(), EngineClass)
    GetMoviePlayer()->PassLoadingScreenWindowBackToGame()
    FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame()
    GEngine->ParseCommandline()
    InitTime()
 GEngine->Init() // 游戲用UGameEngine,編輯器用UEditorEngine
      UEngine->Init()  //基類,讀Engine Config,加載默認資源等|
      GameInstance = NewObject<UGameInstance>(this, GameInstanceClass)
      GameInstance->InitializeStandalone()
        CreateNewWorldContext(EWorldType::Game)
        UWorld* DummyWorld = UWorld::CreateWorld(EWorldType::Game)
        WorldContext->SetCurrentWorld(DummyWorld)
        Init()
      ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass)
      ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance)
      GameViewportWindow = CreateGameWindow() // GameViewportWindow類型為TWeakPtr<class SWindow>
      CreateGameViewport( ViewportClient )
        CreateGameViewportWidget( GameViewportClient ) // GameViewportClient類型為UGameViewportClient*
          TSharedRef<SOverlay> ViewportOverlayWidgetRef = SNew( SOverlay )
          TSharedRef<SGameLayerManager> GameLayerManagerRef GameLayerManagerRef = SNew(SGameLayerManager) [ViewportOverlayWidgetRef]
          TSharedRef<SViewport> GameViewportWidgetRef = SNew( SViewport ) [GameLayerManagerRef]
        SceneViewport = MakeShareable( new FSceneViewport( GameViewportClient, GameViewportWidgetRef ) ) // SceneViewport類型為TSharedPtr<class FSceneViewport> 
        GameViewportWidgetRef->SetViewportInterface( SceneViewport.ToSharedRef() )
        FSceneViewport* ViewportFrame = SceneViewport.Get()
        GameViewport->SetViewportFrame(ViewportFrame) // GameViewport類型為UGameViewportClient*
        GameViewport->GetGameLayerManager()->SetSceneViewport(ViewportFrame)
      ViewportClient->SetupInitialLocalPlayer
        ViewportGameInstance->CreateInitialPlayer
          UGamelnstane::CreatelLocalPlayer  //這里創建玩家,分屏模式就有多個,也會創建多個Vlewport
      UGameViewportClient::OnViewportCreated().Broadcast()
    FCoreDelegates::OnPostEngineInit.Broadcast()
 GEngine->Start() // UGameEngine
      GameInstance->StartGameInstance()
        OnStart() // UGameInstance
    if (HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
      FPreLoadScreenManager::Get()->SetEngineLoadingComplete(true)
      FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish()
    else
      GetMoviePlayer()->WaitForMovieToFinish()
    FThreadHeartBeat::Get().Start()
    FShaderPipelineCache::PauseBatching()
    FCoreDelegates::OnFEngineLoopInitComplete.Broadcast()
    FShaderPipelineCache::ResumeBatching()

引擎的Shader是在游戲啟動非常早的時候就會准備好,處理這塊熱更要注意

Init函數中會創建Viewport(游戲最終畫到的地方),然后調用GameInstance->StartGameInstance()函數來開始業務邏輯的執行

 

游戲Tick循環

 GEngineLoop.Tick() // FEngineLoop::Tick()                                                                                    RenderThread(渲染線程)
    LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame())
FThreadHeartBeat::Get().HeartBeat(true)
FGameThreadHitchHeartBeat::Get().FrameStart()
FPlatformMisc::TickHotfixables()
如果不使用渲染線程,在這里會先執行TickRenderingTickables() IConsoleManager::Get().CallAllConsoleVariableSinks() //CVar有變化,這里廣播通知 FCoreDelegates::OnBeginFrame.Broadcast()
GLog->FlushThreadedLogs() GEngine->UpdateTimeAndHandleMaxTickRate()//更新CurrentTime和DeltaTime,如果設了最大幀率會在這里等待 SceneComponent在RegisterComponent時候,CreateRenderState就會創建Scenelnfo, 這里會真正更新到渲染線程 遍歷WorldContext通知渲染線程UpdateScenePrimitives
--------ENQUEUE_RENDER_COMMAND(UpdateScenePrimitives)--------Scene->UpdateAllPrimitiveSceneInfos(RHICmdList) ①處理RemovedLocalPrimitiveSceneInfos這里只是從數組中摘掉,收集到一起等最后再刪 ②處理AddedLocalPrimitiveSceneInfos ③處理更新Transform的 FlushRuntimeVirtualTexture SetTransform AddPrimitiveToUpdateGPU,如果支持,會更新GPUScene UpdatedAttachmentRoots UpdatedCustomPrimitiveParams更新Custom數據 然后SetNeedsUniformBufferUpdate DistanceFieldSceneDataUpdates ④刪掉前面收集的DeletedSceneInfos
通知渲染線程BeginFrame  ------------------------ENQUEUE_RENDER_COMMAND(BeginFrame)---------------------------BeginFrameRenderThread(RHICmdList, CurrentFrameCounter) 遍歷WorldContext,遁知渲染線程StartFrame
----------------ENQUEUE_RENDER_COMMAND(SceneStartFrame)---------------Scene->StartFrame(RHICmdList) VelocityData.PrimitiveSceneInfo->SetNeedsUniformBufferUpdate(true) 之前SceneComponenty移動調了Update Transform會加到VelocityData里 這里標記一下要刷UniformBuffer,緊接著10幀都會在這標記刷 GMalloc->UpdateStats()
FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) 
CalculateFPSTimings() // 計算平均fps/ms 通知渲染線程ResetDeferredUpdates
----------------ENQUEUE_RENDER_COMMAND(ResetDeferredUpdates)------------------FDeferredUpdateResource::ResetNeedsUpdate() FlushPendingDeleteRHIResources_RenderThread() 這里會把渲染線程准備要刪的RHIResource都刪了, 如果drawcall太多或大量銷毀資源這里會卡 FlushRHI drawcall太多這里就會卡着等刷完 BlockUntilGPUIdle delete... FPlatformApplicationMisc::PumpMessages(true)//處理windows消息循環 如果是IdleMode = ShouldUseIdleMode()會在這里Sleep 0.1秒 處理輸入:FCoreDelegates::OnSamplingInput.Broadcast() SlateApp.PollGameDeviceState() SlateApp.FinishedInputThisFrame() MediaModule->TickPreEngine() GEngine->Tick(FApp::GetDeltaTime(), bIdleMode) StaticTick(DeltaSeconds, !!GAsyncLoadingUseFullTimeLimit, GAsyncLoadingTimeLimit / 1000.f) // 里面做等待資源加載的一些邏輯 遍歷WorldContext調用 TickWorldTravel(Context, DeltaSeconds) //處理關卡加載的一些邏輯 Context.World()->Tick( LEVELTICK_All, DeltaSeconds ) BeginTickDrawEvent()通知渲染線程 --------------ENQUEUE_RENDER_COMMAND(BeginDrawEventCommand)-------------BeginDrawEvent WorldTick FWorldDelegates::OnWorldTickStart.Broadcast //Tick網絡 BroadcastTickDispatch(DeltaSeconds) BroadcastPostTickDispatch() TickNetClient( DeltaSeconds ) 設了高優先級加載,並且在無縫切圖中,執行ProcessAsyncLoading 設原點偏移SetNewWorldOrigin NavigationSystem->Tick(DeltaSeconds) //導航系統Tick,里面處理NavMesh FWorldDelegates::OnWorldPreActorTick.Broadcast MovieSceneSequenceTick.Broadcast 追歷LevelCollections,收集需要Tick的 RunTickGroup(TG_PrePhysics) 這些是在物理線程前需要Tick的 Tick這個階段的Actor和Component EnsureCollisionTreeIsBuilt()//這里會阻塞等物理 RunTickGroup(TG_StartPhysics) Tick這個階段的Actor和Component RunTickGroup(TG_DuringPhysics, false) 物理線程執行的時候,這些組件Tick,不依賴物理的組件可以放這里 Tick這個階段的Actor和Component RunTickGroup(TG_EndPhysics) Tick這個階段的Actor和Component RunTickGroup(TG_PostPhysics) //物理做完后,需要Tick的放這里,一般這些組件依賴物理的結果 Tick這個階段的Actor和Component if (LevelCollections[i].GetType() == ELevelCollectionType::DynamicSourceLevels) CurrentLatentActionManager.ProcessLatentActions GetTimerManager().Tick(DeltaSeconds)//業務的Timer會在這里觸發 FTickableGameObject::TickObjects PlayerController->UpdateCameraManager//這里會更新相機 ProcessLevelStreamingVolumes() WorldComposition->UpdateStreamingState() RunTickGroup(TG_PostUpdateWork) Tick這個階段的Actor和Component RunTickGroup(TG_LastDemotable) Tick這個階段的Actor和Component FTickTaskManagerInterface::Get().EndFrame() FWorldDelegates::OnWorldPostActorTick.Broadcast FinishAsyncTrace()//異步的物理查詢在這出結果 Flush網絡 BroadcastTickFlush(RealDeltaSeconds) BroadcastPostTickFlush(RealDeltaSeconds) Scene->UpdateSpeedTreeWind(TimeSeconds) //這是個搖樹的組件 FXSystem->Tick(DeltaSeconds)//特效或粒子系統 GEngine->ConditionalCollectGarbage()//嘗試垃圾回收
GEngine->AddOnScreenDebugMessage
編輯器:UpdateCullDistanceVolumes() EndTickDrawEvent(TickDrawEvent)
---------------ENQUEUE_RENDER_COMMAND(EndDrawEventCommand)--------------EndDrawEvent 這樣渲染線程就知道了卡在這之間的指令都是WorldTick的 如果非DS,關卡都加載好了,就更新 USkyLightComponent::UpdateSkyCaptureContents(Context.World()) UReflectionCaptureComponent::UpdateReflectionCaptureContents(Context.World()) UpdateTransitionType(Context.World()) BlockTillLevelStreamingCompleted(Context.World()) 服務器 Context.World()->UpdateLevelStreaming() ConditionalCommitMapChange(Context) FTickableGameObject::TickObjects(nullptr, LEVELTICK_All, false, DeltaSeconds) MediaModule->TickPostEngine() GameViewport->Tick(DeltaSeconds)//GameViewport類型為UGameViewportClient* RedrawViewports() GameViewport->Viewport->Draw(bShouldPresent)//這個FViewport繼承的是RenderTarget 前面初始化時候,這里被設為了FSceneViewport EnqueueBeginRenderFrame(bShouldPresent) ----------ENQUEUE_RENDER_COMMAND(BeginDrawingCommand)-----------RHICmdList.BeginDrawingViewport(GetViewportRHI(), FTextureRHIRef()); UpdateRenderTargetSurfaceRHIToCurrentBackBuffer(); 更新RT到BackBuffer上
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY)) ViewportClient->Draw(this, &Canvas)
游戲實際調用UGameViewportClient::Draw BeginDrawDelegate.Broadcast CanvasObject->Canvas = SceneCanvas 設到UCanvas上 FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(InViewport,MyWorld->Scene,EngineShowFlags).SetRealtimeUpdate(true))//構造FSceneViewFamilyContext ViewFamily 遍歷所有ULocalPlayer,拿到APlayerController因為相機在這里 如果是StereoRendering就有多個FSceneView,否則就1個,初始化一堆參數 給VR游戲用的左右眼,所以是多個View IStreamingManager::Get().AddViewInformation 把View信息告訴資源加載模塊,看注釋FOV較小時可以5倍速加載 FinalizeViews(&ViewFamily, PlayerViewMap) 根據所有View計算一個最大包圍盒 GetRendererModule().BeginRenderingViewFamily 實際調用的是FRendererModule::BeginRenderingViewFamily
World->SendAllEndOfFrameUpdates()
BeginSendEndOfFrameUpdatesDrawEvent(Scene ? Scene->GetGPUSkinCache() : nullptr) -----ENQUEUE_RENDER_COMMAND(BeginDrawEventCommand)----SendAllEndOfFrameUpdates->GPUSkinCache->BeginBatchDispatch(RHICmdList)
EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates)----------------ENQUEUE_RENDER_COMMAND(EndDrawEventCommand)----------------SendAllEndOfFrameUpdates->GPUSkinCache->EndBatchDispatch(RHICmdList)
                                                                                          SendAllEndOfFrameUpdates->GPUSkinCache->TransitionAllToReadable(RHICmdList)
                                                                                          delete SendAllEndOfFrameUpdates
FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(ViewFamily, Canvas->GetHitProxyConsumer())
創建一個SceneRenderer, 里面會根據ShadingPath決定是Deferred渲染器還是Mobile渲染器 (Mobile里也可以DeferredShading) ENQUEUE_RENDER_COMMAND(FInitFXSystemCommand) USceneCaptureComponent::UpdateDeferredCaptures ENQUEUE_RENDER_COMMAND(FViewExtensionPreDrawCommand) 遍歷場景中的PlanarReflections做SceneRenderer->Scene->UpdatePlanarReflectionContents SceneRenderer->ViewFamily.DisplayInternalsData.Setup(World) 提交FDrawSceneCommand這里開始讓渲染線程畫場景,SceneRenderer作為lambda傳給了渲染線程----ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)-----RenderViewFamily_RenderThread(RHICmdList, SceneRenderer)| SceneRenderer->Render FDeferredShadingSceneRenderer::Render FMobileSceneRenderer::Render 前面的2選1決定這里用哪個,這個函數就是UE4的整個渲染管線 后續填坑,這里不細說了 FlushPendingDeleteRHIResources_RenderThread()
遍歷所有PlayerController,畫HUD,注意這里是ToneMapping后才畫,不是在場景RT上畫
CanvasObject->Init
CanvasObject->ApplySafeZoneTransform()
PlayerController->MyHUD->SetCanvas(CanvasObject, DebugCanvasObject)
PlayerController->MyHUD->PostRender() SceneCanvas->Flush_GameThread()
DrawnDelegate.Broadcast()
PostRender(DebugCanvasObject)
DebugCanvasObject->Init
DrawStatsHUD
ViewportConsole->PostRender_Console(DebugCanvasObject) EndDrawDelegate.Broadcast() Canvas.Flush_GameThread() SetRequiresVsync(bLockToVsync) EnqueueEndRenderFrame(bLockToVsync, bShouldPresent)
FViewport::EnqueueEndRenderFrame(bLockToVsync, bShouldPresent) -----------ENQUEUE_RENDER_COMMAND(EndDrawingCommand)------------ViewportEndDrawing(RHICmdList, Params)
Parameters.Viewport->EndRenderFrame GetRendererModule().PostRenderAllViewports() IStreamingManager::Get().Tick 通知渲染線程TickRenderingTimer
----------------ENQUEUE_RENDER_COMMAND(TickRenderingTime)-----------------這里就是更新RT池 GRenderingRealtimeClock.Tick(DeltaSeconds)
GRenderTargetPool.TickPoolElements() FRDGBuilder::TickPoolElements() ICustomResourcePool::TickPoolElements(RHICmdList) 編輯器:BroadcastPostEditorTick(DeltaSeconds); FAssetRegistryModule::TickAssetRegistry(DeltaSeconds)
PreLoadScreenManager相關,播視頻時候會阻塞在這里直到完成再向下執行 GShaderCompilingManager->ProcessAsyncResults
GDistanceFieldAsyncQueue->ProcessAsyncTasks MediaModule->TickPreSlate() FSlateApplication::Get().Tick(ESlateTickType::PlatformAndInput) CurrentDemoNetDriver可能會建-個ConcurrentTask執行TickFlushAsyncEndOfFrame FSlateApplication::Get().Tick(ESlateTickType::TimeAndWidgets) TickTime()
TickAndDrawWidgets(DeltaTime)
PreTickEvent.Broadcast(DeltaTime)
DrawWindows()
PrivateDrawWindows()
DrawPrepass( DrawOnlyThisWindow )
DrawWindowAndChildren
Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer )
SlateBeginDrawingWindowsCommand -----------ENQUEUE_RENDER_COMMAND(SlateBeginDrawingWindowsCommand)------------Policy->BeginDrawingWindows()
SlateDrawWindowsCommand ----------------ENQUEUE_RENDER_COMMAND(SlateDrawWindowsCommand)---------------Params.Renderer->DrawWindow_RenderThread
SlateWindowRendered.Broadcast
SlateEndDrawingWindowsCommand ------------ENQUEUE_RENDER_COMMAND(SlateEndDrawingWindowsCommand)-------------FSlateEndDrawingWindowsCommand::EndDrawingWindows
if (DeferredUpdateContexts.Num() > 0)
DrawWidgetRendererImmediate --------------ENQUEUE_RENDER_COMMAND(DrawWidgetRendererImmediate)-------------循環FRenderThreadUpdateContext,執行Context.Renderer->DrawWindowToTarget_RenderThread(RHICmdList, Context)
PostTickEvent.Broadcast(DeltaTime)
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConcurrentTask) 通知渲染線程WaitForOutstandingTasks
---ENQUEUE_RENDER_COMMAND(WaitForOutstandingTasksOnly_for_DelaySceneRenderCompletion)---FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly) RHITick( FApp::GetDeltaTime() ) GFrameCounter++; TotalTickTime += FApp::GetDeltaTime() FrameEndSync.Sync//等渲染線程的同步 delete PreviousPendingCleanupObjects DeleteLoaders() FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()) FThreadManager::Get().Tick() GEngine->TickDeferredCommands() MediaModule->TickPostRender() FCoreDelegates::OnEndFrame.Broadcast() 通知渲染線程EndFrame---------------------------ENQUEUE_RENDER_COMMAND(EndFrame)--------------------------EndFrameRenderThread(RHICmdList, CurrentFrameCounter) GEngine->SetGameLatencyMarkerEnd(CurrentFrameCounter)

在Tick中會先執行游戲邏輯,調用World的Tick,然后Tick所有注冊需要Tick的Actor和Component,這里會根據注冊的階段分別在不同時期Tick。

結束之后會進入繪制視口,會先畫場景,在畫場景時才相當於是渲染線程這幀真正開始了,然后畫UI。

然后中間很多地方都穿插着多線程調度。最終我們看到引擎執行一幀大概如下圖所示:

注1:由於UI在場景之后繪制,假如UI遮擋住了大部分場景,被遮擋住的部分就白畫了。

         所以如果能修改引擎代碼的話,可以考慮在繪制開始階段,先在場景的RT上UI對應的位置寫上深度(需要額外處理半透明)或者建一些對應輪廓面片放在鏡頭近平面上擋住場景對應區域,這樣就可以跳過這些像素的繪制。

注2:如果游戲線程做的事情很少,基本上會阻塞在最后的FrameEndSync.Sync上。

         當你有一些很重的工作,但是又和渲染無關,比如網絡游戲的解包或其他比較重的邏輯,就可以考慮在繪制這一階段期間開啟一個單獨的線程,讓子線程去做這些工作,而不是放在前面的Tick階段。

 

在Tick開始處,在Scene->UpdateAllPrimitiveSceneInfos(RHICmdList)中會先把場景數據的各種信息比如,Transform在渲染線程上刷一遍(因為很多東西是會動的)。

然后引擎開始Tick World。這里比較重要的一點是,我們可以看到Tick的對象有很多階段,平常用的比較多的是PrePhysics(Tick默認為該類型),DuringPhysics,PostPhysics這3個地方。

為什么要區分這些階段呢?這是因為UE4是個多線程的引擎,物理是一個很重的計算流程,物理的計算發生在一個單獨的線程上,因此將Tick拆分成這些階段,就可以讓業務代碼選擇在什么時期執行。

因為大部分的組件都是需要先准備好數據,交給物理線程來執行,所以UE4把Tick默認都放在了PrePhysics上,這樣當所有組件Tick完,物理線程得到的數據就是最新的。

但是考慮到假如你的組件或Actor和物理沒任何關系,那么物理線程就會等待邏輯執行,在物理線程開始執行后,由於DurningPhysics基本沒事情做,又反過來等待物理線程,這樣游戲線程的總耗時就會被拉長。

因此可以把一些不需要依賴物理的組件放在其他階段,說不定能起到很好的優化效果。

注:Actor和ActorComponent的Tick是分別注冊且互相獨立的,互相不存在依賴關系。所以當不需要Tick Component時,關掉Actor是不夠的,Component也要單獨關閉。

 

參考

UE4的執行流程和CPU優化

剖析虛幻渲染體系(01)- 綜述和基礎(引擎啟動流程) 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM