// Copyright Epic Games, Inc. All Rights Reserved. #include "MetalDerivedData.h" #include "MetalShaderFormat.h" #include "Serialization/MemoryWriter.h" #include "RHIDefinitions.h" #include "Misc/FileHelper.h" #include "hlslcc.h" #include "MetalShaderFormat.h" #include "MetalShaderResources.h" #include "Misc/Paths.h" #include "Misc/Compression.h" #include "MetalBackend.h" #include "Serialization/MemoryReader.h" #if 1 #if PLATFORM_MAC || PLATFORM_WINDOWS THIRD_PARTY_INCLUDES_START #include "ShaderConductor/ShaderConductor.hpp" #include "spirv_reflect.h" THIRD_PARTY_INCLUDES_END #endif #endif extern bool ExecXcodeCommand(EShaderPlatform ShaderPlatform, const TCHAR* Command, const TCHAR* Parameters, int32* OutReturnCode, FString* OutStdOut, FString* OutStdErr); extern FString GetXcodePath(); extern bool RemoteFileExists(const FString& Path); extern FString MakeRemoteTempFolder(FString Path); extern FString LocalPathToRemote(const FString& LocalPath, const FString& RemoteFolder); extern bool CopyLocalFileToRemote(FString const& LocalPath, FString const& RemotePath); extern bool CopyRemoteFileToLocal(FString const& RemotePath, FString const& LocalPath); extern bool ChecksumRemoteFile(FString const& RemotePath, uint32* CRC, uint32* Len); extern bool ModificationTimeRemoteFile(FString const& RemotePath, uint64& Time); extern bool RemoveRemoteFile(FString const& RemotePath); extern const FString& GetMetalToolsPath(EShaderPlatform ShaderPlatform); extern const FString& GetMetalCompilerVersion(EShaderPlatform ShaderPlatform); extern uint16 GetXcodeVersion(uint64& BuildVersion); extern EShaderPlatform MetalShaderFormatToLegacyShaderPlatform(FName ShaderFormat); extern void BuildMetalShaderOutput( FShaderCompilerOutput& ShaderOutput, const FShaderCompilerInput& ShaderInput, FSHAHash const& GUIDHash, uint32 CCFlags, const ANSICHAR* InShaderSource, uint32 SourceLen, uint32 SourceCRCLen, uint32 SourceCRC, uint8 Version, TCHAR const* Standard, TCHAR const* MinOSVersion, EMetalTypeBufferMode TypeMode, TArray& OutErrors, FMetalTessellationOutputs const& TessOutputAttribs, uint32 TypedBuffers, uint32 InvariantBuffers, uint32 TypedUAVs, uint32 ConstantBuffers, TArray const& TypedBufferFormats, bool bAllowFastIntriniscs ); FMetalShaderDebugInfoCooker::FMetalShaderDebugInfoCooker(FMetalShaderDebugInfoJob& InJob) : Job(InJob) { } FMetalShaderDebugInfoCooker::~FMetalShaderDebugInfoCooker() { } #if PLATFORM_MAC || PLATFORM_IOS #pragma mark - FDerivedDataPluginInterface Interface - #endif const TCHAR* FMetalShaderDebugInfoCooker::GetPluginName() const { return TEXT("FMetalShaderDebugInfo"); } const TCHAR* FMetalShaderDebugInfoCooker::GetVersionString() const { static FString Version = FString::Printf(TEXT("%u"), (uint32)GetMetalFormatVersion(Job.ShaderFormat)); return *Version; } FString FMetalShaderDebugInfoCooker::GetPluginSpecificCacheKeySuffix() const { EShaderPlatform Platform = MetalShaderFormatToLegacyShaderPlatform(Job.ShaderFormat); const FString& CompilerVersion = GetMetalCompilerVersion(Platform); FString VersionedName = FString::Printf(TEXT("%s%u%u%s%s%s%s%s%s"), *Job.ShaderFormat.GetPlainNameString(), Job.SourceCRCLen, Job.SourceCRC, *Job.Hash.ToString(), *Job.CompilerVersion, *Job.MinOSVersion, *Job.DebugInfo, *Job.MathMode, *Job.Standard); // get rid of some not so filename-friendly characters ('=',' ' -> '_') VersionedName = VersionedName.Replace(TEXT("="), TEXT("_")).Replace(TEXT(" "), TEXT("_")); return VersionedName; } bool FMetalShaderDebugInfoCooker::IsBuildThreadsafe() const { return false; } bool FMetalShaderDebugInfoCooker::Build(TArray& OutData) { bool bSucceeded = false; uint32 CodeSize = FCStringAnsi::Strlen(TCHAR_TO_UTF8(*Job.MetalCode)) + 1; int32 CompressedSize = FCompression::CompressMemoryBound(NAME_Zlib, CodeSize); Output.CompressedData.SetNum(CompressedSize); if (FCompression::CompressMemory(NAME_Zlib, Output.CompressedData.GetData(), CompressedSize, TCHAR_TO_UTF8(*Job.MetalCode), CodeSize)) { Output.UncompressedSize = CodeSize; Output.CompressedData.SetNum(CompressedSize); Output.CompressedData.Shrink(); bSucceeded = true; FMemoryWriter Ar(OutData); Ar << Output; } return bSucceeded; } FMetalShaderBytecodeCooker::FMetalShaderBytecodeCooker(FMetalShaderBytecodeJob& InJob) : Job(InJob) { } FMetalShaderBytecodeCooker::~FMetalShaderBytecodeCooker() { } #if PLATFORM_MAC || PLATFORM_IOS #pragma mark - FDerivedDataPluginInterface Interface - #endif const TCHAR* FMetalShaderBytecodeCooker::GetPluginName() const { return TEXT("MetalShaderBytecode"); } const TCHAR* FMetalShaderBytecodeCooker::GetVersionString() const { static FString Version = FString::Printf(TEXT("%u"), (uint32)GetMetalFormatVersion(Job.ShaderFormat)); return *Version; } FString FMetalShaderBytecodeCooker::GetPluginSpecificCacheKeySuffix() const { FString CompilerVersion = Job.CompilerVersion; EShaderPlatform ShaderPlatform = MetalShaderFormatToLegacyShaderPlatform(Job.ShaderFormat); const FString& CompilerPath = GetMetalToolsPath(ShaderPlatform); uint64 BuildVersion = 0; uint32 XcodeVersion = GetXcodeVersion(BuildVersion); // PCHs need the modifiction time (in secs. since UTC Epoch) to ensure that the result can be used with the current version of the file uint64 ModTime = 0; if (Job.bCompileAsPCH) { ModificationTimeRemoteFile(*Job.InputFile, ModTime); CompilerVersion += FString::Printf(TEXT("xc%u%llu"), XcodeVersion, BuildVersion); } FString VersionedName = FString::Printf(TEXT("%s%u%u%llu%s%s%s%s%s%s%s%d%d"), *Job.ShaderFormat.GetPlainNameString(), Job.SourceCRCLen, Job.SourceCRC, ModTime, *Job.Hash.ToString(), *CompilerVersion, *Job.MinOSVersion, *Job.DebugInfo, *Job.MathMode, *Job.Standard, Job.bRetainObjectFile ? TEXT("+O") : TEXT(""), GetTypeHash(CompilerPath), GetTypeHash(Job.Defines)); // get rid of some not so filename-friendly characters ('=',' ' -> '_') VersionedName = VersionedName.Replace(TEXT("="), TEXT("_")).Replace(TEXT(" "), TEXT("_")); return VersionedName; } bool FMetalShaderBytecodeCooker::IsBuildThreadsafe() const { return false; } bool FMetalShaderBytecodeCooker::Build(TArray& OutData) { bool bSucceeded = false; #if PLATFORM_MAC // Unset the SDKROOT to avoid problems with the incorrect path being used when compiling with the shared PCH. FString SdkRoot = FPlatformMisc::GetEnvironmentVariable(TEXT("SDKROOT")); if (SdkRoot.Len() > 0) { unsetenv("SDKROOT"); } #endif bool bRemoteBuildingConfigured = IsRemoteBuildingConfigured(); const FString RemoteFolder = MakeRemoteTempFolder(Job.TmpFolder); const FString RemoteInputFile = LocalPathToRemote(Job.InputFile, RemoteFolder); // Input file to the compiler - Copied from local machine to remote machine const FString RemoteInputPCHFile = LocalPathToRemote(Job.InputPCHFile, RemoteFolder); // Input file to the compiler - Copied from local machine to remote machine const FString RemoteObjFile = LocalPathToRemote(Job.OutputObjectFile, RemoteFolder); // Output from the compiler -> Input file to the archiver const FString RemoteOutputFilename = LocalPathToRemote(Job.OutputFile, RemoteFolder); // Output from the library generator - Copied from remote machine to local machine EShaderPlatform ShaderPlatform = MetalShaderFormatToLegacyShaderPlatform(Job.ShaderFormat); const FString& MetalToolsPath = GetMetalToolsPath(ShaderPlatform); FString IncludeArgs = Job.IncludeDir.Len() ? FString::Printf(TEXT("-I %s"), *Job.IncludeDir) : TEXT(""); FString MetalParams; if (Job.bCompileAsPCH) { if (Job.InputPCHFile.Len()) { MetalParams = FString::Printf(TEXT("-x metal-header -include-pch %s %s %s %s %s %s %s -o %s"), *Job.InputPCHFile, *Job.MinOSVersion, *Job.MathMode, *Job.Standard, *Job.Defines, *IncludeArgs, *Job.InputFile, *RemoteOutputFilename); } else { MetalParams = FString::Printf(TEXT("-x metal-header %s %s %s %s %s %s -o %s"), *Job.MinOSVersion, *Job.MathMode, *Job.Standard, *Job.Defines, *IncludeArgs, *Job.InputFile, *RemoteOutputFilename); } } else { CopyLocalFileToRemote(Job.InputFile, RemoteInputFile); // PCH bool bUseSharedPCH = Job.InputPCHFile.Len() && IFileManager::Get().FileExists(*Job.InputPCHFile); if (bUseSharedPCH) { CopyLocalFileToRemote(Job.InputPCHFile, RemoteInputPCHFile); MetalParams = FString::Printf(TEXT("-include-pch %s %s %s %s %s -Wno-null-character -fbracket-depth=1024 %s %s %s %s -o %s"), *RemoteInputPCHFile, *Job.MinOSVersion, *Job.DebugInfo, *Job.MathMode, TEXT("-c"), *Job.Standard, *Job.Defines, *IncludeArgs, *RemoteInputFile, *RemoteObjFile); } else { MetalParams = FString::Printf(TEXT("%s %s %s %s -Wno-null-character -fbracket-depth=1024 %s %s %s %s -o %s"), *Job.MinOSVersion, *Job.DebugInfo, *Job.MathMode, TEXT("-c"), *Job.Standard, *Job.Defines, *IncludeArgs, *RemoteInputFile, *RemoteObjFile); } } TCHAR const* CompileType = bRemoteBuildingConfigured ? TEXT("remotely") : TEXT("locally"); bSucceeded = (ExecXcodeCommand(ShaderPlatform, TEXT("metal"), *MetalParams, &Job.ReturnCode, &Job.Results, &Job.Errors) && Job.ReturnCode == 0); if (bSucceeded) { if (!Job.bCompileAsPCH) { FString LibraryParams = FString::Printf(TEXT("-o %s %s"), *RemoteOutputFilename, *RemoteObjFile); bSucceeded = (ExecXcodeCommand(ShaderPlatform, TEXT("metallib"), *LibraryParams, &Job.ReturnCode, &Job.Results, &Job.Errors) && Job.ReturnCode == 0); if (bSucceeded) { if (Job.bRetainObjectFile) { CopyRemoteFileToLocal(RemoteObjFile, Job.OutputObjectFile); bSucceeded = FFileHelper::LoadFileToArray(Output.ObjectFile, *Job.OutputObjectFile); if (!bSucceeded) { Job.Message = FString::Printf(TEXT("Failed to load object file: %s"), *Job.OutputObjectFile); } RemoveRemoteFile(RemoteObjFile); } } else { Job.Message = FString::Printf(TEXT("Failed to package into library %s, code: %d, output: %s %s"), CompileType, Job.ReturnCode, *Job.Results, *Job.Errors); } } if (bSucceeded) { RemoveRemoteFile(RemoteObjFile); CopyRemoteFileToLocal(RemoteOutputFilename, Job.OutputFile); Output.NativePath = RemoteInputFile; bSucceeded = FFileHelper::LoadFileToArray(Output.OutputFile, *Job.OutputFile); if (!bSucceeded) { Job.Message = FString::Printf(TEXT("Failed to load output file: %s"), *Job.OutputFile); } } if (!Job.bCompileAsPCH) { RemoveRemoteFile(RemoteOutputFilename); } } else { if (Job.bCompileAsPCH) { Job.Message = FString::Printf(TEXT("Metal Shared PCH generation failed %s to generate %s: %s."), CompileType, *RemoteOutputFilename, *Job.Errors); } else { Job.Message = FString::Printf(TEXT("Failed to compile %s to bytecode %s, code: %d, output: %s %s"), CompileType, *RemoteOutputFilename, Job.ReturnCode, *Job.Results, *Job.Errors); } } if (bSucceeded) { FMemoryWriter Ar(OutData); Ar << Output; } #if PLATFORM_MAC // Reset the SDKROOT environment we unset earlier. if (SdkRoot.Len() > 0) { setenv("SDKROOT", TCHAR_TO_UTF8(*SdkRoot), 1); } #endif return bSucceeded; } FMetalShaderOutputCooker::FMetalShaderOutputCooker(const FShaderCompilerInput& _Input, FShaderCompilerOutput& _Output, const FString& _WorkingDirectory, FString _PreprocessedShader, FSHAHash _GUIDHash, uint8 _VersionEnum, uint32 _CCFlags, EHlslCompileTarget _HlslCompilerTarget, EHlslCompileTarget _MetalCompilerTarget, EMetalGPUSemantics _Semantics, EMetalTypeBufferMode _TypeMode, uint32 _MaxUnrollLoops, EHlslShaderFrequency _Frequency, bool _bDumpDebugInfo, FString _Standard, FString _MinOSVersion) : Input(_Input) , Output(_Output) , WorkingDirectory(_WorkingDirectory) , PreprocessedShader(_PreprocessedShader) , GUIDHash(_GUIDHash) , VersionEnum(_VersionEnum) , CCFlags(_CCFlags) , IABTier(0) , HlslCompilerTarget(_HlslCompilerTarget) , MetalCompilerTarget(_MetalCompilerTarget) , Semantics(_Semantics) , TypeMode(_TypeMode) , MaxUnrollLoops(_MaxUnrollLoops) , Frequency(_Frequency) , bDumpDebugInfo(_bDumpDebugInfo) , Standard(_Standard) , MinOSVersion(_MinOSVersion) { FString const* IABVersion = Input.Environment.GetDefinitions().Find(TEXT("METAL_INDIRECT_ARGUMENT_BUFFERS")); if (IABVersion && VersionEnum >= 4) { if(IABVersion->IsNumeric()) { LexFromString(IABTier, *(*IABVersion)); } } } FMetalShaderOutputCooker::~FMetalShaderOutputCooker() { } #if PLATFORM_MAC || PLATFORM_IOS #pragma mark - FDerivedDataPluginInterface Interface - #endif const TCHAR* FMetalShaderOutputCooker::GetPluginName() const { return TEXT("MetalShaderOutput"); } const TCHAR* FMetalShaderOutputCooker::GetVersionString() const { static FString Version = FString::Printf(TEXT("%u"), (uint32)GetMetalFormatVersion(Input.ShaderFormat)); return *Version; } FString FMetalShaderOutputCooker::GetPluginSpecificCacheKeySuffix() const { FString CachedOutputName; { FSHAHash Hash; FSHA1::HashBuffer(*PreprocessedShader, PreprocessedShader.Len() * sizeof(TCHAR), Hash.Hash); uint32 Len = PreprocessedShader.Len(); uint16 FormatVers = GetMetalFormatVersion(Input.ShaderFormat); uint64 Flags = 0; for (uint32 Flag : Input.Environment.CompilerFlags) { Flags |= (1ull << uint64(Flag)); } CachedOutputName = FString::Printf(TEXT("%s-%s_%s-%u_%hu_%llu_%hhu_%d_%s_%s"), *Input.ShaderFormat.GetPlainNameString(), *Input.EntryPointName, *Hash.ToString(), Len, FormatVers, Flags, VersionEnum, IABTier, *GUIDHash.ToString(), *Standard); } return CachedOutputName; } bool FMetalShaderOutputCooker::IsBuildThreadsafe() const { return false; } struct FMetalShaderOutputMetaData { TArray TypedBufferFormats; uint32 InvariantBuffers = 0; uint32 TypedBuffers = 0; uint32 TypedUAVs = 0; uint32 ConstantBuffers = 0; }; // Replace the special texture "gl_LastFragData" to a native subpass fetch operation static void PatchSpecialTextureInHlslSource(std::string& SourceData, int& SubpassInput0Dim) { // Invalidate output parameter for dimension of subpass input attachemnt at slot 0 (primary slot for "gl_LastFragData"). SubpassInput0Dim = 0; // Check if special texture is present in the code static const std::string GSpecialTextureLastFragData = "gl_LastFragData"; if (SourceData.find(GSpecialTextureLastFragData) != std::string::npos) { // Replace declaration of special texture with corresponding 'SubpassInput' declaration with respective dimension, i.e. float, float4, etc. struct FHlslVectorType { std::string TypenameIdent; int Dimension; }; const FHlslVectorType FragDeclTypes[4] = { { "float4", 4 }, { "float3", 3 }, { "float2", 2 }, { "float", 1 }, }; for (const FHlslVectorType& FragDeclType : FragDeclTypes) { // Try to find "Texture2D" or "Texture2D< T >" (where T is the vector type), because a rewritten HLSL might have changed the formatting. std::string FragDecl = "Texture2D<" + FragDeclType.TypenameIdent + "> " + GSpecialTextureLastFragData + ";"; size_t FragDeclIncludePos = SourceData.find(FragDecl); if (FragDeclIncludePos == std::string::npos) { FragDecl = "Texture2D< " + FragDeclType.TypenameIdent + " > " + GSpecialTextureLastFragData + ";"; FragDeclIncludePos = SourceData.find(FragDecl); } if (FragDeclIncludePos != std::string::npos) { // Replace declaration of Texture2D with SubpassInput SourceData.replace( FragDeclIncludePos, FragDecl.length(), ("[[vk::input_attachment_index(0)]] SubpassInput<" + FragDeclType.TypenameIdent + "> " + GSpecialTextureLastFragData + ";") ); SubpassInput0Dim = FragDeclType.Dimension; break; } } // Replace all uses of special texture by 'SubpassLoad' operation static const std::string FragLoad = GSpecialTextureLastFragData + ".Load(uint3(0, 0, 0), 0)"; for (size_t FragLoadIncludePos = 0; (FragLoadIncludePos = SourceData.find(FragLoad)) != std::string::npos;) { SourceData.replace( FragLoadIncludePos, FragLoad.length(), (GSpecialTextureLastFragData + ".SubpassLoad()") ); } } } bool FMetalShaderOutputCooker::Build(TArray& OutData) { Output.bSucceeded = false; std::string MetalSource; FString MetalErrors; bool const bZeroInitialise = Input.Environment.CompilerFlags.Contains(CFLAG_ZeroInitialise); bool const bBoundsChecks = Input.Environment.CompilerFlags.Contains(CFLAG_BoundsChecking); bool bSwizzleSample = false; FString const* Swizzle = Input.Environment.GetDefinitions().Find(TEXT("METAL_SWIZZLE_SAMPLES")); if (Swizzle) { LexFromString(bSwizzleSample, *(*Swizzle)); } bool bAllowFastIntriniscs = false; FString const* FastIntrinsics = Input.Environment.GetDefinitions().Find(TEXT("METAL_USE_FAST_INTRINSICS")); if (FastIntrinsics) { LexFromString(bAllowFastIntriniscs, *(*FastIntrinsics)); } bool bForceInvariance = false; FString const* UsingWPO = Input.Environment.GetDefinitions().Find(TEXT("USES_WORLD_POSITION_OFFSET")); if (UsingWPO && FString("1") == *UsingWPO) { // WPO requires that we make all multiply/sincos instructions invariant :( bForceInvariance = true; } FMetalShaderOutputMetaData OutputData; FMetalTessellationOutputs Attribs; uint32 CRCLen = 0; uint32 CRC = 0; uint32 SourceLen = 0; int32 Result = 0; struct FMetalResourceTableEntry : FResourceTableEntry { FString Name; uint32 Size; uint32 SetIndex; bool bUsed; }; TMap> IABs; FString const* UsingTessellationDefine = Input.Environment.GetDefinitions().Find(TEXT("USING_TESSELLATION")); bool bUsingTessellation = ((UsingTessellationDefine != nullptr && FString("1") == *UsingTessellationDefine && Frequency == HSF_VertexShader) || Frequency == HSF_HullShader || Frequency == HSF_DomainShader); // Its going to take a while to get dxc+SPIRV working... bool const bUseSC = Input.Environment.CompilerFlags.Contains(CFLAG_ForceDXC); #if 1 #if PLATFORM_MAC || PLATFORM_WINDOWS if (!bUseSC) #endif #endif { FMetalCodeBackend MetalBackEnd(Attribs, CCFlags, MetalCompilerTarget, VersionEnum, Semantics, TypeMode, MaxUnrollLoops, bZeroInitialise, bBoundsChecks, bAllowFastIntriniscs, bForceInvariance, bSwizzleSample); FMetalLanguageSpec MetalLanguageSpec(VersionEnum); FHlslCrossCompilerContext CrossCompilerContext(CCFlags, Frequency, HlslCompilerTarget); if (CrossCompilerContext.Init(TCHAR_TO_ANSI(*Input.VirtualSourceFilePath), &MetalLanguageSpec)) { char* MetalShaderSource = NULL; char* ErrorLog = NULL; Result = CrossCompilerContext.Run( TCHAR_TO_ANSI(*PreprocessedShader), TCHAR_TO_ANSI(*Input.EntryPointName), &MetalBackEnd, &MetalShaderSource, &ErrorLog ) ? 1 : 0; OutputData.TypedBufferFormats = MetalBackEnd.TypedBufferFormats; OutputData.InvariantBuffers = MetalBackEnd.InvariantBuffers; OutputData.TypedBuffers = MetalBackEnd.TypedBuffers; OutputData.TypedUAVs = MetalBackEnd.TypedUAVs; OutputData.ConstantBuffers = MetalBackEnd.ConstantBuffers; CRCLen = MetalShaderSource ? (uint32)FCStringAnsi::Strlen(MetalShaderSource) : 0u; CRC = CRCLen ? FCrc::MemCrc_DEPRECATED(MetalShaderSource, CRCLen) : 0u; SourceLen = CRCLen; if (MetalShaderSource) { ANSICHAR* Main = FCStringAnsi::Strstr(MetalShaderSource, "Main_00000000_00000000"); check(Main); ANSICHAR MainCRC[24]; int32 NewLen = FCStringAnsi::Snprintf(MainCRC, 24, "Main_%0.8x_%0.8x", CRCLen, CRC); FMemory::Memcpy(Main, MainCRC, NewLen); uint32 Len = FCStringAnsi::Strlen(TCHAR_TO_ANSI(*Input.GetSourceFilename())) + FCStringAnsi::Strlen(TCHAR_TO_ANSI(*Input.DebugGroupName)) + FCStringAnsi::Strlen(TCHAR_TO_ANSI(*Input.EntryPointName)) + FCStringAnsi::Strlen(MetalShaderSource) + 21; char* Dest = (char*)malloc(Len); FCStringAnsi::Snprintf(Dest, Len, "// ! %s/%s.usf:%s\n%s", (const char*)TCHAR_TO_ANSI(*Input.DebugGroupName), (const char*)TCHAR_TO_ANSI(*Input.GetSourceFilename()), (const char*)TCHAR_TO_ANSI(*Input.EntryPointName), (const char*)MetalShaderSource); free(MetalShaderSource); MetalShaderSource = Dest; SourceLen = (uint32)FCStringAnsi::Strlen(MetalShaderSource); } if (MetalShaderSource) { MetalSource = MetalShaderSource; free(MetalShaderSource); } if (ErrorLog) { MetalErrors = ANSI_TO_TCHAR(ErrorLog); free(ErrorLog); } } } #if 1 #if PLATFORM_MAC || PLATFORM_WINDOWS else { ShaderConductor::Compiler::Options Options; Options.removeUnusedGlobals = true; // Metal is inverted vs. SPIRV for some curious reason Options.packMatricesInRowMajor = false; Options.enableDebugInfo = false; Options.enable16bitTypes = false; Options.disableOptimizations = false; Options.enableFMAPass = (VersionEnum == 2 || VersionEnum == 3); ShaderConductor::Compiler::SourceDesc SourceDesc; std::string SourceData(TCHAR_TO_UTF8(*PreprocessedShader)); std::string FileName(TCHAR_TO_UTF8(*Input.VirtualSourceFilePath)); std::string EntryPointName(TCHAR_TO_UTF8(*Input.EntryPointName)); SourceDesc.source = SourceData.c_str(); SourceDesc.fileName = FileName.c_str(); SourceDesc.entryPoint = EntryPointName.c_str(); SourceDesc.numDefines = 0; SourceDesc.defines = nullptr; enum class EMetalTessellationMetadataTags : uint8 { TessellationOutputControlPoints, TessellationDomain, TessellationInputControlPoints, TessellationMaxTessFactor, TessellationOutputWinding, TessellationPartitioning, TessellationPatchesPerThreadGroup, TessellationPatchCountBuffer, TessellationIndexBuffer, TessellationHSOutBuffer, TessellationControlPointOutBuffer, TessellationHSTFOutBuffer, TessellationControlPointIndexBuffer, Num }; FString TESStrings[(uint8)EMetalTessellationMetadataTags::Num]; switch (Frequency) { case HSF_VertexShader: { SourceDesc.stage = ShaderConductor::ShaderStage::VertexShader; break; } case HSF_PixelShader: { Options.enableFMAPass = false; SourceDesc.stage = ShaderConductor::ShaderStage::PixelShader; break; } case HSF_GeometryShader: { SourceDesc.stage = ShaderConductor::ShaderStage::GeometryShader; break; } case HSF_HullShader: { Options.enableFMAPass = true; SourceDesc.stage = ShaderConductor::ShaderStage::HullShader; break; } case HSF_DomainShader: { SourceDesc.stage = ShaderConductor::ShaderStage::DomainShader; break; } case HSF_ComputeShader: { Options.enableFMAPass = false; SourceDesc.stage = ShaderConductor::ShaderStage::ComputeShader; break; } default: { break; } } ShaderConductor::Blob* RewriteBlob = nullptr; // Can't rewrite tessellation shaders because the rewriter doesn't handle HLSL attributes like patchFunction properly and it isn't clear how it should. if (!bUsingTessellation) { // Rewrite HLSL to eliminate unused global variables ahead of compilation ShaderConductor::Compiler::ResultDesc Results = ShaderConductor::Compiler::Rewrite(SourceDesc, Options); RewriteBlob = Results.target; SourceData.clear(); SourceData.resize(RewriteBlob->Size()); FCStringAnsi::Strncpy(&SourceData[0], reinterpret_cast(RewriteBlob->Data()), RewriteBlob->Size()); } // Replace special case texture "gl_LastFragData" by native subpass fetch operation int SubpassInput0Dim = 0; PatchSpecialTextureInHlslSource(SourceData, SubpassInput0Dim); // Pass latest content of HLSL to input descriptor for ShaderConductor SourceDesc.source = SourceData.c_str(); if (bDumpDebugInfo) { // Dump final HLSL content to debug file FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / FPaths::GetBaseFilename(Input.GetSourceFilename()) + TEXT(".dxc.hlsl"))); if (FileWriter) { FileWriter->Serialize(reinterpret_cast(&SourceData[0]), SourceData.size()); FileWriter->Close(); delete FileWriter; } } Options.removeUnusedGlobals = false; ShaderConductor::Compiler::TargetDesc TargetDesc; FString MetaData = FString::Printf(TEXT("// ! %s/%s.usf:%s\n"), *Input.DebugGroupName, *Input.GetSourceFilename(), *Input.EntryPointName); FString EntryPoint = Input.EntryPointName; EHlslShaderFrequency Freq = Frequency; FString ALNString; FString IABString; FString UAVString; FString SRVString; FString SMPString; FString UBOString; FString GLOString; FString PAKString; FString INPString; FString OUTString; FString WKGString; uint32 IABOffsetIndex = 0; TargetDesc.language = ShaderConductor::ShadingLanguage::SpirV; ShaderConductor::Compiler::ResultDesc Results = ShaderConductor::Compiler::Compile(SourceDesc, Options, TargetDesc); Result = (Results.hasError) ? 0 : 1; uint64 BufferIndices = 0xffffffffffffffff; if (!Results.hasError) { // Dump SPIRV module before code reflection so we can analyse the dumped output as early as possible (in case of issues in SPIRV-Reflect) if (bDumpDebugInfo) { if (Results.target->Size() > 0u) { FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / FPaths::GetBaseFilename(Input.GetSourceFilename()) + TEXT(".dxc.spv"))); if (FileWriter) { TArray SpirvData(reinterpret_cast(Results.target->Data()), Results.target->Size()); FileWriter->Serialize(&SpirvData[0], SpirvData.Num()); FileWriter->Close(); delete FileWriter; } } } // Now perform reflection on the SPIRV and tweak any decorations that we need to. // This used to be done via JSON, but that was slow and alloc happy so use SPIRV-Reflect instead. spv_reflect::ShaderModule Reflection(Results.target->Size(), Results.target->Data()); check(Reflection.GetResult() == SPV_REFLECT_RESULT_SUCCESS); SpvReflectResult SPVRResult = SPV_REFLECT_RESULT_NOT_READY; uint32 Count = 0; TArray Bindings; TSet Counters; TArray InputVars; TArray OutputVars; TArray ConstantBindings; TArray ExecutionModes; uint8 UAVIndices = 0xff; uint64 TextureIndices = 0xffffffffffffffff; uint64 SamplerIndices = 0xffffffffffffffff; TArray TableNames; TMap ResourceTable; if (IABTier >= 1) { for (auto Pair : Input.Environment.ResourceTableLayoutHashes) { TableNames.Add(*Pair.Key); } for (auto Pair : Input.Environment.ResourceTableMap) { const FResourceTableEntry& Entry = Pair.Value; TArray& Resources = IABs.FindOrAdd(Entry.UniformBufferName); if ((uint32)Resources.Num() <= Entry.ResourceIndex) { Resources.SetNum(Entry.ResourceIndex + 1); } FMetalResourceTableEntry NewEntry; NewEntry.UniformBufferName = Entry.UniformBufferName; NewEntry.Type = Entry.Type; NewEntry.ResourceIndex = Entry.ResourceIndex; NewEntry.Name = Pair.Key; NewEntry.Size = 1; NewEntry.bUsed = false; Resources[Entry.ResourceIndex] = NewEntry; } for (uint32 i = 0; i < (uint32)TableNames.Num(); ) { if (!IABs.Contains(TableNames[i])) { TableNames.RemoveAt(i); } else { i++; } } for (auto Pair : IABs) { uint32 Index = 0; for (uint32 i = 0; i < (uint32)Pair.Value.Num(); i++) { FMetalResourceTableEntry& Entry = Pair.Value[i]; switch(Entry.Type) { case UBMT_UAV: case UBMT_RDG_TEXTURE_UAV: case UBMT_RDG_BUFFER_UAV: Entry.ResourceIndex = Index; Entry.Size = 1; Index += 2; break; default: Entry.ResourceIndex = Index; Index++; break; } for (uint32 j = 0; j < (uint32)TableNames.Num(); j++) { if (Entry.UniformBufferName == TableNames[j]) { Entry.SetIndex = j; break; } } ResourceTable.Add(Entry.Name, Entry); } } } if(Frequency == HSF_HullShader) { uint32 PatchSize = 0; size_t ThreadSizeIdx = SourceData.find("InputPatch<"); check(ThreadSizeIdx != std::string::npos); char const* String = SourceData.c_str() + ThreadSizeIdx; #if !PLATFORM_WINDOWS size_t Found = sscanf(String, "InputPatch< %*s , %u >", &PatchSize); #else size_t Found = sscanf_s(String, "InputPatch< %*s , %u >", &PatchSize); #endif check(Found == 1); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationInputControlPoints] = FString::Printf(TEXT("// @TessellationInputControlPoints: %u\n"), PatchSize); ThreadSizeIdx = SourceData.find("[maxtessfactor("); check(ThreadSizeIdx != std::string::npos); String = SourceData.c_str() + ThreadSizeIdx; uint32 MaxTessFactor = 0; #if !PLATFORM_WINDOWS Found = sscanf(String, "[maxtessfactor(%u)]", &MaxTessFactor); #else Found = sscanf_s(String, "[maxtessfactor(%u)]", &MaxTessFactor); #endif check(Found == 1); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationMaxTessFactor] = FString::Printf(TEXT("// @TessellationMaxTessFactor: %u\n"), MaxTessFactor); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPatchesPerThreadGroup] = TEXT("// @TessellationPatchesPerThreadGroup: 1\n"); } { Count = 0; SPVRResult = Reflection.EnumerateExecutionModes(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); ExecutionModes.SetNum(Count); SPVRResult = Reflection.EnumerateExecutionModes(&Count, ExecutionModes.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); for (uint32 i = 0; i < Count; i++) { auto* Mode = ExecutionModes[i]; switch (Mode->mode) { case SpvExecutionModeLocalSize: case SpvExecutionModeLocalSizeHint: if (Frequency == HSF_ComputeShader) { check(Mode->operands_count == 3); for (uint32 j = 0; j < Mode->operands_count; j++) { WKGString += FString::Printf(TEXT("%s%u"), WKGString.Len() ? TEXT(", ") : TEXT(""), Mode->operands[j]); } } break; case SpvExecutionModeTriangles: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationDomain] = TEXT("// @TessellationDomain: tri\n"); Attribs.HSTFOutSize = 8; // sizeof(MTLTriangleTessellationFactorsHalf); } break; case SpvExecutionModeQuads: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationDomain] = TEXT("// @TessellationDomain: quad\n"); Attribs.HSTFOutSize = 12; // sizeof(MTLQuadTessellationFactorsHalf); } break; case SpvExecutionModeIsolines: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { check(0); // Not supported by Metal } break; case SpvExecutionModeOutputVertices: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { check(Mode->operands_count == 1); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationOutputControlPoints] = FString::Printf(TEXT("// @TessellationOutputControlPoints: %d\n"), Mode->operands[0]); } break; case SpvExecutionModeVertexOrderCw: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationOutputWinding] = TEXT("// @TessellationOutputWinding: cw\n"); } break; case SpvExecutionModeVertexOrderCcw: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationOutputWinding] = TEXT("// @TessellationOutputWinding: ccw\n"); } break; case SpvExecutionModeSpacingEqual: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPartitioning] = TEXT("// @TessellationPartitioning: integer\n"); } break; case SpvExecutionModeSpacingFractionalEven: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPartitioning] = TEXT("// @TessellationPartitioning: fractional_even\n"); } break; case SpvExecutionModeSpacingFractionalOdd: if (Frequency == HSF_HullShader || Frequency == HSF_DomainShader) { TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPartitioning] = TEXT("// @TessellationPartitioning: fractional_odd\n"); } break; case SpvExecutionModeInvocations: case SpvExecutionModePixelCenterInteger: case SpvExecutionModeOriginUpperLeft: case SpvExecutionModeOriginLowerLeft: case SpvExecutionModeEarlyFragmentTests: case SpvExecutionModePointMode: case SpvExecutionModeXfb: case SpvExecutionModeDepthReplacing: case SpvExecutionModeDepthGreater: case SpvExecutionModeDepthLess: case SpvExecutionModeDepthUnchanged: case SpvExecutionModeInputPoints: case SpvExecutionModeInputLines: case SpvExecutionModeInputLinesAdjacency: case SpvExecutionModeInputTrianglesAdjacency: case SpvExecutionModeOutputPoints: case SpvExecutionModeOutputLineStrip: case SpvExecutionModeOutputTriangleStrip: case SpvExecutionModeVecTypeHint: case SpvExecutionModeContractionOff: case SpvExecutionModeInitializer: case SpvExecutionModeFinalizer: case SpvExecutionModeSubgroupSize: case SpvExecutionModeSubgroupsPerWorkgroup: case SpvExecutionModeSubgroupsPerWorkgroupId: case SpvExecutionModeLocalSizeId: case SpvExecutionModeLocalSizeHintId: case SpvExecutionModePostDepthCoverage: case SpvExecutionModeStencilRefReplacingEXT: break; default: break; } } } Count = 0; SPVRResult = Reflection.EnumerateDescriptorBindings(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); Bindings.SetNum(Count); SPVRResult = Reflection.EnumerateDescriptorBindings(&Count, Bindings.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { TArray ResourceBindings; TArray ArgumentBindings; TArray UniformBindings; TArray SamplerBindings; TArray TextureSRVBindings; TArray TextureUAVBindings; TArray TBufferSRVBindings; TArray TBufferUAVBindings; TArray SBufferSRVBindings; TArray SBufferUAVBindings; TSet UsedSets; // Extract all the bindings first so that we process them in order - this lets us assign UAVs before other resources // Which is necessary to match the D3D binding scheme. for (auto const& Binding : Bindings) { if (Binding->resource_type != SPV_REFLECT_RESOURCE_FLAG_CBV && ResourceTable.Contains(UTF8_TO_TCHAR(Binding->name))) { ResourceBindings.Add(Binding); FMetalResourceTableEntry Entry = ResourceTable.FindRef(UTF8_TO_TCHAR(Binding->name)); UsedSets.Add(Entry.UniformBufferName); continue; } switch(Binding->resource_type) { case SPV_REFLECT_RESOURCE_FLAG_CBV: { check(Binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER); if (Binding->accessed) { if (TableNames.Contains(UTF8_TO_TCHAR(Binding->name))) { ArgumentBindings.Add(Binding); } else { UniformBindings.Add(Binding); } } break; } case SPV_REFLECT_RESOURCE_FLAG_SAMPLER: { check(Binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER); if (Binding->accessed) { SamplerBindings.Add(Binding); } break; } case SPV_REFLECT_RESOURCE_FLAG_SRV: { switch(Binding->descriptor_type) { case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE: { if (Binding->accessed) { TextureSRVBindings.Add(Binding); } break; } case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: { if (Binding->accessed) { TBufferSRVBindings.Add(Binding); } break; } case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER: { if (Binding->accessed) { SBufferSRVBindings.Add(Binding); } break; } default: { // check(false); break; } } break; } case SPV_REFLECT_RESOURCE_FLAG_UAV: { if (Binding->uav_counter_binding) { Counters.Add(Binding->uav_counter_binding); } switch(Binding->descriptor_type) { case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE: { TextureUAVBindings.Add(Binding); break; } case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: { TBufferUAVBindings.Add(Binding); break; } case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER: { if (!Counters.Contains(Binding) || Binding->accessed) { SBufferUAVBindings.Add(Binding); } break; } default: { // check(false); break; } } break; } default: { // check(false); break; } } } for (uint32 i = 0; i < (uint32)TableNames.Num(); ) { if (UsedSets.Contains(TableNames[i])) { IABs.FindChecked(TableNames[i])[0].SetIndex = i; i++; } else { IABs.Remove(TableNames[i]); TableNames.RemoveAt(i); } } for (uint32 i = 0; i < (uint32)ArgumentBindings.Num(); ) { FString Name = UTF8_TO_TCHAR(ArgumentBindings[i]->name); if (TableNames.Contains(Name)) { auto* ResourceArray = IABs.Find(Name); auto const& LastResource = ResourceArray->Last(); uint32 ResIndex = LastResource.ResourceIndex + LastResource.Size; uint32 SetIndex = SPV_REFLECT_SET_NUMBER_DONT_CHANGE; for (uint32 j = 0; j < (uint32)TableNames.Num(); j++) { if (Name == TableNames[j]) { SetIndex = j; break; } } FMetalResourceTableEntry Entry; Entry.UniformBufferName = LastResource.UniformBufferName; Entry.Name = Name; Entry.ResourceIndex = ResIndex; Entry.SetIndex = SetIndex; Entry.bUsed = true; ResourceArray->Add(Entry); ResourceTable.Add(Name, Entry); ResourceBindings.Add(ArgumentBindings[i]); i++; } else { UniformBindings.Add(ArgumentBindings[i]); ArgumentBindings.RemoveAt(i); } } uint32 GlobalSetId = 32; for (auto const& Binding : TBufferUAVBindings) { check(UAVIndices); uint32 Index = FPlatformMath::CountTrailingZeros(UAVIndices); // UAVs always claim all slots so we don't have conflicts as D3D expects 0-7 BufferIndices &= ~(1ull << (uint64)Index); TextureIndices &= ~(1ull << (uint64)Index); UAVIndices &= ~(1 << Index); OutputData.TypedUAVs |= (1 << Index); OutputData.TypedBuffers |= (1 << Index); UAVString += FString::Printf(TEXT("%s%s(%u:%u)"), UAVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : SBufferUAVBindings) { check(UAVIndices); uint32 Index = FPlatformMath::CountTrailingZeros(UAVIndices); // UAVs always claim all slots so we don't have conflicts as D3D expects 0-7 BufferIndices &= ~(1ull << (uint64)Index); TextureIndices &= ~(1ull << (uint64)Index); UAVIndices &= ~(1 << Index); OutputData.InvariantBuffers |= (1 << Index); UAVString += FString::Printf(TEXT("%s%s(%u:%u)"), UAVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : TextureUAVBindings) { check(UAVIndices); uint32 Index = FPlatformMath::CountTrailingZeros(UAVIndices); // UAVs always claim all slots so we don't have conflicts as D3D expects 0-7 // For texture2d this allows us to emulate atomics with buffers BufferIndices &= ~(1ull << (uint64)Index); TextureIndices &= ~(1ull << (uint64)Index); UAVIndices &= ~(1 << Index); UAVString += FString::Printf(TEXT("%s%s(%u:%u)"), UAVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } IABOffsetIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); TMap IABTier1Index; if (IABTier == 1) { for (auto const& Binding : ResourceBindings) { FMetalResourceTableEntry* Entry = ResourceTable.Find(UTF8_TO_TCHAR(Binding->name)); auto* ResourceArray = IABs.Find(Entry->UniformBufferName); if (!IABTier1Index.Contains(Entry->UniformBufferName)) { IABTier1Index.Add(Entry->UniformBufferName, 0); } if (Binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { bool bFoundBufferSizes = false; for (auto& Resource : *ResourceArray) { if (Resource.ResourceIndex == 65535) { bFoundBufferSizes = true; break; } } if (!bFoundBufferSizes) { FMetalResourceTableEntry BufferSizes; BufferSizes.UniformBufferName = Entry->UniformBufferName; BufferSizes.Name = TEXT("BufferSizes"); BufferSizes.Type = UBMT_SRV; BufferSizes.ResourceIndex = 65535; BufferSizes.SetIndex = Entry->SetIndex; BufferSizes.Size = 1; BufferSizes.bUsed = true; ResourceArray->Insert(BufferSizes, 0); IABTier1Index[Entry->UniformBufferName] = 1; } } } } for (auto const& Binding : ResourceBindings) { FMetalResourceTableEntry* Entry = ResourceTable.Find(UTF8_TO_TCHAR(Binding->name)); for (uint32 j = 0; j < (uint32)TableNames.Num(); j++) { if (Entry->UniformBufferName == TableNames[j]) { Entry->SetIndex = j; BufferIndices &= ~(1ull << ((uint64)j + IABOffsetIndex)); TextureIndices &= ~(1ull << ((uint64)j + IABOffsetIndex)); break; } } Entry->bUsed = true; auto* ResourceArray = IABs.Find(Entry->UniformBufferName); uint32 ResourceIndex = Entry->ResourceIndex; if (IABTier == 1) { for (auto& Resource : *ResourceArray) { Resource.SetIndex = Entry->SetIndex; if (Resource.ResourceIndex == Entry->ResourceIndex) { uint32& Tier1Index = IABTier1Index.FindChecked(Entry->UniformBufferName); ResourceIndex = Tier1Index++; Resource.bUsed = true; break; } } if (Entry->ResourceIndex != 65535) { SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, ResourceIndex, Entry->SetIndex); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } } else { for (auto& Resource : *ResourceArray) { if (Resource.Name == Entry->Name) { Resource.SetIndex = Entry->SetIndex; Resource.bUsed = true; break; } } SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Entry->ResourceIndex + 1, Entry->SetIndex); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } } for (auto const& Pair : IABs) { FString Name = Pair.Key; auto const& ResourceArray = Pair.Value; if (IABString.Len()) { IABString += TEXT(","); } uint32 SetIndex = ResourceArray[0].SetIndex + IABOffsetIndex; IABString += FString::Printf(TEXT("%d["), SetIndex); bool bComma = false; for (auto const& Resource : ResourceArray) { if (Resource.bUsed) { if (bComma) { IABString += TEXT(","); } IABString += FString::Printf(TEXT("%u"), (Resource.ResourceIndex == 65535 ? 0 : Resource.ResourceIndex + 1)); bComma = true; } } IABString += TEXT("]"); UBOString += FString::Printf(TEXT("%s%s(%u)"), UBOString.Len() ? TEXT(",") : TEXT(""), *Name, SetIndex); } for (auto const& Binding : TBufferSRVBindings) { check(TextureIndices); uint32 Index = FPlatformMath::CountTrailingZeros(TextureIndices); // No support for 3-component types in dxc/SPIRV/MSL - need to expose my workarounds there too BufferIndices &= ~(1ull << (uint64)Index); TextureIndices &= ~(1ull << uint64(Index)); OutputData.TypedBuffers |= (1 << Index); SRVString += FString::Printf(TEXT("%s%s(%u:%u)"), SRVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : SBufferSRVBindings) { check(BufferIndices); uint32 Index = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)Index); OutputData.InvariantBuffers |= (1 << Index); SRVString += FString::Printf(TEXT("%s%s(%u:%u)"), SRVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : UniformBindings) { check(BufferIndices); uint32 Index = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)Index); OutputData.ConstantBuffers |= (1 << Index); // Global uniform buffer - handled specially as we care about the internal layout if (strstr(Binding->name, "$Globals")) { TCBDMARangeMap CBRanges; GLOString = FString::Printf(TEXT("Globals(%u): "), Index); FString MbrString; for (uint32 i = 0; i < Binding->block.member_count; i++) { SpvReflectBlockVariable& member = Binding->block.members[i]; uint32 MbrOffset = member.absolute_offset / sizeof(float); uint32 MbrSize = member.size / sizeof(float); MbrString += FString::Printf(TEXT("%s%s(%u,%u)"), MbrString.Len() > 0 ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(member.name), MbrOffset, MbrSize); unsigned DestCBPrecision = TEXT('h'); unsigned SourceOffset = MbrOffset; unsigned DestOffset = MbrOffset; unsigned DestSize = MbrSize; unsigned DestCBIndex = 0; InsertRange(CBRanges, Index, SourceOffset, DestSize, DestCBIndex, DestCBPrecision, DestOffset); } GLOString += MbrString; for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter) { TDMARangeList& List = Iter->second; for (auto IterList = List.begin(); IterList != List.end(); ++IterList) { check(IterList->DestCBIndex == 0); PAKString += FString::Printf(TEXT("%s%u:%u-%c:%u:%u"), PAKString.Len() ? TEXT(",") : TEXT(""), IterList->SourceCB, IterList->SourceOffset, IterList->DestCBPrecision, IterList->DestOffset, IterList->Size); } } } else { // Regular uniform buffer - we only care about the binding index UBOString += FString::Printf(TEXT("%s%s(%u)"), UBOString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index); } SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : TextureSRVBindings) { check(TextureIndices); uint32 Index = FPlatformMath::CountTrailingZeros64(TextureIndices); TextureIndices &= ~(1ull << uint64(Index)); SRVString += FString::Printf(TEXT("%s%s(%u:%u)"), SRVString.Len() ? TEXT(",") : TEXT(""), UTF8_TO_TCHAR(Binding->name), Index, 1); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } for (auto const& Binding : SamplerBindings) { check(SamplerIndices); uint32 Index = FPlatformMath::CountTrailingZeros64(SamplerIndices); SamplerIndices &= ~(1ull << (uint64)Index); SMPString += FString::Printf(TEXT("%s%u:%s"), SMPString.Len() ? TEXT(",") : TEXT(""), Index, UTF8_TO_TCHAR(Binding->name)); SPVRResult = Reflection.ChangeDescriptorBindingNumbers(Binding, Index, GlobalSetId); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } } if (Frequency == HSF_PixelShader) { Count = 0; SPVRResult = Reflection.EnumerateOutputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); OutputVars.SetNum(Count); SPVRResult = Reflection.EnumerateOutputVariables(&Count, OutputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { for (auto const& Var : OutputVars) { if (Var->storage_class == SpvStorageClassOutput && Var->built_in == -1 && strstr(Var->name, "SV_Target")) { FString TypeQualifier; auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : TypeQualifier = TEXT("b"); break; case SPV_REFLECT_TYPE_FLAG_INT : TypeQualifier = (type.traits.numeric.scalar.signedness ? TEXT("i") : TEXT("u")); break; case SPV_REFLECT_TYPE_FLAG_FLOAT : TypeQualifier = (type.traits.numeric.scalar.width == 32 ? TEXT("f") : TEXT("h")); break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { TypeQualifier += FString::Printf(TEXT("%d%d"), type.traits.numeric.matrix.row_count, type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { TypeQualifier += FString::Printf(TEXT("%d"), type.traits.numeric.vector.component_count); } else { TypeQualifier += TEXT("1"); } OUTString += FString::Printf(TEXT("%s%s:SV_Target%d"), OUTString.Len() ? TEXT(",") : TEXT(""), *TypeQualifier, Var->location); } } } } if (Frequency == HSF_VertexShader) { Count = 0; SPVRResult = Reflection.EnumerateInputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); InputVars.SetNum(Count); SPVRResult = Reflection.EnumerateInputVariables(&Count, InputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { uint32 AssignedInputs = 0; for (auto const& Var : InputVars) { if (Var->storage_class == SpvStorageClassInput && Var->built_in == -1) { unsigned Location = Var->location; unsigned SemanticIndex = Location; check(Var->semantic); unsigned i = (unsigned)strlen(Var->semantic); check(i); while (isdigit((unsigned char)(Var->semantic[i-1]))) { i--; } if (i < strlen(Var->semantic)) { SemanticIndex = (unsigned)atoi(Var->semantic + i); if (Location != SemanticIndex) { Location = SemanticIndex; } } while ((1 << Location) & AssignedInputs) { Location++; } if (Location != Var->location) { SPVRResult = Reflection.ChangeInputVariableLocation(Var, Location); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); } uint32 ArrayCount = 1; for (uint32 Dim = 0; Dim < Var->array.dims_count; Dim++) { ArrayCount *= Var->array.dims[Dim]; } FString TypeQualifier; auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : TypeQualifier = TEXT("b"); break; case SPV_REFLECT_TYPE_FLAG_INT : TypeQualifier = (type.traits.numeric.scalar.signedness ? TEXT("i") : TEXT("u")); break; case SPV_REFLECT_TYPE_FLAG_FLOAT : TypeQualifier = (type.traits.numeric.scalar.width == 32 ? TEXT("f") : TEXT("h")); break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { TypeQualifier += FString::Printf(TEXT("%d%d"), type.traits.numeric.matrix.row_count, type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { TypeQualifier += FString::Printf(TEXT("%d"), type.traits.numeric.vector.component_count); } else { TypeQualifier += TEXT("1"); } for (uint32 j = 0; j < ArrayCount; j++) { AssignedInputs |= (1 << (Location + j)); INPString += FString::Printf(TEXT("%s%s:in_ATTRIBUTE%d"), INPString.Len() ? TEXT(",") : TEXT(""), *TypeQualifier, (Location + j)); } } } } } if (Frequency == HSF_VertexShader && bUsingTessellation) { Count = 0; SPVRResult = Reflection.EnumerateOutputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); OutputVars.SetNum(Count); SPVRResult = Reflection.EnumerateOutputVariables(&Count, OutputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { OutputVars.Sort([](SpvReflectInterfaceVariable& LHS, SpvReflectInterfaceVariable& RHS) { return LHS.location < RHS.location; }); uint32 Offset = 0; uint32 InitialAlign = 0; for (auto const& Var : OutputVars) { if (Var->storage_class == SpvStorageClassOutput && Var->built_in == -1) { unsigned Location = Var->location; uint32 ArrayCount = 1; for (uint32 Dim = 0; Dim < Var->array.dims_count; Dim++) { ArrayCount *= Var->array.dims[Dim]; } auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; for (uint32 j = 0; j < ArrayCount; j++) { FMetalAttribute& Attrib = Attribs.HSOut.AddDefaulted_GetRef(); Attrib.Index = Location + j; uint32 ElementSize = 0; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : Attrib.Type = EMetalComponentType::Bool; ElementSize = 1; break; case SPV_REFLECT_TYPE_FLAG_INT : Attrib.Type = (type.traits.numeric.scalar.signedness ? EMetalComponentType::Int : EMetalComponentType::Uint); ElementSize = 4; break; case SPV_REFLECT_TYPE_FLAG_FLOAT : Attrib.Type = (type.traits.numeric.scalar.width == 32 ? EMetalComponentType::Float : EMetalComponentType::Half); if(type.traits.numeric.scalar.width == 32) { ElementSize = 4; } else { ElementSize = 2; } break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { Attrib.Components = (type.traits.numeric.matrix.row_count * type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { Attrib.Components = (type.traits.numeric.vector.component_count); } else { Attrib.Components = 1; } char const* NumberedSemantic = strstr(Var->name, Var->semantic); FString Semantic = FString::Printf(TEXT("%s_%u"), UTF8_TO_TCHAR(NumberedSemantic), j); Attrib.Semantic = GetTypeHash(Semantic); uint32 AlignedComponents = Attrib.Components == 3 ? 4 : Attrib.Components; InitialAlign = InitialAlign == 0 ? ElementSize * AlignedComponents : InitialAlign; Offset = Align(Offset, ElementSize * AlignedComponents); Attrib.Offset = Offset; Offset += ElementSize * AlignedComponents; if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT("%sHSOut.%s(%u)"), ALNString.Len() ? TEXT(",") : TEXT(""), *Semantic, Attrib.Offset); } } } Attribs.HSOutSize = Align(Offset, 16); if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT(" = HSOutSize(%u)"), Attribs.HSOutSize); } } if (Frequency == HSF_HullShader) { Count = 0; SPVRResult = Reflection.EnumerateInputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); InputVars.SetNum(Count); SPVRResult = Reflection.EnumerateInputVariables(&Count, InputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { InputVars.Sort([](SpvReflectInterfaceVariable& LHS, SpvReflectInterfaceVariable& RHS) { return LHS.location < RHS.location; }); uint32 Offset = 0; for (auto const& Var : InputVars) { if (Var->storage_class == SpvStorageClassInput && Var->built_in == -1) { unsigned Location = Var->location; uint32 ArrayCount = 1; for (uint32 Dim = 1; Dim < Var->array.dims_count; Dim++) { ArrayCount *= Var->array.dims[Dim]; } auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; for (uint32 j = 0; j < ArrayCount; j++) { FMetalAttribute& Attrib = Attribs.HSIn.AddDefaulted_GetRef(); Attrib.Index = Location + j; uint32 ElementSize = 0; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : Attrib.Type = EMetalComponentType::Bool; ElementSize = 1; break; case SPV_REFLECT_TYPE_FLAG_INT : Attrib.Type = (type.traits.numeric.scalar.signedness ? EMetalComponentType::Int : EMetalComponentType::Uint); ElementSize = 4; break; case SPV_REFLECT_TYPE_FLAG_FLOAT : Attrib.Type = (type.traits.numeric.scalar.width == 32 ? EMetalComponentType::Float : EMetalComponentType::Half); if(type.traits.numeric.scalar.width == 32) { ElementSize = 4; } else { ElementSize = 2; } break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { Attrib.Components = (type.traits.numeric.matrix.row_count * type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { Attrib.Components = (type.traits.numeric.vector.component_count); } else { Attrib.Components = 1; } char const* NumberedSemantic = strstr(Var->name, Var->semantic); FString Semantic = FString::Printf(TEXT("%s_%u"), UTF8_TO_TCHAR(NumberedSemantic), j); Attrib.Semantic = GetTypeHash(Semantic); uint32 AlignedComponents = Attrib.Components == 3 ? 4 : Attrib.Components; Offset = Align(Offset, ElementSize * AlignedComponents); Attrib.Offset = Offset; Offset += ElementSize * AlignedComponents; if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT("%sHSIn.%s(%u)"), ALNString.Len() ? TEXT(",") : TEXT(""), *Semantic, Attrib.Offset); } } } } Count = 0; SPVRResult = Reflection.EnumerateOutputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); OutputVars.SetNum(Count); SPVRResult = Reflection.EnumerateOutputVariables(&Count, OutputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { uint32 HSOutOffset = 0; uint32 PatchControlPointOffset = 0; uint32 HSOutAlign = 0; uint32 PatchControlPointAlign = 0; OutputVars.Sort([](SpvReflectInterfaceVariable& LHS, SpvReflectInterfaceVariable& RHS) { return LHS.location < RHS.location; }); for (auto const& Var : OutputVars) { if (Var->storage_class == SpvStorageClassOutput && Var->built_in == -1) { unsigned Location = Var->location; uint32 ArrayCount = 1; for (uint32 Dim = 1; Dim < Var->array.dims_count; Dim++) { ArrayCount *= Var->array.dims[Dim]; } auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; for (uint32 j = 0; j < ArrayCount; j++) { FMetalAttribute& Attrib = Var->array.dims_count ? Attribs.PatchControlPointOut.AddDefaulted_GetRef() : Attribs.HSOut.AddDefaulted_GetRef(); Attrib.Index = Location + j; uint32 ElementSize = 0; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : Attrib.Type = EMetalComponentType::Bool; ElementSize = 1; break; case SPV_REFLECT_TYPE_FLAG_INT : Attrib.Type = (type.traits.numeric.scalar.signedness ? EMetalComponentType::Int : EMetalComponentType::Uint); ElementSize = 4; break; case SPV_REFLECT_TYPE_FLAG_FLOAT : Attrib.Type = (type.traits.numeric.scalar.width == 32 ? EMetalComponentType::Float : EMetalComponentType::Half); if(type.traits.numeric.scalar.width == 32) { ElementSize = 4; } else { ElementSize = 2; } break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { Attrib.Components = (type.traits.numeric.matrix.row_count * type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { Attrib.Components = (type.traits.numeric.vector.component_count); } else { Attrib.Components = 1; } uint32& Offset = Var->array.dims_count ? PatchControlPointOffset : HSOutOffset; uint32& InitialAlign = Var->array.dims_count ? PatchControlPointAlign : HSOutAlign; TCHAR const* Name = Var->array.dims_count ? TEXT("PatchOut") : TEXT("HSOut"); char const* NumberedSemantic = strstr(Var->name, Var->semantic); FString Semantic = FString::Printf(TEXT("%s_%u"), UTF8_TO_TCHAR(NumberedSemantic), j); Attrib.Semantic = GetTypeHash(Semantic); uint32 AlignedComponents = Attrib.Components == 3 ? 4 : Attrib.Components; InitialAlign = InitialAlign == 0 ? ElementSize * AlignedComponents : InitialAlign; Offset = Align(Offset, ElementSize * AlignedComponents); Attrib.Offset = Offset; Offset += ElementSize * AlignedComponents; if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT("%s%s.%s(%u)"), ALNString.Len() ? TEXT(",") : TEXT(""), Name, *Semantic, Attrib.Offset); } } } Attribs.HSOutSize = Align(HSOutOffset, 16); Attribs.PatchControlPointOutSize = Align(PatchControlPointOffset, 16); if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT(" = HSOutSize(%u), PatchControlPointOutSize(%u)"), Attribs.HSOutSize, Attribs.PatchControlPointOutSize); } } if (Frequency == HSF_DomainShader) { Count = 0; SPVRResult = Reflection.EnumerateInputVariables(&Count, nullptr); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); InputVars.SetNum(Count); SPVRResult = Reflection.EnumerateInputVariables(&Count, InputVars.GetData()); check(SPVRResult == SPV_REFLECT_RESULT_SUCCESS); if (Count > 0) { uint32 HSOutOffset = 0; uint32 PatchControlPointOffset = 0; uint32 HSOutAlign = 0; uint32 PatchControlPointAlign = 0; InputVars.Sort([](SpvReflectInterfaceVariable& LHS, SpvReflectInterfaceVariable& RHS) { return LHS.location < RHS.location; }); for (auto const& Var : InputVars) { if (Var->storage_class == SpvStorageClassInput && Var->built_in == -1) { unsigned Location = Var->location; uint32 ArrayCount = 1; for (uint32 Dim = 1; Dim < Var->array.dims_count; Dim++) { ArrayCount *= Var->array.dims[Dim]; } auto const type = *Var->type_description; uint32_t masked_type = type.type_flags & 0xF; for (uint32 j = 0; j < ArrayCount; j++) { FMetalAttribute& Attrib = Var->array.dims_count ? Attribs.PatchControlPointOut.AddDefaulted_GetRef() : Attribs.HSOut.AddDefaulted_GetRef(); Attrib.Index = Location + j; uint32 ElementSize = 0; switch (masked_type) { default: checkf(false, TEXT("unsupported component type %d"), masked_type); break; case SPV_REFLECT_TYPE_FLAG_BOOL : Attrib.Type = EMetalComponentType::Bool; ElementSize = 1; break; case SPV_REFLECT_TYPE_FLAG_INT : Attrib.Type = (type.traits.numeric.scalar.signedness ? EMetalComponentType::Int : EMetalComponentType::Uint); ElementSize = 4; break; case SPV_REFLECT_TYPE_FLAG_FLOAT : Attrib.Type = (type.traits.numeric.scalar.width == 32 ? EMetalComponentType::Float : EMetalComponentType::Half); if(type.traits.numeric.scalar.width == 32) { ElementSize = 4; } else { ElementSize = 2; } break; } if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) { Attrib.Components = (type.traits.numeric.matrix.row_count * type.traits.numeric.matrix.column_count); } else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { Attrib.Components = (type.traits.numeric.vector.component_count); } else { Attrib.Components = 1; } uint32& Offset = Var->array.dims_count ? PatchControlPointOffset : HSOutOffset; uint32& InitialAlign = Var->array.dims_count ? PatchControlPointAlign : HSOutAlign; TCHAR const* Name = Var->array.dims_count ? TEXT("PatchOut") : TEXT("HSOut"); char const* NumberedSemantic = strstr(Var->name, Var->semantic); FString Semantic = FString::Printf(TEXT("%s_%u"), UTF8_TO_TCHAR(NumberedSemantic), j); Attrib.Semantic = GetTypeHash(Semantic); uint32 AlignedComponents = Attrib.Components == 3 ? 4 : Attrib.Components; InitialAlign = InitialAlign == 0 ? ElementSize * AlignedComponents : InitialAlign; Offset = Align(Offset, ElementSize * AlignedComponents); Attrib.Offset = Offset; Offset += ElementSize * AlignedComponents; if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT("%s%s.%s(%u)"), ALNString.Len() ? TEXT(",") : TEXT(""), Name, *Semantic, Attrib.Offset); } } } Attribs.HSOutSize = Align(HSOutOffset, 16); Attribs.PatchControlPointOutSize = Align(PatchControlPointOffset, 16); if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo)) ALNString += FString::Printf(TEXT(" = HSOutSize(%u), PatchControlPointOutSize(%u)"), Attribs.HSOutSize, Attribs.PatchControlPointOutSize); } } ShaderConductor::Blob* OldData = Results.target; Results.target = ShaderConductor::CreateBlob(Reflection.GetCode(), Reflection.GetCodeSize()); ShaderConductor::DestroyBlob(OldData); } uint32 SideTableIndex = 0; uint32 OutputBufferIndex = 0; uint32 IndirectParamsIndex = 0; uint32 PatchBufferIndex = 0; uint32 TessFactorBufferIndex = 0; uint32 HullIndexBuffer = 0; if (Result) { ShaderConductor::DestroyBlob(Results.errorWarningMsg); Results.errorWarningMsg = nullptr; TargetDesc.language = ShaderConductor::ShadingLanguage::Msl; SideTableIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); char BufferIdx[3]; FCStringAnsi::Snprintf(BufferIdx, 3, "%d", SideTableIndex); BufferIndices &= ~(1ull << (uint64)SideTableIndex); ShaderConductor::MacroDefine Defines[17] = { {"texel_buffer_texture_width", "0"}, {"enforce_storge_buffer_bounds", "1"}, {"buffer_size_buffer_index", BufferIdx}, {"invariant_float_math", Options.enableFMAPass ? "1" : "0"}, {"enable_decoration_binding","1"}, }; TargetDesc.numOptions = 5; TargetDesc.options = &Defines[0]; switch(Semantics) { case EMetalGPUSemanticsImmediateDesktop: TargetDesc.platform = "macOS"; break; case EMetalGPUSemanticsTBDRDesktop: TargetDesc.platform = "iOS"; Defines[TargetDesc.numOptions++] = { "ios_support_base_vertex_instance", "1" }; Defines[TargetDesc.numOptions++] = { "ios_use_framebuffer_fetch_subpasses", "1" }; Defines[TargetDesc.numOptions++] = { "emulate_cube_array", "1" }; break; case EMetalGPUSemanticsMobile: default: TargetDesc.platform = "iOS"; Defines[TargetDesc.numOptions++] = { "ios_use_framebuffer_fetch_subpasses", "1" }; Defines[TargetDesc.numOptions++] = { "emulate_cube_array", "1" }; break; } char SubpassInput0DimStr[2]; if (SubpassInput0Dim >= 1 && SubpassInput0Dim <= 4) { // If a dimension for the subpass input attachment at binding slot 0 was determined, // forward this dimension to SPIRV-Cross because SPIR-V doesn't support a dimension for OpTypeImage instruction with SubpassData FCStringAnsi::Snprintf(SubpassInput0DimStr, 2, "%u", static_cast(SubpassInput0Dim)); Defines[TargetDesc.numOptions++] = { "subpass_input_dimension0", SubpassInput0DimStr }; } if (Frequency == HSF_VertexShader && bUsingTessellation) { Defines[TargetDesc.numOptions++] = { "capture_output_to_buffer", "1" }; } char OutputBufferIdx[3]; char IndirectParamsIdx[3]; if (Frequency == HSF_VertexShader && bUsingTessellation) { OutputBufferIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)OutputBufferIndex); FCStringAnsi::Snprintf(OutputBufferIdx, 3, "%u", OutputBufferIndex); Defines[TargetDesc.numOptions++] = { "shader_output_buffer_index", OutputBufferIdx }; IndirectParamsIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)IndirectParamsIndex); FCStringAnsi::Snprintf(IndirectParamsIdx, 3, "%u", IndirectParamsIndex); Defines[TargetDesc.numOptions++] = { "indirect_params_buffer_index", IndirectParamsIdx }; TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationHSOutBuffer] = FString::Printf(TEXT("// @TessellationHSOutBuffer: %u\n"), OutputBufferIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPatchCountBuffer] = FString::Printf(TEXT("// @TessellationPatchCountBuffer: %u\n"), IndirectParamsIndex); } if (Frequency == HSF_DomainShader) { OutputBufferIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)OutputBufferIndex); IndirectParamsIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)IndirectParamsIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationHSOutBuffer] = FString::Printf(TEXT("// @TessellationHSOutBuffer: %u\n"), OutputBufferIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationControlPointOutBuffer] = FString::Printf(TEXT("// @TessellationControlPointOutBuffer: %u\n"), IndirectParamsIndex); } char PatchBufferIdx[3]; char TessFactorBufferIdx[3]; if (Frequency == HSF_HullShader) { OutputBufferIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)OutputBufferIndex); FCStringAnsi::Snprintf(OutputBufferIdx, 3, "%u", OutputBufferIndex); Defines[TargetDesc.numOptions++] = { "shader_output_buffer_index", OutputBufferIdx }; IndirectParamsIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)IndirectParamsIndex); FCStringAnsi::Snprintf(IndirectParamsIdx, 3, "%u", IndirectParamsIndex); Defines[TargetDesc.numOptions++] = { "indirect_params_buffer_index", IndirectParamsIdx }; PatchBufferIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)PatchBufferIndex); FCStringAnsi::Snprintf(PatchBufferIdx, 3, "%u", PatchBufferIndex); Defines[TargetDesc.numOptions++] = { "shader_patch_output_buffer_index", PatchBufferIdx }; TessFactorBufferIndex = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)TessFactorBufferIndex); FCStringAnsi::Snprintf(TessFactorBufferIdx, 3, "%u", TessFactorBufferIndex); Defines[TargetDesc.numOptions++] = { "shader_tess_factor_buffer_index", TessFactorBufferIdx }; Defines[TargetDesc.numOptions++] = { "shader_input_wg_index", "0" }; HullIndexBuffer = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)HullIndexBuffer); uint32 HullVertexBuffer = FPlatformMath::CountTrailingZeros64(BufferIndices); BufferIndices &= ~(1ull << (uint64)HullVertexBuffer); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationHSOutBuffer] = FString::Printf(TEXT("// @TessellationHSOutBuffer: %u\n"), PatchBufferIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationPatchCountBuffer] = FString::Printf(TEXT("// @TessellationPatchCountBuffer: %u\n"), IndirectParamsIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationControlPointIndexBuffer] = FString::Printf(TEXT("// @TessellationControlPointIndexBuffer: %u\n"), HullVertexBuffer); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationHSTFOutBuffer] = FString::Printf(TEXT("// @TessellationHSTFOutBuffer: %u\n"), TessFactorBufferIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationControlPointOutBuffer] = FString::Printf(TEXT("// @TessellationControlPointOutBuffer: %u\n"), OutputBufferIndex); TESStrings[(uint8)EMetalTessellationMetadataTags::TessellationIndexBuffer] = FString::Printf(TEXT("// @TessellationIndexBuffer: %u\n"), HullIndexBuffer); } char ArgumentBufferOffset[3]; switch (VersionEnum) { case 6: case 5: case 4: { if (IABTier >= 1) { FCStringAnsi::Snprintf(ArgumentBufferOffset, 3, "%u", IABOffsetIndex); Defines[TargetDesc.numOptions++] = { "argument_buffers", "1" }; Defines[TargetDesc.numOptions++] = { "argument_buffer_offset", ArgumentBufferOffset }; } Defines[TargetDesc.numOptions++] = { "texture_buffer_native", "1" }; TargetDesc.version = "20100"; break; } case 3: { TargetDesc.version = "20000"; break; } case 2: { TargetDesc.version = "10200"; break; } case 1: { TargetDesc.version = "10100"; break; } case 0: default: { TargetDesc.version = "10000"; break; } } ShaderConductor::Blob* IRBlob = Results.target; Results = ShaderConductor::Compiler::ConvertBinary(Results, SourceDesc, TargetDesc); ShaderConductor::DestroyBlob(IRBlob); } Result = (Results.hasError) ? 0 : 1; if (!Results.hasError && Results.target) { MetaData += TEXT("// Compiled by ShaderConductor\n"); if (INPString.Len()) { MetaData += FString::Printf(TEXT("// @Inputs: %s\n"), *INPString); } if (OUTString.Len()) { MetaData += FString::Printf(TEXT("// @Outputs: %s\n"), *OUTString); } if (UBOString.Len()) { MetaData += FString::Printf(TEXT("// @UniformBlocks: %s\n"), *UBOString); } if (GLOString.Len()) { MetaData += FString::Printf(TEXT("// @PackedUB: %s\n"), *GLOString); } if (PAKString.Len()) { MetaData += FString::Printf(TEXT("// @PackedUBGlobalCopies: %s\n"), *PAKString); } if (SRVString.Len()) { MetaData += FString::Printf(TEXT("// @Samplers: %s\n"), *SRVString); } if (UAVString.Len()) { MetaData += FString::Printf(TEXT("// @UAVs: %s\n"), *UAVString); } if (SMPString.Len()) { MetaData += FString::Printf(TEXT("// @SamplerStates: %s\n"), *SMPString); } if (WKGString.Len()) { MetaData += FString::Printf(TEXT("// @NumThreads: %s\n"), *WKGString); } if(strstr((const char*)Results.target->Data(), "spvBufferSizeConstants")) { MetaData += FString::Printf(TEXT("// @SideTable: spvBufferSizeConstants(%d)\n"), SideTableIndex); } if (IABString.Len()) { MetaData += TEXT("// @ArgumentBuffers: "); MetaData += IABString; MetaData += TEXT("\n"); } FString TESString; for (uint32 i = 0; i < (uint32)EMetalTessellationMetadataTags::Num; i++) { TESString += TESStrings[i]; } if (TESString.Len()) { MetaData += TESString; } MetaData += TEXT("\n\n"); if (ALNString.Len()) { MetaData += TEXT("// Attributes: "); MetaData += ALNString; MetaData += TEXT("\n\n"); } MetalSource = TCHAR_TO_UTF8(*MetaData); MetalSource += std::string((const char*)Results.target->Data(), Results.target->Size()); // Tessellation vertex & hull shaders must always use FMA if (Options.enableFMAPass) { std::string FMADefine = std::string("\n" "template\n" "static inline __attribute__((always_inline))\n" "T ue4_cross(T x, T y)\n" "{\n" " metal::float3 fx = metal::float3(x);\n" " metal::float3 fy = metal::float3(y);\n" " return T(metal::fma(fx[1], fy[2], -metal::fma(fy[1], fx[2], 0.0)), metal::fma(fx[2], fy[0], -metal::fma(fy[2], fx[0], 0.0)), metal::fma(fx[0], fy[1], -metal::fma(fy[0], fx[1], 0.0)));\n" "}\n" "#define cross ue4_cross\n\n" "using namespace metal;" ); std::string IncludeString = "using namespace metal;"; size_t IncludePos = MetalSource.find(IncludeString); if (IncludePos != std::string::npos) MetalSource.replace(IncludePos, IncludeString.length(), FMADefine); } if (Frequency == HSF_DomainShader) { uint32 PatchSize = 0; size_t ThreadSizeIdx = SourceData.find("OutputPatch<"); check(ThreadSizeIdx != std::string::npos); char const* String = SourceData.c_str() + ThreadSizeIdx; while(!isdigit((unsigned char)*String)) { String++; } #if !PLATFORM_WINDOWS size_t Found = sscanf(String, "%u", &PatchSize); #else size_t Found = sscanf_s(String, "%u", &PatchSize); #endif check(Found == 1); std::string PatchAttr; std::string PatchSearch = "[[ patch(triangle, 0) ]]"; size_t PatchIdx = MetalSource.find(PatchSearch); if (PatchIdx != std::string::npos) { PatchAttr = "[[ patch(triangle, "; } else { PatchSearch = "[[ patch(quad, 0) ]]"; PatchIdx = MetalSource.find(PatchSearch); PatchAttr = "[[ patch(quad, "; } if (PatchIdx != std::string::npos) { char BufferIdx[3]; FCStringAnsi::Snprintf(BufferIdx, 3, "%d", PatchSize); PatchAttr += BufferIdx; PatchAttr += ") ]]"; MetalSource.replace(PatchIdx, PatchSearch.length(), PatchAttr); } CRCLen = MetalSource.length(); CRC = FCrc::MemCrc_DEPRECATED(MetalSource.c_str(), CRCLen); std::string PatchInputName = EntryPointName + "_patchIn"; std::string PatchNameDefine = "#define "; PatchNameDefine += PatchInputName; PatchNameDefine += " "; PatchNameDefine += PatchInputName; PatchNameDefine += std::to_string(CRC); PatchNameDefine += std::to_string(CRCLen); PatchNameDefine += "\n"; std::string PatchInput = EntryPointName + "_in"; std::string PatchDefine = "#define "; PatchDefine += PatchInput; PatchDefine += " "; PatchDefine += PatchInput; PatchDefine += std::to_string(CRC); PatchDefine += std::to_string(CRCLen); PatchDefine += "\n"; size_t Pos = MetalSource.find("#include "); MetalSource.insert(Pos, PatchNameDefine); MetalSource.insert(Pos, PatchDefine); } CRCLen = MetalSource.length(); CRC = FCrc::MemCrc_DEPRECATED(MetalSource.c_str(), CRCLen); ANSICHAR MainCRC[25]; int32 NewLen = FCStringAnsi::Snprintf(MainCRC, 25, "Main_%0.8x_%0.8x(", CRCLen, CRC); std::string MainEntryPoint = EntryPointName + "("; size_t Pos; do { Pos = MetalSource.find(MainEntryPoint); if (Pos != std::string::npos) MetalSource.replace(Pos, MainEntryPoint.length(), MainCRC); } while(Pos != std::string::npos); } // Version 6 means Tier 2 IABs for now. if (IABTier >= 2) { char BufferIdx[3]; for (auto& IAB : IABs) { uint32 Index = IAB.Value[0].SetIndex; FMemory::Memzero(BufferIdx); FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Index); std::string find_str = "struct spvDescriptorSetBuffer"; find_str += BufferIdx; size_t Pos = MetalSource.find(find_str); if (Pos != std::string::npos) { size_t StartPos = MetalSource.find("{", Pos); size_t EndPos = MetalSource.find("}", StartPos); std::string IABName(TCHAR_TO_UTF8(*IAB.Key)); size_t UBPos = MetalSource.find("constant type_" + IABName + "*"); std::string Declaration = find_str + "\n{\n\tconstant uint* spvBufferSizeConstants [[id(0)]];\n"; for (FMetalResourceTableEntry& Entry : IAB.Value) { std::string EntryString; std::string Name(TCHAR_TO_UTF8(*Entry.Name)); switch(Entry.Type) { case UBMT_TEXTURE: case UBMT_RDG_TEXTURE: case UBMT_RDG_TEXTURE_SRV: case UBMT_SRV: case UBMT_SAMPLER: case UBMT_RDG_BUFFER: case UBMT_RDG_BUFFER_SRV: case UBMT_UAV: case UBMT_RDG_TEXTURE_UAV: case UBMT_RDG_BUFFER_UAV: { size_t EntryPos = MetalSource.find(Name + " [[id("); if (EntryPos != std::string::npos) { while(MetalSource[--EntryPos] != '\n') {} while(MetalSource[++EntryPos] != '\n') { EntryString += MetalSource[EntryPos]; } EntryString += "\n"; } else { switch(Entry.Type) { case UBMT_TEXTURE: case UBMT_RDG_TEXTURE: case UBMT_RDG_TEXTURE_SRV: case UBMT_SRV: { std::string typeName = "texture_buffer"; int32 NameIndex = PreprocessedShader.Find(Entry.Name + ";"); int32 DeclIndex = NameIndex; if (DeclIndex > 0) { while(PreprocessedShader[--DeclIndex] != TEXT('\n')) {} FString Decl = PreprocessedShader.Mid(DeclIndex, NameIndex - DeclIndex); TCHAR const* Types[] = { TEXT("ByteAddressBuffer<"), TEXT("StructuredBuffer<"), TEXT("Buffer<"), TEXT("Texture2DArray"), TEXT("TextureCubeArray"), TEXT("Texture2D"), TEXT("Texture3D"), TEXT("TextureCube") }; char const* NewTypes[] = { "device void*", "device void*", "texture_buffer", "texture2d_array", "texturecube_array", "texture2d", "texture3d", "texturecube" }; for (uint32 i = 0; i < 8; i++) { if (Decl.Contains(Types[i])) { typeName = NewTypes[i]; break; } } } FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 1); EntryString = "\t"; EntryString += typeName; EntryString += " "; EntryString += Name; EntryString += " [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; break; } case UBMT_SAMPLER: { FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 1); EntryString = "\tsampler "; EntryString += Name; EntryString += " [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; break; } case UBMT_RDG_BUFFER: case UBMT_RDG_BUFFER_SRV: { FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 1); EntryString = "\tdevice void* "; EntryString += Name; EntryString += " [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; break; } case UBMT_UAV: case UBMT_RDG_TEXTURE_UAV: { std::string typeName = "texture_buffer"; int32 NameIndex = PreprocessedShader.Find(Entry.Name + ";"); int32 DeclIndex = NameIndex; if (DeclIndex > 0) { while(PreprocessedShader[--DeclIndex] != TEXT('\n')) {} FString Decl = PreprocessedShader.Mid(DeclIndex, NameIndex - DeclIndex); TCHAR const* Types[] = { TEXT("ByteAddressBuffer<"), TEXT("StructuredBuffer<"), TEXT("Buffer<"), TEXT("Texture2DArray"), TEXT("TextureCubeArray"), TEXT("Texture2D"), TEXT("Texture3D"), TEXT("TextureCube") }; char const* NewTypes[] = { "device void*", "device void*", "texture_buffer", "texture2d_array", "texturecube_array", "texture2d", "texture3d", "texturecube" }; for (uint32 i = 0; i < 8; i++) { if (Decl.Contains(Types[i])) { typeName = NewTypes[i]; break; } } } FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 1); EntryString = "\t"; EntryString += typeName; EntryString += " "; EntryString += Name; EntryString += " [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 2); EntryString = "\tdevice void* "; EntryString += Name; EntryString += "_atomic [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; break; } case UBMT_RDG_BUFFER_UAV: { FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 1); EntryString = "\ttexture_buffer "; EntryString += Name; EntryString += " [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; FCStringAnsi::Snprintf(BufferIdx, 3, "%d", Entry.ResourceIndex + 2); EntryString = "\tdevice void* "; EntryString += Name; EntryString += "_atomic [[id("; EntryString += BufferIdx; EntryString += ")]];\n"; break; } default: break; } } Declaration += EntryString; break; } default: { break; } } } if (UBPos < EndPos) { size_t UBEnd = MetalSource.find(";", UBPos); std::string UBStr = MetalSource.substr(UBPos, (UBEnd - UBPos)); Declaration += "\t"; Declaration += UBStr; Declaration += ";\n"; } else { Declaration += "\tconstant void* uniformdata [[id("; FMemory::Memzero(BufferIdx); FCStringAnsi::Snprintf(BufferIdx, 3, "%d", IAB.Value.Num() + 1); Declaration += BufferIdx; Declaration += ")]];\n"; } Declaration += "}"; MetalSource.replace(Pos, (EndPos - Pos) + 1, Declaration); } } } if (Results.errorWarningMsg) { std::string ErrorText((const char*)Results.errorWarningMsg->Data(), Results.errorWarningMsg->Size()); TArray ErrorLines; MetalErrors = ANSI_TO_TCHAR((char const*)ErrorText.c_str()); MetalErrors.ParseIntoArray(ErrorLines, TEXT("\n"), true); MetalErrors = TEXT(""); for (int32 LineIndex = 0; LineIndex < ErrorLines.Num(); ++LineIndex) { MetalErrors += Input.VirtualSourceFilePath + TEXT("(0): ") + ErrorLines[LineIndex] + TEXT("\n"); } ShaderConductor::DestroyBlob(Results.errorWarningMsg); Results.errorWarningMsg = nullptr; } if (Results.target) { ShaderConductor::DestroyBlob(Results.target); Results.target = nullptr; } if (RewriteBlob) { ShaderConductor::DestroyBlob(RewriteBlob); } } #endif #endif if (bDumpDebugInfo) { if (MetalSource.length() > 0u) { FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / FPaths::GetBaseFilename(Input.GetSourceFilename()) + TEXT(".metal"))); if (FileWriter) { FileWriter->Serialize(const_cast(MetalSource.c_str()), MetalSource.length()); FileWriter->Close(); delete FileWriter; } } } if (Result != 0) { Output.Target = Input.Target; BuildMetalShaderOutput(Output, Input, GUIDHash, CCFlags, MetalSource.c_str(), MetalSource.length(), CRCLen, CRC, VersionEnum, *Standard, *MinOSVersion, TypeMode, Output.Errors, Attribs, OutputData.TypedBuffers, OutputData.InvariantBuffers, OutputData.TypedUAVs, OutputData.ConstantBuffers, OutputData.TypedBufferFormats, bAllowFastIntriniscs); FMemoryWriter Ar(OutData); Ar << Output; } else { TArray ErrorLines; MetalErrors.ParseIntoArray(ErrorLines, TEXT("\n"), true); bool const bDirectCompile = FParse::Param(FCommandLine::Get(), TEXT("directcompile")); for (int32 LineIndex = 0; LineIndex < ErrorLines.Num(); ++LineIndex) { const FString& Line = ErrorLines[LineIndex]; Output.Errors.Add(FShaderCompilerError(*Line)); CrossCompiler::ParseHlslccError(Output.Errors, Line, false); if (bDirectCompile) { UE_LOG(LogShaders, Error, TEXT("%s"), *Line); } } } return Output.bSucceeded; }