// Copyright Epic Games, Inc. All Rights Reserved. #include "ShaderConductorContext.h" #include "HAL/ExceptionHandling.h" #include "ShaderCompilerDefinitions.h" #include "Containers/AnsiString.h" #if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX THIRD_PARTY_INCLUDES_START #include "ShaderConductor/ShaderConductor.hpp" THIRD_PARTY_INCLUDES_END #endif namespace CrossCompiler { #if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX static void ScRewriteWrapper( const ShaderConductor::Compiler::SourceDesc& InSourceDesc, const ShaderConductor::Compiler::Options& InOptions, ShaderConductor::Compiler::ResultDesc& OutResultDesc) { OutResultDesc = ShaderConductor::Compiler::Rewrite(InSourceDesc, InOptions); } static void ScCompileWrapper( const ShaderConductor::Compiler::SourceDesc& InSourceDesc, const ShaderConductor::Compiler::Options& InOptions, const ShaderConductor::Compiler::TargetDesc& InTargetDesc, ShaderConductor::Compiler::ResultDesc& OutResultDesc) { OutResultDesc = ShaderConductor::Compiler::Compile(InSourceDesc, InOptions, InTargetDesc); } static void ScConvertBinaryWrapper( const ShaderConductor::Compiler::ResultDesc& InBinaryDesc, const ShaderConductor::Compiler::SourceDesc& InSourceDesc, const ShaderConductor::Compiler::TargetDesc& InTargetDesc, const ShaderConductor::Compiler::Options& InOptions, ShaderConductor::Compiler::ResultDesc& OutResultDesc) { OutResultDesc = ShaderConductor::Compiler::ConvertBinary(InBinaryDesc, InSourceDesc, InOptions, InTargetDesc); } // Converts the specified ShaderConductor blob to FString. static bool ConvertScBlobToFString(ShaderConductor::Blob* Blob, FString& OutString) { if (Blob && Blob->Size() > 0) { OutString = StringCast(reinterpret_cast(Blob->Data()), Blob->Size()); return true; } return false; } static ShaderConductor::ShaderStage ToShaderConductorShaderStage(EShaderFrequency Frequency) { check(Frequency >= SF_Vertex && Frequency <= SF_RayCallable); switch (Frequency) { case SF_Vertex: return ShaderConductor::ShaderStage::VertexShader; case SF_Mesh: return ShaderConductor::ShaderStage::MeshShader; case SF_Amplification: return ShaderConductor::ShaderStage::AmplificationShader; case SF_Pixel: return ShaderConductor::ShaderStage::PixelShader; case SF_Geometry: return ShaderConductor::ShaderStage::GeometryShader; case SF_Compute: return ShaderConductor::ShaderStage::ComputeShader; case SF_RayGen: return ShaderConductor::ShaderStage::RayGen; case SF_RayMiss: return ShaderConductor::ShaderStage::RayMiss; case SF_RayHitGroup: return ShaderConductor::ShaderStage::RayHitGroup; case SF_RayCallable: return ShaderConductor::ShaderStage::RayCallable; default: break; } return ShaderConductor::ShaderStage::NumShaderStages; } struct FOptionalConvertedAnsiString { FOptionalConvertedAnsiString& operator=(FStringView WideView) { Storage.Empty(); Storage.Append(WideView); AnsiView = FAnsiStringView(Storage); return *this; } FOptionalConvertedAnsiString& operator=(FAnsiStringView InAnsiView) { AnsiView = InAnsiView; return *this; } void CopyAnsi(FAnsiStringView InAnsiView) { Storage = InAnsiView; AnsiView = FAnsiStringView(Storage); } FAnsiString Storage; FAnsiStringView AnsiView; }; // Wrapper structure to hold all intermediate buffers for ShaderConductor struct FShaderConductorContext::FShaderConductorIntermediates { FShaderConductorIntermediates() : Stage(ShaderConductor::ShaderStage::NumShaderStages) { } FOptionalConvertedAnsiString ShaderSource; FOptionalConvertedAnsiString Filename; FOptionalConvertedAnsiString EntryPoint; ShaderConductor::ShaderStage Stage; TArray> Defines; TArray DefineRefs; TArray> Flags; TArray FlagRefs; FAnsiString InternalDxcArgs; TArray CustomDxcArgs; TArray CustomDxcArgRefs; TArray DxcArgRefs; }; static void ConvertScSourceDesc(const FShaderConductorContext::FShaderConductorIntermediates& Intermediates, ShaderConductor::Compiler::SourceDesc& OutSourceDesc) { // Convert descriptor with pointers to the ANSI strings OutSourceDesc.source = Intermediates.ShaderSource.AnsiView.GetData(); OutSourceDesc.fileName = Intermediates.Filename.AnsiView.GetData(); OutSourceDesc.entryPoint = Intermediates.EntryPoint.AnsiView.GetData(); OutSourceDesc.stage = Intermediates.Stage; if (Intermediates.DefineRefs.Num() > 0) { OutSourceDesc.defines = Intermediates.DefineRefs.GetData(); OutSourceDesc.numDefines = static_cast(Intermediates.DefineRefs.Num()); } else { OutSourceDesc.defines = nullptr; OutSourceDesc.numDefines = 0; } } static const ANSICHAR* GetHlslVersionString(int32 Version) { switch (Version) { case 50: return "50"; case 60: return "60"; case 61: return "61"; case 62: return "62"; case 63: return "63"; case 64: return "64"; case 65: return "65"; case 66: return "66"; default: return nullptr; } } static void ConvertScTargetDescLanguageHlsl(const FShaderConductorTarget& InTarget, ShaderConductor::Compiler::TargetDesc& OutTargetDesc) { OutTargetDesc.language = ShaderConductor::ShadingLanguage::Hlsl; OutTargetDesc.version = GetHlslVersionString(InTarget.Version); checkf(OutTargetDesc.version!=nullptr, TEXT("Unsupported target shader version for HLSL: SM%d.%d"), InTarget.Version / 10, InTarget.Version % 10); } static const ANSICHAR* GetGlslFamilyVersionString(int32 Version) { switch (Version) { //ESSL case 310: return "310"; case 320: return "320"; //GLSL case 330: return "330"; case 430: return "430"; case 440: return "440"; case 450: return "450"; case 460: return "460"; default: return nullptr; } } static void ConvertScTargetDescLanguageGlslFamily(const FShaderConductorTarget& InTarget, ShaderConductor::Compiler::TargetDesc& OutTargetDesc) { OutTargetDesc.language = (InTarget.Language == EShaderConductorLanguage::Glsl ? ShaderConductor::ShadingLanguage::Glsl : ShaderConductor::ShadingLanguage::Essl); OutTargetDesc.version = GetGlslFamilyVersionString(InTarget.Version); checkf(OutTargetDesc.version!=nullptr, TEXT("Unsupported target shader version for GLSL family: %d"), InTarget.Version); } static const ANSICHAR* GetMetalFamilyVersionString(int32 Version) { switch (Version) { case 30100: return "30100"; case 30000: return "30000"; case 20400: return "20400"; case 20300: return "20300"; case 20200: return "20200"; case 20100: return "20100"; case 20000: return "20000"; case 10200: return "10200"; case 10100: return "10100"; case 10000: return "10000"; default: return nullptr; } } static void ConvertScTargetDescLanguageMetalFamily(const FShaderConductorTarget& InTarget, ShaderConductor::Compiler::TargetDesc& OutTargetDesc) { OutTargetDesc.language = (InTarget.Language == EShaderConductorLanguage::Metal_macOS ? ShaderConductor::ShadingLanguage::Msl_macOS : ShaderConductor::ShadingLanguage::Msl_iOS); OutTargetDesc.version = GetMetalFamilyVersionString(InTarget.Version); checkf(OutTargetDesc.version!=nullptr, TEXT("Unsupported target shader version for Metal family: %d"), InTarget.Version); } // Converts an array of FString to a C-style array of char* pointers static void ConvertStringArrayToAnsiArray(const TArray& InPairs, TArray& OutPairs, TArray& OutPairRefs) { // Convert map into an array container TArray Value; for (const FString& Iter : InPairs) { OutPairs.Emplace(Iter); } // Store references after all elements have been added to the container so the pointers remain valid OutPairRefs.SetNum(OutPairs.Num()); for (int32 Index = 0; Index < OutPairs.Num(); ++Index) { OutPairRefs[Index] = *OutPairs[Index]; } } // Converts a map of string pairs to a C-Style macro defines array static void ConvertDefineMapToMacroDefines(const FShaderCompilerDefinitions& Definitions, TArray>& OutPairs, TArray& OutPairRefs) { // Convert map into an array container for (FShaderCompilerDefinitions::FConstIterator Iter(Definitions); Iter; ++Iter) { OutPairs.Emplace(Iter.Key(), Iter.Value()); } // Store references after all elements have been added to the container so the pointers remain valid OutPairRefs.SetNum(OutPairs.Num()); for (int32 Index = 0; Index < OutPairs.Num(); ++Index) { OutPairRefs[Index].name = *OutPairs[Index].Key; OutPairRefs[Index].value = *OutPairs[Index].Value; } } static void ConvertScTargetDesc(FShaderConductorContext::FShaderConductorIntermediates& Intermediates, const FShaderConductorTarget& InTarget, ShaderConductor::Compiler::TargetDesc& OutTargetDesc) { // Convert FString to ANSI string and store them as intermediates FMemory::Memzero(OutTargetDesc); Intermediates.Flags.Empty(); Intermediates.FlagRefs.Empty(); switch (InTarget.Language) { case EShaderConductorLanguage::Hlsl: ConvertScTargetDescLanguageHlsl(InTarget, OutTargetDesc); break; case EShaderConductorLanguage::Glsl: case EShaderConductorLanguage::Essl: ConvertScTargetDescLanguageGlslFamily(InTarget, OutTargetDesc); break; case EShaderConductorLanguage::Metal_macOS: case EShaderConductorLanguage::Metal_iOS: ConvertScTargetDescLanguageMetalFamily(InTarget, OutTargetDesc); break; } // Convert flags map into an array container ConvertDefineMapToMacroDefines(*InTarget.CompileFlags, Intermediates.Flags, Intermediates.FlagRefs); OutTargetDesc.options = Intermediates.FlagRefs.GetData(); OutTargetDesc.numOptions = static_cast(Intermediates.FlagRefs.Num()); // Wrap input function into lambda to convert to ShaderConductor interface if (InTarget.VariableTypeRenameCallback) { OutTargetDesc.variableTypeRenameCallback = [InnerCallback = InTarget.VariableTypeRenameCallback](const char* VariableName, const char* TypeName) -> ShaderConductor::Blob { // Forward callback to public interface callback FString RenamedTypeName; if (InnerCallback(FAnsiStringView(VariableName), FAnsiStringView(TypeName), RenamedTypeName)) { if (!RenamedTypeName.IsEmpty()) { // Convert renamed type name from FString to ShaderConductor::Blob return ShaderConductor::Blob(TCHAR_TO_ANSI(*RenamedTypeName), RenamedTypeName.Len() + 1); } } return ShaderConductor::Blob{}; }; } } /* * Reduced list of SPIRV-Tools optimization passes to avoid nested expressions in GLSL output. * The order of passes has been adopted from the standard '-O' optimization configuration with the following passes removed: * - LocalSingleBlockLoadStoreElimPass * - LocalSingleStoreElimPass * - LocalMultiStoreElimPass * - SSARewritePass */ static const TCHAR* GSpirvTools_OptPasses_PresetRelaxNestedExpr = TEXT( "--wrap-opkill," "--eliminate-dead-branches," "--merge-return," "--inline-entry-points-exhaustive," "--eliminate-dead-functions," "--eliminate-dead-code-aggressive," "--private-to-local," "--eliminate-dead-code-aggressive," "--scalar-replacement," "--convert-local-access-chains," "--eliminate-dead-code-aggressive," "--ccp," "--eliminate-dead-code-aggressive," "--loop-unroll," "--eliminate-dead-branches," "--redundancy-elimination," "--combine-access-chains," "--simplify-instructions," "--scalar-replacement," "--convert-local-access-chains," "--eliminate-dead-code-aggressive," "--vector-dce," "--eliminate-dead-inserts," "--eliminate-dead-branches," "--simplify-instructions," "--if-conversion," "--copy-propagate-arrays," "--reduce-load-size," "--eliminate-dead-code-aggressive," "--merge-blocks," "--redundancy-elimination," "--eliminate-dead-branches," "--merge-blocks," "--simplify-instructions," "--eliminate-dead-members," "--merge-blocks," "--redundancy-elimination," "--simplify-instructions," "--eliminate-dead-code-aggressive," "--cfg-cleanup" ); static const TCHAR* SelectSpirvCustomOptimizationPasses(const FString& OptimizationPasses) { if (OptimizationPasses == TEXT("preset(relax-nested-expr)")) { return GSpirvTools_OptPasses_PresetRelaxNestedExpr; } else { // Interpret input argument as set of optimization passes return *OptimizationPasses; } } static void AppendDxcArguments(const FShaderConductorOptions& InOptions, TArray& DxcArguments, bool bGenerateSpirv = true) { if(bGenerateSpirv) { DxcArguments.Add("-spirv"); } DxcArguments.Add("-Qunused-arguments"); switch (InOptions.HlslVersion) { case 2015: DxcArguments.Add("-HV"); DxcArguments.Add("2015"); break; case 2016: DxcArguments.Add("-HV"); DxcArguments.Add("2016"); break; case 2017: DxcArguments.Add("-HV"); DxcArguments.Add("2017"); break; case 2018: DxcArguments.Add("-HV"); DxcArguments.Add("2018"); break; case 2021: DxcArguments.Add("-HV"); DxcArguments.Add("2021"); break; default: checkf(false, TEXT("Invalid HLSL version: expected 2015, 2016, 2017, 2018, or 2021 but %u was specified"), InOptions.HlslVersion); break; } // Ignore unknwon attributes as UE uses custom attributes for intermediate source transformation DxcArguments.Add("-Wno-unknown-attributes"); // We only treat warnings as errors for input source code, not intermediate source since DXC rewriter might produce new warnings the shader authors don't have control over. if (InOptions.bWarningsAsErrors) { DxcArguments.Add("-WX"); } // Add additional DXC arguments that are not exposed by ShaderConductor API directly if (!InOptions.bDisableScalarBlockLayout) { DxcArguments.Add("-fvk-use-scalar-layout"); } if (InOptions.bPreserveStorageInput) { DxcArguments.Add("-fspv-preserve-storage-input"); } if (InOptions.bForceStorageImageFormat) { DxcArguments.Add("-fvk-force-storage-image-format"); } if (InOptions.bSvPositionImplicitInvariant) { DxcArguments.Add("-fspv-svposition-implicit-invariant"); } if (InOptions.bSupportPreciseOutputs) { DxcArguments.Add("-fspv-support-precise-outputs"); } using ETargetEnvironment = CrossCompiler::FShaderConductorOptions::ETargetEnvironment; if (InOptions.bEnable16bitTypes) { DxcArguments.Add("-fspv-target-env=universal1.5"); // ShaderConductor.cpp forgot to pipedown enable16bitTypes, so work arround by adding the parameter manually in here. DxcArguments.Add("-enable-16bit-types"); } else { switch (InOptions.TargetEnvironment) { default: checkf(false, TEXT("Unexpected SPIR-V target environment: %d"), (uint32)InOptions.TargetEnvironment); case ETargetEnvironment::Vulkan_1_0: DxcArguments.Add("-fspv-target-env=vulkan1.0"); break; case ETargetEnvironment::Vulkan_1_1: DxcArguments.Add("-fspv-target-env=vulkan1.1"); break; case ETargetEnvironment::Vulkan_1_2: DxcArguments.Add("-fspv-target-env=vulkan1.2"); break; case ETargetEnvironment::Vulkan_1_3: DxcArguments.Add("-fspv-target-env=vulkan1.3"); break; } } } static void ConvertScOptions(FShaderConductorContext::FShaderConductorIntermediates& Intermediates, const FShaderConductorOptions& InOptions, ShaderConductor::Compiler::Options& OutOptions, bool bIgnoreCustomDxcArgs = false, bool bGenerateSpirv = true) { // Validate input shader model with respect to certain language features. checkf( (!InOptions.bEnable16bitTypes || InOptions.ShaderModel >= FHlslShaderModel{ 6, 2 }), TEXT("DXC option '-enable-16bit-types' only supported with SM6.2+ but SM%u.%u was specified"), InOptions.ShaderModel.Major, InOptions.ShaderModel.Minor ); OutOptions.removeUnusedGlobals = InOptions.bRemoveUnusedGlobals; OutOptions.packMatricesInRowMajor = InOptions.bPackMatricesInRowMajor; OutOptions.enable16bitTypes = InOptions.bEnable16bitTypes; OutOptions.enableDebugInfo = InOptions.bEnableDebugInfo; OutOptions.disableOptimizations = InOptions.bDisableOptimizations; OutOptions.enableFMAPass = InOptions.bEnableFMAPass; OutOptions.enableSeparateSamplers = InOptions.bEnableSeparateSamplersInGlsl; OutOptions.shaderModel = ShaderConductor::Compiler::ShaderModel { static_cast(InOptions.ShaderModel.Major), static_cast(InOptions.ShaderModel.Minor) }; TArray& DxcArgRefs = Intermediates.DxcArgRefs; DxcArgRefs.Empty(); AppendDxcArguments(InOptions, DxcArgRefs, bGenerateSpirv); if (!InOptions.SpirvCustomOptimizationPasses.IsEmpty()) { Intermediates.InternalDxcArgs = FAnsiString::Printf("-Oconfig=%ls", SelectSpirvCustomOptimizationPasses(InOptions.SpirvCustomOptimizationPasses)); DxcArgRefs.Add(*Intermediates.InternalDxcArgs); } if (DxcArgRefs.Num() > 0) { // Use DXC argument container and append custom arguments if (!bIgnoreCustomDxcArgs) { DxcArgRefs.Append(Intermediates.CustomDxcArgRefs); } OutOptions.numDXCArgs = DxcArgRefs.Num(); OutOptions.DXCArgs = (const char**)DxcArgRefs.GetData(); } else if (!bIgnoreCustomDxcArgs) { // Use custom DXC arguments only OutOptions.numDXCArgs = Intermediates.CustomDxcArgRefs.Num(); OutOptions.DXCArgs = (const char**)Intermediates.CustomDxcArgRefs.GetData(); } else { // No additional DXC arguments OutOptions.numDXCArgs = 0; OutOptions.DXCArgs = nullptr; } } // Returns whether the specified line of text contains only these characters, making it a valid line marker from DXC: ' ', '\t', '~', '^' static bool IsTextLineDxcLineMarker(const FString& Line) { bool bContainsLineMarkerChars = false; for (TCHAR Char : Line) { if (Char == TCHAR('~') || Char == TCHAR('^')) { // Line contains at least one of the necessary characters to be a potential DXC line marker. bContainsLineMarkerChars = true; } else if (!(Char == TCHAR(' ') || Char == TCHAR('\t'))) { // Illegal character for a potential DXC line marker. return false; } } return bContainsLineMarkerChars; } // Converts the error blob from ShaderConductor into an array of error reports (of type FShaderCompilerError). static void ConvertScCompileErrors(ShaderConductor::Blob& ErrorBlob, TArray& OutErrors) { // Convert blob into FString FString ErrorString; if (ConvertScBlobToFString(&ErrorBlob, ErrorString)) { // Convert FString into array of FString (one for each line) TArray ErrorStringLines; ErrorString.ParseIntoArray(ErrorStringLines, TEXT("\n")); // Forward parsed array of lines to primary conversion function FShaderConductorContext::ConvertCompileErrors(MoveTemp(ErrorStringLines), OutErrors); } } FShaderConductorTarget::FShaderConductorTarget() { CompileFlags = MakePimpl(); } FShaderConductorContext::FShaderConductorContext() : Intermediates(new FShaderConductorIntermediates()) { } FShaderConductorContext::~FShaderConductorContext() { delete Intermediates; } FShaderConductorContext::FShaderConductorContext(FShaderConductorContext&& Rhs) : Errors(MoveTemp(Rhs.Errors)) , Intermediates(Rhs.Intermediates) { Rhs.Intermediates = nullptr; } FShaderConductorContext& FShaderConductorContext::operator = (FShaderConductorContext&& Rhs) { Errors = MoveTemp(Rhs.Errors); delete Intermediates; Intermediates = Rhs.Intermediates; Rhs.Intermediates = nullptr; return *this; } bool FShaderConductorContext::LoadSource(FStringView ShaderSource, const FString& Filename, const FString& EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { // Convert FString to ANSI string and store them as intermediates Intermediates->ShaderSource = ShaderSource; Intermediates->Filename = Filename; Intermediates->EntryPoint = EntryPoint; // Convert macro definitions map into an array container if (Definitions != nullptr) { ConvertDefineMapToMacroDefines(*Definitions, Intermediates->Defines, Intermediates->DefineRefs); } if (ExtraDxcArgs && ExtraDxcArgs->Num() > 0) { ConvertStringArrayToAnsiArray(*ExtraDxcArgs, Intermediates->CustomDxcArgs, Intermediates->CustomDxcArgRefs); } // Convert shader stage Intermediates->Stage = ToShaderConductorShaderStage(ShaderStage); return true; } bool FShaderConductorContext::LoadSource(FAnsiStringView ShaderSource, const FString& Filename, const FString& EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { Intermediates->ShaderSource = ShaderSource; Intermediates->Filename = Filename; Intermediates->EntryPoint = EntryPoint; // Convert macro definitions map into an array container if (Definitions != nullptr) { ConvertDefineMapToMacroDefines(*Definitions, Intermediates->Defines, Intermediates->DefineRefs); } if (ExtraDxcArgs && ExtraDxcArgs->Num() > 0) { ConvertStringArrayToAnsiArray(*ExtraDxcArgs, Intermediates->CustomDxcArgs, Intermediates->CustomDxcArgRefs); } // Convert shader stage Intermediates->Stage = ToShaderConductorShaderStage(ShaderStage); return true; } bool FShaderConductorContext::LoadSource(const ANSICHAR* ShaderSource, const ANSICHAR* Filename, const ANSICHAR* EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { Intermediates->ShaderSource = ShaderSource; Intermediates->Filename = Filename; Intermediates->EntryPoint = EntryPoint; // Convert macro definitions map into an array container if (Definitions != nullptr) { ConvertDefineMapToMacroDefines(*Definitions, Intermediates->Defines, Intermediates->DefineRefs); } if (ExtraDxcArgs && ExtraDxcArgs->Num() > 0) { ConvertStringArrayToAnsiArray(*ExtraDxcArgs, Intermediates->CustomDxcArgs, Intermediates->CustomDxcArgRefs); } // Convert shader stage Intermediates->Stage = ToShaderConductorShaderStage(ShaderStage); return true; } bool FShaderConductorContext::CompileHlslToDxil(const FShaderConductorOptions& Options, TArray& OutDxil) { constexpr bool bGenerateSpirv = false; // Convert descriptors for ShaderConductor interface ShaderConductor::Compiler::SourceDesc ScSourceDesc; ConvertScSourceDesc(*Intermediates, ScSourceDesc); ShaderConductor::Compiler::TargetDesc ScTargetDesc; FMemory::Memzero(ScTargetDesc); ScTargetDesc.language = ShaderConductor::ShadingLanguage::Dxil; ShaderConductor::Compiler::Options ScOptions; ConvertScOptions(*Intermediates, Options, ScOptions, false, bGenerateSpirv); // Compile HLSL source code to DXIL bool bSucceeded = false; ShaderConductor::Compiler::ResultDesc ResultDesc; ScCompileWrapper(ScSourceDesc, ScOptions, ScTargetDesc, ResultDesc); if (!ResultDesc.hasError && ResultDesc.target.Size() > 0) { // Copy result blob into output DXIL module OutDxil = TArray(reinterpret_cast(ResultDesc.target.Data()), ResultDesc.target.Size() / 4); bSucceeded = true; } else { bSucceeded = false; } // Append compile error and warning to output reports ConvertScCompileErrors(ResultDesc.errorWarningMsg, Errors); return bSucceeded; } bool FShaderConductorContext::CompileHlslToSpirv(const FShaderConductorOptions& Options, TArray& OutSpirv) { // Convert descriptors for ShaderConductor interface ShaderConductor::Compiler::SourceDesc ScSourceDesc; ConvertScSourceDesc(*Intermediates, ScSourceDesc); ShaderConductor::Compiler::TargetDesc ScTargetDesc; FMemory::Memzero(ScTargetDesc); ScTargetDesc.language = ShaderConductor::ShadingLanguage::SpirV; ShaderConductor::Compiler::Options ScOptions; ConvertScOptions(*Intermediates, Options, ScOptions); // Compile HLSL source code to SPIR-V bool bSucceeded = false; ShaderConductor::Compiler::ResultDesc ResultDesc; ScCompileWrapper(ScSourceDesc, ScOptions, ScTargetDesc, ResultDesc); if (!ResultDesc.hasError && ResultDesc.target.Size() > 0) { // Copy result blob into output SPIR-V module OutSpirv = TArray(reinterpret_cast(ResultDesc.target.Data()), ResultDesc.target.Size() / 4); bSucceeded = true; } // Append compile error and warning to output reports ConvertScCompileErrors(ResultDesc.errorWarningMsg, Errors); return bSucceeded; } void FShaderConductorContext::RemoveLineDirectives() { if (!Intermediates || GetSourceLength() == 0) { return; } Intermediates->ShaderSource.Storage.ReplaceInline("#line", "//ine"); } bool FShaderConductorContext::OptimizeSpirv(TArray& Spirv, const ANSICHAR* const* OptConfigs, int32 OptConfigCount) { // Ignore this call if no optimization configurations were specified if (OptConfigCount > 0) { check(OptConfigs != nullptr); // Convert input SPIR-V module to Blob instance for ShaderConductor interface ShaderConductor::Compiler::ResultDesc SpirvInput; SpirvInput.target = ShaderConductor::Blob(Spirv.GetData(), Spirv.Num() * sizeof(uint32)); SpirvInput.isText = false; SpirvInput.hasError = false; // Run optimization passes through ShaderConductor ShaderConductor::Compiler::ResultDesc SpirvOutput = ShaderConductor::Compiler::Optimize(SpirvInput, OptConfigs, static_cast(OptConfigCount)); if (!SpirvOutput.hasError && SpirvOutput.target.Size() > 0) { // Convert Blob instance back to our SPIR-V module Spirv = TArray(reinterpret_cast(SpirvOutput.target.Data()), SpirvOutput.target.Size() / 4); } else { // Extract errors if (SpirvOutput.errorWarningMsg.Size() > 0) { FString ErrorString; if (ConvertScBlobToFString(&SpirvOutput.errorWarningMsg, ErrorString)) { Errors.Add(*ErrorString); } } return false; } } return true; } bool FShaderConductorContext::CompileSpirvToSource(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, FString& OutSource) { return CompileSpirvToSourceBuffer( Options, Target, InSpirv, InSpirvByteSize, [&OutSource](const void* Data, uint32 Size) { // Convert source buffer to FString FString Converted = FString::ConstructFromPtrSize(reinterpret_cast(Data), Size); OutSource = MoveTemp(Converted); } ); } bool FShaderConductorContext::CompileSpirvToSourceAnsi(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, TArray& OutSource) { return CompileSpirvToSourceBuffer( Options, Target, InSpirv, InSpirvByteSize, [&OutSource](const void* Data, uint32 Size) { // Convert source buffer to ANSI string FAnsiString Copy = FAnsiString::ConstructFromPtrSize(reinterpret_cast(Data), Size); OutSource = MoveTemp(Copy.GetCharArray()); } ); } bool FShaderConductorContext::CompileSpirvToSourceBuffer(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, const TFunction& OutputCallback) { check(OutputCallback != nullptr); check(InSpirv != nullptr); check(InSpirvByteSize > 0); checkf(InSpirvByteSize % 4 == 0, TEXT("SPIR-V code unaligned. Size must be a multiple of 4, but %u was specified."), InSpirvByteSize); // Convert descriptors for ShaderConductor interface ShaderConductor::Compiler::SourceDesc ScSourceDesc; ConvertScSourceDesc(*Intermediates, ScSourceDesc); ShaderConductor::Compiler::TargetDesc ScTargetDesc; ConvertScTargetDesc(*Intermediates, Target, ScTargetDesc); ShaderConductor::Compiler::Options ScOptions; ConvertScOptions(*Intermediates, Options, ScOptions); ShaderConductor::Compiler::ResultDesc ScBinaryDesc; ScBinaryDesc.target.Reset(InSpirv, InSpirvByteSize); ScBinaryDesc.isText = false; ScBinaryDesc.hasError = false; // Convert the input SPIR-V into Metal high level source bool bSucceeded = false; ShaderConductor::Compiler::ResultDesc ResultDesc; ScConvertBinaryWrapper(ScBinaryDesc, ScSourceDesc, ScTargetDesc, ScOptions, ResultDesc); if (!ResultDesc.hasError && ResultDesc.target.Size() > 0) { // Copy result blob into output SPIR-V module OutputCallback(ResultDesc.target.Data(), ResultDesc.target.Size()); bSucceeded = true; } // Append compile error and warning to output reports if (ResultDesc.errorWarningMsg.Size() > 0) { FString ErrorString; if (ConvertScBlobToFString(&ResultDesc.errorWarningMsg, ErrorString)) { Errors.Add(*ErrorString); } } return bSucceeded; } void FShaderConductorContext::FlushErrors(TArray& OutErrors) { if (OutErrors.Num() > 0) { // Append internal list of errors to output list, then clear internal list for (const FShaderCompilerError& ErrorEntry : Errors) { OutErrors.Add(ErrorEntry); } Errors.Empty(); } else { // Move internal list of errors into output list OutErrors = MoveTemp(Errors); } } const ANSICHAR* FShaderConductorContext::GetSourceString() const { return (Intermediates->ShaderSource.AnsiView.Len() > 0 ? Intermediates->ShaderSource.AnsiView.GetData() : nullptr); } int32 FShaderConductorContext::GetSourceLength() const { return Intermediates->ShaderSource.AnsiView.Len(); } static const TCHAR* GetHlslShaderModelProfile(ShaderConductor::ShaderStage Stage) { switch (Stage) { case ShaderConductor::ShaderStage::VertexShader: return TEXT("vs"); case ShaderConductor::ShaderStage::PixelShader: return TEXT("ps"); case ShaderConductor::ShaderStage::GeometryShader: return TEXT("gs"); case ShaderConductor::ShaderStage::HullShader: return TEXT("hl"); case ShaderConductor::ShaderStage::DomainShader: return TEXT("ds"); case ShaderConductor::ShaderStage::ComputeShader: return TEXT("cs"); default: return TEXT("lib"); } } FString FShaderConductorContext::GenerateDxcArguments(const FShaderConductorOptions& Options) const { FString CmdLineArgs = FString::Printf( TEXT("-E %hs -T %s_%d_%d"), Intermediates->EntryPoint.AnsiView.GetData(), GetHlslShaderModelProfile(Intermediates->Stage), (int32)Options.ShaderModel.Major, (int32)Options.ShaderModel.Minor ); TArray DxcArguments; AppendDxcArguments(Options, DxcArguments); for (const ANSICHAR* Argument : DxcArguments) { CmdLineArgs += TEXT(" "); CmdLineArgs += ANSI_TO_TCHAR(Argument); } return CmdLineArgs; } void FShaderConductorContext::ConvertCompileErrors(TArray&& ErrorStringLines, TArray& OutErrors) { // Returns whether the specified line in the 'ErrorStringLines' array has a line marker. auto HasErrorLineMarker = [&ErrorStringLines](int32 LineIndex) { if (LineIndex + 2 < ErrorStringLines.Num()) { return IsTextLineDxcLineMarker(ErrorStringLines[LineIndex + 2]); } return false; }; // Iterate over all errors. Most (but not all) contain a highlighted line and line marker. for (int32 LineIndex = 0; LineIndex < ErrorStringLines.Num();) { if (HasErrorLineMarker(LineIndex)) { // Add current line as error with highlighted source line (LineIndex+1) and line marker (LineIndex+2) OutErrors.Emplace(MoveTemp(ErrorStringLines[LineIndex]), MoveTemp(ErrorStringLines[LineIndex + 1]), MoveTemp(ErrorStringLines[LineIndex + 2])); LineIndex += 3; } else { // Add current line as single error OutErrors.Emplace(MoveTemp(ErrorStringLines[LineIndex])); LineIndex += 1; } } } bool FShaderConductorContext::Disassemble(EShaderConductorIR Language, const void* Binary, uint32 BinaryByteSize, TArray& OutAssemblyText) { // Initialize Blob with input SPIR-V code ShaderConductor::Compiler::DisassembleDesc BinaryDesc; switch (Language) { case EShaderConductorIR::Spirv: BinaryDesc.language = ShaderConductor::ShadingLanguage::SpirV; break; case EShaderConductorIR::Dxil: BinaryDesc.language = ShaderConductor::ShadingLanguage::Dxil; break; } BinaryDesc.binary = reinterpret_cast(Binary); BinaryDesc.binarySize = BinaryByteSize; // Disassemble via ShaderConductor interface ShaderConductor::Compiler::ResultDesc TextOutput = ShaderConductor::Compiler::Disassemble(BinaryDesc); if (TextOutput.isText && !TextOutput.hasError) { // Convert and return output to ANSI string FAnsiString Copy = FAnsiString::ConstructFromPtrSize(reinterpret_cast(TextOutput.target.Data()), TextOutput.target.Size()); OutAssemblyText = MoveTemp(Copy.GetCharArray()); return true; } return false; } #else // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX FShaderConductorContext::FShaderConductorContext() { checkf(0, TEXT("Cannot instantiate FShaderConductorContext for unsupported platform")); } FShaderConductorContext::~FShaderConductorContext() { // Dummy } FShaderConductorContext::FShaderConductorContext(FShaderConductorContext&& Rhs) { // Dummy } FShaderConductorContext& FShaderConductorContext::operator = (FShaderConductorContext&& Rhs) { return *this; // Dummy } bool FShaderConductorContext::LoadSource(const FString& ShaderSource, const FString& Filename, const FString& EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { return false; // Dummy } bool FShaderConductorContext::LoadSource(FStringView ShaderSource, const FString& Filename, const FString& EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { return false; // Dummy } bool FShaderConductorContext::LoadSource(const ANSICHAR* ShaderSource, const ANSICHAR* Filename, const ANSICHAR* EntryPoint, EShaderFrequency ShaderStage, const FShaderCompilerDefinitions* Definitions, const TArray* ExtraDxcArgs) { return false; // Dummy } bool FShaderConductorContext::RewriteHlsl(const FShaderConductorOptions& Options, FString* OutSource) { return false; // Dummy } bool FShaderConductorContext::CompileHlslToDxil(const FShaderConductorOptions& Options, TArray& OutDxil) { return false; // Dummy } bool FShaderConductorContext::CompileHlslToSpirv(const FShaderConductorOptions& Options, TArray& OutSpirv) { return false; // Dummy } bool FShaderConductorContext::CompileSpirvToSource(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, FString& OutSource) { return false; // Dummy } bool FShaderConductorContext::CompileSpirvToSourceAnsi(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, TArray& OutSource) { return false; // Dummy } bool FShaderConductorContext::CompileSpirvToSourceBuffer(const FShaderConductorOptions& Options, const FShaderConductorTarget& Target, const void* InSpirv, uint32 InSpirvByteSize, const TFunction& OutputCallback) { return false; // Dummy } void FShaderConductorContext::FlushErrors(TArray& OutErrors) { // Dummy } const ANSICHAR* FShaderConductorContext::GetSourceString() const { return nullptr; // Dummy } int32 FShaderConductorContext::GetSourceLength() const { return 0; // Dummy } void FShaderConductorContext::ConvertCompileErrors(const TArray& ErrorStringLines, TArray& OutErrors) { // Dummy } bool FShaderConductorContext::Disassemble(EShaderConductorIR Language, const void* Binary, uint32 BinaryByteSize, TArray& OutAssemblyText) { return false; // Dummy } #endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX bool FShaderConductorContext::IsIntermediateSpirvOutputVariable(const ANSICHAR* SpirvVariableName) { // This is only true for "temp.var.hullMainRetVal" which is generated by DXC as intermediate output variable to communicate patch constant data in a Hull Shader. return (SpirvVariableName != nullptr && FCStringAnsi::Strcmp(SpirvVariableName, FShaderConductorContext::GetIdentifierTable().IntermediateTessControlOutput) == 0); } const FShaderConductorIdentifierTable& FShaderConductorContext::GetIdentifierTable() { static const FShaderConductorIdentifierTable IdentifierTable { /*InputAttribute:*/ "in.var.ATTRIBUTE", /*GlobalsUniformBuffer:*/ "$Globals", /*IntermediateTessControlOutput:*/ "temp.var.hullMainRetVal", /*DummySampler:*/ "SPIRV_Cross_DummySampler", }; return IdentifierTable; } static const TCHAR* GetGlslShaderFileExt(EShaderFrequency ShaderStage) { switch (ShaderStage) { case SF_Vertex: return TEXT("vert"); case SF_Mesh: return TEXT("mesh"); case SF_Amplification: return TEXT("task"); case SF_Pixel: return TEXT("frag"); case SF_Geometry: return TEXT("geom"); case SF_Compute: return TEXT("comp"); case SF_RayGen: return TEXT("rgen"); case SF_RayMiss: return TEXT("rmiss"); case SF_RayHitGroup: return TEXT("rahit"); // rahit/rchit case SF_RayCallable: return TEXT("rcall"); default: return TEXT("glsl"); } } const TCHAR* FShaderConductorContext::GetShaderFileExt(EShaderConductorLanguage Language, EShaderFrequency ShaderStage) { switch (Language) { case EShaderConductorLanguage::Hlsl: return TEXT("hlsl"); case EShaderConductorLanguage::Glsl: [[fallthrough]]; case EShaderConductorLanguage::Essl: return GetGlslShaderFileExt(ShaderStage); case EShaderConductorLanguage::Metal_macOS: [[fallthrough]]; case EShaderConductorLanguage::Metal_iOS: return TEXT("metal"); default: return TEXT(""); } } static const TCHAR* LexToString(EShaderConductorIR Language) { switch (Language) { case EShaderConductorIR::Spirv: return TEXT("SPIR-V"); case EShaderConductorIR::Dxil: return TEXT("DXIL"); default: return TEXT(""); } } bool FShaderConductorContext::Disassemble(EShaderConductorIR Language, const void* Binary, uint32 BinaryByteSize, FGenericShaderStat& OutShaderStat) { TArray AssemblyText; if (FShaderConductorContext::Disassemble(Language, Binary, BinaryByteSize, AssemblyText)) { OutShaderStat.StatName = LexToString(Language); OutShaderStat.Value.Set(ANSI_TO_TCHAR(AssemblyText.GetData())); OutShaderStat.Flags = FGenericShaderStat::EFlags::Hidden; OutShaderStat.TagName = FShaderStatTagNames::AnalysisArtifactsName; return true; } return false; } void FShaderConductorContext::Shutdown() { #if PLATFORM_LINUX ShaderConductor::Compiler::Shutdown(); #endif } } // namespace CrossCompiler