Unreal Source Explained (USE) is an Unreal source code analysis, based on profilers.
For more infomation, see the repo in github.
- Overview
- Initialization
- Loop
- Memory Management
- Thread Management
- Blueprint Visual Scripting
- Rendering
- Gameplay
Unreal has these several important threads:
- Game thread
- Main thread
- Task Threads
- Render thread (maybe with the additional RHI thread)
- File I/O threads
- Mipmap streaming calculations
- etc.
This following image is the threads overview in the profiler. Threads are sorted by their CPU Time, which usually infer their importance.
We'll briefly discuss some important threads below.
see "
[IOSAppDelegate MainAppThread:]
" in the above thread overview image.
Game thread's main mission is running FEngineLoop
(link), including its initialization PreInit()
(link) and tick Tick()
(link).
Every game is running frame by frame. Inside one frame, several submodules are called sequentially. This routine is known as Game Loop.
FEngineLoop
is Unreal's game loop. Each time FEngineLoop::Tick()
is called, that means one new frame has just begun.
Note that in Unreal, game thread's name is [IOSAppDelegate MainAppThread:]
, it's Unreal's "main thread", do not confuse with the process's main thread.
see "
Main Thread
" in the above thread overview image.
This thread is the iOS process's main thread, it's the first thread that gets created and the entry point gets called.
In Unreal, Main thread doesn't carry out heavy jobs, it just handles some native iOS messages, such as touch event.
Unreal has several ways to assign tasks to threads, these threads are called Task Threads. This task threads management will be discussed in future chapters.
These following threads are implemented as task threads.
see "
FRenderingThread::Run()
" in the above thread overview image.
Render thread calls FRenderingThread::Run()
(link), and takes charge of all the rendering tasks, such as updating pritimitives' transform, updating particle systems, drawing slate ui elements, etc. These rendering tasks usually update and prepare rendering data for the GPU to run.
Render thread and the game thread are usually the heaviest 2 threads in most games. You can see the render thread is actually the heaviest thread in this profiling.
Render thread is created in FEngineLoop::PreInit()
(link). You can observe the thread creation in the Allocation profiler, because each thread creation comes along with some amount of thread stack memory allocation.
Note the thread creation call stack is reversed, the caller is under the callee.
Notice that sometimes you can see there seems to be another thread running FRenderingThread::Run()
in the Time Profiler, this is because render thread will be recreated during viewport resizes(link), and the Time Profiler captures both the destroyed and recreated render threads. There is only one render thread at any given time.
What's more, Unreal can be Parallel Rendering with the RHI (Render Hardware Interface) thread, which translates the render thread's render commands into specific device GPU commands. This RHI thread may improve performance in some platform.
However, in iOS the RHI thread is disabled, because GRHISupportsRHIThread
(link) and bSupportsRHIThread
(link) is disabled. Unreal has this comment(link):
/** Parallel execution is available on Mac but not iOS for the moment - it needs to be tested because it isn't cost-free */
You might modify the source code to enable the RHI thread in mobile devices with proper device capability test.
see "
FAsyncTask<FGenericReadRequestWorker>::DoThreadedWork()
" in the above thread overview image.