// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /*============================================================================= Shader.cpp: Shader implementation. =============================================================================*/ #include "Shader.h" #include "Misc/CoreMisc.h" #include "Stats/StatsMisc.h" #include "Serialization/MemoryWriter.h" #include "VertexFactory.h" #include "ProfilingDebugging/DiagnosticTable.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.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 "Misc/ScopeLock.h" #if WITH_EDITORONLY_DATA #include "Interfaces/IShaderFormat.h" #endif DEFINE_LOG_CATEGORY(LogShaders); RENDERCORE_API bool UsePreExposure(EShaderPlatform Platform) { // Mobile platforms are excluded because they use a different pre-exposure logic in MobileBasePassPixelShader.usf static const auto CVarUsePreExposure = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.UsePreExposure")); return CVarUsePreExposure->GetValueOnAnyThread() != 0 && !IsMobilePlatform(Platform) && IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5); } static const FName ShaderCompressionFormat = NAME_Zlib; static TAutoConsoleVariable CVarUsePipelines( TEXT("r.ShaderPipelines"), 1, TEXT("Enable using Shader pipelines.")); static TLinkedList* GShaderTypeList = nullptr; static TLinkedList* GShaderPipelineList = nullptr; static TMap* GShaderNameToTypeMap = 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, 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; // There will be unbound parameters for Metal's "Hull" shader stage as it is merely a placeholder to provide binding indices to the RHI if(!IsMetalPlatform((EShaderPlatform)Target.Platform) || Target.Frequency != SF_Hull) { // 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)); } } bool FShaderType::bInitializedSerializationHistory = false; FShaderType::FShaderType( EShaderTypeForDynamicCast InShaderTypeForDynamicCast, const TCHAR* InName, const TCHAR* InSourceFilename, const TCHAR* InFunctionName, uint32 InFrequency, int32 InTotalPermutationCount, ConstructSerializedType InConstructSerializedRef, GetStreamOutElementsType InGetStreamOutElementsRef, const FShaderParametersMetadata* InRootParametersMetadata ): ShaderTypeForDynamicCast(InShaderTypeForDynamicCast), Name(InName), TypeName(InName), SourceFilename(InSourceFilename), FunctionName(InFunctionName), Frequency(InFrequency), TotalPermutationCount(InTotalPermutationCount), ConstructSerializedRef(InConstructSerializedRef), GetStreamOutElementsRef(InGetStreamOutElementsRef), RootParametersMetadata(InRootParametersMetadata), GlobalListLink(this) { bCachedUniformBufferStructDeclarations = false; // 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(!bInitializedSerializationHistory, TEXT("Shader type was loaded after engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier.")); //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(TypeName, this); // Assign the shader type the next unassigned hash index. static uint32 NextHashIndex = 0; HashIndex = NextHashIndex++; } FShaderType::~FShaderType() { GlobalListLink.Unlink(); GetNameToTypeMap().Remove(TypeName); } 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* Filename) { TArray OutShaders; for(TLinkedList::TIterator It(GetTypeList()); It; It.Next()) { FShaderType* Type = *It; if (FPlatformString::Strcmp(Filename, Type->GetShaderFilename()) == 0) { OutShaders.Add(Type); } } return OutShaders; } TMap& FShaderType::GetNameToTypeMap() { if(!GShaderNameToTypeMap) { GShaderNameToTypeMap = new TMap(); } return *GShaderNameToTypeMap; } inline bool FShaderType::GetOutdatedCurrentType(TArray& OutdatedShaderTypes, TArray& OutdatedFactoryTypes) const { bool bOutdated = false; #if WITH_EDITOR for (TMap::TConstIterator ShaderIt(ShaderIdMap);ShaderIt;++ShaderIt) { FShader* Shader = ShaderIt.Value(); const FVertexFactoryParameterRef* VFParameterRef = Shader->GetVertexFactoryParameterRef(); const FSHAHash& SavedHash = Shader->GetHash(); const FSHAHash& CurrentHash = GetSourceHash(Shader->GetShaderPlatform()); const bool bOutdatedShader = SavedHash != CurrentHash; const bool bOutdatedVertexFactory = VFParameterRef && VFParameterRef->GetVertexFactoryType() && VFParameterRef->GetVertexFactoryType()->GetSourceHash(VFParameterRef->GetShaderPlatform()) != VFParameterRef->GetHash(); if (bOutdatedShader) { OutdatedShaderTypes.AddUnique(Shader->Type); bOutdated = true; } if (bOutdatedVertexFactory) { OutdatedFactoryTypes.AddUnique(VFParameterRef->GetVertexFactoryType()); bOutdated = true; } } #endif // WITH_EDITOR return bOutdated; } void FShaderType::GetOutdatedTypes(TArray& OutdatedShaderTypes, TArray& OutdatedFactoryTypes) { for(TLinkedList::TIterator It(GetTypeList()); It; It.Next()) { FShaderType* Type = *It; Type->GetOutdatedCurrentType(OutdatedShaderTypes, OutdatedFactoryTypes); } for (int32 TypeIndex = 0; TypeIndex < OutdatedShaderTypes.Num(); TypeIndex++) { UE_LOG(LogShaders, Warning, TEXT(" Recompiling %s"), OutdatedShaderTypes[TypeIndex]->GetName()); } for (int32 TypeIndex = 0; TypeIndex < OutdatedFactoryTypes.Num(); TypeIndex++) { UE_LOG(LogShaders, Warning, TEXT(" Recompiling %s"), OutdatedFactoryTypes[TypeIndex]->GetName()); } } 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::FindShaderById(const FShaderId& Id) { check(IsInGameThread()); FShader* Result = ShaderIdMap.FindRef(Id); check(!Result || Result->GetId() == Id); return Result; } FShader* FShaderType::ConstructForDeserialization() const { return (*ConstructSerializedRef)(); } const FSHAHash& FShaderType::GetSourceHash(EShaderPlatform ShaderPlatform) const { return GetShaderFileHash(GetShaderFilename(), ShaderPlatform); } void FShaderType::Initialize(const TMap >& ShaderFileToUniformBufferVariables) { //#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 for(TLinkedList::TIterator It(FShaderType::GetTypeList()); It; It.Next()) { FShaderType* Type = *It; #if UE_BUILD_DEBUG UniqueShaderTypes.Add(Type); #endif GenerateReferencedUniformBuffers(Type->SourceFilename, Type->Name, ShaderFileToUniformBufferVariables, Type->ReferencedUniformBufferStructsCache); // Cache serialization history for each shader type // This history is used to detect when shader serialization changes without a corresponding .usf change { // Construct a temporary shader, which is initialized to safe values for serialization FShader* TempShader = Type->ConstructForDeserialization(); check(TempShader != NULL); TempShader->Type = Type; // Serialize the temp shader to memory and record the number and sizes of serializations TArray TempData; FMemoryWriter Ar(TempData, true); FShaderSaveArchive SaveArchive(Ar, Type->SerializationHistory); TempShader->SerializeBase(SaveArchive, false, false); // Destroy the temporary shader delete TempShader; } } #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 } bInitializedSerializationHistory = true; } void FShaderType::Uninitialize() { for(TLinkedList::TIterator It(FShaderType::GetTypeList()); It; It.Next()) { FShaderType* Type = *It; Type->SerializationHistory = FSerializationHistory(); } bInitializedSerializationHistory = false; } TMap FShaderResource::ShaderResourceIdMap; #if RHI_RAYTRACING TArray FShaderResource::GlobalUnusedIndicies; TArray FShaderResource::GlobalRayTracingMaterialLibrary; FCriticalSection FShaderResource::GlobalRayTracingMaterialLibraryCS; void FShaderResource::GetRayTracingMaterialLibrary(TArray& RayTracingMaterials, FRHIRayTracingShader* DefaultShader) { FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS); RayTracingMaterials = GlobalRayTracingMaterialLibrary; for (uint32 Index : GlobalUnusedIndicies) { RayTracingMaterials[Index] = DefaultShader; } } uint32 FShaderResource::AddToRayTracingLibrary(FRHIRayTracingShader* Shader) { FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS); if (GlobalUnusedIndicies.Num() != 0) { uint32 Index = GlobalUnusedIndicies.Pop(false); checkSlow(GlobalRayTracingMaterialLibrary[Index] == nullptr); GlobalRayTracingMaterialLibrary[Index] = Shader; return Index; } else { GlobalRayTracingMaterialLibrary.Add(Shader); return GlobalRayTracingMaterialLibrary.Num() - 1; } } void FShaderResource::RemoveFromRayTracingLibrary(uint32 Index) { FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS); GlobalUnusedIndicies.Push(Index); GlobalRayTracingMaterialLibrary[Index] = nullptr; } #endif FShaderResource::FShaderResource() : SpecificType(NULL) , SpecificPermutationId(0) , NumRefs(0) , NumInstructions(0) #if WITH_EDITORONLY_DATA , NumTextureSamplers(0) #endif , bCodeInSharedLocation(false) , bCodeInSharedLocationRequested(false) { INC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1); } FShaderResource::FShaderResource(const FShaderCompilerOutput& Output, FShaderType* InSpecificType, int32 InSpecificPermutationId) : SpecificType(InSpecificType) , SpecificPermutationId(InSpecificPermutationId) , NumRefs(0) , NumInstructions(Output.NumInstructions) #if WITH_EDITORONLY_DATA , NumTextureSamplers(Output.NumTextureSamplers) #endif , bCodeInSharedLocation(false) , bCodeInSharedLocationRequested(false) { BuildParameterMapInfo(Output.ParameterMap.GetParameterMap()); check(!(SpecificPermutationId != 0 && SpecificType == nullptr)); Target = Output.Target; CompressCode(Output.ShaderCode.GetReadAccess()); check(Code.Num() > 0); OutputHash = Output.OutputHash; checkSlow(OutputHash != FSHAHash()); #if WITH_EDITORONLY_DATA PlatformDebugData = Output.PlatformDebugData; #endif { check(IsInGameThread()); ShaderResourceIdMap.Add(GetId(), this); } INC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num()); INC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes()); INC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1); } FShaderResource::~FShaderResource() { check(NumRefs == 0); DEC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num()); DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes()); DEC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1); } void FShaderResource::BuildParameterMapInfo(const TMap& ParameterMap) { for (int32 ParameterTypeIndex = 0; ParameterTypeIndex < (int32)EShaderParameterType::Num; ParameterTypeIndex++) { EShaderParameterType CurrentParameterType = (EShaderParameterType)ParameterTypeIndex; if (CurrentParameterType == EShaderParameterType::LooseData) { for (TMap::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt) { const FParameterAllocation& ParamValue = ParameterIt.Value(); if (ParamValue.Type == CurrentParameterType) { bool bAddedToExistingBuffer = false; for (int32 LooseParameterBufferIndex = 0; LooseParameterBufferIndex < ParameterMapInfo.LooseParameterBuffers.Num(); LooseParameterBufferIndex++) { FShaderLooseParameterBufferInfo& LooseParameterBufferInfo = ParameterMapInfo.LooseParameterBuffers[LooseParameterBufferIndex]; if (LooseParameterBufferInfo.BufferIndex == ParamValue.BufferIndex) { FShaderParameterInfo ParameterInfo(ParamValue.BaseIndex, ParamValue.Size); LooseParameterBufferInfo.Parameters.Add(ParameterInfo); LooseParameterBufferInfo.BufferSize += ParamValue.Size; bAddedToExistingBuffer = true; } } if (!bAddedToExistingBuffer) { FShaderLooseParameterBufferInfo NewParameterBufferInfo(ParamValue.BufferIndex, ParamValue.Size); FShaderParameterInfo ParameterInfo(ParamValue.BaseIndex, ParamValue.Size); NewParameterBufferInfo.Parameters.Add(ParameterInfo); ParameterMapInfo.LooseParameterBuffers.Add(NewParameterBufferInfo); } } } } else if (CurrentParameterType != EShaderParameterType::UAV) { int32 NumParameters = 0; for (TMap::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt) { const FParameterAllocation& ParamValue = ParameterIt.Value(); if (ParamValue.Type == CurrentParameterType) { NumParameters++; } } TArray* ParameterInfoArray = &ParameterMapInfo.UniformBuffers; if (CurrentParameterType == EShaderParameterType::Sampler) { ParameterInfoArray = &ParameterMapInfo.TextureSamplers; } else if (CurrentParameterType == EShaderParameterType::SRV) { ParameterInfoArray = &ParameterMapInfo.SRVs; } else { check(CurrentParameterType == EShaderParameterType::UniformBuffer); } ParameterInfoArray->Empty(NumParameters); for (TMap::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt) { const FParameterAllocation& ParamValue = ParameterIt.Value(); if (ParamValue.Type == CurrentParameterType) { const uint16 BaseIndex = CurrentParameterType == EShaderParameterType::UniformBuffer ? ParamValue.BufferIndex : ParamValue.BaseIndex; FShaderParameterInfo ParameterInfo(BaseIndex, ParamValue.Size); ParameterInfoArray->Add(ParameterInfo); } } } } } void FShaderResource::UncompressCode(TArray& UncompressedCode) const { if (Code.Num() != UncompressedCodeSize) { UncompressedCode.SetNum(UncompressedCodeSize); auto bSucceed = FCompression::UncompressMemory(ShaderCompressionFormat, UncompressedCode.GetData(), UncompressedCodeSize, Code.GetData(), Code.Num()); check(bSucceed); } else { UncompressedCode = Code; } } void FShaderResource::CompressCode(const TArray& UncompressedCode) { UncompressedCodeSize = UncompressedCode.Num(); Code = UncompressedCode; int32 CompressedSize = Code.Num(); if (FCompression::CompressMemory(ShaderCompressionFormat, Code.GetData(), CompressedSize, UncompressedCode.GetData(), UncompressedCode.Num())) { Code.SetNum(CompressedSize); } Code.Shrink(); } void FShaderResource::Register() { check(IsInGameThread()); ShaderResourceIdMap.Add(GetId(), this); } // Note: this is derived data. Bump guid in ShaderVersion.ush if changing the format, no backwards compat is necessary void FShaderResource::Serialize(FArchive& Ar, bool bLoadedByCookedMaterial) { check(!(SpecificPermutationId != 0 && SpecificType == nullptr)); Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); Ar << SpecificType; if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId) { Ar << SpecificPermutationId; } Ar << Target; if (Ar.CustomVer(FRenderingObjectVersion::GUID) < FRenderingObjectVersion::ShaderResourceCodeSharing) { Ar << Code; } Ar << OutputHash; Ar << NumInstructions; #if WITH_EDITORONLY_DATA if ((!Ar.IsCooking() || Ar.CookingTarget()->HasEditorOnlyData()) && !bLoadedByCookedMaterial) { Ar << NumTextureSamplers; } #endif // WITH_EDITORONLY_DATA Ar << ParameterMapInfo; if (Ar.UE4Ver() >= VER_UE4_COMPRESSED_SHADER_RESOURCES) { Ar << UncompressedCodeSize; } if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderResourceCodeSharing) { SerializeShaderCode(Ar); } #if WITH_EDITORONLY_DATA if (!bLoadedByCookedMaterial) { SerializePlatformDebugData(Ar); } #endif if (Ar.IsLoading()) { INC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), (int64)Code.Num()); INC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes()); } } void FShaderResource::SerializeShaderCode(FArchive& Ar) { // To not pollute the DDC we don't change the state of this object in memory, just the state of the object in the serialised archive. bool bCodeShared = bCodeInSharedLocation; #if WITH_EDITOR // in case shader code sharing is enabled, code will be saved outside of material asset if(Ar.IsSaving() && Ar.IsCooking() && Ar.IsPersistent() && !Ar.IsObjectReferenceCollector() && !bCodeInSharedLocation) { bCodeShared = FShaderCodeLibrary::AddShaderCode((EShaderPlatform)Target.Platform, (EShaderFrequency)Target.Frequency, OutputHash, Code, UncompressedCodeSize); } #endif Ar << bCodeShared; if (Ar.IsLoading()) { bCodeInSharedLocation = bCodeShared; if (bCodeInSharedLocation) { if (!GRHILazyShaderCodeLoading) { if (FShaderCodeLibrary::RequestShaderCode(OutputHash, &Ar)) { bCodeInSharedLocationRequested = true; } } else { FShaderCodeLibrary::LazyRequestShaderCode(OutputHash, &Ar); } } } if (!bCodeShared) { Ar << Code; } } #if WITH_EDITORONLY_DATA void FShaderResource::SerializePlatformDebugData(FArchive& Ar) { #if WITH_ENGINE if (Ar.IsCooking()) { // 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. if (PlatformDebugData.Num()) { TArray ShaderFormatNames; Ar.CookingTarget()->GetAllTargetedShaderFormats(ShaderFormatNames); for (FName FormatName : ShaderFormatNames) { const IShaderFormat* ShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(FormatName); if (ShaderFormat) { ShaderFormat->NotifyShaderCooked(PlatformDebugData, FormatName); } } } } if (!Ar.IsCooking() || Ar.CookingTarget()->HasEditorOnlyData()) #endif { // Always serialize if we're not cooking, the cook target requires editor only data, or we don't have the engine (i.e. we're SCW). Ar << PlatformDebugData; } } #endif void FShaderResource::AddRef() { checkSlow(IsInGameThread()); ++NumRefs; } void FShaderResource::Release() { checkSlow(IsInGameThread()); check(NumRefs != 0); if(--NumRefs == 0) { ShaderResourceIdMap.Remove(GetId()); // Send a release message to the rendering thread when the shader loses its last reference. BeginReleaseResource(this); BeginCleanup(this); if (bCodeInSharedLocation) { if (bCodeInSharedLocationRequested) { FShaderCodeLibrary::ReleaseShaderCode(OutputHash); } else { FShaderCodeLibrary::LazyReleaseShaderCode(OutputHash); } } } } FShaderResource* FShaderResource::FindShaderResourceById(const FShaderResourceId& Id) { check(IsInGameThread()); FShaderResource* Result = ShaderResourceIdMap.FindRef(Id); return Result; } FShaderResource* FShaderResource::FindOrCreateShaderResource(const FShaderCompilerOutput& Output, FShaderType* SpecificType, int32 SpecificPermutationId) { const FShaderResourceId ResourceId(Output.Target, Output.OutputHash, SpecificType ? SpecificType->GetName() : nullptr, SpecificPermutationId); FShaderResource* Resource = FindShaderResourceById(ResourceId); if (!Resource) { Resource = new FShaderResource(Output, SpecificType, SpecificPermutationId); } return Resource; } void FShaderResource::GetAllShaderResourceId(TArray& Ids) { check(IsInGameThread()); ShaderResourceIdMap.GetKeys(Ids); } bool FShaderResource::ArePlatformsCompatible(EShaderPlatform CurrentPlatform, EShaderPlatform TargetPlatform) { bool bFeatureLevelCompatible = CurrentPlatform == TargetPlatform; if (!bFeatureLevelCompatible && IsPCPlatform(CurrentPlatform) && IsPCPlatform(TargetPlatform) ) { bFeatureLevelCompatible = GetMaxSupportedFeatureLevel(CurrentPlatform) >= GetMaxSupportedFeatureLevel(TargetPlatform); bool const bIsTargetD3D = TargetPlatform == SP_PCD3D_SM5 || TargetPlatform == SP_PCD3D_SM4 || TargetPlatform == SP_PCD3D_ES3_1 || TargetPlatform == SP_PCD3D_ES2; bool const bIsCurrentPlatformD3D = CurrentPlatform == SP_PCD3D_SM5 || CurrentPlatform == SP_PCD3D_SM4 || TargetPlatform == SP_PCD3D_ES3_1 || CurrentPlatform == SP_PCD3D_ES2; // 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; } FSHAHash &FShaderResource::FilterShaderSourceHashForSerialization(const FArchive& Ar, FSHAHash &HashToSerialize) { #if KEEP_SHADER_SOURCE_HASHES return (!Ar.IsCooking()) ? HashToSerialize : ShaderSourceDefaultHash; #else return ShaderSourceDefaultHash; #endif } static void SafeAssignHash(FRHIShader* InShader, const FSHAHash& Hash) { if (InShader) { InShader->SetHash(Hash); } } void FShaderResource::InitRHI() { checkf(bCodeInSharedLocation || Code.Num() > 0, TEXT("FShaderResource::InitRHI was called with empty bytecode, which can happen if the resource is initialized multiple times on platforms with no editor data.")); // we can't have this called on the wrong platform's shaders if (!ArePlatformsCompatible(GMaxRHIShaderPlatform, (EShaderPlatform)Target.Platform)) { if (FPlatformProperties::RequiresCookedData()) { UE_LOG(LogShaders, Fatal, TEXT("FShaderResource::InitRHI got platform %s but it is not compatible with %s"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString(), *LegacyShaderPlatformToShaderFormat(GMaxRHIShaderPlatform).ToString()); } return; } TArray UncompressedCode; if (!bCodeInSharedLocation) { UncompressCode(UncompressedCode); } INC_DWORD_STAT_BY(STAT_Shaders_NumShadersUsedForRendering, 1); SCOPE_CYCLE_COUNTER(STAT_Shaders_RTShaderLoadTime); if(Target.Frequency == SF_Vertex) { Shader = FShaderCodeLibrary::CreateVertexShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } else if(Target.Frequency == SF_Pixel) { Shader = FShaderCodeLibrary::CreatePixelShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } else if(Target.Frequency == SF_Hull) { Shader = FShaderCodeLibrary::CreateHullShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } else if(Target.Frequency == SF_Domain) { Shader = FShaderCodeLibrary::CreateDomainShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } else if(Target.Frequency == SF_Geometry) { if (SpecificType) { FStreamOutElementList ElementList; TArray StreamStrides; int32 RasterizedStream = -1; SpecificType->GetStreamOutElements(ElementList, StreamStrides, RasterizedStream); checkf(ElementList.Num(), TEXT("Shader type %s was given GetStreamOutElements implementation that had no elements!"), SpecificType->GetName()); //@todo - not using the cache PRAGMA_DISABLE_DEPRECATION_WARNINGS Shader = FShaderCodeLibrary::CreateGeometryShaderWithStreamOutput((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode, ElementList, StreamStrides.Num(), StreamStrides.GetData(), RasterizedStream); PRAGMA_ENABLE_DEPRECATION_WARNINGS } else { Shader = FShaderCodeLibrary::CreateGeometryShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); } UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } else if(Target.Frequency == SF_Compute) { Shader = FShaderCodeLibrary::CreateComputeShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode); UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); } #if RHI_RAYTRACING else if (Target.Frequency == SF_RayGen || Target.Frequency == SF_RayMiss || Target.Frequency == SF_RayHitGroup || Target.Frequency == SF_RayCallable) { if (GRHISupportsRayTracing) { RayTracingShader = RHICreateRayTracingShader(UncompressedCode, Target.GetFrequency()); UE_CLOG((bCodeInSharedLocation && !IsValidRef(RayTracingShader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString()); if (Target.Frequency == SF_RayHitGroup) { RayTracingMaterialLibraryIndex = AddToRayTracingLibrary(RayTracingShader); } RayTracingShader->SetHash(OutputHash); } } #endif // RHI_RAYTRACING else { checkNoEntry(); // Unexpected shader target frequency } if (Target.Frequency != SF_Geometry) { checkf(!SpecificType, TEXT("Only geometry shaders can use GetStreamOutElements, shader type %s"), SpecificType->GetName()); } if (!FPlatformProperties::HasEditorOnlyData()) { DEC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num()); DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, Code.GetAllocatedSize()); Code.Empty(); if (bCodeInSharedLocation) { if (bCodeInSharedLocationRequested) { FShaderCodeLibrary::ReleaseShaderCode(OutputHash); } else { FShaderCodeLibrary::LazyReleaseShaderCode(OutputHash); } } bCodeInSharedLocation = false; bCodeInSharedLocationRequested = false; } } void FShaderResource::ReleaseRHI() { DEC_DWORD_STAT_BY(STAT_Shaders_NumShadersUsedForRendering, 1); #if RHI_RAYTRACING if (IsInitialized() && RayTracingMaterialLibraryIndex != UINT_MAX) { RemoveFromRayTracingLibrary(RayTracingMaterialLibraryIndex); RayTracingMaterialLibraryIndex = UINT_MAX; } #endif Shader.SafeRelease(); #if RHI_RAYTRACING RayTracingShader.SafeRelease(); #endif // RHI_RAYTRACING } void FShaderResource::InitializeShaderRHI() { if (!IsInitialized()) { STAT(double ShaderInitializationTime = 0); { SCOPE_CYCLE_COUNTER(STAT_Shaders_FrameRTShaderInitForRenderingTime); SCOPE_SECONDS_COUNTER(ShaderInitializationTime); InitResourceFromPossiblyParallelRendering(); } INC_FLOAT_STAT_BY(STAT_Shaders_TotalRTShaderInitForRenderingTime,(float)ShaderInitializationTime); } checkSlow(IsInitialized()); } FShaderResourceId FShaderResource::GetId() const { return FShaderResourceId(Target, OutputHash, SpecificType ? SpecificType->GetName() : nullptr, SpecificPermutationId); } FShaderId::FShaderId(const FSHAHash& InMaterialShaderMapHash, const FShaderPipelineType* InShaderPipeline, FVertexFactoryType* InVertexFactoryType, FShaderType* InShaderType, int32 InPermutationId, FShaderTarget InTarget) : MaterialShaderMapHash(InMaterialShaderMapHash) #if KEEP_SHADER_SOURCE_HASHES , SourceHash(InShaderType->GetSourceHash(InTarget.GetPlatform())) #endif , Target(InTarget) , ShaderPipeline(InShaderPipeline) , ShaderType(InShaderType) , PermutationId(InPermutationId) , SerializationHistory(InShaderType->GetSerializationHistory()) { if (InVertexFactoryType) { VFSerializationHistory = InVertexFactoryType->GetSerializationHistory(InTarget.GetFrequency()); VertexFactoryType = InVertexFactoryType; #if KEEP_SHADER_SOURCE_HASHES VFSourceHash = InVertexFactoryType->GetSourceHash(InTarget.GetPlatform()); #endif } else { VFSerializationHistory = nullptr; VertexFactoryType = nullptr; } } FSelfContainedShaderId::FSelfContainedShaderId() : Target(FShaderTarget(SF_NumFrequencies, SP_NumPlatforms)) {} FSelfContainedShaderId::FSelfContainedShaderId(const FShaderId& InShaderId) { MaterialShaderMapHash = InShaderId.MaterialShaderMapHash; VertexFactoryTypeName = InShaderId.VertexFactoryType ? InShaderId.VertexFactoryType->GetName() : TEXT(""); ShaderPipelineName = InShaderId.ShaderPipeline ? InShaderId.ShaderPipeline->GetName() : TEXT(""); VFSerializationHistory = InShaderId.VFSerializationHistory ? *InShaderId.VFSerializationHistory : FSerializationHistory(); ShaderTypeName = InShaderId.ShaderType->GetName(); PermutationId = InShaderId.PermutationId; #if KEEP_SHADER_SOURCE_HASHES SourceHash = InShaderId.SourceHash; VFSourceHash = InShaderId.VFSourceHash; #endif SerializationHistory = InShaderId.SerializationHistory; Target = InShaderId.Target; } bool FSelfContainedShaderId::IsValid() { FShaderType** TypePtr = FShaderType::GetNameToTypeMap().Find(FName(*ShaderTypeName)); if (TypePtr #if KEEP_SHADER_SOURCE_HASHES && SourceHash == (*TypePtr)->GetSourceHash(Target.GetPlatform()) #endif && SerializationHistory == (*TypePtr)->GetSerializationHistory()) { FVertexFactoryType* VFTypePtr = FVertexFactoryType::GetVFByName(VertexFactoryTypeName); if (VertexFactoryTypeName == TEXT("") || (VFTypePtr #if KEEP_SHADER_SOURCE_HASHES && VFSourceHash == VFTypePtr->GetSourceHash(Target.GetPlatform()) #endif && VFSerializationHistory == *VFTypePtr->GetSerializationHistory(Target.GetFrequency()))) { return true; } } return false; } FArchive& operator<<(FArchive& Ar,class FSelfContainedShaderId& Ref) { Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); #if KEEP_SHADER_SOURCE_HASHES FSHAHash& VFHash = Ref.VFSourceHash; FSHAHash& Hash = Ref.SourceHash; #else FSHAHash VFHash, Hash; #endif Ar << Ref.MaterialShaderMapHash << Ref.VertexFactoryTypeName << Ref.ShaderPipelineName << FShaderResource::FilterShaderSourceHashForSerialization(Ar, VFHash) << Ref.VFSerializationHistory << Ref.ShaderTypeName << FShaderResource::FilterShaderSourceHashForSerialization(Ar, Hash) << Ref.SerializationHistory << Ref.Target; if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId) { Ar << Ref.PermutationId; } return Ar; } /** * Used to construct a shader for deserialization. * This still needs to initialize members to safe values since FShaderType::GenerateSerializationHistory uses this constructor. */ FShader::FShader() : SerializedResource(nullptr) , ShaderPipeline(nullptr) , VFType(nullptr) , Type(nullptr) , PermutationId(0) , NumRefs(0) { // set to undefined (currently shared with SF_Vertex) Target.Frequency = 0; Target.Platform = GShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel]; } /** * Construct a shader from shader compiler output. */ FShader::FShader(const CompiledShaderInitializerType& Initializer) : MaterialShaderMapHash(Initializer.MaterialShaderMapHash) , SerializedResource(nullptr) , ShaderPipeline(Initializer.ShaderPipeline) , VFType(Initializer.VertexFactoryType) , Type(Initializer.Type) , PermutationId(Initializer.PermutationId) , Target(Initializer.Target) , NumRefs(0) { check(Type); #if KEEP_SHADER_SOURCE_HASHES OutputHash = Initializer.OutputHash; checkSlow(OutputHash != FSHAHash()); // 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 = Type->GetSourceHash(Target.GetPlatform()); if (VFType) { // Store off the VF source hash that this shader was compiled with VFSourceHash = VFType->GetSourceHash(Target.GetPlatform()); } #endif // Bind uniform buffer parameters automatically for (TLinkedList::TIterator StructIt(FShaderParametersMetadata::GetStructList()); StructIt; StructIt.Next()) { if (Initializer.ParameterMap.ContainsParameterAllocation(StructIt->GetShaderVariableName())) { UniformBufferParameterStructs.Add(*StructIt); UniformBufferParameters.Add(new FShaderUniformBufferParameter()); FShaderUniformBufferParameter* Parameter = UniformBufferParameters.Last(); Parameter->Bind(Initializer.ParameterMap, StructIt->GetShaderVariableName(), SPF_Mandatory); } } SetResource(Initializer.Resource); // Register the shader now that it is valid, so that it can be reused Register(false); } FShader::~FShader() { check(NumRefs == 0); for (int32 StructIndex = 0; StructIndex < UniformBufferParameters.Num(); StructIndex++) { delete UniformBufferParameters[StructIndex]; } } const FSHAHash& FShader::GetHash() const { #if KEEP_SHADER_SOURCE_HASHES return SourceHash; #else return ShaderSourceDefaultHash; #endif } EShaderPlatform FShader::GetShaderPlatform() const { return Target.GetPlatform(); } bool FShader::SerializeBase(FArchive& Ar, bool bShadersInline, bool bLoadedByCookedMaterial) { Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); Serialize(Ar); Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); #if KEEP_SHADER_SOURCE_HASHES FSHAHash& VFHash = VFSourceHash; FSHAHash& Hash = SourceHash; #else FSHAHash VFHash, Hash, OutputHash; #endif Ar << OutputHash; Ar << MaterialShaderMapHash; Ar << ShaderPipeline; Ar << VFType; Ar << FShaderResource::FilterShaderSourceHashForSerialization(Ar, VFHash); Ar << Type; if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId) { Ar << PermutationId; } Ar << FShaderResource::FilterShaderSourceHashForSerialization(Ar, Hash); Ar << Target; // TODO(RDG): Kill that once all shaders are refactored. if (Ar.IsLoading()) { int32 NumUniformParameters; Ar << NumUniformParameters; UniformBufferParameterStructs.Empty(NumUniformParameters); UniformBufferParameters.Empty(NumUniformParameters); for (int32 ParameterIndex = 0; ParameterIndex < NumUniformParameters; ParameterIndex++) { FShaderParametersMetadata* Struct = nullptr; if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::MaterialInstanceSerializeOptimization_ShaderFName) { FString StructName; Ar << StructName; Struct = FindUniformBufferStructByName(*StructName); checkf(Struct, TEXT("Uniform Buffer Struct %s no longer exists, which shader of type %s was compiled with. Modify ShaderVersion.ush to invalidate old shaders."), *StructName, Type->GetName()); } else { FName StructFName; Ar << StructFName; Struct = FindUniformBufferStructByFName(StructFName); checkf(Struct, TEXT("Uniform Buffer Struct %s no longer exists, which shader of type %s was compiled with. Modify ShaderVersion.ush to invalidate old shaders."), *StructFName.ToString(), Type->GetName()); } FShaderUniformBufferParameter* Parameter = new FShaderUniformBufferParameter(); Ar << *Parameter; UniformBufferParameterStructs.Add(Struct); UniformBufferParameters.Add(Parameter); } } else { int32 NumUniformParameters = UniformBufferParameters.Num(); Ar << NumUniformParameters; for (int32 StructIndex = 0; StructIndex < UniformBufferParameters.Num(); StructIndex++) { FString StructName(UniformBufferParameterStructs[StructIndex]->GetStructTypeName()); if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::MaterialInstanceSerializeOptimization_ShaderFName) { Ar << StructName; } else { FName StructFName(*StructName); Ar << StructFName; } Ar << *UniformBufferParameters[StructIndex]; } } if (bShadersInline) { // Save the shader resource if we are inlining shaders if (Ar.IsSaving()) { check(Resource->Target == Target); Resource->Serialize(Ar, false); } if (Ar.IsLoading()) { // Load the inlined shader resource SerializedResource = new FShaderResource(); SerializedResource->Serialize(Ar, bLoadedByCookedMaterial); checkSlow(OutputHash == SerializedResource->OutputHash); } } else { // if saving, there's nothing to, the required data is already saved above to look it up at load time if (Ar.IsLoading()) { // generate a resource id FShaderResourceId ResourceId(Target, OutputHash, Type->LimitShaderResourceToThisType() ? Type->GetName() : nullptr, Type->LimitShaderResourceToThisType() ? PermutationId : 0); // use it to look up in the registered resource map FShaderResource* ExistingResource = FShaderResource::FindShaderResourceById(ResourceId); SetResource(ExistingResource); } } Ar << Bindings; return false; } void FShader::AddRef() { ++NumRefs; if (NumRefs == 1) { INC_DWORD_STAT_BY(STAT_Shaders_ShaderMemory, GetSizeBytes()); INC_DWORD_STAT_BY(STAT_Shaders_NumShadersLoaded,1); } } void FShader::Release() { if(--NumRefs == 0) { DEC_DWORD_STAT_BY(STAT_Shaders_ShaderMemory, GetSizeBytes()); DEC_DWORD_STAT_BY(STAT_Shaders_NumShadersLoaded,1); // Deregister the shader now to eliminate references to it by the type's ShaderIdMap Deregister(); BeginCleanup(this); } } void FShader::Register(bool bLoadedByCookedMaterial) { FShaderId ShaderId = GetId(); check(ShaderId.MaterialShaderMapHash != FSHAHash()); #if KEEP_SHADER_SOURCE_HASHES check(ShaderId.SourceHash != FSHAHash() || FPlatformProperties::RequiresCookedData() || bLoadedByCookedMaterial); #endif check(Resource); Type->AddToShaderIdMap(ShaderId, this); } void FShader::Deregister() { Type->RemoveFromShaderIdMap(GetId()); } FShaderId FShader::GetId() const { FShaderId ShaderId(Type->GetSerializationHistory()); ShaderId.MaterialShaderMapHash = MaterialShaderMapHash; ShaderId.ShaderPipeline = ShaderPipeline; ShaderId.VertexFactoryType = VFType; ShaderId.VFSerializationHistory = VFType ? VFType->GetSerializationHistory((EShaderFrequency)GetTarget().Frequency) : NULL; ShaderId.ShaderType = Type; ShaderId.PermutationId = PermutationId; #if KEEP_SHADER_SOURCE_HASHES ShaderId.SourceHash = SourceHash; ShaderId.VFSourceHash = VFSourceHash; #endif ShaderId.Target = Target; return ShaderId; } void FShader::RegisterSerializedResource() { if (SerializedResource) { FShaderResource* ExistingResource = FShaderResource::FindShaderResourceById(SerializedResource->GetId()); // Reuse an existing shader resource if a matching one already exists in memory if (ExistingResource) { delete SerializedResource; SerializedResource = ExistingResource; } else { // Register the newly loaded shader resource so it can be reused by other shaders SerializedResource->Register(); } SetResource(SerializedResource); } } void FShader::SetResource(FShaderResource* InResource) { check(InResource && InResource->Target == Target); Resource = InResource; } void FShader::DumpDebugInfo() { UE_LOG(LogConsoleResponse, Display, TEXT(" FShader :MaterialShaderMapHash %s"), *MaterialShaderMapHash.ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :Target %s"), GetShaderFrequencyString((EShaderFrequency)Target.Frequency)); UE_LOG(LogConsoleResponse, Display, TEXT(" :Target %s"), *LegacyShaderPlatformToShaderFormat(EShaderPlatform(Target.Platform)).ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :VFType %s"), VFType ? VFType->GetName() : TEXT("null")); UE_LOG(LogConsoleResponse, Display, TEXT(" :Type %s"), Type->GetName()); UE_LOG(LogConsoleResponse, Display, TEXT(" :PermutationId %d"), PermutationId); #if KEEP_SHADER_SOURCE_HASHES UE_LOG(LogConsoleResponse, Display, TEXT(" :SourceHash %s"), *SourceHash.ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :VFSourceHash %s"), *VFSourceHash.ToString()); UE_LOG(LogConsoleResponse, Display, TEXT(" :OutputHash %s"), *OutputHash.ToString()); #endif } void FShader::SaveShaderStableKeys(EShaderPlatform TargetShaderPlatform, const FStableShaderKeyAndValue& InSaveKeyVal) { #if WITH_EDITOR if ((TargetShaderPlatform == EShaderPlatform::SP_NumPlatforms || EShaderPlatform(Target.Platform) == TargetShaderPlatform) && FShaderCodeLibrary::NeedsShaderStableKeys(TargetShaderPlatform)) { FStableShaderKeyAndValue SaveKeyVal(InSaveKeyVal); SaveKeyVal.TargetFrequency = FName(GetShaderFrequencyString((EShaderFrequency)Target.Frequency)); SaveKeyVal.TargetPlatform = FName(*LegacyShaderPlatformToShaderFormat(EShaderPlatform(Target.Platform)).ToString()); SaveKeyVal.VFType = FName(VFType ? VFType->GetName() : TEXT("null")); SaveKeyVal.PermutationId = FName(*FString::Printf(TEXT("Perm_%d"), PermutationId)); SaveKeyVal.OutputHash = OutputHash; if (Type) { Type->GetShaderStableKeyParts(SaveKeyVal); } FShaderCodeLibrary::AddShaderStableKeyValue(EShaderPlatform(Target.Platform), SaveKeyVal); } #endif } bool FShaderPipelineType::bInitialized = false; FShaderPipelineType::FShaderPipelineType( const TCHAR* InName, const FShaderType* InVertexShader, const FShaderType* InHullShader, const FShaderType* InDomainShader, const FShaderType* InGeometryShader, const FShaderType* InPixelShader, bool bInShouldOptimizeUnusedOutputs) : Name(InName), TypeName(Name), GlobalListLink(this), bShouldOptimizeUnusedOutputs(bInShouldOptimizeUnusedOutputs) { checkf(Name && *Name, TEXT("Shader Pipeline Type requires a valid Name!")); checkf(InVertexShader, TEXT("A Shader Pipeline always requires a Vertex Shader")); checkf((InHullShader == nullptr && InDomainShader == nullptr) || (InHullShader != nullptr && InDomainShader != nullptr), TEXT("Both Hull & Domain shaders are needed for tessellation on Pipeline %s"), Name); //make sure the name is shorter than the maximum serializable length check(FCString::Strlen(InName) < NAME_SIZE); FMemory::Memzero(AllStages); if (InPixelShader) { Stages.Add(InPixelShader); AllStages[SF_Pixel] = InPixelShader; } if (InGeometryShader) { Stages.Add(InGeometryShader); AllStages[SF_Geometry] = InGeometryShader; } if (InDomainShader) { Stages.Add(InDomainShader); AllStages[SF_Domain] = InDomainShader; Stages.Add(InHullShader); AllStages[SF_Hull] = InHullShader; } Stages.Add(InVertexShader); AllStages[SF_Vertex] = InVertexShader; 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(TypeName, this); // 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(TypeName); GlobalListLink.Unlink(); } TMap& FShaderPipelineType::GetNameToTypeMap() { static TMap* GShaderPipelineNameToTypeMap = NULL; if (!GShaderPipelineNameToTypeMap) { GShaderPipelineNameToTypeMap = new TMap(); } return *GShaderPipelineNameToTypeMap; } TLinkedList*& FShaderPipelineType::GetTypeList() { return GShaderPipelineList; } TArray FShaderPipelineType::GetShaderPipelineTypesByFilename(const TCHAR* Filename) { TArray PipelineTypes; for (TLinkedList::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next()) { auto* PipelineType = *It; for (auto* ShaderType : PipelineType->Stages) { if (FPlatformString::Strcmp(Filename, ShaderType->GetShaderFilename()) == 0) { 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; } void FShaderPipelineType::Uninitialize() { check(bInitialized); bInitialized = false; } void FShaderPipelineType::GetOutdatedTypes(TArray& OutdatedShaderTypes, TArray& OutdatedShaderPipelineTypes, TArray& OutdatedFactoryTypes) { for (TLinkedList::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next()) { const auto* PipelineType = *It; auto& Stages = PipelineType->GetStages(); bool bOutdated = false; for (const FShaderType* ShaderType : Stages) { bOutdated = ShaderType->GetOutdatedCurrentType(OutdatedShaderTypes, OutdatedFactoryTypes) || bOutdated; } if (bOutdated) { OutdatedShaderPipelineTypes.AddUnique(PipelineType); } } for (int32 TypeIndex = 0; TypeIndex < OutdatedShaderPipelineTypes.Num(); TypeIndex++) { UE_LOG(LogShaders, Warning, TEXT(" Recompiling Pipeline %s"), OutdatedShaderPipelineTypes[TypeIndex]->GetName()); } } const FShaderPipelineType* FShaderPipelineType::GetShaderPipelineTypeByName(FName Name) { for (TLinkedList::TIterator It(GetTypeList()); It; It.Next()) { const FShaderPipelineType* Type = *It; if (Name == Type->GetFName()) { return Type; } } return nullptr; } const FSHAHash& FShaderPipelineType::GetSourceHash(EShaderPlatform ShaderPlatform) const { TArray Filenames; for (const FShaderType* ShaderType : Stages) { Filenames.Add(ShaderType->GetShaderFilename()); } return GetShaderFilesHash(Filenames, ShaderPlatform); } FShaderPipeline::FShaderPipeline( const FShaderPipelineType* InPipelineType, FShader* InVertexShader, FShader* InHullShader, FShader* InDomainShader, FShader* InGeometryShader, FShader* InPixelShader) : PipelineType(InPipelineType), VertexShader(InVertexShader), HullShader(InHullShader), DomainShader(InDomainShader), GeometryShader(InGeometryShader), PixelShader(InPixelShader) { check(InPipelineType); Validate(); } FShaderPipeline::FShaderPipeline(const FShaderPipelineType* InPipelineType, const TArray& InStages) : PipelineType(InPipelineType), VertexShader(nullptr), HullShader(nullptr), DomainShader(nullptr), GeometryShader(nullptr), PixelShader(nullptr) { check(InPipelineType); for (FShader* Shader : InStages) { if (Shader) { switch (Shader->GetType()->GetFrequency()) { case SF_Vertex: check(!VertexShader); VertexShader = Shader; break; case SF_Pixel: check(!PixelShader); PixelShader = Shader; break; case SF_Hull: check(!HullShader); HullShader = Shader; break; case SF_Domain: check(!DomainShader); DomainShader = Shader; break; case SF_Geometry: check(!GeometryShader); GeometryShader = Shader; break; default: checkf(0, TEXT("Invalid stage %u found!"), (uint32)Shader->GetType()->GetFrequency()); break; } } } Validate(); } FShaderPipeline::FShaderPipeline(const FShaderPipelineType* InPipelineType, const TArray< TRefCountPtr >& InStages) : PipelineType(InPipelineType), VertexShader(nullptr), HullShader(nullptr), DomainShader(nullptr), GeometryShader(nullptr), PixelShader(nullptr) { check(InPipelineType); for (FShader* Shader : InStages) { if (Shader) { switch (Shader->GetType()->GetFrequency()) { case SF_Vertex: check(!VertexShader); VertexShader = Shader; break; case SF_Pixel: check(!PixelShader); PixelShader = Shader; break; case SF_Hull: check(!HullShader); HullShader = Shader; break; case SF_Domain: check(!DomainShader); DomainShader = Shader; break; case SF_Geometry: check(!GeometryShader); GeometryShader = Shader; break; default: checkf(0, TEXT("Invalid stage %u found!"), (uint32)Shader->GetType()->GetFrequency()); break; } } } Validate(); } FShaderPipeline::~FShaderPipeline() { // Manually set references to nullptr, helps debugging VertexShader = nullptr; HullShader = nullptr; DomainShader = nullptr; GeometryShader = nullptr; PixelShader = nullptr; } void FShaderPipeline::Validate() { for (const FShaderType* Stage : PipelineType->GetStages()) { switch (Stage->GetFrequency()) { case SF_Vertex: check(VertexShader && VertexShader->GetType() == Stage); break; case SF_Pixel: check(PixelShader && PixelShader->GetType() == Stage); break; case SF_Hull: check(HullShader && HullShader->GetType() == Stage); break; case SF_Domain: check(DomainShader && DomainShader->GetType() == Stage); break; case SF_Geometry: check(GeometryShader && GeometryShader->GetType() == Stage); break; default: // Can never happen :) break; } } } void FShaderPipeline::CookPipeline(FShaderPipeline* Pipeline) { #if WITH_EDITOR FShaderCodeLibrary::AddShaderPipeline(Pipeline); #endif } 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; 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(); 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"),TotalShaderCount / (float)TotalTypeCount); ShaderTypeViewer.AddColumn(TEXT("%.1f"),(float)TotalInstructionCount / TotalShaderCount); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalSize / (float)TotalShaderCount); ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalSizePerType / TotalTypeCount); 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(FName ShaderTypeName) { FShaderType** FoundShader = FShaderType::GetNameToTypeMap().Find(ShaderTypeName); if (FoundShader) { return *FoundShader; } return nullptr; } void DispatchComputeShader( FRHICommandList& RHICmdList, FShader* Shader, uint32 ThreadGroupCountX, uint32 ThreadGroupCountY, uint32 ThreadGroupCountZ) { RHICmdList.DispatchComputeShader(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); } void DispatchComputeShader( FRHIAsyncComputeCommandListImmediate& RHICmdList, FShader* Shader, uint32 ThreadGroupCountX, uint32 ThreadGroupCountY, uint32 ThreadGroupCountZ) { RHICmdList.DispatchComputeShader(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); } void DispatchIndirectComputeShader( FRHICommandList& RHICmdList, FShader* Shader, FRHIVertexBuffer* ArgumentBuffer, uint32 ArgumentOffset) { RHICmdList.DispatchIndirectComputeShader(ArgumentBuffer, ArgumentOffset); } void ShaderMapAppendKeyString(EShaderPlatform Platform, FString& KeyString) { // 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")); KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_DXTN") : TEXT("_BC5N"); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ClearCoatNormal")); KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_CCBN") : TEXT("_NoCCBN"); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.IrisNormal")); KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_Iris") : TEXT("_NoIris"); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.CompileShadersForDevelopment")); KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_DEV") : TEXT("_NoDEV"); } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting")); const bool bValue = CVar ? CVar->GetValueOnAnyThread() != 0 : true; KeyString += bValue ? TEXT("_SL") : TEXT("_NoSL"); } { KeyString += IsUsingBasePassVelocity(Platform) ? TEXT("_GV") : TEXT(""); } { static const auto CVarInstancedStereo = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.InstancedStereo")); static const auto CVarMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MultiView")); static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView")); static const auto CVarODSCapture = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.ODSCapture")); const bool bIsInstancedStereo = (RHISupportsInstancedStereo(Platform) && (CVarInstancedStereo && CVarInstancedStereo->GetValueOnGameThread() != 0)); const bool bIsMultiView = (RHISupportsMultiView(Platform) && (CVarMultiView && CVarMultiView->GetValueOnGameThread() != 0)); const bool bIsAndroidGLES = RHISupportsMobileMultiView(Platform); const bool bIsMobileMultiView = (bIsAndroidGLES && (CVarMobileMultiView && CVarMobileMultiView->GetValueOnGameThread() != 0)); const bool bIsODSCapture = CVarODSCapture && (CVarODSCapture->GetValueOnGameThread() != 0); if (bIsInstancedStereo) { KeyString += TEXT("_VRIS"); if (bIsMultiView) { KeyString += TEXT("_MVIEW"); } } if (bIsMobileMultiView) { KeyString += TEXT("_MMVIEW"); } if (bIsODSCapture) { KeyString += TEXT("_ODSC"); } } { KeyString += IsUsingSelectiveBasePassOutputs(Platform) ? TEXT("_SO") : TEXT(""); } { KeyString += UsePreExposure(Platform) ? TEXT("_PreExp") : TEXT(""); } { KeyString += IsUsingDBuffers(Platform) ? TEXT("_DBuf") : TEXT("_NoDBuf"); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AllowGlobalClipPlane")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_ClipP") : TEXT(""); } { KeyString += ShouldKeepShaderDebugInfo(Platform) ? TEXT("_NoStrip") : TEXT(""); } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.Optimize")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("") : TEXT("_NoOpt"); } { // Always default to fast math unless specified static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FastMath")); KeyString += (CVar && CVar->GetInt() == 0) ? TEXT("_NoFastMath") : TEXT(""); } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FlowControlMode")); if (CVar) { switch(CVar->GetInt()) { case 2: KeyString += TEXT("_AvoidFlow"); break; case 1: KeyString += TEXT("_PreferFlow"); break; case 0: default: break; } } } if (!AllowPixelDepthOffset(Platform)) { KeyString += TEXT("_NoPDO"); } if (IsD3DPlatform(Platform, false)) { { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.D3D.RemoveUnusedInterpolators")); if (CVar && CVar->GetInt() != 0) { KeyString += TEXT("_UnInt"); } } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.D3D.ForceDXC")); if (CVar && CVar->GetInt() != 0) { KeyString += TEXT("_DXC"); } } } if (IsMobilePlatform(Platform)) { { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.DisableVertexFog")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_NoVFog") : TEXT(""); } { static const auto* CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shadow.CSM.MaxMobileCascades")); KeyString += (CVar) ? FString::Printf(TEXT("MMC%d"), CVar->GetValueOnAnyThread()) : TEXT(""); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.UseLegacyShadingModel")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_legshad") : TEXT(""); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.ForceFullPrecisionInPS")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_highp") : TEXT(""); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.AllowDitheredLODTransition")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_DLODT") : TEXT(""); } if (IsOpenGLPlatform(Platform)) { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("OpenGL.UseEmulatedUBs")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_NoUB") : TEXT(""); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.EnableMovableSpotlights")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_MSPTL") : TEXT(""); } { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.UseHWsRGBEncoding")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_HWsRGB") : TEXT(""); } { // make it per shader platform ? static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.SupportGPUScene")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_MobGPUSc") : TEXT(""); } } if (Platform == SP_PS4) { { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4MixedModeShaderDebugInfo")); if (CVar && CVar->GetValueOnAnyThread() != 0) { KeyString += TEXT("_MMDBG"); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4ShaderSDBMode")); switch (CVar ? CVar->GetValueOnAnyThread() : 0) { case 1: KeyString += TEXT("_SDB1"); break; case 2: KeyString += TEXT("_SDB2"); break; default: break; } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4UseTTrace")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyString += FString::Printf(TEXT("TT%d"), CVar->GetValueOnAnyThread()); } } } // 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")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_ZeroInit") : TEXT(""); } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.BoundsChecking")); KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_BoundsChecking") : TEXT(""); } { KeyString += RHISupportsManualVertexFetch(Platform) ? TEXT("_MVF_") : TEXT(""); } uint32 ShaderVersion = RHIGetShaderLanguageVersion(Platform); KeyString += FString::Printf(TEXT("_MTLSTD%u_"), ShaderVersion); bool bAllowFastIntrinsics = false; bool bEnableMathOptimisations = true; bool bForceFloats = 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->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("ForceFloats"), bForceFloats, 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); } if (bAllowFastIntrinsics) { KeyString += TEXT("_MTLSL_FastIntrin"); } // Same as console-variable above, but that's global and this is per-platform, per-project if (!bEnableMathOptimisations) { KeyString += TEXT("_NoFastMath"); } if (bForceFloats) { KeyString += TEXT("_FP32"); } // 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) { KeyString += TEXT("_ARCHIVE"); } { static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Metal.ForceDXC")); if (CVar && CVar->GetInt() != 0) { KeyString += TEXT("_DXC"); } } } if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4)) { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.StencilForLODDither")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyString += TEXT("_SD"); } } { bool bForwardShading = false; ITargetPlatform* TargetPlatform = GetTargetPlatformManager()->FindTargetPlatform(ShaderPlatformToPlatformName(Platform).ToString()); 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) { KeyString += TEXT("_FS"); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PostProcessing.PropagateAlpha")); if (CVar && CVar->GetValueOnAnyThread() > 0) { if (CVar->GetValueOnAnyThread() == 2) { KeyString += TEXT("_SA2"); } else { KeyString += TEXT("_SA"); } } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VertexFoggingForOpaque")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyString += TEXT("_VFO"); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.EarlyZPassOnlyMaterialMasking")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyString += TEXT("_EZPMM"); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.EightBit")); if (CVar && CVar->GetValueOnAnyThread() > 0) { KeyString += TEXT("_8u"); } } { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GPUSkin.Limit2BoneInfluences")); if (CVar && CVar->GetValueOnAnyThread() != 0) { KeyString += TEXT("_2bi"); } } { if(UseGPUScene(Platform, GetMaxSupportedFeatureLevel(Platform))) { KeyString += TEXT("_gs1"); } else { KeyString += TEXT("_gs0"); } } { 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")); const bool VTTextures = CVarVirtualTexture && CVarVirtualTexture->GetValueOnAnyThread() != 0; static const auto CVarVTFactor = IConsoleManager::Get().FindConsoleVariable(TEXT("r.vt.FeedbackFactor")); check(CVarVTFactor); const int32 VTFeedbackFactor = CVarVTFactor->GetInt(); ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); check(TPM); auto TargetPlatform = TPM->GetRunningTargetPlatform(); check(TargetPlatform); const bool VTSupported = TargetPlatform->SupportsFeature(ETargetPlatformFeatures::VirtualTextureStreaming); auto tt = FString::Printf(TEXT("_VT-%d-%d-%d-%d"), VTLightmaps, VTTextures, VTFeedbackFactor, VTSupported); KeyString += tt; } }