You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
reviewedby michael.trepka, rolando.caloca, lee.clark [CL 2670859 by Mark Satterthwaite in Main branch]
654 lines
22 KiB
C++
654 lines
22 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
// ..
|
|
|
|
#include "MetalShaderFormat.h"
|
|
#include "Core.h"
|
|
#include "ShaderCore.h"
|
|
#include "MetalShaderResources.h"
|
|
#include "ShaderCompilerCommon.h"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "AllowWindowsPlatformTypes.h"
|
|
#include "Windows/PreWindowsApi.h"
|
|
#include <objbase.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include "Windows/PostWindowsApi.h"
|
|
#include "Windows/MinWindows.h"
|
|
#include "HideWindowsPlatformTypes.h"
|
|
#endif
|
|
#include "ShaderPreprocessor.h"
|
|
#include "hlslcc.h"
|
|
#include "MetalBackend.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogMetalShaderCompiler, Log, All);
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Shader compiling.
|
|
------------------------------------------------------------------------------*/
|
|
|
|
static inline uint32 ParseNumber(const TCHAR* Str)
|
|
{
|
|
uint32 Num = 0;
|
|
while (*Str && *Str >= '0' && *Str <= '9')
|
|
{
|
|
Num = Num * 10 + *Str++ - '0';
|
|
}
|
|
return Num;
|
|
}
|
|
|
|
/**
|
|
* Construct the final microcode from the compiled and verified shader source.
|
|
* @param ShaderOutput - Where to store the microcode and parameter map.
|
|
* @param InShaderSource - Metal source with input/output signature.
|
|
* @param SourceLen - The length of the Metal source code.
|
|
*/
|
|
static void BuildMetalShaderOutput(
|
|
FShaderCompilerOutput& ShaderOutput,
|
|
const FShaderCompilerInput& ShaderInput,
|
|
const ANSICHAR* InShaderSource,
|
|
int32 SourceLen,
|
|
bool bIsMobile,
|
|
TArray<FShaderCompilerError>& OutErrors
|
|
)
|
|
{
|
|
const ANSICHAR* USFSource = InShaderSource;
|
|
CrossCompiler::FHlslccHeader CCHeader;
|
|
if (!CCHeader.Read(USFSource, SourceLen))
|
|
{
|
|
UE_LOG(LogMetalShaderCompiler, Fatal, TEXT("Bad hlslcc header found"));
|
|
}
|
|
|
|
FMetalCodeHeader Header = {0};
|
|
FShaderParameterMap& ParameterMap = ShaderOutput.ParameterMap;
|
|
EShaderFrequency Frequency = (EShaderFrequency)ShaderOutput.Target.Frequency;
|
|
|
|
TBitArray<> UsedUniformBufferSlots;
|
|
UsedUniformBufferSlots.Init(false,32);
|
|
|
|
// Write out the magic markers.
|
|
Header.Frequency = Frequency;
|
|
|
|
// Only inputs for vertex shaders must be tracked.
|
|
if (Frequency == SF_Vertex)
|
|
{
|
|
static const FString AttributePrefix = TEXT("in_ATTRIBUTE");
|
|
for (auto& Input : CCHeader.Inputs)
|
|
{
|
|
// Only process attributes.
|
|
if (Input.Name.StartsWith(AttributePrefix))
|
|
{
|
|
uint8 AttributeIndex = ParseNumber(*Input.Name + AttributePrefix.Len());
|
|
Header.Bindings.InOutMask |= (1 << AttributeIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then the list of outputs.
|
|
static const FString TargetPrefix = "out_Target";
|
|
static const FString GL_FragDepth = "gl_FragDepth";
|
|
// Only outputs for pixel shaders must be tracked.
|
|
if (Frequency == SF_Pixel)
|
|
{
|
|
for (auto& Output : CCHeader.Outputs)
|
|
{
|
|
// Handle targets.
|
|
if (Output.Name.StartsWith(TargetPrefix))
|
|
{
|
|
uint8 TargetIndex = ParseNumber(*Output.Name + TargetPrefix.Len());
|
|
Header.Bindings.InOutMask |= (1 << TargetIndex);
|
|
}
|
|
// Handle depth writes.
|
|
else if (Output.Name.Equals(GL_FragDepth))
|
|
{
|
|
Header.Bindings.InOutMask |= 0x8000;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bHasRegularUniformBuffers = false;
|
|
|
|
// Then 'normal' uniform buffers.
|
|
for (auto& UniformBlock : CCHeader.UniformBlocks)
|
|
{
|
|
uint16 UBIndex = UniformBlock.Index;
|
|
if (UBIndex >= Header.Bindings.NumUniformBuffers)
|
|
{
|
|
Header.Bindings.NumUniformBuffers = UBIndex + 1;
|
|
}
|
|
UsedUniformBufferSlots[UBIndex] = true;
|
|
ParameterMap.AddParameterAllocation(*UniformBlock.Name, UBIndex, 0, 0);
|
|
bHasRegularUniformBuffers = true;
|
|
}
|
|
|
|
// Packed global uniforms
|
|
const uint16 BytesPerComponent = 4;
|
|
TMap<ANSICHAR, uint16> PackedGlobalArraySize;
|
|
for (auto& PackedGlobal : CCHeader.PackedGlobals)
|
|
{
|
|
ParameterMap.AddParameterAllocation(
|
|
*PackedGlobal.Name,
|
|
PackedGlobal.PackedType,
|
|
PackedGlobal.Offset * BytesPerComponent,
|
|
PackedGlobal.Count * BytesPerComponent
|
|
);
|
|
|
|
uint16& Size = PackedGlobalArraySize.FindOrAdd(PackedGlobal.PackedType);
|
|
Size = FMath::Max<uint16>(BytesPerComponent * (PackedGlobal.Offset + PackedGlobal.Count), Size);
|
|
}
|
|
|
|
// Packed Uniform Buffers
|
|
TMap<int, TMap<ANSICHAR, uint16> > PackedUniformBuffersSize;
|
|
for (auto& PackedUB : CCHeader.PackedUBs)
|
|
{
|
|
check(PackedUB.Attribute.Index == Header.Bindings.NumUniformBuffers);
|
|
UsedUniformBufferSlots[PackedUB.Attribute.Index] = true;
|
|
ParameterMap.AddParameterAllocation(*PackedUB.Attribute.Name, Header.Bindings.NumUniformBuffers++, 0, 0);
|
|
|
|
// Nothing else...
|
|
//for (auto& Member : PackedUB.Members)
|
|
//{
|
|
//}
|
|
}
|
|
|
|
// Packed Uniform Buffers copy lists & setup sizes for each UB/Precision entry
|
|
for (auto& PackedUBCopy : CCHeader.PackedUBCopies)
|
|
{
|
|
CrossCompiler::FUniformBufferCopyInfo CopyInfo;
|
|
CopyInfo.SourceUBIndex = PackedUBCopy.SourceUB;
|
|
CopyInfo.SourceOffsetInFloats = PackedUBCopy.SourceOffset;
|
|
CopyInfo.DestUBIndex = PackedUBCopy.DestUB;
|
|
CopyInfo.DestUBTypeName = PackedUBCopy.DestPackedType;
|
|
CopyInfo.DestUBTypeIndex = CrossCompiler::PackedTypeNameToTypeIndex(CopyInfo.DestUBTypeName);
|
|
CopyInfo.DestOffsetInFloats = PackedUBCopy.DestOffset;
|
|
CopyInfo.SizeInFloats = PackedUBCopy.Count;
|
|
|
|
Header.UniformBuffersCopyInfo.Add(CopyInfo);
|
|
|
|
auto& UniformBufferSize = PackedUniformBuffersSize.FindOrAdd(CopyInfo.DestUBIndex);
|
|
uint16& Size = UniformBufferSize.FindOrAdd(CopyInfo.DestUBTypeName);
|
|
Size = FMath::Max<uint16>(BytesPerComponent * (CopyInfo.DestOffsetInFloats + CopyInfo.SizeInFloats), Size);
|
|
}
|
|
|
|
for (auto& PackedUBCopy : CCHeader.PackedUBGlobalCopies)
|
|
{
|
|
CrossCompiler::FUniformBufferCopyInfo CopyInfo;
|
|
CopyInfo.SourceUBIndex = PackedUBCopy.SourceUB;
|
|
CopyInfo.SourceOffsetInFloats = PackedUBCopy.SourceOffset;
|
|
CopyInfo.DestUBIndex = PackedUBCopy.DestUB;
|
|
CopyInfo.DestUBTypeName = PackedUBCopy.DestPackedType;
|
|
CopyInfo.DestUBTypeIndex = CrossCompiler::PackedTypeNameToTypeIndex(CopyInfo.DestUBTypeName);
|
|
CopyInfo.DestOffsetInFloats = PackedUBCopy.DestOffset;
|
|
CopyInfo.SizeInFloats = PackedUBCopy.Count;
|
|
|
|
Header.UniformBuffersCopyInfo.Add(CopyInfo);
|
|
|
|
uint16& Size = PackedGlobalArraySize.FindOrAdd(CopyInfo.DestUBTypeName);
|
|
Size = FMath::Max<uint16>(BytesPerComponent * (CopyInfo.DestOffsetInFloats + CopyInfo.SizeInFloats), Size);
|
|
}
|
|
Header.Bindings.bHasRegularUniformBuffers = bHasRegularUniformBuffers;
|
|
|
|
// Setup Packed Array info
|
|
Header.Bindings.PackedGlobalArrays.Reserve(PackedGlobalArraySize.Num());
|
|
for (auto Iterator = PackedGlobalArraySize.CreateIterator(); Iterator; ++Iterator)
|
|
{
|
|
ANSICHAR TypeName = Iterator.Key();
|
|
uint16 Size = Iterator.Value();
|
|
Size = (Size + 0xf) & (~0xf);
|
|
CrossCompiler::FPackedArrayInfo Info;
|
|
Info.Size = Size;
|
|
Info.TypeName = TypeName;
|
|
Info.TypeIndex = CrossCompiler::PackedTypeNameToTypeIndex(TypeName);
|
|
Header.Bindings.PackedGlobalArrays.Add(Info);
|
|
}
|
|
|
|
// Setup Packed Uniform Buffers info
|
|
Header.Bindings.PackedUniformBuffers.Reserve(PackedUniformBuffersSize.Num());
|
|
for (auto Iterator = PackedUniformBuffersSize.CreateIterator(); Iterator; ++Iterator)
|
|
{
|
|
int BufferIndex = Iterator.Key();
|
|
auto& ArraySizes = Iterator.Value();
|
|
TArray<CrossCompiler::FPackedArrayInfo> InfoArray;
|
|
InfoArray.Reserve(ArraySizes.Num());
|
|
for (auto IterSizes = ArraySizes.CreateIterator(); IterSizes; ++IterSizes)
|
|
{
|
|
ANSICHAR TypeName = IterSizes.Key();
|
|
uint16 Size = IterSizes.Value();
|
|
Size = (Size + 0xf) & (~0xf);
|
|
CrossCompiler::FPackedArrayInfo Info;
|
|
Info.Size = Size;
|
|
Info.TypeName = TypeName;
|
|
Info.TypeIndex = CrossCompiler::PackedTypeNameToTypeIndex(TypeName);
|
|
InfoArray.Add(Info);
|
|
}
|
|
|
|
Header.Bindings.PackedUniformBuffers.Add(InfoArray);
|
|
}
|
|
|
|
// Then samplers.
|
|
TMap<FString, uint32> SamplerMap;
|
|
for (auto& Sampler : CCHeader.Samplers)
|
|
{
|
|
ParameterMap.AddParameterAllocation(
|
|
*Sampler.Name,
|
|
0,
|
|
Sampler.Offset,
|
|
Sampler.Count
|
|
);
|
|
|
|
Header.Bindings.NumSamplers = FMath::Max<uint8>(
|
|
Header.Bindings.NumSamplers,
|
|
Sampler.Offset + Sampler.Count
|
|
);
|
|
|
|
for (auto& SamplerState : Sampler.SamplerStates)
|
|
{
|
|
SamplerMap.Add(SamplerState, Sampler.Count);
|
|
}
|
|
}
|
|
|
|
// Then UAVs (images in Metal)
|
|
for (auto& UAV : CCHeader.UAVs)
|
|
{
|
|
ParameterMap.AddParameterAllocation(
|
|
*UAV.Name,
|
|
0,
|
|
UAV.Offset,
|
|
UAV.Count
|
|
);
|
|
|
|
Header.Bindings.NumUAVs = FMath::Max<uint8>(
|
|
Header.Bindings.NumSamplers,
|
|
UAV.Offset + UAV.Count
|
|
);
|
|
}
|
|
|
|
for (auto& SamplerState : CCHeader.SamplerStates)
|
|
{
|
|
ParameterMap.AddParameterAllocation(
|
|
*SamplerState.Name,
|
|
0,
|
|
SamplerState.Index,
|
|
SamplerMap[SamplerState.Name]
|
|
);
|
|
}
|
|
|
|
Header.NumThreadsX = CCHeader.NumThreads[0];
|
|
Header.NumThreadsY = CCHeader.NumThreads[1];
|
|
Header.NumThreadsZ = CCHeader.NumThreads[2];
|
|
|
|
// Build the SRT for this shader.
|
|
{
|
|
// Build the generic SRT for this shader.
|
|
FShaderResourceTable GenericSRT;
|
|
BuildResourceTableMapping(ShaderInput.Environment.ResourceTableMap, ShaderInput.Environment.ResourceTableLayoutHashes, UsedUniformBufferSlots, ShaderOutput.ParameterMap, GenericSRT);
|
|
|
|
// Copy over the bits indicating which resource tables are active.
|
|
Header.Bindings.ShaderResourceTable.ResourceTableBits = GenericSRT.ResourceTableBits;
|
|
|
|
Header.Bindings.ShaderResourceTable.ResourceTableLayoutHashes = GenericSRT.ResourceTableLayoutHashes;
|
|
|
|
// Now build our token streams.
|
|
BuildResourceTableTokenStream(GenericSRT.TextureMap, GenericSRT.MaxBoundResourceTable, Header.Bindings.ShaderResourceTable.TextureMap);
|
|
BuildResourceTableTokenStream(GenericSRT.ShaderResourceViewMap, GenericSRT.MaxBoundResourceTable, Header.Bindings.ShaderResourceTable.ShaderResourceViewMap);
|
|
BuildResourceTableTokenStream(GenericSRT.SamplerMap, GenericSRT.MaxBoundResourceTable, Header.Bindings.ShaderResourceTable.SamplerMap);
|
|
BuildResourceTableTokenStream(GenericSRT.UnorderedAccessViewMap, GenericSRT.MaxBoundResourceTable, Header.Bindings.ShaderResourceTable.UnorderedAccessViewMap);
|
|
|
|
Header.Bindings.NumUniformBuffers = FMath::Max((uint8)GetNumUniformBuffersUsed(GenericSRT), Header.Bindings.NumUniformBuffers);
|
|
}
|
|
|
|
const int32 MaxSamplers = GetFeatureLevelMaxTextureSamplers(ERHIFeatureLevel::ES3_1);
|
|
Header.ShaderName = CCHeader.Name.GetCharArray();
|
|
|
|
if (Header.Bindings.NumSamplers > MaxSamplers)
|
|
{
|
|
ShaderOutput.bSucceeded = false;
|
|
FShaderCompilerError* NewError = new(ShaderOutput.Errors) FShaderCompilerError();
|
|
NewError->StrippedErrorMessage =
|
|
FString::Printf(TEXT("shader uses %d samplers exceeding the limit of %d"),
|
|
Header.Bindings.NumSamplers, MaxSamplers);
|
|
}
|
|
else if(ShaderInput.Environment.CompilerFlags.Contains(CFLAG_Debug))
|
|
{
|
|
// Write out the header and shader source code.
|
|
FMemoryWriter Ar(ShaderOutput.Code, true);
|
|
uint8 PrecompiledFlag = 0;
|
|
Ar << PrecompiledFlag;
|
|
Ar << Header;
|
|
Ar.Serialize((void*)USFSource, SourceLen + 1 - (USFSource - InShaderSource));
|
|
|
|
ShaderOutput.NumInstructions = 0;
|
|
ShaderOutput.NumTextureSamplers = Header.Bindings.NumSamplers;
|
|
ShaderOutput.bSucceeded = true;
|
|
}
|
|
else
|
|
{
|
|
// at this point, the shader source is ready to be compiled
|
|
FString InputFilename = FPaths::CreateTempFilename(*FPaths::EngineIntermediateDir(), TEXT("ShaderIn"), TEXT(""));
|
|
FString ObjFilename = InputFilename + TEXT(".o");
|
|
FString ArFilename = InputFilename + TEXT(".ar");
|
|
FString OutputFilename = InputFilename + TEXT(".lib");
|
|
InputFilename = InputFilename + TEXT(".metal");
|
|
|
|
// write out shader source
|
|
FFileHelper::SaveStringToFile(FString(USFSource), *InputFilename);
|
|
|
|
int32 ReturnCode = 0;
|
|
FString Results;
|
|
FString Errors;
|
|
bool bCompileAtRuntime = true;
|
|
bool bSucceeded = false;
|
|
|
|
#if PLATFORM_MAC
|
|
FString XcodePath = FPlatformMisc::GetXcodePath();
|
|
if (XcodePath.Len() > 0 && (bIsMobile || FPlatformMisc::MacOSXVersionCompare(10, 11, 0) >= 0))
|
|
{
|
|
FString MetalToolsPath = FString::Printf(TEXT("%s/Toolchains/XcodeDefault.xctoolchain/usr/bin"), *XcodePath);
|
|
FString MetalPath = MetalToolsPath + TEXT("/metal");
|
|
if (IFileManager::Get().FileSize(*MetalPath) <= 0)
|
|
{
|
|
MetalToolsPath = FString::Printf(TEXT("%s/Platforms/iPhoneOS.platform/usr/bin"), *XcodePath);
|
|
MetalPath = MetalToolsPath + TEXT("/metal");
|
|
}
|
|
|
|
// metal commandlines
|
|
FString Standard = (bIsMobile ? TEXT("-std=ios-metal1.0") : TEXT("-std=osx-metal1.1 -Wno-null-character -ffmast-math"));
|
|
|
|
FString Params = FString::Printf(TEXT("%s %s -o %s"), *Standard, *InputFilename, *ObjFilename);
|
|
FPlatformProcess::ExecProcess( *MetalPath, *Params, &ReturnCode, &Results, &Errors );
|
|
|
|
// handle compile error
|
|
if (ReturnCode != 0 || IFileManager::Get().FileSize(*ObjFilename) <= 0)
|
|
{
|
|
FShaderCompilerError* Error = new(OutErrors) FShaderCompilerError();
|
|
Error->ErrorFile = InputFilename;
|
|
Error->ErrorLineString = TEXT("0");
|
|
Error->StrippedErrorMessage = Results + Errors;
|
|
bSucceeded = false;
|
|
}
|
|
else
|
|
{
|
|
Params = FString::Printf(TEXT("r %s %s"), *ArFilename, *ObjFilename);
|
|
FString MetalArPath = MetalToolsPath + TEXT("/metal-ar");
|
|
FPlatformProcess::ExecProcess( *MetalArPath, *Params, &ReturnCode, &Results, &Errors );
|
|
|
|
// handle compile error
|
|
if (ReturnCode != 0 || IFileManager::Get().FileSize(*ArFilename) <= 0)
|
|
{
|
|
FShaderCompilerError* Error = new(OutErrors) FShaderCompilerError();
|
|
Error->ErrorFile = InputFilename;
|
|
Error->ErrorLineString = TEXT("0");
|
|
Error->StrippedErrorMessage = Results + Errors;
|
|
bSucceeded = false;
|
|
}
|
|
else
|
|
{
|
|
Params = FString::Printf(TEXT("-o %s %s"), *OutputFilename, *ArFilename);
|
|
FString MetalLibPath = MetalToolsPath + TEXT("/metallib");
|
|
FPlatformProcess::ExecProcess( *MetalLibPath, *Params, &ReturnCode, &Results, &Errors );
|
|
|
|
// handle compile error
|
|
if (ReturnCode != 0 || IFileManager::Get().FileSize(*OutputFilename) <= 0)
|
|
{
|
|
FShaderCompilerError* Error = new(OutErrors) FShaderCompilerError();
|
|
Error->ErrorFile = InputFilename;
|
|
Error->ErrorLineString = TEXT("0");
|
|
Error->StrippedErrorMessage = Results + Errors;
|
|
bSucceeded = false;
|
|
}
|
|
else
|
|
{
|
|
bCompileAtRuntime = false;
|
|
|
|
// Write out the header and compiled shader code
|
|
FMemoryWriter Ar(ShaderOutput.Code, true);
|
|
uint8 PrecompiledFlag = 1;
|
|
Ar << PrecompiledFlag;
|
|
Ar << Header;
|
|
|
|
// load output
|
|
TArray<uint8> CompiledShader;
|
|
FFileHelper::LoadFileToArray(CompiledShader, *OutputFilename);
|
|
|
|
// jam it into the output bytes
|
|
Ar.Serialize(CompiledShader.GetData(), CompiledShader.Num());
|
|
|
|
ShaderOutput.NumInstructions = 0;
|
|
ShaderOutput.NumTextureSamplers = Header.Bindings.NumSamplers;
|
|
ShaderOutput.bSucceeded = true;
|
|
bSucceeded = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
// Assume success for non-Mac
|
|
bSucceeded = true;
|
|
#endif // PLATFORM_MAC
|
|
|
|
if (bCompileAtRuntime)
|
|
{
|
|
// Write out the header and shader source code.
|
|
FMemoryWriter Ar(ShaderOutput.Code, true);
|
|
uint8 PrecompiledFlag = 0;
|
|
Ar << PrecompiledFlag;
|
|
Ar << Header;
|
|
Ar.Serialize((void*)USFSource, SourceLen + 1 - (USFSource - InShaderSource));
|
|
|
|
ShaderOutput.NumInstructions = 0;
|
|
ShaderOutput.NumTextureSamplers = Header.Bindings.NumSamplers;
|
|
ShaderOutput.bSucceeded = bSucceeded || ShaderOutput.bSucceeded;
|
|
}
|
|
else
|
|
{
|
|
IFileManager::Get().Delete(*InputFilename);
|
|
IFileManager::Get().Delete(*ObjFilename);
|
|
IFileManager::Get().Delete(*ArFilename);
|
|
IFileManager::Get().Delete(*OutputFilename);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
External interface.
|
|
------------------------------------------------------------------------------*/
|
|
|
|
static FString CreateCommandLineHLSLCC( const FString& ShaderFile, const FString& OutputFile, const FString& EntryPoint, EHlslShaderFrequency Frequency, uint32 CCFlags )
|
|
{
|
|
const TCHAR* VersionSwitch = TEXT("-metal");
|
|
return CrossCompiler::CreateBatchFileContents(ShaderFile, OutputFile, Frequency, EntryPoint, VersionSwitch, CCFlags, TEXT(""));
|
|
}
|
|
|
|
void CompileShader_Metal(const FShaderCompilerInput& Input,FShaderCompilerOutput& Output,const FString& WorkingDirectory)
|
|
{
|
|
FString PreprocessedShader;
|
|
FShaderCompilerDefinitions AdditionalDefines;
|
|
EHlslCompileTarget HlslCompilerTarget = HCT_FeatureLevelES3_1;
|
|
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_HLSLCC"), 1 );
|
|
|
|
// @todo - Zebra - Work out which standard we need, this is dependent on the shader platform.
|
|
// For now only SP_METAL will compile for iOS, with MRT & SM5 only for Mac.
|
|
const bool bIsMobile = (Input.Target.Platform == SP_METAL);
|
|
if (bIsMobile)
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("IOS"), 1);
|
|
}
|
|
else
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("MAC"), 1);
|
|
}
|
|
|
|
AdditionalDefines.SetDefine(TEXT("row_major"), TEXT(""));
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_METAL"), 1);
|
|
|
|
static FName NAME_SF_METAL(TEXT("SF_METAL"));
|
|
static FName NAME_SF_METAL_MRT(TEXT("SF_METAL_MRT"));
|
|
static FName NAME_SF_METAL_SM5(TEXT("SF_METAL_SM5"));
|
|
|
|
if (Input.ShaderFormat == NAME_SF_METAL)
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("METAL_PROFILE"), 1);
|
|
}
|
|
else if (Input.ShaderFormat == NAME_SF_METAL_MRT)
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("METAL_MRT_PROFILE"), 1);
|
|
}
|
|
else if (Input.ShaderFormat == NAME_SF_METAL_SM5)
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("METAL_SM5_PROFILE"), 1);
|
|
}
|
|
else
|
|
{
|
|
Output.bSucceeded = false;
|
|
new(Output.Errors) FShaderCompilerError(*FString::Printf(TEXT("Invalid shader format '%s' passed to compiler."), *Input.ShaderFormat.ToString()));
|
|
return;
|
|
}
|
|
|
|
const bool bDumpDebugInfo = (Input.DumpDebugInfoPath != TEXT("") && IFileManager::Get().DirectoryExists(*Input.DumpDebugInfoPath));
|
|
|
|
if(Input.Environment.CompilerFlags.Contains(CFLAG_AvoidFlowControl))
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_SUPPORTS_ATTRIBUTES"), (uint32)1);
|
|
}
|
|
else
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_SUPPORTS_ATTRIBUTES"), (uint32)0);
|
|
}
|
|
|
|
if (PreprocessShader(PreprocessedShader, Output, Input, AdditionalDefines))
|
|
{
|
|
char* MetalShaderSource = NULL;
|
|
char* ErrorLog = NULL;
|
|
|
|
const EHlslShaderFrequency FrequencyTable[] =
|
|
{
|
|
HSF_VertexShader,
|
|
HSF_InvalidFrequency,
|
|
HSF_InvalidFrequency,
|
|
HSF_PixelShader,
|
|
HSF_InvalidFrequency,
|
|
HSF_ComputeShader
|
|
};
|
|
|
|
const EHlslShaderFrequency Frequency = FrequencyTable[Input.Target.Frequency];
|
|
if (Frequency == HSF_InvalidFrequency)
|
|
{
|
|
Output.bSucceeded = false;
|
|
FShaderCompilerError* NewError = new(Output.Errors) FShaderCompilerError();
|
|
NewError->StrippedErrorMessage = FString::Printf(
|
|
TEXT("%s shaders not supported for use in Metal."),
|
|
CrossCompiler::GetFrequencyName((EShaderFrequency)Input.Target.Frequency)
|
|
);
|
|
return;
|
|
}
|
|
|
|
|
|
// This requires removing the HLSLCC_NoPreprocess flag later on!
|
|
if (!RemoveUniformBuffersFromSource(PreprocessedShader))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Write out the preprocessed file and a batch file to compile it if requested (DumpDebugInfoPath is valid)
|
|
if (bDumpDebugInfo)
|
|
{
|
|
FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / Input.SourceFilename + TEXT(".usf")));
|
|
if (FileWriter)
|
|
{
|
|
auto AnsiSourceFile = StringCast<ANSICHAR>(*PreprocessedShader);
|
|
FileWriter->Serialize((ANSICHAR*)AnsiSourceFile.Get(), AnsiSourceFile.Length());
|
|
FileWriter->Close();
|
|
delete FileWriter;
|
|
}
|
|
}
|
|
|
|
uint32 CCFlags = HLSLCC_NoPreprocess | HLSLCC_PackUniforms;
|
|
if (!bIsMobile)
|
|
{
|
|
CCFlags |= HLSLCC_FixAtomicReferences;
|
|
}
|
|
//CCFlags |= HLSLCC_FlattenUniformBuffers | HLSLCC_FlattenUniformBufferStructures;
|
|
|
|
if (bDumpDebugInfo)
|
|
{
|
|
const FString MetalFile = (Input.DumpDebugInfoPath / TEXT("Output.metal"));
|
|
const FString USFFile = (Input.DumpDebugInfoPath / Input.SourceFilename) + TEXT(".usf");
|
|
const FString CCBatchFileContents = CreateCommandLineHLSLCC(USFFile, MetalFile, *Input.EntryPointName, Frequency, CCFlags);
|
|
if (!CCBatchFileContents.IsEmpty())
|
|
{
|
|
FFileHelper::SaveStringToFile(CCBatchFileContents, *(Input.DumpDebugInfoPath / TEXT("CrossCompile.bat")));
|
|
}
|
|
}
|
|
|
|
// Required as we added the RemoveUniformBuffersFromSource() function (the cross-compiler won't be able to interpret comments w/o a preprocessor)
|
|
CCFlags &= ~HLSLCC_NoPreprocess;
|
|
|
|
FMetalCodeBackend MetalBackEnd(CCFlags, HlslCompilerTarget);
|
|
FMetalLanguageSpec MetalLanguageSpec;
|
|
|
|
int32 Result = 0;
|
|
FHlslCrossCompilerContext CrossCompilerContext(CCFlags, Frequency, HlslCompilerTarget);
|
|
if (CrossCompilerContext.Init(TCHAR_TO_ANSI(*Input.SourceFilename), &MetalLanguageSpec))
|
|
{
|
|
Result = CrossCompilerContext.Run(
|
|
TCHAR_TO_ANSI(*PreprocessedShader),
|
|
TCHAR_TO_ANSI(*Input.EntryPointName),
|
|
&MetalBackEnd,
|
|
&MetalShaderSource,
|
|
&ErrorLog
|
|
) ? 1 : 0;
|
|
}
|
|
|
|
int32 SourceLen = MetalShaderSource ? FCStringAnsi::Strlen(MetalShaderSource) : 0;
|
|
if(MetalShaderSource)
|
|
{
|
|
uint32 Len = FCStringAnsi::Strlen(TCHAR_TO_ANSI(*Input.SourceFilename)) + FCStringAnsi::Strlen(TCHAR_TO_ANSI(*Input.EntryPointName)) + FCStringAnsi::Strlen(MetalShaderSource) + 20;
|
|
char* Dest = (char*)malloc(Len);
|
|
FCStringAnsi::Snprintf(Dest, Len, "// ! %s.usf:%s\n%s", (const char*)TCHAR_TO_ANSI(*Input.SourceFilename), (const char*)TCHAR_TO_ANSI(*Input.EntryPointName), (const char*)MetalShaderSource);
|
|
free(MetalShaderSource);
|
|
MetalShaderSource = Dest;
|
|
SourceLen = FCStringAnsi::Strlen(MetalShaderSource);
|
|
}
|
|
if (bDumpDebugInfo)
|
|
{
|
|
if (SourceLen > 0)
|
|
{
|
|
FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / Input.SourceFilename + TEXT(".metal")));
|
|
if (FileWriter)
|
|
{
|
|
FileWriter->Serialize(MetalShaderSource, SourceLen + 1);
|
|
FileWriter->Close();
|
|
delete FileWriter;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Result != 0)
|
|
{
|
|
Output.Target = Input.Target;
|
|
BuildMetalShaderOutput(Output, Input, MetalShaderSource, SourceLen, bIsMobile, Output.Errors);
|
|
}
|
|
else
|
|
{
|
|
FString Tmp = ANSI_TO_TCHAR(ErrorLog);
|
|
TArray<FString> ErrorLines;
|
|
Tmp.ParseIntoArray(ErrorLines, TEXT("\n"), true);
|
|
for (int32 LineIndex = 0; LineIndex < ErrorLines.Num(); ++LineIndex)
|
|
{
|
|
const FString& Line = ErrorLines[LineIndex];
|
|
CrossCompiler::ParseHlslccError(Output.Errors, Line);
|
|
}
|
|
}
|
|
|
|
if (MetalShaderSource)
|
|
{
|
|
free(MetalShaderSource);
|
|
}
|
|
if (ErrorLog)
|
|
{
|
|
free(ErrorLog);
|
|
}
|
|
}
|
|
}
|