可可西

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() // 初始化文件系统
GGameThreadId = FPlatformTLS::GetCurrentThreadId();
GIsGameThreadIdInitialized = true;
FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetMainGameMask());
FPlatformProcess::SetupGameThread(); FTaskGraphInterface::Startup(FPlatformMisc::NumberOfCores()) // TaskGraph初始化,并根据当前机器cpu的核数来new FTaskGraphImplementation,在FTaskGraphImplementation的构造函数中初始化工作线程 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 PluginManager.LoadModulesForEnabledPlugins: ELoadingPhase::EarliestPossible ProjectManager.CheckModuleCompatibility ProjectManager.LoadModulesForProject: ELoadingPhase::PostConfigInit FQueuedThreadPool: GIOThreadPool创建和初始化 GSystemSettings.Initialize Scalability::InitScalabilitySystem()
UDeviceProfileManager::InitializeCVarsForActiveDeviceProfile() // 加载DeviceProfile配置,执行里面的CVars命令
Scalability::LoadState((bHasEditorToken && !GEditorSettingsIni.IsEmpty()) ? GEditorSettingsIni : GGameUserSettingsIni); FConfigCacheIni::LoadConsoleVariablesFromINI // 读取%EngineDir%\Engine\Config\ConsoleVariables.ini中Startup标签下的命令并执行 FPlatformMisc::PlatformInit() FPlatformApplicationMisc::Init() FPlatformMemory::Init() InitializeStdOutDevice() GLog
->AddOutputDevice(GScopedStdOut.Get()) InitGamePhys() // 初始化物理引擎,加载相应的物理模块;创建FPhysXAllocator* GPhysXAllocator、physx::PxFoundation* GPhysXFoundation、PxPvd* GPhysXVisualDebugger、physx::PxPhysics* GPhysXSDK、apex::ApexSDK* GApexSDK等 InitEngineTextLocalization() FSlateApplication::Create() FShaderParametersMetadata::InitializeAllUniformBufferStructs() RHIInit(bHasEditorToken) RenderUtilsInit() FShaderCodeLibrary::InitForRuntime FShaderPipelineCache::Initialize CreateMoviePlayer() PostInitRHI() StartRenderingThread() FSlateApplication: InitializeRenderer IProjectManager::Get().LoadModulesForProject: ELoadingPhase::PostSplashScreen FPreLoadScreenManager初始化和播放 FEngineloop:PreInitPostStartupScreen GetMoviePlayer()->SetupLoadingScreenFromIni() IProjectManager::Get().LoadModulesForProject: ELoadingPhase::PreEarlyLoadingScreen GetMoviePlayer()->Initialize GetMoviePlayer()->PlayEarlyStartupMovies() // 播放启动视频 FPlatformMisc::PlatformHandleSplashScreen(true) FCoreDelegates::OnMountAllPakFiles.Execute(PakFolders) // 挂载pak IProjectManager::Get().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等 IProjectManager::Get().LoadModulesForProject: ELoadingPhase::PreLoadingScreen FPreLoadScreenManager::Get()->Initialize(*Renderer) PostInitRHI() StartRenderingThread()
if (!IsRunningDedicatedServer() && !IsRunningCommandlet() && !GetMoviePlayer()->IsMovieCurrentlyPlaying())
if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen)) FPreLoadScreenManager::Get()
->PlayFirstPreLoadScreen
else
GetMoviePlayer()->PlayMovie() // 启动LoadingScreen线程 FPlatformMisc::PlatformHandleSplashScreen
// 显示splash screen LoadStartupModules IPluginManager::Get().LoadModulesForProject: ELoadingPhase::PreDefault IPluginManager::Get().LoadModulesForProject: ELoadingPhase::Default IPluginManager::Get().LoadModulesForProject: ELoadingPhase::PostDefault if (!bIsRegularClient) // 非客户端时 GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass) // 创建GEngine GEngine->ParseCommandline() GEngine->Init(this) FCoreDelegates::OnPostEngineInit.Broadcast() IPluginManager::Get().LoadModulesForProject: ELoadingPhase::PostEngineInit IPluginManager::Get().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() // 结束LoadingScreen线程
GIsRunning = true; 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()) // 每帧更新LLM统计数据
// 将LLM统计数据从PendingThreadStates数组拷贝到ThreadStates数组中,然后用ThreadStates来得到EnumTagAmounts数组各统计项的数据
// 可用FLowLevelMemTracker::Get().GetTagAmountForTracker((ELLMTracker)Tracker, (ELLMTag)Tag)来得到Tracker下的Tag的统计项数值
// 由于LLM的内存统计是在Tick中异步计算的,因此在FLLMTracker::TrackAllocation捕获内存分配时,调用GetTagAmount不能实时得到指定Tag的统计项数值(只能得到上一帧的)
GetTracker((ELLMTracker)TrackerIndex)->Update(CustomTags,ParentTags);
FThreadHeartBeat::Get().HeartBeat(true) // 用于挂起检测。需要启用USE_HANG_DETECTION宏。Android/IOS平台不支持检测,具体详见:AllowThreadHeartBeat()函数
FGameThreadHitchHeartBeat::Get().FrameStart() // 用于卡顿检测。需要启用USE_HITCH_DETECTION宏。
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) //主要作用是删除、增加、更新CPU侧的图元数据,且同步到GPU端 ①处理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的
SetupPhysicsTickFunctions(DeltaSeconds)
FTickTaskManagerInterface::Get().StartFrame(this, DeltaSeconds, TickType, LevelsToTick) RunTickGroup
(TG_PrePhysics) 这些是在物理线程前需要Tick的 FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // Tick这个阶段的Actor和Component EnsureCollisionTreeIsBuilt()//构建物理碰撞树  注:该逻辑只会执行一次,因为构建完成后会修改bIsBuilt为true,后面进入该函数会先检查bIsBuilt是否为true,为true则直接退出 RunTickGroup(TG_StartPhysics) FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // Tick这个阶段的Actor和Component RunTickGroup(TG_DuringPhysics,
false) 物理线程执行的时候,这些组件Tick,不依赖物理的组件可以放这里 FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // Tick这个阶段的Actor和Component RunTickGroup(TG_EndPhysics) FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // Tick这个阶段的Actor和Component RunTickGroup(TG_PostPhysics) //物理做完后,需要Tick的放这里,一般这些组件依赖物理的结果 FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // 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) FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // Tick这个阶段的Actor和Component RunTickGroup(TG_LastDemotable) FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete) // 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(给VR游戏用的左右眼,所以是多个View),否则就1个 LocalPlayer->CalcSceneView(计算ViewFamily、View的各种属性)
初始化FSceneView* View一堆参数 IStreamingManager::Get().AddViewInformation 把View信息告诉资源加载模块,看注释FOV较小时可以5倍速加载 FinalizeViews(&ViewFamily, PlayerViewMap)
MyWorld->UpdateLevelStreaming() 根据所有View计算一个最大包围盒 GetRendererModule().BeginRenderingViewFamily 实际调用的是FRendererModule::BeginRenderingViewFamily
World->SendAllEndOfFrameUpdates()
BeginSendEndOfFrameUpdatesDrawEvent(Scene ? Scene->GetGPUSkinCache() : nullptr) -----ENQUEUE_RENDER_COMMAND(BeginDrawEventCommand)----SendAllEndOfFrameUpdates->GPUSkinCache->BeginBatchDispatch(RHICmdList)
GTWork
循环遍历ComponentsThatNeedEndOfFrameUpdate_OnGameThread数组中的ActorComponent
Component->DoDeferredRenderUpdates_Concurrent()//将Component在游戏线程中的状态延迟更新到渲染线程
ParallelWork
循环遍历ComponentsThatNeedEndOfFrameUpdate数组中的ActorComponent
NextComponent->DoDeferredRenderUpdates_Concurrent()//将NextComponent在游戏线程中的状态延迟更新到渲染线程
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)

if (FPreLoadScreenManager::Get())
if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
if (FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish(); // PreLoadScreenManager相关,播视频时候会阻塞在这里直到完成再向下执行
GameEngine->SwitchGameWindowToUseGameViewport();
else
GetMoviePlayer()->WaitForMovieToFinish(true); 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
RHICmdList.BeginDrawingViewport(ViewportInfo.ViewportRHI, FTextureRHIRef());
RHICmdList.SetViewport(0, 0, 0, ViewportWidth, ViewportHeight, 0.0f);
RHICmdList.EndDrawingViewport(ViewportInfo.ViewportRHI, true, DrawCommandParams.bLockToVsync); // SwapBuffers
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)- 综述和基础(引擎启动流程) 

UE4中Game线程的主流程Tick

UE4中Game线程的最外层Tick

posted on 2021-08-28 21:40  可可西  阅读(4596)  评论(1编辑  收藏  举报

导航