foliage.ForceLOD

foliage.ForceLOD

#Overview

name: foliage.ForceLOD

This variable is created as a Console Variable (cvar).

It is referenced in 13 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of foliage.ForceLOD is to force the Level of Detail (LOD) for foliage rendering in Unreal Engine 5. This setting variable is primarily used in the rendering system, specifically for managing the detail level of foliage elements in the game world.

Unreal Engine subsystems that rely on this setting variable include:

  1. The Hierarchical Instanced Static Mesh (HISM) system, which is part of the Engine module.
  2. The Movie Render Pipeline, which is a plugin for high-quality rendering of cinematics.

The value of this variable is set through the console variable system. It can be modified at runtime using console commands or through code using the CVarForceLOD variable.

Other variables that interact with foliage.ForceLOD include:

  1. foliage.DitheredLOD
  2. r.ParticleLODBias
  3. r.ForceLOD (a similar variable used for general LOD forcing)

Developers must be aware of the following when using this variable:

  1. Setting a value greater than or equal to zero forces the foliage LOD to that specific level.
  2. It overrides the normal LOD selection process, which could impact performance and visual quality.
  3. It’s often used in conjunction with other LOD-related settings for comprehensive control over rendering detail.

Best practices when using this variable:

  1. Use it primarily for debugging or specific optimization scenarios.
  2. Be cautious when setting it in production builds, as it can significantly affect performance and visual quality.
  3. Consider the interaction with other LOD-related settings to ensure consistent behavior.
  4. Reset the value to -1 when not actively using it to allow normal LOD selection to occur.

Regarding the associated variable CVarForceLOD:

The purpose of CVarForceLOD is to provide a programmatic way to control the foliage.ForceLOD setting. It’s an internal representation of the console variable used by the engine code.

CVarForceLOD is used in the Engine and RenderCore modules. It’s typically set and accessed through the TAutoConsoleVariable system, which allows for runtime modification of engine behavior.

The value of CVarForceLOD is set through the console variable system, just like foliage.ForceLOD.

CVarForceLOD interacts closely with the rendering system, particularly in the HISM rendering code and the general LOD forcing system (r.ForceLOD).

Developers should be aware that CVarForceLOD is the actual variable used in the C++ code to control LOD forcing, while foliage.ForceLOD is the console-accessible name.

Best practices for CVarForceLOD are similar to those for foliage.ForceLOD, with the addition of using the appropriate getter methods (GetValueOnRenderThread() or GetValueOnAnyThread()) depending on the context in which it’s being accessed.

#References in C++ code

#Callsites

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

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:45

Scope: file

Source code excerpt:


static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("foliage.ForceLOD"),
	-1,
	TEXT("If greater than or equal to zero, forces the foliage LOD to that level."));

static TAutoConsoleVariable<int32> CVarOnlyLOD(
	TEXT("foliage.OnlyLOD"),
	-1,

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/Graph/Nodes/MovieGraphGlobalGameOverrides.cpp:54

Scope (from outer to inner):

file
function     void UMovieGraphGlobalGameOverridesNode::BuildNewProcessCommandLineArgsImpl

Source code excerpt:

		InOutDeviceProfileCvars.Add(TEXT("r.ParticleLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.DitheredLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.ForceLOD=0"));
	}

	if (bDisableHLODs)
	{
		// It's a command and not an integer cvar (despite taking 1/0)
		InOutExecCmds.Add(TEXT("r.HLOD 0"));

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/Graph/Nodes/MovieGraphGlobalGameOverrides.cpp:120

Scope (from outer to inner):

file
function     void UMovieGraphGlobalGameOverridesNode::ApplySettings

Source code excerpt:

		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousParticleLODBias, TEXT("r.ParticleLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageDitheredLOD, TEXT("foliage.DitheredLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageForceLOD, TEXT("foliage.ForceLOD"), 0, bOverrideValues);
	}

	if (bDisableHLODs)
	{
		// It's a command and not an integer cvar (despite taking 1/0), so we can't cache it 
		if (GEngine && InWorld)

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineGameOverrideSetting.cpp:68

Scope (from outer to inner):

file
function     void UMoviePipelineGameOverrideSetting::ApplyCVarSettings

Source code excerpt:

		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousParticleLODBias, TEXT("r.ParticleLODBias"), -10, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageDitheredLOD, TEXT("foliage.DitheredLOD"), 0, bOverrideValues);
		MOVIEPIPELINE_STORE_AND_OVERRIDE_CVAR_INT(PreviousFoliageForceLOD, TEXT("foliage.ForceLOD"), 0, bOverrideValues);
	}

	if (bDisableHLODs)
	{
		// It's a command and not an integer cvar (despite taking 1/0), so we can't cache it 
		if(GEngine)

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Private/MoviePipelineGameOverrideSetting.cpp:194

Scope (from outer to inner):

file
function     void UMoviePipelineGameOverrideSetting::BuildNewProcessCommandLineArgsImpl

Source code excerpt:

		InOutDeviceProfileCvars.Add(TEXT("r.ParticleLODBias=-10"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.DitheredLOD=0"));
		InOutDeviceProfileCvars.Add(TEXT("foliage.ForceLOD=0"));
	}

	if (bDisableHLODs)
	{
		// It's a command and not an integer cvar (despite taking 1/0)
		InOutExecCmds.Add(TEXT("r.HLOD 0"));

#Loc: <Workspace>/Engine/Plugins/MovieScene/MovieRenderPipeline/Source/MovieRenderPipelineCore/Public/Graph/Nodes/MovieGraphGlobalGameOverrides.h:121

Scope (from outer to inner):

file
function     class MOVIERENDERPIPELINECORE_API UMovieGraphGlobalGameOverridesNode : public UMovieGraphSettingNode { GENERATED_BODY

Source code excerpt:

	 * - r.ParticleLODBias
	 * - foliage.DitheredLOD
	 * - foliage.ForceLOD
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering", meta = (EditCondition = "bOverride_bDisableLODs"))
	bool bDisableLODs;

	/**
	 * Determines if hierarchical LODs should be disabled and their real meshes used instead, regardless of distance.
	 * Note that this does not affect World Partition HLODs.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering", meta = (EditCondition = "bOverride_bDisableHLODs"))
	bool bDisableHLODs;

	/**
	 * Flushing level streaming ensures that any pending changes to sub-levels or world partition are fully processed before we render
	 * the frame. This feature generally only adds to render times on the frames that have level visibility state changes, so generally
	 * safe to leave turned on all the time.
	 */

#Associated Variable and Callsites

This variable is associated with another variable named CVarForceLOD. They share the same value. See the following C++ source code.

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:41

Scope: file

Source code excerpt:

static TAutoConsoleVariable<int32> CVarFoliageSplitFactor(
	TEXT("foliage.SplitFactor"),
	16,
	TEXT("This controls the branching factor of the foliage tree."));

static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("foliage.ForceLOD"),
	-1,
	TEXT("If greater than or equal to zero, forces the foliage LOD to that level."));

static TAutoConsoleVariable<int32> CVarOnlyLOD(
	TEXT("foliage.OnlyLOD"),
	-1,
	TEXT("If greater than or equal to zero, only renders the foliage LOD at that level."));

static TAutoConsoleVariable<int32> CVarDisableCull(
	TEXT("foliage.DisableCull"),

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1489

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:

	QUICK_SCOPE_CYCLE_COUNTER(STAT_HierarchicalInstancedStaticMeshSceneProxy_GetMeshElements);
	SCOPE_CYCLE_COUNTER(STAT_HISMCGetDynamicMeshElement);

	bool bMultipleSections = bDitheredLODTransitions && CVarDitheredLOD.GetValueOnRenderThread() > 0;
	// Disable multiple selections when forced LOD is set
	bMultipleSections = bMultipleSections && ForcedLodModel <= 0 && CVarForceLOD.GetValueOnRenderThread() < 0;

	bool bSingleSections = !bMultipleSections;
	bool bOverestimate = CVarOverestimateLOD.GetValueOnRenderThread() > 0;

	int32 MinVertsToSplitNode = CVarMinVertsToSplitNode.GetValueOnRenderThread();

	const FMatrix WorldToLocal = GetLocalToWorld().Inverse();

	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
	{
		if (VisibilityMap & (1 << ViewIndex))

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1745

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:

					{
						UseMinLOD = FMath::Max(UseMinLOD, DebugMin);
					}
					int32 UseMaxLOD = InstanceParams.LODs;

					int32 Force = CVarForceLOD.GetValueOnRenderThread() >= 0 ? CVarForceLOD.GetValueOnRenderThread() : (ForcedLodModel > 0 ? ForcedLodModel : -1); 
					if (Force >= 0)
					{
						UseMinLOD = FMath::Clamp(Force, 0, InstanceParams.LODs - 1);
						UseMaxLOD = FMath::Clamp(Force, 0, InstanceParams.LODs - 1);
					}

					// Clamp the min LOD to available LOD taking mesh streaming into account as well
					const int8 CurFirstLODIdx = GetCurrentFirstLODIdx_RenderThread();
					UseMinLOD = FMath::Max(UseMinLOD, CurFirstLODIdx);

					if (CVarCullAll.GetValueOnRenderThread() < 1)

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp:1823

Scope (from outer to inner):

file
function     void FHierarchicalStaticMeshSceneProxy::GetDynamicMeshElements

Source code excerpt:


				if (UnbuiltInstanceCount < 1000 && UnbuiltBounds.Num() >= UnbuiltInstanceCount)
				{
					const int32 NumLODs = RenderData->LODResources.Num();

					int32 Force = CVarForceLOD.GetValueOnRenderThread() >= 0 ? CVarForceLOD.GetValueOnRenderThread() : (ForcedLodModel > 0 ? ForcedLodModel : -1);
					if (Force >= 0)
					{
						Force = FMath::Clamp(Force, 0, NumLODs - 1);
						int32 LastInstanceIndex = FirstUnbuiltIndex + UnbuiltInstanceCount - 1;
						InstanceParams.AddRun(Force, Force, FirstUnbuiltIndex, LastInstanceIndex);
					}
					else
					{
						FVector ViewOriginInLocalZero = WorldToLocal.TransformPosition(View->GetTemporalLODOrigin(0, bMultipleSections));
						FVector ViewOriginInLocalOne  = WorldToLocal.TransformPosition(View->GetTemporalLODOrigin(1, bMultipleSections));
						float LODPlanesMax[MAX_STATIC_MESH_LODS];

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:205

Scope: file

Source code excerpt:


#define EXPOSE_FORCE_LOD !(UE_BUILD_SHIPPING || UE_BUILD_TEST) || WITH_EDITOR

#if EXPOSE_FORCE_LOD

static TAutoConsoleVariable<int32> CVarForceLOD(
	TEXT("r.ForceLOD"),
	-1,
	TEXT("LOD level to force, -1 is off."),
	ECVF_Scalability | ECVF_Default | ECVF_RenderThreadSafe
	);

static TAutoConsoleVariable<int32> CVarForceLODShadow(
	TEXT("r.ForceLODShadow"),
	-1,
	TEXT("LOD level to force for the shadow map generation only, -1 is off."),
	ECVF_Scalability | ECVF_Default | ECVF_RenderThreadSafe

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:278

Scope (from outer to inner):

file
function     int32 GetCVarForceLOD

Source code excerpt:

{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD
	{
		Ret = CVarForceLOD.GetValueOnRenderThread();
	}
#endif // EXPOSE_FORCE_LOD

	return Ret;
}

RENDERCORE_API int32 GetCVarForceLOD_AnyThread()
{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD
	{
		Ret = CVarForceLOD.GetValueOnAnyThread();
	}
#endif // EXPOSE_FORCE_LOD

	return Ret;
}

RENDERCORE_API int32 GetCVarForceLODShadow()
{
	int32 Ret = -1;

#if EXPOSE_FORCE_LOD

#Loc: <Workspace>/Engine/Source/Runtime/RenderCore/Private/RenderCore.cpp:291

Scope (from outer to inner):

file
function     int32 GetCVarForceLOD_AnyThread

Source code excerpt:

#if EXPOSE_FORCE_LOD
	{
		Ret = CVarForceLOD.GetValueOnAnyThread();
	}
#endif // EXPOSE_FORCE_LOD

	return Ret;
}