// Copyright Epic Games, Inc. All Rights Reserved. #include "PipelineCacheUtilities.h" #if WITH_EDITOR // this has no business existing in a cooked game #include "Misc/SecureHash.h" #include "Serialization/NameAsStringIndexProxyArchive.h" #include "Serialization/VarInt.h" #include "HAL/FileManagerGeneric.h" namespace UE { namespace PipelineCacheUtilities { namespace Private { /** Header of the binary file. */ struct FStableKeysSerializedHeader { enum class EMagic : uint64 { Magic = 0x524448534C425453ULL // STBLSHDR }; enum class EVersion : int32 { Current = 1 }; /** Magic to reject other files */ EMagic Magic = EMagic::Magic; /** Format version */ EVersion Version = EVersion::Current; /** Number of stable key entries. */ int64 NumEntries; friend FArchive& operator<<(FArchive& Ar, FStableKeysSerializedHeader& Info) { return Ar << Info.Magic << Info.Version << Info.NumEntries; } }; } } } bool UE::PipelineCacheUtilities::LoadStableKeysFile(const FStringView& Filename, TArray& InOutArray) { TUniquePtr FileArchiveInner(IFileManager::Get().CreateFileReader(*FString(Filename.Len(), Filename.GetData()))); if (!FileArchiveInner) { return false; } TUniquePtr Archive(new FNameAsStringIndexProxyArchive(*FileArchiveInner.Get())); UE::PipelineCacheUtilities::Private::FStableKeysSerializedHeader Header; UE::PipelineCacheUtilities::Private::FStableKeysSerializedHeader SupportedHeader; *Archive << Header; if (Header.Magic != SupportedHeader.Magic) { return false; } // start restrictive, as the format isn't really forward compatible, nor needs to be if (Header.Version != SupportedHeader.Version) { return false; } TArray Hashes; int32 NumHashes; *Archive << NumHashes; Hashes.AddUninitialized(NumHashes); for (int32 IdxHash = 0; IdxHash < NumHashes; ++IdxHash) { *Archive << Hashes[IdxHash]; } for (int64 Idx = 0; Idx < Header.NumEntries; ++Idx) { FStableShaderKeyAndValue Item; int8 CompactNamesNum = -1; *Archive << CompactNamesNum; if (CompactNamesNum > 0) { Item.ClassNameAndObjectPath.ObjectClassAndPath.AddDefaulted(CompactNamesNum); for (int IdxName = 0; IdxName < (int)CompactNamesNum; ++IdxName) { *Archive << Item.ClassNameAndObjectPath.ObjectClassAndPath[IdxName]; } } *Archive << Item.ShaderType; *Archive << Item.ShaderClass; *Archive << Item.MaterialDomain; *Archive << Item.FeatureLevel; *Archive << Item.QualityLevel; *Archive << Item.TargetFrequency; *Archive << Item.TargetPlatform; *Archive << Item.VFType; *Archive << Item.PermutationId; uint64 HashIdx = ReadVarUIntFromArchive(*Archive); Item.PipelineHash = Hashes[static_cast(HashIdx)]; HashIdx = ReadVarUIntFromArchive(*Archive); Item.OutputHash = Hashes[static_cast(HashIdx)]; // Standardize on all CompactNames being parsed from string. This is a temporary hack until the names are parsed from CSV when reading StablePC FString StringRep = Item.ClassNameAndObjectPath.ToString(); Item.ClassNameAndObjectPath.ParseFromString(StringRep); Item.ComputeKeyHash(); InOutArray.Add(Item); } return true; } bool UE::PipelineCacheUtilities::SaveStableKeysFile(const FStringView& Filename, const TSet& Values) { TUniquePtr FileArchiveInner(IFileManager::Get().CreateFileWriter(*FString(Filename.Len(), Filename.GetData()))); if (!FileArchiveInner) { return false; } TUniquePtr Archive(new FNameAsStringIndexProxyArchive(*FileArchiveInner.Get())); UE::PipelineCacheUtilities::Private::FStableKeysSerializedHeader Header; Header.NumEntries = Values.Num(); *Archive << Header; // go through all the hashes and index them TArray Hashes; TMap HashToIndex; auto IndexHash = [&Hashes, &HashToIndex](const FSHAHash& Hash) { if (HashToIndex.Find(Hash) == nullptr) { Hashes.Add(Hash); HashToIndex.Add(Hash, Hashes.Num() - 1); } }; for (const FStableShaderKeyAndValue& Item : Values) { IndexHash(Item.PipelineHash); IndexHash(Item.OutputHash); } int32 NumHashes = Hashes.Num(); *Archive << NumHashes; for (int32 IdxHash = 0; IdxHash < NumHashes; ++IdxHash) { *Archive << Hashes[IdxHash]; } // save the rest of the properties for (const FStableShaderKeyAndValue& ConstItem : Values) { // serialization unfortunately needs non-const and this is easier than const-casting every field FStableShaderKeyAndValue& Item = const_cast(ConstItem); int8 CompactNamesNum = static_cast(Item.ClassNameAndObjectPath.ObjectClassAndPath.Num()); ensure(Item.ClassNameAndObjectPath.ObjectClassAndPath.Num() < 256); *Archive << CompactNamesNum; for (int Idx = 0; Idx < (int)CompactNamesNum; ++Idx) { *Archive << Item.ClassNameAndObjectPath.ObjectClassAndPath[Idx]; } *Archive << Item.ShaderType; *Archive << Item.ShaderClass; *Archive << Item.MaterialDomain; *Archive << Item.FeatureLevel; *Archive << Item.QualityLevel; *Archive << Item.TargetFrequency; *Archive << Item.TargetPlatform; *Archive << Item.VFType; *Archive << Item.PermutationId; uint64 PipelineHashIdx = static_cast(*HashToIndex.Find(Item.PipelineHash)) ; WriteVarUIntToArchive(*Archive, PipelineHashIdx); uint64 OutputHashIdx = static_cast(*HashToIndex.Find(Item.OutputHash)); WriteVarUIntToArchive(*Archive, OutputHashIdx); } return true; } #endif // WITH_EDITOR