Oct 29, 2020.Knowledge
Originally written by Ryan B.
Understanding level loading in general
There are two ways to load a level in UE, a streaming load or a blocking load.
The “blocking load” concept for us in UE4 is to prioritize overall load speed over everything else, so it takes all the CPU priority at the expense of any other threads, including render threads. Also Open Level by default throws away everything that exists in the current world and would normally have nothing to render. The use of “Open Level” blueprint node to transition between levels results in a hard transition as it changes between the two maps. While it’s possible to display a static loading screen during a blocking load, prioritizing load speed over everything else means that displaying an animated loading screen (or updating the display in response to head movement) can be difficult or impossible.
One way to overcome this is to use “Load Stream Level” to asynchronously stream sub levels, which will allow rendering to continue while the sub level is read into memory. Depending on how the project was set up, this can require changing the construction of the levels in the project.
Another alternative to a blocking load does use Open Level, but does so using a feature known as seamless travel to continue to render something while transition between persistent levels with a transition map in-between, it requires using a server (which can be a “listen server,” or a client and server that are the same process) and some amount of setup, which may be more or less setup than the method outlined above with sub-levels, depending on the project. It has not been tested extensively with XR Splash Screens, which are typically used for loading in XR.
The concerns when it comes to displaying a loading screen in stereo rendering are largely the same as those of displaying an animated loading screen in non-stereo rendering.
All the regular settings that should be checked when concerned about level loading times or streaming hitches should be considered. Many of the settings in Project Settings > Engine > Streaming that are relevant here:
First, in the Package Streaming section, the Async Loading thread Enabled and Event Driven Loader Enabled toggles will change loading quite a bit, you should try different combinations of those. Event driven loader was explained in the 4.14 release notes:
Unreal Engine 4.14 Released: (section “New: Fast Asynchronous Loading System (experimental)”)
“Cooked builds can now use a completely new Event Driven Loader which is far more efficient than the old streaming code. Games using the EDL should see the load times drop by about 50%, but in many cases it may be dramatically faster. The Event Driven Loader comes with an unified code path for loading assets. This means that all packages will be loaded using the new async path instead of the old blocking path. EDL is currently an experimental feature and disabled by default, but can easily be enabled through Project Settings.”
Below that, the Level Streaming section has a lot of numbers that are how many milliseconds to use each frame loading. See the tool tips on each item to see an explanation. A lot of them say “maximum amount of time to spend in a frame doing __ (in milliseconds)”. This is how you you can balance what portion of the computation time in a frame is given to loading, and what portion is given to continuing to run and render the game. The lower the numbers, the longer the overall load time will be, but the smoother the game will run as it loads.
The time a level takes to load is heavily influenced by the content and logic in the project itself, as any content referenced by the level also must be loaded, and any code you put in a C++ constructor for a class that is spawned when loading a level, or in a PostLoad functions for any UObjects in the level are executed on load.
Designing levels to stream without hitching is a significant undertaking, and really must be considered and tested continuously from the very early stages of production until ship, not just right before shipping a game.
Testing in Play in Editor or Play in VR are not necessarily representative of load times, because the Editor actually keeps anything ever loaded into memory in memory and never releases it, so you will quite often see faster load times.
A better method is to launch the Editor in “game” mode with the command “UE4Editor.exe MyProject -game”. But this is loading uncooked content and has a lot of debug code enabled, so it’s still not entirely representative.
The best way to profile load times (or other performance-related metrics) is in a build as close to a Shipping build as possible, but with profiling enabled. That is the Test build configuration. This can be made from the File menu, under Package Project > Configuration. This is only available in an Engine built from source. If you don’t see the option for “Test” here, you may need to build the engine from source.
From here, you would profile the game during load with Unreal Insights or the CPU profiler of your target platform (if you have more than one target platform or more than one target HMD, you need to profile them all), and see what functions look to be taking up the most time. Also using stat dumphitches will show you spikes that may need to be investigated.
In the past (and maybe still currently), SteamVR has a very strict policy of displaying the generic (gray?) room if rendering commands are delayed in the slightest.Preventing the Steam VR room from appearing at all can be difficult, as it seems to happen automatically if rendering is paused for any significant amount of time, which can happen easily when loading a large level from disk. One potential mitigation is to break the level up into smaller pieces, and load them as the player moves through the level, rather than all at once. There’s a setting in SteamVR to disable this locally, but each player would have to set that setting, I don’t believe an application can opt into it.
- Recommend against using “automatically show/hide loading screen” setting
- Not sure the automatic detection of loading that it tries to do is actually possible, but it at least currently doesn’t work very well