// Copyright 1998-2017 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 "IShaderFormatArchive.h" #if WITH_EDITORONLY_DATA #include "Modules/ModuleManager.h" #include "IShaderFormat.h" #include "IShaderFormatModule.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #endif static const ECompressionFlags ShaderLibraryCompressionFlag = ECompressionFlags::COMPRESS_ZLIB; static FString GetShaderCodeFilename(const FString& BaseDir, FName Platform) { return BaseDir / TEXT("ShaderCode-") + Platform.ToString(); } struct FShaderArchiveEntry { uint32 Offset; uint32 Size; uint32 UncompressedSize; uint8 Frequency; }; static FArchive& operator <<(FArchive& Ar, FShaderArchiveEntry& Ref) { return Ar << Ref.Offset << Ref.Size << Ref.UncompressedSize << Ref.Frequency; } static TArray& FShaderLibraryHelperUncompressCode(EShaderPlatform Platform, uint32 UncompressedSize, TArray& Code, TArray& UncompressedCode) { if (RHISupportsShaderCompression(Platform) && Code.Num() != UncompressedSize) { UncompressedCode.SetNum(UncompressedSize); bool bSucceed = FCompression::UncompressMemory(ShaderLibraryCompressionFlag, UncompressedCode.GetData(), UncompressedSize, Code.GetData(), Code.Num()); check(bSucceed); return UncompressedCode; } else { return Code; } } static void FShaderLibraryHelperCompressCode(EShaderPlatform Platform, const TArray& UncompressedCode, TArray& CompressedCode) { if (RHISupportsShaderCompression(Platform)) { auto CompressedSize = CompressedCode.Num(); if (FCompression::CompressMemory(ShaderLibraryCompressionFlag, CompressedCode.GetData(), CompressedSize, UncompressedCode.GetData(), UncompressedCode.Num())) { CompressedCode.SetNum(CompressedSize); } else { CompressedCode = UncompressedCode; } CompressedCode.Shrink(); } else { CompressedCode = UncompressedCode; } } struct FShaderCodeEntry { FString FileName; EShaderFrequency Frequency; }; class FShaderCodeArchive final : public FShaderFactoryInterface { public: FShaderCodeArchive(EShaderPlatform InPlatform, FString const& Filename) : FShaderFactoryInterface(InPlatform) , FilePath(Filename) { TArray ShaderFiles; IFileManager::Get().FindFiles(ShaderFiles, *Filename, TEXT("*.ushaderbytecode")); for (FString const& FileName : ShaderFiles) { if (FileName.Len() > 2 && FileName[1] == TEXT('_')) { FShaderCodeEntry Entry; TCHAR FreqChar[2] = {0, 0}; FreqChar[0] = FileName[0]; Entry.Frequency = (EShaderFrequency)FCStringWide::Atoi(FreqChar); check(Entry.Frequency < SF_NumFrequencies); Entry.FileName = FileName; FSHAHash Hash; FString Name = FPaths::GetBaseFilename(FileName); HexToBytes(Name.RightChop(2), Hash.Hash); Shaders.Add(Hash, Entry); } } UE_LOG(LogShaders, Display, TEXT("Using %s for material shader code"), *Filename); } virtual ~FShaderCodeArchive() { } virtual bool IsLibraryNativeFormat() const {return false;} bool LookupShaderCode(uint8 Frequency, const FSHAHash& Hash, TArray& OutCode, uint32& OutSize) { FShaderCodeEntry* Entry = Shaders.Find(Hash); if (Entry) { FString Path = FilePath / Entry->FileName; FArchive* Ar = IFileManager::Get().CreateFileReader(*Path); if (Ar) { *Ar << OutSize; *Ar << OutCode; Ar->Close(); return true; } } return false; } FPixelShaderRHIRef CreatePixelShader(const FSHAHash& Hash) override final { FPixelShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Pixel, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreatePixelShader(UncompressedCode); } return Shader; } FVertexShaderRHIRef CreateVertexShader(const FSHAHash& Hash) override final { FVertexShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Vertex, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateVertexShader(UncompressedCode); } return Shader; } FHullShaderRHIRef CreateHullShader(const FSHAHash& Hash) override final { FHullShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Hull, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateHullShader(UncompressedCode); } return Shader; } FDomainShaderRHIRef CreateDomainShader(const FSHAHash& Hash) override final { FDomainShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Domain, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateDomainShader(UncompressedCode); } return Shader; } FGeometryShaderRHIRef CreateGeometryShader(const FSHAHash& Hash) override final { FGeometryShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Geometry, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateGeometryShader(UncompressedCode); } return Shader; } FGeometryShaderRHIRef CreateGeometryShaderWithStreamOutput(const FSHAHash& Hash, const FStreamOutElementList& ElementList, uint32 NumStrides, const uint32* Strides, int32 RasterizedStream) override final { FGeometryShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Geometry, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateGeometryShaderWithStreamOutput(UncompressedCode, ElementList, NumStrides, Strides, RasterizedStream); } return Shader; } FComputeShaderRHIRef CreateComputeShader(const FSHAHash& Hash) override final { FComputeShaderRHIRef Shader; TArray Code; uint32 Size = 0; if (LookupShaderCode(SF_Compute, Hash, Code, Size)) { TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Shader = RHICreateComputeShader(UncompressedCode); } 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 = 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 && IsOpenGLPlatform(Platform)) { TArray PipelineNames; IFileManager::Get().FindFiles(PipelineNames, *FilePath, TEXT("*.ushaderpipeline")); for (FString const& Name : PipelineNames) { FString Path = FilePath / Name; FArchive* Ar = IFileManager::Get().CreateFileReader(*Path); if (Ar) { FShaderCodeLibraryPipeline Pipeline; *Ar << Pipeline; Ar->Close(); Pipelines.Add(Pipeline); } } } return &Pipelines; } virtual uint32 GetShaderCount(void) const override final { return Shaders.Num(); } private: // The path to the library FString FilePath; // The shader files present in the library TMap Shaders; // De-serialised pipeline map TSet Pipelines; }; #if WITH_EDITOR //TODO: Looks like a copy from private ShaderCompiler.cpp - need to move this and FindShaderFormat below maybe into IShaderFormat helper functions static const TArray& GetShaderFormats() { static bool bInitialized = false; static TArray Results; if (!bInitialized) { bInitialized = true; Results.Empty(Results.Num()); TArray Modules; FModuleManager::Get().FindModules(SHADERFORMAT_MODULE_WILDCARD, Modules); if (!Modules.Num()) { UE_LOG(LogShaders, Error, TEXT("No target shader formats found!")); } for (int32 Index = 0; Index < Modules.Num(); Index++) { IShaderFormat* Format = FModuleManager::LoadModuleChecked(Modules[Index]).GetShaderFormat(); if (Format != nullptr) { Results.Add(Format); } } } return Results; } static const IShaderFormat* FindShaderFormat(FName Name) { const TArray& ShaderFormats = GetShaderFormats(); for (int32 Index = 0; Index < ShaderFormats.Num(); Index++) { TArray Formats; ShaderFormats[Index]->GetSupportedFormats(Formats); for (int32 FormatIndex = 0; FormatIndex < Formats.Num(); FormatIndex++) { if (Formats[FormatIndex] == Name) { return ShaderFormats[Index]; } } } return nullptr; } struct FEditorShaderCodeArchive { struct FEditorShaderCodeEntry { FString FileName; uint8 Frequency; TArray Code; uint32 UncompressedSize; }; FEditorShaderCodeArchive(FName InFormat) : FormatName(InFormat) , Format(nullptr) { Format = FindShaderFormat(InFormat); check(Format); } ~FEditorShaderCodeArchive() {} FName GetFormat( void ) const { return FormatName; } bool AddShader( uint8 Frequency, const FSHAHash& Hash, TArray const& InCode, uint32 const UncompressedSize ) { bool bAdd = false; if (!Shaders.Contains(Hash)) { uint8 Count = 0; for (uint8 i : InCode) { Count |= i; } check(Count > 0); FEditorShaderCodeEntry Entry; Entry.FileName = FString::Printf(TEXT("%d_%s.ushaderbytecode"), (uint32)Frequency, *Hash.ToString()); Entry.Frequency = Frequency; Entry.Code = InCode; Entry.UncompressedSize = UncompressedSize; Shaders.Add(Hash, Entry); bAdd = true; } return bAdd; } bool AddPipeline(FShaderPipeline* Pipeline) { EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(FormatName); if (IsOpenGLPlatform(ShaderPlatform)) { 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; } bool Finalize(FString OutputDir, FString DebugDir, bool bNativeFormat) { EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(FormatName); FString OutputPath = GetShaderCodeFilename(OutputDir, FormatName); FString DebugPath = GetShaderCodeFilename(DebugDir, FormatName); IFileManager::Get().MakeDirectory(*DebugPath, true); for (TPair& Pair : Shaders) { // Write to a temporary file FString TempFilePath = FPaths::CreateTempFilename(*OutputPath, TEXT("ShaderCodeArchive-")); FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*TempFilePath, FILEWRITE_NoFail); if (FileWriter) { check(Format); if (Format->CanStripShaderCode()) { uint32 Size = Pair.Value.UncompressedSize; TArray Code = Pair.Value.Code; TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, Size, Code, UCode); Format->StripShaderCode(UncompressedCode, DebugPath, bNativeFormat); TArray CCode; FShaderLibraryHelperCompressCode(Platform, UncompressedCode, CCode); Size = UncompressedCode.Num(); *FileWriter << Size; *FileWriter << CCode; } else { *FileWriter << Pair.Value.UncompressedSize; *FileWriter << Pair.Value.Code; } FileWriter->Close(); // As on POSIX only file moves on the same device are atomic FString DestFilePath = OutputPath / Pair.Value.FileName; IFileManager::Get().Move(*DestFilePath, *TempFilePath, false, false, true, true); IFileManager::Get().Delete(*TempFilePath); } } for (FShaderCodeLibraryPipeline& Pipeline : Pipelines) { // Write to a temporary file FString TempFilePath = FPaths::CreateTempFilename(*OutputPath, TEXT("ShaderCodeArchive-")); FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*TempFilePath, FILEWRITE_NoFail); if (FileWriter) { *FileWriter << Pipeline; FileWriter->Close(); // As on POSIX only file moves on the same device are atomic FString DestFilePath = OutputPath / FString::Printf(TEXT("%u.ushaderpipeline"), GetTypeHash(Pipeline)); IFileManager::Get().Move(*DestFilePath, *TempFilePath, false, false, true, true); IFileManager::Get().Delete(*TempFilePath); } } return true; } bool PackageNativeShaderLibrary(const FString& ShaderCodeDir, const FString& DebugShaderCodeDir) { bool bOK = false; FString TempPath = GetShaderCodeFilename(FPaths::GameIntermediateDir(), FormatName) / TEXT("NativeLibrary"); EShaderPlatform Platform = ShaderFormatToLegacyShaderPlatform(FormatName); IShaderFormatArchive* Archive = Format->CreateShaderArchive(FormatName, TempPath); if (Archive) { FString OutputPath = GetShaderCodeFilename(ShaderCodeDir, FormatName); FString DebugPath = GetShaderCodeFilename(DebugShaderCodeDir, FormatName); bOK = true; TArray ShaderFiles; IFileManager::Get().FindFiles(ShaderFiles, *OutputPath, TEXT("*.ushaderbytecode")); for (FString const& FileName : ShaderFiles) { if (FileName.Len() > 2 && FileName[1] == TEXT('_')) { FShaderCodeEntry Entry; TCHAR FreqChar[2] = {0, 0}; FreqChar[0] = FileName[0]; Entry.Frequency = (EShaderFrequency)FCStringWide::Atoi(FreqChar); check(Entry.Frequency < SF_NumFrequencies); FSHAHash Hash; FString Name = FPaths::GetBaseFilename(FileName); HexToBytes(Name.RightChop(2), Hash.Hash); FString Path = OutputPath / FileName; FArchive* Ar = IFileManager::Get().CreateFileReader(*Path); if (Ar) { uint32 UncompressedSize = 0; TArray CompressedCode; *Ar << UncompressedSize; *Ar << CompressedCode; Ar->Close(); TArray UCode; TArray& UncompressedCode = FShaderLibraryHelperUncompressCode(Platform, UncompressedSize, CompressedCode, UCode); if(!Archive->AddShader(Entry.Frequency, Hash, UncompressedCode)) { bOK = false; break; } } else { bOK = false; break; } } } if (bOK) { bOK = Archive->Finalize(ShaderCodeDir, DebugPath, nullptr); if (bOK) { IFileManager::Get().DeleteDirectory(*DebugPath, true, true); IFileManager::Get().DeleteDirectory(*OutputPath, true, true); } } } return bOK; } FName FormatName; TMap Shaders; TSet Pipelines; const IShaderFormat* Format; }; 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 FRHIShaderLibraryRef ShaderCodeArchive; #if WITH_EDITOR // At cook time, shader code collection for each shader platform FEditorShaderCodeArchive* EditorShaderCodeArchive[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; public: FShaderCodeLibraryImpl(bool bInNativeFormat) : bSupportsPipelines(false) , bNativeFormat(bInNativeFormat) { #if WITH_EDITOR FMemory::Memzero(EditorShaderCodeArchive); 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]; } } FMemory::Memzero(EditorShaderCodeArchive); #endif } // At runtime, open shader code collection for specified shader platform bool OpenShaderCode(const FString& ShaderCodeDir, EShaderPlatform ShaderPlatform) { ShaderCodeArchive = RHICreateShaderLibrary(ShaderPlatform, ShaderCodeDir); if(ShaderCodeArchive.IsValid()) { bNativeFormat = true; } else { FString Filename = GetShaderCodeFilename(ShaderCodeDir, LegacyShaderPlatformToShaderFormat(ShaderPlatform)); if (IFileManager::Get().DirectoryExists(*Filename)) { ShaderCodeArchive = new FShaderCodeArchive(ShaderPlatform, Filename); bSupportsPipelines = (ShaderCodeArchive != nullptr); } } return IsValidRef(ShaderCodeArchive); } FVertexShaderRHIRef CreateVertexShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateVertexShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateVertexShader(Hash); } FPixelShaderRHIRef CreatePixelShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreatePixelShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreatePixelShader(Hash); } FGeometryShaderRHIRef CreateGeometryShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateGeometryShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateGeometryShader(Hash); } FGeometryShaderRHIRef CreateGeometryShaderWithStreamOutput(EShaderPlatform Platform, FSHAHash Hash, const FStreamOutElementList& ElementList, uint32 NumStrides, const uint32* Strides, int32 RasterizedStream) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateGeometryShaderWithStreamOutput(ElementList, NumStrides, Strides, RasterizedStream, ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateGeometryShaderWithStreamOutput(Hash, ElementList, NumStrides, Strides, RasterizedStream); } FHullShaderRHIRef CreateHullShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateHullShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateHullShader(Hash); } FDomainShaderRHIRef CreateDomainShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateDomainShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateDomainShader(Hash); } FComputeShaderRHIRef CreateComputeShader(EShaderPlatform Platform, FSHAHash Hash) { checkSlow(Platform == GetRuntimeShaderPlatform()); if(bNativeFormat) { return RHICreateComputeShader(ShaderCodeArchive, Hash); } return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->CreateComputeShader(Hash); } TRefCountPtr CreateIterator(void) { return ShaderCodeArchive->CreateIterator(); } uint32 GetShaderCount(void) { return ShaderCodeArchive->GetShaderCount(); } EShaderPlatform GetRuntimeShaderPlatform(void) { return ShaderCodeArchive->GetPlatform(); } TSet const* GetShaderPipelines(EShaderPlatform Platform) { if (bSupportsPipelines) { checkSlow(Platform == GetRuntimeShaderPlatform()); return ((FShaderCodeArchive*)ShaderCodeArchive.GetReference())->GetShaderPipelines(Platform); } return nullptr; } #if WITH_EDITOR void AddShaderCode(EShaderPlatform ShaderPlatform, EShaderFrequency Frequency, const FSHAHash& Hash, const TArray& InCode, uint32 const UncompressedSize) { FShaderCodeStats& CodeStats = EditorShaderCodeStats[ShaderPlatform]; CodeStats.NumShaders++; CodeStats.ShadersSize += InCode.Num(); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[ShaderPlatform]; if (!CodeArchive) { FName Format = LegacyShaderPlatformToShaderFormat(ShaderPlatform); CodeArchive = new FEditorShaderCodeArchive(Format); EditorShaderCodeArchive[ShaderPlatform] = CodeArchive; EditorArchivePipelines[ShaderPlatform] = IsOpenGLPlatform(ShaderPlatform); } check(CodeArchive); if (CodeArchive->AddShader((uint8)Frequency, Hash, InCode, UncompressedSize)) { CodeStats.NumUniqueShaders++; CodeStats.ShadersUniqueSize += InCode.Num(); } } bool AddShaderPipeline(FShaderPipeline* Pipeline) { check(Pipeline); EShaderPlatform ShaderPlatform = SP_NumPlatforms; for (uint8 Freq = 0; Freq < SF_Compute; Freq++) { FShader* Shader = Pipeline->GetShader((EShaderFrequency)Freq); if (Shader) { if (ShaderPlatform == SP_NumPlatforms) { ShaderPlatform = (EShaderPlatform)Shader->GetTarget().Platform; } else { check(ShaderPlatform == (EShaderPlatform)Shader->GetTarget().Platform); } } } FShaderCodeStats& CodeStats = EditorShaderCodeStats[ShaderPlatform]; CodeStats.NumPipelines++; FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[ShaderPlatform]; if (!CodeArchive) { FName Format = LegacyShaderPlatformToShaderFormat(ShaderPlatform); CodeArchive = new FEditorShaderCodeArchive(Format); EditorShaderCodeArchive[ShaderPlatform] = CodeArchive; EditorArchivePipelines[ShaderPlatform] = IsOpenGLPlatform(ShaderPlatform); } check(CodeArchive); bool bAdded = false; if (EditorArchivePipelines[ShaderPlatform] && ((FEditorShaderCodeArchive*)CodeArchive)->AddPipeline(Pipeline)) { CodeStats.NumUniquePipelines++; bAdded = true; } return bAdded; } void SaveShaderCode(const FString& ShaderCodeDir, const FString& DebugOutputDir, const TArray& ShaderFormats) { for (int32 i = 0; i < ShaderFormats.Num(); ++i) { FName ShaderFormatName = ShaderFormats[i]; EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(ShaderFormatName); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[ShaderPlatform]; if (CodeArchive) { CodeArchive->Finalize(ShaderCodeDir, DebugOutputDir, bNativeFormat); } } } bool PackageNativeShaderLibrary(const FString& ShaderCodeDir, const FString& DebugShaderCodeDir, const TArray& ShaderFormats) { bool bOK = true; for (int32 i = 0; i < ShaderFormats.Num(); ++i) { FName ShaderFormatName = ShaderFormats[i]; EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(ShaderFormatName); FEditorShaderCodeArchive* CodeArchive = EditorShaderCodeArchive[ShaderPlatform]; if (CodeArchive) { bOK &= CodeArchive->PackageNativeShaderLibrary(ShaderCodeDir, DebugShaderCodeDir); } } 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(LogShaders, Display, TEXT("")); UE_LOG(LogShaders, Display, TEXT("Shader Code Stats: %s"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)PlatformId).ToString()); UE_LOG(LogShaders, Display, TEXT("=================")); UE_LOG(LogShaders, Display, TEXT("Unique Shaders: %d, Total Shaders: %d"), CodeStats.NumUniqueShaders, CodeStats.NumShaders); UE_LOG(LogShaders, Display, TEXT("Unique Shaders Size: %.2fmb, Total Shader Size: %.2fmb"), UniqueSizeMB, TotalSizeMB); UE_LOG(LogShaders, Display, TEXT("=================")); } PlatformId++; } } #endif// WITH_EDITOR }; static FShaderCodeLibraryImpl* Impl = nullptr; void FShaderCodeLibrary::InitForRuntime(EShaderPlatform ShaderPlatform) { check(Impl == nullptr); check(FPlatformProperties::RequiresCookedData()); if (!FPlatformProperties::IsServerOnly() && FApp::CanEverRender()) { Impl = new FShaderCodeLibraryImpl(false); if (!Impl->OpenShaderCode(FPaths::GameContentDir(), ShaderPlatform)) { Shutdown(); } } } void FShaderCodeLibrary::InitForCooking(bool bNativeFormat) { Impl = new FShaderCodeLibraryImpl(bNativeFormat); } void FShaderCodeLibrary::Shutdown() { #if WITH_EDITOR DumpShaderCodeStats(); #endif delete Impl; Impl = nullptr; } bool FShaderCodeLibrary::AddShaderCode(EShaderPlatform ShaderPlatform, EShaderFrequency Frequency, const FSHAHash& Hash, const TArray& InCode, uint32 const UncompressedSize) { #if WITH_EDITOR if (Impl) { Impl->AddShaderCode(ShaderPlatform, Frequency, Hash, InCode, UncompressedSize); return true; } #endif// WITH_EDITOR return false; } bool FShaderCodeLibrary::AddShaderPipeline(FShaderPipeline* Pipeline) { #if WITH_EDITOR if (Impl && Pipeline) { Impl->AddShaderPipeline(Pipeline); return true; } #endif// WITH_EDITOR return false; } FVertexShaderRHIRef FShaderCodeLibrary::CreateVertexShader(EShaderPlatform Platform, FSHAHash Hash, TArray const& Code) { FVertexShaderRHIRef Shader; if (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = 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 (Impl && FPlatformProperties::RequiresCookedData()) { Shader = Impl->CreateComputeShader(Platform, Hash); } if (!IsValidRef(Shader)) { Shader = RHICreateComputeShader(Code); } SafeAssignHash(Shader, Hash); return Shader; } TRefCountPtr FShaderCodeLibrary::CreateIterator(void) { TRefCountPtr It; if (Impl && FPlatformProperties::RequiresCookedData()) { It = Impl->CreateIterator(); } return It; } uint32 FShaderCodeLibrary::GetShaderCount(void) { uint32 Num = 0; if (Impl && FPlatformProperties::RequiresCookedData()) { Num = Impl->GetShaderCount(); } return Num; } TSet const* FShaderCodeLibrary::GetShaderPipelines(EShaderPlatform Platform) { TSet const* Pipelines = nullptr; if (Impl && FPlatformProperties::RequiresCookedData() && IsOpenGLPlatform(Platform)) { Pipelines = Impl->GetShaderPipelines(Platform); } return Pipelines; } EShaderPlatform FShaderCodeLibrary::GetRuntimeShaderPlatform(void) { EShaderPlatform Platform = SP_NumPlatforms; if (Impl && FPlatformProperties::RequiresCookedData()) { Platform = Impl->GetRuntimeShaderPlatform(); } return Platform; } #if WITH_EDITOR void FShaderCodeLibrary::SaveShaderCode(const FString& OutputDir, const FString& DebugDir, const TArray& ShaderFormats) { if (Impl) { Impl->SaveShaderCode(OutputDir, DebugDir, ShaderFormats); } } bool FShaderCodeLibrary::PackageNativeShaderLibrary(const FString& ShaderCodeDir, const FString& DebugShaderCodeDir, const TArray& ShaderFormats) { if (Impl) { return Impl->PackageNativeShaderLibrary(ShaderCodeDir, DebugShaderCodeDir, ShaderFormats); } return true; } void FShaderCodeLibrary::DumpShaderCodeStats() { if (Impl) { Impl->DumpShaderCodeStats(); } } #endif// WITH_EDITOR void FShaderCodeLibrary::SafeAssignHash(FRHIShader* InShader, const FSHAHash& Hash) { if (InShader) { InShader->SetHash(Hash); } }