MemoryMargin

MemoryMargin

#Overview

name: MemoryMargin

The value of this variable can be defined or overridden in .ini config files. 1 .ini config file referencing this setting variable.

It is referenced in 12 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of MemoryMargin is to define a safety buffer in the render asset streaming system, specifically for texture and mesh streaming in Unreal Engine 5. It represents the amount of memory to leave free in the render asset pool to account for temporary allocations and system overhead.

This setting variable is primarily used in the Engine’s rendering and streaming subsystems, particularly in the AsyncTextureStreaming and StreamingManagerTexture modules.

The value of MemoryMargin is set during the initialization of the FRenderAssetStreamingManager. It’s read from the engine configuration file (GEngineIni) under the “TextureStreaming” section with the key “MemoryMargin”. The value is then converted from megabytes to bytes.

MemoryMargin interacts with several other variables in the streaming system, such as:

Developers should be aware that:

  1. MemoryMargin affects the actual available memory for streaming assets.
  2. It’s used in calculations to determine when to reset mip bias and adjust the memory budget.
  3. It’s part of the safety pool in streaming statistics.

Best practices when using this variable include:

  1. Carefully consider the value set in the configuration file, as it directly impacts streaming performance.
  2. Monitor streaming statistics to ensure the margin is appropriate for your game’s needs.
  3. Adjust the value if you notice frequent memory allocation issues or underutilization of available memory.
  4. Consider platform-specific requirements when setting this value, as memory constraints may vary.

#Setting Variables

#References In INI files

Location: <Workspace>/Engine/Config/BaseEngine.ini:1812, section: [TextureStreaming]

#References in C++ code

#Callsites

This variable is referenced in the following C++ source code:

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.cpp:558

Scope (from outer to inner):

file
function     void FRenderAssetStreamingMipCalcTask::UpdateBudgetedMips_Async

Source code excerpt:

	bool bResetMipBias = false;

	if (PerfectWantedMipsBudgetResetThresold - MemoryBudgeted - MeshMemoryBudgeted > TempMemoryBudget + MemoryMargin)
	{
		// Reset the budget tradeoffs if the required pool size shrinked significantly.
		PerfectWantedMipsBudgetResetThresold = MemoryBudgeted;
		bResetMipBias = true;
	}
	else if (MemoryBudgeted + MeshMemoryBudgeted > PerfectWantedMipsBudgetResetThresold)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.cpp:572

Scope (from outer to inner):

file
function     void FRenderAssetStreamingMipCalcTask::UpdateBudgetedMips_Async

Source code excerpt:


	const int64 NonStreamingRenderAssetMemory =  AllocatedMemory - MemoryUsed + MemoryUsedByNonTextures;
	int64 AvailableMemoryForStreaming = PoolSize - NonStreamingRenderAssetMemory - MemoryMargin;

	// If the platform defines a max VRAM usage, check if the pool size must be reduced,
	// but also check if it would be safe to some of the NonStreamingRenderAssetMemory from the pool size computation.
	// The later helps significantly in low budget settings, where NonStreamingRenderAssetMemory would take too much of the pool.
	if (GPoolSizeVRAMPercentage > 0 && TotalGraphicsMemory > 0)
	{
		const int64 UsableVRAM = FMath::Max<int64>(TotalGraphicsMemory * GPoolSizeVRAMPercentage / 100, TotalGraphicsMemory - Settings.VRAMPercentageClamp * 1024ll * 1024ll);
		const int64 UsedVRAM = (int64)GRHIGlobals.NonStreamingTextureMemorySizeInKB * 1024ll + NonStreamingRenderAssetMemory; // Add any other...
		const int64 AvailableVRAMForStreaming = FMath::Min<int64>(UsableVRAM - UsedVRAM - MemoryMargin, PoolSize);
		if (Settings.bLimitPoolSizeToVRAM || AvailableVRAMForStreaming > AvailableMemoryForStreaming)
		{
			AvailableMemoryForStreaming = AvailableVRAMForStreaming;
		}
	}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.cpp:596

Scope (from outer to inner):

file
function     void FRenderAssetStreamingMipCalcTask::UpdateBudgetedMips_Async

Source code excerpt:

		MemoryBudget = FMath::Max<int64>(AvailableMemoryForStreaming, 0);
	}
	else if (AvailableMemoryForStreaming - MemoryBudget > TempMemoryBudget + MemoryMargin)
	{
		// Increase size considering that the variation does not come from temp memory or allocator overhead (or other recurring cause).
		// It's unclear how much temp memory is actually in there, but the value will decrease if temp memory increases.
		MemoryBudget = AvailableMemoryForStreaming;
		bResetMipBias = true;
	}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.cpp:947

Scope (from outer to inner):

file
function     void FRenderAssetStreamingMipCalcTask::UpdateStats_Async

Source code excerpt:

	Stats.UsedStreamingPool = 0;

	Stats.SafetyPool = MemoryMargin; 
	Stats.TemporaryPool = TempMemoryBudget;
	Stats.StreamingPool = MemoryBudget;
	Stats.NonStreamingMips = AllocatedMemory;

	Stats.RequiredPool = 0;
	Stats.VisibleMips = 0;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.cpp:1083

Scope (from outer to inner):

file
function     void FRenderAssetStreamingMipCalcTask::UpdateCSVOnlyStats_Async

Source code excerpt:

	Stats.RenderAssetPool = PoolSize;

	Stats.SafetyPool = MemoryMargin;
	Stats.TemporaryPool = TempMemoryBudget;
	Stats.StreamingPool = MemoryBudget;
	Stats.NonStreamingMips = AllocatedMemory;

	Stats.RequiredPool = 0;
	Stats.CachedMips = 0;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.h:148

Scope (from outer to inner):

file
class        class FRenderAssetStreamingMipCalcTask : public FNonAbandonableTask
function     void Reset

Source code excerpt:

		PoolSize = InPoolSize;
		TempMemoryBudget = InTempMemoryBudget;
		MemoryMargin = InMemoryMargin;

		bAbort = false;
	}

	/** Notifies the async work that it should abort the thread ASAP. */
	void Abort() { bAbort = true; }

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/AsyncTextureStreaming.h:234

Scope (from outer to inner):

file
class        class FRenderAssetStreamingMipCalcTask : public FNonAbandonableTask

Source code excerpt:


	/** How much temp memory is allowed (temp memory is taken when changing mip count). */
	int64 MemoryMargin;

	/** How much memory is available for textures/meshes. */
	int64 MemoryBudget;

	/** How much memory is available for meshes if a separate pool is used. */
	int64 MeshMemoryBudget;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp:118

Scope (from outer to inner):

file
function     FRenderAssetStreamingManager::FRenderAssetStreamingManager

Source code excerpt:

,	bUseDynamicStreaming( false )
,	BoostPlayerTextures( 3.0f )
,	MemoryMargin(0)
,	EffectiveStreamingPoolSize(0)
,	MemoryOverBudget(0)
,	MaxEverRequired(0)
,	bPauseRenderAssetStreaming(false)
,	LastWorldUpdateTime(GIsEditor ? -FLT_MAX : 0) // In editor, visibility is not taken into consideration.
,	LastWorldUpdateTime_MipCalcTask(LastWorldUpdateTime)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp:128

Scope (from outer to inner):

file
function     FRenderAssetStreamingManager::FRenderAssetStreamingManager

Source code excerpt:

	// Read settings from ini file.
	int32 TempInt;
	verify( GConfig->GetInt( TEXT("TextureStreaming"), TEXT("MemoryMargin"),				TempInt,						GEngineIni ) );
	MemoryMargin = TempInt;

	verify( GConfig->GetFloat( TEXT("TextureStreaming"), TEXT("LightmapStreamingFactor"),			GLightmapStreamingFactor,		GEngineIni ) );
	verify( GConfig->GetFloat( TEXT("TextureStreaming"), TEXT("ShadowmapStreamingFactor"),			GShadowmapStreamingFactor,		GEngineIni ) );

	int32 PoolSizeIniSetting = 0;
	GConfig->GetInt(TEXT("TextureStreaming"), TEXT("PoolSize"), PoolSizeIniSetting, GEngineIni);

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp:156

Scope (from outer to inner):

file
function     FRenderAssetStreamingManager::FRenderAssetStreamingManager

Source code excerpt:


	// Convert from MByte to byte.
	MemoryMargin *= 1024 * 1024;

	for ( int32 LODGroup=0; LODGroup < TEXTUREGROUP_MAX; ++LODGroup )
	{
		const TextureGroup TextureLODGroup = (TextureGroup)LODGroup;
		const FTextureLODGroup& TexGroup = UDeviceProfileManager::Get().GetActiveProfile()->GetTextureLODSettings()->GetTextureLODGroup(TextureLODGroup);
		NumStreamedMips_Texture[LODGroup] = TexGroup.NumStreamedMips;

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.cpp:713

Scope (from outer to inner):

file
function     void FRenderAssetStreamingManager::PrepareAsyncTask

Source code excerpt:

	{
		const int64 TempMemoryBudget = static_cast<int64>(Settings.MaxTempMemoryAllowed) * 1024 * 1024;
		AsyncTask.Reset(Stats.TotalGraphicsMemory, Stats.StreamingMemorySize, Stats.TexturePoolSize, TempMemoryBudget, MemoryMargin);
	}
	else
	{
		// Temp must be smaller since membudget only updates if it has a least temp memory available.
		AsyncTask.Reset(0, Stats.StreamingMemorySize, MAX_int64, MAX_int64 / 2, 0);
	}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/Streaming/StreamingManagerTexture.h:395

Scope: file

Source code excerpt:


	/** Amount of memory to leave free in the render asset pool. */
	int64					MemoryMargin;

	/** The actual memory pool size available to stream textures/meshes, excludes non-streaming texture/mesh, temp memory (for streaming mips), memory margin (allocator overhead). */
	int64					EffectiveStreamingPoolSize;

	// Stats we need to keep across frames as we only iterate over a subset of textures.