Franco,
There are two parts to level streaming, in terms of loading. The first is reading the assets from the disk, and the second is creating the necessary UObjects for the level.
UE4 requires that UObjects and derived classes be created on the Game Thread (there are a number of reasons for this). The main parts of Async Loading that happens in a separate thread is actually reading data from disk.
As long as you have the Async Loading Thread enabled, I/O work will be handled in a separate background thread. However, there’s still some amount of work that needs to happen on the GameThread (like creating UObjects). This is in AsyncLoading.cpp, and can be changed as you normally would CVars (including through INI):
static int32 GAsyncLoadingThreadEnabled;
static FAutoConsoleVariableRef CVarAsyncLoadingThreadEnabledg(
TEXT("s.AsyncLoadingThreadEnabled"),
GAsyncLoadingThreadEnabled,
TEXT("Placeholder console variable, currently not used in runtime."),
ECVF_Default
);
There’s also a few settings in CoreSettings.cpp:
float GAsyncLoadingTimeLimit = 5.0f;
int32 GAsyncLoadingUseFullTimeLimit = 1;
float GPriorityAsyncLoadingExtraTime = 20.0f;
static FAutoConsoleVariableRef CVarAsyncLoadingTimeLimit(
TEXT("s.AsyncLoadingTimeLimit"),
GAsyncLoadingTimeLimit,
TEXT("Maximum amount of time to spend doing asynchronous loading (ms per frame)."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarAsyncLoadingUseFullTimeLimit(
TEXT("s.AsyncLoadingUseFullTimeLimit"),
GAsyncLoadingUseFullTimeLimit,
TEXT("Whether to use the entire time limit even if blocked on I/O."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarPriorityAsyncLoadingExtraTime(
TEXT("s.PriorityAsyncLoadingExtraTime"),
GPriorityAsyncLoadingExtraTime,
TEXT("Additional time to spend asynchronous loading during a high priority load."),
ECVF_Default
);
Second is actually creating the level + objects in game. This does not happen on a separate thread. So, the way level streaming deals with this is by splitting up the Creation and Registration of objects across multiple frames. There are a number of options for this. These options are also CVars that can be set, and there is also a section under property settings which makes editing them easier. These are defined in CoreSettings.cpp:
int32 GUseBackgroundLevelStreaming = 1;
float GAsyncLoadingTimeLimit = 5.0f;
int32 GAsyncLoadingUseFullTimeLimit = 1;
float GPriorityAsyncLoadingExtraTime = 20.0f;
float GLevelStreamingActorsUpdateTimeLimit = 5.0f;
float GLevelStreamingUnregisterComponentsTimeLimit = 1.0f;
int32 GLevelStreamingComponentsRegistrationGranularity = 10;
int32 GLevelStreamingComponentsUnregistrationGranularity = 5;
static FAutoConsoleVariableRef CVarUseBackgroundLevelStreaming(
TEXT("s.UseBackgroundLevelStreaming"),
GUseBackgroundLevelStreaming,
TEXT("Whether to allow background level streaming."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarAsyncLoadingTimeLimit(
TEXT("s.AsyncLoadingTimeLimit"),
GAsyncLoadingTimeLimit,
TEXT("Maximum amount of time to spend doing asynchronous loading (ms per frame)."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarAsyncLoadingUseFullTimeLimit(
TEXT("s.AsyncLoadingUseFullTimeLimit"),
GAsyncLoadingUseFullTimeLimit,
TEXT("Whether to use the entire time limit even if blocked on I/O."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarPriorityAsyncLoadingExtraTime(
TEXT("s.PriorityAsyncLoadingExtraTime"),
GPriorityAsyncLoadingExtraTime,
TEXT("Additional time to spend asynchronous loading during a high priority load."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarLevelStreamingActorsUpdateTimeLimit(
TEXT("s.LevelStreamingActorsUpdateTimeLimit"),
GLevelStreamingActorsUpdateTimeLimit,
TEXT("Maximum allowed time to spend for actor registration steps during level streaming (ms per frame)."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarLevelStreamingUnregisterComponentsTimeLimit(
TEXT("s.UnregisterComponentsTimeLimit"),
GLevelStreamingUnregisterComponentsTimeLimit,
TEXT("Maximum allowed time to spend for actor unregistration steps during level streaming (ms per frame). If this is zero then we don't timeslice"),
ECVF_Default
);
static FAutoConsoleVariableRef CVarLevelStreamingComponentsRegistrationGranularity(
TEXT("s.LevelStreamingComponentsRegistrationGranularity"),
GLevelStreamingComponentsRegistrationGranularity,
TEXT("Batching granularity used to register actor components during level streaming."),
ECVF_Default
);
static FAutoConsoleVariableRef CVarLevelStreamingComponentsUnregistrationGranularity(
TEXT("s.LevelStreamingComponentsUnregistrationGranularity"),
GLevelStreamingComponentsUnregistrationGranularity,
TEXT("Batching granularity used to unregister actor components during level unstreaming."),
ECVF_Default
);
One thing to point out is that any of the “time” values mentioned above are in milliseconds (I’ve seen a few licensees assume it was seconds).
Basically, there are a number of different settings associated with level streaming. There’s definitely no “one size fits all” adjustment of these settings, so it’s up to individual projects to figure out what values work well for them.
One thing you can do to start is enable PERF_TRACK_DETAILED_ASYNC_STATS (which is defined in AsyncLoading.h). This will print out detailed information on how much time each frame was spent on handling Streaming Steps when a world is loaded. From there, you can figure out what you need to adjust in order to get a better framerate. You want to make sure to disable this when not testing, as there are other pieces of code that also use this can it could bloat your logs a bit.
Of course, you’ll have to make tradeoffs and find a good balance between total loading time and framerate.
Thanks,
Jon