UE Performance Analysis: Memory Optimization

UE性能分析:内存优化

In game development, program performance is a crucial issue that needs to be emphasized. To cover the largest possible user base, we must consider the operational effects on mid to low-end devices and ensure compatibility with a wide range of hardware specifications. In this context, it is key to analyze and optimize the performance bottlenecks of the game.

Loading more resources into memory at runtime is essentially a trade-off of space for time. Frequent I/O from the disk is quite time-consuming, and pre-loading resources into memory allows for fast retrieval. However, memory resources are also limited and cannot be used without restrictions, especially for some mid to low-end mobile devices where devices with 4GB or even less memory still have a significant market share. Therefore, it is important to avoid wasting memory, as excessive memory usage may lead to termination by the system.

Memory optimization fundamentally seeks to strike a balance between loading efficiency and memory consumption, aiming to use as much available memory as possible while ensuring that more low-end devices run normally without triggering OOM.

I plan to write a few articles related to performance optimization. This article will start with UE memory analysis, introducing commonly used memory analysis tools and methods, as well as organizing the memory optimization techniques that can be applied in UE projects. This part was previously recorded in the form of notes at notes/ue, and subsequent memory-related content will be added to this article.

Memory optimization primarily focuses on the following four aspects:

  1. Identifying memory leaks
  2. Trimming unnecessary modules
  3. Optimizing the memory usage of existing modules
  4. Lossy optimization: cutting content (material quality, etc.)

A three-step approach: debug, squeeze out excess, cut down requirements.

Memory Analysis Tools

Before performing memory optimization, it’s essential to gain a rough understanding of the memory distribution of the UE project, which can be done using the memory analysis tools provided by UE and some native platform analysis tools.

Memory Analysis Resources:

Some common console commands:

1
2
3
4
stat memory # Displays memory usage of various subsystems in the engine
stat MemoryAllocator # Displays memory allocation information
stat MemoryPlatform # Displays platform memory information
stat MemoryStaticMesh # Displays memory information for static meshes

You can also enable LLM by adding the -LLM parameter at startup.

1
2
3
4
-LLM # Enable LLM
-LLMCSV # Continuously write all values to a CSV file. Automatically enables -LLM.
-llmtagsets=Assets # Experimental feature. Displays the total memory allocated for each asset.
-llmtagsets=AssetClasses # Experimental feature. Displays the total for each UObject class type.

Then you can use the following console commands during runtime:

1
2
3
4
stat llm # Displays LLM summary. All lower-level engine statistics are consolidated into a single engine statistic.
stat llmfull # Displays all LLM statistics
stat LLMPlatform # Displays all memory information allocated from the OS
stat LLMOverhead # Displays memory used internally by LLM

Memory analysis can also use the following tools:

  • memreport
  • MemoryProfiler
  • Heapprofd(Android)
  • Instrument(IOS)

Entering memreport (-full) in the game console will create a directory and .memreport file in Saved/Profiling/Memreports, which can be opened with a text editor to view the memory usage of different parts of the game.

I will provide more details on the usage of specific memory analysis tools and the analysis process for memory allocation in the UE engine, as time allows. For more detailed information about LLM, please refer to the UE documentation.

Memory Optimization Solutions

The listed optimization methods are actually optional; it is not necessary to implement all of them to achieve the best results, as memory optimization must balance efficiency. Therefore, optimizations can be controlled based on project requirements across different devices while ensuring feature consistency for low-end machines.

Here, I will mainly list which parts of UE can be optimized and how to do so. Specific optimization data will be analyzed and supplemented over time.

Disable Unnecessary Feature Support

Based on the requirements, the following engine module supports can be trimmed:

  • APEX: If the Nvidia APEX destruction system is not used, support for APEX can be removed during engine compilation. This can be set in BuildSetting or TargetRules by setting bCompileAPEX=true.
  • Recast(NavMesh): If the client does not need support for Recast during runtime and does not require local NavMesh navigation, NavMesh support can be trimmed at runtime. This can be set in BuildSetting or TargetRules by setting bCompileRecast=false.
  • FreeType: If FreeType font support is needed, it can be set in BuildSetting or TargetRules by setting bCompileFreeType=false.
  • ICU(unicode/i18n): The Core module of the engine supports unicode/i18n, which can be controlled via bCompileICU=false in BuildSetting or TargetRules.
  • CompileForSize: UE provides an optimization option that strictly controls size during compilation but sacrifices performance. This can be set in BuildSetting or TargetRules by setting bCompileForSize=false; if true, the compilation will use -Oz for Android, or -O3 if false.
  • CEF3: Optional support for the Chromium Embedded Framework, Google’s embedded browser support. This can be controlled by setting bCompileCEF3=false in BuildSetting or TargetRules.
  • bUsesSteam: Whether to use Steam; can be disabled for mobile games, controlled through bUsesSteam in TargetRules.
  • SpeedTree: If the game does not require SpeedTree for vegetation modeling, compilation of SpeedTree can be disabled via bOverrideCompileSpeedTree in TargetRules.
  • Audio Module: If the project uses WWise or similar as an audio playback interface and does not require the built-in Audio module of the engine, that functionality is redundant and can be trimmed away.
  • Internationalization Module: If multi-language support of the game does not rely on UE’s text collection and translation functionality, this module can be removed.

This can reduce the size of the static program after compilation and decrease unnecessary execution logic.

Control AssetRegistry Serialization

The AssetRegistry is mainly used in the Editor for resource searching and filtering operations, primarily utilized by the Content Browser, as described in the UE documentation: Asset Registry.

For the project, it may not be necessary to use it at runtime, but the AssetRegistry module loads AssetRegistry.bin into memory as soon as it starts, consuming memory unnecessarily if it is not needed.

Fortunately, UE provides methods to not serialize or partially serialize AssetRegistry data. During the construction of UAssetRegistryImpl, the InitializeSerializationOptionsFromIni function will read configurations from DefaultEngine.ini and construct an FAssetRegistrySerializationOptions structure to store the settings, which will be used in subsequent Serialize functions to control what part of the data is serialized into the AssetRegistry.

Runtime/AssetRegistry/Private/AssetRegistry.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void UAssetRegistryImpl::InitializeSerializationOptionsFromIni(FAssetRegistrySerializationOptions& Options, const FString& PlatformIniName) const
{
FConfigFile* EngineIni = nullptr;
#if WITH_EDITOR
// Use passed in platform, or current platform if empty
FConfigFile PlatformEngineIni;
FConfigCacheIni::LoadLocalIniFile(PlatformEngineIni, TEXT("Engine"), true, (!PlatformIniName.IsEmpty() ? *PlatformIniName : ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName())));
EngineIni = &PlatformEngineIni;
#else
// In cooked builds, always use the normal engine INI
EngineIni = GConfig->FindConfigFile(GEngineIni);
#endif

EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeAssetRegistry"), Options.bSerializeAssetRegistry);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeDependencies"), Options.bSerializeDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeNameDependencies"), Options.bSerializeSearchableNameDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeManageDependencies"), Options.bSerializeManageDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializePackageData"), Options.bSerializePackageData);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bUseAssetRegistryTagsWhitelistInsteadOfBlacklist"), Options.bUseAssetRegistryTagsWhitelistInsteadOfBlacklist);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterAssetDataWithNoTags"), Options.bFilterAssetDataWithNoTags);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterDependenciesWithNoTags"), Options.bFilterDependenciesWithNoTags);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterSearchableNames"), Options.bFilterSearchableNames);
// ...
}

This control method can regulate whether to create AssetRegistry.bin at packaging and control which AssetRegistry data to deserialize at runtime (it will not impact DevelopmentAssetRegistry.bin, which can be used for asset auditing).

The deserialization process is as follows:

  1. Check bSerializeAssetRegistry; if true, load AssetRegistry.bin in binary form into memory.
  2. Use the Serialize function to deserialize the binary data.
  3. Release the memory occupied by loading AssetRegistry.bin.

Therefore, the memory consumption of AssetRegistry is based on the serialized data, and FAssetRegistrySerializationOptions controls which data gets serialized.

Runtime/AssetRegistry/Public/AssetRegistryState.h
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
/** Load/Save options used to modify how the cache is serialized. These are read out of the AssetRegistry section of Engine.ini and can be changed per platform. */
struct FAssetRegistrySerializationOptions
{
/** True rather to load/save registry at all */
bool bSerializeAssetRegistry;

/** True rather to load/save dependency info. If true this will handle hard and soft package references */
bool bSerializeDependencies;

/** True rather to load/save dependency info for Name references, */
bool bSerializeSearchableNameDependencies;

/** True rather to load/save dependency info for Manage references, */
bool bSerializeManageDependencies;

/** If true will read/write FAssetPackageData */
bool bSerializePackageData;

/** True if CookFilterlistTagsByClass is a whitelist. False if it is a blacklist. */
bool bUseAssetRegistryTagsWhitelistInsteadOfBlacklist;

/** True if we want to only write out asset data if it has valid tags. This saves memory by not saving data for things like textures */
bool bFilterAssetDataWithNoTags;

/** True if we also want to filter out dependency data for assets that have no tags. Only filters if bFilterAssetDataWithNoTags is also true */
bool bFilterDependenciesWithNoTags;

/** Filter out searchable names from dependency data */
bool bFilterSearchableNames;

/** The map of classname to tag set of tags that are allowed in cooked builds. This is either a whitelist or blacklist depending on bUseAssetRegistryTagsWhitelistInsteadOfBlacklist */
TMap<FName, TSet<FName>> CookFilterlistTagsByClass;

FAssetRegistrySerializationOptions()
: bSerializeAssetRegistry(false)
, bSerializeDependencies(false)
, bSerializeSearchableNameDependencies(false)
, bSerializeManageDependencies(false)
, bSerializePackageData(false)
, bUseAssetRegistryTagsWhitelistInsteadOfBlacklist(false)
, bFilterAssetDataWithNoTags(false)
, bFilterDependenciesWithNoTags(false)
, bFilterSearchableNames(false)
{}

/** Options used to read/write the DevelopmentAssetRegistry, which includes all data */
void ModifyForDevelopment()
{
bSerializeAssetRegistry = bSerializeDependencies = bSerializeSearchableNameDependencies = bSerializeManageDependencies = bSerializePackageData = true;
DisableFilters();
}

/** Disable all filters */
void DisableFilters()
{
bFilterAssetDataWithNoTags = false;
bFilterDependenciesWithNoTags = false;
bFilterSearchableNames = false;
}
};

The configuration reading occurs in the following code:

Runtime/AssetRegistry/Private/AssetRegistry.cpp
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
void UAssetRegistryImpl::InitializeSerializationOptionsFromIni(FAssetRegistrySerializationOptions& Options, const FString& PlatformIniName) const
{
FConfigFile* EngineIni = nullptr;
#if WITH_EDITOR
// Use passed in platform, or current platform if empty
FConfigFile PlatformEngineIni;
FConfigCacheIni::LoadLocalIniFile(PlatformEngineIni, TEXT("Engine"), true, (!PlatformIniName.IsEmpty() ? *PlatformIniName : ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName())));
EngineIni = &PlatformEngineIni;
#else
// In cooked builds, always use the normal engine INI
EngineIni = GConfig->FindConfigFile(GEngineIni);
#endif

EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeAssetRegistry"), Options.bSerializeAssetRegistry);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeDependencies"), Options.bSerializeDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeNameDependencies"), Options.bSerializeSearchableNameDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeManageDependencies"), Options.bSerializeManageDependencies);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bSerializePackageData"), Options.bSerializePackageData);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bUseAssetRegistryTagsWhitelistInsteadOfBlacklist"), Options.bUseAssetRegistryTagsWhitelistInsteadOfBlacklist);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterAssetDataWithNoTags"), Options.bFilterAssetDataWithNoTags);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterDependenciesWithNoTags"), Options.bFilterDependenciesWithNoTags);
EngineIni->GetBool(TEXT("AssetRegistry"), TEXT("bFilterSearchableNames"), Options.bFilterSearchableNames);

TArray<FString> FilterlistItems;
if (Options.bUseAssetRegistryTagsWhitelistInsteadOfBlacklist)
{
EngineIni->GetArray(TEXT("AssetRegistry"), TEXT("CookedTagsWhitelist"), FilterlistItems);
}
else
{
EngineIni->GetArray(TEXT("AssetRegistry"), TEXT("CookedTagsBlacklist"), FilterlistItems);
}

{
// This only needs to be done once, and only on builds using USE_COMPACT_ASSET_REGISTRY
TArray<FString> AsFName;
EngineIni->GetArray(TEXT("AssetRegistry"), TEXT("CookedTagsAsFName"), AsFName);
TArray<FString> AsPathName;
EngineIni->GetArray(TEXT("AssetRegistry"), TEXT("CookedTagsAsPathName"), AsPathName);
TArray<FString> AsLocText;
EngineIni->GetArray(TEXT("AssetRegistry"), TEXT("CookedTagsAsLocText"), AsLocText);
FAssetRegistryState::IngestIniSettingsForCompact(AsFName, AsPathName, AsLocText);
}
// ...
}

In Config/DefaultEngine.ini, creating an AssetRegistry section using the above names can control the serialization of AssetRegistry, which reduces the package size and memory consumption at packaging (the AssetRegistry is loaded into memory when the engine starts).

DefaultEngine.ini
1
2
3
4
5
6
[AssetRegistry]
bSerializeAssetRegistry=false
bSerializeDependencies=false
bSerializeNameDependencies=false
bSerializeManageDependencies=false
bSerializePackageData=false

You can also specify settings for individual platforms by modifying the platform-specific ini files:

1
2
3
Config/Windows/WindowsEngine.ini
Config/Android/AndroidEngine.ini
Config/IOS/IOSEngine.ini

Load Only the Shader Quality Levels Needed

By default, the engine loads all quality level shaders into memory. If there is no need to implement quality switching, unnecessary quality levels can be avoided to reduce shader memory usage.

In Project Settings - Engine - Rendering - Materials - Game Discards Unused Material Quality Levels:

Alternatively, you can add the following configuration to DefaultEngine.ini:

DefaultEngine.ini
1
2
[/Script/Engine.RendererSettings]
r.DiscardUnusedQuality=True

When running in game mode, whether to keep shaders for all quality levels in memory or only those needed for the current quality level.

  • Unchecked: Keep all quality levels in memory allowing a runtime quality level change.(default)
  • Checked: Discard unused quality levels when loading content for the game, saving some memory.

Reduce Shader Variants

You can reduce the number of parent materials and enable the following options in Project Settings - Engine - Rendering:

Share Material Shader Code

In the packaging settings of Project Settings - Packaging, you can set Share Material Shader Code and Shared Material Native Libraries to reduce the size of the package and memory usage (which increases loading time).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 
* By default shader code gets saved inline inside material assets,
* enabling this option will store only shader code once as individual files
* This will reduce overall package size but might increase loading time
*/
UPROPERTY(config, EditAnywhere, Category=Packaging)
bool bShareMaterialShaderCode;

/**
* By default shader shader code gets saved into individual platform agnostic files,
* enabling this option will use the platform-specific library format if and only if one is available
* This will reduce overall package size but might increase loading time
*/
UPROPERTY(config, EditAnywhere, Category=Packaging, meta = (EditCondition = "bShareMaterialShaderCode", ConfigRestartRequired = true))
bool bSharedMaterialNativeLibraries;

After enabling this, the packaged output will generate the following files:

1
2
ShaderArchive-Blank425-PCD3D_SM5.ushaderbytecode
ShaderCode-Global-PCD3D_SM5.ushaderbytecode

However, if subsequent cook resource shader changes occur while the base package still contains old ShaderBytecode information, it may lead to material loss.

There are three solutions:

  1. Package the Shaderbytecode files in the pak during subsequent packaging and load them at mount time;
  2. During cooked hot updates, package the Shaderbytecode within the resource;
  3. Create a ShaderPatch for loading after hot updates.

For a more specific implementation process regarding hot updating Shaderbytecode, you can refer to my previous article UE4 Hot Update: Create Shader Patch.

Disable UMG Template Creation

The engine has a feature to cache blueprint controls for accelerated creation, but it can lead to memory wastage. This can be configured to be disabled:

You can also directly modify the engine’s code to provide default values through class initialization:

Source\Editor\UMGEditor\Public\WidgetBlueprint.h
1
2
UPROPERTY(EditAnywhere, AdvancedDisplay, Category=WidgetBlueprintOptions, AssetRegistrySearchable)
bool bForceSlowConstructionPath;

This variable is checked in the following code:

Source\Editor\UMGEditor\Private\WidgetBlueprintCompiler.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool FWidgetBlueprintCompilerContext::CanAllowTemplate(FCompilerResultsLog& MessageLog, UWidgetBlueprintGeneratedClass* InClass)
{
// ...
// If this widget forces the slow construction path, we can't template it.
if ( WidgetBP->bForceSlowConstructionPath )
{
if (GetDefault<UUMGEditorProjectSettings>()->CompilerOption_CookSlowConstructionWidgetTree(WidgetBP))
{
MessageLog.Note(*LOCTEXT("ForceSlowConstruction", "Fast Templating Disabled By User.").ToString());
return false;
}
else
{
MessageLog.Error(*LOCTEXT("UnableToForceSlowConstruction", "This project has [Cook Slow Construction Widget Tree] disabled, so [Force Slow Construction Path] is no longer allowed.").ToString());
}
}
// ...
}

Disable PakCache

The engine has PakCache enabled by default. When reading files from a Pak, it will read additional memory for caching, which can be a considerable memory usage (viewed through stat memory):

At game startup, PakCache logs will show:

1
2
3
4
[2021.03.23-10.49.21:354][445]LogPakFile: Precache HighWater 16MB
[2021.03.23-10.49.21:382][447]LogPakFile: Precache HighWater 32MB
[2021.03.23-10.49.21:442][450]LogPakFile: Precache HighWater 48MB
[2021.03.23-10.49.21:470][452]LogPakFile: Precache HighWater 64MB

You can configure it to disable with:

1
2
[ConsoleVariables]
pakcache.Enable=0

Disabling PakCache may lead to frequent I/O issues, but the specific performance impact details will need to be analyzed when there is more time.

Unload Pak Entry Filenames

Starting from UE4.23, the engine provides memory optimization configurations for mounting PakFiles:

DefaultEngine.ini
1
2
3
4
5
[Pak]
UnloadPakEntryFilenamesIfPossible=true
DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames="*/Config/Tags/"
+DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames="*/Content/Localization/*"
ShrinkPakEntriesMemoryUsage=true

When FPakPlatformFile executes Initialize, it binds to FCoreDelegates::OnOptimizeMemoryUsageForMountedPaks, and this delegate can be invoked to notify PakPlatformFile to optimize memory for mounted Paks.

Runtime\PakFile\Private\IPlatformFilePak.cpp
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
void FPakPlatformFile::OptimizeMemoryUsageForMountedPaks()
{
#if !(IS_PROGRAM || WITH_EDITOR)
FSlowHeartBeatScope SuspendHeartBeat;
bool bUnloadPakEntryFilenamesIfPossible = FParse::Param(FCommandLine::Get(), TEXT("unloadpakentryfilenames"));
GConfig->GetBool(TEXT("Pak"), TEXT("UnloadPakEntryFilenamesIfPossible"), bUnloadPakEntryFilenamesIfPossible, GEngineIni);

if ((bUnloadPakEntryFilenamesIfPossible && !FParse::Param(FCommandLine::Get(), TEXT("nounloadpakentries"))) || FParse::Param(FCommandLine::Get(), TEXT("unloadpakentries")))
{
// With [Pak] UnloadPakEntryFilenamesIfPossible enabled, [Pak] DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames
// can contain pak entry directory wildcards of which the entire recursive directory structure of filenames underneath a
// matching wildcard will be kept.
//
// Example:
// [Pak]
// DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames="*/Config/Tags/"
// +DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames="*/Content/Localization/*"
TArray<FString> DirectoryRootsToKeep;
GConfig->GetArray(TEXT("Pak"), TEXT("DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames"), DirectoryRootsToKeep, GEngineIni);

FPakPlatformFile* PakPlatformFile = (FPakPlatformFile*)(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName()));
PakPlatformFile->UnloadPakEntryFilenames(&DirectoryRootsToKeep);
}

bool bShrinkPakEntriesMemoryUsage = FParse::Param(FCommandLine::Get(), TEXT("shrinkpakentries"));
GConfig->GetBool(TEXT("Pak"), TEXT("ShrinkPakEntriesMemoryUsage"), bShrinkPakEntriesMemoryUsage, GEngineIni);
if (bShrinkPakEntriesMemoryUsage)
{
FPakPlatformFile* PakPlatformFile = (FPakPlatformFile*)(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName()));
PakPlatformFile->ShrinkPakEntriesMemoryUsage();
}
#endif
}
  • UnloadPakEntryFilenamesIfPossible: Allows unloading memory occupied by PakEntry filenames.
  • DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames: Directories to retain in memory when unloading PakEntry filenames.
  • bShrinkPakEntriesMemoryUsage: Reduces the memory occupied by PakEntry.

After the call, if UnloadPakEntryFilenamesIfPossible is enabled, it will save memory by hashing the list of filenames in the Pak. However, after unloading the Pak entry filenames, wildcard matching paths will no longer be usable.

Runtime\PakFile\Public\IPlatformFilePak.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** Iterator class used to iterate over all files in pak. */
class FFileIterator
{
// ...
/**
* Saves memory by hashing the filenames, if possible. After this process,
* wildcard scanning of pak entries can no longer be performed. Returns TRUE
* if the process successfully unloaded filenames from this pak
*
* @param CrossPakCollisionChecker A map of hash->fileentry records encountered during filename unloading on other pak files. Used to detect collisions with entries in other pak files.
* @param DirectoryRootsToKeep An array of strings in wildcard format that specify whole directory structures of filenames to keep in memory for directory iteration to work.
* @param bAllowRetries If a collision is encountered, change the intial seed and try again a fixed number of times before failing
*/
bool UnloadPakEntryFilenames(TMap<uint64, FPakEntry>& CrossPakCollisionChecker, TArray<FString>* DirectoryRootsToKeep = nullptr, bool bAllowRetries = true);
};

Compress Textures

Texture compression is a lossy process that can reduce both package size and memory size when loaded. Although it is lossy, the reduction in quality on mobile platforms is often negligible, and settings can be adjusted based on project circumstances.

In previous notes, it was mentioned that the default asset quality and size level can be set in Project Settings - Cooker - Texture - ASTC Compression vs Size:

1
2
3
4
5
0=12x12 
1=10x10
2=8x8
3=6x6
4=4x4

You can also set it individually for a specific Texture in the texture asset editor:

Lowest->Highest corresponds to values 0-4, using Default will apply the project’s settings.

Additionally, setting the Compression Settings type will also influence the type of compression applied; Default uses the project’s settings parameter, setting it to NormalMap type will lead to ASTC_4x4.

本篇文章会持续更新,欢迎交流。

Scan the QR code on WeChat and follow me.

Title:UE Performance Analysis: Memory Optimization
Author:LIPENGZHA
Publish Date:2021/03/30 10:59
Word Count:10k Words
Link:https://en.imzlp.com/posts/19135/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!