游戲啟動初始化
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也要單獨關閉。
參考