// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /*============================================================================= ShaderCodeLibrary.cpp: Bound shader state cache implementation. =============================================================================*/ #include "ShaderCodeLibrary.h" #include "Shader.h" #include "Misc/SecureHash.h" #include "Misc/Paths.h" #include "Math/UnitConversion.h" #include "HAL/FileManagerGeneric.h" #include "HAL/LowLevelMemTracker.h" #include "Misc/ScopeLock.h" #include "Misc/ScopeRWLock.h" #include "Async/AsyncFileHandle.h" #include "PipelineFileCache.h" #include "Interfaces/IPluginManager.h" #include "Hash/CityHash.h" #include "Interfaces/IShaderFormatArchive.h" #include "ShaderPipelineCache.h" #include "Misc/FileHelper.h" #include "Misc/ConfigCacheIni.h" #if WITH_EDITORONLY_DATA #include "Modules/ModuleManager.h" #include "Interfaces/IShaderFormat.h" #include "Interfaces/IShaderFormatModule.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #endif // FORT-93125 #define CHECK_SHADER_CREATION (PLATFORM_XBOXONE) DEFINE_LOG_CATEGORY(LogShaderLibrary); static const FName ShaderLibraryCompressionFormat = NAME_Zlib; static uint32 GShaderCodeArchiveVersion = 1; static uint32 GShaderPipelineArchiveVersion = 1; static FString ShaderExtension = TEXT(".ushaderbytecode"); static FString StableExtension = TEXT(".scl.csv"); static FString PipelineExtension = TEXT(".ushaderpipelines"); static FString GetCodeArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) { return BaseDir / FString::Printf(TEXT("ShaderArchive-%s-"), *LibraryName) + Platform.ToString() + ShaderExtension; } static FString GetStableInfoArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) { return BaseDir / FString::Printf(TEXT("ShaderStableInfo-%s-"), *LibraryName) + Platform.ToString() + StableExtension; } static FString GetPipelinesArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) { return BaseDir / FString::Printf(TEXT("ShaderArchive-%s-"), *LibraryName) + Platform.ToString() + PipelineExtension; } static FString GetShaderCodeFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) { return BaseDir / FString::Printf(TEXT("ShaderCode-%s-"), *LibraryName) + Platform.ToString() + ShaderExtension; } static FString GetShaderDebugFolder(const FString& BaseDir, const FString& LibraryName, FName Platform) { return BaseDir / FString::Printf(TEXT("ShaderDebug-%s-"), *LibraryName) + Platform.ToString(); } static TArray& FShaderLibraryHelperUncompressCode(EShaderPlatform Platform, int32 UncompressedSize, TArray& Code, TArray& UncompressedCode) { if (Code.Num() != UncompressedSize) { UncompressedCode.SetNum(UncompressedSize); bool bSucceed = FCompression::UncompressMemory(ShaderLibraryCompressionFormat, UncompressedCode.GetData(), UncompressedSize, Code.GetData(), Code.Num()); check(bSucceed); return UncompressedCode; } else { return Code; } } static void FShaderLibraryHelperCompressCode(EShaderPlatform Platform, const TArray& UncompressedCode, TArray& CompressedCode) { int32 CompressedSize = UncompressedCode.Num() * 4.f / 3.f; CompressedCode.SetNumUninitialized(CompressedSize); // Allocate large enough buffer for compressed code if (FCompression::CompressMemory(ShaderLibraryCompressionFormat, CompressedCode.GetData(), CompressedSize, UncompressedCode.GetData(), UncompressedCode.Num())) { CompressedCode.SetNum(CompressedSize); } else { CompressedCode = UncompressedCode; } CompressedCode.Shrink(); } FORCEINLINE FName ParseFNameCached(const FString& Src, TMap& NameCache) { uint32 SrcHash = CityHash32((char*)Src.GetCharArray().GetData(), Src.GetCharArray().GetAllocatedSize()); if (FName* Name = NameCache.Find(SrcHash)) { return *Name; } else { return NameCache.Emplace(SrcHash, FName(*Src)); } } FString FCompactFullName::ToString() const { FString RetString; RetString.Reserve(256); if (!ObjectClassAndPath.Num()) { RetString += TEXT("empty"); } else { for (int32 NameIdx = 0; NameIdx < ObjectClassAndPath.Num(); NameIdx++) { RetString += ObjectClassAndPath[NameIdx].ToString(); if (NameIdx == 0) { RetString += TEXT(" "); } else if (NameIdx < ObjectClassAndPath.Num() - 1) { if (NameIdx == ObjectClassAndPath.Num() - 2) { RetString += TEXT("."); } else { RetString += TEXT("/"); } } } } return RetString; } void FCompactFullName::ParseFromString(const FString& InSrc) { FString Src = InSrc; Src.ReplaceInline(TEXT("\t"), TEXT(" ")); Src.ReplaceInline(TEXT("."), TEXT(" ")); Src.ReplaceInline(TEXT("/"), TEXT(" ")); TArray Fields; Src.TrimStartAndEnd().ParseIntoArray(Fields, TEXT(" "), true); if (Fields.Num() == 1 && Fields[0] == TEXT("empty")) { Fields.Empty(); } ObjectClassAndPath.Empty(Fields.Num()); for (const FString& Item : Fields) { ObjectClassAndPath.Add(FName(*Item)); } } uint32 GetTypeHash(const FCompactFullName& A) { uint32 Hash = 0; for (const FName& Name : A.ObjectClassAndPath) { Hash = HashCombine(Hash, GetTypeHash(Name)); } return Hash; } void FixupUnsanitizedNames(const FString& Src, TArray& OutFields) { FString NewSrc(Src); int32 ParenOpen = -1; int32 ParenClose = -1; if (NewSrc.FindChar(TCHAR('('), ParenOpen) && NewSrc.FindChar(TCHAR(')'), ParenClose) && ParenOpen < ParenClose && ParenOpen >= 0 && ParenClose >= 0) { for (int32 Index = ParenOpen + 1; Index < ParenClose; Index++) { if (NewSrc[Index] == TCHAR(',')) { NewSrc[Index] = ' '; } } OutFields.Empty(); NewSrc.TrimStartAndEnd().ParseIntoArray(OutFields, TEXT(","), false); check(OutFields.Num() == 11); } } void FStableShaderKeyAndValue::ComputeKeyHash() { KeyHash = GetTypeHash(ClassNameAndObjectPath); KeyHash = HashCombine(KeyHash, GetTypeHash(ShaderType)); KeyHash = HashCombine(KeyHash, GetTypeHash(ShaderClass)); KeyHash = HashCombine(KeyHash, GetTypeHash(MaterialDomain)); KeyHash = HashCombine(KeyHash, GetTypeHash(FeatureLevel)); KeyHash = HashCombine(KeyHash, GetTypeHash(QualityLevel)); KeyHash = HashCombine(KeyHash, GetTypeHash(TargetFrequency)); KeyHash = HashCombine(KeyHash, GetTypeHash(TargetPlatform)); KeyHash = HashCombine(KeyHash, GetTypeHash(VFType)); KeyHash = HashCombine(KeyHash, GetTypeHash(PermutationId)); } void FStableShaderKeyAndValue::ParseFromString(const FString& Src) { TArray Fields; Src.TrimStartAndEnd().ParseIntoArray(Fields, TEXT(","), false); if (Fields.Num() > 11) { // hack fix for unsanitized names, should not occur anymore. FixupUnsanitizedNames(Src, Fields); } check(Fields.Num() == 11); int32 Index = 0; ClassNameAndObjectPath.ParseFromString(Fields[Index++]); ShaderType = FName(*Fields[Index++]); ShaderClass = FName(*Fields[Index++]); MaterialDomain = FName(*Fields[Index++]); FeatureLevel = FName(*Fields[Index++]); QualityLevel = FName(*Fields[Index++]); TargetFrequency = FName(*Fields[Index++]); TargetPlatform = FName(*Fields[Index++]); VFType = FName(*Fields[Index++]); PermutationId = FName(*Fields[Index++]); OutputHash.FromString(Fields[Index++]); check(Index == 11); ComputeKeyHash(); } void FStableShaderKeyAndValue::ParseFromStringCached(const FString& Src, TMap& NameCache) { TArray Fields; Src.TrimStartAndEnd().ParseIntoArray(Fields, TEXT(","), false); if (Fields.Num() > 11) { // hack fix for unsanitized names, should not occur anymore. FixupUnsanitizedNames(Src, Fields); } check(Fields.Num() == 11); int32 Index = 0; ClassNameAndObjectPath.ParseFromString(Fields[Index++]); // There is a high level of uniformity on the following FNames, use // the local name cache to accelerate lookup ShaderType = ParseFNameCached(Fields[Index++], NameCache); ShaderClass = ParseFNameCached(Fields[Index++], NameCache); MaterialDomain = ParseFNameCached(Fields[Index++], NameCache); FeatureLevel = ParseFNameCached(Fields[Index++], NameCache); QualityLevel = ParseFNameCached(Fields[Index++], NameCache); TargetFrequency = ParseFNameCached(Fields[Index++], NameCache); TargetPlatform = ParseFNameCached(Fields[Index++], NameCache); VFType = ParseFNameCached(Fields[Index++], NameCache); PermutationId = ParseFNameCached(Fields[Index++], NameCache); OutputHash.FromString(Fields[Index++]); check(Index == 11); ComputeKeyHash(); } FString FStableShaderKeyAndValue::ToString() const { FString Result; ToString(Result); return Result; } void FStableShaderKeyAndValue::ToString(FString& OutResult) const { const TCHAR* Delim = TEXT(","); OutResult.Reset(255); OutResult += ClassNameAndObjectPath.ToString().Replace(Delim, TEXT(" ")); OutResult += Delim; OutResult += ShaderType.ToString().Replace(Delim, TEXT(" ")); OutResult += Delim; OutResult += ShaderClass.ToString().Replace(Delim, TEXT(" ")); OutResult += Delim; OutResult += MaterialDomain.ToString(); OutResult += Delim; OutResult += FeatureLevel.ToString(); OutResult += Delim; OutResult += QualityLevel.ToString(); OutResult += Delim; OutResult += TargetFrequency.ToString(); OutResult += Delim; OutResult += TargetPlatform.ToString(); OutResult += Delim; OutResult += VFType.ToString(); OutResult += Delim; OutResult += PermutationId.ToString(); OutResult += Delim; OutResult += OutputHash.ToString(); } FString FStableShaderKeyAndValue::HeaderLine() { FString Result; const FString Delim(TEXT(",")); Result += TEXT("ClassNameAndObjectPath"); Result += Delim; Result += TEXT("ShaderType"); Result += Delim; Result += TEXT("ShaderClass"); Result += Delim; Result += TEXT("MaterialDomain"); Result += Delim; Result += TEXT("FeatureLevel"); Result += Delim; Result += TEXT("QualityLevel"); Result += Delim; Result += TEXT("TargetFrequency"); Result += Delim; Result += TEXT("TargetPlatform"); Result += Delim; Result += TEXT("VFType"); Result += Delim; Result += TEXT("Permutation"); Result += Delim; Result += TEXT("OutputHash"); return Result; } struct FShaderCodeEntry { // Serialized uint32 Size; uint64 Offset; uint32 UncompressedSize; uint8 Frequency; // Transient TArray LoadedCode; int32 NumRefs; TWeakPtr ReadRequest; #if DO_CHECK volatile int32 bReadCompleted; #endif FShaderCodeEntry() : Size(0) , Offset(0) , UncompressedSize(0) , Frequency(0) , NumRefs(0) #if DO_CHECK , bReadCompleted(0) #endif {} }; static FArchive& operator <<(FArchive& Ar, FShaderCodeEntry& Ref) { return Ar << Ref.Offset << Ref.Size << Ref.UncompressedSize << Ref.Frequency; } class FShaderCodeArchive final : public FShaderFactoryInterface { public: FShaderCodeArchive(EShaderPlatform InPlatform, const FString& InLibraryDir, const FString& InLibraryName) : FShaderFactoryInterface(InPlatform, InLibraryName) , LibraryDir(InLibraryDir) , LibraryCodeOffset(0) , LibraryAsyncFileHandle(nullptr) { FName PlatformName = LegacyShaderPlatformToShaderFormat(InPlatform); FString DestFilePath = GetCodeArchiveFilename(LibraryDir, LibraryName, PlatformName); FArchive* Ar = IFileManager::Get().CreateFileReader(*DestFilePath); if (Ar) { uint32 Version = 0; *Ar << Version; if (Version == GShaderCodeArchiveVersion) { *Ar << Shaders; LibraryCodeOffset = Ar->Tell(); } Ar->Close(); delete Ar; // Open library for async reads LibraryAsyncFileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenAsyncRead(*DestFilePath); UE_LOG(LogShaderLibrary, Display, TEXT("Using %s for material shader code. Total %d unique shaders."), *DestFilePath, Shaders.Num()); } } virtual ~FShaderCodeArchive() { } virtual bool IsLibraryNativeFormat() const { return false; } TArray* LookupShaderCode(const FSHAHash& Hash, int32& OutSize, bool& bWasSynchronous) { FShaderCodeEntry* Entry = Shaders.Find(Hash); bWasSynchronous = false; if (Entry) { FScopeLock ScopeLock(&ReadRequestLock); check(Entry->NumRefs >= 0); if (Entry->NumRefs == 0 && Entry->LoadedCode.Num() == 0) { static FThreadSafeCounter SyncCount; SyncCount.Increment(); // Someone has asked for a shader without previously invoking RequestEntry, we cannot afford to crash because this happens all too frequently. double StartTime = FPlatformTime::Seconds(); bool bFound = RequestEntryInternal(Hash, nullptr, true); check(bFound); float ThisTimeMS = (FPlatformTime::Seconds() - StartTime) * 1000.0; UE_LOG(LogShaderLibrary, Warning, TEXT("Took %6.2fms (%d total sync shader loads) to synchronously load shader %s from library: %s"), ThisTimeMS, SyncCount.GetValue(), *Hash.ToString(), *GetName()); bWasSynchronous = bFound; check(Entry->NumRefs > 0); check(Entry->LoadedCode.Num() != 0); check(Entry->bReadCompleted == 1); } check(Entry->NumRefs > 0); check(Entry->LoadedCode.Num() != 0); check(Entry->bReadCompleted == 1); OutSize = Entry->UncompressedSize; return &Entry->LoadedCode; } return nullptr; } virtual bool ContainsEntry(const FSHAHash& Hash) final override { FShaderCodeEntry* Entry = Shaders.Find(Hash); return (Entry != nullptr); } virtual bool RequestEntry(const FSHAHash& Hash, FArchive* Ar) final override { return RequestEntryInternal(Hash, Ar, false); } virtual bool RequestEntry(const FSHAHash& Hash, TArray& OutRaw) final override { int32 Size = -1; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, OutRaw); if (bWasSync) { ReleaseShaderCode(Hash); } return true; } return false; } bool RequestEntryInternal(const FSHAHash& Hash, FArchive* Ar, bool bHiPriSync) { FShaderCodeEntry* Entry = Shaders.Find(Hash); if (Entry) { FScopeLock ScopeLock(&ReadRequestLock); int32 CodeNumRefs = Entry->NumRefs++; TSharedPtr LocalReadRequest = Entry->ReadRequest.Pin(); bool bHasReadRequest = LocalReadRequest.IsValid(); if (CodeNumRefs == 0 && !bHasReadRequest) { // should not have allocated mem for code if there is no active read request check(Entry->LoadedCode.Num() == 0); int64 ReadSize = Entry->Size; int64 ReadOffset = LibraryCodeOffset + Entry->Offset; Entry->LoadedCode.SetNumUninitialized(ReadSize); LocalReadRequest = MakeShareable(LibraryAsyncFileHandle->ReadRequest(ReadOffset, ReadSize, bHiPriSync ? AIOP_CriticalPath : AIOP_Normal, nullptr, Entry->LoadedCode.GetData())); Entry->ReadRequest = LocalReadRequest; bHasReadRequest = true; } if (bHasReadRequest) { FExternalReadCallback ExternalReadCallback = [this, Entry, LocalReadRequest](double ReaminingTime) { return this->OnExternalReadCallback(LocalReadRequest, Entry, ReaminingTime); }; if (!Ar || !Ar->AttachExternalReadDependency(ExternalReadCallback)) { // Archive does not support async loading // do a blocking load ExternalReadCallback(0.0); // Should be loaded now check(Entry->LoadedCode.Num() != 0); check(Entry->bReadCompleted == 1); } } else { // already loaded check(Entry->LoadedCode.Num() != 0); check(Entry->bReadCompleted == 1); } return true; } return false; } bool OnExternalReadCallback(const TSharedPtr& AsyncReadRequest, FShaderCodeEntry* Entry, double RemainingTime) { if (RemainingTime < 0.0 && !AsyncReadRequest->PollCompletion()) { return false; } else if (RemainingTime >= 0.0 && !AsyncReadRequest->WaitCompletion(RemainingTime)) { return false; } #if DO_CHECK Entry->bReadCompleted = 1; #endif return true; } void ReleaseShaderCode(const FSHAHash& Hash) { FShaderCodeEntry* Entry = Shaders.Find(Hash); if (Entry) { FScopeLock ScopeLock(&ReadRequestLock); Entry->NumRefs--; if (Entry->NumRefs == 0) { // should not attempt to release shader code while it's loading check(Entry->ReadRequest.IsValid() == false); Entry->LoadedCode.Empty(); #if DO_CHECK Entry->bReadCompleted = 0; #endif } } } FPixelShaderRHIRef CreatePixelShader(const FSHAHash& Hash) override final { FPixelShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreatePixelShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FVertexShaderRHIRef CreateVertexShader(const FSHAHash& Hash) override final { FVertexShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateVertexShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FHullShaderRHIRef CreateHullShader(const FSHAHash& Hash) override final { FHullShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateHullShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FDomainShaderRHIRef CreateDomainShader(const FSHAHash& Hash) override final { FDomainShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateDomainShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FGeometryShaderRHIRef CreateGeometryShader(const FSHAHash& Hash) override final { FGeometryShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateGeometryShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FGeometryShaderRHIRef CreateGeometryShaderWithStreamOutput(const FSHAHash& Hash, const FStreamOutElementList& ElementList, uint32 NumStrides, const uint32* Strides, int32 RasterizedStream) override final { FGeometryShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateGeometryShaderWithStreamOutput(UncompressedCode, ElementList, NumStrides, Strides, RasterizedStream); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } FComputeShaderRHIRef CreateComputeShader(const FSHAHash& Hash) override final { FComputeShaderRHIRef Shader; int32 Size = 0; bool bWasSync = false; TArray* Code = LookupShaderCode(Hash, Size, bWasSync); if (Code) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, *Code, UCode); Shader = RHICreateComputeShader(UncompressedCode); CheckShaderCreation(Shader.GetReference(), Hash); if (bWasSync) { ReleaseShaderCode(Hash); } } return Shader; } class FShaderCodeLibraryIterator : public FRHIShaderLibrary::FShaderLibraryIterator { public: FShaderCodeLibraryIterator(FShaderCodeArchive* Owner, EShaderPlatform Plat, TMap::TIterator It) : FRHIShaderLibrary::FShaderLibraryIterator(Owner) , Platform(Plat) , IteratorImpl(It) {} virtual bool IsValid() const final override { return !!IteratorImpl; } virtual FRHIShaderLibrary::FShaderLibraryEntry operator*() const final override { FRHIShaderLibrary::FShaderLibraryEntry Entry; TPair const& Pair = *IteratorImpl; Entry.Hash = Pair.Key; Entry.Frequency = (EShaderFrequency)Pair.Value.Frequency; Entry.Platform = Platform; return Entry; } virtual FRHIShaderLibrary::FShaderLibraryIterator& operator++() final override { ++IteratorImpl; return *this; } private: EShaderPlatform Platform; TMap::TIterator IteratorImpl; }; virtual TRefCountPtr CreateIterator(void) override final { return new FShaderCodeLibraryIterator(this, Platform, Shaders.CreateIterator()); } TSet const* GetShaderPipelines(EShaderPlatform InPlatform) { if (Pipelines.Num() == 0) { FName PlatformName = LegacyShaderPlatformToShaderFormat(InPlatform); FString DestFilePath = GetPipelinesArchiveFilename(LibraryDir, LibraryName, PlatformName); FArchive* Ar = IFileManager::Get().CreateFileReader(*DestFilePath); if (Ar) { uint32 Version = 0; *Ar << Version; if (Version == GShaderPipelineArchiveVersion) { *Ar << Pipelines; } Ar->Close(); delete Ar; } } return &Pipelines; } virtual uint32 GetShaderCount(void) const override final { return Shaders.Num(); } private: // Library directory FString LibraryDir; // Offset at where shader code starts in a code library int64 LibraryCodeOffset; // Library file handle for async reads IAsyncReadFileHandle* LibraryAsyncFileHandle; FCriticalSection ReadRequestLock; // The shader code present in the library TMap Shaders; // De-serialised pipeline map TSet Pipelines; FORCENOINLINE void CheckShaderCreation(void* ShaderPtr, const FSHAHash& Hash) { #if CHECK_SHADER_CREATION if (!ShaderPtr) { FSHAHash DebugCopy; FMemory::Memcpy(DebugCopy.Hash, Hash.Hash, sizeof(Hash.Hash)); UE_LOG(LogShaderLibrary, Fatal, TEXT("Failed to create shader %s, %s, %s"), *DebugCopy.ToString(), *LibraryName, *LibraryDir); } #endif } }; #if WITH_EDITOR struct FEditorShaderCodeArchive { FEditorShaderCodeArchive(FName InFormat) : FormatName(InFormat) , Format(nullptr) { Format = GetTargetPlatformManagerRef().FindShaderFormat(InFormat); check(Format); } ~FEditorShaderCodeArchive() {} const IShaderFormat* GetFormat() const { return Format; } void OpenLibrary(FString const& Name) { check(LibraryName.Len() == 0); check(Name.Len() > 0); LibraryName = Name; Offset = 0; Shaders.Empty(); Pipelines.Empty(); } void CloseLibrary(FString const& Name) { check(LibraryName == Name); LibraryName = TEXT(""); } bool HasShader(const FSHAHash& Hash) const { return Shaders.Contains(Hash); } bool AddShader(uint8 Frequency, const FSHAHash& Hash, TArray const& InCode, int32 const UncompressedSize) { bool bAdd = false; if (!Shaders.Contains(Hash)) { #if DO_CHECK uint8 Count = 0; for (uint8 i : InCode) { Count |= i; } check(Count > 0); #endif FShaderCodeEntry Entry; Entry.Size = InCode.Num(); Entry.Offset = Offset; Entry.UncompressedSize = UncompressedSize; Entry.Frequency = Frequency; Entry.LoadedCode = InCode; Offset += Entry.Size; Shaders.Add(Hash, Entry); bAdd = true; } return bAdd; } bool AddPipeline(FShaderPipeline* Pipeline) { check(LibraryName.Len() != 0); EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(FormatName); FShaderCodeLibraryPipeline LibraryPipeline; if (IsValidRef(Pipeline->VertexShader)) { LibraryPipeline.VertexShader = Pipeline->VertexShader->GetOutputHash(); } if (IsValidRef(Pipeline->GeometryShader)) { LibraryPipeline.GeometryShader = Pipeline->GeometryShader->GetOutputHash(); } if (IsValidRef(Pipeline->HullShader)) { LibraryPipeline.HullShader = Pipeline->HullShader->GetOutputHash(); } if (IsValidRef(Pipeline->DomainShader)) { LibraryPipeline.DomainShader = Pipeline->DomainShader->GetOutputHash(); } if (IsValidRef(Pipeline->PixelShader)) { LibraryPipeline.PixelShader = Pipeline->PixelShader->GetOutputHash(); } if (!Pipelines.Contains(LibraryPipeline)) { Pipelines.Add(LibraryPipeline); return true; } return false; } void AddExistingShaderCodeLibrary(FString const& OutputDir) { check(LibraryName.Len() > 0); const FString ShaderIntermediateLocation = FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(); TArray ShaderFiles; IFileManager::Get().FindFiles(ShaderFiles, *ShaderIntermediateLocation, *ShaderExtension); for (const FString& ShaderFileName : ShaderFiles) { if (ShaderFileName.Contains(LibraryName + TEXT("-") + FormatName.ToString() + TEXT("."))) { FArchive* PrevCookedAr = IFileManager::Get().CreateFileReader(*GetCodeArchiveFilename(OutputDir, LibraryName, FormatName)); if (PrevCookedAr) { uint32 Version = 0; *PrevCookedAr << Version; if (Version == GShaderCodeArchiveVersion) { TMap PrevCookedShaders; *PrevCookedAr << PrevCookedShaders; int64 PrevCookedShadersCodeStart = PrevCookedAr->Tell(); for (TMap::TIterator It(PrevCookedShaders); It; ++It) { FSHAHash& Hash = It.Key(); if (!Shaders.Contains(Hash)) { // Shader not in list - lazy load shader code FShaderCodeEntry& CodeEntry = It.Value(); int64 ReadSize = CodeEntry.Size; int64 ReadOffset = PrevCookedShadersCodeStart + CodeEntry.Offset; CodeEntry.LoadedCode.SetNumUninitialized(ReadSize); // Read shader code from archive and add shader to set PrevCookedAr->Seek(ReadOffset); PrevCookedAr->Serialize(CodeEntry.LoadedCode.GetData(), ReadSize); AddShader(CodeEntry.Frequency, Hash, CodeEntry.LoadedCode, CodeEntry.UncompressedSize); } } } PrevCookedAr->Close(); delete PrevCookedAr; } } } TArray PipelineFiles; IFileManager::Get().FindFiles(PipelineFiles, *ShaderIntermediateLocation, *PipelineExtension); for (const FString& ShaderFileName : PipelineFiles) { if (ShaderFileName.Contains(LibraryName + TEXT("-") + FormatName.ToString() + TEXT("."))) { FArchive* PrevCookedAr = IFileManager::Get().CreateFileReader(*GetPipelinesArchiveFilename(OutputDir, LibraryName, FormatName)); if (PrevCookedAr) { uint32 Version = 0; *PrevCookedAr << Version; if (Version == GShaderPipelineArchiveVersion) { TSet PrevCookedPipelines; *PrevCookedAr << PrevCookedPipelines; int64 PrevCookedShadersCodeStart = PrevCookedAr->Tell(); Pipelines.Append(PrevCookedPipelines); } PrevCookedAr->Close(); delete PrevCookedAr; } } } } bool Finalize(FString OutputDir, bool bNativeFormat, bool bMasterCooker) { check(LibraryName.Len() > 0); if (bMasterCooker) { AddExistingShaderCodeLibrary(OutputDir); } bool bSuccess = IFileManager::Get().MakeDirectory(*OutputDir, true); EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(FormatName); // Shader library if (bSuccess && Shaders.Num() > 0) { // Write to a intermediate file FString IntermediateFormatPath = GetShaderCodeFilename(FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(), LibraryName, FormatName); FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*IntermediateFormatPath, FILEWRITE_NoFail); if (FileWriter) { check(Format); *FileWriter << GShaderCodeArchiveVersion; if (Format->CanStripShaderCode(bNativeFormat) && !bNativeFormat) { FString DebugPlatformDir = GetShaderDebugFolder(FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(), LibraryName, FormatName); IFileManager::Get().MakeDirectory(*DebugPlatformDir, true); TMap StrippedShaders; uint64 TotalSize = 0; for (const auto& Pair : Shaders) { TArray CompressedCode; CompressedCode.Append(Pair.Value.LoadedCode.GetData(), Pair.Value.Size); int32 UncompressedSize = Pair.Value.UncompressedSize; TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, UncompressedSize, CompressedCode, UCode); if (!Format->StripShaderCode(UncompressedCode, DebugPlatformDir, bNativeFormat)) { bSuccess = false; } FShaderLibraryHelperCompressCode(Platform, UncompressedCode, CompressedCode); FShaderCodeEntry StrippedEntry; StrippedEntry.Size = CompressedCode.Num(); StrippedEntry.Offset = TotalSize; StrippedEntry.UncompressedSize = UncompressedCode.Num(); StrippedEntry.Frequency = Pair.Value.Frequency; StrippedEntry.LoadedCode = CompressedCode; TotalSize += StrippedEntry.Size; StrippedShaders.Add(Pair.Key, StrippedEntry); } // Write stripped shader library *FileWriter << StrippedShaders; for (auto& Pair : StrippedShaders) { FileWriter->Serialize(Pair.Value.LoadedCode.GetData(), Pair.Value.Size); } // Delete the temp dir IFileManager::Get().DeleteDirectory(*DebugPlatformDir, false, true); } else { // Write shader library *FileWriter << Shaders; for (auto& Pair : Shaders) { FileWriter->Serialize(Pair.Value.LoadedCode.GetData(), Pair.Value.Size); } } FileWriter->Close(); delete FileWriter; // Only the master cooker needs to write to the output directory, child cookers only write to the Saved directory if (bMasterCooker) { FString OutputFilePath = GetCodeArchiveFilename(OutputDir, LibraryName, FormatName); // Copy to output location - support for iterative native library cooking uint32 Result = IFileManager::Get().Copy(*OutputFilePath, *IntermediateFormatPath, true, true); if (Result != COPY_OK) { UE_LOG(LogShaderLibrary, Error, TEXT("FEditorShaderCodeArchive shader library copy failed to %s. Failed to finalize Shared Shader Library %s with format %s"), *OutputFilePath, *LibraryName, *FormatName.ToString()); bSuccess = false; } } } } // Pipelines if (bSuccess && Pipelines.Num() > 0) { // Write to a temporary file FString TempFilePath = GetPipelinesArchiveFilename(FPaths::ProjectSavedDir() / TEXT("Shaders"), LibraryName, FormatName); FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*TempFilePath, FILEWRITE_NoFail); *FileWriter << GShaderPipelineArchiveVersion; *FileWriter << Pipelines; FileWriter->Close(); delete FileWriter; // Only the master cooker needs to write to the output directory, child cookers only write to the Saved directory if (bMasterCooker) { FString OutputFilePath = GetPipelinesArchiveFilename(OutputDir, LibraryName, FormatName); // Copy to output location - support for iterative native library cooking uint32 Result = IFileManager::Get().Copy(*OutputFilePath, *TempFilePath, true, true); if (Result != COPY_OK) { UE_LOG(LogShaderLibrary, Error, TEXT("FEditorShaderCodeArchive pipeline copy failed to %s. Failed to finalize Shared Shader Library %s with format %s"), *OutputFilePath, *LibraryName, *FormatName.ToString()); bSuccess = false; } } } return bSuccess; } bool PackageNativeShaderLibrary(const FString& ShaderCodeDir) { if (Shaders.Num() == 0) { return true; } bool bOK = false; FString IntermediateFormatPath = GetShaderDebugFolder(FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(), LibraryName, FormatName); FString TempPath = IntermediateFormatPath / TEXT("NativeLibrary"); IFileManager::Get().MakeDirectory(*TempPath, true); IFileManager::Get().MakeDirectory(*ShaderCodeDir, true); EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(FormatName); IShaderFormatArchive* Archive = Format->CreateShaderArchive(LibraryName, FormatName, TempPath); if (Archive) { bOK = true; // Add the shaders to the archive. for (auto& Pair : Shaders) { FSHAHash& Hash = Pair.Key; FShaderCodeEntry& Entry = Pair.Value; TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Entry.UncompressedSize, Entry.LoadedCode, UCode); if (Format->CanStripShaderCode(true)) { if (!Format->StripShaderCode(UncompressedCode, IntermediateFormatPath, true)) { bOK = false; break; } } if (!Archive->AddShader(Entry.Frequency, Hash, UncompressedCode)) { bOK = false; break; } } if (bOK) { bOK = Archive->Finalize(ShaderCodeDir, IntermediateFormatPath, nullptr); // Delete Shader code library / pipelines as we now have native versions { FString OutputFilePath = GetCodeArchiveFilename(ShaderCodeDir, LibraryName, FormatName); IFileManager::Get().Delete(*OutputFilePath); } { FString OutputFilePath = GetPipelinesArchiveFilename(ShaderCodeDir, LibraryName, FormatName); IFileManager::Get().Delete(*OutputFilePath); } } } // Clean up the saved directory of temporary files IFileManager::Get().DeleteDirectory(*IntermediateFormatPath, false, true); IFileManager::Get().DeleteDirectory(*TempPath, false, true); return bOK; } private: FName FormatName; FString LibraryName; TMap Shaders; TSet Pipelines; uint64 Offset; const IShaderFormat* Format; }; struct FEditorShaderStableInfo { FEditorShaderStableInfo(FName InFormat) : FormatName(InFormat) { } void OpenLibrary(FString const& Name) { check(LibraryName.Len() == 0); check(Name.Len() > 0); LibraryName = Name; Offset = 0; StableMap.Empty(); } void CloseLibrary(FString const& Name) { check(LibraryName == Name); LibraryName = TEXT(""); } void AddShader(FStableShaderKeyAndValue& StableKeyValue) { FStableShaderKeyAndValue* Existing = StableMap.Find(StableKeyValue); if (Existing && Existing->OutputHash != StableKeyValue.OutputHash) { UE_LOG(LogShaderLibrary, Warning, TEXT("Duplicate key in stable shader library, but different keys, skipping new item:")); UE_LOG(LogShaderLibrary, Warning, TEXT(" Existing: %s"), *Existing->ToString()); UE_LOG(LogShaderLibrary, Warning, TEXT(" New : %s"), *StableKeyValue.ToString()); return; } StableMap.Add(StableKeyValue); } void AddExistingShaderCodeLibrary(FString const& OutputDir) { check(LibraryName.Len() > 0); TMap NameCache; NameCache.Reserve(2048); const FString ShaderIntermediateLocation = FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(); TArray ShaderFiles; IFileManager::Get().FindFiles(ShaderFiles, *ShaderIntermediateLocation, *ShaderExtension); for (const FString& ShaderFileName : ShaderFiles) { if (ShaderFileName.Contains(LibraryName + TEXT("-") + FormatName.ToString() + TEXT("."))) { TArray SourceFileContents; if (FFileHelper::LoadFileToStringArray(SourceFileContents, *GetStableInfoArchiveFilename(OutputDir, LibraryName, FormatName))) { for (int32 Index = 1; Index < SourceFileContents.Num(); Index++) { FStableShaderKeyAndValue Item; Item.ParseFromStringCached(SourceFileContents[Index], NameCache); AddShader(Item); } } } } } bool Finalize(FString OutputDir, bool bNativeFormat, bool bMasterCooker, FString& OutSCLCSVPath) { check(LibraryName.Len() > 0); OutSCLCSVPath = FString(); if (bMasterCooker) { AddExistingShaderCodeLibrary(OutputDir); } bool bSuccess = IFileManager::Get().MakeDirectory(*OutputDir, true); EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(FormatName); // Shader library if (bSuccess && StableMap.Num() > 0) { // Write to a intermediate file FString IntermediateFormatPath = GetStableInfoArchiveFilename(FPaths::ProjectSavedDir() / TEXT("Shaders") / FormatName.ToString(), LibraryName, FormatName); // Write directly to the file { TUniquePtr IntermediateFormatAr(IFileManager::Get().CreateFileWriter(*IntermediateFormatPath)); const FString HeaderText = FStableShaderKeyAndValue::HeaderLine(); auto HeaderSrc = StringCast(*HeaderText, HeaderText.Len()); IntermediateFormatAr->Serialize((ANSICHAR*)HeaderSrc.Get(), HeaderSrc.Length() * sizeof(ANSICHAR)); FString LineBuffer; LineBuffer.Reserve(512); for (const FStableShaderKeyAndValue& Item : StableMap) { Item.ToString(LineBuffer); auto LineConverted = StringCast(*LineBuffer, LineBuffer.Len()); IntermediateFormatAr->Serialize((ANSICHAR*)LineConverted.Get(), LineConverted.Length() * sizeof(ANSICHAR)); } } // Only the master cooker needs to write to the output directory, child cookers only write to the Saved directory if (bMasterCooker) { FString OutputFilePath = GetStableInfoArchiveFilename(OutputDir, LibraryName, FormatName); // Copy to output location - support for iterative native library cooking uint32 Result = IFileManager::Get().Copy(*OutputFilePath, *IntermediateFormatPath, true, true); if (Result == COPY_OK) { OutSCLCSVPath = OutputFilePath; } else { UE_LOG(LogShaderLibrary, Error, TEXT("FEditorShaderStableInfo copy failed to %s. Failed to finalize Shared Shader Library %s with format %s"), *OutputFilePath, *LibraryName, *FormatName.ToString()); bSuccess = false; } } } return bSuccess; } private: FName FormatName; FString LibraryName; TSet StableMap; uint64 Offset; }; struct FShaderCodeStats { int64 ShadersSize; int64 ShadersUniqueSize; int32 NumShaders; int32 NumUniqueShaders; int32 NumPipelines; int32 NumUniquePipelines; }; #endif //WITH_EDITOR class FShaderCodeLibraryImpl { // At runtime, shader code collection for current shader platform TArray ShaderCodeArchiveStack; TSet Pipelines; EShaderPlatform ShaderPlatform; uint64 ShaderCount; FRWLock LibraryMutex; #if WITH_EDITOR FCriticalSection ShaderCodeCS; // At cook time, shader code collection for each shader platform FEditorShaderCodeArchive* EditorShaderCodeArchive[EShaderPlatform::SP_NumPlatforms]; // At cook time, shader code collection for each shader platform FEditorShaderStableInfo* EditorShaderStableInfo[EShaderPlatform::SP_NumPlatforms]; // At cook time, shader stats for each shader platform FShaderCodeStats EditorShaderCodeStats[EShaderPlatform::SP_NumPlatforms]; // At cook time, whether the shader archive supports pipelines (only OpenGL should) bool EditorArchivePipelines[EShaderPlatform::SP_NumPlatforms]; #endif //WITH_EDITOR bool bSupportsPipelines; bool bNativeFormat; class FShaderCodeLibraryIterator : public FRHIShaderLibrary::FShaderLibraryIterator { public: FShaderCodeLibraryIterator(TArray& Stack, FRWLock& InLibraryMutex) : FShaderLibraryIterator(nullptr) , LibraryMutex(InLibraryMutex, SLT_ReadOnly) , IteratorImpl(Stack.CreateIterator()) { if (Stack.Num() > 0) { Current = (*IteratorImpl)->CreateIterator(); ShaderLibrarySource = *IteratorImpl; } } virtual bool IsValid() const final override { return IsValidRef(Current) && Current->IsValid(); } virtual FRHIShaderLibrary::FShaderLibraryEntry operator*() const final override { check(IsValid()); return *(*Current); } virtual FShaderLibraryIterator& operator++() final override { ++(*Current); if (!Current->IsValid()) { ++IteratorImpl; if (!!IteratorImpl) { Current = (*IteratorImpl)->CreateIterator(); ShaderLibrarySource = *IteratorImpl; } } return *this; } private: FRWScopeLock LibraryMutex; TArray::TIterator IteratorImpl; TRefCountPtr Current; }; public: static FShaderCodeLibraryImpl* Impl; FShaderCodeLibraryImpl(bool bInNativeFormat) : ShaderPlatform(SP_NumPlatforms) , ShaderCount(0) , bSupportsPipelines(false) , bNativeFormat(bInNativeFormat) { #if WITH_EDITOR FMemory::Memzero(EditorShaderCodeArchive); FMemory::Memzero(EditorShaderStableInfo); FMemory::Memzero(EditorShaderCodeStats); FMemory::Memzero(EditorArchivePipelines); #endif } ~FShaderCodeLibraryImpl() { #if WITH_EDITOR for (uint32 i = 0; i < EShaderPlatform::SP_NumPlatforms; i++) { if (EditorShaderCodeArchive[i]) { delete EditorShaderCodeArchive[i]; } if (EditorShaderStableInfo[i]) { delete EditorShaderStableInfo[i]; } } FMemory::Memzero(EditorShaderCodeArchive); FMemory::Memzero(EditorShaderStableInfo); #endif } bool OpenLibrary(FString const& Name, FString const& Directory) { LLM_SCOPE(ELLMTag::Shaders); bool bResult = false; if (ShaderPlatform < SP_NumPlatforms) { if (OpenShaderCode(Directory, ShaderPlatform, Name)) { bResult = true; // Attempt to open the shared-cooked override code library if there is one. // This is probably not ideal, but it should get shared-cooks working. OpenShaderCode(Directory, ShaderPlatform, Name + TEXT("_SC")); // Inform the pipeline cache that the state of loaded libraries has changed FShaderPipelineCache::ShaderLibraryStateChanged(FShaderPipelineCache::Opened, ShaderPlatform, Name); } } #if WITH_EDITOR for (uint32 i = 0; i < EShaderPlatform::SP_NumPlatforms; i++) { FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[i]; if (CodeArchive) { CodeArchive->OpenLibrary(Name); } } for (uint32 i = 0; i < EShaderPlatform::SP_NumPlatforms; i++) { FEditorShaderStableInfo* StableArchive = EditorShaderStableInfo[i]; if (StableArchive) { StableArchive->OpenLibrary(Name); } } #endif return bResult; } void CloseLibrary(FString const& Name) { { FRWScopeLock(LibraryMutex, SLT_Write); for (uint32 i = ShaderCodeArchiveStack.Num(); i > 0; i--) { FRHIShaderLibraryParamRef ShaderCodeArchive = ShaderCodeArchiveStack[i - 1]; if (ShaderCodeArchive->GetName() == Name) { ShaderCodeArchiveStack.RemoveAt(i - 1); break; } } } // Inform the pipeline cache that the state of loaded libraries has changed FShaderPipelineCache::ShaderLibraryStateChanged(FShaderPipelineCache::Closed, ShaderPlatform, Name); #if WITH_EDITOR for (uint32 i = 0; i < EShaderPlatform::SP_NumPlatforms; i++) { if (EditorShaderCodeArchive[i]) { EditorShaderCodeArchive[i]->CloseLibrary(Name); } if (EditorShaderStableInfo[i]) { EditorShaderStableInfo[i]->CloseLibrary(Name); } } #endif } // At runtime, open shader code collection for specified shader platform bool OpenShaderCode(const FString& ShaderCodeDir, EShaderPlatform InShaderPlatform, FString const& Library) { check(ShaderPlatform == SP_NumPlatforms || InShaderPlatform == ShaderPlatform); ShaderPlatform = InShaderPlatform; FRHIShaderLibraryRef ShaderCodeArchive = new FShaderCodeArchive(ShaderPlatform, ShaderCodeDir, Library); if (ShaderCodeArchive->GetShaderCount() > 0) { bSupportsPipelines = true; UE_LOG(LogShaderLibrary, Display, TEXT("Cooked Context: Using Shared Shader Library %s"), *Library); } else if (RHISupportsNativeShaderLibraries(ShaderPlatform)) { ShaderCodeArchive = RHICreateShaderLibrary(ShaderPlatform, ShaderCodeDir, Library); if (ShaderCodeArchive.IsValid()) { bNativeFormat = true; UE_LOG(LogShaderLibrary, Display, TEXT("Cooked Context: Loaded Native Shared Shader Library %s"), *Library); } else { UE_LOG(LogShaderLibrary, Display, TEXT("Cooked Context: No Native Shared Shader Library for %s"), *Library); } } else { UE_LOG(LogShaderLibrary, Display, TEXT("Cooked Context: No Shared Shader Library for: %s and native library not supported."), *Library); } bool const bOK = IsValidRef(ShaderCodeArchive); if (bOK) { FRWScopeLock(LibraryMutex, SLT_Write); ShaderCodeArchiveStack.Add(ShaderCodeArchive); ShaderCount += ShaderCodeArchive->GetShaderCount(); if (bSupportsPipelines && !bNativeFormat) { TSet const* NewPipelines = ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->GetShaderPipelines(ShaderPlatform); if (NewPipelines) { Pipelines.Append(*NewPipelines); } } } return bOK; } FVertexShaderRHIRef CreateVertexShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FVertexShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateVertexShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateVertexShader(Hash); } return Result; } FPixelShaderRHIRef CreatePixelShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FPixelShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreatePixelShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreatePixelShader(Hash); } return Result; } FGeometryShaderRHIRef CreateGeometryShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FGeometryShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateGeometryShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateGeometryShader(Hash); } return Result; } FGeometryShaderRHIRef CreateGeometryShaderWithStreamOutput(EShaderPlatform Platform, FSHAHash Hash, const FStreamOutElementList& ElementList, uint32 NumStrides, const uint32* Strides, int32 RasterizedStream) { checkSlow(Platform == GetRuntimeShaderPlatform()); FGeometryShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateGeometryShaderWithStreamOutput(ElementList, NumStrides, Strides, RasterizedStream, ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateGeometryShaderWithStreamOutput(Hash, ElementList, NumStrides, Strides, RasterizedStream); } return Result; } FHullShaderRHIRef CreateHullShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FHullShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateHullShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateHullShader(Hash); } return Result; } FDomainShaderRHIRef CreateDomainShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FDomainShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateDomainShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateDomainShader(Hash); } return Result; } FComputeShaderRHIRef CreateComputeShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); FComputeShaderRHIRef Result; FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) { if (bNativeFormat || GRHILazyShaderCodeLoading) Result = RHICreateComputeShader(ShaderCodeArchive, Hash); else Result = ((FShaderCodeArchive*)ShaderCodeArchive)->CreateComputeShader(Hash); } return Result; } TRefCountPtr CreateIterator(void) { return new FShaderCodeLibraryIterator(ShaderCodeArchiveStack, LibraryMutex); } uint32 GetShaderCount(void) { return ShaderCount; } EShaderPlatform GetRuntimeShaderPlatform(void) { return ShaderPlatform; } TSet const* GetShaderPipelines(EShaderPlatform Platform) { if (bSupportsPipelines) { FRWScopeLock(LibraryMutex, SLT_ReadOnly); checkSlow(Platform == GetRuntimeShaderPlatform()); return &Pipelines; } return nullptr; } FRHIShaderLibraryParamRef FindShaderLibrary(const FSHAHash& Hash) { FRWScopeLock(LibraryMutex, SLT_ReadOnly); FRHIShaderLibraryParamRef Result = nullptr; // Search in library opened order for (int32 i = 0; i < ShaderCodeArchiveStack.Num(); ++i) { FRHIShaderLibraryParamRef ShaderCodeArchive = ShaderCodeArchiveStack[i]; if (ShaderCodeArchive->ContainsEntry(Hash)) { Result = ShaderCodeArchive; break; } } return Result; } bool ContainsShaderCode(const FSHAHash& Hash) { FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) return true; else return false; } bool RequestShaderCode(const FSHAHash& Hash, FArchive* Ar) { FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) return ShaderCodeArchive->RequestEntry(Hash, Ar); else return false; } void ReleaseShaderCode(const FSHAHash& Hash) { if (!bNativeFormat) { FRHIShaderLibraryParamRef ShaderCodeArchive = FindShaderLibrary(Hash); if (ShaderCodeArchive) ((FShaderCodeArchive*)ShaderCodeArchive)->ReleaseShaderCode(Hash); } } #if WITH_EDITOR void CleanDirectories(TArray const& ShaderFormats) { for (FName const& Format : ShaderFormats) { FString ShaderIntermediateLocation = FPaths::ProjectSavedDir() / TEXT("Shaders") / Format.ToString(); IFileManager::Get().DeleteDirectory(*ShaderIntermediateLocation, false, true); } } void CookShaderFormats(TArray const& ShaderFormats) { for (FName const& Format : ShaderFormats) { EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(Format); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[Platform]; if (!CodeArchive) { CodeArchive = new FEditorShaderCodeArchive(Format); EditorShaderCodeArchive[Platform] = CodeArchive; EditorArchivePipelines[Platform] = !bNativeFormat; } check(CodeArchive); } for (FName const& Format : ShaderFormats) { EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(Format); FEditorShaderStableInfo* StableArchive = EditorShaderStableInfo[Platform]; if (!StableArchive) { StableArchive = new FEditorShaderStableInfo(Format); EditorShaderStableInfo[Platform] = StableArchive; } check(StableArchive); } } void AddShaderCode(EShaderPlatform Platform, EShaderFrequency Frequency, const FSHAHash& Hash, const TArray& InCode, uint32 const UncompressedSize) { FScopeLock ScopeLock(&ShaderCodeCS); FShaderCodeStats& CodeStats = EditorShaderCodeStats[Platform]; CodeStats.NumShaders++; CodeStats.ShadersSize += InCode.Num(); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[Platform]; check(CodeArchive); if (CodeArchive->AddShader((uint8)Frequency, Hash, InCode, UncompressedSize)) { CodeStats.NumUniqueShaders++; CodeStats.ShadersUniqueSize += InCode.Num(); } } void AddShaderStableKeyValue(EShaderPlatform InShaderPlatform, FStableShaderKeyAndValue& StableKeyValue) { FScopeLock ScopeLock(&ShaderCodeCS); StableKeyValue.ComputeKeyHash(); FEditorShaderStableInfo* StableArchive = EditorShaderStableInfo[InShaderPlatform]; check(StableArchive); StableArchive->AddShader(StableKeyValue); } bool AddShaderPipeline(FShaderPipeline* Pipeline) { check(Pipeline); EShaderPlatform SPlatform = SP_NumPlatforms; for (uint8 Freq = 0; Freq < SF_Compute; Freq++) { FShader* Shader = Pipeline->GetShader((EShaderFrequency)Freq); if (Shader) { if (SPlatform == SP_NumPlatforms) { SPlatform = (EShaderPlatform)Shader->GetTarget().Platform; } else { check(SPlatform == (EShaderPlatform)Shader->GetTarget().Platform); } } } FScopeLock ScopeLock(&ShaderCodeCS); FShaderCodeStats& CodeStats = EditorShaderCodeStats[SPlatform]; CodeStats.NumPipelines++; FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[SPlatform]; check(CodeArchive); bool bAdded = false; if (EditorArchivePipelines[SPlatform] && ((FEditorShaderCodeArchive*)CodeArchive)->AddPipeline(Pipeline)) { CodeStats.NumUniquePipelines++; bAdded = true; } return bAdded; } bool SaveShaderCode(const FString& ShaderCodeDir, const FString& MetaOutputDir, const TArray& ShaderFormats, bool bMaster, TArray& OutSCLCSVPath) { bool bOk = ShaderFormats.Num() > 0; FScopeLock ScopeLock(&ShaderCodeCS); for (int32 i = 0; i < ShaderFormats.Num(); ++i) { FName ShaderFormatName = ShaderFormats[i]; EShaderPlatform SPlatform = ShaderFormatToLegacyShaderPlatform(ShaderFormatName); { FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[SPlatform]; if (CodeArchive) { bOk &= CodeArchive->Finalize(ShaderCodeDir, bNativeFormat, bMaster); } } { FEditorShaderStableInfo* StableArchive = EditorShaderStableInfo[SPlatform]; if (StableArchive) { FString SCLCSVPath; bOk &= StableArchive->Finalize(MetaOutputDir, bNativeFormat, bMaster, SCLCSVPath); OutSCLCSVPath.Add(SCLCSVPath); } } } return bOk; } bool PackageNativeShaderLibrary(const FString& ShaderCodeDir, const TArray& ShaderFormats) { bool bOK = true; for (int32 i = 0; i < ShaderFormats.Num(); ++i) { FName ShaderFormatName = ShaderFormats[i]; EShaderPlatform SPlatform = ShaderFormatToLegacyShaderPlatform(ShaderFormatName); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[SPlatform]; if (CodeArchive && CodeArchive->GetFormat()->SupportsShaderArchives()) { bOK &= CodeArchive->PackageNativeShaderLibrary(ShaderCodeDir); } } return bOK; } void DumpShaderCodeStats() { int32 PlatformId = 0; for (const FShaderCodeStats& CodeStats : EditorShaderCodeStats) { if (CodeStats.NumShaders > 0) { float UniqueSize = CodeStats.ShadersUniqueSize; float UniqueSizeMB = FUnitConversion::Convert(UniqueSize, EUnit::Bytes, EUnit::Megabytes); float TotalSize = CodeStats.ShadersSize; float TotalSizeMB = FUnitConversion::Convert(TotalSize, EUnit::Bytes, EUnit::Megabytes); UE_LOG(LogShaderLibrary, Display, TEXT("")); UE_LOG(LogShaderLibrary, Display, TEXT("Shader Code Stats: %s"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)PlatformId).ToString()); UE_LOG(LogShaderLibrary, Display, TEXT("=================")); UE_LOG(LogShaderLibrary, Display, TEXT("Unique Shaders: %d, Total Shaders: %d"), CodeStats.NumUniqueShaders, CodeStats.NumShaders); UE_LOG(LogShaderLibrary, Display, TEXT("Unique Shaders Size: %.2fmb, Total Shader Size: %.2fmb"), UniqueSizeMB, TotalSizeMB); UE_LOG(LogShaderLibrary, Display, TEXT("=================")); } PlatformId++; } } #endif// WITH_EDITOR }; static FSharedShaderCodeRequest OnSharedShaderCodeRequest; static FSharedShaderCodeRelease OnSharedShaderCodeRelease; FShaderCodeLibraryImpl* FShaderCodeLibraryImpl::Impl = nullptr; static void FShaderCodeLibraryPluginMountedCallback(IPlugin& Plugin) { if (Plugin.CanContainContent() && Plugin.IsEnabled()) { FShaderCodeLibrary::OpenLibrary(Plugin.GetName(), Plugin.GetBaseDir()); FShaderCodeLibrary::OpenLibrary(Plugin.GetName(), Plugin.GetContentDir()); } } void FShaderCodeLibrary::InitForRuntime(EShaderPlatform ShaderPlatform) { check(FPlatformProperties::RequiresCookedData()); if (FShaderCodeLibraryImpl::Impl != nullptr) { //cooked, can't change shader platform on the fly check(FShaderCodeLibraryImpl::Impl->GetRuntimeShaderPlatform() == ShaderPlatform); return; } // Cannot be enabled by the server, pointless if we can't ever render and not compatible with cook-on-the-fly bool bArchive = false; GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("bShareMaterialShaderCode"), bArchive, GGameIni); bool bEnable = !FPlatformProperties::IsServerOnly() && FApp::CanEverRender() && bArchive; #if !UE_BUILD_SHIPPING FString FileHostIP; const bool bCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("filehostip"), FileHostIP); bEnable &= !bCookOnTheFly; #endif if (bEnable) { FShaderCodeLibraryImpl::Impl = new FShaderCodeLibraryImpl(false); if (FShaderCodeLibraryImpl::Impl && FShaderCodeLibraryImpl::Impl->OpenShaderCode(FPaths::ProjectContentDir(), ShaderPlatform, TEXT("Global"))) { IPluginManager::Get().OnNewPluginMounted().AddStatic(&FShaderCodeLibraryPluginMountedCallback); #if !UE_BUILD_SHIPPING // support shared cooked builds by also opening the shared cooked build shader code file FShaderCodeLibraryImpl::Impl->OpenShaderCode(FPaths::ProjectContentDir(), ShaderPlatform, TEXT("Global_SC")); #endif auto Plugins = IPluginManager::Get().GetEnabledPluginsWithContent(); for (auto Plugin : Plugins) { FShaderCodeLibraryPluginMountedCallback(*Plugin); } } else { #if !WITH_EDITOR UE_LOG(LogShaderLibrary, Fatal, TEXT("Failed to initialise ShaderCodeLibrary required by the project because part of the Global shader library is missing from %s."), *FPaths::ProjectContentDir()); #endif Shutdown(); } } } void FShaderCodeLibrary::Shutdown() { if (FShaderCodeLibraryImpl::Impl) { #if WITH_EDITOR DumpShaderCodeStats(); #endif delete FShaderCodeLibraryImpl::Impl; FShaderCodeLibraryImpl::Impl = nullptr; } } bool FShaderCodeLibrary::IsEnabled() { return FShaderCodeLibraryImpl::Impl != nullptr; } bool FShaderCodeLibrary::ContainsShaderCode(const FSHAHash& Hash) { if (FShaderCodeLibraryImpl::Impl) { return FShaderCodeLibraryImpl::Impl->ContainsShaderCode(Hash); } return false; } bool FShaderCodeLibrary::RequestShaderCode(const FSHAHash& Hash, FArchive* Ar) { if (FShaderCodeLibraryImpl::Impl) { OnSharedShaderCodeRequest.Broadcast(Hash, Ar); return FShaderCodeLibraryImpl::Impl->RequestShaderCode(Hash, Ar); } return false; } bool FShaderCodeLibrary::LazyRequestShaderCode(const FSHAHash& Hash, FArchive* Ar) { if (FShaderCodeLibraryImpl::Impl) { OnSharedShaderCodeRequest.Broadcast(Hash, Ar); return true; } return false; } void FShaderCodeLibrary::ReleaseShaderCode(const FSHAHash& Hash) { if (FShaderCodeLibraryImpl::Impl) { OnSharedShaderCodeRelease.Broadcast(Hash); return FShaderCodeLibraryImpl::Impl->ReleaseShaderCode(Hash); } } void FShaderCodeLibrary::LazyReleaseShaderCode(const FSHAHash& Hash) { if (FShaderCodeLibraryImpl::Impl) { OnSharedShaderCodeRelease.Broadcast(Hash); } } FVertexShaderRHIRef FShaderCodeLibrary::CreateVertexShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FVertexShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateVertexShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateVertexShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } FPixelShaderRHIRef FShaderCodeLibrary::CreatePixelShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FPixelShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreatePixelShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreatePixelShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } FGeometryShaderRHIRef FShaderCodeLibrary::CreateGeometryShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FGeometryShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateGeometryShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateGeometryShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } FGeometryShaderRHIRef FShaderCodeLibrary::CreateGeometryShaderWithStreamOutput(EShaderPlatform Platform, FSHAHash Hash, const TArray& Code, const FStreamOutElementList& ElementList, uint32 NumStrides, const uint32* Strides, int32 RasterizedStream) { FGeometryShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateGeometryShaderWithStreamOutput(Platform, Hash, ElementList, NumStrides, Strides, RasterizedStream); } if (!IsValidRef(Shader)) { Shader = RHICreateGeometryShaderWithStreamOutput(Code, ElementList, NumStrides, Strides, RasterizedStream); } SafeAssignHash(Shader, Hash); return Shader; } FHullShaderRHIRef FShaderCodeLibrary::CreateHullShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FHullShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateHullShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateHullShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } FDomainShaderRHIRef FShaderCodeLibrary::CreateDomainShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FDomainShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateDomainShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateDomainShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } FComputeShaderRHIRef FShaderCodeLibrary::CreateComputeShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FComputeShaderRHIRef Shader; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Shader = FShaderCodeLibraryImpl::Impl->CreateComputeShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateComputeShader(Code); } SafeAssignHash(Shader, Hash); FPipelineFileCache::CacheComputePSO(GetTypeHash(Shader.GetReference()), Shader.GetReference()); Shader->SetStats(FPipelineFileCache::RegisterPSOStats(GetTypeHash(Shader.GetReference()))); return Shader; } TRefCountPtr FShaderCodeLibrary::CreateIterator(void) { TRefCountPtr It; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { It = FShaderCodeLibraryImpl::Impl->CreateIterator(); } return It; } uint32 FShaderCodeLibrary::GetShaderCount(void) { uint32 Num = 0; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Num = FShaderCodeLibraryImpl::Impl->GetShaderCount(); } return Num; } TSet const* FShaderCodeLibrary::GetShaderPipelines(EShaderPlatform Platform) { TSet const* Pipelines = nullptr; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Pipelines = FShaderCodeLibraryImpl::Impl->GetShaderPipelines(Platform); } return Pipelines; } EShaderPlatform FShaderCodeLibrary::GetRuntimeShaderPlatform(void) { EShaderPlatform Platform = SP_NumPlatforms; if (FShaderCodeLibraryImpl::Impl && FPlatformProperties::RequiresCookedData()) { Platform = FShaderCodeLibraryImpl::Impl->GetRuntimeShaderPlatform(); } return Platform; } bool FShaderCodeLibrary::OpenLibrary(FString const& Name, FString const& Directory) { bool bResult = false; if (FShaderCodeLibraryImpl::Impl) { bResult = FShaderCodeLibraryImpl::Impl->OpenLibrary(Name, Directory); } return bResult; } void FShaderCodeLibrary::CloseLibrary(FString const& Name) { if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->CloseLibrary(Name); } } #if WITH_EDITOR void FShaderCodeLibrary::InitForCooking(bool bNativeFormat) { FShaderCodeLibraryImpl::Impl = new FShaderCodeLibraryImpl(bNativeFormat); } void FShaderCodeLibrary::CleanDirectories(TArray const& ShaderFormats) { if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->CleanDirectories(ShaderFormats); } } void FShaderCodeLibrary::CookShaderFormats(TArray const& ShaderFormats) { if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->CookShaderFormats(ShaderFormats); } } bool FShaderCodeLibrary::AddShaderCode(EShaderPlatform ShaderPlatform, EShaderFrequency Frequency, const FSHAHash& Hash, const TArray& InCode, uint32 const UncompressedSize) { #if WITH_EDITOR if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->AddShaderCode(ShaderPlatform, Frequency, Hash, InCode, UncompressedSize); return true; } #endif// WITH_EDITOR return false; } bool FShaderCodeLibrary::NeedsShaderStableKeys() { #if WITH_EDITOR if (FShaderCodeLibraryImpl::Impl) { return true; } #endif// WITH_EDITOR return false; } void FShaderCodeLibrary::AddShaderStableKeyValue(EShaderPlatform ShaderPlatform, FStableShaderKeyAndValue& StableKeyValue) { #if WITH_EDITOR if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->AddShaderStableKeyValue(ShaderPlatform, StableKeyValue); } #endif// WITH_EDITOR } bool FShaderCodeLibrary::AddShaderPipeline(FShaderPipeline* Pipeline) { #if WITH_EDITOR if (FShaderCodeLibraryImpl::Impl && Pipeline) { FShaderCodeLibraryImpl::Impl->AddShaderPipeline(Pipeline); return true; } #endif// WITH_EDITOR return false; } bool FShaderCodeLibrary::SaveShaderCodeMaster(const FString& OutputDir, const FString& MetaOutputDir, const TArray& ShaderFormats, TArray& OutSCLCSVPath) { if (FShaderCodeLibraryImpl::Impl) { return FShaderCodeLibraryImpl::Impl->SaveShaderCode(OutputDir, MetaOutputDir, ShaderFormats, true, OutSCLCSVPath); } return false; } bool FShaderCodeLibrary::SaveShaderCodeChild(const FString& OutputDir, const FString& MetaOutputDir, const TArray& ShaderFormats) { if (FShaderCodeLibraryImpl::Impl) { TArray OutSCLCSVPathJunk; return FShaderCodeLibraryImpl::Impl->SaveShaderCode(OutputDir, MetaOutputDir, ShaderFormats, false, OutSCLCSVPathJunk); } return false; } bool FShaderCodeLibrary::PackageNativeShaderLibrary(const FString& ShaderCodeDir, const TArray& ShaderFormats) { if (FShaderCodeLibraryImpl::Impl) { return FShaderCodeLibraryImpl::Impl->PackageNativeShaderLibrary(ShaderCodeDir, ShaderFormats); } return false; } void FShaderCodeLibrary::DumpShaderCodeStats() { if (FShaderCodeLibraryImpl::Impl) { FShaderCodeLibraryImpl::Impl->DumpShaderCodeStats(); } } #endif// WITH_EDITOR void FShaderCodeLibrary::SafeAssignHash(FRHIShader* InShader, const FSHAHash& Hash) { if (InShader) { InShader->SetHash(Hash); } } FDelegateHandle FShaderCodeLibrary::RegisterSharedShaderCodeRequestDelegate_Handle(const FSharedShaderCodeRequest::FDelegate& Delegate) { return OnSharedShaderCodeRequest.Add(Delegate); } void FShaderCodeLibrary::UnregisterSharedShaderCodeRequestDelegate_Handle(FDelegateHandle Handle) { OnSharedShaderCodeRequest.Remove(Handle); } FDelegateHandle FShaderCodeLibrary::RegisterSharedShaderCodeReleaseDelegate_Handle(const FSharedShaderCodeRelease::FDelegate& Delegate) { return OnSharedShaderCodeRelease.Add(Delegate); } void FShaderCodeLibrary::UnregisterSharedShaderCodeReleaseDelegate_Handle(FDelegateHandle Handle) { OnSharedShaderCodeRelease.Remove(Handle); }