2020-02-07 11:01:05 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2020-02-06 13:13:41 -05:00
/*=============================================================================
ShaderResource . cpp : ShaderResource implementation .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
# include "Shader.h"
# include "Misc/CoreMisc.h"
2020-04-21 13:46:17 -04:00
# include "Misc/StringBuilder.h"
2020-02-06 13:13:41 -05:00
# include "Stats/StatsMisc.h"
# include "Serialization/MemoryWriter.h"
# include "VertexFactory.h"
# include "ProfilingDebugging/DiagnosticTable.h"
# include "Interfaces/ITargetPlatform.h"
# include "Interfaces/ITargetPlatformManagerModule.h"
# include "Interfaces/IShaderFormat.h"
# include "ShaderCodeLibrary.h"
# include "ShaderCore.h"
# include "RenderUtils.h"
# include "Misc/ConfigCacheIni.h"
# include "Misc/ScopeLock.h"
# include "UObject/RenderingObjectVersion.h"
# include "UObject/FortniteMainBranchObjectVersion.h"
# include "ProfilingDebugging/LoadTimeTracker.h"
# include "Misc/MemStack.h"
2020-06-23 18:40:00 -04:00
# include "ShaderCompilerCore.h"
2022-01-04 00:58:30 -05:00
# include "Compression/OodleDataCompression.h"
2020-02-06 13:13:41 -05:00
# if WITH_EDITORONLY_DATA
# include "Interfaces/IShaderFormat.h"
# endif
2022-06-14 17:03:34 -04:00
DECLARE_LOG_CATEGORY_CLASS ( LogShaderWarnings , Log , Log ) ;
2022-01-04 00:58:30 -05:00
int32 GShaderCompressionFormatChoice = 2 ;
2021-03-11 21:19:35 -04:00
static FAutoConsoleVariableRef CVarShaderCompressionFormatChoice (
TEXT ( " r.Shaders.CompressionFormat " ) ,
GShaderCompressionFormatChoice ,
TEXT ( " Select the compression methods for the shader code. \n " )
TEXT ( " 0: None (uncompressed) \n " )
2022-01-04 00:58:30 -05:00
TEXT ( " 1: LZ4 \n " )
TEXT ( " 2: Oodle (default) \n " )
TEXT ( " 3: ZLib \n " ) ,
ECVF_ReadOnly ) ;
2022-01-06 20:16:56 -05:00
int32 GShaderCompressionOodleAlgo = 2 ;
2022-01-04 00:58:30 -05:00
static FAutoConsoleVariableRef CVarShaderCompressionOodleAlgo (
TEXT ( " r.Shaders.CompressionFormat.Oodle.Algo " ) ,
GShaderCompressionOodleAlgo ,
TEXT ( " Oodle compression method for the shader code, from fastest to slowest to decode. \n " )
TEXT ( " 0: None (invalid setting) \n " )
TEXT ( " 1: Selkie (fastest to decode) \n " )
TEXT ( " 2: Mermaid \n " )
TEXT ( " 3: Kraken \n " )
TEXT ( " 4: Leviathan (slowest to decode) \n " ) ,
ECVF_ReadOnly ) ;
int32 GShaderCompressionOodleLevel = 6 ;
static FAutoConsoleVariableRef CVarShaderCompressionOodleAlgoChoice (
TEXT ( " r.Shaders.CompressionFormat.Oodle.Level " ) ,
GShaderCompressionOodleLevel ,
TEXT ( " Oodle compression level. This mostly trades encode speed vs compression ratio, decode speed is determined by r.Shaders.CompressionFormat.Oodle.Algo \n " )
TEXT ( " -4 : HyperFast4 \n " )
TEXT ( " -3 : HyperFast3 \n " )
TEXT ( " -2 : HyperFast2 \n " )
TEXT ( " -1 : HyperFast1 \n " )
TEXT ( " 0 : None \n " )
TEXT ( " 1 : SuperFast \n " )
TEXT ( " 2 : VeryFast \n " )
TEXT ( " 3 : Fast \n " )
TEXT ( " 4 : Normal \n " )
TEXT ( " 5 : Optimal1 \n " )
TEXT ( " 6 : Optimal2 \n " )
TEXT ( " 7 : Optimal3 \n " )
TEXT ( " 8 : Optimal4 \n " ) ,
2021-03-11 21:19:35 -04:00
ECVF_ReadOnly ) ;
2022-06-14 17:03:34 -04:00
static int32 GShaderCompilerEmitWarningsOnLoad = 0 ;
static FAutoConsoleVariableRef CVarShaderCompilerEmitWarningsOnLoad (
TEXT ( " r.ShaderCompiler.EmitWarningsOnLoad " ) ,
GShaderCompilerEmitWarningsOnLoad ,
TEXT ( " When 1, shader compiler warnings are emitted to the log for all shaders as they are loaded. " ) ,
ECVF_Default
) ;
2021-03-11 21:19:35 -04:00
FName GetShaderCompressionFormat ( const FName & ShaderFormat )
{
// support an older developer-only CVar for compatibility and make it preempt
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
static const IConsoleVariable * CVarSkipCompression = IConsoleManager : : Get ( ) . FindConsoleVariable ( TEXT ( " r.Shaders.SkipCompression " ) ) ;
static bool bSkipCompression = ( CVarSkipCompression & & CVarSkipCompression - > GetInt ( ) ! = 0 ) ;
if ( UNLIKELY ( bSkipCompression ) )
{
return NAME_None ;
}
# endif
2022-01-04 00:58:30 -05:00
static FName Formats [ ]
{
NAME_None ,
NAME_LZ4 ,
NAME_Oodle ,
NAME_Zlib
} ;
//GShaderCompressionFormatChoice = (GShaderCompressionFormatChoice < 0) ? 0 : GShaderCompressionFormatChoice;
2022-02-25 10:09:25 -05:00
GShaderCompressionFormatChoice = FMath : : Clamp < int32 > ( GShaderCompressionFormatChoice , 0 , UE_ARRAY_COUNT ( Formats ) - 1 ) ;
2022-01-04 00:58:30 -05:00
return Formats [ GShaderCompressionFormatChoice ] ;
}
void GetShaderCompressionOodleSettings ( FOodleDataCompression : : ECompressor & OutCompressor , FOodleDataCompression : : ECompressionLevel & OutLevel , const FName & ShaderFormat )
{
// support an older developer-only CVar for compatibility and make it preempt
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
static const IConsoleVariable * CVarSkipCompression = IConsoleManager : : Get ( ) . FindConsoleVariable ( TEXT ( " r.Shaders.SkipCompression " ) ) ;
static bool bSkipCompression = ( CVarSkipCompression & & CVarSkipCompression - > GetInt ( ) ! = 0 ) ;
if ( UNLIKELY ( bSkipCompression ) )
{
OutCompressor = FOodleDataCompression : : ECompressor : : Selkie ;
OutLevel = FOodleDataCompression : : ECompressionLevel : : None ;
return ;
}
# endif
GShaderCompressionOodleAlgo = FMath : : Clamp ( GShaderCompressionOodleAlgo , static_cast < int32 > ( FOodleDataCompression : : ECompressor : : NotSet ) , static_cast < int32 > ( FOodleDataCompression : : ECompressor : : Leviathan ) ) ;
OutCompressor = static_cast < FOodleDataCompression : : ECompressor > ( GShaderCompressionOodleAlgo ) ;
GShaderCompressionOodleLevel = FMath : : Clamp ( GShaderCompressionOodleLevel , static_cast < int32 > ( FOodleDataCompression : : ECompressionLevel : : HyperFast4 ) , static_cast < int32 > ( FOodleDataCompression : : ECompressionLevel : : Optimal4 ) ) ;
OutLevel = static_cast < FOodleDataCompression : : ECompressionLevel > ( GShaderCompressionOodleLevel ) ;
2021-03-11 21:19:35 -04:00
}
2020-02-06 13:13:41 -05:00
bool FShaderMapResource : : ArePlatformsCompatible ( EShaderPlatform CurrentPlatform , EShaderPlatform TargetPlatform )
{
bool bFeatureLevelCompatible = CurrentPlatform = = TargetPlatform ;
if ( ! bFeatureLevelCompatible & & IsPCPlatform ( CurrentPlatform ) & & IsPCPlatform ( TargetPlatform ) )
{
bFeatureLevelCompatible = GetMaxSupportedFeatureLevel ( CurrentPlatform ) > = GetMaxSupportedFeatureLevel ( TargetPlatform ) ;
2021-03-11 19:26:01 -04:00
bool const bIsTargetD3D = IsD3DPlatform ( TargetPlatform ) ;
2020-02-06 13:13:41 -05:00
2021-03-11 19:26:01 -04:00
bool const bIsCurrentPlatformD3D = IsD3DPlatform ( CurrentPlatform ) ;
2020-02-06 13:13:41 -05:00
// For Metal in Editor we can switch feature-levels, but not in cooked projects when using Metal shader librariss.
bool const bIsCurrentMetal = IsMetalPlatform ( CurrentPlatform ) ;
bool const bIsTargetMetal = IsMetalPlatform ( TargetPlatform ) ;
bool const bIsMetalCompatible = ( bIsCurrentMetal = = bIsTargetMetal )
# if !WITH_EDITOR // Static analysis doesn't like (|| WITH_EDITOR)
& & ( ! IsMetalPlatform ( CurrentPlatform ) | | ( CurrentPlatform = = TargetPlatform ) )
# endif
;
bool const bIsCurrentOpenGL = IsOpenGLPlatform ( CurrentPlatform ) ;
bool const bIsTargetOpenGL = IsOpenGLPlatform ( TargetPlatform ) ;
bFeatureLevelCompatible = bFeatureLevelCompatible & & ( bIsCurrentPlatformD3D = = bIsTargetD3D & & bIsMetalCompatible & & bIsCurrentOpenGL = = bIsTargetOpenGL ) ;
}
return bFeatureLevelCompatible ;
}
# if RHI_RAYTRACING
2022-05-19 07:03:30 -04:00
class FRayTracingShaderLibrary
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
public :
uint32 AddShader ( FRHIRayTracingShader * Shader )
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
FScopeLock Lock ( & CS ) ;
if ( UnusedIndicies . Num ( ) ! = 0 )
{
uint32 Index = UnusedIndicies . Pop ( false ) ;
checkSlow ( Shaders [ Index ] = = nullptr ) ;
Shaders [ Index ] = Shader ;
return Index ;
}
else
{
Shaders . Add ( Shader ) ;
return Shaders . Num ( ) - 1 ;
}
2020-02-06 13:13:41 -05:00
}
2022-05-19 07:03:30 -04:00
void RemoveShader ( uint32 Index )
{
if ( Index ! = ~ 0u )
{
FScopeLock Lock ( & CS ) ;
UnusedIndicies . Push ( Index ) ;
Shaders [ Index ] = nullptr ;
}
}
void GetShaders ( TArray < FRHIRayTracingShader * > & OutShaders , FRHIRayTracingShader * DefaultShader )
{
FScopeLock Lock ( & CS ) ;
OutShaders = Shaders ;
for ( uint32 Index : UnusedIndicies )
{
OutShaders [ Index ] = DefaultShader ;
}
}
private :
TArray < uint32 > UnusedIndicies ;
TArray < FRHIRayTracingShader * > Shaders ;
FCriticalSection CS ;
} ;
static FRayTracingShaderLibrary GlobalRayTracingHitGroupLibrary ;
static FRayTracingShaderLibrary GlobalRayTracingCallableShaderLibrary ;
static FRayTracingShaderLibrary GlobalRayTracingMissShaderLibrary ;
void FShaderMapResource : : GetRayTracingHitGroupLibrary ( TArray < FRHIRayTracingShader * > & RayTracingShaders , FRHIRayTracingShader * DefaultShader )
{
GlobalRayTracingHitGroupLibrary . GetShaders ( RayTracingShaders , DefaultShader ) ;
2020-02-06 13:13:41 -05:00
}
2022-05-19 07:03:30 -04:00
void FShaderMapResource : : GetRayTracingCallableShaderLibrary ( TArray < FRHIRayTracingShader * > & RayTracingCallableShaders , FRHIRayTracingShader * DefaultShader )
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
GlobalRayTracingCallableShaderLibrary . GetShaders ( RayTracingCallableShaders , DefaultShader ) ;
2020-02-06 13:13:41 -05:00
}
2022-05-19 07:03:30 -04:00
void FShaderMapResource : : GetRayTracingMissShaderLibrary ( TArray < FRHIRayTracingShader * > & RayTracingMissShaders , FRHIRayTracingShader * DefaultShader )
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
GlobalRayTracingMissShaderLibrary . GetShaders ( RayTracingMissShaders , DefaultShader ) ;
2020-02-06 13:13:41 -05:00
}
# endif // RHI_RAYTRACING
static void ApplyResourceStats ( FShaderMapResourceCode & Resource )
{
# if STATS
INC_DWORD_STAT_BY ( STAT_Shaders_ShaderResourceMemory , Resource . GetSizeBytes ( ) ) ;
2020-04-21 13:46:17 -04:00
for ( const FShaderMapResourceCode : : FShaderEntry & Shader : Resource . ShaderEntries )
2020-02-06 13:13:41 -05:00
{
2020-04-21 13:46:17 -04:00
INC_DWORD_STAT_BY_FName ( GetMemoryStatType ( Shader . Frequency ) . GetName ( ) , Shader . Code . Num ( ) ) ;
2020-02-06 13:13:41 -05:00
}
# endif // STATS
}
static void RemoveResourceStats ( FShaderMapResourceCode & Resource )
{
# if STATS
DEC_DWORD_STAT_BY ( STAT_Shaders_ShaderResourceMemory , Resource . GetSizeBytes ( ) ) ;
2020-04-21 13:46:17 -04:00
for ( const FShaderMapResourceCode : : FShaderEntry & Shader : Resource . ShaderEntries )
2020-02-06 13:13:41 -05:00
{
2020-04-21 13:46:17 -04:00
DEC_DWORD_STAT_BY_FName ( GetMemoryStatType ( Shader . Frequency ) . GetName ( ) , Shader . Code . Num ( ) ) ;
2020-02-06 13:13:41 -05:00
}
# endif // STATS
}
Fix memory leak of shader code during shader compilation (during cooks, deployments, or editor). Depending on settings and project this can save a considerable amount. For example, this saved ~39GB on the test project I was cooking.
- FShaderMapResourceCode derives from FThreadSafeRefCountedObject which has a member that keeps track of the number of references to the object. We were doing deep copies using the Copy Constructor of FShaderMapResourceCode which resulted in a member wise copy of the num refs. This is not the behavior you'd expect.
- The offending line of code that caused the memory leak was in FShaderMapBase::AssignCopy:
- Code = new FShaderMapResourceCode(*Source.Code);
- Like FRefCountBase I've deleted the copy constructor and assignment operator for FRefCountedObject and FThreadSafeRefCountedObject to catch issues at compile time where we are copying across NumRefs incorrectly.
- To fix the issue I've manually created copy constructors for FGPUProfilerEventNodeStats and FShaderMapResourceCode.
#rb Devin.Doucette, Dave.Jones, Arciel.Rekman, Ben.Ingram
#review-16115628 @Devin.Doucette, @Dave.Jones, @Arciel.Rekman, @Ben.Ingram
#jira UE-108598, UE-113736
[CL 16116577 by Jason Nadro in ue5-main branch]
2021-04-26 14:03:07 -04:00
FShaderMapResourceCode : : FShaderMapResourceCode ( const FShaderMapResourceCode & Other )
{
ResourceHash = Other . ResourceHash ;
ShaderHashes = Other . ShaderHashes ;
ShaderEntries = Other . ShaderEntries ;
# if WITH_EDITORONLY_DATA
PlatformDebugData = Other . PlatformDebugData ;
PlatformDebugDataHashes = Other . PlatformDebugDataHashes ;
# endif // WITH_EDITORONLY_DATA
}
2020-02-06 13:13:41 -05:00
FShaderMapResourceCode : : ~ FShaderMapResourceCode ( )
{
RemoveResourceStats ( * this ) ;
}
void FShaderMapResourceCode : : Finalize ( )
{
FSHA1 Hasher ;
Hasher . Update ( ( uint8 * ) ShaderHashes . GetData ( ) , ShaderHashes . Num ( ) * sizeof ( FSHAHash ) ) ;
Hasher . Final ( ) ;
Hasher . GetHash ( ResourceHash . Hash ) ;
ApplyResourceStats ( * this ) ;
2022-06-14 17:03:34 -04:00
# if WITH_EDITORONLY_DATA
LogShaderCompilerWarnings ( ) ;
# endif
2020-02-06 13:13:41 -05:00
}
2020-04-21 13:46:17 -04:00
uint32 FShaderMapResourceCode : : GetSizeBytes ( ) const
{
2022-01-07 10:39:08 -05:00
uint64 Size = sizeof ( * this ) + ShaderHashes . GetAllocatedSize ( ) + ShaderEntries . GetAllocatedSize ( ) ;
2020-04-21 13:46:17 -04:00
for ( const FShaderEntry & Entry : ShaderEntries )
{
Size + = Entry . Code . GetAllocatedSize ( ) ;
}
2022-01-07 10:39:08 -05:00
check ( Size < = TNumericLimits < uint32 > : : Max ( ) ) ;
return static_cast < uint32 > ( Size ) ;
2020-04-21 13:46:17 -04:00
}
int32 FShaderMapResourceCode : : FindShaderIndex ( const FSHAHash & InHash ) const
{
return Algo : : BinarySearch ( ShaderHashes , InHash ) ;
}
2020-06-23 18:40:00 -04:00
void FShaderMapResourceCode : : AddShaderCompilerOutput ( const FShaderCompilerOutput & Output )
{
# if WITH_EDITORONLY_DATA
AddPlatformDebugData ( Output . PlatformDebugData ) ;
2022-06-14 17:03:34 -04:00
for ( const FShaderCompilerError & Error : Output . Errors )
{
CompilerWarnings . Add ( Error . GetErrorString ( ) ) ;
}
2020-06-23 18:40:00 -04:00
# endif
2021-03-11 21:19:35 -04:00
AddShaderCode ( Output . Target . GetFrequency ( ) , Output . OutputHash , Output . ShaderCode ) ;
2020-06-23 18:40:00 -04:00
}
2021-03-11 21:19:35 -04:00
void FShaderMapResourceCode : : AddShaderCode ( EShaderFrequency InFrequency , const FSHAHash & InHash , const FShaderCode & InCode )
2020-04-21 13:46:17 -04:00
{
2021-01-25 12:06:02 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FShaderMapResourceCode : : AddShaderCode ) ;
2020-04-21 13:46:17 -04:00
const int32 Index = Algo : : LowerBound ( ShaderHashes , InHash ) ;
if ( Index > = ShaderHashes . Num ( ) | | ShaderHashes [ Index ] ! = InHash )
{
ShaderHashes . Insert ( InHash , Index ) ;
FShaderEntry & Entry = ShaderEntries . InsertDefaulted_GetRef ( Index ) ;
Entry . Frequency = InFrequency ;
2021-03-11 21:19:35 -04:00
const TArray < uint8 > & ShaderCode = InCode . GetReadAccess ( ) ;
2020-04-21 13:46:17 -04:00
2021-03-11 21:19:35 -04:00
FName ShaderCompressionFormat = GetShaderCompressionFormat ( ) ;
if ( ShaderCompressionFormat ! = NAME_None )
2020-04-21 13:46:17 -04:00
{
2021-03-11 21:19:35 -04:00
Entry . UncompressedSize = InCode . GetUncompressedSize ( ) ;
// we trust that SCWs also obeyed by the same CVar, so we expect a compressed shader code at this point
// However, if we see an uncompressed shader, it perhaps means that SCW tried to compress it, but the result was worse than uncompressed.
// Because of that we special-case NAME_None here
if ( ShaderCompressionFormat ! = InCode . GetCompressionFormat ( ) )
{
if ( InCode . GetCompressionFormat ( ) ! = NAME_None )
{
UE_LOG ( LogShaders , Fatal , TEXT ( " Shader %s is expected to be compressed with %s, but it is compressed with %s instead. " ) ,
* InHash . ToString ( ) ,
* ShaderCompressionFormat . ToString ( ) ,
* InCode . GetCompressionFormat ( ) . ToString ( )
) ;
// unreachable
return ;
}
// assume uncompressed due to worse ratio than the compression
Entry . UncompressedSize = ShaderCode . Num ( ) ;
2022-01-26 12:32:20 -05:00
UE_LOG ( LogShaders , Verbose , TEXT ( " Shader %s is expected to be compressed with %s, but it arrived uncompressed (size=%d). Assuming compressing made it longer and storing uncompressed. " ) ,
2021-03-11 21:19:35 -04:00
* InHash . ToString ( ) ,
2022-01-26 12:32:20 -05:00
* ShaderCompressionFormat . ToString ( ) ,
ShaderCode . Num ( )
2021-03-11 21:19:35 -04:00
) ;
}
2022-01-04 00:58:30 -05:00
else if ( ShaderCompressionFormat = = NAME_Oodle )
{
// check if Oodle-specific settings match
FOodleDataCompression : : ECompressor OodleCompressor ;
FOodleDataCompression : : ECompressionLevel OodleLevel ;
GetShaderCompressionOodleSettings ( OodleCompressor , OodleLevel ) ;
if ( InCode . GetOodleCompressor ( ) ! = OodleCompressor | | InCode . GetOodleLevel ( ) ! = OodleLevel )
{
UE_LOG ( LogShaders , Fatal , TEXT ( " Shader %s is expected to be compressed with Oodle compressor %d level %d, but it is compressed with compressor %d level %d instead. " ) ,
* InHash . ToString ( ) ,
static_cast < int32 > ( OodleCompressor ) ,
static_cast < int32 > ( OodleLevel ) ,
static_cast < int32 > ( InCode . GetOodleCompressor ( ) ) ,
static_cast < int32 > ( InCode . GetOodleLevel ( ) )
) ;
// unreachable
return ;
}
}
2020-04-21 13:46:17 -04:00
}
else
{
2021-03-11 21:19:35 -04:00
Entry . UncompressedSize = ShaderCode . Num ( ) ;
2020-04-21 13:46:17 -04:00
}
2021-03-11 21:19:35 -04:00
Entry . Code = ShaderCode ;
2020-04-21 13:46:17 -04:00
}
}
# if WITH_EDITORONLY_DATA
void FShaderMapResourceCode : : AddPlatformDebugData ( TConstArrayView < uint8 > InPlatformDebugData )
{
if ( InPlatformDebugData . Num ( ) = = 0 )
{
return ;
}
FSHAHash Hash ;
{
FSHA1 Hasher ;
Hasher . Update ( InPlatformDebugData . GetData ( ) , InPlatformDebugData . Num ( ) ) ;
Hasher . Final ( ) ;
Hasher . GetHash ( Hash . Hash ) ;
}
const int32 Index = Algo : : LowerBound ( PlatformDebugDataHashes , Hash ) ;
if ( Index > = PlatformDebugDataHashes . Num ( ) | | PlatformDebugDataHashes [ Index ] ! = Hash )
{
PlatformDebugDataHashes . Insert ( Hash , Index ) ;
PlatformDebugData . EmplaceAt ( Index , InPlatformDebugData . GetData ( ) , InPlatformDebugData . Num ( ) ) ;
}
}
2022-06-14 17:03:34 -04:00
void FShaderMapResourceCode : : LogShaderCompilerWarnings ( )
{
if ( CompilerWarnings . Num ( ) > 0 & & GShaderCompilerEmitWarningsOnLoad ! = 0 )
{
// Emit all the compiler warnings seen whilst serializing/loading this shader to the log.
// Since successfully compiled shaders are stored in the DDC, we'll get the compiler warnings
// even if we didn't compile the shader this run.
for ( const FString & CompilerWarning : CompilerWarnings )
{
UE_LOG ( LogShaderWarnings , Warning , TEXT ( " %s " ) , * CompilerWarning ) ;
}
}
}
2020-04-21 13:46:17 -04:00
# endif // WITH_EDITORONLY_DATA
void FShaderMapResourceCode : : ToString ( FStringBuilderBase & OutString ) const
{
OutString . Appendf ( TEXT ( " Shaders: Num=%d \n " ) , ShaderHashes . Num ( ) ) ;
for ( int32 i = 0 ; i < ShaderHashes . Num ( ) ; + + i )
{
const FShaderEntry & Entry = ShaderEntries [ i ] ;
OutString . Appendf ( TEXT ( " [%d]: { Hash: %s, Freq: %s, Size: %d, UncompressedSize: %d } \n " ) ,
i , * ShaderHashes [ i ] . ToString ( ) , GetShaderFrequencyString ( Entry . Frequency ) , Entry . Code . Num ( ) , Entry . UncompressedSize ) ;
}
}
2020-03-02 21:52:09 -05:00
void FShaderMapResourceCode : : Serialize ( FArchive & Ar , bool bLoadedByCookedMaterial )
2020-02-06 13:13:41 -05:00
{
Ar < < ResourceHash ;
Ar < < ShaderHashes ;
Ar < < ShaderEntries ;
check ( ShaderEntries . Num ( ) = = ShaderHashes . Num ( ) ) ;
2020-03-02 21:52:09 -05:00
# if WITH_EDITORONLY_DATA
2022-06-14 17:03:34 -04:00
const bool bSerializeEditorOnlyData = ! bLoadedByCookedMaterial & & ( ! Ar . IsCooking ( ) | | Ar . CookingTarget ( ) - > HasEditorOnlyData ( ) ) ;
if ( bSerializeEditorOnlyData )
2020-03-02 21:52:09 -05:00
{
2020-04-21 13:46:17 -04:00
Ar < < PlatformDebugDataHashes ;
2020-03-02 21:52:09 -05:00
Ar < < PlatformDebugData ;
2022-06-14 17:03:34 -04:00
Ar < < CompilerWarnings ;
2020-03-02 21:52:09 -05:00
}
# endif // WITH_EDITORONLY_DATA
2020-02-06 13:13:41 -05:00
ApplyResourceStats ( * this ) ;
2022-06-14 17:03:34 -04:00
# if WITH_EDITORONLY_DATA
if ( Ar . IsLoading ( ) )
{
LogShaderCompilerWarnings ( ) ;
}
# endif
2020-02-06 13:13:41 -05:00
}
2020-03-02 21:52:09 -05:00
# if WITH_EDITORONLY_DATA
2021-08-16 23:18:23 -04:00
void FShaderMapResourceCode : : NotifyShadersCompiled ( FName FormatName )
2020-03-02 21:52:09 -05:00
{
# if WITH_ENGINE
// Notify the platform shader format that this particular shader is being used in the cook.
// We discard this data in cooked builds unless Ar.CookingTarget()->HasEditorOnlyData() is true.
2020-04-21 13:46:17 -04:00
if ( PlatformDebugData . Num ( ) )
2020-03-02 21:52:09 -05:00
{
2021-08-16 23:18:23 -04:00
if ( const IShaderFormat * ShaderFormat = GetTargetPlatformManagerRef ( ) . FindShaderFormat ( FormatName ) )
2020-03-02 21:52:09 -05:00
{
2021-08-16 23:18:23 -04:00
for ( const TArray < uint8 > & Entry : PlatformDebugData )
2020-03-02 21:52:09 -05:00
{
2021-08-16 23:18:23 -04:00
ShaderFormat - > NotifyShaderCompiled ( Entry , FormatName ) ;
2020-03-02 21:52:09 -05:00
}
}
}
# endif // WITH_ENGINE
}
2021-08-16 23:18:23 -04:00
void FShaderMapResourceCode : : NotifyShadersCooked ( const ITargetPlatform * TargetPlatform )
{
# if WITH_ENGINE
TArray < FName > ShaderFormatNames ;
TargetPlatform - > GetAllTargetedShaderFormats ( ShaderFormatNames ) ;
for ( FName FormatName : ShaderFormatNames )
{
NotifyShadersCompiled ( FormatName ) ;
}
# endif
}
2020-03-02 21:52:09 -05:00
# endif // WITH_EDITORONLY_DATA
2020-02-06 13:13:41 -05:00
FShaderMapResource : : FShaderMapResource ( EShaderPlatform InPlatform , int32 NumShaders )
2020-06-23 18:40:00 -04:00
: NumRHIShaders ( NumShaders )
, Platform ( InPlatform )
2020-02-06 13:13:41 -05:00
, NumRefs ( 0 )
{
2020-06-23 18:40:00 -04:00
RHIShaders = MakeUnique < std : : atomic < FRHIShader * > [ ] > ( NumRHIShaders ) ; // this MakeUnique() zero-initializes the array
2020-02-06 13:13:41 -05:00
# if RHI_RAYTRACING
2021-11-23 10:25:31 -05:00
if ( GRHISupportsRayTracing & & GRHISupportsRayTracingShaders )
2020-06-23 18:40:00 -04:00
{
2022-05-19 07:03:30 -04:00
RayTracingLibraryIndices . AddUninitialized ( NumShaders ) ;
FMemory : : Memset ( RayTracingLibraryIndices . GetData ( ) , 0xff , NumShaders * RayTracingLibraryIndices . GetTypeSize ( ) ) ;
2020-06-23 18:40:00 -04:00
}
2020-02-06 13:13:41 -05:00
# endif // RHI_RAYTRACING
}
FShaderMapResource : : ~ FShaderMapResource ( )
{
2020-06-23 18:40:00 -04:00
ReleaseShaders ( ) ;
2022-03-30 20:57:33 -04:00
check ( NumRefs . load ( std : : memory_order_relaxed ) = = 0 ) ;
2020-02-06 13:13:41 -05:00
}
void FShaderMapResource : : AddRef ( )
{
2022-03-30 20:57:33 -04:00
NumRefs . fetch_add ( 1 , std : : memory_order_relaxed ) ;
2020-02-06 13:13:41 -05:00
}
void FShaderMapResource : : Release ( )
{
2022-03-30 20:57:33 -04:00
check ( NumRefs . load ( std : : memory_order_relaxed ) > 0 ) ;
if ( NumRefs . fetch_sub ( 1 , std : : memory_order_release ) - 1 = = 0 & & TryRelease ( ) )
2020-02-06 13:13:41 -05:00
{
2022-03-30 20:57:33 -04:00
//check https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html for explanation
std : : atomic_thread_fence ( std : : memory_order_acquire ) ;
2020-02-06 13:13:41 -05:00
// Send a release message to the rendering thread when the shader loses its last reference.
BeginReleaseResource ( this ) ;
BeginCleanup ( this ) ;
DEC_DWORD_STAT_BY ( STAT_Shaders_ShaderResourceMemory , GetSizeBytes ( ) ) ;
}
}
2020-06-23 18:40:00 -04:00
void FShaderMapResource : : ReleaseShaders ( )
{
if ( RHIShaders )
{
for ( int32 Idx = 0 ; Idx < NumRHIShaders ; + + Idx )
{
if ( FRHIShader * Shader = RHIShaders [ Idx ] . load ( std : : memory_order_acquire ) )
{
Shader - > Release ( ) ;
2021-09-27 19:54:25 -04:00
DEC_DWORD_STAT ( STAT_Shaders_NumShadersUsedForRendering ) ;
2020-06-23 18:40:00 -04:00
}
}
RHIShaders = nullptr ;
NumRHIShaders = 0 ;
}
}
2020-02-06 13:13:41 -05:00
void FShaderMapResource : : ReleaseRHI ( )
{
# if RHI_RAYTRACING
2022-05-19 07:03:30 -04:00
if ( GRHISupportsRayTracing & & GRHISupportsRayTracingShaders )
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
check ( NumRHIShaders = = RayTracingLibraryIndices . Num ( ) ) ;
for ( int32 Idx = 0 ; Idx < NumRHIShaders ; + + Idx )
{
if ( FRHIShader * Shader = RHIShaders [ Idx ] . load ( std : : memory_order_acquire ) )
{
int32 IndexInLibrary = RayTracingLibraryIndices [ Idx ] ;
switch ( Shader - > GetFrequency ( ) )
{
case SF_RayHitGroup :
GlobalRayTracingHitGroupLibrary . RemoveShader ( IndexInLibrary ) ;
break ;
case SF_RayCallable :
GlobalRayTracingCallableShaderLibrary . RemoveShader ( IndexInLibrary ) ;
break ;
case SF_RayMiss :
GlobalRayTracingMissShaderLibrary . RemoveShader ( IndexInLibrary ) ;
break ;
default :
break ;
}
}
}
2020-02-06 13:13:41 -05:00
}
2022-05-19 07:03:30 -04:00
RayTracingLibraryIndices . Empty ( ) ;
2020-02-06 13:13:41 -05:00
# endif // RHI_RAYTRACING
2020-06-23 18:40:00 -04:00
ReleaseShaders ( ) ;
2020-02-06 13:13:41 -05:00
}
void FShaderMapResource : : BeginCreateAllShaders ( )
{
FShaderMapResource * Resource = this ;
ENQUEUE_RENDER_COMMAND ( InitCommand ) (
[ Resource ] ( FRHICommandListImmediate & RHICmdList )
{
for ( int32 ShaderIndex = 0 ; ShaderIndex < Resource - > GetNumShaders ( ) ; + + ShaderIndex )
{
Resource - > GetShader ( ShaderIndex ) ;
}
} ) ;
}
2020-06-23 18:40:00 -04:00
FRHIShader * FShaderMapResource : : CreateShader ( int32 ShaderIndex )
2020-02-06 13:13:41 -05:00
{
check ( IsInParallelRenderingThread ( ) ) ;
2020-06-23 18:40:00 -04:00
check ( ! RHIShaders [ ShaderIndex ] . load ( std : : memory_order_acquire ) ) ;
2020-02-06 13:13:41 -05:00
TRefCountPtr < FRHIShader > RHIShader = CreateRHIShader ( ShaderIndex ) ;
# if RHI_RAYTRACING
2022-05-19 07:03:30 -04:00
if ( GRHISupportsRayTracing & & GRHISupportsRayTracingShaders & & RHIShader . IsValid ( ) )
2020-02-06 13:13:41 -05:00
{
2022-05-19 07:03:30 -04:00
switch ( RHIShader - > GetFrequency ( ) )
{
case SF_RayHitGroup :
RayTracingLibraryIndices [ ShaderIndex ] = GlobalRayTracingHitGroupLibrary . AddShader ( static_cast < FRHIRayTracingShader * > ( RHIShader . GetReference ( ) ) ) ;
break ;
case SF_RayCallable :
RayTracingLibraryIndices [ ShaderIndex ] = GlobalRayTracingCallableShaderLibrary . AddShader ( static_cast < FRHIRayTracingShader * > ( RHIShader . GetReference ( ) ) ) ;
break ;
case SF_RayMiss :
RayTracingLibraryIndices [ ShaderIndex ] = GlobalRayTracingMissShaderLibrary . AddShader ( static_cast < FRHIRayTracingShader * > ( RHIShader . GetReference ( ) ) ) ;
break ;
default :
break ;
}
2020-02-06 13:13:41 -05:00
}
# endif // RHI_RAYTRACING
2020-06-23 18:40:00 -04:00
// keep the reference alive (the caller will release)
if ( RHIShader . IsValid ( ) )
{
RHIShader - > AddRef ( ) ;
}
return RHIShader . GetReference ( ) ;
2020-02-06 13:13:41 -05:00
}
TRefCountPtr < FRHIShader > FShaderMapResource_InlineCode : : CreateRHIShader ( int32 ShaderIndex )
{
Fix memory leak of shader code during shader compilation (during cooks, deployments, or editor). Depending on settings and project this can save a considerable amount. For example, this saved ~39GB on the test project I was cooking.
- FShaderMapResourceCode derives from FThreadSafeRefCountedObject which has a member that keeps track of the number of references to the object. We were doing deep copies using the Copy Constructor of FShaderMapResourceCode which resulted in a member wise copy of the num refs. This is not the behavior you'd expect.
- The offending line of code that caused the memory leak was in FShaderMapBase::AssignCopy:
- Code = new FShaderMapResourceCode(*Source.Code);
- Like FRefCountBase I've deleted the copy constructor and assignment operator for FRefCountedObject and FThreadSafeRefCountedObject to catch issues at compile time where we are copying across NumRefs incorrectly.
- To fix the issue I've manually created copy constructors for FGPUProfilerEventNodeStats and FShaderMapResourceCode.
#rb Devin.Doucette, Dave.Jones, Arciel.Rekman, Ben.Ingram
#review-16115628 @Devin.Doucette, @Dave.Jones, @Arciel.Rekman, @Ben.Ingram
#jira UE-108598, UE-113736
[CL 16116577 by Jason Nadro in ue5-main branch]
2021-04-26 14:03:07 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FShaderMapResource_InlineCode : : CreateRHIShader ) ;
2021-09-27 19:54:25 -04:00
# if STATS
double TimeFunctionEntered = FPlatformTime : : Seconds ( ) ;
ON_SCOPE_EXIT
{
if ( IsInRenderingThread ( ) )
{
double ShaderCreationTime = FPlatformTime : : Seconds ( ) - TimeFunctionEntered ;
INC_FLOAT_STAT_BY ( STAT_Shaders_TotalRTShaderInitForRenderingTime , ShaderCreationTime ) ;
}
} ;
# endif
Fix memory leak of shader code during shader compilation (during cooks, deployments, or editor). Depending on settings and project this can save a considerable amount. For example, this saved ~39GB on the test project I was cooking.
- FShaderMapResourceCode derives from FThreadSafeRefCountedObject which has a member that keeps track of the number of references to the object. We were doing deep copies using the Copy Constructor of FShaderMapResourceCode which resulted in a member wise copy of the num refs. This is not the behavior you'd expect.
- The offending line of code that caused the memory leak was in FShaderMapBase::AssignCopy:
- Code = new FShaderMapResourceCode(*Source.Code);
- Like FRefCountBase I've deleted the copy constructor and assignment operator for FRefCountedObject and FThreadSafeRefCountedObject to catch issues at compile time where we are copying across NumRefs incorrectly.
- To fix the issue I've manually created copy constructors for FGPUProfilerEventNodeStats and FShaderMapResourceCode.
#rb Devin.Doucette, Dave.Jones, Arciel.Rekman, Ben.Ingram
#review-16115628 @Devin.Doucette, @Dave.Jones, @Arciel.Rekman, @Ben.Ingram
#jira UE-108598, UE-113736
[CL 16116577 by Jason Nadro in ue5-main branch]
2021-04-26 14:03:07 -04:00
2020-02-06 13:13:41 -05:00
// we can't have this called on the wrong platform's shaders
if ( ! ArePlatformsCompatible ( GMaxRHIShaderPlatform , GetPlatform ( ) ) )
{
if ( FPlatformProperties : : RequiresCookedData ( ) )
{
UE_LOG ( LogShaders , Fatal , TEXT ( " FShaderMapResource_InlineCode::InitRHI got platform %s but it is not compatible with %s " ) ,
* LegacyShaderPlatformToShaderFormat ( GetPlatform ( ) ) . ToString ( ) , * LegacyShaderPlatformToShaderFormat ( GMaxRHIShaderPlatform ) . ToString ( ) ) ;
}
return TRefCountPtr < FRHIShader > ( ) ;
}
FMemStackBase & MemStack = FMemStack : : Get ( ) ;
const FShaderMapResourceCode : : FShaderEntry & ShaderEntry = Code - > ShaderEntries [ ShaderIndex ] ;
2020-04-21 13:46:17 -04:00
const uint8 * ShaderCode = ShaderEntry . Code . GetData ( ) ;
2020-02-06 13:13:41 -05:00
FMemMark Mark ( MemStack ) ;
2020-04-21 13:46:17 -04:00
if ( ShaderEntry . Code . Num ( ) ! = ShaderEntry . UncompressedSize )
2020-02-06 13:13:41 -05:00
{
void * UncompressedCode = MemStack . Alloc ( ShaderEntry . UncompressedSize , 16 ) ;
2022-01-04 00:58:30 -05:00
bool bSucceed = FCompression : : UncompressMemory ( GetShaderCompressionFormat ( ) , UncompressedCode , ShaderEntry . UncompressedSize , ShaderCode , ShaderEntry . Code . Num ( ) ) ;
2020-02-06 13:13:41 -05:00
check ( bSucceed ) ;
ShaderCode = ( uint8 * ) UncompressedCode ;
}
const auto ShaderCodeView = MakeArrayView ( ShaderCode , ShaderEntry . UncompressedSize ) ;
const FSHAHash & ShaderHash = Code - > ShaderHashes [ ShaderIndex ] ;
const EShaderFrequency Frequency = ShaderEntry . Frequency ;
TRefCountPtr < FRHIShader > RHIShader ;
switch ( Frequency )
{
case SF_Vertex : RHIShader = RHICreateVertexShader ( ShaderCodeView , ShaderHash ) ; break ;
2021-03-18 18:42:49 -04:00
case SF_Mesh : RHIShader = RHICreateMeshShader ( ShaderCodeView , ShaderHash ) ; break ;
case SF_Amplification : RHIShader = RHICreateAmplificationShader ( ShaderCodeView , ShaderHash ) ; break ;
2020-02-06 13:13:41 -05:00
case SF_Pixel : RHIShader = RHICreatePixelShader ( ShaderCodeView , ShaderHash ) ; break ;
case SF_Geometry : RHIShader = RHICreateGeometryShader ( ShaderCodeView , ShaderHash ) ; break ;
case SF_Compute : RHIShader = RHICreateComputeShader ( ShaderCodeView , ShaderHash ) ; break ;
case SF_RayGen : case SF_RayMiss : case SF_RayHitGroup : case SF_RayCallable :
2020-09-01 14:07:48 -04:00
# if RHI_RAYTRACING
2021-11-23 10:25:31 -05:00
if ( GRHISupportsRayTracing & & GRHISupportsRayTracingShaders )
2020-02-06 13:13:41 -05:00
{
RHIShader = RHICreateRayTracingShader ( ShaderCodeView , ShaderHash , Frequency ) ;
}
# endif // RHI_RAYTRACING
2020-09-01 14:07:48 -04:00
break ;
2020-02-06 13:13:41 -05:00
default :
checkNoEntry ( ) ;
break ;
}
if ( RHIShader )
{
2021-09-27 19:54:25 -04:00
INC_DWORD_STAT ( STAT_Shaders_NumShadersUsedForRendering ) ;
2020-02-06 13:13:41 -05:00
RHIShader - > SetHash ( ShaderHash ) ;
}
return RHIShader ;
}