-
-
[原创] UE4的启动流程分析
-
发表于: 3天前 636
-
本文基于Unreal Engine版本为:
- 当前分支:4.27
- 最新提交:3abfe77d0b Branch snapshot for CL 19160214
本文主要是理清楚在 Ue4 启动阶段做了些什么工作,方便我们后续拆分逻辑进行分析。然后本文可能会略显啰嗦,虽然可能一些知识点用概括性话总结一下就可以了,但是本人还是比较喜欢有代码详细情况分析(让人更安心一些),所以整体还是基于源码片段进行分析辅助理解的。
参考文章:
- 160K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0E0j5i4c8G2i4K6u0V1K9r3q4Z5j5g2)9J5c8Y4m8Q4x3V1j5I4y4K6b7@1x3K6R3&6x3W2)9J5k6h3S2@1L8h3H3`.
架构
整体架构

完整初始化流程

平台入口层
每个平台进入不太一样,主要都是针对于各个平台特性的一些处理。这里我们暂时不深入了解。

GuardedMain(引擎入口)
大多数平台都是通过 GuardedMain 进行启动的。也就是我们的引擎入口函数。
我们主要分析在 GuardedMain 阶段执行了哪些函数.
根据函数分析我们可以知道最主要的流程就是:PreInit,Init,Tick循环,Exit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /** * Static guarded main function. Rolled into own function so we can have error handling for debug/ release builds depending * on whether a debugger is attached or not. */int32 GuardedMain( const TCHAR* CmdLine ){#if !(UE_BUILD_SHIPPING) if (FParse::Param(CmdLine, TEXT("waitforattach"))) { while (!FPlatformMisc::IsDebuggerPresent()); UE_DEBUG_BREAK(); }#endif BootTimingPoint("DefaultMain"); // Super early init code. DO NOT MOVE THIS ANYWHERE ELSE! FCoreDelegates::GetPreMainInitDelegate().Broadcast(); // make sure GEngineLoop::Exit() is always called. struct EngineLoopCleanupGuard { ~EngineLoopCleanupGuard() { // Don't shut down the engine on scope exit when we are running embedded // because the outer application will take care of that. if (!GUELibraryOverrideSettings.bIsEmbedded) { EngineExit(); } } } CleanupGuard; // Set up minidump filename. We cannot do this directly inside main as we use an FString that requires // destruction and main uses SEH. // These names will be updated as soon as the Filemanager is set up so we can write to the log file. // That will also use the user folder for installed builds so we don't write into program files or whatever.#if PLATFORM_WINDOWS FCString::Strcpy(MiniDumpFilenameW, *FString::Printf(TEXT("unreal-v%i-%s.dmp"), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString())); GIsConsoleExecutable = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_CHAR);#endif int32 ErrorLevel = EnginePreInit( CmdLine ); // exit if PreInit failed. if ( ErrorLevel != 0 || IsEngineExitRequested() ) { return ErrorLevel; } { FScopedSlowTask SlowTask(100, NSLOCTEXT("EngineInit", "EngineInit_Loading", "Loading...")); // EnginePreInit leaves 20% unused in its slow task. // Here we consume 80% immediately so that the percentage value on the splash screen doesn't change from one slow task to the next. // (Note, we can't include the call to EnginePreInit in this ScopedSlowTask, because the engine isn't fully initialized at that point) SlowTask.EnterProgressFrame(80); SlowTask.EnterProgressFrame(20);#if WITH_EDITOR if (GIsEditor) { ErrorLevel = EditorInit(GEngineLoop); } else#endif { ErrorLevel = EngineInit(); } } double EngineInitializationTime = FPlatformTime::Seconds() - GStartTime; UE_LOG(LogLoad, Log, TEXT("(Engine Initialization) Total time: %.2f seconds"), EngineInitializationTime);#if WITH_EDITOR UE_LOG(LogLoad, Log, TEXT("(Engine Initialization) Total Blueprint compile time: %.2f seconds"), BlueprintCompileAndLoadTimerData.GetTime());#endif ACCUM_LOADTIME(TEXT("EngineInitialization"), EngineInitializationTime); BootTimingPoint("Tick loop starting"); DumpBootTiming(); // Don't tick if we're running an embedded engine - we rely on the outer // application ticking us instead. if (!GUELibraryOverrideSettings.bIsEmbedded) { while( !IsEngineExitRequested() ) { EngineTick(); } } TRACE_BOOKMARK(TEXT("Tick loop end"));#if WITH_EDITOR if( GIsEditor ) { EditorExit(); }#endif return ErrorLevel;} |
调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | GuardedMain (Launch.cpp:93)│├─ 调试器等待 (96-100行)│ └─ FParse::Param(CmdLine, TEXT("waitforattach"))│ └─ while (!FPlatformMisc::IsDebuggerPresent())│├─ PreMainInit委托广播 (106行)│ └─ FCoreDelegates::GetPreMainInitDelegate().Broadcast()│├─ 创建清理守卫 (109-120行)│ └─ EngineLoopCleanupGuard (RAII)│ └─ ~EngineLoopCleanupGuard() → EngineExit()│├─ EnginePreInit (132行)│ └─ GEngineLoop.PreInit (LaunchEngineLoop.cpp:3647)│ ││ ├─ PreInitPreStartupScreen (1434行)│ │ ├─ GLog->SetCurrentThreadAsMasterThread() (1446行)│ │ ├─ FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir() (1476行)│ │ ├─ FCommandLine::Set(CmdLine) (1480行)│ │ ├─ FTraceAuxiliary::Initialize(CmdLine) (1501行)│ │ ├─ FLowLevelMemTracker::Get().ProcessCommandLine() (1506行)│ │ ├─ LaunchSetGameName() (1555行)│ │ ├─ FPlatformApplicationMisc::GetErrorOutputDevice() (1660行)│ │ ├─ IFileManager::Get().ProcessCommandLineOptions() (1723行)│ │ ├─ FParse::Token() 解析命令行Token (1761行)│ │ ├─ IProjectManager::Get().LoadProjectFile() (2020行)│ │ ├─ FTaskGraphInterface::Startup() (2059行)│ │ └─ LoadCoreModules() (2076行)│ │ └─ FModuleManager::Get().LoadModule(TEXT("CoreUObject")) (3674行)│ ││ └─ PreInitPostStartupScreen (2819行)│ ├─ GetMoviePlayer()->SetupLoadingScreenFromIni() (2863行)│ ├─ IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreEarlyLoadingScreen) (2869行)│ ├─ GetMoviePlayer()->PlayEarlyStartupMovies() (2891行)│ └─ FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen() (2970行)│├─ 检查PreInit结果 (135-138行)│ └─ if (ErrorLevel != 0 || IsEngineExitRequested())│├─ EngineInit (158行) [游戏模式]│ └─ GEngineLoop.Init() (LaunchEngineLoop.cpp:3946)│ ││ ├─ 创建GEngine对象 (3958-3988行)│ │ ├─ GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), ...) (3965行)│ │ └─ NewObject<UEngine>(GetTransientPackage(), EngineClass) (3971行)│ ││ ├─ GEngine->ParseCommandline() (4003行)│ ││ ├─ InitTime() (4010行)│ ││ ├─ GEngine->Init(this) (4017行)│ │ └─ UGameEngine::Init() / UUnrealEdEngine::Init()│ ││ ├─ FCoreDelegates::OnPostEngineInit.Broadcast() (4021行)│ ││ ├─ IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) (4045行)│ ││ ├─ SessionService->Start() (4035行)│ ││ ├─ EngineService = new FEngineService() (4039行)│ ││ ├─ GEngine->Start() (4054行)│ │ └─ UGameEngine::Start() / UUnrealEdEngine::Start()│ ││ └─ GetMoviePlayer()->WaitForMovieToFinish() (4068行)│└─ EngineTick循环 (178-182行) └─ while (!IsEngineExitRequested()) └─ EngineTick() (180行) └─ GEngineLoop.Tick() (每帧调用) └─ GEngine->Tick() (引擎主循环) |
调试器等待
主要是方便开发者调试,也就是命令行如果包含了 waitforattach 关键字,那么就会等待调试器附加后再继续执行。
1 2 3 4 5 | if (FParse::Param(CmdLine, TEXT("waitforattach"))){ while (!FPlatformMisc::IsDebuggerPresent()); UE_DEBUG_BREAK();} |
PreMainInit (委托广播)
广播 preMaininit 委托,允许引擎在初始化前执行代码。
在引擎主初始化之前执行,此时基础系统(日志、文件系统等)可能尚未完全初始化,允许平台特定代码或者特殊模块在启动前进行必要的初始化
也就是这里会去触发所有已经注册回调函数。
这里我们可以了解一下 UE 中模块的概念。
1 | FCoreDelegates::GetPreMainInitDelegate().Broadcast(); |
什么是模块?
在 UE 中,Module 是一个独立的代码单元,通常对应一个 DLL或者静态库。每个模块都有自己的生命周期。
这是模块的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | /** * Interface class that all module implementations should derive from. This is used to initialize * a module after it's been loaded, and also to clean it up before the module is unloaded. */class IModuleInterface{public: /** * Note: Even though this is an interface class we need a virtual destructor here because modules are deleted via a pointer to this interface */ virtual ~IModuleInterface() { } /** * Called right after the module DLL has been loaded and the module object has been created * Load dependent modules here, and they will be guaranteed to be available during ShutdownModule. ie: * * FModuleManager::Get().LoadModuleChecked(TEXT("HTTP")); */ virtual void StartupModule() { } /** * Called before the module has been unloaded */ virtual void PreUnloadCallback() { } /** * Called after the module has been reloaded */ virtual void PostLoadCallback() { } /** * Called before the module is unloaded, right before the module object is destroyed. * During normal shutdown, this is called in reverse order that modules finish StartupModule(). * This means that, as long as a module references dependent modules in it's StartupModule(), it * can safely reference those dependencies in ShutdownModule() as well. */ virtual void ShutdownModule() { } /** * Override this to set whether your module is allowed to be unloaded on the fly * * @return Whether the module supports shutdown separate from the rest of the engine. */ virtual bool SupportsDynamicReloading() { return true; } /** * Override this to set whether your module would like cleanup on application shutdown * * @return Whether the module supports shutdown on application exit */ virtual bool SupportsAutomaticShutdown() { return true; } /** * Returns true if this module hosts gameplay code * * @return True for "gameplay modules", or false for engine code modules, plugins, etc. */ virtual bool IsGameModule() const { return false; }}; |
模块加载
- PreInitPreStartupScreen阶段:加载CoreUObject等核心模块
// Load Core modules required for everything else to work (needs to be loaded before InitializeRenderingCVarsCaching)
{
SCOPED_BOOT_TIMING("LoadCoreModules");
if (!LoadCoreModules())
{
UE_LOG(LogInit, Error, TEXT("Failed to load Core modules."));
return 1;
}
}
- PreInitPostStartupScreen阶段:加载PreEarlyLoadingScreen阶段的模块
- Init阶段:加载PostEngineInit阶段的模块

模块的类型
- 引擎模块(Engine Modules)
- 位置:Engine/Source/Runtime/ 或 Engine/Source/Editor/
- 示例:Core、CoreUObject、Engine、Renderer
- 特点:引擎核心功能
- 游戏模块(Game Modules)
- 位置:项目目录下的 Source/
- 示例:项目自己的模块
- 特点:游戏特定代码
- 插件模块(Plugin Modules)
- 位置:Plugins/ 目录
- 示例:第三方插件
- 特点:可选的扩展功能
模块注册方式
也就是不同模块,可以将自己的回调函数注册到 PreMainInit 委托上.
然后在 GuardedMain 调用Broadcast的时候就会依次去执行。比如这里就是windows平台崩溃报告系统中的一个列子。
- Addraw
1 | FCoreDelegates::GetPreMainInitDelegate().AddRaw(this, &FCrashReportingThread::RegisterUnhandledExceptionHandler); |
- addStatic
1 2 3 4 5 6 | #if !CSV_PROFILER_USE_CUSTOM_FRAME_TIMINGS FCoreDelegates::OnBeginFrame.AddStatic(CsvProfilerBeginFrame); FCoreDelegates::OnEndFrame.AddStatic(CsvProfilerEndFrame); FCoreDelegates::OnBeginFrameRT.AddStatic(CsvProfilerBeginFrameRT); FCoreDelegates::OnEndFrameRT.AddStatic(CsvProfilerEndFrameRT);#endif |
- AddLambda - 绑定Lambda表达式
1 2 3 4 | FCoreDelegates::GetOutOfMemoryDelegate().AddLambda([this](){ PanicDump(TYPE_Malloc, nullptr, nullptr);}); |
- AddUFunction - 绑定UObject的UFUNCTION
1 | ExternalNotifyHandlers.FindOrAdd(NotifyEventName).AddUFunction(ExternalHandlerObject, NotifyEventName); |
创建清理守卫
确保退出的时候能够调用 EngineExit (非嵌入式模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 | // make sure GEngineLoop::Exit() is always called.struct EngineLoopCleanupGuard { ~EngineLoopCleanupGuard() { // Don't shut down the engine on scope exit when we are running embedded // because the outer application will take care of that. if (!GUELibraryOverrideSettings.bIsEmbedded) { EngineExit(); } }} CleanupGuard; |
PreInit (引擎预初始化)
1 2 3 4 5 6 | int32 ErrorLevel = EnginePreInit( CmdLine );// exit if PreInit failed.if ( ErrorLevel != 0 || IsEngineExitRequested() ){ return ErrorLevel;} |
EngineInit/EditorInit (引擎初始化)
根据情况看进入的是编辑器模式还是引擎模式
1 2 3 4 5 6 7 8 9 10 11 12 | SlowTask.EnterProgressFrame(20);#if WITH_EDITORif (GIsEditor){ ErrorLevel = EditorInit(GEngineLoop);}else#endif{ ErrorLevel = EngineInit();} |
Tick 循环
主循环,持续调用 EngineTick 直到请求退出
1 2 3 4 5 6 7 | if (!GUELibraryOverrideSettings.bIsEmbedded){ while( !IsEngineExitRequested() ) { EngineTick(); }} |
循环层
我们可以发现在 GuardedMain 调用的函数本质是调用的 FEngineLoop 的函数
接下来我们重点分析每个函数具体做了些什么内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** * PreInits the engine loop */int32 EnginePreInit( const TCHAR* CmdLine ){ int32 ErrorLevel = GEngineLoop.PreInit( CmdLine ); return( ErrorLevel );}/** * Inits the engine loop */int32 EngineInit(){ int32 ErrorLevel = GEngineLoop.Init(); return( ErrorLevel );}/** * Ticks the engine loop */LAUNCH_API void EngineTick( void ){ GEngineLoop.Tick();}/** * Shuts down the engine */LAUNCH_API void EngineExit( void ){ // Make sure this is set RequestEngineExit(TEXT("EngineExit() was called")); GEngineLoop.Exit();}extern FEngineLoop GEngineLoop; |
数据结构
- PreInitContext: 保存 PreInit 阶段的上下文信息
- EngineService: 引擎服务,用于远程调试等
- SessionService: 会话服务,用于多实例通信
- PendingCleanupObjects: 待清理对象列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | /** * Implements the main engine loop. */class FEngineLoop#if WITH_ENGINE : public IEngineLoop#endif{public: /** Default constructor. */ FEngineLoop(); virtual ~FEngineLoop() { }public: /** * Pre-Initialize the main loop, and generates the commandline from standard ArgC/ArgV from main(). * * @param ArgC The number of strings in ArgV. * @param ArgV The command line parameters (ArgV[0] is expected to be the executable name). * @param AdditionalCommandLine Optional string to append to the command line (after ArgV is put together). * @return Returns the error level, 0 if successful and > 0 if there were errors. */ int32 PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline = nullptr); /** * Pre-Initialize the main loop - parse command line, sets up GIsEditor, etc. * * @param CmdLine The command line. * @return The error level; 0 if successful, > 0 if there were errors. */ int32 PreInit(const TCHAR* CmdLine); /** First part of PreInit. */ int32 PreInitPreStartupScreen(const TCHAR* CmdLine); /** Second part of PreInit. */ int32 PreInitPostStartupScreen(const TCHAR* CmdLine); /** Load all modules needed before Init. */ void LoadPreInitModules(); /** Load core modules. */ bool LoadCoreModules(); /** Clean up PreInit context. */ void CleanupPreInitContext();#if WITH_ENGINE /** Load all core modules needed at startup time. */ bool LoadStartupCoreModules(); /** Load all modules needed at startup time. */ bool LoadStartupModules(); /** * Initialize the main loop (the rest of the initialization). * * @return The error level; 0 if successful, > 0 if there were errors. */ virtual int32 Init() override; /** Initialize the timing options from the command line. */ void InitTime(); /** Performs shut down. */ void Exit(); /** Whether the engine should operate in an idle mode that uses no CPU or GPU time. */ bool ShouldUseIdleMode() const; /** Advances the main loop. */ virtual void Tick() override; /** Removes references to any objects pending cleanup by deleting them. */ virtual void ClearPendingCleanupObjects() override;#endif // WITH_ENGINE /** RHI post-init initialization */ static void PostInitRHI(); /** Pre-init HMD device (if necessary). */ static void PreInitHMDDevice();public: /** Initializes the application. */ static bool AppInit(); /** * Prepares the application for shutdown. * * This function is called from within guarded exit code, only during non-error exits. */ static void AppPreExit(); /** * Shuts down the application. * * This function called outside guarded exit code, during all exits (including error exits). */ static void AppExit();private: /** Utility function that processes Slate operations. */ void ProcessLocalPlayerSlateOperations() const;protected: /** Holds a dynamically expanding array of frame times in milliseconds (if FApp::IsBenchmarking() is set). */ TArray<float> FrameTimes; /** Holds the total time spent ticking engine. */ double TotalTickTime; /** Holds the maximum number of seconds engine should be ticked. */ double MaxTickTime; /** Holds the maximum number of frames to render in benchmarking mode. */ uint64 MaxFrameCounter; /** Holds the number of cycles in the last frame. */ uint32 LastFrameCycles;#if WITH_ENGINE /** Holds the objects which need to be cleaned up when the rendering thread finishes the previous frame. */ FPendingCleanupObjects* PendingCleanupObjects;#endif //WITH_ENGINEprivate:#if WITH_ENGINE /** Holds the engine service. */ FEngineService* EngineService; /** Holds the application session service. */ TSharedPtr<ISessionService> SessionService;#endif // WITH_ENGINE FPreInitContext PreInitContext;}; |
PreInit 阶段
PreInit 有两个核心函数进行初始化,其实主要是分为屏幕显示之前和屏幕显示之后。
PreInitPreStartupScreen主要是完成基础系统(内存,文件,核心模块等)+图像系统基础初始化。让用户更快进入到图像界面,后续更高级的chu shi hua
PreInitPostStartupScreen主要是完成图形系统的高级功能+UObject系统的初始化
并且两个核心函数通过 PreInitContext 保存状态(包括 SlateEnderer)避免重复创建
我们这里主要是了解这些就行,更具体的可以单独出一篇文章来分析。

PreInitPreStartupScreen(启动屏幕前)
这个函数代码量巨大,只能写个函数头了:-<
- 基础系统初始化(日志、命令行、文件系统)
- 内存和追踪系统初始化
- 项目文件加载
- TaskGraph 和 Stats 系统初始化
- 核心模块加载(CoreUObject)
- RHI 初始化(渲染硬件接口)
- SlateRenderer 创建
- 显示启动屏幕(Splash Screen)
1 | int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine) |
PreInitPostStartupScreen(启动屏幕后)
- 恢复 PreInitContext(包括 SlateRenderer)
- 加载需要图形系统的模块(PreEarlyLoadingScreen)
- 播放早期启动电影
- 挂载 Pak 文件
- 打开着色器库
- 初始化 UObject 系统
- 初始化默认材质和流式管理器
1 | int32 FEngineLoop::PreInitPostStartupScreen(const TCHAR* CmdLine) |
核心数据结构
PreInitContext 保存上下文
我们可以清晰看见这里保存了 SlowTaskPtr,SlateRenderer,CommandLineCopy等重要资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | struct FPreInitContext{ bool bDumpEarlyConfigReads = false; bool bDumpEarlyPakFileReads = false; bool bForceQuitAfterEarlyReads = false; bool bWithConfigPatching = false; bool bDisableDisregardForGC = false; bool bHasEditorToken = false; bool bIsRegularClient = false; bool bTokenDoesNotHaveDash = false; FString Token; const TCHAR* CommandletCommandLine = nullptr; TCHAR* CommandLineCopy = nullptr; FScopedSlowTask* SlowTaskPtr = nullptr; void Cleanup();#if WITH_ENGINE && !UE_SERVER TSharedPtr<FSlateRenderer> SlateRenderer;#endif // WITH_ENGINE && !UE_SERVER}; |
CommandLineCopy(命令行副本)
1 | PreInitContext.CommandLineCopy = CommandLineCopy; |
SlateRenderer(图形渲染器)
为什么需要保存捏?
- 创建成本高(需要加载模块、初始化 RHI)
- 需要在两个函数间复用
- 避免重复创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | TSharedPtr<FSlateRenderer> SlateRenderer = GUsingNullRHI ? FModuleManager::Get().LoadModuleChecked<ISlateNullRendererModule>("SlateNullRenderer").CreateSlateNullRenderer() : FModuleManager::Get().GetModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer").CreateSlateRHIRenderer();TSharedRef<FSlateRenderer> SlateRendererSharedRef = SlateRenderer.ToSharedRef();{ SCOPED_BOOT_TIMING("CurrentSlateApp.InitializeRenderer"); // If Slate is being used, initialize the renderer after RHIInit FSlateApplication& CurrentSlateApp = FSlateApplication::Get(); CurrentSlateApp.InitializeRenderer(SlateRendererSharedRef);}{ SCOPED_BOOT_TIMING("FEngineFontServices::Create"); // Create the engine font services now that the Slate renderer is ready FEngineFontServices::Create();}{ SCOPED_BOOT_TIMING("LoadModulesForProject(ELoadingPhase::PostSplashScreen)"); // Load up all modules that need to hook into the custom splash screen if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostSplashScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostSplashScreen)) { return 1; }}{ SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen"); if (FPreLoadScreenManager::Get()) { { SCOPED_BOOT_TIMING("PlayFirstPreLoadScreen - FPreLoadScreenManager::Get()->Initialize"); // initialize and present custom splash screen FPreLoadScreenManager::Get()->Initialize(SlateRendererSharedRef.Get()); } if (FPreLoadScreenManager::Get()->HasRegisteredPreLoadScreenType(EPreLoadScreenTypes::CustomSplashScreen)) { FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen(EPreLoadScreenTypes::CustomSplashScreen); }#if PLATFORM_XBOXONE && WITH_LEGACY_XDK && ENABLE_XBOXONE_FAST_ACTIVATION else { UE_LOG(LogInit, Warning, TEXT("Enable fast activation without enabling a custom splash screen may cause garbage frame buffer being presented")); }#endif }}PreInitContext.SlateRenderer = SlateRenderer; |
SlowTaskPtr(进度条对象)
- 需要在两个函数间保持进度连续性
- 避免重复创建进度条
1 2 | PreInitContext.SlowTaskPtr = new FScopedSlowTask(100, NSLOCTEXT("EngineLoop", "EngineLoop_Initializing", "Initializing..."));FScopedSlowTask& SlowTask = *PreInitContext.SlowTaskPtr; |
调用链
PreInitPre
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | FEngineLoop::PreInitPreStartupScreen (1434行)│├─ 1. 基础初始化阶段│ ├─ FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates (1436行)│ ├─ GLog->SetCurrentThreadAsMasterThread() (1446行)│ ├─ FWindowsPlatformMisc::SetGracefulTerminationHandler() (1456行) [Windows]│ ├─ FMemory::SetupTLSCachesOnCurrentThread() (1468行)│ ├─ FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir() (1476行)│ └─ FCommandLine::Set(CmdLine) (1480行) → 失败返回-1│├─ 2. Trace和内存系统│ ├─ FTraceAuxiliary::Initialize(CmdLine) (1501行)│ ├─ FLowLevelMemTracker::Get().ProcessCommandLine() (1506行)│ └─ FPlatformMisc::InitTaggedStorage(1024) (1515行)│├─ 3. 委托注册│ ├─ FCoreUObjectDelegates::PostGarbageCollectConditionalBeginDestroy.AddStatic() (1519行)│ └─ FCoreDelegates::OnSamplingInput.AddStatic() (1521行)│├─ 4. 游戏名称设置│ └─ LaunchSetGameName(CmdLine, ...) (1555行) → 失败返回1│├─ 5. 输出设备初始化│ ├─ FPlatformApplicationMisc::CreateConsoleOutputDevice() (1566行)│ ├─ GLog->EnableBacklog(true) (1572行)│ └─ InitializeStdOutDevice() (1580行) [如果stdout参数]│├─ 6. 文件系统初始化│ ├─ LaunchCheckForFileOverride() (1705行) → 失败返回1│ ├─ FModuleManager::Get().AddExtraBinarySearchPaths() (1716行)│ ├─ IFileManager::Get().ProcessCommandLineOptions() (1723行)│ └─ FPlatformFileManager::Get().InitializeNewAsyncIO() (1729行)│├─ 7. 线程设置│ ├─ GGameThreadId = FPlatformTLS::GetCurrentThreadId() (1749行)│ ├─ FPlatformProcess::SetThreadAffinityMask() (1752行)│ └─ FPlatformProcess::SetupGameThread() (1753行)│├─ 8. 命令行解析│ ├─ FParse::Token() (1761行)│ ├─ AddShaderSourceDirectoryMapping() (1765行)│ └─ UCommandlet::ParseCommandLine() (1769行)│├─ 9. 项目文件加载│ └─ IProjectManager::Get().LoadProjectFile() (2020行) → 失败返回1│├─ 10. TaskGraph和Stats│ ├─ FTaskGraphInterface::Startup() (2059行)│ └─ FThreadStats::StartThread() (2066行)│└─ 11. 核心模块加载 └─ LoadCoreModules() (2076行) └─ FModuleManager::Get().LoadModule("CoreUObject") (3674行) → 失败返回1 |
PreInitPost
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | FEngineLoop::PreInitPostStartupScreen (2819行)│├─ 1. 检查退出请求│ └─ IsEngineExitRequested() (2823行) → 如果已请求则返回0│├─ 2. 图形系统初始化 [非服务器且非Commandlet]│ ├─ GetMoviePlayer()->SetupLoadingScreenFromIni() (2863行)│ ├─ IProjectManager::Get().LoadModulesForProject(PreEarlyLoadingScreen) (2869行)│ └─ IPluginManager::Get().LoadModulesForEnabledPlugins(PreEarlyLoadingScreen) (2869行) → 失败返回1│├─ 3. 启动屏幕处理│ ├─ GetMoviePlayer()->PlayEarlyStartupMovies() (2891行) [如果有早期电影]│ └─ FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen() (2970行) [否则]│├─ 4. Pak文件和配置│ ├─ FCoreDelegates::OnMountAllPakFiles.Execute() (3006行) [如果没有Bundle管理器]│ ├─ DumpEarlyReads() (3013行)│ └─ HandleConfigReload() (3019行) [如果有配置补丁]│├─ 5. 着色器系统│ ├─ FShaderCodeLibrary::OpenLibrary() (3028行)│ └─ FShaderPipelineCache::OpenPipelineFileCache() (3035行)│├─ 6. UObject系统初始化│ ├─ BeginInitGameTextLocalization() (3052行)│ ├─ FPackageName::RegisterShortPackageNamesForUObjectModules() (3058行)│ ├─ ProcessNewlyLoadedUObjects() (3078行)│ └─ EndInitGameTextLocalization() (3080行)│└─ 7. 材质和流式管理 ├─ UMaterialInterface::InitDefaultMaterials() (3109行) └─ IStreamingManager::Get() (3118行) |
Init 阶段
算是 UE 的真正初始化,会进行引擎的配置各种初始化和核心的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | int32 FEngineLoop::Init(){ LLM_SCOPE(ELLMTag::EngineInitMemory); SCOPED_BOOT_TIMING("FEngineLoop::Init"); DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FEngineLoop::Init" ), STAT_FEngineLoop_Init, STATGROUP_LoadTime ); FScopedSlowTask SlowTask(100); SlowTask.EnterProgressFrame(10); FEmbeddedCommunication::ForceTick(10); // Figure out which UEngine variant to use. UClass* EngineClass = nullptr; if( !GIsEditor ) { SCOPED_BOOT_TIMING("Create GEngine"); // We're the game. FString GameEngineClassName; GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni); EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName); if (EngineClass == nullptr) { UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *GameEngineClassName); } GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass); } else {#if WITH_EDITOR // We're UnrealEd. FString UnrealEdEngineClassName; GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni); EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName); if (EngineClass == nullptr) { UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *UnrealEdEngineClassName); } GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);#else check(0);#endif } FEmbeddedCommunication::ForceTick(11); check( GEngine ); GetMoviePlayer()->PassLoadingScreenWindowBackToGame(); if (FPreLoadScreenManager::Get()) { FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame(); } { SCOPED_BOOT_TIMING("GEngine->ParseCommandline()"); GEngine->ParseCommandline(); } FEmbeddedCommunication::ForceTick(12); { SCOPED_BOOT_TIMING("InitTime"); InitTime(); } SlowTask.EnterProgressFrame(60); { SCOPED_BOOT_TIMING("GEngine->Init"); GEngine->Init(this); } // Call init callbacks FCoreDelegates::OnPostEngineInit.Broadcast(); SlowTask.EnterProgressFrame(30); // initialize engine instance discovery if (FPlatformProcess::SupportsMultithreading()) { SCOPED_BOOT_TIMING("SessionService etc"); if (!IsRunningCommandlet()) { SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService(); if (SessionService.IsValid()) { SessionService->Start(); } } EngineService = new FEngineService(); } { SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit)"); // Load all the post-engine init modules if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit)) { RequestEngineExit(TEXT("One or more modules failed PostEngineInit")); return 1; } } { SCOPED_BOOT_TIMING("GEngine->Start()"); GEngine->Start(); } FEmbeddedCommunication::ForceTick(13); if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen)) { SCOPED_BOOT_TIMING("WaitForEngineLoadingScreenToFinish"); FPreLoadScreenManager::Get()->SetEngineLoadingComplete(true); FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish(); } else { SCOPED_BOOT_TIMING("WaitForMovieToFinish"); GetMoviePlayer()->WaitForMovieToFinish(); } FTraceAuxiliary::EnableChannels();#if !UE_SERVER // initialize media framework IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media"); if (MediaModule != nullptr) { MediaModule->SetTimeSource(MakeShareable(new FAppMediaTimeSource)); }#endif FEmbeddedCommunication::ForceTick(14); // initialize automation worker#if WITH_AUTOMATION_WORKER FModuleManager::Get().LoadModule("AutomationWorker");#endif // Automation tests can be invoked locally in non-editor builds configuration (e.g. performance profiling in Test configuration)#if WITH_ENGINE && !UE_BUILD_SHIPPING FModuleManager::Get().LoadModule("AutomationController"); FModuleManager::GetModuleChecked<IAutomationControllerModule>("AutomationController").Init();#endif#if WITH_EDITOR if (GIsEditor) { FModuleManager::Get().LoadModule(TEXT("ProfilerClient")); } FModuleManager::Get().LoadModule(TEXT("SequenceRecorder")); FModuleManager::Get().LoadModule(TEXT("SequenceRecorderSections"));#endif GIsRunning = true; if (!GIsEditor) { // hide a couple frames worth of rendering FViewport::SetGameRenderingEnabled(true, 3); } FEmbeddedCommunication::ForceTick(15); FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved); // Ready to measure thread heartbeat FThreadHeartBeat::Get().Start(); FShaderPipelineCache::PauseBatching(); {#if defined(WITH_CODE_GUARD_HANDLER) && WITH_CODE_GUARD_HANDLER void CheckImageIntegrity(); CheckImageIntegrity();#endif } { SCOPED_BOOT_TIMING("FCoreDelegates::OnFEngineLoopInitComplete.Broadcast()"); FCoreDelegates::OnFEngineLoopInitComplete.Broadcast(); } FShaderPipelineCache::ResumeBatching();#if BUILD_EMBEDDED_APP FEmbeddedCommunication::AllowSleep(TEXT("Startup")); FEmbeddedCommunication::KeepAwake(TEXT("FirstTicks"), true);#endif #if UE_EXTERNAL_PROFILING_ENABLED FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::InitActiveProfiler(); if (ActiveProfiler) { ActiveProfiler->Register(); }#endif // UE_EXTERNAL_PROFILING_ENABLED FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::EndOfEngineInit); // Emit logging. Don't edit! Automation looks for this to detect failures during initialization. UE_LOG(LogInit, Display, TEXT("Engine is initialized. Leaving FEngineLoop::Init()")); return 0;} |
函数调用链
FEngineLoop::Init() (LaunchEngineLoop.cpp:3946)
│
├─ 模块1: 创建引擎对象 (3958-3988行)
│ ├─ [游戏模式]
│ │ ├─ GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), ...) (3965行)
│ │ ├─ StaticLoadClass(UGameEngine::StaticClass(), ...) (3966行)
│ │ └─ NewObject<UEngine>(GetTransientPackage(), EngineClass) (3971行)
│ │
│ └─ [编辑器模式]
│ ├─ GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), ...) (3978行)
│ ├─ StaticLoadClass(UUnrealEdEngine::StaticClass(), ...) (3979行)
│ └─ NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass) (3984行)
│
├─ 模块2: 传递窗口控制 (3994-3999行)
│ ├─ GetMoviePlayer()->PassLoadingScreenWindowBackToGame() (3994行)
│ └─ FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame() (3998行)
│
├─ 模块3: 引擎配置初始化 (4003-4010行)
│ ├─ GEngine->ParseCommandline() (4003行)
│ └─ InitTime() (4010行)
│ ├─ FApp::SetCurrentTime(FPlatformTime::Seconds())
│ ├─ 解析-SECONDS=、-BENCHMARKSECONDS=、-FPS=参数
│ └─ 计算MaxFrameCounter
│
├─ 模块4: 引擎核心初始化 (4017行)
│ └─ GEngine->Init(this) (4017行)
│ │
│ ├─ [UGameEngine::Init] (GameEngine.cpp:1066)
│ │ ├─ UEngine::Init(InEngineLoop) [基类]
│ │ ├─ GetGameUserSettings()->LoadSettings() (1083行)
│ │ ├─ NewObject<UGameInstance>(this, GameInstanceClass) (1097行)
│ │ ├─ GameInstance->InitializeStandalone() (1099行)
│ │ ├─ NewObject<UGameViewportClient>(this, GameViewportClientClass) (1121行)
│ │ ├─ ViewportClient->Init(...) (1122行)
│ │ ├─ CreateGameWindow() (1137行)
│ │ ├─ CreateGameViewport(ViewportClient) (1140行)
│ │ └─ ViewportClient->SetupInitialLocalPlayer(Error) (1148行)
│ │
│ └─ [UUnrealEdEngine::Init] (UnrealEdEngine.cpp:75)
│ ├─ Super::Init(InEngineLoop) [基类]
│ ├─ ValidateFreeDiskSpace() (80行)
│ ├─ FSourceCodeNavigation::Initialize() (83行)
│ └─ PackageAutoSaver = new FPackageAutoSaver() (85行)
│
├─ 模块5: 服务与模块加载 (4021-4050行)
│ ├─ FCoreDelegates::OnPostEngineInit.Broadcast() (4021行)
│ ├─ SessionService初始化 (4026-4037行)
│ │ ├─ FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices") (4031行)
│ │ └─ SessionService->Start() (4035行)
│ ├─ EngineService = new FEngineService() (4039行)
│ └─ PostEngineInit模块加载 (4043-4050行)
│ ├─ IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) (4045行)
│ └─ IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit) (4045行)
│ └─ 失败则RequestEngineExit并返回1 (4047行)
│
├─ 模块6: 启动引擎 (4054行)
│ └─ GEngine->Start() (4054行)
│ │
│ └─ [UGameEngine::Start] (GameEngine.cpp:1162)
│ └─ GameInstance->StartGameInstance() (1166行)
│
├─ 模块7: 等待加载完成 (4059-4069行)
│ ├─ {有EngineLoadingScreen?}
│ │ ├─ [是] FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish() (4063行)
│ │ └─ [否] GetMoviePlayer()->WaitForMovieToFinish() (4068行)
│
└─ 模块8: 后续初始化 (4071-4148行)
├─ FTraceAuxiliary::EnableChannels() (4071行)
├─ IMediaModule->SetTimeSource(...) (4079行) [非服务器]
├─ FModuleManager::Get().LoadModule("AutomationWorker") (4087行)
├─ FModuleManager::Get().LoadModule("AutomationController") (4092行) [非Shipping]
├─ FModuleManager::Get().LoadModule("ProfilerClient") (4099行) [编辑器]
├─ GIsRunning = true (4106行)
├─ FViewport::SetGameRenderingEnabled(true, 3) (4111行) [非编辑器]
├─ FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved) (4116行)
├─ FThreadHeartBeat::Get().Start() (4119行)
├─ CheckImageIntegrity() (4125行) [如果WITH_CODE_GUARD_HANDLER]
├─ FCoreDelegates::OnFEngineLoopInitComplete.Broadcast() (4131行)
└─ FDelayedAutoRegisterHelper::RunAndClearDelayedAutoRegisterDelegates(EDelayedRegisterRunPhase::EndOfEngineInit) (4148行)
创建引擎实列
- 根据 GIsEditor 决定创建 UGameEngine 或 UUnrealEdEngine
- 从配置文件读取引擎类名(默认 GameEngine/UnrealEdEngine)
- 使用 StaticLoadClass 加载类,NewObject 创建实例
- 编辑器模式下同时设置 GEditor 和 GUnrealEd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // Figure out which UEngine variant to use.UClass* EngineClass = nullptr;if( !GIsEditor ){ SCOPED_BOOT_TIMING("Create GEngine"); // We're the game. FString GameEngineClassName; GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni); EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName); if (EngineClass == nullptr) { UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *GameEngineClassName); } GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);}else{#if WITH_EDITOR // We're UnrealEd. FString UnrealEdEngineClassName; GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni); EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName); if (EngineClass == nullptr) { UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *UnrealEdEngineClassName); } GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);#endif} |
传递加载画面窗口
将加载画面窗口所有权交还给游戏窗口系统。
这里其实很好理解因为我们刚启动的时候是一个窗口主要复杂启动等一系列工作,然后游戏画面照理来说会是一个崭新的窗口,但是创建多个窗口太割裂了。于是我们就复用加载画面的窗口来作为游戏主窗口。

1 2 3 4 5 6 | GetMoviePlayer()->PassLoadingScreenWindowBackToGame();if (FPreLoadScreenManager::Get()){ FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame();} |
解析命令行
让引擎实例解析命令行参数,设置相关配置。方便后续系统进行读取命令行参数
1 2 3 4 | { SCOPED_BOOT_TIMING("GEngine->ParseCommandline()"); GEngine->ParseCommandline();} |
初始化时间系统
- 初始化时间相关变量(当前时间、帧计数器、Tick 时间限制)
- 从命令行读取基准测试参数(-SECONDS=、-BENCHMARKSECONDS=、-FPS=)
- 设置固定帧率(如果指定)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | { SCOPED_BOOT_TIMING("InitTime"); InitTime();} void FEngineLoop::InitTime(){ // Init variables used for benchmarking and ticking. FApp::SetCurrentTime(FPlatformTime::Seconds()); MaxFrameCounter = 0; MaxTickTime = 0; TotalTickTime = 0; LastFrameCycles = FPlatformTime::Cycles(); float FloatMaxTickTime = 0;#if (!UE_BUILD_SHIPPING || ENABLE_PGO_PROFILE) FParse::Value(FCommandLine::Get(),TEXT("SECONDS="),FloatMaxTickTime); MaxTickTime = FloatMaxTickTime; // look of a version of seconds that only is applied if FApp::IsBenchmarking() is set. if (FApp::IsBenchmarking()) { if (FParse::Value(FCommandLine::Get(),TEXT("BENCHMARKSECONDS="),FloatMaxTickTime) && FloatMaxTickTime) { MaxTickTime = FloatMaxTickTime; } } // Use -FPS=X to override fixed tick rate if e.g. -BENCHMARK is used. float FixedFPS = 0; FParse::Value(FCommandLine::Get(),TEXT("FPS="),FixedFPS); if( FixedFPS > 0 ) { FApp::SetFixedDeltaTime(1 / FixedFPS); }#endif // convert FloatMaxTickTime into number of frames (using 1 / FApp::GetFixedDeltaTime() to convert fps to seconds ) MaxFrameCounter = FMath::TruncToInt(MaxTickTime / FApp::GetFixedDeltaTime());} |
Uengine::Init (引擎初始化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | { SCOPED_BOOT_TIMING("GEngine->Init"); GEngine->Init(this);}//// Initialize the engine.//void UEngine::Init(IEngineLoop* InEngineLoop){ UE_LOG(LogEngine, Log, TEXT("Initializing Engine...")); DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Engine Initialized"), STAT_EngineStartup, STATGROUP_LoadTime); // Start capturing errors and warnings#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) ErrorsAndWarningsCollector.Initialize();#endif#if WITH_EDITOR if(!FEngineBuildSettings::IsInternalBuild()) { TArray<TSharedRef<IPlugin>> EnabledPlugins = IPluginManager::Get().GetEnabledPlugins(); for (auto Plugin : EnabledPlugins) { const FPluginDescriptor& Desc = Plugin->GetDescriptor(); // encode a minimal plugin description for the crash reporter FString DescStr; TSharedRef< TJsonWriter<> > WriterRef = TJsonWriterFactory<>::Create(&DescStr); TJsonWriter<>& Writer = WriterRef.Get(); Writer.WriteObjectStart(); Writer.WriteValue(TEXT("Version"), Desc.Version); Writer.WriteValue(TEXT("VersionName"), Desc.VersionName); Writer.WriteValue(TEXT("FriendlyName"), Desc.FriendlyName); Writer.WriteObjectEnd(); Writer.Close(); FGenericCrashContext::AddPlugin(DescStr); } }#endif // Set the memory warning handler if (!FPlatformMisc::HasMemoryWarningHandler()) { FPlatformMisc::SetMemoryWarningHandler(EngineMemoryWarningHandler); } EngineLoop = InEngineLoop; // Subsystems. FURL::StaticInit(); FLinkerLoad::StaticInit(UTexture2D::StaticClass()); EngineSubsystemCollection->Initialize(this);#if !UE_BUILD_SHIPPING // Check for overrides to the default map on the command line TCHAR MapName[512]; if ( FParse::Value(FCommandLine::Get(), TEXT("DEFAULTMAP="), MapName, UE_ARRAY_COUNT(MapName)) ) { UE_LOG(LogEngine, Log, TEXT("Overriding default map to %s"), MapName); FString MapString = FString(MapName); UGameMapsSettings::SetGameDefaultMap(MapString); }#endif // !UE_BUILD_SHIPPING InitializeRunningAverageDeltaTime(); // Add to root. AddToRoot(); FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddStatic(UEngine::PreGarbageCollect); if (!FApp::IsProjectNameEmpty()) { // Initialize the HMDs and motion controllers, if any InitializeHMDDevice(); // Initialize attached eye tracking devices, if any InitializeEyeTrackingDevice(); } // Disable the screensaver when running the game. if( GIsClient && !GIsEditor ) { EnableScreenSaver( false ); } if (!IsRunningDedicatedServer() && !IsRunningCommandlet()) { // If Slate is being used, initialize the renderer after RHIInit FSlateApplication& CurrentSlateApp = FSlateApplication::Get(); CurrentSlateApp.InitializeSound( TSharedRef<FSlateSoundDevice>( new FSlateSoundDevice() ) );#if !UE_BUILD_SHIPPING // Create test windows (if we were asked to do that) if( FParse::Param( FCommandLine::Get(), TEXT("SlateDebug") ) ) { RestoreSlateTestSuite(); }#endif // #if !UE_BUILD_SHIPPING } // Assign thumbnail compressor/decompressor FObjectThumbnail::SetThumbnailCompressor( new FPNGThumbnailCompressor() ); //UEngine::StaticClass()->GetDefaultObject(true); LoadObject<UClass>(UEngine::StaticClass()->GetOuter(), *UEngine::StaticClass()->GetName(), NULL, LOAD_Quiet|LOAD_NoWarn, NULL ); // This reads the Engine.ini file to get the proper DefaultMaterial, etc. LoadConfig(); SetConfiguredProcessLimits(); bIsOverridingSelectedColor = false; // Set colors for selection materials SelectedMaterialColor = DefaultSelectedMaterialColor; SelectionOutlineColor = DefaultSelectedMaterialColor; InitializeObjectReferences(); if (GConfig) { bool bTemp = true; GConfig->GetBool(TEXT("/Script/Engine.Engine"), TEXT("bEnableOnScreenDebugMessages"), bTemp, GEngineIni); bEnableOnScreenDebugMessages = bTemp ? true : false; bEnableOnScreenDebugMessagesDisplay = bEnableOnScreenDebugMessages; } // Update Script Maximum loop iteration count FBlueprintCoreDelegates::SetScriptMaximumLoopIterations( GEngine->MaximumLoopIterationCount ); GNearClippingPlane = NearClipPlane; UTextRenderComponent::InitializeMIDCache(); // Initialize scene cached cvars extern FReadOnlyCVARCache GReadOnlyCVARCache; GReadOnlyCVARCache.Init(); if (GIsEditor) { // Create a WorldContext for the editor to use and create an initially empty world. FWorldContext &InitialWorldContext = CreateNewWorldContext(EWorldType::Editor); InitialWorldContext.SetCurrentWorld( UWorld::CreateWorld( EWorldType::Editor, true ) ); GWorld = InitialWorldContext.World(); } // Initialize the audio device after a world context is setup InitializeAudioDeviceManager(); // Make sure networking checksum has access to project version const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>(); FNetworkVersion::SetProjectVersion(*ProjectSettings.ProjectVersion);#if !(UE_BUILD_SHIPPING) || ENABLE_PGO_PROFILE // Optionally Exec an exec file FString Temp; if( FParse::Value(FCommandLine::Get(), TEXT("EXEC="), Temp) ) { new(GEngine->DeferredCommands) FString(FString(TEXT("exec ")) + Temp); } // Optionally exec commands passed in the command line. FString ExecCmds; if( FParse::Value(FCommandLine::Get(), TEXT("ExecCmds="), ExecCmds, false) ) { // Read the command array, ignoring any commas in single-quotes. // Convert any single-quotes to double-quotes and skip leading whitespace // This allows passing of strings, e:g -execcmds="exampleCvar '0,1,2,3'" TArray<FString> CommandArray; FString CurrentCommand = ""; bool bInQuotes = false; bool bSkippingWhitespace = true; for (int i = 0; i < ExecCmds.Len(); i++) { TCHAR CurrentChar = ExecCmds[i]; if (CurrentChar == '\'') { bInQuotes = !bInQuotes; CurrentCommand += "\""; } else if (CurrentChar == ',' && !bInQuotes) { if (CurrentCommand.Len() > 0) { CommandArray.Add(CurrentCommand); CurrentCommand = ""; } bSkippingWhitespace = true; } else { if (bSkippingWhitespace) { bSkippingWhitespace = FChar::IsWhitespace(CurrentChar); } if (!bSkippingWhitespace) { CurrentCommand += CurrentChar; } } } if (CurrentCommand.Len() > 0) { CommandArray.Add(CurrentCommand); } for( int32 Cx = 0; Cx < CommandArray.Num(); ++Cx ) { new(GEngine->DeferredCommands) FString(*CommandArray[Cx]); } } // optionally set the vsync console variable if( FParse::Param(FCommandLine::Get(), TEXT("vsync")) ) { new(GEngine->DeferredCommands) FString(TEXT("r.vsync 1")); } // optionally set the vsync console variable if( FParse::Param(FCommandLine::Get(), TEXT("novsync")) ) { new(GEngine->DeferredCommands) FString(TEXT("r.vsync 0")); }#endif // !(UE_BUILD_SHIPPING) || ENABLE_PGO_PROFILE if (GetDerivedDataCache()) { GetDerivedDataCacheRef().NotifyBootComplete(); } // register the engine with the travel and network failure broadcasts // games can override these to provide proper behavior in each error case OnTravelFailure().AddUObject(this, &UEngine::HandleTravelFailure); OnNetworkFailure().AddUObject(this, &UEngine::HandleNetworkFailure); OnNetworkLagStateChanged().AddUObject(this, &UEngine::HandleNetworkLagStateChanged); UE_LOG(LogInit, Log, TEXT("Texture streaming: %s"), IStreamingManager::Get().IsTextureStreamingEnabled() ? TEXT("Enabled") : TEXT("Disabled") ); // Initialize the online subsystem as early as possible FOnlineExternalUIChanged OnExternalUIChangeDelegate; OnExternalUIChangeDelegate.BindUObject(this, &UEngine::OnExternalUIChange); UOnlineEngineInterface::Get()->BindToExternalUIOpening(OnExternalUIChangeDelegate); // Initialise buffer visualization system data GetBufferVisualizationData().Initialize(); // Initialize Portal services if (!IsRunningCommandlet() && !IsRunningDedicatedServer()) { InitializePortalServices(); } // Connect the engine analytics provider FEngineAnalytics::Initialize(); // Dynamically load engine runtime modules { FModuleManager::Get().LoadModule("ImageWriteQueue"); FModuleManager::Get().LoadModuleChecked("StreamingPauseRendering"); FModuleManager::Get().LoadModuleChecked("MovieScene"); FModuleManager::Get().LoadModuleChecked("MovieSceneTracks"); FModuleManager::Get().LoadModule("LevelSequence"); } // Enable the live coding module if it's a developer build#if WITH_LIVE_CODING FModuleManager::Get().LoadModule("LiveCoding");#endif // Finish asset manager loading if (AssetManager) { AssetManager->FinishInitialLoading(); } bool bIsRHS = true; if (GConfig) { GConfig->GetBool(TEXT("DevOptions.Debug"), TEXT("bEngineStatsOnRHS"), bIsRHS, GEngineIni); } // Add the stats to the list, note this is also the order that they get rendered in if active.#if !UE_BUILD_SHIPPING EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Version"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatVersion), FEngineStatToggle(), bIsRHS));#endif // !UE_BUILD_SHIPPING EngineStats.Add(FEngineStatFuncs(TEXT("STAT_NamedEvents"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatNamedEvents), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatNamedEvents), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_FPS"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatFPS), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatFPS), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Summary"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSummary), FEngineStatToggle(), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Unit"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatUnit), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatUnit), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_DrawCount"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatDrawCount), FEngineStatToggle(), bIsRHS)); /* @todo Slate Rendering #if STATS EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SlateBatches"), TEXT("STATCAT_Engine"), FText::GetEmpty(), &UEngine::RenderStatSlateBatches, NULL, true)); #endif */ EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Hitches"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatHitches), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatHitches), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_AI"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatAI), FEngineStatToggle(), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Timecode"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatTimecode), FEngineStatToggle(), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_FrameCounter"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatFrameCounter), FEngineStatToggle(), bIsRHS)); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_ColorList"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatColorList), FEngineStatToggle())); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Levels"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatLevels), FEngineStatToggle()));#if !UE_BUILD_SHIPPING EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundMixes"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSoundMixes), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatSoundMixes))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundModulators"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSoundModulators), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatSoundModulators))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundModulatorsHelp"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::PostStatSoundModulatorHelp))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_AudioStreaming"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatAudioStreaming), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatAudioStreaming))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundReverb"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSoundReverb), FEngineStatToggle())); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Sounds"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSounds), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatSounds))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundCues"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSoundCues), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatSoundCues))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_SoundWaves"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatSoundWaves), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatSoundWaves)));#endif // !UE_BUILD_SHIPPING /* @todo UE4 physx fix this once we have convexelem drawing again EngineStats.Add(FEngineStatFuncs(TEXT("STAT_LevelMap"), TEXT("STATCAT_Engine"), FText::GetEmpty(), &UEngine::RenderStatLevelMap, NULL)); */ EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Detailed"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatDetailed)));#if !UE_BUILD_SHIPPING EngineStats.Add(FEngineStatFuncs(TEXT("STAT_UnitMax"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatUnitMax))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_UnitGraph"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatUnitGraph))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_UnitTime"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatUnitTime))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_Raw"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender(), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatRaw))); EngineStats.Add(FEngineStatFuncs(TEXT("STAT_ParticlePerf"), TEXT("STATCAT_Engine"), FText::GetEmpty(), FEngineStatRender::CreateUObject(this, &UEngine::RenderStatParticlePerf), FEngineStatToggle::CreateUObject(this, &UEngine::ToggleStatParticlePerf), bIsRHS));#endif // !UE_BUILD_SHIPPING // Let any listeners know about the new stats for (int32 StatIdx = 0; StatIdx < EngineStats.Num(); StatIdx++) { const FEngineStatFuncs& EngineStat = EngineStats[StatIdx]; NewStatDelegate.Broadcast(EngineStat.CommandName, EngineStat.CategoryName, EngineStat.DescriptionString); } // Command line option for enabling named events if (FParse::Param(FCommandLine::Get(), TEXT("statnamedevents"))) { GCycleStatsShouldEmitNamedEvents = 1; }#if UE_NET_TRACE_ENABLED uint32 NetTraceVerbosity; if(FParse::Value(FCommandLine::Get(), TEXT("NetTrace="), NetTraceVerbosity)) { FNetTrace::SetTraceVerbosity(NetTraceVerbosity); }#endif // Record the analytics for any attached HMD devices RecordHMDAnalytics();#if !UE_BUILD_SHIPPING UE_CLOG(FPlatformMemory::IsExtraDevelopmentMemoryAvailable(), LogInit, Warning, TEXT("Running with %dMB of extra development memory!"),FPlatformMemory::GetExtraDevelopmentMemorySize()/1024ull/1024ull);#endif} |
Init 后续
PostEngineInit (广播init结束委托)
通知模块系统已经init结束。
1 2 | // Call init callbacksFCoreDelegates::OnPostEngineInit.Broadcast(); |
初始化 SessionService
SessionService 是一个基于消息总线的服务,用于引擎实例发现和远程通信。它允许远程工具(如 UnrealFrontend、编辑器)发现并连接到运行中的引擎实例。
比如日志转发,ping/pong响应,远程通信等等。仅在支持多线程的平台启用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // initialize engine instance discoveryif (FPlatformProcess::SupportsMultithreading()){ SCOPED_BOOT_TIMING("SessionService etc"); if (!IsRunningCommandlet()) { SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService(); if (SessionService.IsValid()) { SessionService->Start(); } } EngineService = new FEngineService();} |
加载 PostEngineInit 模块
也就是加载需要在引擎初始化后加载的项目和插件模块
1 2 3 4 5 6 7 8 9 | { SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit)"); // Load all the post-engine init modules if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit)) { RequestEngineExit(TEXT("One or more modules failed PostEngineInit")); return 1; }} |
启动引擎
在这里就正式启动 GameInstance 开始游戏逻辑了。
1 2 3 4 | { SCOPED_BOOT_TIMING("GEngine->Start()"); GEngine->Start();} |