// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= Shader.cpp: Shader implementation. =============================================================================*/ #include "Shader.h" #include "Misc/CoreMisc.h" #include "Misc/StringBuilder.h" #include "VertexFactory.h" #include "ProfilingDebugging/DiagnosticTable.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Interfaces/IShaderFormat.h" #include "Internationalization/Regex.h" #include "Serialization/ShaderKeyGenerator.h" #include "ShaderCodeLibrary.h" #include "ShaderCore.h" #include "ShaderCompilerCore.h" #include "RenderUtils.h" #include "StereoRenderUtils.h" #include "Misc/ConfigCacheIni.h" #include "Misc/ScopeLock.h" #include "UObject/RenderingObjectVersion.h" #include "ProfilingDebugging/LoadTimeTracker.h" #include "Misc/LargeWorldRenderPosition.h" #include "DataDrivenShaderPlatformInfo.h" #include "ShaderPlatformCachedIniValue.h" #include "ColorManagement/ColorSpace.h" #if WITH_EDITORONLY_DATA #include "Interfaces/IShaderFormat.h" #endif #if WITH_EDITOR #include "Serialization/CompactBinary.h" #include "Serialization/CompactBinaryWriter.h" #endif #if RHI_RAYTRACING #include "RayTracingPayloadType.h" #endif DEFINE_LOG_CATEGORY(LogShaders); IMPLEMENT_TYPE_LAYOUT(FShader); IMPLEMENT_TYPE_LAYOUT(FShaderParameterBindings); IMPLEMENT_TYPE_LAYOUT(FShaderMapContent); IMPLEMENT_TYPE_LAYOUT(FShaderTypeDependency); IMPLEMENT_TYPE_LAYOUT(FShaderPipeline); IMPLEMENT_TYPE_LAYOUT(FShaderUniformBufferParameterInfo); IMPLEMENT_TYPE_LAYOUT(FShaderResourceParameterInfo); IMPLEMENT_TYPE_LAYOUT(FShaderLooseParameterInfo); IMPLEMENT_TYPE_LAYOUT(FShaderLooseParameterBufferInfo); IMPLEMENT_TYPE_LAYOUT(FShaderParameterMapInfo); void Freeze::IntrinsicToString(const TIndexedPtr& Object, const FTypeLayoutDesc& TypeDesc, const FPlatformTypeLayoutParameters& LayoutParams, FMemoryToStringContext& OutContext) { const FShaderType* Type = Object.Get(OutContext.TryGetPrevPointerTable()); if (Type) { OutContext.String->Appendf(TEXT("%s\n"), Type->GetName()); } else { OutContext.AppendNullptr(); } } void Freeze::IntrinsicToString(const TIndexedPtr& Object, const FTypeLayoutDesc& TypeDesc, const FPlatformTypeLayoutParameters& LayoutParams, FMemoryToStringContext& OutContext) { const FVertexFactoryType* Type = Object.Get(OutContext.TryGetPrevPointerTable()); if (Type) { OutContext.String->Appendf(TEXT("%s\n"), Type->GetName()); } else { OutContext.AppendNullptr(); } } IMPLEMENT_EXPORTED_INTRINSIC_TYPE_LAYOUT(TIndexedPtr); IMPLEMENT_EXPORTED_INTRINSIC_TYPE_LAYOUT(TIndexedPtr); static TAutoConsoleVariable CVarUsePipelines( TEXT("r.ShaderPipelines"), 1, TEXT("Enable using Shader pipelines.")); static TAutoConsoleVariable CVarRemoveUnusedInterpolators( TEXT("r.Shaders.RemoveUnusedInterpolators"), 0, TEXT("Enables removing unused interpolators mode when compiling shader pipelines.\n") TEXT(" 0: Disable (default)\n") TEXT(" 1: Enable removing unused"), ECVF_ReadOnly ); static TAutoConsoleVariable CVarSkipShaderCompression( TEXT("r.Shaders.SkipCompression"), 0, TEXT("Skips shader compression after compiling. Shader compression time can be quite significant when using debug shaders. This CVar is only valid in non-shipping/test builds."), ECVF_ReadOnly | ECVF_Cheat ); static TAutoConsoleVariable CVarAllowCompilingThroughWorkers( TEXT("r.Shaders.AllowCompilingThroughWorkers"), 1, TEXT("Allows shader compilation through external ShaderCompileWorker processes.\n") TEXT("1 - (Default) Allows external shader compiler workers\n") TEXT("0 - Disallows external shader compiler workers. Will run shader compilation in proc of UE process."), ECVF_ReadOnly ); static TAutoConsoleVariable CVarShadersForceDXC( TEXT("r.Shaders.ForceDXC"), 1, TEXT("Forces DirectX Shader Compiler (DXC) to be used for all shaders instead of HLSLcc if supported.\n") TEXT(" 1: Force new compiler for all shaders (default)\n") TEXT(" 0: Disable"), ECVF_ReadOnly); static TLinkedList* GShaderTypeList = nullptr; static TLinkedList* GShaderPipelineList = nullptr; static FSHAHash ShaderSourceDefaultHash; //will only be read (never written) for the cooking case /** * Find the shader pipeline type with the given name. * @return NULL if no type matched. */ inline const FShaderPipelineType* FindShaderPipelineType(FName TypeName) { for (TLinkedList::TIterator ShaderPipelineTypeIt(FShaderPipelineType::GetTypeList()); ShaderPipelineTypeIt; ShaderPipelineTypeIt.Next()) { if (ShaderPipelineTypeIt->GetFName() == TypeName) { return *ShaderPipelineTypeIt; } } return nullptr; } /** * Serializes a reference to a shader pipeline type. */ FArchive& operator<<(FArchive& Ar, const FShaderPipelineType*& TypeRef) { if (Ar.IsSaving()) { FName TypeName = TypeRef ? FName(TypeRef->Name) : NAME_None; Ar << TypeName; } else if (Ar.IsLoading()) { FName TypeName = NAME_None; Ar << TypeName; TypeRef = FindShaderPipelineType(TypeName); } return Ar; } void FShaderParameterMap::VerifyBindingsAreComplete(const TCHAR* ShaderTypeName, FShaderTarget Target, const FVertexFactoryType* InVertexFactoryType) const { #if WITH_EDITORONLY_DATA // Only people working on shaders (and therefore have LogShaders unsuppressed) will want to see these errors if (UE_LOG_ACTIVE(LogShaders, Warning)) { const TCHAR* VertexFactoryName = InVertexFactoryType ? InVertexFactoryType->GetName() : TEXT("?"); bool bBindingsComplete = true; FString UnBoundParameters = TEXT(""); for (TMap::TConstIterator ParameterIt(ParameterMap);ParameterIt;++ParameterIt) { const FString& ParamName = ParameterIt.Key(); const FParameterAllocation& ParamValue = ParameterIt.Value(); if(!ParamValue.bBound) { // Only valid parameters should be in the shader map checkSlow(ParamValue.Size > 0); bBindingsComplete = bBindingsComplete && ParamValue.bBound; UnBoundParameters += FString(TEXT(" Parameter ")) + ParamName + TEXT(" not bound!\n"); } } if (!bBindingsComplete) { FString ErrorMessage = FString(TEXT("Found unbound parameters being used in shadertype ")) + ShaderTypeName + TEXT(" (VertexFactory: ") + VertexFactoryName + TEXT(")\n") + UnBoundParameters; // We use a non-Slate message box to avoid problem where we haven't compiled the shaders for Slate. FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *ErrorMessage, TEXT("Error")); } } #endif // WITH_EDITORONLY_DATA } void FShaderParameterMap::UpdateHash(FSHA1& HashState) const { for(TMap::TConstIterator ParameterIt(ParameterMap);ParameterIt;++ParameterIt) { const FString& ParamName = ParameterIt.Key(); const FParameterAllocation& ParamValue = ParameterIt.Value(); HashState.Update((const uint8*)*ParamName, ParamName.Len() * sizeof(TCHAR)); HashState.Update((const uint8*)&ParamValue.BufferIndex, sizeof(ParamValue.BufferIndex)); HashState.Update((const uint8*)&ParamValue.BaseIndex, sizeof(ParamValue.BaseIndex)); HashState.Update((const uint8*)&ParamValue.Size, sizeof(ParamValue.Size)); } } static TArray& GetSortedShaderTypes(FShaderType::EShaderTypeForDynamicCast Type) { static TArray* SortedTypesArray = new TArray[(uint32)FShaderType::EShaderTypeForDynamicCast::NumShaderTypes]; return SortedTypesArray[(uint32)Type]; } namespace { uint32 RegisteredRayTracingPayloads = 0; uint32 RayTracingPayloadSizes[32] = {}; TRaytracingPayloadSizeFunction RayTracingPayloadSizeFunctions[32] = {}; bool IsRayTracingPayloadRegistered(ERayTracingPayloadType PayloadType) { // make sure all bits are on in the registered bitmask return (static_cast(PayloadType) & RegisteredRayTracingPayloads) == static_cast(PayloadType); } } // anonymous namespace FShaderType::FShaderType( EShaderTypeForDynamicCast InShaderTypeForDynamicCast, FTypeLayoutDesc& InTypeLayout, const TCHAR* InName, const TCHAR* InSourceFilename, const TCHAR* InFunctionName, uint32 InFrequency, int32 InTotalPermutationCount, ConstructSerializedType InConstructSerializedRef, ConstructCompiledType InConstructCompiledRef, ShouldCompilePermutationType InShouldCompilePermutationRef, ShouldPrecachePermutationType InShouldPrecachePermutationRef, GetRayTracingPayloadTypeType InGetRayTracingPayloadTypeRef, GetShaderBindingLayoutType InGetShaderBindingLayoutTypeRef, #if WITH_EDITOR ModifyCompilationEnvironmentType InModifyCompilationEnvironmentRef, ValidateCompiledResultType InValidateCompiledResultRef, GetOverrideJobPriorityType InGetOverrideJobPriorityRef, #endif // WITH_EDITOR uint32 InTypeSize, const FShaderParametersMetadata* InRootParametersMetadata #if WITH_EDITOR , GetPermutationIdStringType InGetPermutationIdStringRef #endif ) : ShaderTypeForDynamicCast(InShaderTypeForDynamicCast) , TypeLayout(&InTypeLayout) , Name(InName) , TypeName(InName) , HashedName(TypeName) , HashedSourceFilename(InSourceFilename) , SourceFilename(InSourceFilename) , FunctionName(InFunctionName) , Frequency(InFrequency) , TypeSize(InTypeSize) , TotalPermutationCount(InTotalPermutationCount) , ConstructSerializedRef(InConstructSerializedRef) , ConstructCompiledRef(InConstructCompiledRef) , ShouldCompilePermutationRef(InShouldCompilePermutationRef) , ShouldPrecachePermutationRef(InShouldPrecachePermutationRef) , GetRayTracingPayloadTypeRef(InGetRayTracingPayloadTypeRef) , GetShaderBindingLayoutTypeRef(InGetShaderBindingLayoutTypeRef) #if WITH_EDITOR , ModifyCompilationEnvironmentRef(InModifyCompilationEnvironmentRef) , ValidateCompiledResultRef(InValidateCompiledResultRef) , GetOverrideJobPriorityRef(InGetOverrideJobPriorityRef) , GetPermutationIdStringRef(InGetPermutationIdStringRef) #endif // WITH_EDITOR , RootParametersMetadata(InRootParametersMetadata) , GlobalListLink(this) { FTypeLayoutDesc::Register(InTypeLayout); //make sure the name is shorter than the maximum serializable length check(FCString::Strlen(InName) < NAME_SIZE); // Make sure the format of the source file path is right. check(CheckVirtualShaderFilePath(InSourceFilename)); // register this shader type GlobalListLink.LinkHead(GetTypeList()); GetNameToTypeMap().Add(HashedName, this); TArray& SortedTypes = GetSortedShaderTypes(InShaderTypeForDynamicCast); const int32 SortedIndex = Algo::LowerBoundBy(SortedTypes, HashedName, [](const FShaderType* InType) { return InType->GetHashedName(); }); SortedTypes.Insert(this, SortedIndex); } FShaderType::~FShaderType() { GlobalListLink.Unlink(); GetNameToTypeMap().Remove(HashedName); TArray& SortedTypes = GetSortedShaderTypes(ShaderTypeForDynamicCast); const int32 SortedIndex = Algo::BinarySearchBy(SortedTypes, HashedName, [](const FShaderType* InType) { return InType->GetHashedName(); }); check(SortedIndex != INDEX_NONE); SortedTypes.RemoveAt(SortedIndex); } bool FShaderTypeRegistration::bShaderTypesInitialized = false; static TArray* GShaderTypeRegistrationInstances = nullptr; TArray& FShaderTypeRegistration::GetInstances() { if (GShaderTypeRegistrationInstances == nullptr) { GShaderTypeRegistrationInstances = new TArray(); } return *GShaderTypeRegistrationInstances; } void FShaderTypeRegistration::CommitAll() { for (const auto& Instance : GetInstances()) { FShaderType& ShaderType = Instance->LazyShaderTypeAccessor(); // constructs and registers type } GetInstances().Empty(); bShaderTypesInitialized = true; } bool FShaderTypeRegistration::AreShaderTypesInitialized() { return bShaderTypesInitialized; } TLinkedList*& FShaderType::GetTypeList() { return GShaderTypeList; } FShaderType* FShaderType::GetShaderTypeByName(const TCHAR* Name) { for(TLinkedList::TIterator It(GetTypeList()); It; It.Next()) { FShaderType* Type = *It; if (FPlatformString::Strcmp(Name, Type->GetName()) == 0) { return Type; } } return nullptr; } TArray FShaderType::GetShaderTypesByFilename(const TCHAR* InFilename, bool bSearchAsRegexFilter) { if (bSearchAsRegexFilter) { const FRegexPattern RegexSearch(FString(InFilename).Replace(TEXT("*"), TEXT("(.)*")), ERegexPatternFlags::CaseInsensitive); return FShaderType::GetShaderTypesByFilenameFilter( [&RegexSearch](const TCHAR* ShaderTypeFilename) -> bool { return FRegexMatcher(RegexSearch, ShaderTypeFilename).FindNext(); } ); } else { return FShaderType::GetShaderTypesByFilenameFilter( [InFilename](const TCHAR* ShaderTypeFilename) -> bool { return FPlatformString::Strcmp(InFilename, ShaderTypeFilename) == 0; } ); } } TArray FShaderType::GetShaderTypesByFilenameFilter(const TFunction& InFilenameFilter) { TArray OutShaders; for (TLinkedList::TIterator It(GetTypeList()); It; It.Next()) { const FShaderType* Type = *It; if (InFilenameFilter(Type->GetShaderFilename())) { OutShaders.Add(Type); } } return OutShaders; } TMap& FShaderType::GetNameToTypeMap() { static TMap ShaderNameToTypeMap; return ShaderNameToTypeMap; } const TArray& FShaderType::GetSortedTypes(EShaderTypeForDynamicCast Type) { return GetSortedShaderTypes(Type); } FArchive& operator<<(FArchive& Ar,FShaderType*& Ref) { if(Ar.IsSaving()) { FName ShaderTypeName = Ref ? FName(Ref->Name) : NAME_None; Ar << ShaderTypeName; } else if(Ar.IsLoading()) { FName ShaderTypeName = NAME_None; Ar << ShaderTypeName; Ref = NULL; if(ShaderTypeName != NAME_None) { // look for the shader type in the global name to type map FShaderType** ShaderType = FShaderType::GetNameToTypeMap().Find(ShaderTypeName); if (ShaderType) { // if we found it, use it Ref = *ShaderType; } else { UE_LOG(LogShaders, Verbose, TEXT("ShaderType '%s' dependency was not found."), *ShaderTypeName.ToString()); } } } return Ar; } FShader* FShaderType::ConstructForDeserialization() const { return (*ConstructSerializedRef)(); } FShader* FShaderType::ConstructCompiled(const FShader::CompiledShaderInitializerType& Initializer) const { return (*ConstructCompiledRef)(Initializer); } static bool ShouldCompileShaderFrequency(EShaderFrequency Frequency, EShaderPlatform ShaderPlatform) { if (IsMobilePlatform(ShaderPlatform)) { return Frequency == SF_Vertex || Frequency == SF_Pixel || Frequency == SF_Compute; } return true; } bool FShaderType::ShouldCompilePermutation(const FShaderPermutationParameters& Parameters) const { return ShouldCompileShaderFrequency((EShaderFrequency)Frequency, Parameters.Platform) && (*ShouldCompilePermutationRef)(Parameters); } EShaderPermutationPrecacheRequest FShaderType::ShouldPrecachePermutation(const FShaderPermutationParameters& Parameters) const { return ShouldCompileShaderFrequency((EShaderFrequency)Frequency, Parameters.Platform) ? (*ShouldPrecachePermutationRef)(Parameters) : EShaderPermutationPrecacheRequest::NotUsed; } const FShaderBindingLayout* FShaderType::GetShaderBindingLayout(const FShaderPermutationParameters& Parameters) const { return (*GetShaderBindingLayoutTypeRef)(Parameters); } #if WITH_EDITOR void FShaderType::ModifyCompilationEnvironment(const FShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) const { (*ModifyCompilationEnvironmentRef)(Parameters, OutEnvironment); OutEnvironment.ShaderBindingLayout = GetShaderBindingLayout(Parameters); if (OutEnvironment.ShaderBindingLayout) { // Store copy of RHI version of the shader binding layout in the environment so it can be serialized for the shader compiler workers OutEnvironment.RHIShaderBindingLayout = OutEnvironment.ShaderBindingLayout->RHILayout; } if (Frequency == SF_RayHitGroup) { // TODO: add a define for each of the 3 possible entry points? // See UE::ShaderCompilerCommon::ParseRayTracingEntryPoint for how to parse them } else { // define the function name as itself so one can use #ifdef to isolate the shader being compiled within a larger .usf file OutEnvironment.SetDefine(FunctionName, FunctionName); } #if RHI_RAYTRACING ERayTracingPayloadType RayTracingPayloadType = GetRayTracingPayloadType(Parameters.PermutationId); switch (Frequency) { case SF_RayGen: { // Raygen shader can use any number of payloads, but must use at least one checkf(RayTracingPayloadType != ERayTracingPayloadType::None, TEXT("Raygen shader %s did not declare which payload type(s) it uses. Make sure you override GetRayTracingPayloadType()"), Name); break; } case SF_RayHitGroup: case SF_RayMiss: case SF_RayCallable: { // these shader types must know which payload type they are using checkf(RayTracingPayloadType != ERayTracingPayloadType::None, TEXT("Raytracing shader %s did not declare which payload type(s) it uses. Make sure you override GetRayTracingPayloadType()"), Name); checkf(FMath::CountBits(static_cast(RayTracingPayloadType)) == 1, TEXT("Raytracing shader %s did not declare a unique payload type. Only one payload type is supported for this shader frequency."), Name); break; } default: { // not a raytracing shader, specifying a payload type would suggest some confusion has occured checkf(RayTracingPayloadType == ERayTracingPayloadType::None, TEXT("Non-Raytracing shader %s declared a payload type!"), Name); break; } } if (RayTracingPayloadType != ERayTracingPayloadType::None) { checkf(IsRayTracingPayloadRegistered(RayTracingPayloadType), TEXT("Raytracing shader %s is using a payload type (%u) which was never registered"), Name, RayTracingPayloadType); OutEnvironment.SetDefineAndCompileArgument(TEXT("RT_PAYLOAD_TYPE"), static_cast(RayTracingPayloadType)); OutEnvironment.SetDefineAndCompileArgument(TEXT("RT_PAYLOAD_MAX_SIZE"), GetRayTracingPayloadTypeMaxSize(RayTracingPayloadType)); if ( (uint32(RayTracingPayloadType) & uint32(ERayTracingPayloadType::RayTracingMaterial)) || (uint32(RayTracingPayloadType) & uint32(ERayTracingPayloadType::GPULightmass)) ) { // If any payload requires a fully simplified material, we force fully simplified material all the way. // That is used to have material ray tracing shaders compressed to single slab. // Smaller payload means faster performance and for some tracing this will be enough, e.g. reflected materials, lightmass diffuse interactions. OutEnvironment.SetDefine(TEXT("SUBSTRATE_USE_FULLYSIMPLIFIED_MATERIAL"), 1); } } #endif } bool FShaderType::ValidateCompiledResult(EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray& OutError) const { return (*ValidateCompiledResultRef)(Platform, ParameterMap, OutError); } EShaderCompileJobPriority FShaderType::GetOverrideJobPriority() const { return (*GetOverrideJobPriorityRef)(); } void FShaderType::UpdateReferencedUniformBufferNames(const TMap>& ShaderFileToUniformBufferVariables) { ReferencedUniformBuffers.Empty(); GenerateReferencedUniformBuffers(SourceFilename, Name, ShaderFileToUniformBufferVariables, ReferencedUniformBuffers); } FString FShaderType::GetPermutationIdString(int32 PermutationId, bool bFullNames) const { // This function is for diagnostics only, so ignore it if the shader type did not initialize its format string if (GetPermutationIdStringRef) { FString PermutationIdentifier; (*GetPermutationIdStringRef)(PermutationId, PermutationIdentifier, bFullNames); return PermutationIdentifier; } return FString(); } #endif // WITH_EDITOR ERayTracingPayloadType FShaderType::GetRayTracingPayloadType(const int32 PermutationId) const { #if RHI_RAYTRACING return (*GetRayTracingPayloadTypeRef)(PermutationId); #else return static_cast(0); #endif } const FSHAHash& FShaderType::GetSourceHash(EShaderPlatform ShaderPlatform) const { return GetShaderFileHash(GetShaderFilename(), ShaderPlatform); } void FShaderType::Initialize(const TMap >& ShaderFileToUniformBufferVariables) { #if WITH_EDITOR //#todo-rco: Need to call this only when Initializing from a Pipeline once it's removed from the global linked list if (!FPlatformProperties::RequiresCookedData()) { #if UE_BUILD_DEBUG TArray UniqueShaderTypes; #endif TArray> Tasks; Tasks.Reserve(FShaderType::GetNameToTypeMap().Num()); for(TLinkedList::TIterator It(FShaderType::GetTypeList()); It; It.Next()) { FShaderType* Type = *It; #if UE_BUILD_DEBUG UniqueShaderTypes.Add(Type); #endif Tasks.Emplace( UE::Tasks::Launch( TEXT("UpdateReferencedUniformBufferNames"), [Type, &ShaderFileToUniformBufferVariables]() mutable { Type->UpdateReferencedUniformBufferNames(ShaderFileToUniformBufferVariables); } ) ); } UE::Tasks::Wait(Tasks); #if UE_BUILD_DEBUG // Check for duplicated shader type names UniqueShaderTypes.Sort([](const FShaderType& A, const FShaderType& B) { return (SIZE_T)&A < (SIZE_T)&B; }); for (int32 Index = 1; Index < UniqueShaderTypes.Num(); ++Index) { checkf(UniqueShaderTypes[Index - 1] != UniqueShaderTypes[Index], TEXT("Duplicated FShader type name %s found, please rename one of them!"), UniqueShaderTypes[Index]->GetName()); } #endif } #endif // WITH_EDITOR } int32 FShaderMapPointerTable::AddIndexedPointer(const FTypeLayoutDesc& TypeDesc, void* Ptr) { int32 Index = INDEX_NONE; if (ShaderTypes.TryAddIndexedPtr(TypeDesc, Ptr, Index)) return Index; if (VFTypes.TryAddIndexedPtr(TypeDesc, Ptr, Index)) return Index; return Index; } void* FShaderMapPointerTable::GetIndexedPointer(const FTypeLayoutDesc& TypeDesc, uint32 i) const { void* Ptr = nullptr; if (ShaderTypes.TryGetIndexedPtr(TypeDesc, i, Ptr)) return Ptr; if (VFTypes.TryGetIndexedPtr(TypeDesc, i, Ptr)) return Ptr; return Ptr; } void FShaderMapPointerTable::SaveToArchive(FArchive& Ar, const FPlatformTypeLayoutParameters& LayoutParams, const void* FrozenObject) const { FPointerTableBase::SaveToArchive(Ar, LayoutParams, FrozenObject); int32 NumTypes = ShaderTypes.Num(); int32 NumVFTypes = VFTypes.Num(); Ar << NumTypes; Ar << NumVFTypes; for (int32 TypeIndex = 0; TypeIndex < NumTypes; ++TypeIndex) { const FShaderType* Type = ShaderTypes.GetIndexedPointer(TypeIndex); FHashedName TypeName = Type->GetHashedName(); Ar << TypeName; } for (int32 VFTypeIndex = 0; VFTypeIndex < NumVFTypes; ++VFTypeIndex) { const FVertexFactoryType* VFType = VFTypes.GetIndexedPointer(VFTypeIndex); FHashedName TypeName = VFType->GetHashedName(); Ar << TypeName; } } bool FShaderMapPointerTable::LoadFromArchive(FArchive& Ar, const FPlatformTypeLayoutParameters& LayoutParams, void* FrozenObject) { SCOPED_LOADTIMER(FShaderMapPointerTable_LoadFromArchive); const bool bResult = FPointerTableBase::LoadFromArchive(Ar, LayoutParams, FrozenObject); int32 NumTypes = 0; int32 NumVFTypes = 0; Ar << NumTypes; Ar << NumVFTypes; ShaderTypes.Empty(NumTypes); for (int32 TypeIndex = 0; TypeIndex < NumTypes; ++TypeIndex) { FHashedName TypeName; Ar << TypeName; FShaderType* Type = FindShaderTypeByName(TypeName); ShaderTypes.LoadIndexedPointer(Type); } VFTypes.Empty(NumVFTypes); for (int32 VFTypeIndex = 0; VFTypeIndex < NumVFTypes; ++VFTypeIndex) { FHashedName TypeName; Ar << TypeName; FVertexFactoryType* VFType = FVertexFactoryType::GetVFByName(TypeName); VFTypes.LoadIndexedPointer(VFType); } return bResult; } FShaderCompiledShaderInitializerType::FShaderCompiledShaderInitializerType( const FShaderType* InType, const FShaderType::FParameters* InParameters, int32 InPermutationId, const FShaderCompilerOutput& CompilerOutput, const FSHAHash& InMaterialShaderMapHash, const FShaderPipelineType* InShaderPipeline, const FVertexFactoryType* InVertexFactoryType ) : Type(InType) , Parameters(InParameters) , Target(CompilerOutput.Target) , Code(CompilerOutput.ShaderCode.GetReadView()) , ParameterMap(CompilerOutput.ParameterMap) , OutputHash(CompilerOutput.OutputHash) , MaterialShaderMapHash(InMaterialShaderMapHash) , ShaderPipeline(InShaderPipeline) , VertexFactoryType(InVertexFactoryType) , NumInstructions(CompilerOutput.NumInstructions) , NumTextureSamplers(CompilerOutput.NumTextureSamplers) , CodeSize(CompilerOutput.ShaderCode.GetShaderCodeSize()) , PermutationId(InPermutationId) , ShaderStatistics(CompilerOutput.ShaderStatistics) { } /** * Used to construct a shader for deserialization. * This still needs to initialize members to safe values since FShaderType::GenerateSerializationHistory uses this constructor. */ FShader::FShader() // set to undefined (currently shared with SF_Vertex) : Target((EShaderFrequency)0, GShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel]) , ResourceIndex(INDEX_NONE) #if WITH_EDITORONLY_DATA , NumInstructions(0u) , NumTextureSamplers(0u) , CodeSize(0u) #endif // WITH_EDITORONLY_DATA { } /** * Construct a shader from shader compiler output. */ FShader::FShader(const CompiledShaderInitializerType& Initializer) : Type(const_cast(Initializer.Type)) // TODO - remove const_cast, make TIndexedPtr work with 'const' , VFType(const_cast(Initializer.VertexFactoryType)) , Target(Initializer.Target) , ResourceIndex(INDEX_NONE) #if WITH_EDITORONLY_DATA , NumInstructions(Initializer.NumInstructions) , NumTextureSamplers(Initializer.NumTextureSamplers) , CodeSize(Initializer.CodeSize) #endif // WITH_EDITORONLY_DATA { checkSlow(Initializer.OutputHash != FSHAHash()); // Only store a truncated hash to minimize memory overhead static_assert(sizeof(SortKey) <= sizeof(Initializer.OutputHash.Hash)); FMemory::Memcpy(&SortKey, Initializer.OutputHash.Hash, sizeof(SortKey)); #if WITH_EDITORONLY_DATA OutputHash = Initializer.OutputHash; // Store off the source hash that this shader was compiled with // This will be used as part of the shader key in order to identify when shader files have been changed and a recompile is needed SourceHash = Initializer.Type->GetSourceHash(Initializer.Target.GetPlatform()); if (Initializer.VertexFactoryType) { // Store off the VF source hash that this shader was compiled with VFSourceHash = Initializer.VertexFactoryType->GetSourceHash(Initializer.Target.GetPlatform()); } #endif // WITH_EDITORONLY_DATA BuildParameterMapInfo(Initializer.ParameterMap.GetParameterMap()); // Bind uniform buffer parameters automatically for (TLinkedList::TIterator StructIt(FShaderParametersMetadata::GetStructList()); StructIt; StructIt.Next()) { if (Initializer.ParameterMap.ContainsParameterAllocation(StructIt->GetShaderVariableName())) { UniformBufferParameterStructs.Add(StructIt->GetShaderVariableHashedName()); FShaderUniformBufferParameter& Parameter = UniformBufferParameters.AddDefaulted_GetRef(); Parameter.Bind(Initializer.ParameterMap, StructIt->GetShaderVariableName(), SPF_Mandatory); } } // Register the shader now that it is valid, so that it can be reused //Register(false); } FShader::~FShader() { } void FShader::Finalize(const FShaderMapResourceCode* Code) { const FSHAHash& Hash = GetOutputHash(); const int32 NewResourceIndex = Code->FindShaderIndex(Hash); checkf(NewResourceIndex != INDEX_NONE, TEXT("Missing shader code %s"), *Hash.ToString()); ResourceIndex = NewResourceIndex; } template static void CityHashArray(uint64& Hash, const TMemoryImageArray& Array) { const int32 ArrayNum = Array.Num(); CityHash64WithSeed((const char*)&ArrayNum, sizeof(ArrayNum), Hash); CityHash64WithSeed((const char*)Array.GetData(), Array.Num() * sizeof(TType), Hash); } void FShader::BuildParameterMapInfo(const TMap& ParameterMap) { uint32 UniformCount = 0; uint32 SamplerCount = 0; uint32 SRVCount = 0; for (TMap::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt) { const FParameterAllocation& ParamValue = ParameterIt.Value(); switch (ParamValue.Type) { case EShaderParameterType::UniformBuffer: UniformCount++; break; case EShaderParameterType::BindlessSampler: case EShaderParameterType::Sampler: SamplerCount++; break; case EShaderParameterType::BindlessSRV: case EShaderParameterType::SRV: SRVCount++; break; } } ParameterMapInfo.UniformBuffers.Empty(UniformCount); ParameterMapInfo.TextureSamplers.Empty(SamplerCount); ParameterMapInfo.SRVs.Empty(SRVCount); auto GetResourceParameterMap = [this](EShaderParameterType ParameterType) -> TMemoryImageArray* { switch (ParameterType) { case EShaderParameterType::Sampler: return &ParameterMapInfo.TextureSamplers; case EShaderParameterType::SRV: return &ParameterMapInfo.SRVs; case EShaderParameterType::BindlessSRV: return &ParameterMapInfo.SRVs; case EShaderParameterType::BindlessSampler: return &ParameterMapInfo.TextureSamplers; default: return nullptr; } }; for (TMap::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt) { const FParameterAllocation& ParamValue = ParameterIt.Value(); if (ParamValue.Type == EShaderParameterType::LooseData) { bool bAddedToExistingBuffer = false; for (int32 LooseParameterBufferIndex = 0; LooseParameterBufferIndex < ParameterMapInfo.LooseParameterBuffers.Num(); LooseParameterBufferIndex++) { FShaderLooseParameterBufferInfo& LooseParameterBufferInfo = ParameterMapInfo.LooseParameterBuffers[LooseParameterBufferIndex]; if (LooseParameterBufferInfo.BaseIndex == ParamValue.BufferIndex) { LooseParameterBufferInfo.Parameters.Emplace(ParamValue.BaseIndex, ParamValue.Size); LooseParameterBufferInfo.Size += ParamValue.Size; bAddedToExistingBuffer = true; } } if (!bAddedToExistingBuffer) { FShaderLooseParameterBufferInfo NewParameterBufferInfo(ParamValue.BufferIndex, ParamValue.Size); NewParameterBufferInfo.Parameters.Emplace(ParamValue.BaseIndex, ParamValue.Size); ParameterMapInfo.LooseParameterBuffers.Add(NewParameterBufferInfo); } } else if (ParamValue.Type == EShaderParameterType::UniformBuffer) { ParameterMapInfo.UniformBuffers.Emplace(ParamValue.BufferIndex); } else if (TMemoryImageArray* ParameterInfoArray = GetResourceParameterMap(ParamValue.Type)) { ParameterInfoArray->Emplace(ParamValue.BaseIndex, ParamValue.BufferIndex, ParamValue.Type); } } for (FShaderLooseParameterBufferInfo& Info : ParameterMapInfo.LooseParameterBuffers) { Info.Parameters.Sort(); } ParameterMapInfo.LooseParameterBuffers.Sort(); ParameterMapInfo.UniformBuffers.Sort(); ParameterMapInfo.TextureSamplers.Sort(); ParameterMapInfo.SRVs.Sort(); uint64 Hash = 0; { const auto CityHashValue = [&](auto Value) { CityHash64WithSeed((const char*)&Value, sizeof(Value), Hash); }; for (FShaderLooseParameterBufferInfo& Info : ParameterMapInfo.LooseParameterBuffers) { CityHashValue(Info.BaseIndex); CityHashValue(Info.Size); CityHashArray(Hash, Info.Parameters); } CityHashArray(Hash, ParameterMapInfo.UniformBuffers); CityHashArray(Hash, ParameterMapInfo.TextureSamplers); CityHashArray(Hash, ParameterMapInfo.SRVs); } ParameterMapInfo.Hash = Hash; } const FSHAHash& FShader::GetOutputHash() const { #if WITH_EDITORONLY_DATA return OutputHash; #else return ShaderSourceDefaultHash; #endif } const FSHAHash& FShader::GetHash() const { #if WITH_EDITORONLY_DATA return SourceHash; #else return ShaderSourceDefaultHash; #endif } const FSHAHash& FShader::GetVertexFactoryHash() const { #if WITH_EDITORONLY_DATA return VFSourceHash; #else return ShaderSourceDefaultHash; #endif } const FTypeLayoutDesc& GetTypeLayoutDesc(const FPointerTableBase* PtrTable, const FShader& Shader) { const FShaderType* Type = Shader.GetType(PtrTable); checkf(Type, TEXT("FShaderType is missing")); return Type->GetLayout(); } const FShaderParametersMetadata* FShader::FindAutomaticallyBoundUniformBufferStruct(int32 BaseIndex) const { for (int32 i = 0; i < UniformBufferParameters.Num(); i++) { if (UniformBufferParameters[i].GetBaseIndex() == BaseIndex) { FShaderParametersMetadata** Parameters = FShaderParametersMetadata::GetNameStructMap().Find(UniformBufferParameterStructs[i]); return Parameters ? *Parameters : nullptr; } } return nullptr; } void FShader::DumpDebugInfo(const FShaderMapPointerTable& InPtrTable) { FVertexFactoryType* VertexFactoryType = GetVertexFactoryType(InPtrTable); UE_LOG(LogConsoleResponse, Display, TEXT(" FShader :Frequency %s"), GetShaderFrequencyString(GetFrequency())); UE_LOG(LogConsoleResponse, Display, TEXT(" :Target %s"), *LegacyShaderPlatformToShaderFormat(GetShaderPlatform()).ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :VFType %s"), VertexFactoryType ? VertexFactoryType->GetName() : TEXT("null")); UE_LOG(LogConsoleResponse, Display, TEXT(" :Type %s"), GetType(InPtrTable)->GetName()); UE_LOG(LogConsoleResponse, Display, TEXT(" :SourceHash %s"), *GetHash().ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :VFSourceHash %s"), *GetVertexFactoryHash().ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :OutputHash %s"), *GetOutputHash().ToString()); } #if WITH_EDITOR void FShader::SaveShaderStableKeys(const FShaderMapPointerTable& InPtrTable, EShaderPlatform TargetShaderPlatform, int32 PermutationId, const FStableShaderKeyAndValue& InSaveKeyVal) { if ((TargetShaderPlatform == EShaderPlatform::SP_NumPlatforms || GetShaderPlatform() == TargetShaderPlatform) && FShaderLibraryCooker::NeedsShaderStableKeys(TargetShaderPlatform)) { FShaderType* ShaderType = GetType(InPtrTable); FVertexFactoryType* VertexFactoryType = GetVertexFactoryType(InPtrTable); FStableShaderKeyAndValue SaveKeyVal(InSaveKeyVal); SaveKeyVal.TargetFrequency = FName(GetShaderFrequencyString(GetFrequency())); SaveKeyVal.TargetPlatform = LegacyShaderPlatformToShaderFormat(GetShaderPlatform()); SaveKeyVal.VFType = FName(VertexFactoryType ? VertexFactoryType->GetName() : TEXT("null")); SaveKeyVal.PermutationId = FName(*FString::Printf(TEXT("Perm_%d"), PermutationId)); SaveKeyVal.OutputHash = GetOutputHash(); if (ShaderType) { ShaderType->GetShaderStableKeyParts(SaveKeyVal); } FShaderLibraryCooker::AddShaderStableKeyValue(GetShaderPlatform(), SaveKeyVal); } } #endif // WITH_EDITOR bool FShaderPipelineType::bInitialized = false; static TArray& GetSortedShaderPipelineTypes(FShaderType::EShaderTypeForDynamicCast Type) { static TArray SortedTypes[(uint32)FShaderType::EShaderTypeForDynamicCast::NumShaderTypes]; return SortedTypes[(uint32)Type]; } FShaderPipelineType::FShaderPipelineType( const TCHAR* InName, const FShaderType* InVertexOrMeshShader, const FShaderType* InGeometryOrAmplificationShader, const FShaderType* InPixelShader, bool bInIsMeshPipeline, bool bInShouldOptimizeUnusedOutputs) : Name(InName), TypeName(Name), HashedName(TypeName), HashedPrimaryShaderFilename(InVertexOrMeshShader->GetShaderFilename()), GlobalListLink(this), bShouldOptimizeUnusedOutputs(bInShouldOptimizeUnusedOutputs) { checkf(Name && *Name, TEXT("Shader Pipeline Type requires a valid Name!")); checkf(InVertexOrMeshShader, TEXT("A Shader Pipeline always requires a Vertex or Mesh Shader")); //make sure the name is shorter than the maximum serializable length check(FCString::Strlen(InName) < NAME_SIZE); FMemory::Memzero(AllStages); if (InPixelShader) { check(InPixelShader->GetTypeForDynamicCast() == InVertexOrMeshShader->GetTypeForDynamicCast()); Stages.Add(InPixelShader); AllStages[SF_Pixel] = InPixelShader; } if (InGeometryOrAmplificationShader) { check(InGeometryOrAmplificationShader->GetTypeForDynamicCast() == InVertexOrMeshShader->GetTypeForDynamicCast()); Stages.Add(InGeometryOrAmplificationShader); AllStages[bInIsMeshPipeline ? SF_Amplification : SF_Geometry] = InGeometryOrAmplificationShader; } Stages.Add(InVertexOrMeshShader); AllStages[bInIsMeshPipeline ? SF_Mesh : SF_Vertex] = InVertexOrMeshShader; for (uint32 FrequencyIndex = 0; FrequencyIndex < SF_NumStandardFrequencies; ++FrequencyIndex) { if (const FShaderType* ShaderType = AllStages[FrequencyIndex]) { checkf(ShaderType->GetPermutationCount() == 1, TEXT("Shader '%s' has multiple shader permutations. Shader pipelines only support a single permutation."), ShaderType->GetName()) } } static uint32 TypeHashCounter = 0; ++TypeHashCounter; HashIndex = TypeHashCounter; GlobalListLink.LinkHead(GetTypeList()); GetNameToTypeMap().Add(HashedName, this); TArray& SortedTypes = GetSortedShaderPipelineTypes(InVertexOrMeshShader->GetTypeForDynamicCast()); const int32 SortedIndex = Algo::LowerBoundBy(SortedTypes, HashedName, [](const FShaderPipelineType* InType) { return InType->GetHashedName(); }); SortedTypes.Insert(this, SortedIndex); // This will trigger if an IMPLEMENT_SHADER_TYPE was in a module not loaded before InitializeShaderTypes // Shader types need to be implemented in modules that are loaded before that checkf(!bInitialized, TEXT("Shader Pipeline was loaded after Engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier.")); } FShaderPipelineType::~FShaderPipelineType() { GetNameToTypeMap().Remove(HashedName); GlobalListLink.Unlink(); TArray& SortedTypes = GetSortedShaderPipelineTypes(AllStages[HasMeshShader() ? SF_Mesh : SF_Vertex]->GetTypeForDynamicCast()); const int32 SortedIndex = Algo::BinarySearchBy(SortedTypes, HashedName, [](const FShaderPipelineType* InType) { return InType->GetHashedName(); }); check(SortedIndex != INDEX_NONE); SortedTypes.RemoveAt(SortedIndex); } TMap& FShaderPipelineType::GetNameToTypeMap() { static TMap GShaderPipelineNameToTypeMap; return GShaderPipelineNameToTypeMap; } TLinkedList*& FShaderPipelineType::GetTypeList() { return GShaderPipelineList; } const TArray& FShaderPipelineType::GetSortedTypes(FShaderType::EShaderTypeForDynamicCast Type) { return GetSortedShaderPipelineTypes(Type); } TArray FShaderPipelineType::GetShaderPipelineTypesByFilename(const TCHAR* InFilename, bool bSearchAsRegexFilter) { if (bSearchAsRegexFilter) { const FRegexPattern RegexSearch(FString(InFilename).Replace(TEXT("*"), TEXT("(.)*")), ERegexPatternFlags::CaseInsensitive); return FShaderPipelineType::GetShaderPipelineTypesByFilenameFilter( [&RegexSearch](const TCHAR* ShaderTypeFilename) -> bool { return FRegexMatcher(RegexSearch, ShaderTypeFilename).FindNext(); } ); } else { return FShaderPipelineType::GetShaderPipelineTypesByFilenameFilter( [InFilename](const TCHAR* ShaderTypeFilename) -> bool { return FPlatformString::Strcmp(InFilename, ShaderTypeFilename) == 0; } ); } } TArray FShaderPipelineType::GetShaderPipelineTypesByFilenameFilter(const TFunction& InFilenameFilter) { TArray PipelineTypes; for (TLinkedList::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next()) { auto* PipelineType = *It; for (auto* ShaderType : PipelineType->Stages) { if (InFilenameFilter(ShaderType->GetShaderFilename())) { PipelineTypes.AddUnique(PipelineType); break; } } } return PipelineTypes; } void FShaderPipelineType::Initialize() { check(!bInitialized); TSet UsedNames; #if UE_BUILD_DEBUG TArray UniqueShaderPipelineTypes; #endif for (TLinkedList::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next()) { const auto* PipelineType = *It; #if UE_BUILD_DEBUG UniqueShaderPipelineTypes.Add(PipelineType); #endif // Validate stages for (int32 Index = 0; Index < SF_NumFrequencies; ++Index) { check(!PipelineType->AllStages[Index] || PipelineType->AllStages[Index]->GetFrequency() == (EShaderFrequency)Index); } auto& Stages = PipelineType->GetStages(); // #todo-rco: Do we allow mix/match of global/mesh/material stages? // Check all shaders are the same type, start from the top-most stage const FGlobalShaderType* GlobalType = Stages[0]->GetGlobalShaderType(); const FMeshMaterialShaderType* MeshType = Stages[0]->GetMeshMaterialShaderType(); const FMaterialShaderType* MateriallType = Stages[0]->GetMaterialShaderType(); for (int32 Index = 1; Index < Stages.Num(); ++Index) { if (GlobalType) { checkf(Stages[Index]->GetGlobalShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name); } else if (MeshType) { checkf(Stages[Index]->GetMeshMaterialShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name); } else if (MateriallType) { checkf(Stages[Index]->GetMaterialShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name); } } FName PipelineName = PipelineType->GetFName(); checkf(!UsedNames.Contains(PipelineName), TEXT("Two Pipelines with the same name %s found!"), PipelineType->Name); UsedNames.Add(PipelineName); } #if UE_BUILD_DEBUG // Check for duplicated shader pipeline type names UniqueShaderPipelineTypes.Sort([](const FShaderPipelineType& A, const FShaderPipelineType& B) { return (SIZE_T)&A < (SIZE_T)&B; }); for (int32 Index = 1; Index < UniqueShaderPipelineTypes.Num(); ++Index) { checkf(UniqueShaderPipelineTypes[Index - 1] != UniqueShaderPipelineTypes[Index], TEXT("Duplicated FShaderPipeline type name %s found, please rename one of them!"), UniqueShaderPipelineTypes[Index]->GetName()); } #endif bInitialized = true; } const FShaderPipelineType* FShaderPipelineType::GetShaderPipelineTypeByName(const FHashedName& Name) { FShaderPipelineType** FoundType = GetNameToTypeMap().Find(Name); return FoundType ? *FoundType : nullptr; } bool FShaderPipelineType::ShouldOptimizeUnusedOutputs(EShaderPlatform Platform) const { return bShouldOptimizeUnusedOutputs && RHISupportsShaderPipelines(Platform); } const FSHAHash& FShaderPipelineType::GetSourceHash(EShaderPlatform ShaderPlatform) const { TArray Filenames; for (const FShaderType* ShaderType : Stages) { Filenames.Add(ShaderType->GetShaderFilename()); } return GetShaderFilesHash(Filenames, ShaderPlatform); } bool FShaderPipelineType::ShouldCompilePermutation(const FShaderPermutationParameters& Parameters) const { for (const FShaderType* ShaderType : Stages) { if (!ShaderType->ShouldCompilePermutation(Parameters)) { return false; } } return true; } EShaderPermutationPrecacheRequest FShaderPipelineType::ShouldPrecachePermutation(const FShaderPermutationParameters& Parameters) const { EShaderPermutationPrecacheRequest Result = EShaderPermutationPrecacheRequest::NotUsed; for (const FShaderType* ShaderType : Stages) { EShaderPermutationPrecacheRequest ShaderTypeRequest = ShaderType->ShouldPrecachePermutation(Parameters); if (ShaderTypeRequest == EShaderPermutationPrecacheRequest::Precached) { return ShaderTypeRequest; } else if (ShaderTypeRequest == EShaderPermutationPrecacheRequest::NotPrecached) { Result = ShaderTypeRequest; } } return Result; } void FShaderTypeDependency::RefreshCachedSourceHash(EShaderPlatform ShaderPlatform) { const FShaderType*const* ShaderTypePtr = FShaderType::GetNameToTypeMap().Find(ShaderTypeName); const FShaderType* ShaderType = ShaderTypePtr ? *ShaderTypePtr : nullptr; if (!ShaderType) { SourceHash = FSHAHash(); return; } SourceHash = ShaderType->GetSourceHash(ShaderPlatform); } void FShaderPipelineTypeDependency::RefreshCachedSourceHash(EShaderPlatform ShaderPlatform) { const FShaderPipelineType* ShaderPipelineType = FShaderPipelineType::GetShaderPipelineTypeByName(ShaderPipelineTypeName); if (!ShaderPipelineType) { StagesSourceHash = FSHAHash(); return; } StagesSourceHash = ShaderPipelineType->GetSourceHash(ShaderPlatform); } #if WITH_EDITOR void FShaderTypeDependency::Save(FCbWriter& Writer) const { Writer.BeginArray(); Writer << ShaderTypeName; Writer << SourceHash; Writer << PermutationId; Writer.EndArray(); } bool FShaderTypeDependency::TryLoad(FCbFieldView Field) { *this = FShaderTypeDependency(); FCbFieldViewIterator ElementField(Field.CreateViewIterator()); if (!LoadFromCompactBinary(ElementField++, ShaderTypeName)) { return false; } if (!LoadFromCompactBinary(ElementField++, SourceHash)) { return false; } PermutationId = ElementField.AsInt32(); if ((ElementField++).HasError()) { return false; } return true; } bool LoadFromCompactBinary(FCbFieldView Field, FShaderTypeDependency& OutValue) { return OutValue.TryLoad(Field); } void FShaderPipelineTypeDependency::Save(FCbWriter& Writer) const { Writer.BeginArray(); Writer << ShaderPipelineTypeName; Writer << StagesSourceHash; Writer.EndArray(); } bool FShaderPipelineTypeDependency::TryLoad(FCbFieldView Field) { *this = FShaderPipelineTypeDependency(); FCbFieldViewIterator ElementField(Field.CreateViewIterator()); if (!LoadFromCompactBinary(ElementField++, ShaderPipelineTypeName)) { return false; } if (!LoadFromCompactBinary(ElementField++, StagesSourceHash)) { return false; } return true; } bool LoadFromCompactBinary(FCbFieldView Field, FShaderPipelineTypeDependency& OutValue) { return OutValue.TryLoad(Field); } #endif // WITH_EDITOR void FShaderPipeline::AddShader(FShader* Shader, int32 PermutationId) { const EShaderFrequency Frequency = Shader->GetFrequency(); check(Shaders[Frequency].IsNull()); Shaders[Frequency] = Shader; PermutationIds[Frequency] = PermutationId; } FShader* FShaderPipeline::FindOrAddShader(FShader* Shader, int32 PermutationId) { const EShaderFrequency Frequency = Shader->GetFrequency(); FShader* PrevShader = Shaders[Frequency]; if (PrevShader && PermutationIds[Frequency] == PermutationId) { DeleteObjectFromLayout(Shader); return PrevShader; } Shaders[Frequency].SafeDelete(); Shaders[Frequency] = Shader; PermutationIds[Frequency] = PermutationId; return Shader; } FShaderPipeline::~FShaderPipeline() { // Manually set references to nullptr, helps debugging for (uint32 i = 0u; i < SF_NumGraphicsFrequencies; ++i) { Shaders[i] = nullptr; } } void FShaderPipeline::Validate(const FShaderPipelineType* InPipelineType) const { check(InPipelineType->GetHashedName() == TypeName); for (const FShaderType* Stage : InPipelineType->GetStages()) { const FShader* Shader = GetShader(Stage->GetFrequency()); check(Shader); check(Shader->GetTypeUnfrozen() == Stage); } } void FShaderPipeline::Finalize(const FShaderMapResourceCode* Code) { for (uint32 i = 0u; i < SF_NumGraphicsFrequencies; ++i) { if (Shaders[i]) { Shaders[i]->Finalize(Code); } } } #if WITH_EDITOR void FShaderPipeline::SaveShaderStableKeys(const FShaderMapPointerTable& InPtrTable, EShaderPlatform TargetShaderPlatform, const struct FStableShaderKeyAndValue& InSaveKeyVal) const { // the higher level code can pass SP_NumPlatforms, in which case play it safe and use a platform that we know can remove inteprolators const EShaderPlatform ShaderPlatformThatSupportsRemovingInterpolators = SP_PCD3D_SM5; checkf(RHISupportsShaderPipelines(ShaderPlatformThatSupportsRemovingInterpolators), TEXT("We assumed that shader platform %d supports shaderpipelines while it doesn't"), static_cast(ShaderPlatformThatSupportsRemovingInterpolators)); FShaderPipelineType** FoundPipelineType = FShaderPipelineType::GetNameToTypeMap().Find(TypeName); check(FoundPipelineType); FShaderPipelineType* PipelineType = *FoundPipelineType; bool bCanHaveUniqueShaders = (TargetShaderPlatform != SP_NumPlatforms) ? PipelineType->ShouldOptimizeUnusedOutputs(TargetShaderPlatform) : PipelineType->ShouldOptimizeUnusedOutputs(ShaderPlatformThatSupportsRemovingInterpolators); if (bCanHaveUniqueShaders) { FStableShaderKeyAndValue SaveKeyVal(InSaveKeyVal); SaveKeyVal.SetPipelineHash(this); // could use PipelineType->GetSourceHash(), but each pipeline instance even of the same type can have unique shaders for (uint32 Frequency = 0u; Frequency < SF_NumGraphicsFrequencies; ++Frequency) { FShader* Shader = Shaders[Frequency]; if (Shader) { Shader->SaveShaderStableKeys(InPtrTable, TargetShaderPlatform, PermutationIds[Frequency], SaveKeyVal); } } } } #endif // WITH_EDITOR void DumpShaderStats(EShaderPlatform Platform, EShaderFrequency Frequency) { #if ALLOW_DEBUG_FILES FDiagnosticTableViewer ShaderTypeViewer(*FDiagnosticTableViewer::GetUniqueTemporaryFilePath(TEXT("ShaderStats"))); // Iterate over all shader types and log stats. int32 TotalShaderCount = 0; int32 TotalTypeCount = 0; int32 TotalInstructionCount = 0; int32 TotalSize = 0; int32 TotalPipelineCount = 0; float TotalSizePerType = 0; // Write a row of headings for the table's columns. ShaderTypeViewer.AddColumn(TEXT("Type")); ShaderTypeViewer.AddColumn(TEXT("Instances")); ShaderTypeViewer.AddColumn(TEXT("Average instructions")); ShaderTypeViewer.AddColumn(TEXT("Size")); ShaderTypeViewer.AddColumn(TEXT("AvgSizePerInstance")); ShaderTypeViewer.AddColumn(TEXT("Pipelines")); ShaderTypeViewer.AddColumn(TEXT("Shared Pipelines")); ShaderTypeViewer.CycleRow(); for( TLinkedList::TIterator It(FShaderType::GetTypeList()); It; It.Next() ) { const FShaderType* Type = *It; if (Type->GetNumShaders()) { // Calculate the average instruction count and total size of instances of this shader type. float AverageNumInstructions = 0.0f; int32 NumInitializedInstructions = 0; int32 Size = 0; int32 NumShaders = 0; int32 NumPipelines = 0; int32 NumSharedPipelines = 0; #if 0 for (TMap::TConstIterator ShaderIt(Type->ShaderIdMap);ShaderIt;++ShaderIt) { const FShader* Shader = ShaderIt.Value(); // Skip shaders that don't match frequency. if( Shader->GetTarget().Frequency != Frequency && Frequency != SF_NumFrequencies ) { continue; } // Skip shaders that don't match platform. if( Shader->GetTarget().Platform != Platform && Platform != SP_NumPlatforms ) { continue; } NumInitializedInstructions += Shader->GetNumInstructions(); Size += Shader->GetCode().Num(); NumShaders++; } AverageNumInstructions = (float)NumInitializedInstructions / (float)Type->GetNumShaders(); #endif for (TLinkedList::TConstIterator PipelineIt(FShaderPipelineType::GetTypeList()); PipelineIt; PipelineIt.Next()) { const FShaderPipelineType* PipelineType = *PipelineIt; bool bFound = false; if (Frequency == SF_NumFrequencies) { if (PipelineType->GetShader(Type->GetFrequency()) == Type) { ++NumPipelines; bFound = true; } } else { if (PipelineType->GetShader(Frequency) == Type) { ++NumPipelines; bFound = true; } } if (!PipelineType->ShouldOptimizeUnusedOutputs(Platform) && bFound) { ++NumSharedPipelines; } } // Only add rows if there is a matching shader. if( NumShaders ) { // Write a row for the shader type. ShaderTypeViewer.AddColumn(Type->GetName()); ShaderTypeViewer.AddColumn(TEXT("%u"),NumShaders); ShaderTypeViewer.AddColumn(TEXT("%.1f"),AverageNumInstructions); ShaderTypeViewer.AddColumn(TEXT("%u"),Size); ShaderTypeViewer.AddColumn(TEXT("%.1f"),Size / (float)NumShaders); ShaderTypeViewer.AddColumn(TEXT("%d"), NumPipelines); ShaderTypeViewer.AddColumn(TEXT("%d"), NumSharedPipelines); ShaderTypeViewer.CycleRow(); TotalShaderCount += NumShaders; TotalPipelineCount += NumPipelines; TotalInstructionCount += NumInitializedInstructions; TotalTypeCount++; TotalSize += Size; TotalSizePerType += Size / (float)NumShaders; } } } // go through non shared pipelines // Write a total row. ShaderTypeViewer.AddColumn(TEXT("Total")); ShaderTypeViewer.AddColumn(TEXT("%u"),TotalShaderCount); ShaderTypeViewer.AddColumn(TEXT("%u"),TotalInstructionCount); ShaderTypeViewer.AddColumn(TEXT("%u"),TotalSize); ShaderTypeViewer.AddColumn(TEXT("0")); ShaderTypeViewer.AddColumn(TEXT("%u"), TotalPipelineCount); ShaderTypeViewer.AddColumn(TEXT("-")); ShaderTypeViewer.CycleRow(); // Write an average row. ShaderTypeViewer.AddColumn(TEXT("Average")); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalTypeCount ? (TotalShaderCount / (float)TotalTypeCount) : 0.0f); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalShaderCount ? ((float)TotalInstructionCount / TotalShaderCount) : 0.0f); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalShaderCount ? (TotalSize / (float)TotalShaderCount) : 0.0f); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalTypeCount ? (TotalSizePerType / TotalTypeCount) : 0.0f); ShaderTypeViewer.AddColumn(TEXT("-")); ShaderTypeViewer.AddColumn(TEXT("-")); ShaderTypeViewer.CycleRow(); #endif } void DumpShaderPipelineStats(EShaderPlatform Platform) { #if ALLOW_DEBUG_FILES FDiagnosticTableViewer ShaderTypeViewer(*FDiagnosticTableViewer::GetUniqueTemporaryFilePath(TEXT("ShaderPipelineStats"))); int32 TotalNumPipelines = 0; int32 TotalSize = 0; float TotalSizePerType = 0; // Write a row of headings for the table's columns. ShaderTypeViewer.AddColumn(TEXT("Type")); ShaderTypeViewer.AddColumn(TEXT("Shared/Unique")); // Exclude compute for (int32 Index = 0; Index < SF_NumFrequencies - 1; ++Index) { ShaderTypeViewer.AddColumn(GetShaderFrequencyString((EShaderFrequency)Index)); } ShaderTypeViewer.CycleRow(); int32 TotalTypeCount = 0; for (TLinkedList::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next()) { const FShaderPipelineType* Type = *It; // Write a row for the shader type. ShaderTypeViewer.AddColumn(Type->GetName()); ShaderTypeViewer.AddColumn(Type->ShouldOptimizeUnusedOutputs(Platform) ? TEXT("U") : TEXT("S")); for (int32 Index = 0; Index < SF_NumFrequencies - 1; ++Index) { const FShaderType* ShaderType = Type->GetShader((EShaderFrequency)Index); ShaderTypeViewer.AddColumn(ShaderType ? ShaderType->GetName() : TEXT("")); } ShaderTypeViewer.CycleRow(); } #endif } FShaderType* FindShaderTypeByName(const FHashedName& ShaderTypeName) { FShaderType** FoundShader = FShaderType::GetNameToTypeMap().Find(ShaderTypeName); if (FoundShader) { return *FoundShader; } return nullptr; } void DispatchComputeShader( FRHIComputeCommandList& RHICmdList, FShader* Shader, uint32 ThreadGroupCountX, uint32 ThreadGroupCountY, uint32 ThreadGroupCountZ) { RHICmdList.DispatchComputeShader(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); } void DispatchIndirectComputeShader( FRHIComputeCommandList& RHICmdList, FShader* Shader, FRHIBuffer* ArgumentBuffer, uint32 ArgumentOffset) { RHICmdList.DispatchIndirectComputeShader(ArgumentBuffer, ArgumentOffset); } bool IsDxcEnabledForPlatform(EShaderPlatform Platform, bool bHlslVersion2021) { // Check the generic console variable first (if DXC is supported) if (FDataDrivenShaderPlatformInfo::GetSupportsDxc(Platform)) { static FShaderPlatformCachedIniValue ShaderForceDXC(TEXT("r.Shaders.ForceDXC")); if (bHlslVersion2021 || (ShaderForceDXC.Get(Platform) != 0)) { return true; } } // Check backend specific console variables next if (IsD3DPlatform(Platform) && IsPCPlatform(Platform)) { // D3D backend supports a precompile step for HLSL2021 which is separate from ForceDXC option static const IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.D3D.ForceDXC")); return ((CVar && CVar->GetInt() != 0)); } // Hlslcc has been removed for Metal, Vulkan, and OpenGL backends. There is only DXC now. if (IsMetalPlatform(Platform) || IsVulkanPlatform(Platform) || IsOpenGLPlatform(Platform)) { return true; } return false; } bool IsUsingEmulatedUniformBuffers(EShaderPlatform Platform) { if (IsOpenGLPlatform(Platform)) { // DXC only supports emulated uniform buffers on GLES return true; } return false; } void ShaderMapAppendKeyString(EShaderPlatform Platform, FString& KeyString) { FShaderKeyGenerator KeyGen(KeyString); ShaderMapAppendKey(Platform, KeyGen); } void ShaderMapAppendKey(EShaderPlatform Platform, FShaderKeyGenerator& KeyGen) { const FName ShaderFormatName = LegacyShaderPlatformToShaderFormat(Platform); for (const FAutoConsoleObject* ConsoleObject : FAutoConsoleObject::AccessGeneralShaderChangeCvars()) { FString ConsoleObjectName = IConsoleManager::Get().FindConsoleObjectName(ConsoleObject->AsVariable()); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObjectName); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObject->AsVariable()->GetString()); } if (IsMobilePlatform(Platform)) { for (const FAutoConsoleObject* ConsoleObject : FAutoConsoleObject::AccessMobileShaderChangeCvars()) { FString ConsoleObjectName = IConsoleManager::Get().FindConsoleObjectName(ConsoleObject->AsVariable()); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObjectName); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObject->AsVariable()->GetString()); } } else if (IsConsolePlatform(Platform)) { for (const FAutoConsoleObject* ConsoleObject : FAutoConsoleObject::AccessDesktopShaderChangeCvars()) { FString ConsoleObjectName = IConsoleManager::Get().FindConsoleObjectName(ConsoleObject->AsVariable()); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObjectName); KeyGen.AppendSeparator(); KeyGen.Append(ConsoleObject->AsVariable()->GetString()); } } // Globals that should cause all shaders to recompile when changed must be appended to the key here // Key should be kept as short as possible while being somewhat human readable for debugging { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("Compat.UseDXT5NormalMaps")); KeyGen.AppendSeparator(); KeyGen.Append((CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("DXTN") : TEXT("BC5N")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ClearCoatNormal")); KeyGen.AppendSeparator(); KeyGen.Append((CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("CCBN") : TEXT("NoCCBN")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.IrisNormal")); KeyGen.AppendSeparator(); KeyGen.Append((CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("Iris") : TEXT("NoIris")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.CompileShadersForDevelopment")); KeyGen.AppendSeparator(); KeyGen.Append((CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("DEV") : TEXT("NoDEV")); } { const bool bValue = IsStaticLightingAllowed(); KeyGen.AppendSeparator(); KeyGen.Append(bValue ? TEXT("SL") : TEXT("NoSL")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MaterialEditor.LWCTruncateMode")); const int32 LWCTruncateValue = CVar ? CVar->GetValueOnAnyThread() : 0; if (LWCTruncateValue == 1) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LWC1")); } else if (LWCTruncateValue == 2) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LWC2")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VelocityOutputPass")); const int32 VelocityOutputPassValue = CVar ? CVar->GetValueOnAnyThread() : 0; if (VelocityOutputPassValue == 1) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("GV")); } else { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VOP")); KeyGen.AppendSeparator(); KeyGen.Append(VelocityOutputPassValue); } } { const UE::StereoRenderUtils::FStereoShaderAspects Aspects(Platform); if (Aspects.IsInstancedStereoEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VRIS")); if (Aspects.IsInstancedMultiViewportEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MVIEW")); } } if (Aspects.IsMobileMultiViewEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MMVIEW")); } } { if (IsUsingSelectiveBasePassOutputs(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SO")); } } { // PreExposure is always used KeyGen.AppendSeparator(); KeyGen.Append(TEXT("PreExp")); } { KeyGen.AppendSeparator(); KeyGen.Append(IsUsingDBuffers(Platform) ? TEXT("DBuf") : TEXT("NoDBuf")); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AllowGlobalClipPlane")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ClipP")); } } { // Extra data (names, etc) if (ShouldEnableExtraShaderData(ShaderFormatName)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ExtraData")); } // Symbols and/or SymbolsInfo and version if symbols serialization changes if (ShouldGenerateShaderSymbols(ShaderFormatName)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Symbols")); } if (ShouldGenerateShaderSymbolsInfo(ShaderFormatName)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SymbolsInfo")); } // Are symbols based on source or results if (ShouldAllowUniqueShaderSymbols(ShaderFormatName)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("FullDbg")); } } if (!ShouldOptimizeShaders(ShaderFormatName)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoOpt")); } { // Always default to fast math unless specified static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FastMath")); if ((CVar && CVar->GetInt() == 0)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoFastMath")); } } { static FShaderPlatformCachedIniValue CVarWarningsAsErrorsPerPlatform(TEXT("r.Shaders.WarningsAsErrors")); if (const int32 Level = CVarWarningsAsErrorsPerPlatform.Get(Platform); Level != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("WX")); KeyGen.Append(Level); } } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.CheckLevel")); // Note: Since 1 is the default, we don't modify the hash for this case, so as to not force a rebuild, and to keep the hash shorter. if (CVar && (CVar->GetInt() == 0 || CVar->GetInt() == 2)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("C")); KeyGen.Append(CVar->GetInt()); } } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FlowControlMode")); if (CVar) { switch(CVar->GetInt()) { case 2: KeyGen.AppendSeparator(); KeyGen.Append(TEXT("AvoidFlow")); break; case 1: KeyGen.AppendSeparator(); KeyGen.Append(TEXT("PreferFlow")); break; case 0: default: break; } } } if (!AllowPixelDepthOffset(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoPDO")); } if (!AllowPerPixelShadingModels(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoPPSM")); } if (UseRemoveUnsedInterpolators(Platform) && !IsOpenGLPlatform(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("UnInt")); } if (ForwardShadingForcesSkyLightCubemapBlending(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("FwdSkyBlnd")); } if (IsMobilePlatform(Platform)) { { KeyGen.AppendSeparator(); KeyGen.Append(IsMobileHDR() ? TEXT("HDR") : TEXT("LDR")); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.DisableVertexFog")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoVFog")); } } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.FloatPrecisionMode")); if (CVar && CVar->GetInt() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("highp")); KeyGen.Append(CVar->GetInt()); } } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.AllowDitheredLODTransition")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("DLODT")); } } if (IsUsingEmulatedUniformBuffers(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoUB")); } { const bool bMobileMovableSpotlightShadowsEnabled = IsMobileMovableSpotlightShadowsEnabled(Platform); if (bMobileMovableSpotlightShadowsEnabled) { KeyGen.Append(TEXT("S")); } } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.UseHWsRGBEncoding")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("HWsRGB")); } } { // make it per shader platform ? static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.SupportGPUScene")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobGPUSc")); } } { bool bIsMobileDeferredShading = IsMobileDeferredShadingEnabled(Platform); if (bIsMobileDeferredShading) { KeyGen.AppendSeparator(); KeyGen.Append(MobileUsesExtenedGBuffer(Platform) ? TEXT("MobDShEx") : TEXT("MobDSh")); } else { static IConsoleVariable* MobileForwardEnableClusteredReflectionsCVAR = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.Forward.EnableClusteredReflections")); if (MobileForwardEnableClusteredReflectionsCVAR && MobileForwardEnableClusteredReflectionsCVAR->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobFCR")); } } } { static IConsoleVariable* MobileGTAOPreIntegratedTextureTypeCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.GTAOPreIntegratedTextureType")); static IConsoleVariable* MobileAmbientOcclusionCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.AmbientOcclusion")); int32 GTAOPreIntegratedTextureType = MobileGTAOPreIntegratedTextureTypeCVar ? MobileGTAOPreIntegratedTextureTypeCVar->GetInt() : 0; if (MobileAmbientOcclusionCVar && MobileAmbientOcclusionCVar->GetInt() != 0 && IsMobileHDR()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobileAO")); KeyGen.AppendSeparator(); KeyGen.Append(GTAOPreIntegratedTextureType); } } if (IsMobileDistanceFieldEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobSDF")); } { static FShaderPlatformCachedIniValue EnableCullBeforeFetchIniValue(TEXT("r.CullBeforeFetch")); if (EnableCullBeforeFetchIniValue.Get(Platform) == 1) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("CBF")); } static FShaderPlatformCachedIniValue EnableWarpCullingIniValue(TEXT("r.WarpCulling")); if (EnableWarpCullingIniValue.Get(Platform) == 1) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("WC")); } } if (MobileUsesFullDepthPrepass(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobFDP")); } if (AreMobileScreenSpaceReflectionsEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MobSSR")); } if (MobileAllowFramebufferFetch(Platform) == false) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoFBF")); } } else { if (IsUsingEmulatedUniformBuffers(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoUB")); } } if (RenderRectLightsAsSpotLights(GetMaxSupportedFeatureLevel(Platform))) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("R2S")); } uint32 PlatformShadingModelsMask = GetPlatformShadingModelsMask(Platform); if (PlatformShadingModelsMask != 0xFFFFFFFF) { KeyGen.Append(TEXT("SMM")); KeyGen.AppendSeparator(); KeyGen.AppendHex(PlatformShadingModelsMask); } const IShaderFormat* ShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(ShaderFormatName); if (ShaderFormat) { FString ShaderFormatExtraData; ShaderFormat->AppendToKeyString(ShaderFormatExtraData); KeyGen.Append(ShaderFormatExtraData); } ITargetPlatform* TargetPlatform = GetTargetPlatformManagerRef().FindTargetPlatformWithSupport(TEXT("ShaderFormat"), ShaderFormatName); uint32 SupportedHardwareMask = TargetPlatform ? TargetPlatform->GetSupportedHardwareMask() : 0; if (SupportedHardwareMask != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SHM")); KeyGen.AppendSeparator(); KeyGen.AppendHex(SupportedHardwareMask); } // Encode the Metal standard into the shader compile options so that they recompile if the settings change. if (IsMetalPlatform(Platform)) { { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.ZeroInitialise")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ZeroInit")); } } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.BoundsChecking")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("BoundsChecking")); } } { if (RHISupportsManualVertexFetch(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MVF")); KeyGen.AppendSeparator(); } } uint32 ShaderVersion = RHIGetMetalShaderLanguageVersion(Platform); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MTLSTD")); KeyGen.Append(ShaderVersion); KeyGen.AppendSeparator(); bool bAllowFastIntrinsics = false; bool bEnableMathOptimisations = true; bool bForceFloats = false; bool bSupportAppleA8 = false; int32 IndirectArgumentTier = 0; bool bMetalOptimizeBySize = false; if (IsPCPlatform(Platform)) { GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("UseFastIntrinsics"), bAllowFastIntrinsics, GEngineIni); GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("EnableMathOptimisations"), bEnableMathOptimisations, GEngineIni); GConfig->GetInt(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("IndirectArgumentTier"), IndirectArgumentTier, GEngineIni); GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("MetalOptimizeBySize"), bMetalOptimizeBySize, GEngineIni); } else { GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("UseFastIntrinsics"), bAllowFastIntrinsics, GEngineIni); GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("EnableMathOptimisations"), bEnableMathOptimisations, GEngineIni); GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("ForceFloats"), bForceFloats, GEngineIni); GConfig->GetInt(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("IndirectArgumentTier"), IndirectArgumentTier, GEngineIni); GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportAppleA8"), bSupportAppleA8, GEngineIni); GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("MetalOptimizeBySize"), bMetalOptimizeBySize, GEngineIni); } if (bAllowFastIntrinsics) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MTLSL_FastIntrin")); } // Same as console-variable above, but that's global and this is per-platform, per-project if (!bEnableMathOptimisations) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoFastMath")); } if (bForceFloats) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("FP32")); } if(bSupportAppleA8) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("A8GPU")); } if(bMetalOptimizeBySize) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Os")); } KeyGen.AppendSeparator(); KeyGen.Append(TEXT("IAB")); KeyGen.Append(IndirectArgumentTier); // Shaders built for archiving - for Metal that requires compiling the code in a different way so that we can strip it later bool bArchive = false; GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("bSharedMaterialNativeLibraries"), bArchive, GGameIni); if (bArchive) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ARCHIVE")); } } if (Platform == SP_VULKAN_ES3_1_ANDROID || Platform == SP_VULKAN_SM5_ANDROID) { bool bStripReflect = true; GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bStripShaderReflection"), bStripReflect, GEngineIni); if (!bStripReflect) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NoStripReflect")); } } // Is DXC shader compiler enabled for this platform? KeyGen.AppendSeparator(); KeyGen.Append(IsDxcEnabledForPlatform(Platform) ? TEXT("DXC1") : TEXT("DXC0")); if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5)) { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.StencilForLODDither")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SD")); } } { bool bForwardShading = false; if (TargetPlatform) { // if there is a specific target platform that matches our shader platform, use that to drive forward shading bForwardShading = TargetPlatform->UsesForwardShading(); } else { // shader platform doesn't match a specific target platform, use cvar setting for forward shading static IConsoleVariable* CVarForwardShadingLocal = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForwardShading")); bForwardShading = CVarForwardShadingLocal ? (CVarForwardShadingLocal->GetInt() != 0) : false; } if (bForwardShading) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("FS")); } } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Deferred.SupportPrimitiveAlphaHoldout")); const bool bDeferredSupportPrimitiveAlphaHoldout = CVar ? CVar->GetBool() : false; if (bDeferredSupportPrimitiveAlphaHoldout) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("PAH")); } } if (TargetPlatform && TargetPlatform->SupportsFeature(ETargetPlatformFeatures::NormalmapLAEncodingMode)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("NLA")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VertexFoggingForOpaque")); bool bVertexFoggingForOpaque = CVar && CVar->GetValueOnAnyThread() > 0; if (TargetPlatform) { const int32 PlatformHeightFogMode = TargetPlatform->GetHeightFogModeForOpaque(); if (PlatformHeightFogMode == 1) { bVertexFoggingForOpaque = false; } else if (PlatformHeightFogMode == 2) { bVertexFoggingForOpaque = true; } } if (bVertexFoggingForOpaque) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VFO")); } } bool bSupportLocalFogVolumes = false; { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.SupportLocalFogVolumes")); bSupportLocalFogVolumes = CVar && CVar->GetInt() > 0; if (bSupportLocalFogVolumes) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LFV")); } } { if (DoesProjectSupportLumenRayTracedTranslucentRefraction()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LTRRT")); } } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.LocalFogVolume.ApplyOnTranslucent")); const bool bLocalFogVolumesApplyOnTranclucent = CVar && CVar->GetInt() > 0; if (bSupportLocalFogVolumes && bLocalFogVolumesApplyOnTranclucent) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LFVTRA")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportSkyAtmosphere")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SKYATM")); static const auto CVarHeightFog = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportSkyAtmosphereAffectsHeightFog")); if (CVarHeightFog && CVarHeightFog->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SKYHF")); } } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VolumetricCloud.Support")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VCLOUD")); } } { if (DoesProjectSupportExpFogMatchesVolumetricFog()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("EXPVFOG")); } } const bool bNeedsSeparateMainDirLightTexture = IsWaterSeparateMainDirLightEnabled(Platform); if (bNeedsSeparateMainDirLightTexture) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SLWSMDLT")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportCloudShadowOnForwardLitTranslucent")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("CLDTRANS")); } } { const bool bTranslucentUsesLightRectLights = GetTranslucentUsesLightRectLights(); if (bTranslucentUsesLightRectLights) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("RECTTRANS")); } } { const bool bTranslucentUsesShadowedLocalLights = GetTranslucentUsesShadowedLocalLights(); if (bTranslucentUsesShadowedLocalLights) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SHALOLIT")); } } { const bool bTranslucentUsesLightIESProfiles = GetTranslucentUsesLightIESProfiles(); if (bTranslucentUsesLightIESProfiles) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("IESTRANS")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shadow.Virtual.TranslucentQuality")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VSMTRANSQUALITY")); } } if (GetHairStrandsUsesTriangleStrips()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("STRDSTRIP")); } if (Substrate::IsSubstrateEnabled()) { { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SUBSTRATE")); } { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SUBGBFMT")); KeyGen.Append(Substrate::IsSubstrateBlendableGBufferEnabled(Platform)); } { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("BUDGET")); KeyGen.Append(Substrate::GetBytePerPixel(Platform)); } { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("CLOSURE")); KeyGen.Append(Substrate::GetClosurePerPixel(Platform)); } if (Substrate::IsDBufferPassEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("DBUFFERPASS")); } if (Substrate::IsHiddenMaterialAssetConversionEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("HIDDENCONV")); } if (Substrate::IsBackCompatibilityEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("BACKCOMPAT")); } if (Substrate::IsOpaqueRoughRefractionEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ROUGHDIFF")); } if (Substrate::GetNormalQuality() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("STRTNRMQ")); } if (Substrate::IsAdvancedVisualizationEnabled()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ADVDEBUG")); } { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("STSHQL")); KeyGen.Append(Substrate::GetShadingQuality(Platform)); } { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SSHEEN")); KeyGen.Append(Substrate::GetSheenQuality(Platform)); } if (Substrate::IsGlintEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("STRTGLT")); } if (Substrate::IsSpecularProfileEnabled(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("STRTSP")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Material.RoughDiffuse")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MATRDIFF")); } } { int32 LightFunctionAtlasFormat = GetLightFunctionAtlasFormat(); if (LightFunctionAtlasFormat > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LFAC")); KeyGen.Append(static_cast(LightFunctionAtlasFormat)); } bool bSingleLayerWaterUsesLightFunctionAtlas = GetSingleLayerWaterUsesLightFunctionAtlas(); if (bSingleLayerWaterUsesLightFunctionAtlas) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SLWLFA")); } bool bTranslucentUsesLightFunctionAtlas = GetTranslucentUsesLightFunctionAtlas(); if (bTranslucentUsesLightFunctionAtlas) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("FWDLFA")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Material.EnergyConservation")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MATENERGY")); } } { if (MaskedInEarlyPass(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("EZPMM")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GPUSkin.Limit2BoneInfluences")); if (CVar && CVar->GetValueOnAnyThread() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("2bi")); } } { if(UseGPUScene(Platform, GetMaxSupportedFeatureLevel(Platform))) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("gs1")); } else { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("gs0")); } } if (FDataDrivenShaderPlatformInfo::GetSupportSceneDataCompressedTransforms(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("sdct")); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GBufferDiffuseSampleOcclusion")); if (CVar && CVar->GetValueOnAnyThread() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("GDSO")); } } { static const auto CVarVirtualTextureLightmaps = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VirtualTexturedLightmaps")); const bool VTLightmaps = CVarVirtualTextureLightmaps && CVarVirtualTextureLightmaps->GetValueOnAnyThread() != 0; static const auto CVarVirtualTexture = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VirtualTextures")); bool VTTextures = CVarVirtualTexture && CVarVirtualTexture->GetValueOnAnyThread() != 0; static const auto CVarVTAnisotropic = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VT.AnisotropicFiltering")); int32 VTFiltering = CVarVTAnisotropic && CVarVTAnisotropic->GetValueOnAnyThread() != 0 ? 1 : 0; if (IsMobilePlatform(Platform) && VTTextures) { static FShaderPlatformCachedIniValue MobileVirtualTexturesIniValue(TEXT("r.Mobile.VirtualTextures")); VTTextures = (MobileVirtualTexturesIniValue.Get(Platform) != false); if (VTTextures) { static FShaderPlatformCachedIniValue CVarVTMobileManualTrilinearFiltering(TEXT("r.VT.Mobile.ManualTrilinearFiltering")); VTFiltering += (CVarVTMobileManualTrilinearFiltering.Get(Platform) ? 2 : 0); } } const bool VTSupported = UseVirtualTexturing(Platform, TargetPlatform); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VT")); KeyGen.AppendDebugText(TEXT("-")); KeyGen.Append(VTLightmaps); KeyGen.AppendDebugText(TEXT("-")); KeyGen.Append(VTTextures); KeyGen.AppendDebugText(TEXT("-")); KeyGen.Append(VTSupported); KeyGen.AppendDebugText(TEXT("-")); KeyGen.Append(VTFiltering); } { const UE::Color::FColorSpace& WCS = UE::Color::FColorSpace::GetWorking(); if (!WCS.IsSRGB()) { // The working color space is uniquely defined by its chromaticities (as loaded from renderer settings). uint32 WCSHash = 0; WCSHash ^= GetTypeHash(WCS.GetRedChromaticity()); WCSHash ^= GetTypeHash(WCS.GetGreenChromaticity()); WCSHash ^= GetTypeHash(WCS.GetBlueChromaticity()); WCSHash ^= GetTypeHash(WCS.GetWhiteChromaticity()); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("WCS")); KeyGen.AppendDebugText(TEXT("-")); KeyGen.Append(WCSHash); } static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.LegacyLuminanceFactors")); if (CVar && CVar->GetInt() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LLF")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shaders.RemoveDeadCode")); if (CVar && CVar->GetValueOnAnyThread() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MIN")); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataBool(TEXT("r.ShaderCompiler.PreprocessedJobCache")); if (CVar && CVar->GetValueOnAnyThread()) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("PJC")); } } if (RHISupportsShaderRootConstants(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SHRC")); } if (RHISupportsShaderBundleDispatch(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SHBD")); } if (RHISupportsRenderTargetWriteMask(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("RTWM")); } if (FDataDrivenShaderPlatformInfo::GetSupportsPerPixelDBufferMask(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("PPDBM")); } if (FDataDrivenShaderPlatformInfo::GetSupportsDistanceFields(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("DF")); } if (RHISupportsMeshShadersTier0(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MS_T0")); } if (RHISupportsMeshShadersTier1(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("MS_T1")); } if (FDataDrivenShaderPlatformInfo::GetSupportsBindless(Platform)) { const ERHIBindlessConfiguration BindlessConfig = UE::ShaderCompiler::GetBindlessConfiguration(Platform); if (BindlessConfig != ERHIBindlessConfiguration::Disabled) { KeyGen.AppendSeparator(); KeyGen.Append("BNDLS"); switch (BindlessConfig) { case ERHIBindlessConfiguration::RayTracing: KeyGen.Append("RT"); break; case ERHIBindlessConfiguration::Minimal: KeyGen.Append("MIN"); break; case ERHIBindlessConfiguration::All: KeyGen.Append("ALL"); break; } } } const ERHIStaticShaderBindingLayoutSupport StaticShaderBindingLayoutSupport = FDataDrivenShaderPlatformInfo::GetStaticShaderBindingLayoutSupport(Platform); if (StaticShaderBindingLayoutSupport != ERHIStaticShaderBindingLayoutSupport::Unsupported) { KeyGen.AppendSeparator(); KeyGen.Append(StaticShaderBindingLayoutSupport == ERHIStaticShaderBindingLayoutSupport::RayTracingOnly ? TEXT("SSBL-RT") : TEXT("SSBL")); } if (ShouldCompileRayTracingShadersForProject(Platform)) { static FShaderPlatformCachedIniValue CVarCompileRayTracingMaterialCHS(TEXT("r.RayTracing.CompileMaterialCHS")); static FShaderPlatformCachedIniValue CVarCompileRayTracingMaterialAHS(TEXT("r.RayTracing.CompileMaterialAHS")); static const auto CVarTextureLod = IConsoleManager::Get().FindConsoleVariable(TEXT("r.RayTracing.UseTextureLod")); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("RAY")); KeyGen.AppendDebugText(TEXT("-CHS")); KeyGen.AppendBoolInt(CVarCompileRayTracingMaterialCHS.Get(Platform) != 0); KeyGen.AppendDebugText(TEXT("AHS")); KeyGen.AppendBoolInt(CVarCompileRayTracingMaterialAHS.Get(Platform) != 0); KeyGen.AppendDebugText(TEXT("LOD")); KeyGen.AppendBoolInt(CVarTextureLod && CVarTextureLod->GetBool()); } if (DoesPlatformSupportHeterogeneousVolumes(Platform)) { static const auto ShadowCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HeterogeneousVolumes.Shadows")); if (ShadowCVar && ShadowCVar->GetValueOnAnyThread() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("HVSHADOW")); } static const auto CompTranslucencyCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Translucency.HeterogeneousVolumes")); if (CompTranslucencyCVar && CompTranslucencyCVar->GetValueOnAnyThread() != 0) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("HVCOMPTRANSL")); } } if (ForceSimpleSkyDiffuse(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SSD")); } if (VelocityEncodeDepth(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("VED")); } { const bool bSupportsAnisotropicMaterials = FDataDrivenShaderPlatformInfo::GetSupportsAnisotropicMaterials(Platform); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Aniso")); KeyGen.AppendDebugText(TEXT("-")); KeyGen.AppendBoolInt(bSupportsAnisotropicMaterials); } { // add shader compression format KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Compr")); FName CompressionFormat = GetShaderCompressionFormat(); KeyGen.Append(CompressionFormat); if (CompressionFormat == NAME_Oodle) { FOodleDataCompression::ECompressor OodleCompressor; FOodleDataCompression::ECompressionLevel OodleLevel; GetShaderCompressionOodleSettings(OodleCompressor, OodleLevel); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Compr")); KeyGen.Append(static_cast(OodleCompressor)); KeyGen.AppendSeparator(); KeyGen.AppendDebugText(TEXT("Lev")); KeyGen.Append(static_cast(OodleLevel)); } } { // add whether or not non-pipelined shader types are included KeyGen.AppendSeparator(); KeyGen.Append(TEXT("ExclNonPipSh-")); KeyGen.AppendBoolInt(ExcludeNonPipelinedShaderTypes(Platform)); } KeyGen.AppendSeparator(); KeyGen.Append(TEXT("LWC-")); KeyGen.Append(FMath::FloorToInt(FLargeWorldRenderScalar::GetTileSize())); uint64 ShaderPlatformPropertiesHash = FDataDrivenShaderPlatformInfo::GetShaderPlatformPropertiesHash(Platform); KeyGen.AppendSeparator(); KeyGen.Append(ShaderPlatformPropertiesHash); if (IsSingleLayerWaterDepthPrepassEnabled(Platform, GetMaxSupportedFeatureLevel(Platform))) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SLWDP")); } if (IsGPUSkinPassThroughSupported(Platform)) { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SKPassThrough1")); } else { KeyGen.AppendSeparator(); KeyGen.Append(TEXT("SKPassThrough0")); } if (DoesRuntimeSupportNanite(Platform, false, true)) { static const auto CVarAllowSpline = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite.AllowSplineMeshes")); static const auto CVarAllowSkinned = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite.AllowSkinnedMeshes")); KeyGen.AppendSeparator(); KeyGen.Append(TEXT("Nanite-")); KeyGen.AppendDebugText(TEXT("Spline")); KeyGen.Append(CVarAllowSpline ? CVarAllowSpline->GetInt() : 0); KeyGen.AppendDebugText(TEXT("Skinned")); KeyGen.Append(CVarAllowSkinned ? CVarAllowSkinned->GetInt() : 0); } } static EShaderPermutationFlags GAdditionalShaderPermutationFlags = EShaderPermutationFlags::None; void SetAdditionalShaderPermutationFlags(EShaderPermutationFlags AdditionalFlags) { check(GAdditionalShaderPermutationFlags == EShaderPermutationFlags::None); GAdditionalShaderPermutationFlags = AdditionalFlags; } EShaderPermutationFlags GetShaderPermutationFlags(const FPlatformTypeLayoutParameters& LayoutParams) { EShaderPermutationFlags Result = GAdditionalShaderPermutationFlags; if (LayoutParams.WithEditorOnly()) { Result |= EShaderPermutationFlags::HasEditorOnlyData; } return Result; } void RegisterRayTracingPayloadType(ERayTracingPayloadType PayloadType, uint32 PayloadSize, TRaytracingPayloadSizeFunction PayloadSizeFunction) { // Make sure we haven't registered this payload type yet uint32 PayloadTypeInt = static_cast(PayloadType); checkf(FMath::CountBits(PayloadTypeInt) == 1, TEXT("PayloadType should have only 1 bit set -- got %u"), PayloadTypeInt); checkf(!IsRayTracingPayloadRegistered(PayloadType), TEXT("Payload type %u has already been registered"), PayloadTypeInt); int32 PayloadIndex = FPlatformMath::CountTrailingZeros(PayloadTypeInt); RayTracingPayloadSizeFunctions[PayloadIndex] = PayloadSizeFunction; RayTracingPayloadSizes[PayloadIndex] = PayloadSizeFunction ? 0u : PayloadSize; RegisteredRayTracingPayloads |= PayloadTypeInt; } uint32 GetRayTracingPayloadTypeMaxSize(ERayTracingPayloadType PayloadType) { // Compute the largest payload size among all set bits uint32 Result = 0; checkf(IsRayTracingPayloadRegistered(PayloadType), TEXT("Payload type %u has not been registered"), PayloadType); for (uint32 PayloadTypeInt = static_cast(PayloadType); PayloadTypeInt;) { const int32 PayloadIndex = FPlatformMath::CountTrailingZeros(PayloadTypeInt); if (RayTracingPayloadSizeFunctions[PayloadIndex] != nullptr) { Result = FMath::Max(Result, RayTracingPayloadSizeFunctions[PayloadIndex]()); } else { Result = FMath::Max(Result, RayTracingPayloadSizes[PayloadIndex]); } // remove bit we just processed PayloadTypeInt &= ~(1u << PayloadIndex); } return Result; }