虚幻4引擎源码学习笔记:主循环LaunchEngineLoop
Posted wolf96
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚幻4引擎源码学习笔记:主循环LaunchEngineLoop相关的知识,希望对你有一定的参考价值。
虚幻引擎主循环为LaunchEngineLoop.cpp,LaunchEngineLoop.h
LaunchEngineLoop.cpp里面有3000+行代码,包含了整个虚幻引擎生命周期的流程
依.cpp顺序依次看一下
引用库和变量定义
大部分功能的库都被引用
#include "LaunchEngineLoop.h"
#include "HAL/PlatformStackWalk.h"
#include "HAL/PlatformOutputDevices.h"
#include "HAL/LowLevelMemTracker.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/QueuedThreadPool.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformAffinity.h"
#include "Misc/FileHelper.h"
#include "Internationalization/TextLocalizationManagerGlobals.h"
#include "Logging/LogSuppressionInterface.h"
#include "Async/TaskGraphInterfaces.h"
#include "Misc/TimeGuard.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/OutputDeviceHelper.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/AutomationTest.h"
#include "Misc/CommandLine.h"
#include "Misc/App.h"
#include "Misc/OutputDeviceConsole.h"
#include "HAL/PlatformFilemanager.h"
#include "Templates/ScopedPointer.h"
#include "HAL/FileManagerGeneric.h"
#include "HAL/ExceptionHandling.h"
#include "Stats/StatsMallocProfilerProxy.h"
#include "HAL/PlatformSplash.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/ThreadManager.h"
#include "ProfilingDebugging/ExternalProfiler.h"
#include "Containers/Ticker.h"
#include "Interfaces/IPluginManager.h"
#include "ProjectDescriptor.h"
#include "Interfaces/IProjectManager.h"
#include "Misc/UProjectInfo.h"
#include "Misc/EngineVersion.h"
#include "Misc/CoreDelegates.h"
#include "Modules/ModuleManager.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Modules/BuildVersion.h"
#include "UObject/DevObjectVersion.h"
#include "HAL/ThreadHeartBeat.h"
#include "Misc/NetworkVersion.h"
#include "Templates/UniquePtr.h"
#if !(IS_PROGRAM || WITH_EDITOR)
#include "IPlatformFilePak.h"
#endif
#if WITH_COREUOBJECT
#include "Internationalization/PackageLocalizationManager.h"
#include "Misc/PackageName.h"
#include "UObject/UObjectHash.h"
#include "UObject/Package.h"
#include "UObject/Linker.h"
#include "UObject/LinkerLoad.h"
#endif
#if WITH_EDITOR
#include "Blueprint/BlueprintSupport.h"
#include "EditorStyleSet.h"
#include "Misc/RemoteConfigIni.h"
#include "EditorCommandLineUtils.h"
#include "Input/Reply.h"
#include "Styling/CoreStyle.h"
#include "RenderingThread.h"
#include "Editor/EditorEngine.h"
#include "UnrealEdMisc.h"
#include "UnrealEdGlobals.h"
#include "Editor/UnrealEdEngine.h"
#include "Settings/EditorExperimentalSettings.h"
#include "Interfaces/IEditorStyleModule.h"
#include "PIEPreviewDeviceProfileSelectorModule.h"
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <objbase.h>
#include "Windows/HideWindowsPlatformTypes.h"
#endif
#endif //WITH_EDITOR
//引擎相关
#if WITH_ENGINE
#include "Engine/GameEngine.h"
#include "UnrealClient.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/GameUserSettings.h"
#include "Features/IModularFeatures.h"
#include "GameFramework/WorldSettings.h"
#include "SystemSettings.h"
#include "EngineStats.h"
#include "EngineGlobals.h"
#include "AudioThread.h"
#if WITH_ENGINE && !UE_BUILD_SHIPPING
#include "IAutomationControllerModule.h"
#endif // WITH_ENGINE && !UE_BUILD_SHIPPING
#include "Database.h"
#include "DerivedDataCacheInterface.h"
#include "ShaderCompiler.h"
#include "DistanceFieldAtlas.h"
#include "GlobalShader.h"
#include "ShaderCodeLibrary.h"
#include "Materials/MaterialInterface.h"
#include "TextureResource.h"
#include "Engine/Texture2D.h"
#include "Internationalization/StringTable.h"
#include "SceneUtils.h"
#include "ParticleHelper.h"
#include "PhysicsPublic.h"
#include "PlatformFeatures.h"
#include "DeviceProfiles/DeviceProfileManager.h"
#include "Commandlets/Commandlet.h"
#include "EngineService.h"
#include "ContentStreaming.h"
#include "HighResScreenshot.h"
#include "Misc/HotReloadInterface.h"
#include "ISessionServicesModule.h"
#include "Net/OnlineEngineInterface.h"
#include "Internationalization/EnginePackageLocalizationCache.h"
#include "Rendering/SlateRenderer.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "IMessagingModule.h"
#include "Engine/DemoNetDriver.h"
#include "LongGPUTask.h"
#include "RenderUtils.h"
#include "DynamicResolutionState.h"
#include "EngineModule.h"
#if !UE_SERVER
#include "AppMediaTimeSource.h"
#include "IHeadMountedDisplayModule.h"
#include "IMediaModule.h"
#include "HeadMountedDisplay.h"
#include "MRMeshModule.h"
#include "Interfaces/ISlateRHIRendererModule.h"
#include "Interfaces/ISlateNullRendererModule.h"
#include "EngineFontServices.h"
#endif
#include "MoviePlayer.h"
#include "ShaderCodeLibrary.h"
#include "ShaderCache.h"
#include "ShaderPipelineCache.h"
#if !UE_BUILD_SHIPPING
#include "STaskGraph.h"
#include "IProfilerServiceModule.h"
#endif
#if WITH_AUTOMATION_WORKER
#include "IAutomationWorkerModule.h"
#endif
#endif //WITH_ENGINE
//渲染器
class FSlateRenderer;
//视口
class SViewport;
//各个平台相关文件
class IPlatformFile;
//外部分析器
class FExternalProfiler;
//反馈上下文
class FFeedbackContext;
#if WITH_EDITO
#include "FeedbackContextEditor.h"
static FFeedbackContextEditor UnrealEdWarn;
#include "AudioEditorModule.h"
#endif // WITH_EDITOR
#if UE_EDITOR
#include "DesktopPlatformModule.h"
#endif
#define LOCTEXT_NAMESPACE "LaunchEngineLoop"
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <ObjBase.h>
#include "Windows/HideWindowsPlatformTypes.h"
#endif
#if WITH_ENGINE
#include "EngineDefines.h"
#if ENABLE_VISUAL_LOG
#include "VisualLogger/VisualLogger.h"
#endif
#include "ProfilingDebugging/CsvProfiler.h"
#include "ProfilingDebugging/TracingProfiler.h"
#endif
#if defined(WITH_LAUNCHERCHECK) && WITH_LAUNCHERCHECK
#include "ILauncherCheckModule.h"
#endif
#if WITH_COREUOBJECT
#ifndef USE_LOCALIZED_PACKAGE_CACHE
#define USE_LOCALIZED_PACKAGE_CACHE 1
#endif
#else
#define USE_LOCALIZED_PACKAGE_CACHE 0
#endif
#ifndef RHI_COMMAND_LIST_DEBUG_TRACES
#define RHI_COMMAND_LIST_DEBUG_TRACES 0
#endif
#ifndef REAPPLY_INI_SETTINGS_AFTER_EARLY_LOADING_SCREEN
#define REAPPLY_INI_SETTINGS_AFTER_EARLY_LOADING_SCREEN 0
#endif
#if WITH_ENGINE
CSV_DECLARE_CATEGORY_MODULE_EXTERN(CORE_API, Basic);
#endif
//在专用服务器上使用GC
int32 GUseDisregardForGCOnDedicatedServers = 1;
static FAutoConsoleVariableRef CVarUseDisregardForGCOnDedicatedServers(
TEXT("gc.UseDisregardForGCOnDedicatedServers"),
GUseDisregardForGCOnDedicatedServers,
TEXT("If false, DisregardForGC will be disabled for dedicated servers."),
ECVF_Default
);
//增加随机的sleep到tick时钟,DoAsyncEndOfFrameTasks 在任意线程上shake loose bugs 。从游戏线程中刷新随机渲染线程
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksRandomize(
TEXT("tick.DoAsyncEndOfFrameTasks.Randomize"),
0,
TEXT("Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread.")
);
//验证 Slate tick中复制的属性没有改变
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties(
TEXT("tick.DoAsyncEndOfFrameTasks.ValidateReplicatedProperties"),
0,
TEXT("If true, validates that replicated properties haven't changed during the Slate tick. Results will not be valid if demo.ClientRecordAsyncEndOfFrame is also enabled.")
);
//任务和进程的优先级,帧任务后的异步 experiemntal???
static FAutoConsoleTaskPriority CPrio_AsyncEndOfFrameGameTasks(
TEXT("TaskGraph.TaskPriorities.AsyncEndOfFrameGameTasks"),
TEXT("Task and thread priority for the experiemntal async end of frame tasks."),
ENamedThreads::HighThreadPriority,
ENamedThreads::NormalTaskPriority,
ENamedThreads::HighTaskPriority
);
I/O部分方法
序列化和GLog相关
//管道输出到std输出
//使得UBT(UnrealBuildTool)回收他自己使用的输出
// Pipe output to std output
// This enables UBT to collect the output for it's own use
//std输出设备? (I/O部分?)
class FOutputDeviceStdOutput : public FOutputDevice
public:
FOutputDeviceStdOutput()
: AllowedLogVerbosity(ELogVerbosity::Display)
if (FParse::Param(FCommandLine::Get(), TEXT("AllowStdOutLogVerbosity")))
AllowedLogVerbosity = ELogVerbosity::Log;
if (FParse::Param(FCommandLine::Get(), TEXT("FullStdOutLogOutput")))
AllowedLogVerbosity = ELogVerbosity::All;
virtual ~FOutputDeviceStdOutput()
//是否可以在任何线程上使用
virtual bool CanBeUsedOnAnyThread() const override
return true;
//序列化
virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override
if (Verbosity <= AllowedLogVerbosity)
#if PLATFORM_TCHAR_IS_CHAR16
printf("%s\\n", TCHAR_TO_UTF8(*FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes)));
#elif PLATFORM_USE_LS_SPEC_FOR_WIDECHAR
// printf prints wchar_t strings just fine with %ls, while mixing printf()/wprintf() is not recommended (see https://stackoverflow.com/questions/8681623/printf-and-wprintf-in-single-c-code)
printf("%ls\\n", *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
#else
wprintf(TEXT("%s\\n"), *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
#endif
fflush(stdout);
private:
ELogVerbosity::Type AllowedLogVerbosity;
;
//如果在日志输出中出现任何指定的短语,就退出游戏/编辑器
// Exits the game/editor if any of the specified phrases appears in the log output
//输出设备测试退出
class FOutputDeviceTestExit : public FOutputDevice
TArray<FString> ExitPhrases;
public:
FOutputDeviceTestExit(const TArray<FString>& InExitPhrases)
: ExitPhrases(InExitPhrases)
virtual ~FOutputDeviceTestExit()
//序列化
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
if (!GIsRequestingExit)
for (auto& Phrase : ExitPhrases)
if (FCString::Stristr(V, *Phrase) && !FCString::Stristr(V, TEXT("-testexit=")))
#if WITH_ENGINE
if (GEngine != nullptr)
if (GIsEditor)
GEngine->DeferredCommands.Add(TEXT("CLOSE_SLATE_MAINFRAME"));
else
GEngine->Exec(nullptr, TEXT("QUIT"));
#else
FPlatformMisc::RequestExit(true);
#endif
break;
;
//Scoped输出部分变量
static TUniquePtr<FOutputDeviceConsole> GScopedLogConsole;
static TUniquePtr<FOutputDeviceStdOutput> GScopedStdOut;
static TUniquePtr<FOutputDeviceTestExit> GScopedTestExit;
//初始化std输出设备并把它加到GLog
/**
* Initializes std out device and adds it to GLog
**/
void InitializeStdOutDevice()
// Check if something is trying to initialize std out device twice.
check(!GScopedStdOut);
GScopedStdOut = MakeUnique<FOutputDeviceStdOutput>();
//把std加到GLog
GLog->AddOutputDevice(GScopedStdOut.Get());
Slate部分
关于Slate
Slate是一种用户界面架构
Slate 是一种完全自定义的、平台无关的用户界面架构,其设计目的是使得构建工具及应用程序(比如虚幻编辑器)
它结合了一种可以轻松设计、布局及风格化组件的声明式语法,使得可以轻松地创建用户界面并进行迭代开发。
https://blog.csdn.net/pizi0475/article/details/50471207?utm_source=copy
Slate控件可以用于在游戏中创建平头显示信息(HUD)或其他用户界面(UI)元素, 比如菜单。您一般可以创建一个或多个 容器 控件,
每个容器可以包含几个其他类型的控件, 这些控件负责用户界面的特定方面。
https://blog.csdn.net/pizi0475/article/details/50471198?utm_source=copy
//关于Slate
//Slate是一种用户界面架构
//Slate 是一种完全自定义的、平台无关的用户界面架构,其设计目的是使得构建工具及应用程序(比如虚幻编辑器)
//它结合了一种可以轻松设计、布局及风格化组件的声明式语法,使得可以轻松地创建用户界面并进行迭代开发。
//https://blog.csdn.net/pizi0475/article/details/50471207?utm_source=copy
//Slate控件可以用于在游戏中创建平头显示信息(HUD)或其他用户界面(UI)元素, 比如菜单。您一般可以创建一个或多个 容器 控件,
//每个容器可以包含几个其他类型的控件, 这些控件负责用户界面的特定方面。
//https://blog.csdn.net/pizi0475/article/details/50471198?utm_source=copy
//在tick时钟滴答时,任务与Slate同时执行。tick.DoAsyncEndOfFrameTasks为true
/** Task that executes concurrently with Slate when tick.DoAsyncEndOfFrameTasks is true. */
class FExecuteConcurrentWithSlateTickTask
TFunctionRef<void()> TickWithSlate;
public:
FExecuteConcurrentWithSlateTickTask(TFunctionRef<void()> InTickWithSlate)
: TickWithSlate(InTickWithSlate)
//获取stat id
static FORCEINLINE TStatId GetStatId()
RETURN_QUICK_DECLARE_CYCLE_STAT(FExecuteConcurrentWithSlateTickTask, STATGROUP_TaskGraphTasks);
//获取目标线程
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
return CPrio_AsyncEndOfFrameGameTasks.Get();
//获取后序的模式??
static FORCEINLINE ESubsequentsMode::Type GetSubsequentsMode() return ESubsequentsMode::TrackSubsequents;
//执行任务
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
TickWithSlate();
;
RHI部分
关于RHI
RHI: Render hardware interface 渲染硬件层接口
就是包了一层图形API的图形接口,每个平台的图形API是固定的,比如PC是DX,手机是OpenGL es,他们相同功能的
这层图形接口是一样的,引擎只需要关注这层抽象出来的图形接口就可以,
这层图形接口里面有各个平台的不同图形API的相同功能的实现
https://blog.csdn.net/tuanxuan123/article/details/52914553
http://www.manew.com/thread-100777-1-1.html
//关于RHI
//RHI: Render hardware interface 渲染硬件层接口
//就是包了一层图形API的图形接口,每个平台的图形API是固定的,比如PC是DX,手机是OpenGL es,他们相同功能的
//这层图形接口是一样的,引擎只需要关注这层抽象出来的图形接口就可以,
//这层图形接口里面有各个平台的不同图形API的相同功能的实现
//https://blog.csdn.net/tuanxuan123/article/details/52914553
//http://www.manew.com/thread-100777-1-1.html
#if WITH_ENGINE
//退出并且停止RHI线程
static void RHIExitAndStopRHIThread()
#if HAS_GPU_STATS
FRealtimeGPUProfiler::Get()->Release();
#endif
FShaderPipelineCache::Shutdown();
// Stop the RHI Thread (using GRHIThread_InternalUseOnly is unreliable since RT may be stopped)
// //图形接口正在运行&线程正在处理任务
if (FTaskGraphInterface::IsRunning() && FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::RHIThread))
//获取RHI线程
DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread Finish"), STAT_WaitForRHIThreadFinish, STATGROUP_TaskGraphTasks);
FGraphEventRef QuitTask = TGraphTask<FReturnGraphTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(ENamedThreads::RHIThread);
//等到RHI任务结束
FTaskGraphInterface::Get().WaitUntilTaskCompletes(QuitTask, ENamedThreads::GameThread_Local);
//退出停止RHI
RHIExit();
#endif
启动部分
打开项目工程相关的实现,设置&检测游戏名称等等
//从命令行解析游戏工程
bool ParseGameProjectFromCommandLine(const TCHAR* InCmdLine, FString& OutProjectFilePath, FString& OutGameName)
const TCHAR *CmdLine = InCmdLine;
FString FirstCommandLineToken = FParse::Token(CmdLine, 0);
// trim any whitespace at edges of string - this can happen if the token was quoted with leading or trailing whitespace
// VC++ tends to do this in its "external tools" config
FirstCommandLineToken.TrimStartInline();
//输出工程路径
OutProjectFilePath = TEXT("");
//输出工程名
OutGameName = TEXT("");
if ( FirstCommandLineToken.Len() && !FirstCommandLineToken.StartsWith(TEXT("-")) )
//如果项目文件存在的话,第一个命令行参数是项目文件.或者如果不是用一个项目文件启动的话,第一个命令行参数是游戏名
// The first command line argument could be the project file if it exists or the game name if not launching with a project file
const FString ProjectFilePath = FString(FirstCommandLineToken);
if ( FPaths::GetExtension(ProjectFilePath) == FProjectDescriptor::GetExtension() )
OutProjectFilePath = FirstCommandLineToken;
// Here we derive the game name from the project file
// 从项目文件中推导出游戏名称
OutGameName = FPaths::GetBaseFilename(OutProjectFilePath);
return true;
else if (FPaths::IsRelative(FirstCommandLineToken) && FPlatformProperties::IsMonolithicBuild() == false)
// Full game name is assumed to be the first token
// 完整的游戏名为第一个token
OutGameName = MoveTemp(FirstCommandLineToken);
//从游戏名称中派生出项目路径。所有的游戏都必须有一个uproject文件,即使它们在根文件夹中。
// Derive the project path from the game name. All games must have a uproject file, even if they are in the root folder.
OutProjectFilePath = FPaths::Combine(*FPaths::RootDir(), *OutGameName, *FString(OutGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
return true;
#if WITH_EDITOR
//解析游戏工程路径
if (FEditorCommandLineUtils::ParseGameProjectPath(InCmdLine, OutProjectFilePath, OutGameName))
return true;
#endif
return false;
//启动部分:设置游戏名
bool LaunchSetGameName(const TCHAR *InCmdLine, FString& OutGameProjectFilePathUnnormalized)
if (GIsGameAgnosticExe)
// Initialize GameName to an empty string. Populate it below.
FApp::SetProjectName(TEXT(""));
FString ProjFilePath;
FString LocalGameName;
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
// Only set the game name if this is NOT a program...
if (FPlatformProperties::IsProgram() == false)
FApp::SetProjectName(*LocalGameName);
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
#if UE_GAME
else
// Try to use the executable name as the game name.
LocalGameName = FPlatformProcess::ExecutableName();
int32 FirstCharToRemove = INDEX_NONE;
if (LocalGameName.FindChar(TCHAR('-'), FirstCharToRemove))
LocalGameName = LocalGameName.Left(FirstCharToRemove);
FApp::SetProjectName(*LocalGameName);
// Check it's not UE4Game, otherwise assume a uproject file relative to the game project directory
if (LocalGameName != TEXT("UE4Game"))
ProjFilePath = FPaths::Combine(TEXT(".."), TEXT(".."), TEXT(".."), *LocalGameName, *FString(LocalGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
#endif
static bool bPrinted = false;
if (!bPrinted)
bPrinted = true;
if (FApp::HasProjectName())
UE_LOG(LogInit, Display, TEXT("Running engine for game: %s"), FApp::GetProjectName());
else
if (FPlatformProperties::RequiresCookedData())
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games on cooked platforms require a uproject file be specified."));
else
UE_LOG(LogInit, Display, TEXT("Running engine without a game"));
else
FString ProjFilePath;
FString LocalGameName;
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
if (FPlatformProperties::RequiresCookedData())
// Non-agnostic exes that require cooked data cannot load projects, so make sure that the LocalGameName is the GameName
if (LocalGameName != FApp::GetProjectName())
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games cannot load projects on cooked platforms - try running UE4Game."));
// Only set the game name if this is NOT a program...
if (FPlatformProperties::IsProgram() == false)
FApp::SetProjectName(*LocalGameName);
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
// In a non-game agnostic exe, the game name should already be assigned by now.
if (!FApp::HasProjectName())
UE_LOG(LogInit, Fatal,TEXT("Could not set game name!"));
return true;
//启动部分:修正游戏名
void LaunchFixGameNameCase()
#if PLATFORM_DESKTOP && !IS_PROGRAM
// This is to make sure this function is not misused and is only called when the game name is set
check(FApp::HasProjectName());
// correct the case of the game name, if possible (unless we're running a program and the game name is already set)
if (FPaths::IsProjectFilePathSet())
const FString GameName(FPaths::GetBaseFilename(IFileManager::Get().GetFilenameOnDisk(*FPaths::GetProjectFilePath())));
const bool bGameNameMatchesProjectCaseSensitive = (FCString::Strcmp(*GameName, FApp::GetProjectName()) == 0);
if (!bGameNameMatchesProjectCaseSensitive && (FApp::IsProjectNameEmpty() || GIsGameAgnosticExe || (GameName.Len() > 0 && GIsGameAgnosticExe)))
if (GameName == FApp::GetProjectName()) // case insensitive compare
FApp::SetProjectName(*GameName);
else
const FText Message = FText::Format(
NSLOCTEXT("Core", "MismatchedGameNames", "The name of the .uproject file ('0') must match the name of the project passed in the command line ('1')."),
FText::FromString(*GameName),
FText::FromString(FApp::GetProjectName()));
if (!GIsBuildMachine)
UE_LOG(LogInit, Warning, TEXT("%s"), *Message.ToString());
FMessageDialog::Open(EAppMsgType::Ok, Message);
FApp::SetProjectName(TEXT("")); // this disables part of the crash reporter to avoid writing log files to a bogus directory
if (!GIsBuildMachine)
exit(1);
UE_LOG(LogInit, Fatal, TEXT("%s"), *Message.ToString());
#endif //PLATFORM_DESKTOP
//有条件地创建File Wrapper
static IPlatformFile* ConditionallyCreateFileWrapper(const TCHAR* Name, IPlatformFile* CurrentPlatformFile, const TCHAR* CommandLine, bool* OutFailedToInitialize = nullptr, bool* bOutShouldBeUsed = nullptr )
if (OutFailedToInitialize)
*OutFailedToInitialize = false;
if ( bOutShouldBeUsed )
*bOutShouldBeUsed = false;
//创建File Wrapper
IPlatformFile* WrapperFile = FPlatformFileManager::Get().GetPlatformFile(Name);
if (WrapperFile != nullptr && WrapperFile->ShouldBeUsed(CurrentPlatformFile, CommandLine))
if ( bOutShouldBeUsed )
*bOutShouldBeUsed = true;
if (WrapperFile->Initialize(CurrentPlatformFile, CommandLine) == false)
if (OutFailedToInitialize)
*OutFailedToInitialize = true;
// Don't delete the platform file. It will be automatically deleted by its module.
WrapperFile = nullptr;
else
// Make sure it won't be used.
WrapperFile = nullptr;
return WrapperFile;
//启动部分:检测文件覆盖
//寻找命令行上的任何的文件覆盖(例如网络连接文件处理程序)
/**
* Look for any file overrides on the command line (i.e. network connection file handler)
*/
bool LaunchCheckForFileOverride(const TCHAR* CmdLine, bool& OutFileOverrideFound)
//输出结果
OutFileOverrideFound = false;
// Get the physical platform file.
IPlatformFile* CurrentPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
// Try to create pak file wrapper
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
PlatformFile = ConditionallyCreateFileWrapper(TEXT("CachedReadFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
// Try to create sandbox wrapper
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SandboxFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
#if !UE_BUILD_SHIPPING // UFS clients are not available in shipping builds.
// Streaming network wrapper (it has a priority over normal network wrapper)
bool bNetworkFailedToInitialize = false;
do
bool bShouldUseStreamingFile = false;
IPlatformFile* NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("StreamingFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize, &bShouldUseStreamingFile);
if (NetworkPlatformFile)
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
bool bShouldUseCookedIterativeFile = false;
if ( !bShouldUseStreamingFile && !NetworkPlatformFile )
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("CookedIterativeFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize, &bShouldUseCookedIterativeFile);
if (NetworkPlatformFile)
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
// if streaming network platform file was tried this loop don't try this one
// Network file wrapper (only create if the streaming wrapper hasn't been created)
if ( !bShouldUseStreamingFile && !bShouldUseCookedIterativeFile && !NetworkPlatformFile)
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("NetworkFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize);
if (NetworkPlatformFile)
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
if (bNetworkFailedToInitialize)
FString HostIpString;
FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIpString);
#if PLATFORM_REQUIRES_FILESERVER
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Failed to connect to file server at %s. RETRYING in 5s.\\n"), *HostIpString);
FPlatformProcess::Sleep(5.0f);
uint32 Result = 2;
#else //PLATFORM_REQUIRES_FILESERVER
// note that this can't be localized because it happens before we connect to a filserver - localizing would cause ICU to try to load.... from over the file server connection!
FString Error = FString::Printf(TEXT("Failed to connect to any of the following file servers:\\n\\n %s\\n\\nWould you like to try again? No will fallback to local disk files, Cancel will quit."), *HostIpString.Replace( TEXT("+"), TEXT("\\n ")));
uint32 Result = FMessageDialog::Open( EAppMsgType::YesNoCancel, FText::FromString( Error ) );
#endif //PLATFORM_REQUIRES_FILESERVER
if (Result == EAppReturnType::No)
break;
else if (Result == EAppReturnType::Cancel)
// Cancel - return a failure, and quit
return false;
while (bNetworkFailedToInitialize);
#endif
#if !UE_BUILD_SHIPPING
// Try to create file profiling wrapper
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("ProfileFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SimpleProfileFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
// Try and create file timings stats wrapper
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileReadStats"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
// Try and create file open log wrapper (lists the order files are first opened)
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileOpenLog"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
#endif //#if !UE_BUILD_SHIPPING
// Wrap the above in a file logging singleton if requested
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("LogFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
// If our platform file is different than it was when we started, then an override was used
OutFileOverrideFound = (CurrentPlatformFile != &FPlatformFileManager::Get().GetPlatformFile());
return true;
//启动部分:验证游戏名称是否合法
bool LaunchHasIncompleteGameName()
if ( FApp::HasProjectName() && !FPaths::IsProjectFilePathSet() )
// Verify this is a legitimate game name
// Launched with a game name. See if the <GameName> folder exists. If it doesn't, it could instead be <GameName>Game
const FString NonSuffixedGameFolder = FPaths::RootDir() / FApp::GetProjectName();
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*NonSuffixedGameFolder) == false)
const FString SuffixedGameFolder = NonSuffixedGameFolder + TEXT("Game");
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*SuffixedGameFolder))
return true;
return false;
//启动部分:更新最近的工程文件
void LaunchUpdateMostRecentProjectFile()
// If we are launching without a game name or project file, we should use the last used project file, if it exists
// 如果在没有游戏名称或项目文件的情况下启动,如果存在最后使用的文件。那么使用最后一个使用的项目文件
// 该方法获取最后使用的文件
const FString& AutoLoadProjectFileName = IProjectManager::Get().GetAutoLoadProjectFileName();
FString RecentProjectFileContents;
if ( FFileHelper::LoadFileToString(RecentProjectFileContents, *AutoLoadProjectFileName) )
if ( RecentProjectFileContents.Len() )
const FString AutoLoadInProgressFilename = AutoLoadProjectFileName + TEXT(".InProgress");
if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*AutoLoadInProgressFilename) )
// We attempted to auto-load a project but the last run did not make it to UEditorEngine::InitEditor.
// This indicates that there was a problem loading the project.
// Do not auto-load the project, instead load normally until the next time the editor starts successfully.
UE_LOG(LogInit, Display, TEXT("There was a problem auto-loading %s. Auto-load will be disabled until the editor successfully starts up with a project."), *RecentProjectFileContents);
else if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*RecentProjectFileContents) )
// The previously loaded project file was found. Change the game name here and update the project file path
FApp::SetProjectName(*FPaths::GetBaseFilename(RecentProjectFileContents));
FPaths::SetProjectFilePath(RecentProjectFileContents);
UE_LOG(LogInit, Display, TEXT("Loading recent project file: %s"), *RecentProjectFileContents);
// Write a file indicating that we are trying to auto-load a project.
// This file prevents auto-loading of projects for as long as it exists. It is a detection system for failed auto-loads.
// The file is deleted in UEditorEngine::InitEditor, thus if the load does not make it that far then the project will not be loaded again.
FFileHelper::SaveStringToFile(TEXT(""), *AutoLoadInProgressFilename);
初始化时钟
初始化一些变量
//初始化时间,
//初始化一些变量
void FEngineLoop::InitTime()
// Init variables used for benchmarking and ticking.
// 用于基准测试和计时的初始变量。
FApp::SetCurrentTime(FPlatformTime::Seconds());
MaxFrameCounter = 0;//最大帧计数器
MaxTickTime = 0;//最大时钟Tick时间
TotalTickTime = 0;//总共时钟Tick时间
LastFrameCycles = FPlatformTime::Cycles();//最后的帧周期
float FloatMaxTickTime = 0;//浮点数最大时钟Tick时间
#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. This makes it easier on
// say, ios, where we have a toggle setting to enable benchmarking, but don't want to have to make user
// also disable the seconds setting as well. -seconds= will exit the app after time even if benchmarking
// is not enabled
// NOTE: This will override -seconds= if it's specified
// 是否以秒为基准,是的话重新获取赋值最大时钟Tick时间
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.
// 使用-FPS=X来覆盖固定的tick率,例如使用了 -BENCHMARK
float FixedFPS = 0;
FParse::Value(FCommandLine::Get(),TEXT("FPS="),FixedFPS);
if( FixedFPS > 0 )
FApp::SetFixedDeltaTime(1 / FixedFPS);
#endif // !UE_BUILD_SHIPPING
// convert FloatMaxTickTime into number of frames (using 1 / FApp::GetFixedDeltaTime() to convert fps to seconds )
// 将浮点maxticktime转换成帧数(使用1/FApp:GetFixedDeltaTime()将fps转换为秒)
MaxFrameCounter = FMath::TruncToInt(MaxTickTime / FApp::GetFixedDeltaTime());
EngineLoop生命周期流程
引擎的整个生命周期,类似unity
初始化->循环->退出
预初始化
这部分代码1500+行,暂时不放出来代码
//预初始化
int32 FEngineLoop::PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline)
FString CmdLine;
// loop over the parameters, skipping the first one (which is the executable name)
// 循环参数,输出到CmdLine中
for (int32 Arg = 1; Arg < ArgC; Arg++)
FString ThisArg = ArgV[Arg];
if (ThisArg.Contains(TEXT(" ")) && !ThisArg.Contains(TEXT("\\"")))
int32 EqualsAt = ThisArg.Find(TEXT("="));
if (EqualsAt > 0 && ThisArg.Find(TEXT(" ")) > EqualsAt)
ThisArg = ThisArg.Left(EqualsAt + 1) + FString("\\"") + ThisArg.RightChop(EqualsAt + 1) + FString("\\"");
else
ThisArg = FString("\\"") + ThisArg + FString("\\"");
CmdLine += ThisArg;
// put a space between each argument (not needed after the end)
if (Arg + 1 < ArgC)
CmdLine += TEXT(" ");
// append the additional extra command line
if (AdditionalCommandline)
CmdLine += TEXT(" ");
CmdLine += AdditionalCommandline;
// send the command line without the exe name
// 将有起动参数的CmdLine传入初始化方法,执行初始化方法
return GEngineLoop.PreInit(*CmdLine);
初始化
//初始化
int32 FEngineLoop::Init()
LLM_SCOPE(ELLMTag::EngineInitMemory);
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FEngineLoop::Init" ), STAT_FEngineLoop_Init, STATGROUP_LoadTime );
//关于FScopedSlowTask:A scope block representing an amount of work divided up into sections. Use one scope at the top of each function to give accurate feedback to the user of a slow operation's progress.
//https://api.unrealengine.com/INT/API/Runtime/Core/Misc/FScopedSlowTask/index.html
FScopedSlowTask SlowTask(100);
//设置进入程序后帧率为10
SlowTask.EnterProgressFrame(10);
// Figure out which UEngine variant to use.
// 为编辑器/引擎生成不同的Engine Class
UClass* EngineClass = nullptr;
if( !GIsEditor )//如果不是编辑器是引擎
// 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
check( GEngine );
GetMoviePlayer()->PassLoadingScreenWindowBackToGame();
//解析命令行
GEngine->ParseCommandline();
//初始化时间,初始化时间相关变量
InitTime();
//设置进入程序后帧率为60
SlowTask.EnterProgressFrame(60);
//初始化引擎
GEngine->Init(this);
// Call init callbacks
// 调用初始化回调
// 初始化后做的事情,,
// ??做了什么事情??
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UEngine::OnPostEngineInit.Broadcast();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
FCoreDelegates::OnPostEngineInit.Broadcast();
//设置进入程序后帧率为30
SlowTask.EnterProgressFrame(30);
// initialize engine instance discovery
// 初始化引擎实例discovery
//如果支持多线程
if (FPlatformProcess::SupportsMultithreading())
if (!IsRunningCommandlet())
//加载会话服务模块
SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService();
//开启会话服务
if (SessionService.IsValid())
SessionService->Start();
//??引擎服务??
EngineService = new FEngineService();
// Load all the post-engine init modules
// 载所有引擎初始化后的模块
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
GIsRequestingExit = true;
return 1;
//开始引擎
GEngine->Start();
GetMoviePlayer()->WaitForMovieToFinish();
#if !UE_SERVER
// initialize media framework
// 初始化媒体架构
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");
if (MediaModule != nullptr)
MediaModule->SetTimeSource(MakeShareable(new FAppMediaTimeSource));
#endif
// 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)
//加载 ProfilerClient模块
FModuleManager::Get().LoadModule(TEXT("ProfilerClient"));
//加载序列录制模块
//https://api.unrealengine.com/CHN/Engine/Sequencer/HowTo/SequenceRecorder/index.html
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);
FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved);
// Ready to measure thread heartbeat
// 准备测量线程心跳
FThreadHeartBeat::Get().Start();
#if defined(WITH_CODE_GUARD_HANDLER) && WITH_CODE_GUARD_HANDLER
void CheckImageIntegrity();
CheckImageIntegrity();
#endif
//发送引擎初始化完毕事件,观察者模式,广播事件
FCoreDelegates::OnFEngineLoopInitComplete.Broadcast();
return 0;
帧循环
//时钟(400+行代码)
//每帧循环执行的部分
void FEngineLoop::Tick()
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST && MALLOC_GT_HOOKS
FScopedSampleMallocChurn ChurnTracker;
#endif
// let the low level mem tracker pump once a frame to update states
// 更新Stats
LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame());
LLM_SCOPE(ELLMTag::EngineMisc);
// Send a heartbeat for the diagnostics thread
// 发送心跳线程一个心跳
FThreadHeartBeat::Get().HeartBeat(true);
FGameThreadHitchHeartBeat::Get().FrameStart();
// Make sure something is ticking the rendering tickables in -onethread mode to avoid leaks/bugs.
if (!GUseThreadedRendering && !GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed))
//渲染部分的Tick
TickRenderingTickables();
// Ensure we aren't starting a frame while loading or playing a loading movie
// 确保在加载或播放正在加载的影片时没有启动框架
ensure(GetMoviePlayer()->IsLoadingFinished() && !GetMoviePlayer()->IsMovieCurrentlyPlaying());
#if UE_EXTERNAL_PROFILING_ENABLED
//同步分析器
FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::GetActiveProfiler();
if (ActiveProfiler)
ActiveProfiler->FrameSync();
#endif // UE_EXTERNAL_PROFILING_ENABLED
FPlatformMisc::BeginNamedEventFrame();
uint64 CurrentFrameCounter = GFrameCounter;
SCOPED_NAMED_EVENT_F(TEXT("Frame %d"), FColor::Red, CurrentFrameCounter);
// execute callbacks for cvar changes
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_CallAllConsoleVariableSinks);
IConsoleManager::Get().CallAllConsoleVariableSinks();
SCOPE_CYCLE_COUNTER(STAT_FrameTime);
#if WITH_PROFILEGPU && !UE_BUILD_SHIPPING
// Issue the measurement of the execution time of a basic LongGPUTask unit on the very first frame
// The results will be retrived on the first call of IssueScalableLongGPUTask
if (GFrameCounter == 0 && IsFeatureLevelSupported(GMaxRHIShaderPlatform, ERHIFeatureLevel::SM4) && FApp::CanEverRender())
//刷新渲染指令
FlushRenderingCommands();
//入队出队渲染指令,,渲染指令在虚幻内部是一个队列数据结构
ENQUEUE_UNIQUE_RENDER_COMMAND(
MeasureLongGPUTaskExecutionTimeCmd,
//估计GPU任务执行时间
MeasureLongGPUTaskExecutionTime(RHICmdList);
);
#endif
//发送广播消息“开始帧”
FCoreDelegates::OnBeginFrame.Broadcast();
// flush debug output which has been buffered by other threads
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_FlushThreadedLogs);
GLog->FlushThreadedLogs();
// exit if frame limit is reached in benchmark mode, or if time limit is reached
if ((FApp::IsBenchmarking() && MaxFrameCounter && (GFrameCounter > MaxFrameCounter)) ||
(MaxTickTime && (TotalTickTime > MaxTickTime)))
FPlatformMisc::RequestExit(0);
// set FApp::CurrentTime, FApp::DeltaTime and potentially wait to enforce max tick rate
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_UpdateTimeAndHandleMaxTickRate);
GEngine->UpdateTimeAndHandleMaxTickRate();
// beginning of RHI frame
ENQUEUE_RENDER_COMMAND(BeginFrame)([CurrentFrameCounter](FRHICommandListImmediate& RHICmdList)
GRHICommandList.LatchBypass();
GFrameNumberRenderThread++;
// If we are profiling, kick off a long GPU task to make the GPU always behind the CPU so that we
// won't get GPU idle time measured in profiling results
IssueLongGPUTaskHelper();
FString FrameString = FString::Printf(TEXT("Frame %d"), CurrentFrameCounter);
FPlatformMisc::BeginNamedEvent(FColor::Yellow, *FrameString);
RHICmdList.PushEvent(*FrameString, FColor::Green);
GPU_STATS_BEGINFRAME(RHICmdList);
RHICmdList.BeginFrame();
FCoreDelegates::OnBeginFrameRT.Broadcast();
);
#if !UE_SERVER && WITH_ENGINE
if (!GIsEditor && GEngine->GameViewport && GEngine->GameViewport->GetWorld() && GEngine->GameViewport->GetWorld()->IsCameraMoveable())
// When not in editor, we emit dynamic resolution's begin frame right after RHI's.
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::BeginFrame);
#endif
// tick performance monitoring
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_TickFPSChart);
GEngine->TickPerformanceMonitoring( FApp::GetDeltaTime() );
extern COREUOBJECT_API void ResetAsyncLoadingStats();
ResetAsyncLoadingStats();
// update memory allocator stats
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Malloc_UpdateStats);
GMalloc->UpdateStats();
FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) );
SCOPE_CYCLE_COUNTER( STAT_FrameTime );
// Calculates average FPS/MS (outside STATS on purpose)
CalculateFPSTimings();
// Note the start of a new frame
MALLOC_PROFILER(GMalloc->Exec(nullptr, *FString::Printf(TEXT("SNAPSHOTMEMORYFRAME")),*GLog));
// handle some per-frame tasks on the rendering thread
ENQUEUE_UNIQUE_RENDER_COMMAND(
ResetDeferredUpdates,
FDeferredUpdateResource::ResetNeedsUpdate();
FlushPendingDeleteRHIResources_RenderThread();
);
SCOPE_CYCLE_COUNTER(STAT_PumpMessages);
FPlatformApplicationMisc::PumpMessages(true);
bool bIdleMode;
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Idle);
// Idle mode prevents ticking and rendering completely
bIdleMode = ShouldUseIdleMode();
if (bIdleMode)
// Yield CPU time
FPlatformProcess::Sleep(.1f);
// @todo vreditor urgent: Temporary hack to allow world-to-meters to be set before
// input is polled for motion controller devices each frame.
extern ENGINE_API float GNewWorldToMetersScale;
if( GNewWorldToMetersScale != 0.0f )
#if WITH_ENGINE
UWorld* WorldToScale = GWorld;
#if WITH_EDITOR
if( GIsEditor && GEditor->PlayWorld != nullptr && GEditor->bIsSimulatingInEditor )
WorldToScale = GEditor->PlayWorld;
#endif //WITH_EDITOR
if( WorldToScale != nullptr )
if( GNewWorldToMetersScale != WorldToScale->GetWorldSettings()->WorldToMeters )
WorldToScale->GetWorldSettings()->WorldToMeters = GNewWorldToMetersScale;
GNewWorldToMetersScale = 0.0f;
#endif //WITH_ENGINE
// tick active platform files
FPlatformFileManager::Get().TickActivePlatformFile();
// process accumulated Slate input
if (FSlateApplication::IsInitialized() && !bIdleMode)
SCOPE_TIME_GUARD(TEXT("SlateInput"));
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_SlateInput);
LLM_SCOPE(ELLMTag::UI);
FSlateApplication& SlateApp = FSlateApplication::Get();
SlateApp.PollGameDeviceState();
// Gives widgets a chance to process any accumulated input
SlateApp.FinishedInputThisFrame();
#if !UE_SERVER
// tick media framework
static const FName MediaModuleName(TEXT("Media"));
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>(MediaModuleName);
if (MediaModule != nullptr)
MediaModule->TickPreEngine();
#endif
// main game engine tick (world, game objects, etc.)
GEngine->Tick(FApp::GetDeltaTime(), bIdleMode);
// If a movie that is blocking the game thread has been playing,
// wait for it to finish before we continue to tick or tick again
// We do this right after GEngine->Tick() because that is where user code would initiate a load / movie.
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_WaitForMovieToFinish);
GetMoviePlayer()->WaitForMovieToFinish(true);
if (GShaderCompilingManager)
// Process any asynchronous shader compile results that are ready, limit execution time
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GShaderCompilingManager);
GShaderCompilingManager->ProcessAsyncResults(true, false);
if (GDistanceFieldAsyncQueue)
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GDistanceFieldAsyncQueue);
GDistanceFieldAsyncQueue->ProcessAsyncTasks();
#if !UE_SERVER
// tick media framework
if (MediaModule != nullptr)
MediaModule->TickPreSlate();
#endif
#if WITH_ENGINE
// process concurrent Slate tasks
FGraphEventRef ConcurrentTask;
const bool bDoConcurrentSlateTick = GEngine->ShouldDoAsyncEndOfFrameTasks();
const UGameViewportClient* const GameViewport = GEngine->GameViewport;
const UWorld* const GameViewportWorld = GameViewport ? GameViewport->GetWorld() : nullptr;
UDemoNetDriver* const CurrentDemoNetDriver = GameViewportWorld ? GameViewportWorld->DemoNetDriver : nullptr;
// Optionally validate that Slate has not modified any replicated properties for client replay recording.
FDemoSavedPropertyState PreSlateObjectStates;
const bool bValidateReplicatedProperties = CurrentDemoNetDriver && CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties.GetValueOnGameThread() != 0;
if (bValidateReplicatedProperties)
PreSlateObjectStates = CurrentDemoNetDriver->SavePropertyState();
if (bDoConcurrentSlateTick)
const float DeltaSeconds = FApp::GetDeltaTime();
if (CurrentDemoNetDriver && CurrentDemoNetDriver->ShouldTickFlushAsyncEndOfFrame())
ConcurrentTask = TGraphTask<FExecuteConcurrentWithSlateTickTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
[CurrentDemoNetDriver, DeltaSeconds]()
if (CVarDoAsyncEndOfFrameTasksRandomize.GetValueOnAnyThread(true) > 0)
FPlatformProcess::Sleep(FMath::RandRange(0.0f, .003f)); // this shakes up the threading to find race conditions
if (CurrentDemoNetDriver != nullptr)
CurrentDemoNetDriver->TickFlushAsyncEndOfFrame(DeltaSeconds);
);
#endif
// tick Slate application
if (FSlateApplication::IsInitialized() && !bIdleMode)
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_ProcessPlayerControllersSlateOperations);
check(!IsRunningDedicatedServer());
// Process slate operations accumulated in the world ticks.
ProcessLocalPlayerSlateOperations();
FSlateApplication::Get().Tick();
#if WITH_ENGINE
if (bValidateReplicatedProperties)
const bool bReplicatedPropertiesDifferent = CurrentDemoNetDriver->ComparePropertyState(PreSlateObjectStates);
if (bReplicatedPropertiesDifferent)
UE_LOG(LogInit, Log, TEXT("Replicated properties changed during Slate tick!"));
if (ConcurrentTask.GetReference())
CSV_SCOPED_TIMING_STAT(Basic, ConcurrentWithSlateTickTasks_Wait);
QUICK_SCOPE_CYCLE_COUNTER(STAT_ConcurrentWithSlateTickTasks_Wait);
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConcurrentTask);
ConcurrentTask = nullptr;
ENQUEUE_UNIQUE_RENDER_COMMAND(WaitForOutstandingTasksOnly_for_DelaySceneRenderCompletion,
QUICK_SCOPE_CYCLE_COUNTER(STAT_DelaySceneRenderCompletion_TaskWait);
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly);
);
#endif
#if STATS
// Clear any stat group notifications we have pending just in case they weren't claimed during FSlateApplication::Get().Tick
extern CORE_API void ClearPendingStatGroups();
ClearPendingStatGroups();
#endif
#if WITH_EDITOR && !UE_BUILD_SHIPPING
// tick automation controller (Editor only)
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationController);
static FName AutomationController("AutomationController");
if (FModuleManager::Get().IsModuleLoaded(AutomationController))
FModuleManager::GetModuleChecked<IAutomationControllerModule>(AutomationController).Tick();
#endif
#if WITH_ENGINE && WITH_AUTOMATION_WORKER
// tick automation worker
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationWorker);
static const FName AutomationWorkerModuleName = TEXT("AutomationWorker");
if (FModuleManager::Get().IsModuleLoaded(AutomationWorkerModuleName))
FModuleManager::GetModuleChecked<IAutomationWorkerModule>(AutomationWorkerModuleName).Tick();
#endif
// tick render hardware interface
SCOPE_CYCLE_COUNTER(STAT_RHITickTime);
RHITick( FApp::GetDeltaTime() ); // Update RHI.
// Increment global frame counter. Once for each engine tick.
GFrameCounter++;
// Disregard first few ticks for total tick time as it includes loading and such.
if (GFrameCounter > 6)
TotalTickTime += FApp::GetDeltaTime();
// Find the objects which need to be cleaned up the next frame.
FPendingCleanupObjects* PreviousPendingCleanupObjects = PendingCleanupObjects;
PendingCleanupObjects = GetPendingCleanupObjects();
SCOPE_CYCLE_COUNTER(STAT_FrameSyncTime);
// this could be perhaps moved down to get greater parallelism
// Sync game and render thread. Either total sync or allowing one frame lag.
static FFrameEndSync FrameEndSync;
static auto CVarAllowOneFrameThreadLag = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag"));
FrameEndSync.Sync( CVarAllowOneFrameThreadLag->GetValueOnGameThread() != 0 );
// tick core ticker, threads & deferred commands
SCOPE_CYCLE_COUNTER(STAT_DeferredTickTime);
// Delete the objects which were enqueued for deferred cleanup before the previous frame.
delete PreviousPendingCleanupObjects;
#if WITH_COREUOBJECT
DeleteLoaders(); // destroy all linkers pending delete
#endif
FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
FThreadManager::Get().Tick();
GEngine->TickDeferredCommands();
#if !UE_SERVER
// tick media framework
if (MediaModule != nullptr)
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_MediaTickPostRender);
MediaModule->TickPostRender();
#endif
FCoreDelegates::OnEndFrame.Broadcast();
#if !UE_SERVER && WITH_ENGINE
// We emit dynamic resolution's end frame right before RHI's. GEngine is going to ignore it if no BeginFrame was done.
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::EndFrame);
#endif
// end of RHI frame
ENQUEUE_UNIQUE_RENDER_COMMAND(EndFrame,
FCoreDelegates::OnEndFrameRT.Broadcast();
RHICmdList.EndFrame();
GPU_STATS_ENDFRAME(RHICmdList);
RHICmdList.PopEvent();
FPlatformMisc::EndNamedEvent();
);
// Set CPU utilization stats.
const FCPUTime CPUTime = FPlatformTime::GetCPUTime();
SET_FLOAT_STAT( STAT_CPUTimePct, CPUTime.CPUTimePct );
SET_FLOAT_STAT( STAT_CPUTimePctRelative, CPUTime.CPUTimePctRelative );
// Set the UObject count stat
#if UE_GC_TRACK_OBJ_AVAILABLE
SET_DWORD_STAT(STAT_Hash_NumObjects, GUObjectArray.GetObjectArrayNumMinusAvailable());
#endif
退出
//退出时要执行的方法
//各种销毁/关闭/清理/置空/停止线程/停止服务
void FEngineLoop::Exit()
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "EngineLoop.Exit" ) );
//运行标记置为false
GIsRunning = 0;
//Log控制台置空
GLogConsole = nullptr;
// shutdown visual logger and flush all data
// 关闭visual logger并刷新所有数据
#if ENABLE_VISUAL_LOG
FVisualLogger::Get().Shutdown();
#endif
// Make sure we're not in the middle of loading something.
// 刷新异步加载,确保没有在加载东西
FlushAsyncLoading();
// Block till all outstanding resource streaming requests are fulfilled.
// 直到所有资源流请求都全部完成
if (!IStreamingManager::HasShutdown())
UTexture2D::CancelPendingTextureStreaming();
IStreamingManager::Get().BlockTillAllRequestsFinished();
#if WITH_ENGINE
// shut down messaging
// 关闭消息
delete EngineService;
//引擎服务器置空
EngineService = nullptr;
//停止会话服务器
if (SessionService.IsValid())
SessionService->Stop();
SessionService.Reset();
//停止DistanceFieldAsyncQueue
if (GDistanceFieldAsyncQueue)
GDistanceFieldAsyncQueue->Shutdown();
delete GDistanceFieldAsyncQueue;
#endif // WITH_ENGINE
//停止声音设备管理
if ( GEngine != nullptr )
GEngine->ShutdownAudioDeviceManager();
//Pre退出
if ( GEngine != nullptr )
GEngine->PreExit();
// close all windows
// 关闭程序,关闭窗口
FSlateApplication::Shutdown();
#if !UE_SERVER
//销毁引擎字体服务
if ( FEngineFontServices::IsInitialized() )
FEngineFontServices::Destroy();
#endif
#if WITH_EDITOR
// These module must be shut down first because other modules may try to access them during shutdown.
// Accessing these modules at shutdown causes instability since the object system will have been shut down and these modules uses uobjects internally.
//卸载资源工具模块
FModuleManager::Get().UnloadModule("AssetTools", true);
#endif // WITH_EDITOR
//卸载资源注册模块
FModuleManager::Get().UnloadModule("AssetRegistry", true);
//非安卓平台走下面部分
#if !PLATFORM_android || PLATFORM_LUMIN // AppPreExit doesn't work on Android
AppPreExit();
TermGamePhys();
ParticleVertexFactoryPool_FreePool();
#else
// AppPreExit() stops malloc profiler, do it here instead
MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); );
#endif // !ANDROID
#if WITH_PROFILEGPU
//清理
ClearLongGPUTaskQueries();
#endif
// Stop the rendering thread.
// 停止渲染线程
StopRenderingThread();
// Disable the shader cache
// 关闭着色器缓存
FShaderCache::ShutdownShaderCache();
// Close shader code map, if any
// 关闭着色器code map
FShaderCodeLibrary::Shutdown();
// Tear down the RHI.
// 推出并停止RHI线程
RHIExitAndStopRHIThread();
#if !PLATFORM_ANDROID || PLATFORM_LUMIN // UnloadModules doesn't work on Android
#if WITH_ENGINE
// Save the hot reload state
IHotReloadInterface* HotReload = IHotReloadInterface::GetPtr();
if(HotReload != nullptr)
HotReload->SaveConfig();
#endif
//卸载所有模块
//注意,这实际上并没有卸载模块的dll,卸载dll会发生在操作系统控制程序退出的时候
// Unload all modules. Note that this doesn't actually unload the module DLLs (that happens at
// process exit by the OS), but it does call ShutdownModule() on all loaded modules in the reverse
// order they were loaded in, so that systems can unregister and perform general clean up.
FModuleManager::Get().UnloadModulesAtShutdown();
#endif // !ANDROID
//销毁MoviePlayer
DestroyMoviePlayer();
// Move earlier?
// 停止stats线程(分析??)
// http://api.unrealengine.com/CHN/Engine/Performance/StatCommands/
// https://www.unrealengine.com/zh-CN/blog/how-to-improve-game-thread-cpu-performance
#if STATS
FThreadStats::StopThread();
#endif
//停止任务图接口
FTaskGraphInterface::Shutdown();
//停止流管理
IStreamingManager::Shutdown();
//平台相关杂项 停止并标记为存储
FPlatformMisc::ShutdownTaggedStorage();
加载模块
各个部分的模块,包含各个部分的功能
不同的模块在流程的不同位置加载
核心模块
CoreUObject模块
//加载核心模块
bool FEngineLoop::LoadCoreModules()
// Always attempt to load CoreUObject. It requires additional pre-init which is called from its module's StartupModule method.
#if WITH_COREUOBJECT
return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr;
#else
return true;
#endif
初始化模块
引擎模块/渲染器模块/实时动画图形模块/Slate RHI渲染器模块
地形模块/shader核心模块/图片压缩模块/音效编辑器模块/动画调节器模块
//加载初始化模块
void FEngineLoop::LoadPreInitModules()
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading PreInit Modules"), STAT_PreInitModules, STATGROUP_LoadTime);
// GGetMapNameDelegate is initialized here
#if WITH_ENGINE
//引擎模块
FModuleManager::Get().LoadModule(TEXT("Engine"));
//渲染器模块
FModuleManager::Get().LoadModule(TEXT("Renderer"));
//实时动画图形模块
FModuleManager::Get().LoadModule(TEXT("AnimGraphRuntime"));
FPlatformApplicationMisc::LoadPreInitModules();
#if !UE_SERVER
if (!IsRunningDedicatedServer() )
if (!GUsingNullRHI)
//Slate RHI渲染器模块
// This needs to be loaded before InitializeShaderTypes is called
FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer");
#endif
//地形模块
FModuleManager::Get().LoadModule(TEXT("Landscape"));
// Initialize ShaderCore before loading or compiling any shaders,
// But after Renderer and any other modules which implement shader types.
// shader核心模块
FModuleManager::Get().LoadModule(TEXT("ShaderCore"));
#if WITH_EDITORONLY_DATA
// Load the texture compressor module before any textures load. They may
// compress asynchronously and that can lead to a race condition.
// 图片压缩模块
FModuleManager::Get().LoadModule(TEXT("TextureCompressor"));
#endif
#endif // WITH_ENGINE
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
// Load audio editor module before engine class CDOs are loaded
// 音效编辑器模块
FModuleManager::Get().LoadModule(TEXT("AudioEditor"));
//动画调节器模块
//https://docs.unrealengine.com/en-us/Engine/Animation/AnimModifiers
FModuleManager::Get().LoadModule(TEXT("AnimationModifiers"));
#endif
启动核心模块
比较多,看代码注释把
//加载启动核心模块
bool FEngineLoop::LoadStartupCoreModules()
FScopedSlowTask SlowTask(100);
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Startup Modules"), STAT_StartupModules, STATGROUP_LoadTime);
bool bSuccess = true;
// Load all Runtime modules
SlowTask.EnterProgressFrame(10);
//???核心模块??
FModuleManager::Get().LoadModule(TEXT("Core"));
//网络模块
FM以上是关于虚幻4引擎源码学习笔记:主循环LaunchEngineLoop的主要内容,如果未能解决你的问题,请参考以下文章
Epic Games官方<<理解项目和文件结构>>虚幻引擎教程学习笔记