2022-11-22 16:14:14 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "ShaderSymbolExport.h"
# if WITH_ENGINE
2022-11-29 13:13:26 -05:00
# include "FileUtilities/ZipArchiveReader.h"
2022-11-22 16:14:14 -05:00
# include "FileUtilities/ZipArchiveWriter.h"
# include "GenericPlatform/GenericPlatformFile.h"
2022-11-24 19:15:00 -05:00
# include "HAL/FileManager.h"
2022-11-22 16:14:14 -05:00
# include "HAL/PlatformFileManager.h"
# include "Logging/LogMacros.h"
# include "Misc/CommandLine.h"
# include "Misc/PathViews.h"
# include "ShaderCompilerCore.h"
DECLARE_LOG_CATEGORY_CLASS ( LogShaderSymbolExport , Display , Display ) ;
static const TCHAR * ZipFileBaseLeafName = TEXT ( " ShaderSymbols " ) ;
static const TCHAR * ZipFileExtension = TEXT ( " .zip " ) ;
2023-07-20 16:51:51 -04:00
static const TCHAR * InfoFileExtension = TEXT ( " .info " ) ;
2022-11-22 16:14:14 -05:00
FShaderSymbolExport : : FShaderSymbolExport ( FName InShaderFormat )
: ShaderFormat ( InShaderFormat )
{
}
FShaderSymbolExport : : ~ FShaderSymbolExport ( ) = default ;
2023-07-20 16:51:51 -04:00
static void DeleteExisting ( IPlatformFile & PlatformFile , const FString & Directory , const TCHAR * BaseLeafName , const TCHAR * Extension )
2022-11-22 16:14:14 -05:00
{
TArray < FString > ExistingZips ;
2023-07-20 16:51:51 -04:00
PlatformFile . FindFiles ( ExistingZips , * Directory , Extension ) ;
2022-11-22 16:14:14 -05:00
for ( const FString & ZipFile : ExistingZips )
{
2023-07-20 16:51:51 -04:00
if ( FPathViews : : GetPathLeaf ( ZipFile ) . StartsWith ( BaseLeafName ) )
2022-11-22 16:14:14 -05:00
{
PlatformFile . DeleteFile ( * ZipFile ) ;
}
}
}
2023-07-20 16:51:51 -04:00
static FString CreateNameAndDeleteOld ( uint32 MultiprocessId , IPlatformFile & PlatformFile , const FString & ExportPath , const TCHAR * BaseLeafName , const TCHAR * Extension )
{
FString Name ;
if ( MultiprocessId = = 0 )
{
DeleteExisting ( PlatformFile , ExportPath , BaseLeafName , Extension ) ;
Name = FString : : Printf ( TEXT ( " %s%s " ) , BaseLeafName , Extension ) ;
}
else
{
Name = FString : : Printf ( TEXT ( " %s_%d%s " ) , BaseLeafName , MultiprocessId , Extension ) ;
}
return Name ;
}
2022-11-22 16:14:14 -05:00
void FShaderSymbolExport : : Initialize ( )
{
const bool bSymbolsEnabled = ShouldWriteShaderSymbols ( ShaderFormat ) ;
const bool bForceSymbols = FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " -ShaderSymbolsExport= " ) , ExportPath ) ;
2023-07-20 16:51:51 -04:00
const bool bSymbolsInfoEnabled = ShouldGenerateShaderSymbolsInfo ( ShaderFormat ) ;
2022-11-22 16:14:14 -05:00
2023-07-20 16:51:51 -04:00
if ( bSymbolsEnabled | | bForceSymbols | | bSymbolsInfoEnabled )
2022-11-22 16:14:14 -05:00
{
// if no command line path is provided, look to the cvar first
if ( ExportPath . IsEmpty ( ) )
{
if ( GetShaderSymbolPathOverride ( ExportPath , ShaderFormat ) )
{
ExportPath = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForWrite ( * ExportPath ) ;
}
}
// if there was no path set via command line or the cvar, fall back to our default
if ( ExportPath . IsEmpty ( ) )
{
ExportPath = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForWrite (
* ( FPaths : : ProjectSavedDir ( ) / TEXT ( " ShaderSymbols " ) / ShaderFormat . ToString ( ) ) ) ;
}
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
bExportShaderSymbols = PlatformFile . CreateDirectoryTree ( * ExportPath ) ;
if ( ! bExportShaderSymbols )
{
UE_LOG ( LogShaderSymbolExport , Error , TEXT ( " Failed to create shader symbols output directory. Shader symbol export will be disabled. " ) ) ;
}
else
{
2023-07-20 16:51:51 -04:00
// setup multiproc data in case we need it
2023-10-19 18:18:01 -04:00
uint32 MultiprocessId = UE : : GetMultiprocessId ( ) ;
2023-07-20 16:51:51 -04:00
bMultiprocessOwner = MultiprocessId = = 0 ;
2022-11-22 16:14:14 -05:00
// Check if the export mode is to an uncompressed archive or loose files.
2023-07-20 16:51:51 -04:00
const bool bExportAsZip = ShouldWriteShaderSymbolsAsZip ( ShaderFormat ) ;
if ( bSymbolsEnabled & & ( bExportAsZip | | FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " ShaderSymbolsExportZip " ) ) ) )
2022-11-22 16:14:14 -05:00
{
2023-07-20 16:51:51 -04:00
FString LeafName = CreateNameAndDeleteOld ( MultiprocessId , PlatformFile , ExportPath , ZipFileBaseLeafName , ZipFileExtension ) ;
2022-11-22 16:14:14 -05:00
FString SingleFilePath = ExportPath / LeafName ;
IFileHandle * OutputZipFile = PlatformFile . OpenWrite ( * SingleFilePath ) ;
if ( ! OutputZipFile )
{
UE_LOG ( LogShaderSymbolExport , Error , TEXT ( " Failed to create shader symbols output file \" %s \" . Shader symbol export will be disabled. " ) , * SingleFilePath ) ;
bExportShaderSymbols = false ;
}
else
{
ZipWriter = MakeUnique < FZipArchiveWriter > ( OutputZipFile ) ;
}
}
2023-07-20 16:51:51 -04:00
if ( bSymbolsInfoEnabled )
{
// if we are exporting collated shader pdb info into one file
FString LeafName = CreateNameAndDeleteOld ( MultiprocessId , PlatformFile , ExportPath , ZipFileBaseLeafName , InfoFileExtension ) ;
InfoFilePath = ExportPath / LeafName ;
}
2022-11-22 16:14:14 -05:00
}
}
if ( bExportShaderSymbols )
{
UE_LOG ( LogShaderSymbolExport , Display , TEXT ( " Shader symbol export enabled. Output directory: \" %s \" " ) , * ExportPath ) ;
if ( ZipWriter )
{
UE_LOG ( LogShaderSymbolExport , Display , TEXT ( " Shader symbol zip mode enabled. Shader symbols will be archived in a single (uncompressed) zip file. " ) ) ;
}
}
}
2023-07-20 16:51:51 -04:00
void FShaderSymbolExport : : WriteSymbolData ( const FString & Filename , const FString & DebugData , TConstArrayView < uint8 > Contents )
2022-11-22 16:14:14 -05:00
{
2023-07-21 18:27:54 -04:00
// No writing is possible if the Filename is empty
if ( Filename . IsEmpty ( ) )
{
return ;
}
2022-11-22 16:14:14 -05:00
// Skip this symbol data if we've already exported it before.
bool bAlreadyInSet = false ;
ExportedShaders . Add ( Filename , & bAlreadyInSet ) ;
if ( bAlreadyInSet )
{
// We've already exported this shader hash
return ;
}
// Emit periodic log messages detailing the size of the shader symbols output file/directory.
static uint64 LastReport = 0 ;
TotalSymbolDataBytes + = Contents . Num ( ) ;
TotalSymbolData + + ;
if ( ( TotalSymbolDataBytes - LastReport ) > = ( 64 * 1024 * 1024 ) )
{
UE_LOG ( LogShaderSymbolExport , Display , TEXT ( " Shader symbols export size: %.2f MB, count: %llu " ) ,
double ( TotalSymbolDataBytes ) / ( 1024.0 * 1024.0 ) , TotalSymbolData ) ;
LastReport = TotalSymbolDataBytes ;
}
2023-07-20 16:51:51 -04:00
if ( ShouldGenerateShaderSymbolsInfo ( ShaderFormat ) & & ! DebugData . IsEmpty ( ) )
2022-11-22 16:14:14 -05:00
{
2023-07-20 16:51:51 -04:00
// Collect the simple shader symbol information
ShaderInfos . Add ( { Filename , DebugData } ) ;
2022-11-22 16:14:14 -05:00
}
2023-07-20 16:51:51 -04:00
if ( ShouldWriteShaderSymbols ( ShaderFormat ) & & Contents . Num ( ) )
2022-11-22 16:14:14 -05:00
{
2023-07-20 16:51:51 -04:00
if ( ZipWriter )
2022-11-22 16:14:14 -05:00
{
2023-07-20 16:51:51 -04:00
// Append the platform data to the zip file
ZipWriter - > AddFile ( Filename , Contents , FDateTime : : Now ( ) ) ;
2022-11-22 16:14:14 -05:00
}
else
{
2023-07-20 16:51:51 -04:00
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
2022-11-22 16:14:14 -05:00
2023-07-20 16:51:51 -04:00
// Write the symbols to the export directory
const FString OutputPath = ExportPath / Filename ;
const FString Directory = FPaths : : GetPath ( OutputPath ) ;
// Filename could contain extra folders, so we need to make sure they exist first.
if ( ! PlatformFile . CreateDirectoryTree ( * Directory ) )
2022-11-22 16:14:14 -05:00
{
2023-07-20 16:51:51 -04:00
UE_LOG ( LogShaderSymbolExport , Error , TEXT ( " Failed to create shader symbol directory \" %s \" . " ) , * Directory ) ;
}
else
{
IFileHandle * File = PlatformFile . OpenWrite ( * OutputPath ) ;
if ( ! File | | ! File - > Write ( Contents . GetData ( ) , Contents . Num ( ) ) )
{
UE_LOG ( LogShaderSymbolExport , Error , TEXT ( " Failed to export shader symbols \" %s \" . " ) , * OutputPath ) ;
}
if ( File )
{
delete File ;
}
2022-11-22 16:14:14 -05:00
}
}
}
}
void FShaderSymbolExport : : NotifyShaderCompilersShutdown ( )
{
2023-07-20 16:51:51 -04:00
if ( ShaderInfos . Num ( ) )
{
if ( InfoFilePath . Len ( ) )
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
if ( bMultiprocessOwner )
{
// if we are the multiprocess owner merge in any other files we find
2023-09-19 10:46:28 -04:00
// we will chunk up the worker files into {Hash, Data} pairs, dedupe them with ours, and sort them all
2023-07-20 16:51:51 -04:00
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
TArray < FString > FilesToMergeIn ;
PlatformFile . FindFiles ( FilesToMergeIn , * ExportPath , InfoFileExtension ) ;
for ( const FString & InfoFile : FilesToMergeIn )
{
TUniquePtr < FArchive > Reader = TUniquePtr < FArchive > ( FileManager . CreateFileReader ( * InfoFile ) ) ;
if ( Reader . IsValid ( ) )
{
int64 Size = Reader - > TotalSize ( ) ;
TArray < uint8 > RawData ;
RawData . AddUninitialized ( Size ) ;
Reader - > Serialize ( RawData . GetData ( ) , Size ) ;
Reader - > Close ( ) ;
2023-09-19 10:46:28 -04:00
TArray < FString > Lines ;
FString ( StringCast < TCHAR > ( reinterpret_cast < const ANSICHAR * > ( RawData . GetData ( ) ) ) . Get ( ) ) . ParseIntoArrayLines ( Lines ) ;
for ( const FString & Line : Lines )
{
int32 Space ;
Line . FindChar ( TEXT ( ' ' ) , Space ) ;
if ( Space ! = INDEX_NONE )
{
FString Filename = Line . Left ( Space ) ;
// if this symbol is new to the multiproc owner, store it
bool bAlreadyInSet = false ;
ExportedShaders . Add ( Filename , & bAlreadyInSet ) ;
if ( ! bAlreadyInSet )
{
FString DebugData = Line . Right ( Line . Len ( ) - Space - 1 ) ;
ShaderInfos . Add ( { Filename , DebugData } ) ;
}
}
}
2023-07-20 16:51:51 -04:00
}
PlatformFile . DeleteFile ( * InfoFile ) ;
}
}
2023-09-19 10:46:28 -04:00
// sort and combine the data for output
ShaderInfos . Sort ( [ ] ( const FShaderInfo & A , const FShaderInfo & B ) { return A . Hash < B . Hash ; } ) ;
TArray < uint8 > Output ;
for ( FShaderInfo Info : ShaderInfos )
{
auto TmpHash = StringCast < ANSICHAR > ( * Info . Hash ) ;
auto TmpData = StringCast < ANSICHAR > ( * Info . Data ) ;
Output . Append ( ( const uint8 * ) TmpHash . Get ( ) , TmpHash . Length ( ) ) ;
Output . Add ( ' ' ) ;
Output . Append ( ( const uint8 * ) TmpData . Get ( ) , TmpData . Length ( ) ) ;
Output . Add ( ' \n ' ) ;
}
2023-07-20 16:51:51 -04:00
TUniquePtr < FArchive > Writer = TUniquePtr < FArchive > ( FileManager . CreateFileWriter ( * InfoFilePath ) ) ;
if ( Writer . IsValid ( ) )
{
Writer - > Serialize ( Output . GetData ( ) , Output . Num ( ) ) ;
Writer - > Close ( ) ;
2023-11-14 15:18:29 -05:00
UE_LOG ( LogShaderSymbolExport , Display , TEXT ( " Wrote %d records into shader symbols info output file \" %s \" . " ) , ShaderInfos . Num ( ) , * InfoFilePath ) ;
2023-07-20 16:51:51 -04:00
}
else
{
UE_LOG ( LogShaderSymbolExport , Error , TEXT ( " Failed to create shader symbols output file \" %s \" . " ) , * InfoFilePath ) ;
}
}
}
if ( ZipWriter & & bMultiprocessOwner )
2022-11-22 16:14:14 -05:00
{
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
TArray < FString > ZipsToMergeIn ;
PlatformFile . FindFiles ( ZipsToMergeIn , * ExportPath , ZipFileExtension ) ;
ZipsToMergeIn . RemoveAll ( [ ] ( const FString & FileName )
{
return FPathViews : : GetBaseFilename ( FileName ) = = ZipFileBaseLeafName ;
} ) ;
2022-11-29 13:13:26 -05:00
# if WITH_EDITOR // FZipArchiveReader is only available in editor
2022-11-22 16:14:14 -05:00
for ( const FString & ZipFile : ZipsToMergeIn )
{
{
2022-11-29 13:13:26 -05:00
FZipArchiveReader Reader ( PlatformFile . OpenRead ( * ZipFile ) ) ;
bool bAllValid = false ;
if ( Reader . IsValid ( ) )
2022-11-22 16:14:14 -05:00
{
2022-11-29 13:13:26 -05:00
bAllValid = true ;
for ( const FString & EmbeddedFileName : Reader . GetFileNames ( ) )
2022-11-22 16:14:14 -05:00
{
2022-11-29 13:13:26 -05:00
TArray < uint8 > Contents ;
if ( ! Reader . TryReadFile ( EmbeddedFileName , Contents ) )
{
bAllValid = false ;
continue ;
}
ZipWriter - > AddFile ( EmbeddedFileName , Contents , FDateTime : : Now ( ) ) ;
2022-11-22 16:14:14 -05:00
}
}
2022-11-29 13:13:26 -05:00
if ( ! bAllValid )
{
UE_LOG ( LogShaderSymbolExport , Error ,
TEXT ( " Failed to read from CookWorker shader symbols output file \" %s \" . Some shader symbols will be missing. " ) ,
* ZipFile ) ;
}
2022-11-22 16:14:14 -05:00
}
PlatformFile . DeleteFile ( * ZipFile ) ;
}
2022-11-29 13:13:26 -05:00
# else
UE_CLOG ( ! ZipsToMergeIn . IsEmpty ( ) , LogShaderSymbolExport , Error ,
TEXT ( " Cannot merge zips from multiprocess instances in %s; merging is only available in editor. " ) , * ExportPath ) ;
# endif
2022-11-22 16:14:14 -05:00
}
ZipWriter . Reset ( ) ;
}
# endif // WITH_ENGINE