2019-12-26 23:01:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
// ShaderCompileWorker.cpp : Defines the entry point for the console application.
//
2016-11-23 15:48:37 -05:00
# include "CoreMinimal.h"
2014-03-14 14:13:41 -04:00
# include "RequiredProgramMainCPPInclude.h"
2022-10-20 09:43:50 -04:00
# include "ShaderCompilerCore.h"
2023-01-20 09:39:05 -05:00
# include "ShaderCompilerCommon.h"
2023-01-26 10:35:39 -05:00
# include "ShaderCompilerJobTypes.h"
2014-03-14 14:13:41 -04:00
# include "ShaderCore.h"
2018-01-20 11:19:29 -05:00
# include "HAL/ExceptionHandling.h"
# include "Interfaces/IShaderFormat.h"
# include "Interfaces/IShaderFormatModule.h"
2018-09-12 15:59:49 -04:00
# include "Interfaces/ITargetPlatformManagerModule.h"
2018-09-11 14:44:10 -04:00
# include "RHIShaderFormatDefinitions.inl"
2020-04-16 16:36:56 -04:00
# include "ShaderCompilerCommon.h"
2021-09-07 15:49:28 -04:00
# include "Serialization/MemoryReader.h"
2022-12-07 10:29:08 -05:00
# include "SocketSubsystem.h"
2014-03-14 14:13:41 -04:00
# define DEBUG_USING_CONSOLE 0
2018-09-11 14:44:10 -04:00
static double LastCompileTime = 0.0 ;
static int32 GNumProcessedJobs = 0 ;
2014-03-14 14:13:41 -04:00
2017-09-07 22:18:47 -04:00
enum class EXGEMode
{
None ,
Xml ,
Intercept
} ;
static EXGEMode GXGEMode = EXGEMode : : None ;
inline bool IsUsingXGE ( )
{
return GXGEMode ! = EXGEMode : : None ;
}
2022-12-07 10:29:08 -05:00
static FSCWErrorCode : : ECode GFailedErrorCode = FSCWErrorCode : : Success ;
2015-07-13 18:02:21 -04:00
2017-09-07 22:18:47 -04:00
static void OnXGEJobCompleted ( const TCHAR * WorkingDirectory )
2014-12-16 20:00:07 -05:00
{
2017-09-07 22:18:47 -04:00
if ( GXGEMode = = EXGEMode : : Xml )
{
2020-01-24 12:16:02 -05:00
// To signal compilation completion, create a zero length file in the working directory.
2017-09-07 22:18:47 -04:00
// This is only required in Xml mode.
2020-01-24 12:16:02 -05:00
delete IFileManager : : Get ( ) . CreateFileWriter ( * FString : : Printf ( TEXT ( " %s/Success " ) , WorkingDirectory ) , FILEWRITE_EvenIfReadOnly ) ;
2017-09-07 22:18:47 -04:00
}
2014-12-16 20:00:07 -05:00
}
2016-09-15 00:21:42 -04:00
# if USING_CODE_ANALYSIS
2022-12-07 10:29:08 -05:00
UE_NORETURN static inline void ExitWithoutCrash ( FSCWErrorCode : : ECode ErrorCode , const FString & Message ) ;
2016-09-15 00:21:42 -04:00
# endif
2022-12-07 10:29:08 -05:00
static inline void ExitWithoutCrash ( FSCWErrorCode : : ECode ErrorCode , const FString & Message )
2016-09-12 22:23:27 -04:00
{
GFailedErrorCode = ErrorCode ;
FCString : : Snprintf ( GErrorExceptionDescription , sizeof ( GErrorExceptionDescription ) , TEXT ( " %s " ) , * Message ) ;
UE_LOG ( LogShaders , Fatal , TEXT ( " %s " ) , * Message ) ;
}
2015-07-13 18:02:21 -04:00
static const TArray < const IShaderFormat * > & GetShaderFormats ( )
2014-03-14 14:13:41 -04:00
{
static bool bInitialized = false ;
static TArray < const IShaderFormat * > Results ;
if ( ! bInitialized )
{
bInitialized = true ;
Results . Empty ( Results . Num ( ) ) ;
TArray < FName > Modules ;
FModuleManager : : Get ( ) . FindModules ( SHADERFORMAT_MODULE_WILDCARD , Modules ) ;
if ( ! Modules . Num ( ) )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : NoTargetShaderFormatsFound , TEXT ( " No target shader formats found! " ) ) ;
2014-03-14 14:13:41 -04:00
}
for ( int32 Index = 0 ; Index < Modules . Num ( ) ; Index + + )
{
IShaderFormat * Format = FModuleManager : : LoadModuleChecked < IShaderFormatModule > ( Modules [ Index ] ) . GetShaderFormat ( ) ;
2014-08-01 02:40:54 -04:00
if ( Format ! = nullptr )
2014-03-14 14:13:41 -04:00
{
Results . Add ( Format ) ;
}
}
}
return Results ;
}
2018-05-15 11:11:48 -04:00
static void UpdateFileSize ( FArchive & OutputFile , int64 FileSizePosition )
{
int64 Current = OutputFile . Tell ( ) ;
OutputFile . Seek ( FileSizePosition ) ;
OutputFile < < Current ;
OutputFile . Seek ( Current ) ;
} ;
2022-12-07 10:29:08 -05:00
static const TCHAR * GetLocalHostname ( int32 * OutHostnameLength = nullptr )
{
static FString Hostname ;
if ( Hostname . IsEmpty ( ) )
{
ISocketSubsystem : : Get ( ) - > GetHostName ( Hostname ) ;
}
if ( OutHostnameLength )
{
* OutHostnameLength = Hostname . Len ( ) ;
}
return * Hostname ;
}
2018-05-15 11:11:48 -04:00
static int64 WriteOutputFileHeader ( FArchive & OutputFile , int32 ErrorCode , int32 CallstackLength , const TCHAR * Callstack ,
int32 ExceptionInfoLength , const TCHAR * ExceptionInfo )
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( WriteOutputFileHeader ) ;
2018-05-15 11:11:48 -04:00
int64 FileSizePosition = 0 ;
int32 OutputVersion = ShaderCompileWorkerOutputVersion ;
OutputFile < < OutputVersion ;
int64 FileSize = 0 ;
// Get the position of the Size value to be patched in as the shader progresses
FileSizePosition = OutputFile . Tell ( ) ;
OutputFile < < FileSize ;
OutputFile < < ErrorCode ;
2018-09-11 14:44:10 -04:00
OutputFile < < GNumProcessedJobs ;
2018-05-15 11:11:48 -04:00
// Note: Can't use FStrings here as SEH can't be used with destructors
OutputFile < < CallstackLength ;
OutputFile < < ExceptionInfoLength ;
2022-12-07 10:29:08 -05:00
int32 HostnameLength = 0 ;
const TCHAR * Hostname = GetLocalHostname ( & HostnameLength ) ;
OutputFile < < HostnameLength ;
2022-12-07 11:27:09 -05:00
if ( ErrorCode ! = FSCWErrorCode : : Success )
2018-05-15 11:11:48 -04:00
{
2022-12-07 11:27:09 -05:00
if ( CallstackLength > 0 )
{
OutputFile . Serialize ( ( void * ) Callstack , CallstackLength * sizeof ( TCHAR ) ) ;
}
2018-05-15 11:11:48 -04:00
2022-12-07 11:27:09 -05:00
if ( ExceptionInfoLength > 0 )
{
OutputFile . Serialize ( ( void * ) ExceptionInfo , ExceptionInfoLength * sizeof ( TCHAR ) ) ;
}
2018-05-15 11:11:48 -04:00
2022-12-07 11:27:09 -05:00
if ( HostnameLength > 0 )
{
OutputFile . Serialize ( ( void * ) Hostname , HostnameLength * sizeof ( TCHAR ) ) ;
}
2022-12-07 10:29:08 -05:00
2022-12-07 11:27:09 -05:00
// Store available and used physical memory of host machine on OOM error
if ( ErrorCode = = FSCWErrorCode : : OutOfMemory )
{
2023-06-16 11:19:12 -04:00
FPlatformMemoryStats MemoryStats = FPlatformMemory : : GetStats ( ) ;
OutputFile
< < MemoryStats . AvailablePhysical
< < MemoryStats . AvailableVirtual
< < MemoryStats . UsedPhysical
< < MemoryStats . PeakUsedPhysical
< < MemoryStats . UsedVirtual
< < MemoryStats . PeakUsedVirtual
;
2022-12-07 11:27:09 -05:00
}
2022-12-07 10:29:08 -05:00
}
2022-12-13 18:21:32 -05:00
// Reset error code as it can be receive a new value now
FSCWErrorCode : : Reset ( ) ;
2018-05-15 11:11:48 -04:00
UpdateFileSize ( OutputFile , FileSizePosition ) ;
return FileSizePosition ;
}
2015-07-13 18:02:21 -04:00
2014-03-14 14:13:41 -04:00
class FWorkLoop
{
public :
2018-03-24 09:22:20 -04:00
// If we have been idle for 20 seconds then exit. Can be overriden from the cmd line with -TimeToLive=N where N is in seconds (and a float value)
float TimeToLive = 20.0f ;
2022-11-22 09:42:02 -05:00
bool DisableFileWrite = false ;
2022-11-29 10:00:37 -05:00
bool KeepInput = false ;
2018-03-24 09:22:20 -04:00
2017-08-24 15:38:57 -04:00
FWorkLoop ( const TCHAR * ParentProcessIdText , const TCHAR * InWorkingDirectory , const TCHAR * InInputFilename , const TCHAR * InOutputFilename , TMap < FString , uint32 > & InFormatVersionMap )
2014-03-14 14:13:41 -04:00
: ParentProcessId ( FCString : : Atoi ( ParentProcessIdText ) )
, WorkingDirectory ( InWorkingDirectory )
, InputFilename ( InInputFilename )
, OutputFilename ( InOutputFilename )
2022-11-02 13:27:30 -04:00
, InputFilePath ( FString ( InWorkingDirectory ) / InInputFilename )
, OutputFilePath ( FString ( InWorkingDirectory ) / InOutputFilename )
2015-07-13 18:02:21 -04:00
, FormatVersionMap ( InFormatVersionMap )
2014-03-14 14:13:41 -04:00
{
2018-03-24 09:22:20 -04:00
TArray < FString > Tokens , Switches ;
FCommandLine : : Parse ( FCommandLine : : Get ( ) , Tokens , Switches ) ;
for ( FString & Switch : Switches )
{
if ( Switch . StartsWith ( TEXT ( " TimeToLive= " ) ) )
{
2022-11-22 09:42:02 -05:00
TimeToLive = FCString : : Atof ( Switch . GetCharArray ( ) . GetData ( ) + 11 ) ;
}
else if ( Switch . Equals ( TEXT ( " DisableFileWrite " ) ) )
{
DisableFileWrite = true ;
2018-03-24 09:22:20 -04:00
}
2022-11-29 10:00:37 -05:00
else if ( Switch . Equals ( TEXT ( " KeepInput " ) ) )
{
KeepInput = true ;
}
2018-03-24 09:22:20 -04:00
}
2014-03-14 14:13:41 -04:00
}
void Loop ( )
{
UE_LOG ( LogShaders , Log , TEXT ( " Entering job loop " ) ) ;
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( Loop ) ;
2014-03-14 14:13:41 -04:00
while ( true )
{
2023-01-26 10:35:39 -05:00
TArray < FShaderCompileJob > SingleJobs ;
TArray < FShaderPipelineCompileJob > PipelineJobs ;
TArray < FString > PipelineJobNames ;
2014-03-14 14:13:41 -04:00
// Read & Process Input
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( ReadInput ) ;
2014-03-14 14:13:41 -04:00
FArchive * InputFilePtr = OpenInputFile ( ) ;
if ( ! InputFilePtr )
{
break ;
}
UE_LOG ( LogShaders , Log , TEXT ( " Processing shader " ) ) ;
2023-01-26 10:35:39 -05:00
ProcessInputFromArchive ( InputFilePtr , SingleJobs , PipelineJobs , PipelineJobNames ) ;
2014-03-14 14:13:41 -04:00
2015-12-10 21:55:37 -05:00
LastCompileTime = FPlatformTime : : Seconds ( ) ;
2014-03-14 14:13:41 -04:00
// Close the input file.
delete InputFilePtr ;
}
// Prepare for output
2022-11-22 09:42:02 -05:00
if ( DisableFileWrite )
{
// write to in-memory bytestream instead for debugging purposes
TArray < uint8 > MemBlock ;
FMemoryWriter MemWriter ( MemBlock ) ;
2023-01-26 10:35:39 -05:00
WriteToOutputArchive ( & MemWriter , SingleJobs , PipelineJobs , PipelineJobNames ) ;
2022-11-22 09:42:02 -05:00
}
else
{
FArchive * OutputFilePtr = CreateOutputArchive ( ) ;
check ( OutputFilePtr ) ;
2023-01-26 10:35:39 -05:00
WriteToOutputArchive ( OutputFilePtr , SingleJobs , PipelineJobs , PipelineJobNames ) ;
2022-11-22 09:42:02 -05:00
// Close the output file.
delete OutputFilePtr ;
2014-03-14 14:13:41 -04:00
2022-11-22 09:42:02 -05:00
// Change the output file name to requested one
IFileManager : : Get ( ) . Move ( * OutputFilePath , * TempFilePath ) ;
}
2014-03-14 14:13:41 -04:00
2017-09-07 22:18:47 -04:00
if ( IsUsingXGE ( ) )
2014-12-16 20:00:07 -05:00
{
// To signal compilation completion, create a zero length file in the working directory.
2017-09-07 22:18:47 -04:00
OnXGEJobCompleted ( * WorkingDirectory ) ;
2014-12-16 20:00:07 -05:00
// We only do one pass per process when using XGE.
break ;
}
2020-09-24 00:43:27 -04:00
2023-01-26 10:35:39 -05:00
if ( TimeToLive = = 0 | | AnyJobUsedHLSLccCompiler ( SingleJobs , PipelineJobs ) )
2020-09-24 00:43:27 -04:00
{
UE_LOG ( LogShaders , Log , TEXT ( " TimeToLive set to 0, or used HLSLcc compiler, exiting after single job " ) ) ;
break ;
}
2022-10-19 10:33:36 -04:00
# if ENABLE_LOW_LEVEL_MEM_TRACKER
{
TRACE_CPUPROFILER_EVENT_SCOPE ( UpdateStatsPerFrame ) ;
FLowLevelMemTracker : : Get ( ) . UpdateStatsPerFrame ( ) ;
}
# endif
2014-03-14 14:13:41 -04:00
}
UE_LOG ( LogShaders , Log , TEXT ( " Exiting job loop " ) ) ;
}
private :
const int32 ParentProcessId ;
const FString WorkingDirectory ;
const FString InputFilename ;
const FString OutputFilename ;
const FString InputFilePath ;
const FString OutputFilePath ;
2017-08-24 15:38:57 -04:00
TMap < FString , uint32 > FormatVersionMap ;
2014-03-14 14:13:41 -04:00
FString TempFilePath ;
/** Opens an input file, trying multiple times if necessary. */
FArchive * OpenInputFile ( )
{
2014-08-01 02:40:54 -04:00
FArchive * InputFile = nullptr ;
2014-03-14 14:13:41 -04:00
bool bFirstOpenTry = true ;
2019-09-12 14:21:26 -04:00
while ( ! InputFile & & ! IsEngineExitRequested ( ) )
2014-03-14 14:13:41 -04:00
{
// Try to open the input file that we are going to process
2015-12-10 21:55:37 -05:00
InputFile = IFileManager : : Get ( ) . CreateFileReader ( * InputFilePath , FILEREAD_Silent ) ;
2014-03-14 14:13:41 -04:00
if ( ! InputFile & & ! bFirstOpenTry )
{
CheckExitConditions ( ) ;
// Give up CPU time while we are waiting
FPlatformProcess : : Sleep ( 0.01f ) ;
}
bFirstOpenTry = false ;
}
return InputFile ;
}
2017-08-24 15:38:57 -04:00
void VerifyFormatVersions ( TMap < FString , uint32 > & ReceivedFormatVersionMap )
2015-07-13 18:02:21 -04:00
{
for ( auto Pair : ReceivedFormatVersionMap )
{
auto * Found = FormatVersionMap . Find ( Pair . Key ) ;
if ( Found )
{
2015-08-19 17:26:48 -04:00
if ( Pair . Value ! = * Found )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadShaderFormatVersion , FString : : Printf ( TEXT ( " Mismatched shader version for format %s: Found version %u but expected %u; did you forget to build ShaderCompilerWorker? " ) , * Pair . Key , * Found , Pair . Value ) ) ;
2015-08-19 17:26:48 -04:00
}
2015-07-13 18:02:21 -04:00
}
}
}
2023-01-26 10:35:39 -05:00
void ProcessInputFromArchive ( FArchive * InputFilePtr , TArray < FShaderCompileJob > & OutSingleJobs , TArray < FShaderPipelineCompileJob > & OutPipelineJobs , TArray < FString > & OutPipelineNames )
2014-03-14 14:13:41 -04:00
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( ProcessInputFromArchive ) ;
2014-03-14 14:13:41 -04:00
int32 InputVersion ;
2021-09-07 15:49:28 -04:00
* InputFilePtr < < InputVersion ;
2016-09-12 22:23:27 -04:00
if ( ShaderCompileWorkerInputVersion ! = InputVersion )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadInputVersion , FString : : Printf ( TEXT ( " Exiting due to ShaderCompilerWorker expecting input version %d, got %d instead! Did you forget to build ShaderCompilerWorker? " ) , ShaderCompileWorkerInputVersion , InputVersion ) ) ;
2016-09-12 22:23:27 -04:00
}
2015-07-13 18:02:21 -04:00
2021-09-07 15:49:28 -04:00
FString CompressionFormatString ;
* InputFilePtr < < CompressionFormatString ;
FName CompressionFormat ( * CompressionFormatString ) ;
bool bWasCompressed = ( CompressionFormat ! = NAME_None ) ;
TArray < uint8 > UncompressedData ;
if ( bWasCompressed )
{
int32 UncompressedDataSize = 0 ;
* InputFilePtr < < UncompressedDataSize ;
if ( UncompressedDataSize = = 0 )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadInputFile , TEXT ( " Exiting due to bad input file to ShaderCompilerWorker (uncompressed size is 0)! Did you forget to build ShaderCompilerWorker? " ) ) ;
2021-09-07 15:49:28 -04:00
// unreachable
return ;
}
UncompressedData . SetNumUninitialized ( UncompressedDataSize ) ;
TArray < uint8 > CompressedData ;
* InputFilePtr < < CompressedData ;
if ( ! FCompression : : UncompressMemory ( CompressionFormat , UncompressedData . GetData ( ) , UncompressedDataSize , CompressedData . GetData ( ) , CompressedData . Num ( ) ) )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadInputFile , FString : : Printf ( TEXT ( " Exiting due to bad input file to ShaderCompilerWorker (cannot uncompress with the format %s)! Did you forget to build ShaderCompilerWorker? " ) , * CompressionFormatString ) ) ;
2021-09-07 15:49:28 -04:00
// unreachable
return ;
}
}
FMemoryReader InputMemory ( UncompressedData ) ;
FArchive & InputFile = bWasCompressed ? InputMemory : * InputFilePtr ;
2017-08-24 15:38:57 -04:00
TMap < FString , uint32 > ReceivedFormatVersionMap ;
2015-07-13 18:02:21 -04:00
InputFile < < ReceivedFormatVersionMap ;
VerifyFormatVersions ( ReceivedFormatVersionMap ) ;
2017-06-27 11:38:28 -04:00
// Apply shader source directory mappings.
{
TMap < FString , FString > DirectoryMappings ;
InputFile < < DirectoryMappings ;
2018-09-11 14:44:10 -04:00
ResetAllShaderSourceDirectoryMappings ( ) ;
2019-02-28 10:47:22 -05:00
for ( TPair < FString , FString > & MappingEntry : DirectoryMappings )
2017-06-27 11:38:28 -04:00
{
2019-02-28 10:47:22 -05:00
FPaths : : NormalizeDirectoryName ( MappingEntry . Value ) ;
2018-09-11 14:44:10 -04:00
AddShaderSourceDirectoryMapping ( MappingEntry . Key , MappingEntry . Value ) ;
2017-06-27 11:38:28 -04:00
}
}
2014-03-14 14:13:41 -04:00
2018-09-11 14:44:10 -04:00
// Initialize shader hash cache before reading any includes.
InitializeShaderHashCache ( ) ;
2021-03-16 16:19:23 -04:00
// Array of string used as const TCHAR* during compilation process.
TArray < TUniquePtr < FString > > AllocatedStrings ;
auto DeserializeConstTCHAR = [ & AllocatedStrings ] ( FArchive & Archive )
{
FString Name ;
Archive < < Name ;
const TCHAR * CharName = nullptr ;
if ( Name . Len ( ) ! = 0 )
{
if ( AllocatedStrings . GetSlack ( ) = = 0 )
{
AllocatedStrings . Reserve ( AllocatedStrings . Num ( ) + 1024 ) ;
}
AllocatedStrings . Add ( MakeUnique < FString > ( Name ) ) ;
CharName = * * AllocatedStrings . Last ( ) ;
}
return CharName ;
} ;
2017-12-15 12:47:47 -05:00
2021-08-18 16:56:25 -04:00
// Array of string used as const ANSICHAR* during compilation process.
TArray < TUniquePtr < TArray < ANSICHAR > > > AllocatedAnsiStrings ;
auto DeserializeConstANSICHAR = [ & AllocatedAnsiStrings ] ( FArchive & Archive )
{
FString Name ;
Archive < < Name ;
const ANSICHAR * CharName = nullptr ;
if ( Name . Len ( ) ! = 0 )
{
if ( AllocatedAnsiStrings . GetSlack ( ) = = 0 )
{
AllocatedAnsiStrings . Reserve ( AllocatedAnsiStrings . Num ( ) + 1024 ) ;
}
TArray < ANSICHAR > AnsiString ;
AnsiString . SetNumZeroed ( Name . Len ( ) + 1 ) ;
ANSICHAR * Dest = & AnsiString [ 0 ] ;
FCStringAnsi : : Strcpy ( Dest , Name . Len ( ) + 1 , TCHAR_TO_ANSI ( * Name ) ) ;
AllocatedAnsiStrings . Add ( MakeUnique < TArray < ANSICHAR > > ( AnsiString ) ) ;
CharName = & ( * AllocatedAnsiStrings . Last ( ) ) [ 0 ] ;
}
return CharName ;
} ;
2017-12-15 12:47:47 -05:00
// Shared inputs
2021-03-16 16:19:23 -04:00
TMap < FString , FThreadSafeSharedStringPtr > ExternalIncludes ;
2017-12-15 12:47:47 -05:00
{
int32 NumExternalIncludes = 0 ;
InputFile < < NumExternalIncludes ;
ExternalIncludes . Reserve ( NumExternalIncludes ) ;
for ( int32 IncludeIndex = 0 ; IncludeIndex < NumExternalIncludes ; IncludeIndex + + )
{
FString NewIncludeName ;
InputFile < < NewIncludeName ;
FString * NewIncludeContents = new FString ( ) ;
InputFile < < ( * NewIncludeContents ) ;
ExternalIncludes . Add ( NewIncludeName , MakeShareable ( NewIncludeContents ) ) ;
}
2021-03-16 16:19:23 -04:00
}
2017-12-15 12:47:47 -05:00
2021-03-16 16:19:23 -04:00
// Shared environments
TArray < FShaderCompilerEnvironment > SharedEnvironments ;
{
2017-12-15 12:47:47 -05:00
int32 NumSharedEnvironments = 0 ;
InputFile < < NumSharedEnvironments ;
SharedEnvironments . Empty ( NumSharedEnvironments ) ;
SharedEnvironments . AddDefaulted ( NumSharedEnvironments ) ;
for ( int32 EnvironmentIndex = 0 ; EnvironmentIndex < NumSharedEnvironments ; EnvironmentIndex + + )
{
InputFile < < SharedEnvironments [ EnvironmentIndex ] ;
}
}
2021-03-16 16:19:23 -04:00
// All the shader parameter structures
// Note: this is a bit more complicated, purposefully to avoid switch const TCHAR* to FString in runtime FShaderParametersMetadata.
TArray < TUniquePtr < FShaderParametersMetadata > > ParameterStructures ;
{
int32 NumParameterStructures = 0 ;
InputFile < < NumParameterStructures ;
ParameterStructures . Reserve ( NumParameterStructures ) ;
for ( int32 StructIndex = 0 ; StructIndex < NumParameterStructures ; StructIndex + + )
{
const TCHAR * LayoutName ;
const TCHAR * StructTypeName ;
const TCHAR * ShaderVariableName ;
FShaderParametersMetadata : : EUseCase UseCase ;
2021-08-18 16:56:25 -04:00
const ANSICHAR * StructFileName ;
2021-03-16 16:19:23 -04:00
int32 StructFileLine ;
uint32 Size ;
int32 MemberCount ;
LayoutName = DeserializeConstTCHAR ( InputFile ) ;
StructTypeName = DeserializeConstTCHAR ( InputFile ) ;
ShaderVariableName = DeserializeConstTCHAR ( InputFile ) ;
InputFile < < UseCase ;
2021-08-18 16:56:25 -04:00
StructFileName = DeserializeConstANSICHAR ( InputFile ) ;
2021-03-16 16:19:23 -04:00
InputFile < < StructFileLine ;
InputFile < < Size ;
InputFile < < MemberCount ;
TArray < FShaderParametersMetadata : : FMember > Members ;
Members . Reserve ( MemberCount ) ;
for ( int32 MemberIndex = 0 ; MemberIndex < MemberCount ; MemberIndex + + )
{
const TCHAR * Name ;
const TCHAR * ShaderType ;
int32 FileLine ;
uint32 Offset ;
uint8 BaseType ;
uint8 PrecisionModifier ;
uint32 NumRows ;
uint32 NumColumns ;
uint32 NumElements ;
int32 StructMetadataIndex ;
static_assert ( sizeof ( BaseType ) = = sizeof ( EUniformBufferBaseType ) , " Cast failure. " ) ;
static_assert ( sizeof ( PrecisionModifier ) = = sizeof ( EShaderPrecisionModifier : : Type ) , " Cast failure. " ) ;
Name = DeserializeConstTCHAR ( InputFile ) ;
ShaderType = DeserializeConstTCHAR ( InputFile ) ;
InputFile < < FileLine ;
InputFile < < Offset ;
InputFile < < BaseType ;
InputFile < < PrecisionModifier ;
InputFile < < NumRows ;
InputFile < < NumColumns ;
InputFile < < NumElements ;
InputFile < < StructMetadataIndex ;
2021-03-17 03:17:06 -04:00
if ( ShaderType = = nullptr )
{
ShaderType = TEXT ( " " ) ;
}
2021-03-16 16:19:23 -04:00
const FShaderParametersMetadata * StructMetadata = nullptr ;
if ( StructMetadataIndex ! = INDEX_NONE )
{
StructMetadata = ParameterStructures [ StructMetadataIndex ] . Get ( ) ;
}
FShaderParametersMetadata : : FMember Member (
Name ,
ShaderType ,
FileLine ,
Offset ,
EUniformBufferBaseType ( BaseType ) ,
EShaderPrecisionModifier : : Type ( PrecisionModifier ) ,
NumRows ,
NumColumns ,
NumElements ,
StructMetadata ) ;
Members . Add ( Member ) ;
}
ParameterStructures . Add ( MakeUnique < FShaderParametersMetadata > (
UseCase ,
EUniformBufferBindingFlags : : Shader ,
/* InLayoutName = */ LayoutName ,
/* InStructTypeName = */ StructTypeName ,
/* InShaderVariableName = */ ShaderVariableName ,
/* InStaticSlotName = */ nullptr ,
2021-08-18 16:56:25 -04:00
StructFileName ,
2021-03-16 16:19:23 -04:00
StructFileLine ,
Size ,
Members ,
/* bCompleteInitialization = */ true ) ) ;
}
}
2018-09-11 14:44:10 -04:00
GNumProcessedJobs = 0 ;
2015-10-30 17:41:13 -04:00
// Individual jobs
2014-03-14 14:13:41 -04:00
{
2015-10-30 17:41:13 -04:00
int32 SingleJobHeader = ShaderCompileWorkerSingleJobHeader ;
InputFile < < SingleJobHeader ;
2016-09-12 22:23:27 -04:00
if ( ShaderCompileWorkerSingleJobHeader ! = SingleJobHeader )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadSingleJobHeader , FString : : Printf ( TEXT ( " Exiting due to ShaderCompilerWorker expecting job header %d, got %d instead! Did you forget to build ShaderCompilerWorker? " ) , ShaderCompileWorkerSingleJobHeader , SingleJobHeader ) ) ;
2016-09-12 22:23:27 -04:00
}
2014-03-14 14:13:41 -04:00
2015-10-30 17:41:13 -04:00
int32 NumBatches = 0 ;
InputFile < < NumBatches ;
2022-06-29 11:51:42 -04:00
2022-11-01 16:31:27 -04:00
FlushShaderFileCache ( ) ;
2023-01-26 10:35:39 -05:00
OutSingleJobs . Reserve ( NumBatches ) ;
2022-07-04 19:06:33 -04:00
2015-10-30 17:41:13 -04:00
for ( int32 BatchIndex = 0 ; BatchIndex < NumBatches ; BatchIndex + + )
2014-03-14 14:13:41 -04:00
{
2023-05-09 05:17:49 -04:00
FShaderCompileJob & Job = OutSingleJobs . AddDefaulted_GetRef ( ) ;
2015-10-30 17:41:13 -04:00
// Deserialize the job's inputs.
2023-05-03 10:17:48 -04:00
Job . SerializeWorkerInput ( InputFile ) ;
2023-01-26 10:35:39 -05:00
Job . Input . DeserializeSharedInputs ( InputFile , ExternalIncludes , SharedEnvironments , ParameterStructures ) ;
2014-03-14 14:13:41 -04:00
2022-08-31 04:38:34 -04:00
// SCW doesn't run DDPI, GShaderHasCache Initialize is run at start with no knowledge of the CustomPlatforms
// CustomPlatforms are known when we parse the WorkerInput so we populate the Directory here
2023-01-26 10:35:39 -05:00
if ( IsCustomPlatform ( ( EShaderPlatform ) Job . Input . Target . Platform ) )
2022-08-31 04:38:34 -04:00
{
2023-01-26 10:35:39 -05:00
const EShaderPlatform ShaderPlatform = ShaderFormatNameToShaderPlatform ( Job . Input . ShaderFormat ) ;
UpdateIncludeDirectoryForPreviewPlatform ( ( EShaderPlatform ) Job . Input . Target . Platform , ShaderPlatform ) ;
2022-08-31 04:38:34 -04:00
}
2015-10-30 17:41:13 -04:00
// Process the job.
2023-01-26 10:35:39 -05:00
CompileShader ( GetShaderFormats ( ) , Job , WorkingDirectory , & GNumProcessedJobs ) ;
2015-10-30 17:41:13 -04:00
}
}
// Shader pipeline jobs
{
int32 PipelineJobHeader = ShaderCompileWorkerPipelineJobHeader ;
InputFile < < PipelineJobHeader ;
2016-09-12 22:23:27 -04:00
if ( ShaderCompileWorkerPipelineJobHeader ! = PipelineJobHeader )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : BadPipelineJobHeader , FString : : Printf ( TEXT ( " Exiting due to ShaderCompilerWorker expecting pipeline job header %d, got %d instead! Did you forget to build ShaderCompilerWorker? " ) , ShaderCompileWorkerSingleJobHeader , PipelineJobHeader ) ) ;
2016-09-12 22:23:27 -04:00
}
2015-10-30 17:41:13 -04:00
int32 NumPipelines = 0 ;
InputFile < < NumPipelines ;
2015-10-31 10:55:13 -04:00
2023-01-26 10:35:39 -05:00
OutPipelineNames . Reserve ( NumPipelines ) ;
OutPipelineJobs . Reserve ( NumPipelines ) ;
2015-10-31 10:55:13 -04:00
for ( int32 Index = 0 ; Index < NumPipelines ; + + Index )
{
2023-05-09 05:17:49 -04:00
FString & PipelineName = OutPipelineNames . AddDefaulted_GetRef ( ) ;
2023-01-26 10:35:39 -05:00
InputFile < < PipelineName ;
2015-10-31 10:55:13 -04:00
int32 NumStages = 0 ;
InputFile < < NumStages ;
2023-05-09 05:17:49 -04:00
FShaderPipelineCompileJob & PipelineJob = OutPipelineJobs . Emplace_GetRef ( NumStages ) ;
2015-10-31 10:55:13 -04:00
for ( int32 StageIndex = 0 ; StageIndex < NumStages ; + + StageIndex )
{
// Deserialize the job's inputs.
2023-05-09 05:17:49 -04:00
FShaderCompileJob * Job = PipelineJob . StageJobs [ StageIndex ] - > GetSingleShaderJob ( ) ;
2023-05-03 10:17:48 -04:00
Job - > SerializeWorkerInput ( InputFile ) ;
2023-01-26 10:35:39 -05:00
Job - > Input . DeserializeSharedInputs ( InputFile , ExternalIncludes , SharedEnvironments , ParameterStructures ) ;
2015-10-31 10:55:13 -04:00
2022-08-31 04:38:34 -04:00
// SCW doesn't run DDPI, GShaderHasCache Initialize is run at start with no knowledge of the CustomPlatforms
// CustomPlatforms are known when we parse the WorkerInput so we populate the Directory here
2023-01-26 10:35:39 -05:00
if ( IsCustomPlatform ( ( EShaderPlatform ) Job - > Input . Target . Platform ) )
2022-08-31 04:38:34 -04:00
{
2023-01-26 10:35:39 -05:00
const EShaderPlatform ShaderPlatform = ShaderFormatNameToShaderPlatform ( Job - > Input . ShaderFormat ) ;
UpdateIncludeDirectoryForPreviewPlatform ( ( EShaderPlatform ) Job - > Input . Target . Platform , ShaderPlatform ) ;
2022-08-31 04:38:34 -04:00
}
2015-10-31 10:55:13 -04:00
}
2023-05-09 05:17:49 -04:00
CompileShaderPipeline ( GetShaderFormats ( ) , & PipelineJob , WorkingDirectory , & GNumProcessedJobs ) ;
2015-10-31 10:55:13 -04:00
}
}
}
2014-03-14 14:13:41 -04:00
FArchive * CreateOutputArchive ( )
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( CreateOutputArchive ) ;
2014-08-01 02:40:54 -04:00
FArchive * OutputFilePtr = nullptr ;
2015-12-10 21:55:37 -05:00
const double StartTime = FPlatformTime : : Seconds ( ) ;
bool bResult = false ;
// It seems XGE does not support deleting files.
2022-11-29 10:00:37 -05:00
// Don't delete the input file if we are running under Incredibuild (or if the cmdline args explicitly told us to keep it).
2017-09-07 22:18:47 -04:00
// In xml mode, we signal completion by creating a zero byte "Success" file after the output file has been fully written.
// In intercept mode, completion is signaled by this process terminating.
2022-11-29 10:00:37 -05:00
if ( ! IsUsingXGE ( ) & & ! KeepInput )
2014-03-14 14:13:41 -04:00
{
2014-04-23 18:06:09 -04:00
do
{
2015-12-10 21:55:37 -05:00
// Remove the input file so that it won't get processed more than once
bResult = IFileManager : : Get ( ) . Delete ( * InputFilePath ) ;
2014-04-23 18:06:09 -04:00
}
2015-12-10 21:55:37 -05:00
while ( ! bResult & & ( FPlatformTime : : Seconds ( ) - StartTime < 2 ) ) ;
if ( ! bResult )
2014-04-23 18:06:09 -04:00
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : CantDeleteInputFile , FString : : Printf ( TEXT ( " Couldn't delete input file %s, is it readonly? " ) , * InputFilePath ) ) ;
2014-04-23 18:06:09 -04:00
}
2014-03-14 14:13:41 -04:00
}
2015-12-10 21:55:37 -05:00
// To make sure that the process waiting for results won't read unfinished output file,
// we use a temp file name during compilation.
do
{
FGuid Guid ;
FPlatformMisc : : CreateGuid ( Guid ) ;
TempFilePath = WorkingDirectory + Guid . ToString ( ) ;
} while ( IFileManager : : Get ( ) . FileSize ( * TempFilePath ) ! = INDEX_NONE ) ;
const double StartTime2 = FPlatformTime : : Seconds ( ) ;
do
{
// Create the output file.
OutputFilePtr = IFileManager : : Get ( ) . CreateFileWriter ( * TempFilePath , FILEWRITE_EvenIfReadOnly ) ;
}
while ( ! OutputFilePtr & & ( FPlatformTime : : Seconds ( ) - StartTime2 < 2 ) ) ;
if ( ! OutputFilePtr )
{
2022-12-07 10:29:08 -05:00
ExitWithoutCrash ( FSCWErrorCode : : CantSaveOutputFile , FString : : Printf ( TEXT ( " Couldn't save output file %s " ) , * TempFilePath ) ) ;
2015-12-10 21:55:37 -05:00
}
2014-03-14 14:13:41 -04:00
return OutputFilePtr ;
}
2023-01-26 10:35:39 -05:00
void WriteToOutputArchive ( FArchive * OutputFilePtr , TArray < FShaderCompileJob > & SingleJobs , TArray < FShaderPipelineCompileJob > & PipelineJobs , TArray < FString > & PipelineNames )
2014-03-14 14:13:41 -04:00
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( WriteToOutputArchive ) ;
2014-03-14 14:13:41 -04:00
FArchive & OutputFile = * OutputFilePtr ;
2022-12-07 10:29:08 -05:00
const int64 FileSizePosition = FSCWErrorCode : : IsSet ( )
? WriteOutputFileHeader ( OutputFile , FSCWErrorCode : : Get ( ) , 0 , nullptr , FSCWErrorCode : : GetInfo ( ) . Len ( ) , * FSCWErrorCode : : GetInfo ( ) )
: WriteOutputFileHeader ( OutputFile , FSCWErrorCode : : Success , 0 , nullptr , 0 , nullptr ) ;
2014-03-14 14:13:41 -04:00
{
2015-10-30 17:41:13 -04:00
int32 SingleJobHeader = ShaderCompileWorkerSingleJobHeader ;
OutputFile < < SingleJobHeader ;
2023-01-26 10:35:39 -05:00
int32 NumBatches = SingleJobs . Num ( ) ;
2015-10-30 17:41:13 -04:00
OutputFile < < NumBatches ;
2023-01-26 10:35:39 -05:00
for ( int32 JobIndex = 0 ; JobIndex < SingleJobs . Num ( ) ; JobIndex + + )
2015-10-30 17:41:13 -04:00
{
2023-01-27 17:02:08 -05:00
SingleJobs [ JobIndex ] . SerializeWorkerOutput ( OutputFile ) ;
2018-05-15 11:11:48 -04:00
UpdateFileSize ( OutputFile , FileSizePosition ) ;
2015-10-30 17:41:13 -04:00
}
}
{
int32 PipelineJobHeader = ShaderCompileWorkerPipelineJobHeader ;
OutputFile < < PipelineJobHeader ;
2023-01-26 10:35:39 -05:00
int32 NumBatches = PipelineJobs . Num ( ) ;
2015-10-31 10:55:13 -04:00
OutputFile < < NumBatches ;
2015-10-30 17:41:13 -04:00
2023-01-26 10:35:39 -05:00
for ( int32 JobIndex = 0 ; JobIndex < PipelineJobs . Num ( ) ; JobIndex + + )
2015-10-31 10:55:13 -04:00
{
2023-01-26 10:35:39 -05:00
auto & PipelineJob = PipelineJobs [ JobIndex ] ;
OutputFile < < PipelineNames [ JobIndex ] ;
bool bSucceeded = ( bool ) PipelineJob . bSucceeded ;
OutputFile < < bSucceeded ;
int32 NumStageJobs = PipelineJob . StageJobs . Num ( ) ;
2015-10-31 10:55:13 -04:00
OutputFile < < NumStageJobs ;
2023-01-26 10:35:39 -05:00
2015-10-31 10:55:13 -04:00
for ( int32 Index = 0 ; Index < NumStageJobs ; + + Index )
{
2023-01-27 17:02:08 -05:00
PipelineJob . StageJobs [ Index ] - > SerializeWorkerOutput ( OutputFile ) ;
2018-05-15 11:11:48 -04:00
UpdateFileSize ( OutputFile , FileSizePosition ) ;
2015-10-31 10:55:13 -04:00
}
}
2014-03-14 14:13:41 -04:00
}
}
/** Called in the idle loop, checks for conditions under which the helper should exit */
void CheckExitConditions ( )
{
2015-07-13 16:53:23 -04:00
if ( ! InputFilename . Contains ( TEXT ( " Only " ) ) )
2014-03-14 14:13:41 -04:00
{
2015-07-13 16:53:23 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " InputFilename did not contain 'Only', exiting after one job. " ) ) ;
2014-03-14 14:13:41 -04:00
FPlatformMisc : : RequestExit ( false ) ;
}
2014-07-25 00:07:42 -04:00
# if PLATFORM_MAC || PLATFORM_LINUX
2014-03-14 14:13:41 -04:00
if ( ! FPlatformMisc : : IsDebuggerPresent ( ) & & ParentProcessId > 0 )
{
// If the parent process is no longer running, exit
if ( ! FPlatformProcess : : IsApplicationRunning ( ParentProcessId ) )
{
2015-04-22 15:04:48 -04:00
FString FilePath = FString ( WorkingDirectory ) + InputFilename ;
checkf ( IFileManager : : Get ( ) . FileSize ( * FilePath ) = = INDEX_NONE , TEXT ( " Exiting due to the parent process no longer running and the input file is present! " ) ) ;
2014-03-14 14:13:41 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " Parent process no longer running, exiting " ) ) ;
FPlatformMisc : : RequestExit ( false ) ;
}
}
const double CurrentTime = FPlatformTime : : Seconds ( ) ;
2018-03-24 09:22:20 -04:00
if ( CurrentTime - LastCompileTime > TimeToLive )
2014-03-14 14:13:41 -04:00
{
2018-03-24 09:22:20 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " No jobs found for %f seconds, exiting " ) , ( float ) ( CurrentTime - LastCompileTime ) ) ;
2014-03-14 14:13:41 -04:00
FPlatformMisc : : RequestExit ( false ) ;
}
# else
// Don't do these if the debugger is present
//@todo - don't do these if Unreal is being debugged either
if ( ! IsDebuggerPresent ( ) )
{
if ( ParentProcessId > 0 )
{
2015-04-22 15:04:48 -04:00
FString FilePath = FString ( WorkingDirectory ) + InputFilename ;
2014-03-14 14:13:41 -04:00
bool bParentStillRunning = true ;
HANDLE ParentProcessHandle = OpenProcess ( SYNCHRONIZE , false , ParentProcessId ) ;
// If we couldn't open the process then it is no longer running, exit
2014-08-01 02:40:54 -04:00
if ( ParentProcessHandle = = nullptr )
2014-03-14 14:13:41 -04:00
{
2015-07-13 16:53:23 -04:00
checkf ( IFileManager : : Get ( ) . FileSize ( * FilePath ) = = INDEX_NONE , TEXT ( " Exiting due to OpenProcess(ParentProcessId) failing and the input file is present! " ) ) ;
2014-03-14 14:13:41 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " Couldn't OpenProcess, Parent process no longer running, exiting " ) ) ;
FPlatformMisc : : RequestExit ( false ) ;
}
else
{
// If we did open the process, that doesn't mean it is still running
// The process object stays alive as long as there are handles to it
// We need to check if the process has signaled, which indicates that it has exited
uint32 WaitResult = WaitForSingleObject ( ParentProcessHandle , 0 ) ;
if ( WaitResult ! = WAIT_TIMEOUT )
{
2015-07-13 16:53:23 -04:00
checkf ( IFileManager : : Get ( ) . FileSize ( * FilePath ) = = INDEX_NONE , TEXT ( " Exiting due to WaitForSingleObject(ParentProcessHandle) signaling and the input file is present! " ) ) ;
2014-03-14 14:13:41 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " WaitForSingleObject signaled, Parent process no longer running, exiting " ) ) ;
FPlatformMisc : : RequestExit ( false ) ;
}
CloseHandle ( ParentProcessHandle ) ;
}
}
const double CurrentTime = FPlatformTime : : Seconds ( ) ;
// If we have been idle for 20 seconds then exit
2018-03-24 09:22:20 -04:00
if ( CurrentTime - LastCompileTime > TimeToLive )
2014-03-14 14:13:41 -04:00
{
2018-03-24 09:22:20 -04:00
UE_LOG ( LogShaders , Log , TEXT ( " No jobs found for %f seconds, exiting " ) , ( float ) ( CurrentTime - LastCompileTime ) ) ;
2014-03-14 14:13:41 -04:00
FPlatformMisc : : RequestExit ( false ) ;
}
}
# endif
}
2020-09-24 00:43:27 -04:00
2023-01-26 10:35:39 -05:00
static bool AnyJobUsedHLSLccCompiler ( TArray < FShaderCompileJob > & SingleJobs , TArray < FShaderPipelineCompileJob > & PipelineJobs )
2020-09-24 00:43:27 -04:00
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( AnyJobUsedHLSLccCompiler ) ;
2023-01-26 10:35:39 -05:00
for ( int32 JobIndex = 0 ; JobIndex < SingleJobs . Num ( ) ; JobIndex + + )
2020-09-24 00:43:27 -04:00
{
2023-01-26 10:35:39 -05:00
if ( SingleJobs [ JobIndex ] . Output . bUsedHLSLccCompiler )
2020-09-24 00:43:27 -04:00
{
return true ;
}
}
2023-01-26 10:35:39 -05:00
for ( int32 JobIndex = 0 ; JobIndex < PipelineJobs . Num ( ) ; JobIndex + + )
2020-09-24 00:43:27 -04:00
{
2023-01-26 10:35:39 -05:00
FShaderPipelineCompileJob & PipelineJob = PipelineJobs [ JobIndex ] ;
for ( int32 Index = 0 ; Index < PipelineJob . StageJobs . Num ( ) ; + + Index )
2020-09-24 00:43:27 -04:00
{
2023-01-26 10:35:39 -05:00
if ( PipelineJob . StageJobs [ Index ] - > Output . bUsedHLSLccCompiler )
2020-09-24 00:43:27 -04:00
{
return true ;
}
}
}
return false ;
}
2014-03-14 14:13:41 -04:00
} ;
2017-03-21 17:46:52 -04:00
static void DirectCompile ( const TArray < const class IShaderFormat * > & ShaderFormats )
2015-12-10 21:55:37 -05:00
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( DirectCompile ) ;
2015-12-10 21:55:37 -05:00
// Find all the info required for compiling a single shader
TArray < FString > Tokens , Switches ;
FCommandLine : : Parse ( FCommandLine : : Get ( ) , Tokens , Switches ) ;
FString InputFile ;
FName FormatName ;
2022-07-04 19:06:33 -04:00
FName ShaderPlatformName ;
2015-12-10 21:55:37 -05:00
FString Entry = TEXT ( " Main " ) ;
2016-04-04 18:44:59 -04:00
bool bPipeline = false ;
2015-12-10 21:55:37 -05:00
EShaderFrequency Frequency = SF_Pixel ;
2016-06-08 16:02:23 -04:00
TArray < FString > UsedOutputs ;
bool bIncludeUsedOutputs = false ;
uint64 CFlags = 0 ;
2016-04-04 18:44:59 -04:00
for ( const FString & Token : Tokens )
2015-12-10 21:55:37 -05:00
{
2016-04-04 18:44:59 -04:00
if ( Switches . Contains ( Token ) )
2015-12-10 21:55:37 -05:00
{
2016-04-04 18:44:59 -04:00
if ( Token . StartsWith ( TEXT ( " format= " ) ) )
{
FormatName = FName ( * Token . RightChop ( 7 ) ) ;
}
else if ( Token . StartsWith ( TEXT ( " entry= " ) ) )
{
Entry = Token . RightChop ( 6 ) ;
2022-01-12 18:08:09 -05:00
// Remove quotations marks at beginning and end; happens when multiple entry points are specified, e.g. -entry="closesthit=A anyhit=B"
if ( Entry . Len ( ) > = 2 & & Entry [ 0 ] = = TEXT ( ' \" ' ) & & Entry [ Entry . Len ( ) - 1 ] = = TEXT ( ' \" ' ) )
{
Entry = Entry . Mid ( 1 , Entry . Len ( ) - 2 ) ;
}
2016-04-04 18:44:59 -04:00
}
2022-07-04 19:06:33 -04:00
else if ( Token . StartsWith ( TEXT ( " shaderPlatformName= " ) ) )
{
ShaderPlatformName = FName ( * Token . RightChop ( 19 ) ) ;
}
2016-06-08 16:02:23 -04:00
else if ( Token . StartsWith ( TEXT ( " cflags= " ) ) )
{
CFlags = FCString : : Atoi64 ( * Token . RightChop ( 7 ) ) ;
}
2016-04-04 18:44:59 -04:00
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " ps " ) ) )
{
Frequency = SF_Pixel ;
}
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " vs " ) ) )
{
Frequency = SF_Vertex ;
}
2021-04-14 17:05:02 -04:00
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " ms " ) ) )
{
Frequency = SF_Mesh ;
}
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " as " ) ) )
{
Frequency = SF_Amplification ;
}
2016-04-04 18:44:59 -04:00
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " gs " ) ) )
{
Frequency = SF_Geometry ;
}
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " cs " ) ) )
{
Frequency = SF_Compute ;
}
2019-01-03 11:42:46 -05:00
# if RHI_RAYTRACING
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " rgs " ) ) )
{
Frequency = SF_RayGen ;
}
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " rms " ) ) )
{
Frequency = SF_RayMiss ;
}
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " rhs " ) ) )
{
Frequency = SF_RayHitGroup ;
}
2019-06-11 18:27:07 -04:00
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " rcs " ) ) )
{
Frequency = SF_RayCallable ;
}
2019-01-03 11:42:46 -05:00
# endif // RHI_RAYTRACING
2016-04-04 18:44:59 -04:00
else if ( ! FCString : : Strcmp ( * Token , TEXT ( " pipeline " ) ) )
{
bPipeline = true ;
}
2016-06-08 16:02:23 -04:00
else if ( Token . StartsWith ( TEXT ( " usedoutputs= " ) ) )
{
FString Outputs = Token . RightChop ( 12 ) ;
bIncludeUsedOutputs = true ;
FString LHS , RHS ;
while ( Outputs . Split ( TEXT ( " + " ) , & LHS , & RHS ) )
{
Outputs = RHS ;
UsedOutputs . Add ( LHS ) ;
}
UsedOutputs . Add ( Outputs ) ;
}
2015-12-10 21:55:37 -05:00
}
2016-04-04 18:44:59 -04:00
else
2015-12-10 21:55:37 -05:00
{
2016-04-04 18:44:59 -04:00
if ( InputFile . Len ( ) = = 0 )
{
InputFile = Token ;
}
2015-12-10 21:55:37 -05:00
}
}
FString Dir = FPlatformProcess : : UserTempDir ( ) ;
2023-01-26 10:35:39 -05:00
FShaderCompileJob Job ;
Job . Input . EntryPointName = Entry ;
Job . Input . ShaderFormat = FormatName ;
Job . Input . ShaderPlatformName = ShaderPlatformName ;
Job . Input . VirtualSourceFilePath = InputFile ;
Job . Input . Target . Platform = ShaderFormatNameToShaderPlatform ( FormatName ) ;
Job . Input . Target . Frequency = Frequency ;
2023-05-19 14:50:25 -04:00
Job . Input . bSkipPreprocessedCache = true ;
2015-12-10 21:55:37 -05:00
2023-01-26 10:35:39 -05:00
Job . Input . Environment . CompilerFlags = FShaderCompilerFlags ( CFlags ) ;
2016-06-08 16:02:23 -04:00
2023-01-26 10:35:39 -05:00
Job . Input . bCompilingForShaderPipeline = bPipeline ;
Job . Input . bIncludeUsedOutputs = bIncludeUsedOutputs ;
Job . Input . UsedOutputs = UsedOutputs ;
2016-04-04 18:44:59 -04:00
2015-12-10 21:55:37 -05:00
FShaderCompilerOutput Output ;
2023-01-26 10:35:39 -05:00
CompileShader ( GetShaderFormats ( ) , Job , Dir , & GNumProcessedJobs ) ;
2015-12-10 21:55:37 -05:00
}
2014-03-14 14:13:41 -04:00
/**
* Main entrypoint, guarded by a try ... except.
* This expects 4 parameters:
* The image path and name
* The working directory path, which has to be unique to the instigating process and thread.
* The parent process Id
* The thread Id corresponding to this worker
*/
2015-12-10 21:55:37 -05:00
static int32 GuardedMain ( int32 argc , TCHAR * argv [ ] , bool bDirectMode )
2014-03-14 14:13:41 -04:00
{
2022-10-19 10:33:36 -04:00
FString ExtraCmdLine = TEXT ( " -NOPACKAGECACHE -ReduceThreadUsage -cpuprofilertrace -nocrashreports " ) ;
2020-01-24 12:16:02 -05:00
// When executing tasks remotely through XGE, enumerating files requires tcp/ip round-trips with
// the initiator, which can slow down engine initialization quite drastically.
// The idea here is to save the Ini and Modules manager state and reuse them on the workers
// to avoid all those directory enumeration during engine init.
FString IniBootstrapFilename ;
FString ModulesBootstrapFilename ;
2022-12-13 18:21:32 -05:00
// Register out-of-memory delegate to report error code on exit
FCoreDelegates : : GetOutOfMemoryDelegate ( ) . AddLambda (
[ ] ( )
{
FSCWErrorCode : : Report ( FSCWErrorCode : : OutOfMemory ) ;
}
) ;
2020-01-24 12:16:02 -05:00
if ( IsUsingXGE ( ) )
{
// Tie the bootstrap filenames to the xge job id to refresh bootstraps state every time a new build starts
// This allows the ini/modules and shadercompilerworker binaries to change between builds.
FGuid XGJobID ;
if ( FGuid : : Parse ( FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " xgJobID " ) ) , XGJobID ) )
{
FString XGJobIDString = XGJobID . ToString ( EGuidFormats : : DigitsWithHyphens ) ;
IniBootstrapFilename = FString : : Printf ( TEXT ( " %s/Bootstrap-%s.inis " ) , argv [ 1 ] , * XGJobIDString ) ;
ModulesBootstrapFilename = FString : : Printf ( TEXT ( " %s/Bootstrap-%s.modules " ) , argv [ 1 ] , * XGJobIDString ) ;
ExtraCmdLine . Appendf ( TEXT ( " -IniBootstrap= \" %s \" -ModulesBootstrap= \" %s \" " ) , * IniBootstrapFilename , * ModulesBootstrapFilename ) ;
// Use Windows API directly because required CreateFile flags are not supported by our current OS abstraction
# if PLATFORM_WINDOWS
// This is advantageous to have only a single worker do the init work instead of having all workers
// do a stampede of the initiator's machine all trying to enumerate directories at the same time.
// I've seen incoming TCP connections going through the roof (350 connections for 150 virtual CPUs)
// coming from workers doing all the same directory enumerations.
// This is not strictly required, but will improve performance when successful.
// Most likely a local worker will win the race and do a fast init.
FString MutexFilename = FString : : Printf ( TEXT ( " %s/Bootstrap-%s.mutex " ) , argv [ 1 ] , * XGJobIDString ) ;
// We need to implement a mutex scheme through a file for it to work with XGE's file virtualization layer.
// The first process to successfully create this file will have the honor of doing the complete initialization.
HANDLE MutexHandle =
CreateFileW (
* MutexFilename ,
GENERIC_WRITE ,
0 ,
nullptr ,
CREATE_NEW ,
FILE_ATTRIBUTE_NORMAL ,
nullptr ) ;
if ( MutexHandle ! = INVALID_HANDLE_VALUE )
{
// We won the race, proceed to initialization.
CloseHandle ( MutexHandle ) ;
}
else
{
// Wait until the race winner writes the last bootstrap file
// Due to a bug in XGE, some workers might never see the new file appear, we must proceed after some timeout value.
for ( int32 Index = 0 ; Index < 10 & & ! FPaths : : FileExists ( ModulesBootstrapFilename ) ; + + Index )
{
Sleep ( 100 ) ;
}
}
# endif
}
}
GEngineLoop . PreInit ( argc , argv , * ExtraCmdLine ) ;
2014-03-14 14:13:41 -04:00
# if DEBUG_USING_CONSOLE
GLogConsole - > Show ( true ) ;
# endif
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( Main ) ;
2020-01-24 12:16:02 -05:00
auto AtomicSave =
[ ] ( const FString & Filename , TFunctionRef < void ( const FString & TmpFile ) > SaveFunction )
{
if ( ! Filename . IsEmpty ( ) & & ! FPaths : : FileExists ( Filename ) )
{
// Use a tmp file for atomic publication and avoid reading incomplete state from other workers
FString TmpFile = FString : : Printf ( TEXT ( " %s-%s " ) , * Filename , * FGuid : : NewGuid ( ) . ToString ( ) ) ;
SaveFunction ( TmpFile ) ;
const bool bReplace = false ;
const bool bDoNotRetryOrError = true ;
const bool bEvenIfReadOnly = false ;
const bool bAttributes = false ;
IFileManager : : Get ( ) . Move ( * Filename , * TmpFile , bReplace , bEvenIfReadOnly , bAttributes , bDoNotRetryOrError ) ;
// In case this process lost the race and wasn't able to move the file, discard the tmp file.
IFileManager : : Get ( ) . Delete ( * TmpFile ) ;
}
} ;
AtomicSave ( IniBootstrapFilename , [ ] ( const FString & TmpFile ) { GConfig - > SaveCurrentStateForBootstrap ( * TmpFile ) ; } ) ;
AtomicSave ( ModulesBootstrapFilename , [ ] ( const FString & TmpFile ) { FModuleManager : : Get ( ) . SaveCurrentStateForBootstrap ( * TmpFile ) ; } ) ;
2022-12-01 09:27:13 -05:00
// Explicitly load ShaderPreprocessor module so it will run its initialization step
FModuleManager : : LoadModuleChecked < IModuleInterface > ( TEXT ( " ShaderPreprocessor " ) ) ;
2014-03-14 14:13:41 -04:00
// We just enumerate the shader formats here for debugging.
const TArray < const class IShaderFormat * > & ShaderFormats = GetShaderFormats ( ) ;
check ( ShaderFormats . Num ( ) ) ;
2017-08-24 15:38:57 -04:00
TMap < FString , uint32 > FormatVersionMap ;
2014-03-14 14:13:41 -04:00
for ( int32 Index = 0 ; Index < ShaderFormats . Num ( ) ; Index + + )
{
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( ShaderFormat ) ;
2014-03-14 14:13:41 -04:00
TArray < FName > OutFormats ;
ShaderFormats [ Index ] - > GetSupportedFormats ( OutFormats ) ;
check ( OutFormats . Num ( ) ) ;
for ( int32 InnerIndex = 0 ; InnerIndex < OutFormats . Num ( ) ; InnerIndex + + )
{
UE_LOG ( LogShaders , Display , TEXT ( " Available Shader Format %s " ) , * OutFormats [ InnerIndex ] . ToString ( ) ) ;
2017-08-24 15:38:57 -04:00
uint32 Version = ShaderFormats [ Index ] - > GetVersion ( OutFormats [ InnerIndex ] ) ;
2015-07-13 18:02:21 -04:00
FormatVersionMap . Add ( OutFormats [ InnerIndex ] . ToString ( ) , Version ) ;
2014-03-14 14:13:41 -04:00
}
}
LastCompileTime = FPlatformTime : : Seconds ( ) ;
2015-12-10 21:55:37 -05:00
if ( bDirectMode )
{
2017-03-21 17:46:52 -04:00
DirectCompile ( ShaderFormats ) ;
2015-12-10 21:55:37 -05:00
}
else
{
# if PLATFORM_WINDOWS
//@todo - would be nice to change application name or description to have the ThreadId in it for debugging purposes
SetConsoleTitle ( argv [ 3 ] ) ;
# endif
2014-03-14 14:13:41 -04:00
2022-10-19 10:33:36 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( FWorkLoop ) ;
2015-12-10 21:55:37 -05:00
FWorkLoop WorkLoop ( argv [ 2 ] , argv [ 1 ] , argv [ 4 ] , argv [ 5 ] , FormatVersionMap ) ;
WorkLoop . Loop ( ) ;
}
2014-03-14 14:13:41 -04:00
return 0 ;
}
2022-10-20 09:43:50 -04:00
2015-12-10 21:55:37 -05:00
static int32 GuardedMainWrapper ( int32 ArgC , TCHAR * ArgV [ ] , const TCHAR * CrashOutputFile , bool bDirectMode )
2014-03-14 14:13:41 -04:00
{
2020-07-02 13:21:15 -04:00
FTaskTagScope Scope ( ETaskTag : : EGameThread ) ;
2014-12-16 20:00:07 -05:00
// We need to know whether we are using XGE now, in case an exception
// is thrown before we parse the command line inside GuardedMain.
2017-09-07 22:18:47 -04:00
if ( ( ArgC > 6 ) & & FCString : : Strcmp ( ArgV [ 6 ] , TEXT ( " -xge_int " ) ) = = 0 )
{
GXGEMode = EXGEMode : : Intercept ;
}
else if ( ( ArgC > 6 ) & & FCString : : Strcmp ( ArgV [ 6 ] , TEXT ( " -xge_xml " ) ) = = 0 )
{
GXGEMode = EXGEMode : : Xml ;
}
else
{
GXGEMode = EXGEMode : : None ;
}
2014-12-16 20:00:07 -05:00
2014-03-14 14:13:41 -04:00
int32 ReturnCode = 0 ;
2014-07-25 00:07:42 -04:00
# if PLATFORM_WINDOWS
2014-03-14 14:13:41 -04:00
if ( FPlatformMisc : : IsDebuggerPresent ( ) )
# endif
{
2015-12-10 21:55:37 -05:00
ReturnCode = GuardedMain ( ArgC , ArgV , bDirectMode ) ;
2014-03-14 14:13:41 -04:00
}
2014-07-25 00:07:42 -04:00
# if PLATFORM_WINDOWS
2014-03-14 14:13:41 -04:00
else
{
2015-07-13 18:02:21 -04:00
// Don't want 32 dialogs popping up when SCW fails
GUseCrashReportClient = false ;
2022-10-20 09:43:50 -04:00
FString ExceptionMsg ;
FString ExceptionCallStack ;
2014-03-14 14:13:41 -04:00
__try
{
GIsGuarded = 1 ;
2015-12-10 21:55:37 -05:00
ReturnCode = GuardedMain ( ArgC , ArgV , bDirectMode ) ;
2014-03-14 14:13:41 -04:00
GIsGuarded = 0 ;
}
2023-01-20 09:39:05 -05:00
__except ( HandleShaderCompileException ( GetExceptionInformation ( ) , ExceptionMsg , ExceptionCallStack ) )
2014-03-14 14:13:41 -04:00
{
2020-06-23 18:40:00 -04:00
FArchive & OutputFile = * IFileManager : : Get ( ) . CreateFileWriter ( CrashOutputFile , FILEWRITE_EvenIfReadOnly ) ;
2014-03-14 14:13:41 -04:00
2022-12-07 10:29:08 -05:00
if ( GFailedErrorCode = = FSCWErrorCode : : Success )
2016-09-12 22:23:27 -04:00
{
2022-12-07 10:29:08 -05:00
if ( FSCWErrorCode : : IsSet ( ) )
2020-04-16 16:36:56 -04:00
{
// Use the value set inside the shader format
2022-12-07 10:29:08 -05:00
GFailedErrorCode = FSCWErrorCode : : Get ( ) ;
2020-04-16 16:36:56 -04:00
}
else
{
// Something else failed before we could set the error code, so mark it as a General Crash
2022-12-07 10:29:08 -05:00
GFailedErrorCode = FSCWErrorCode : : GeneralCrash ;
2020-04-16 16:36:56 -04:00
}
2016-09-12 22:23:27 -04:00
}
2022-12-07 10:29:08 -05:00
int64 FileSizePosition = WriteOutputFileHeader ( OutputFile , GFailedErrorCode , ExceptionCallStack . Len ( ) , * ExceptionCallStack ,
2022-10-20 09:43:50 -04:00
ExceptionMsg . Len ( ) , * ExceptionMsg ) ;
2014-03-14 14:13:41 -04:00
int32 NumBatches = 0 ;
OutputFile < < NumBatches ;
2018-05-15 11:11:48 -04:00
OutputFile < < NumBatches ;
UpdateFileSize ( OutputFile , FileSizePosition ) ;
2014-03-14 14:13:41 -04:00
// Close the output file.
delete & OutputFile ;
2014-12-16 20:00:07 -05:00
2017-09-07 22:18:47 -04:00
if ( IsUsingXGE ( ) )
2014-12-16 20:00:07 -05:00
{
ReturnCode = 1 ;
2017-09-07 22:18:47 -04:00
OnXGEJobCompleted ( ArgV [ 1 ] ) ;
2014-12-16 20:00:07 -05:00
}
2014-03-14 14:13:41 -04:00
}
}
# endif
FEngineLoop : : AppPreExit ( ) ;
2017-08-30 15:02:07 -04:00
FModuleManager : : Get ( ) . UnloadModulesAtShutdown ( ) ;
2014-03-14 14:13:41 -04:00
FEngineLoop : : AppExit ( ) ;
return ReturnCode ;
}
IMPLEMENT_APPLICATION ( ShaderCompileWorker , " ShaderCompileWorker " )
/**
* Application entry point
*
* @param ArgC Command-line argument count
* @param ArgV Argument strings
*/
2014-04-23 20:10:59 -04:00
INT32_MAIN_INT32_ARGC_TCHAR_ARGV ( )
2014-03-14 14:13:41 -04:00
{
2017-09-07 22:18:47 -04:00
// Redirect for special XGE utilities...
extern bool XGEMain ( int ArgC , TCHAR * ArgV [ ] , int32 & ReturnCode ) ;
2014-12-16 20:00:07 -05:00
{
2017-09-07 22:18:47 -04:00
int32 ReturnCode ;
if ( XGEMain ( ArgC , ArgV , ReturnCode ) )
2014-12-16 20:00:07 -05:00
{
2017-09-07 22:18:47 -04:00
return ReturnCode ;
2018-05-15 11:11:48 -04:00
}
2014-12-16 20:00:07 -05:00
}
2017-09-07 22:18:47 -04:00
2018-08-14 18:32:34 -04:00
FString OutputFilePath ;
2015-12-10 21:55:37 -05:00
bool bDirectMode = false ;
for ( int32 Index = 1 ; Index < ArgC ; + + Index )
2014-03-14 14:13:41 -04:00
{
2015-12-10 21:55:37 -05:00
if ( FCString : : Strcmp ( ArgV [ Index ] , TEXT ( " -directcompile " ) ) = = 0 )
{
bDirectMode = true ;
break ;
}
2014-03-14 14:13:41 -04:00
}
2015-12-10 21:55:37 -05:00
if ( ! bDirectMode )
{
if ( ArgC < 6 )
{
2022-11-03 14:22:06 -04:00
printf ( " ShaderCompileWorker (v%d) is called by UnrealEditor, it requires specific command line arguments. \n " , ShaderCompileWorkerOutputVersion ) ;
2015-12-10 21:55:37 -05:00
return - 1 ;
}
2014-03-14 14:13:41 -04:00
2015-12-10 21:55:37 -05:00
// Game exe can pass any number of parameters through with appGetSubprocessCommandline
// so just make sure we have at least the minimum number of parameters.
check ( ArgC > = 6 ) ;
2014-03-14 14:13:41 -04:00
2018-08-14 18:32:34 -04:00
OutputFilePath = ArgV [ 1 ] ;
OutputFilePath + = ArgV [ 5 ] ;
2015-12-10 21:55:37 -05:00
}
2018-08-14 18:32:34 -04:00
return GuardedMainWrapper ( ArgC , ArgV , * OutputFilePath , bDirectMode ) ;
2014-03-14 14:13:41 -04:00
}