r.SkinCache.RecomputeTangentsParallelDispatch

r.SkinCache.RecomputeTangentsParallelDispatch

#Overview

name: r.SkinCache.RecomputeTangentsParallelDispatch

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

It is referenced in 12 C++ source files.

#Summary

#Usage in the C++ source code

The purpose of r.SkinCache.RecomputeTangentsParallelDispatch is to control the parallel dispatching of recompute tangents operations in the GPU Skin Cache system of Unreal Engine 5.

This setting variable is primarily used in the GPU Skin Cache subsystem, which is part of the rendering system in Unreal Engine. It affects how the engine computes tangents for skinned meshes on the GPU.

The value of this variable is set through the console variable system in Unreal Engine. It’s defined as an integer with two possible values:

The associated variable GRecomputeTangentsParallelDispatch directly interacts with r.SkinCache.RecomputeTangentsParallelDispatch. They share the same value and are used interchangeably in the code.

Developers must be aware that enabling this option (setting it to 1) will change the order of operations in the skin cache system and may increase memory usage. It could potentially improve performance but at the cost of higher memory consumption.

Best practices when using this variable include:

  1. Test performance with both settings (0 and 1) to determine which works best for your specific use case.
  2. Monitor memory usage when enabling this option, especially on memory-constrained platforms.
  3. Consider the trade-off between potential performance gains and increased memory usage.

Regarding the associated variable GRecomputeTangentsParallelDispatch:

#References in C++ code

#Callsites

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

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:162

Scope: file

Source code excerpt:

int32 GRecomputeTangentsParallelDispatch = 0;
FAutoConsoleVariableRef CVarRecomputeTangentsParallelDispatch(
	TEXT("r.SkinCache.RecomputeTangentsParallelDispatch"),
	GRecomputeTangentsParallelDispatch,
	TEXT("This option enables parallel dispatches for recompute tangents.\n")
	TEXT(" 0: off (default), triangle pass is interleaved with vertex pass, requires resource barriers in between. \n")
	TEXT(" 1: on, batch triangle passes together, resource barrier, followed by vertex passes together, cost more memory. \n"),
	ECVF_RenderThreadSafe
);

#Associated Variable and Callsites

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

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:160

Scope: file

Source code excerpt:

);

int32 GRecomputeTangentsParallelDispatch = 0;
FAutoConsoleVariableRef CVarRecomputeTangentsParallelDispatch(
	TEXT("r.SkinCache.RecomputeTangentsParallelDispatch"),
	GRecomputeTangentsParallelDispatch,
	TEXT("This option enables parallel dispatches for recompute tangents.\n")
	TEXT(" 0: off (default), triangle pass is interleaved with vertex pass, requires resource barriers in between. \n")
	TEXT(" 1: on, batch triangle passes together, resource barrier, followed by vertex passes together, cost more memory. \n"),
	ECVF_RenderThreadSafe
);

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:928

Scope (from outer to inner):

file
class        class FBaseRecomputeTangentsPerTriangleShader : public FGlobalShader
function     void SetParameters

Source code excerpt:

		// UAV
		SetUAVParameter(BatchedParameters, IntermediateAccumBufferUAV, StagingBuffer.UAV);
		SetShaderValue(BatchedParameters, IntermediateAccumBufferOffset, GRecomputeTangentsParallelDispatch * DispatchData.IntermediateAccumulatedTangentBufferOffset);

        if (!GAllowDupedVertsForRecomputeTangents)
        {
		    SetSRVParameter(BatchedParameters, DuplicatedIndices, DispatchData.DuplicatedIndices);
            SetSRVParameter(BatchedParameters, DuplicatedIndicesIndices, DispatchData.DuplicatedIndicesIndices);
        }

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1044

Scope (from outer to inner):

file
class        class FBaseRecomputeTangentsPerVertexShader : public FGlobalShader
function     void SetParameters

Source code excerpt:

		// UAVs
		SetUAVParameter(BatchedParameters, IntermediateAccumBufferUAV, StagingBuffer.UAV);
		SetShaderValue(BatchedParameters, IntermediateAccumBufferOffset, GRecomputeTangentsParallelDispatch * DispatchData.IntermediateAccumulatedTangentBufferOffset);
		SetUAVParameter(BatchedParameters, TangentBufferUAV, DispatchData.GetTangentRWBuffer()->Buffer.UAV);

		SetSRVParameter(BatchedParameters, TangentInputBuffer, DispatchData.IntermediateTangentBuffer ? DispatchData.IntermediateTangentBuffer->Buffer.SRV : nullptr);

		SetSRVParameter(BatchedParameters, ColorInputBuffer, DispatchData.ColorBufferSRV);
	}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1098

Scope (from outer to inner):

file
function     void FGPUSkinCache::DispatchUpdateSkinTangents

Source code excerpt:

	if (bTrianglePass)
	{
		if (!GRecomputeTangentsParallelDispatch)
		{	
			if (StagingBuffers.Num() != GNumTangentIntermediateBuffers)
			{
				// Release extra buffers if shrinking
				for (int32 Index = GNumTangentIntermediateBuffers; Index < StagingBuffers.Num(); ++Index)
				{

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1167

Scope (from outer to inner):

file
function     void FGPUSkinCache::DispatchUpdateSkinTangents

Source code excerpt:

			}

			if (!GRecomputeTangentsParallelDispatch)
			{
				// When triangle & vertex passes are interleaved, resource transition is needed in between.
				RHICmdList.Transition({
					DispatchData.GetActiveTangentRWBuffer()->UpdateAccessState(ERHIAccess::SRVCompute),
					StagingBuffer->UpdateAccessState(ERHIAccess::UAVCompute)
				});

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1178

Scope (from outer to inner):

file
function     void FGPUSkinCache::DispatchUpdateSkinTangents

Source code excerpt:

			INC_DWORD_STAT_BY(STAT_GPUSkinCache_NumTrianglesForRecomputeTangents, NumTriangles);

			const FRWBuffer& ShaderStagingBuffer = GRecomputeTangentsParallelDispatch ? DispatchData.GetIntermediateAccumulatedTangentBuffer()->Buffer : StagingBuffer->Buffer;

			FRHIComputeShader* ShaderRHI = Shader.GetComputeShader();
			SetComputePipelineState(RHICmdList, Shader.GetComputeShader());

			SetShaderParametersLegacyCS(RHICmdList, Shader, Entry, DispatchData, ShaderStagingBuffer);
			DispatchComputeShader(RHICmdList, Shader.GetShader(), ThreadGroupCountValue, 1, 1);

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1207

Scope (from outer to inner):

file
function     void FGPUSkinCache::DispatchUpdateSkinTangents

Source code excerpt:

		uint32 ThreadGroupCountValue = FMath::DivideAndRoundUp(VertexCount, ComputeShader->ThreadGroupSizeX);

		if (!GRecomputeTangentsParallelDispatch)
		{
			// When triangle & vertex passes are interleaved, resource transition is needed in between.
			RHICmdList.Transition({
				DispatchData.GetTangentRWBuffer()->UpdateAccessState(ERHIAccess::UAVCompute),
				StagingBuffer->UpdateAccessState(ERHIAccess::UAVCompute)
				});

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1218

Scope (from outer to inner):

file
function     void FGPUSkinCache::DispatchUpdateSkinTangents

Source code excerpt:

		SetComputePipelineState(RHICmdList, ComputeShader.GetComputeShader());

		SetShaderParametersLegacyCS(RHICmdList, ComputeShader, Entry, DispatchData, GRecomputeTangentsParallelDispatch ? DispatchData.GetIntermediateAccumulatedTangentBuffer()->Buffer : StagingBuffer->Buffer);
		DispatchComputeShader(RHICmdList, ComputeShader.GetShader(), ThreadGroupCountValue, 1, 1);
		UnsetShaderParametersLegacyCS(RHICmdList, ComputeShader);

		IncrementDispatchCounter(RHICmdList);
	}
}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1512

Scope (from outer to inner):

file
function     void FGPUSkinCache::DoDispatch

Source code excerpt:

			BuffersToSRVForRecomputeTangents.Add(DispatchData.GetPositionRWBuffer());
			BuffersToSRVForRecomputeTangents.Add(DispatchData.GetActiveTangentRWBuffer());
			if (GRecomputeTangentsParallelDispatch)
			{
				IntermediateAccumulatedTangentBuffers.Add(DispatchData.GetIntermediateAccumulatedTangentBuffer());
			}
			BuffersToTransitionToRead.Add(DispatchData.GetPositionRWBuffer());
		}	
	}

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1534

Scope (from outer to inner):

file
function     void FGPUSkinCache::DoDispatch

Source code excerpt:

			{
				DispatchUpdateSkinTangents(RHICmdList, DispatchItem.SkinCacheEntry, DispatchItem.Section, StagingBuffer, true);
				if (!GRecomputeTangentsParallelDispatch)
				{
					// When parallel dispatching is off, triangle pass and vertex pass are dispatched interleaved.
					DispatchUpdateSkinTangents(RHICmdList, DispatchItem.SkinCacheEntry, DispatchItem.Section, StagingBuffer, false);
				}
			}
		}
		if (GRecomputeTangentsParallelDispatch)
		{
			// Do necessary buffer transitions before vertex pass dispatches
			TArray<FSkinCacheRWBuffer*> TangentBuffers;
			for (int32 i = 0; i < BatchCount; ++i)
			{
				FDispatchEntry& DispatchItem = BatchDispatches[i];

#Loc: <Workspace>/Engine/Source/Runtime/Engine/Private/GPUSkinCache.cpp:1631

Scope (from outer to inner):

file
function     void FGPUSkinCache::DoDispatch

Source code excerpt:

			DispatchData.GetActiveTangentRWBuffer()->UpdateAccessState(ERHIAccess::SRVCompute)
		});
		if (GRecomputeTangentsParallelDispatch)
		{
			RHICmdList.Transition(DispatchData.GetIntermediateAccumulatedTangentBuffer()->UpdateAccessState(ERHIAccess::UAVCompute));
		}
		BuffersToTransitionToRead.Add(DispatchData.GetPositionRWBuffer());

		FSkinCacheRWBuffer* StagingBuffer = nullptr;
		DispatchUpdateSkinTangents(RHICmdList, SkinCacheEntry, Section, StagingBuffer, true);
		if (GRecomputeTangentsParallelDispatch)
		{
			RHICmdList.Transition({
				DispatchData.GetTangentRWBuffer()->UpdateAccessState(ERHIAccess::UAVCompute),
				DispatchData.GetIntermediateAccumulatedTangentBuffer()->UpdateAccessState(ERHIAccess::UAVCompute)
			});
		}