Files
UnrealEngine/Engine/Source/Developer/ShaderCompilerCommon/Private/ShaderCompilerCommon.cpp
2025-05-18 13:04:45 +08:00

3161 lines
98 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShaderCompilerCommon.h"
#include "ShaderParameterParser.h"
#include "Misc/Base64.h"
#include "Misc/Compression.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/PathViews.h"
#include "Modules/ModuleManager.h"
#include "HlslccDefinitions.h"
#include "HAL/FileManager.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "String/RemoveFrom.h"
#include "ShaderCompilerDefinitions.h"
#include "ShaderPreprocessor.h"
#include "ShaderPreprocessTypes.h"
#include "ShaderSymbolExport.h"
#include "ShaderMinifier.h"
#include "Algo/Sort.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, ShaderCompilerCommon);
int16 GetNumUniformBuffersUsed(const FShaderCompilerResourceTable& InSRT)
{
auto CountLambda = [&](const TArray<uint32>& In)
{
int16 LastIndex = -1;
for (int32 i = 0; i < In.Num(); ++i)
{
auto BufferIndex = FRHIResourceTableEntry::GetUniformBufferIndex(In[i]);
if (BufferIndex != static_cast<uint16>(FRHIResourceTableEntry::GetEndOfStreamToken()) )
{
LastIndex = FMath::Max(LastIndex, (int16)BufferIndex);
}
}
return LastIndex + 1;
};
int16 Num = CountLambda(InSRT.SamplerMap);
Num = FMath::Max(Num, (int16)CountLambda(InSRT.ShaderResourceViewMap));
Num = FMath::Max(Num, (int16)CountLambda(InSRT.TextureMap));
Num = FMath::Max(Num, (int16)CountLambda(InSRT.UnorderedAccessViewMap));
Num = FMath::Max(Num, (int16)CountLambda(InSRT.ResourceCollectionMap));
return Num;
}
void BuildResourceTableTokenStream(const TArray<uint32>& InResourceMap, int32 MaxBoundResourceTable, TArray<uint32>& OutTokenStream, bool bGenerateEmptyTokenStreamIfNoResources)
{
if (bGenerateEmptyTokenStreamIfNoResources)
{
if (InResourceMap.Num() == 0)
{
return;
}
}
// First we sort the resource map.
TArray<uint32> SortedResourceMap = InResourceMap;
SortedResourceMap.Sort();
// The token stream begins with a table that contains offsets per bound uniform buffer.
// This offset provides the start of the token stream.
OutTokenStream.AddZeroed(MaxBoundResourceTable+1);
auto LastBufferIndex = FRHIResourceTableEntry::GetEndOfStreamToken();
for (int32 i = 0; i < SortedResourceMap.Num(); ++i)
{
auto BufferIndex = FRHIResourceTableEntry::GetUniformBufferIndex(SortedResourceMap[i]);
if (BufferIndex != LastBufferIndex)
{
// Store the offset for resources from this buffer.
OutTokenStream[BufferIndex] = OutTokenStream.Num();
LastBufferIndex = BufferIndex;
}
OutTokenStream.Add(SortedResourceMap[i]);
}
// Add a token to mark the end of the stream. Not needed if there are no bound resources.
if (OutTokenStream.Num())
{
OutTokenStream.Add(FRHIResourceTableEntry::GetEndOfStreamToken());
}
}
void UE::ShaderCompilerCommon::BuildShaderResourceTable(const FShaderCompilerResourceTable& GenericSRT, FShaderResourceTable& OutSRT, bool bGenerateEmptyTokenStreamIfNoResources)
{
// Copy over the bits indicating which resource tables are active.
OutSRT.ResourceTableBits = GenericSRT.ResourceTableBits;
OutSRT.ResourceTableLayoutHashes = GenericSRT.ResourceTableLayoutHashes;
// Now build our token streams.
BuildResourceTableTokenStream(GenericSRT.TextureMap, GenericSRT.MaxBoundResourceTable, OutSRT.TextureMap, bGenerateEmptyTokenStreamIfNoResources);
BuildResourceTableTokenStream(GenericSRT.ShaderResourceViewMap, GenericSRT.MaxBoundResourceTable, OutSRT.ShaderResourceViewMap, bGenerateEmptyTokenStreamIfNoResources);
BuildResourceTableTokenStream(GenericSRT.SamplerMap, GenericSRT.MaxBoundResourceTable, OutSRT.SamplerMap, bGenerateEmptyTokenStreamIfNoResources);
BuildResourceTableTokenStream(GenericSRT.UnorderedAccessViewMap, GenericSRT.MaxBoundResourceTable, OutSRT.UnorderedAccessViewMap, bGenerateEmptyTokenStreamIfNoResources);
BuildResourceTableTokenStream(GenericSRT.ResourceCollectionMap, GenericSRT.MaxBoundResourceTable, OutSRT.ResourceCollectionMap, bGenerateEmptyTokenStreamIfNoResources);
}
static bool DoesUniformBufferNeedReflectedMembers(const TMap<FString, FUniformBufferEntry>& UniformBufferMap, FStringView UniformBufferName)
{
if (const FUniformBufferEntry* Entry = UniformBufferMap.FindByHash(GetTypeHash(UniformBufferName), UniformBufferName))
{
return EnumHasAnyFlags(Entry->Flags, ERHIUniformBufferFlags::NeedsReflectedMembers);
}
return false;
}
bool BuildResourceTableMapping(
const FShaderResourceTableMap& ResourceTableMap,
const TMap<FString, FUniformBufferEntry>& UniformBufferMap,
TBitArray<>& UsedUniformBufferSlots,
FShaderParameterMap& ParameterMap,
FShaderCompilerResourceTable& OutSRT)
{
check(OutSRT.ResourceTableBits == 0);
check(OutSRT.ResourceTableLayoutHashes.Num() == 0);
// Build resource table mapping
int32 MaxBoundResourceTable = -1;
// Go through ALL the members of ALL the UB resources
for (const FUniformResourceEntry& Entry : ResourceTableMap.Resources)
{
const FString& Name = Entry.UniformBufferMemberName;
// If the shaders uses this member (eg View_PerlinNoise3DTexture)...
if (TOptional<FParameterAllocation> Allocation = ParameterMap.FindAndRemoveParameterAllocation(Name))
{
FStringView UniformBufferName = Entry.GetUniformBufferName();
const EShaderParameterType ParameterType = Allocation->Type;
const bool bBindlessParameter = IsParameterBindless(ParameterType);
// Force bindless "indices" to zero since they're not needed in SetResourcesFromTables
const uint16 BaseIndex = bBindlessParameter ? 0 : Allocation->BaseIndex;
if (DoesUniformBufferNeedReflectedMembers(UniformBufferMap, UniformBufferName))
{
FString RenamedMember = Name.Replace(TEXT("_"), TEXT("."));
ParameterMap.AddParameterAllocation(*RenamedMember, Allocation->BufferIndex, Allocation->BaseIndex, Allocation->Size, Allocation->Type);
// Force the parameter to be marked as bound
ParameterMap.FindParameterAllocation(RenamedMember);
}
uint16 UniformBufferIndex = INDEX_NONE;
// Add the UB itself as a parameter if not there
if (TOptional<FParameterAllocation> UniformBufferParameter = ParameterMap.FindParameterAllocation(UniformBufferName))
{
UniformBufferIndex = UniformBufferParameter->BufferIndex;
}
else
{
UniformBufferIndex = UsedUniformBufferSlots.FindAndSetFirstZeroBit();
ParameterMap.AddParameterAllocation(UniformBufferName, UniformBufferIndex, 0, 0, EShaderParameterType::UniformBuffer);
}
// Mark used UB index
if (UniformBufferIndex >= sizeof(OutSRT.ResourceTableBits) * 8)
{
return false;
}
OutSRT.ResourceTableBits |= (1 << UniformBufferIndex);
// How many resource tables max we'll use, and fill it with zeroes
MaxBoundResourceTable = FMath::Max<int32>(MaxBoundResourceTable, (int32)UniformBufferIndex);
auto ResourceMap = FRHIResourceTableEntry::Create(UniformBufferIndex, Entry.ResourceIndex, BaseIndex);
switch( Entry.Type )
{
case UBMT_TEXTURE:
case UBMT_RDG_TEXTURE:
OutSRT.TextureMap.Add(ResourceMap);
break;
case UBMT_SAMPLER:
OutSRT.SamplerMap.Add(ResourceMap);
break;
case UBMT_SRV:
case UBMT_RDG_TEXTURE_SRV:
case UBMT_RDG_TEXTURE_NON_PIXEL_SRV:
case UBMT_RDG_BUFFER_SRV:
OutSRT.ShaderResourceViewMap.Add(ResourceMap);
break;
case UBMT_RESOURCE_COLLECTION:
OutSRT.ResourceCollectionMap.Add(ResourceMap);
break;
case UBMT_UAV:
case UBMT_RDG_TEXTURE_UAV:
case UBMT_RDG_BUFFER_UAV:
OutSRT.UnorderedAccessViewMap.Add(ResourceMap);
break;
default:
return false;
}
}
}
// Emit hashes for all uniform buffers in the parameter map. We need to include the ones without resources as well
// (i.e. just constants), since the global uniform buffer bindings rely on valid hashes.
for (const TPair<FString, FParameterAllocation>& KeyValue : ParameterMap.GetParameterMap())
{
const FString& UniformBufferName = KeyValue.Key;
const FParameterAllocation& UniformBufferParameter = KeyValue.Value;
if (UniformBufferParameter.Type == EShaderParameterType::UniformBuffer)
{
if (OutSRT.ResourceTableLayoutHashes.Num() <= UniformBufferParameter.BufferIndex)
{
OutSRT.ResourceTableLayoutHashes.SetNumZeroed(UniformBufferParameter.BufferIndex + 1);
}
// Data-driven uniform buffers will not have registered this information.
if (const FUniformBufferEntry* UniformBufferEntry = UniformBufferMap.Find(UniformBufferName))
{
OutSRT.ResourceTableLayoutHashes[UniformBufferParameter.BufferIndex] = UniformBufferEntry->LayoutHash;
}
}
}
OutSRT.MaxBoundResourceTable = MaxBoundResourceTable;
return true;
}
void CullGlobalUniformBuffers(const TMap<FString, FUniformBufferEntry>& UniformBufferMap, FShaderParameterMap& ParameterMap)
{
TArray<FString> ParameterNames;
ParameterMap.GetAllParameterNames(ParameterNames);
for (const FString& Name : ParameterNames)
{
if (const FUniformBufferEntry* UniformBufferEntry = UniformBufferMap.Find(*Name))
{
// A uniform buffer that is bound per-shader keeps its allocation in the map.
if (EnumHasAnyFlags(UniformBufferEntry->BindingFlags, EUniformBufferBindingFlags::Shader))
{
continue;
}
ParameterMap.RemoveParameterAllocation(Name);
}
}
}
template <typename CharType>
static bool IsSpaceOrTabOrEOL(CharType Char)
{
return Char == ' ' || Char == '\t' || Char == '\n' || Char == '\r';
}
template <typename StrCharType, typename SearchCharType>
static const StrCharType* FindNextChar(const StrCharType* ReadStart, SearchCharType SearchChar)
{
const StrCharType* SearchPtr = ReadStart;
while (*SearchPtr && *SearchPtr != SearchChar)
{
SearchPtr++;
}
return SearchPtr;
}
template <typename CharType>
const CharType* FindNextWhitespace(const CharType* StringPtr)
{
while (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
{
StringPtr++;
}
if (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr))
{
return StringPtr;
}
else
{
return nullptr;
}
}
template <typename CharType>
const CharType* FindNextNonWhitespace(const CharType* StringPtr)
{
while (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr))
{
StringPtr++;
}
if (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
{
return StringPtr;
}
return nullptr;
}
template <typename CharType>
const CharType* FindPreviousNonWhitespace(const CharType* StringPtr)
{
do
{
StringPtr--;
} while (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr));
if (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
{
return StringPtr;
}
return nullptr;
}
template <typename CharType>
const CharType* FindMatchingClosingParenthesis(const CharType* OpeningCharPtr) { return FindMatchingBlock<CharType>(OpeningCharPtr, '(', ')'); };
// See MSDN HLSL 'Symbol Name Restrictions' doc
template <typename CharType>
inline bool IsValidHLSLIdentifierCharacter(CharType Char)
{
return (Char >= 'a' && Char <= 'z') ||
(Char >= 'A' && Char <= 'Z') ||
(Char >= '0' && Char <= '9') ||
Char == '_';
}
void ParseHLSLTypeName(const TCHAR* SearchString, const TCHAR*& TypeNameStartPtr, const TCHAR*& TypeNameEndPtr)
{
TypeNameStartPtr = FindNextNonWhitespace(SearchString);
check(TypeNameStartPtr);
TypeNameEndPtr = TypeNameStartPtr;
int32 Depth = 0;
const TCHAR* NextWhitespace = FindNextWhitespace(TypeNameStartPtr);
const TCHAR* PotentialExtraTypeInfoPtr = NextWhitespace ? FindNextNonWhitespace(NextWhitespace) : nullptr;
// Find terminating whitespace, but skip over trailing ' < float4 >'
while (*TypeNameEndPtr)
{
if (*TypeNameEndPtr == '<')
{
Depth++;
}
else if (*TypeNameEndPtr == '>')
{
Depth--;
}
else if (Depth == 0
&& IsSpaceOrTabOrEOL(*TypeNameEndPtr)
// If we found a '<', we must not accept any whitespace before it
&& (!PotentialExtraTypeInfoPtr || *PotentialExtraTypeInfoPtr != '<' || TypeNameEndPtr > PotentialExtraTypeInfoPtr))
{
break;
}
TypeNameEndPtr++;
}
check(TypeNameEndPtr);
}
template<typename CharType, typename ViewType>
ViewType ParseHLSLSymbolName(const CharType* SearchString)
{
const CharType* SymbolNameStartPtr = FindNextNonWhitespace(SearchString);
check(SymbolNameStartPtr);
const CharType* SymbolNameEndPtr = SymbolNameStartPtr;
while (*SymbolNameEndPtr && IsValidHLSLIdentifierCharacter(*SymbolNameEndPtr))
{
SymbolNameEndPtr++;
}
return ViewType(SymbolNameStartPtr, SymbolNameEndPtr - SymbolNameStartPtr);
}
const TCHAR* ParseHLSLSymbolName(const TCHAR* SearchString, FString& SymbolName)
{
FStringView Result = ParseHLSLSymbolName<TCHAR, FStringView>(SearchString);
SymbolName = FString(Result);
return Result.GetData() + Result.Len();
}
FStringView FindNextHLSLDefinitionOfType(FStringView Typename, FStringView StartPos)
{
// handle both the case where identifier for declaration immediately precedes a ; and has whitespace separating the two
const TCHAR* NextWhitespace;
const TCHAR* NextNonWhitespace;
FStringView SymbolName;
NextWhitespace = FindNextWhitespace(StartPos.GetData());
if (NextWhitespace == StartPos.GetData())
{
NextNonWhitespace = FindNextNonWhitespace(NextWhitespace);
SymbolName = ParseHLSLSymbolName<TCHAR, FStringView>(NextNonWhitespace);
NextNonWhitespace = FindNextNonWhitespace(NextNonWhitespace + SymbolName.Len());
if (NextNonWhitespace && (*NextNonWhitespace == ';'))
{
return SymbolName;
}
}
return {};
}
FStringView UE::ShaderCompilerCommon::RemoveConstantBufferPrefix(FStringView InName)
{
return UE::String::RemoveFromStart(InName, FStringView(UE::ShaderCompilerCommon::kUniformBufferConstantBufferPrefix));
}
FString UE::ShaderCompilerCommon::RemoveConstantBufferPrefix(const FString& InName)
{
return FString(RemoveConstantBufferPrefix(FStringView(InName)));
}
bool UE::ShaderCompilerCommon::ValidatePackedResourceCounts(FShaderCompilerOutput& Output, const FShaderCodePackedResourceCounts& PackedResourceCounts)
{
if (Output.bSucceeded)
{
auto GetAllResourcesOfType = [&](EShaderParameterType InType)
{
const TArray<FStringView> AllNames = Output.ParameterMap.GetAllParameterNamesOfType(InType);
if (AllNames.IsEmpty())
{
return FString();
}
return FString::Join(AllNames, TEXT(", "));
};
if (EnumHasAnyFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::BindlessResources) && PackedResourceCounts.NumSRVs > 0)
{
const FString Names = GetAllResourcesOfType(EShaderParameterType::SRV);
Output.Errors.Add(FString::Printf(TEXT("Shader is mixing bindless resources with non-bindless resources. %d SRV slots were detected: %s"), PackedResourceCounts.NumSRVs, *Names));
Output.bSucceeded = false;
}
if (EnumHasAnyFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::BindlessResources) && PackedResourceCounts.NumUAVs > 0)
{
const FString Names = GetAllResourcesOfType(EShaderParameterType::UAV);
Output.Errors.Add(FString::Printf(TEXT("Shader is mixing bindless resources with non-bindless resources. %d UAV slots were detected: %s"), PackedResourceCounts.NumUAVs, *Names));
Output.bSucceeded = false;
}
if (EnumHasAnyFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::BindlessSamplers) && PackedResourceCounts.NumSamplers > 0)
{
const FString Names = GetAllResourcesOfType(EShaderParameterType::Sampler);
Output.Errors.Add(FString::Printf(TEXT("Shader is mixing bindless samplers with non-bindless samplers. %d sampler slots were detected: %s"), PackedResourceCounts.NumSamplers, *Names));
Output.bSucceeded = false;
}
}
return Output.bSucceeded;
}
void UE::ShaderCompilerCommon::ParseRayTracingEntryPoint(const FStringView& Input, FStringView& OutMain, FStringView& OutAnyHit, FStringView& OutIntersection)
{
auto ParseEntry = [&Input](const FStringView& Marker)
{
FStringView Result;
const int32 BeginIndex = UE::String::FindFirst(Input, Marker, ESearchCase::IgnoreCase);
if (BeginIndex != INDEX_NONE)
{
int32 EndIndex = UE::String::FindFirst(Input.Mid(BeginIndex), TEXTVIEW(" "), ESearchCase::IgnoreCase);
if (EndIndex == INDEX_NONE)
{
EndIndex = Input.Len() + 1;
}
else
{
EndIndex += BeginIndex;
}
const int32 MarkerLen = Marker.Len();
const int32 Count = EndIndex - BeginIndex;
Result = Input.Mid(BeginIndex + MarkerLen, Count - MarkerLen);
}
return Result;
};
OutMain = ParseEntry(TEXTVIEW("closesthit="));
OutAnyHit = ParseEntry(TEXTVIEW("anyhit="));
OutIntersection = ParseEntry(TEXTVIEW("intersection="));
// If complex hit group entry is not specified, assume a single verbatim entry point
if (OutMain.IsEmpty() && OutAnyHit.IsEmpty() && OutIntersection.IsEmpty())
{
OutMain = Input;
}
}
void UE::ShaderCompilerCommon::ParseRayTracingEntryPoint(const FString& Input, FString& OutMain, FString& OutAnyHit, FString& OutIntersection)
{
FStringView OutMainView;
FStringView OutAnyHitView;
FStringView OutIntersectionView;
ParseRayTracingEntryPoint(Input, OutMainView, OutAnyHitView, OutIntersectionView);
OutMain = OutMainView;
OutAnyHit = OutAnyHitView;
OutIntersection = OutIntersectionView;
}
bool UE::ShaderCompilerCommon::RemoveDeadCode(FShaderSource& InOutPreprocessedShaderSource, TConstArrayView<FStringView> InRequiredSymbols, TArray<FShaderCompilerError>& OutErrors)
{
TRACE_CPUPROFILER_EVENT_SCOPE(RemoveDeadCode);
UE::ShaderMinifier::EMinifyShaderFlags ExtraFlags = UE::ShaderMinifier::EMinifyShaderFlags::None;
#if 0 // Extra features that may be useful during development / debugging
ExtraFlags |= UE::ShaderMinifier::EMinifyShaderFlags::OutputReasons // Output a comment every struct/function describing why it was included (i.e. which code block uses it)
| UE::ShaderMinifier::EMinifyShaderFlags::OutputStats; // Output a comment detailing how many blocks of each type (functions/structs/etc.) were emitted
#endif
TArray<FShaderSource::FStringType> ConvertedRequiredSymbols;
TArray<FShaderSource::FViewType> RequiredSymbolViews;
for (FStringView InSymbol : InRequiredSymbols)
{
FShaderSource::FStringType& ConvertedString = ConvertedRequiredSymbols.AddDefaulted_GetRef();
ConvertedString.Append(InSymbol);
RequiredSymbolViews.Add(FShaderSource::FViewType(ConvertedString));
}
UE::ShaderMinifier::FMinifiedShader Minified = UE::ShaderMinifier::Minify(InOutPreprocessedShaderSource, RequiredSymbolViews,
UE::ShaderMinifier::EMinifyShaderFlags::OutputCommentLines // Preserve comments that were left after preprocessing
| UE::ShaderMinifier::EMinifyShaderFlags::OutputLines // Emit #line directives
| ExtraFlags);
if (Minified.Success())
{
InOutPreprocessedShaderSource = MoveTemp(Minified.Code);
return true;
}
else
{
OutErrors.Add(TEXT("warning: Shader minification failed."));
return false;
}
}
bool UE::ShaderCompilerCommon::RemoveDeadCode(FShaderSource& InOutPreprocessedShaderSource, const FString& EntryPoint, TArray<FShaderCompilerError>& OutErrors)
{
return UE::ShaderCompilerCommon::RemoveDeadCode(InOutPreprocessedShaderSource, EntryPoint, {}, OutErrors);
}
bool UE::ShaderCompilerCommon::RemoveDeadCode(FShaderSource& InOutPreprocessedShaderSource, const FString& EntryPoint, TConstArrayView<FStringView> InRequiredSymbols, TArray<FShaderCompilerError>& OutErrors)
{
TArray<FStringView> RequiredSymbols;
FStringView EntryMain;
FStringView EntryAnyHit;
FStringView EntryIntersection;
UE::ShaderCompilerCommon::ParseRayTracingEntryPoint(EntryPoint, EntryMain, EntryAnyHit, EntryIntersection);
RequiredSymbols.Add(EntryMain);
if (!EntryAnyHit.IsEmpty())
{
RequiredSymbols.Add(EntryAnyHit);
}
if (!EntryIntersection.IsEmpty())
{
RequiredSymbols.Add(EntryIntersection);
}
for (FStringView Symbol : InRequiredSymbols)
{
RequiredSymbols.Add(Symbol);
}
return UE::ShaderCompilerCommon::RemoveDeadCode(InOutPreprocessedShaderSource, RequiredSymbols, OutErrors);
}
void HandleReflectedGlobalConstantBufferMember(
const FString& InMemberName,
uint32 ConstantBufferIndex,
int32 ReflectionOffset,
int32 ReflectionSize,
FShaderCompilerOutput& Output
)
{
FStringView MemberName = InMemberName;
const EShaderParameterType ParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(MemberName);
Output.ParameterMap.AddParameterAllocation(
MemberName,
ConstantBufferIndex,
ReflectionOffset,
ReflectionSize,
ParameterType
);
}
void HandleReflectedUniformBufferConstantBufferMember(
EUniformBufferMemberReflectionReason Reason,
FStringView UniformBufferName,
int32 UniformBufferSlot,
FStringView InMemberName,
int32 ReflectionOffset,
int32 ReflectionSize,
FShaderCompilerOutput& Output
)
{
FStringView MemberName = InMemberName;
const EShaderParameterType ParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(MemberName);
bool bAdd = EnumHasAnyFlags(Reason, EUniformBufferMemberReflectionReason::NeedsReflection);
if (EnumHasAnyFlags(Reason, EUniformBufferMemberReflectionReason::Bindless))
{
bAdd |= (ParameterType != EShaderParameterType::LooseData);
}
if (bAdd)
{
Output.ParameterMap.AddParameterAllocation(
MemberName,
UniformBufferSlot,
ReflectionOffset,
1,
ParameterType
);
}
}
void HandleReflectedRootConstantBufferMember(
const FShaderCompilerInput& Input,
const FShaderParameterParser& ShaderParameterParser,
const FString& InMemberName,
int32 ReflectionOffset,
int32 ReflectionSize,
FShaderCompilerOutput& Output
)
{
ShaderParameterParser.ValidateShaderParameterType(Input, InMemberName, ReflectionOffset, ReflectionSize, Output);
FStringView MemberName = InMemberName;
const EShaderParameterType ParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(MemberName);
if (ParameterType != EShaderParameterType::LooseData)
{
Output.ParameterMap.AddParameterAllocation(
MemberName,
FShaderParametersMetadata::kRootCBufferBindingIndex,
ReflectionOffset,
1,
ParameterType
);
}
}
void HandleReflectedRootConstantBuffer(
int32 ConstantBufferSize,
FShaderCompilerOutput& CompilerOutput
)
{
CompilerOutput.ParameterMap.AddParameterAllocation(
FShaderParametersMetadata::kRootUniformBufferBindingName,
FShaderParametersMetadata::kRootCBufferBindingIndex,
0,
static_cast<uint16>(ConstantBufferSize),
EShaderParameterType::LooseData);
}
void HandleReflectedUniformBuffer(
const FString& UniformBufferName,
int32 ReflectionSlot,
int32 BaseIndex,
int32 BufferSize,
FShaderCompilerOutput& CompilerOutput
)
{
FString AdjustedUniformBufferName(UE::ShaderCompilerCommon::RemoveConstantBufferPrefix(UniformBufferName));
CompilerOutput.ParameterMap.AddParameterAllocation(
AdjustedUniformBufferName,
ReflectionSlot,
BaseIndex,
BufferSize,
EShaderParameterType::UniformBuffer
);
}
EUniformBufferMemberReflectionReason ShouldReflectUniformBufferMembers(const FShaderCompilerInput& Input, FStringView UniformBufferName)
{
EUniformBufferMemberReflectionReason Reason{};
if (Input.IsBindlessEnabled())
{
Reason |= EUniformBufferMemberReflectionReason::Bindless;
}
if (DoesUniformBufferNeedReflectedMembers(Input.Environment.UniformBufferMap, UniformBufferName))
{
Reason |= EUniformBufferMemberReflectionReason::NeedsReflection;
}
return Reason;
}
void HandleReflectedShaderResource(
const FString& ResourceName,
int32 BindOffset,
int32 ReflectionSlot,
int32 BindCount,
FShaderCompilerOutput& CompilerOutput
)
{
CompilerOutput.ParameterMap.AddParameterAllocation(
ResourceName,
BindOffset,
ReflectionSlot,
BindCount,
EShaderParameterType::SRV
);
}
void UpdateStructuredBufferStride(
const FShaderCompilerInput& Input,
const FString& ResourceName,
uint16 BindPoint,
uint16 Stride,
FShaderCompilerOutput& CompilerOutput
)
{
if (BindPoint <= UINT16_MAX && Stride <= UINT16_MAX)
{
CompilerOutput.ParametersStrideToValidate.Add(FShaderCodeValidationStride{ BindPoint, Stride });
}
else
{
FString ErrorMessage = FString::Printf(TEXT("%s: Failed to set stride on parameter %s: Bind point %d, Stride %d"), *Input.GenerateShaderName(), *ResourceName, BindPoint, Stride);
CompilerOutput.Errors.Add(FShaderCompilerError(*ErrorMessage));
}
}
void AddShaderValidationSRVType(uint16 BindPoint,
EShaderCodeResourceBindingType TypeDecl,
FShaderCompilerOutput& CompilerOutput)
{
if (BindPoint <= UINT16_MAX)
{
CompilerOutput.ParametersSRVTypeToValidate.Add(FShaderCodeValidationType{ BindPoint, TypeDecl });
}
}
void AddShaderValidationUAVType(uint16 BindPoint,
EShaderCodeResourceBindingType TypeDecl,
FShaderCompilerOutput& CompilerOutput)
{
if (BindPoint <= UINT16_MAX)
{
CompilerOutput.ParametersUAVTypeToValidate.Add(FShaderCodeValidationType{ BindPoint, TypeDecl });
}
}
void AddShaderValidationUBSize(uint16 BindPoint,
uint32_t Size,
FShaderCompilerOutput& CompilerOutput)
{
if (BindPoint <= UINT16_MAX)
{
CompilerOutput.ParametersUBSizeToValidate.Add(FShaderCodeValidationUBSize{ BindPoint, Size });
}
}
void HandleReflectedShaderUAV(
const FString& UAVName,
int32 BindOffset,
int32 ReflectionSlot,
int32 BindCount,
FShaderCompilerOutput& CompilerOutput
)
{
CompilerOutput.ParameterMap.AddParameterAllocation(
UAVName,
BindOffset,
ReflectionSlot,
BindCount,
EShaderParameterType::UAV
);
}
void HandleReflectedShaderSampler(
const FString& SamplerName,
int32 BindOffset,
int32 ReflectionSlot,
int32 BindCount,
FShaderCompilerOutput& CompilerOutput
)
{
CompilerOutput.ParameterMap.AddParameterAllocation(
SamplerName,
BindOffset,
ReflectionSlot,
BindCount,
EShaderParameterType::Sampler
);
}
void AddNoteToDisplayShaderParameterStructureOnCppSide(
const FShaderParametersMetadata* ParametersStructure,
FShaderCompilerOutput& CompilerOutput)
{
FShaderCompilerError Error;
Error.StrippedErrorMessage = FString::Printf(
TEXT("Note: Definition of structure %s"),
ParametersStructure->GetStructTypeName());
Error.ErrorVirtualFilePath = ANSI_TO_TCHAR(ParametersStructure->GetFileName());
Error.ErrorLineString = FString::FromInt(ParametersStructure->GetFileLine());
CompilerOutput.Errors.Add(Error);
}
void AddUnboundShaderParameterError(
const FShaderCompilerInput& CompilerInput,
const FShaderParameterParser& ShaderParameterParser,
const FString& ParameterBindingName,
FShaderCompilerOutput& CompilerOutput)
{
check(CompilerInput.RootParametersStructure);
const FShaderParameterParser::FParsedShaderParameter& Member = ShaderParameterParser.FindParameterInfos(ParameterBindingName);
check(!Member.bIsBindable);
FShaderCompilerError Error(FString::Printf(
TEXT("Error: Shader parameter %s could not be bound to %s's shader parameter structure %s."),
*ParameterBindingName,
*CompilerInput.ShaderName,
CompilerInput.RootParametersStructure->GetStructTypeName()));
ShaderParameterParser.GetParameterFileAndLine(Member, Error.ErrorVirtualFilePath, Error.ErrorLineString);
CompilerOutput.Errors.Add(Error);
CompilerOutput.bSucceeded = false;
AddNoteToDisplayShaderParameterStructureOnCppSide(CompilerInput.RootParametersStructure, CompilerOutput);
}
struct FUniformBufferMemberInfo
{
// eg View.WorldToClip
FString NameAsStructMember;
// eg View_WorldToClip
FString GlobalName;
};
struct FUniformBufferInfo
{
int32 DefinitionEndOffset;
TArray<FUniformBufferMemberInfo> Members;
};
struct FUniformBufferMemberInfoNew
{
// eg View.WorldToClip
FShaderSource::FViewType NameAsStructMember;
// eg View_WorldToClip
FShaderSource::FViewType GlobalName;
bool operator<(const FUniformBufferMemberInfoNew& Other)
{
if (NameAsStructMember.Len() != Other.NameAsStructMember.Len())
{
return NameAsStructMember.Len() < Other.NameAsStructMember.Len();
}
else
{
return NameAsStructMember.Compare(Other.NameAsStructMember, ESearchCase::CaseSensitive) < 0;
}
}
};
// Index and count of subset of members
struct FUniformBufferMemberView
{
int32 MemberOffset;
int32 MemberCount;
};
struct FUniformBufferInfoNew
{
FShaderSource::FViewType Name;
int32 NextWithSameLength; // Linked list of uniform buffer infos with same name length
TArray<FUniformBufferMemberInfoNew> Members; // Members sorted by length
TArray<FUniformBufferMemberView> MembersByLength; // Offset and count of members of a given length
};
// Tracks the offset and length of commented out uniform buffer declarations in the source code, so we can compact them out
struct FUniformBufferSpan
{
int32 Offset;
int32 Length;
};
// Compacts spaces out of a compound identifier. Returns the new end pointer of the compacted identifier.
// End and result pointers are exclusive (length of the string is End - Start).
static FShaderSource::CharType* CompactCompoundIdentifier(FShaderSource::CharType* Start, FShaderSource::CharType* End)
{
// Find first whitespace in the identifier, if present
FShaderSource::CharType* ReadChar;
for (ReadChar = Start; ReadChar < End; ++ReadChar)
{
if (IsSpaceOrTabOrEOL(*ReadChar))
{
break;
}
}
if (ReadChar == End)
{
// No whitespace, we're done!
return End;
}
// Found some whitespace, so we need to compact the non-whitespace, swapping the whitespace to the end of the range
// WriteChar here will be the first whitespace character that we need to compact into.
FShaderSource::CharType* WriteChar = ReadChar;
for (++ReadChar; ReadChar < End; ++ReadChar)
{
// If the current read character is non-whitespace, compact it down
if (!IsSpaceOrTabOrEOL(*ReadChar))
{
Swap(*ReadChar, *WriteChar);
WriteChar++;
}
}
return WriteChar;
}
const FShaderSource::CharType* ParseUniformBufferDefinition(const FShaderSource::CharType* ReadStart, TArray<FUniformBufferInfoNew>& UniformBufferInfos, uint64 UniformBufferFilter[64], int32 UniformBuffersByLength[64])
{
// TODO: should we check for an existing item? In my testing, there's only one uniform buffer declaration with a given name,
// but the original code used a map, theoretically allowing for multiple.
int32 InfoIndex = UniformBufferInfos.AddDefaulted();
FUniformBufferInfoNew& Info = UniformBufferInfos[InfoIndex];
Info.Name = ParseHLSLSymbolName<FShaderSource::CharType, FShaderSource::FViewType>(ReadStart);
check(Info.Name.Len() < 64);
const FShaderSource::CharType* OpeningBrace = FindNextChar(ReadStart, '{');
const FShaderSource::CharType* ClosingBrace = FindMatchingClosingBrace(OpeningBrace + 1);
const FShaderSource::CharType* CurrentParseStart = OpeningBrace + 1;
const FShaderSource::CharType* NextSemicolon = FindNextChar(CurrentParseStart, ';');
while (NextSemicolon < ClosingBrace)
{
const FShaderSource::CharType* NextSeparator = FindNextChar(CurrentParseStart, '=');
if (NextSeparator < NextSemicolon)
{
const FShaderSource::CharType* StructStart = CurrentParseStart;
const FShaderSource::CharType* StructEnd = NextSeparator - 1;
const FShaderSource::CharType* GlobalStart = NextSeparator + 1;
const FShaderSource::CharType* GlobalEnd = NextSemicolon - 1;
while (IsSpaceOrTabOrEOL(*StructStart))
{
StructStart++;
}
while (IsSpaceOrTabOrEOL(*GlobalStart))
{
GlobalStart++;
}
StructEnd = CompactCompoundIdentifier(const_cast<FShaderSource::CharType*>(StructStart), const_cast<FShaderSource::CharType*>(StructEnd));
GlobalEnd = CompactCompoundIdentifier(const_cast<FShaderSource::CharType*>(GlobalStart), const_cast<FShaderSource::CharType*>(GlobalEnd));
FShaderSource::FViewType StructName(StructStart, StructEnd - StructStart);
FShaderSource::FViewType GlobalName(GlobalStart, GlobalEnd - GlobalStart);
// Avoid unnecessary conversions
if (StructName.Len() == GlobalName.Len() && FShaderSource::FCStringType::Strncmp(StructName.GetData(), GlobalName.GetData(), StructName.Len()) != 0)
{
FUniformBufferMemberInfoNew NewMemberInfo;
NewMemberInfo.NameAsStructMember = StructName;
NewMemberInfo.GlobalName = GlobalName;
// Need to be able to replace strings in place, so make sure GlobalName will fit in space of NameAsStructMember
check(NewMemberInfo.NameAsStructMember.Len() >= NewMemberInfo.GlobalName.Len());
Info.Members.Add(NewMemberInfo);
}
}
CurrentParseStart = NextSemicolon + 1;
NextSemicolon = FindNextChar(CurrentParseStart, ';');
}
const FShaderSource::CharType* EndPtr = ClosingBrace;
// Skip to the end of the UniformBuffer
while (*EndPtr && *EndPtr != ';')
{
EndPtr++;
}
if (Info.Members.Num())
{
// We have members. Sort them. Note that the sort is by length first, not alphabetical, so the last item will be the longest.
Algo::Sort(Info.Members);
int32 MaxLen = Info.Members.Last().NameAsStructMember.Len();
// Initialize table with offset of first member with a given length, and the count of members of that length (going backwards so the
// index of the first element of a given size is the last one written to "MemberOffset").
Info.MembersByLength.SetNumZeroed(MaxLen + 1);
for (int32 MemberIndex = Info.Members.Num() - 1; MemberIndex >= 0; MemberIndex--)
{
int32 CurrentMemberLen = Info.Members[MemberIndex].NameAsStructMember.Len();
Info.MembersByLength[CurrentMemberLen].MemberOffset = MemberIndex;
Info.MembersByLength[CurrentMemberLen].MemberCount++;
}
// Initialize the uniform buffer name filter. The filter is a mask based on the first character of the name (minus 64 so valid token
// starting characters which are in ASCII range 64..127 fit in 64 bits). We can quickly check if a token of the given length and start
// character might be one we care about.
UniformBufferFilter[Info.Name.Len()] |= 1ull << (Info.Name[0] - 64);
// Add to linked list of uniform buffers by name length
Info.NextWithSameLength = UniformBuffersByLength[Info.Name.Len()];
UniformBuffersByLength[Info.Name.Len()] = InfoIndex;
}
else
{
// If no members, we don't care about it
UniformBufferInfos.RemoveAt(UniformBufferInfos.Num() - 1);
}
return EndPtr;
}
enum class AsciiFlags
{
TerminatorOrSlash = (1 << 0), // Null terminator OR slash (latter we care about for skipping commented out uniform blocks)
Whitespace = (1 << 1), // Includes other special characters below 32 (in addition to tab / newline)
Other = (1 << 2), // Anything else not one of the other types
SymbolStart = (1<<3), // Letters plus underscore (anything that can start a symbol)
Digit = (1 << 4),
Dot = (1 << 5),
Quote = (1 << 6),
Hash = (1 << 7),
};
static uint8 AsciiFlagTable[256] =
{
1,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, // Treat all special characters as whitespace
2,4,64,128,4,4,4,4, // 34 == Quote 35 == Hash
4,4,4,4,4,4,32,1, // 46 == Dot 47 == Slash
16,16,16,16,16,16,16,16, // Digits 0-7
16,16,4,4,4,4,4,4, // Digits 8-9
4,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8, 8,8,8,4,4,4,4,8, // Upper case letters, 95 == Underscore
4,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8, 8,8,8,4,4,4,4,4, // Lower case letters
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, // Treat all non-ASCII characters as Other
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
};
struct FCompoundIdentifierResult
{
const FShaderSource::CharType* Identifier; // Start of identifier
const FShaderSource::CharType* IdentifierEnd; // End of entire identifier
const FShaderSource::CharType* IdentifierRootEnd; // End of root token of identifier
};
// Searches for a "compound identifier" (series of symbol tokens separated by dots) that also passes the "RootIdentifierFilter".
// The filter is a mask table of valid identifier start characters indexed by identifier length. Since identifier characters start
// with letters or underscore, we can store a 64-bit mask representing ASCII characters 64..127, as all valid start characters are
// in that range. As an example, if "View" is a valid root identifier, RootIdentifierFilter[4] will have the bit ('V' - 64) set,
// and any other 4 character identifier that doesn't start with that letter can be skipped, saving overhead in the caller.
bool FindNextCompoundIdentifier(const FShaderSource::CharType*& Search, const uint64 RootIdentifierFilter[64], FCompoundIdentifierResult& OutResult)
{
const FShaderSource::CharType* SearchChar = Search;
uint8 SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
// Scanning loop
while (1)
{
static constexpr uint8 AsciiFlagsEchoVerbatim = (uint8)AsciiFlags::Whitespace | (uint8)AsciiFlags::Other;
static constexpr uint8 AsciiFlagsSymbol = (uint8)AsciiFlags::SymbolStart | (uint8)AsciiFlags::Digit;
static constexpr uint8 AsciiFlagsStartNumberOrDirective = (uint8)AsciiFlags::Digit | (uint8)AsciiFlags::Dot | (uint8)AsciiFlags::Hash;
static constexpr uint8 AsciiFlagsEndNumberOrDirective = (uint8)AsciiFlags::Whitespace | (uint8)AsciiFlags::Other | (uint8)AsciiFlags::Quote | (uint8)AsciiFlags::TerminatorOrSlash;
// Conditions here are organized in expected order of frequency
if (SearchCharFlag & AsciiFlagsEchoVerbatim)
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
else if (SearchCharFlag & (uint8)AsciiFlags::SymbolStart)
{
OutResult.Identifier = SearchChar;
SearchChar++;
while ((SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar]) & AsciiFlagsSymbol)
{
SearchChar++;
}
// Track end of our root identifier
OutResult.IdentifierRootEnd = SearchChar;
// Skip any whitespace before a potential dot
while (SearchCharFlag & ((uint8)AsciiFlags::Whitespace))
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
// If we didn't find a dot, go back to initial scanning state
if (!(SearchCharFlag & ((uint8)AsciiFlags::Dot)))
{
continue;
}
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
// Determine if this root identifier passes the filter. If so, we'll continue to parse the rest of the identifier,
// but then go back to scanning. The mask in RootIdentifierFilter starts with ASCII character 64, as token start
// characters are in the range [64..127].
ptrdiff_t IdentifierRootLen = OutResult.IdentifierRootEnd - OutResult.Identifier;
if (IdentifierRootLen >= 64 || !(RootIdentifierFilter[IdentifierRootLen] & (1ull << (*OutResult.Identifier - 64))))
{
// Clear this, marking that we didn't find a candidate root identifier
OutResult.IdentifierRootEnd = nullptr;
}
// Skip any whitespace after dot
while (SearchCharFlag & ((uint8)AsciiFlags::Whitespace))
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
// Check for the start of another symbol after the dot -- if it's not a symbol, switch back to scanning -- some kind of incorrect code
if (!(SearchCharFlag & (uint8)AsciiFlags::SymbolStart))
{
continue;
}
// Repeatedly scan for additional parts of the identifier separated by dots
while (1)
{
SearchChar++;
while ((SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar]) & AsciiFlagsSymbol)
{
SearchChar++;
}
// Track that this may be the end of the identifier (if there's not more dot separated tokens)
OutResult.IdentifierEnd = SearchChar;
// Skip any whitespace before a potential dot
while (SearchCharFlag & ((uint8)AsciiFlags::Whitespace))
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
// If we found something other than a dot, we're done!
if (!(SearchCharFlag & ((uint8)AsciiFlags::Dot)))
{
// Is the root token for this identifier a candidate based on the filter?
if (OutResult.IdentifierRootEnd)
{
Search = SearchChar;
return true;
}
else
{
// If not, go back to initial scanning state
break;
}
}
// Skip the dot
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
// Skip any whitespace after dot
while (SearchCharFlag & ((uint8)AsciiFlags::Whitespace))
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
// Did we find the start of another symbol after the dot? If not, break out, some kind of invalid code...
if (!(SearchCharFlag & (uint8)AsciiFlags::SymbolStart))
{
break;
}
}
}
else if (SearchCharFlag & AsciiFlagsStartNumberOrDirective)
{
// Number or directive, skip to Whitespace, Other, or Quote (numbers may contain letters or #, i.e. "1.#INF" for infinity, or "e" for an exponent)
SearchChar++;
while (!((SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar]) & AsciiFlagsEndNumberOrDirective))
{
SearchChar++;
}
}
else if (SearchCharFlag & (uint8)AsciiFlags::Quote)
{
// Quote, skip to next Quote (or maybe end of string if text is malformed), ignoring the quote if it's escaped
SearchChar++;
while (*SearchChar && (*SearchChar != '\"' || *(SearchChar - 1) == '\\'))
{
SearchChar++;
}
// Could be end of string or the quote -- skip over the quote if not the null terminator
if (*SearchChar)
{
SearchChar++;
}
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
// Must be null terminator or slash at this point -- we've tested all other possibilities
else if (*SearchChar == '/')
{
// Check if this is a commented out block (typically a commented out uniform declaration) and skip over it.
// If the text is bad, there could be a /* right at the end of the string, so we need to check there is at least
// one more character.
if (SearchChar[1] == '*' && SearchChar[2] != 0)
{
// Search for slash (or end of string), starting at SearchChar + 3. If we find a slash, we'll check the previous
// character to see if it's the end of the comment. Starting at +3 is necessary to avoid matching a slash as the
// first character of the comment, i.e. "/*/".
SearchChar += 3;
while (1)
{
while ((SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar]) != (uint8)AsciiFlags::TerminatorOrSlash)
{
SearchChar++;
}
// Is this the end of the comment?
if (*(SearchChar - 1) == '*')
{
if (*SearchChar)
{
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
break;
}
}
else
{
// More characters, continue the comment scanning loop, or if somehow at end of string, return false...
if (*SearchChar)
{
SearchChar++;
}
else
{
return false;
}
}
}
}
else
{
// Just a slash, not part of a block comment
SearchChar++;
SearchCharFlag = AsciiFlagTable[(uint8)*SearchChar];
}
}
else
{
// End of string
Search = SearchChar;
return false;
}
}
}
FShaderSource::CharType* FindNextUniformBufferDefinition(FShaderSource::CharType* SearchPtr, FShaderSource::CharType* SourceStart, FShaderSource::FViewType UniformBufferStructIdentifier)
{
while (SearchPtr)
{
SearchPtr = FShaderSource::FCStringType::Strstr(SearchPtr, UniformBufferStructIdentifier.GetData());
if (SearchPtr)
{
if (SearchPtr > SourceStart && IsSpaceOrTabOrEOL(*(SearchPtr - 1)) && IsSpaceOrTabOrEOL(*(SearchPtr + UniformBufferStructIdentifier.Len())))
{
break;
}
else
{
SearchPtr = SearchPtr + 1;
}
}
}
return SearchPtr;
}
const FShaderSource::CharType* FindPreviousDot(const FShaderSource::CharType* SearchPtr, const FShaderSource::CharType* SearchMin)
{
while ((SearchPtr > SearchMin) && (*SearchPtr != '.'))
{
SearchPtr--;
}
return SearchPtr;
}
// The cross compiler doesn't yet support struct initializers needed to construct static structs for uniform buffers
// Replace all uniform buffer struct member references (View.WorldToClip) with a flattened name that removes the struct dependency (View_WorldToClip)
void CleanupUniformBufferCode(const FShaderCompilerEnvironment& Environment, FShaderSource& PreprocessedShaderSource)
{
TRACE_CPUPROFILER_EVENT_SCOPE(CleanupUniformBufferCode);
TArray<FUniformBufferInfoNew> UniformBufferInfos;
TArray<FUniformBufferSpan> UniformBufferSpans;
uint64 UniformBufferFilter[64] = { 0 }; // A bit set for valid start characters for uniform buffer name of given length
int32 UniformBuffersByLength[64]; // Linked list head index into UniformBufferInfos by length (connected by "NextWithSameLength")
UniformBufferInfos.Reserve(Environment.UniformBufferMap.Num());
UniformBufferSpans.Reserve(Environment.UniformBufferMap.Num());
memset(UniformBuffersByLength, 0xff, sizeof(UniformBuffersByLength));
FShaderSource::FViewType UniformBufferStructIdentifier = ANSITEXTVIEW("UniformBuffer");
FShaderSource::CharType* SourceStart = PreprocessedShaderSource.GetData();
FShaderSource::CharType* SearchPtr = SourceStart;
FShaderSource::CharType* EndOfPreviousUniformBuffer = SourceStart;
bool bUniformBufferFound;
do
{
// Find the next uniform buffer definition
SearchPtr = FindNextUniformBufferDefinition(SearchPtr, SourceStart, UniformBufferStructIdentifier);
if (SearchPtr)
{
// Track that we found a uniform buffer, and temporarily null terminate the string so we can parse to this point
bUniformBufferFound = true;
*SearchPtr = 0;
}
else
{
bUniformBufferFound = false;
}
// Parse the source between the last uniform buffer and the current uniform buffer (or potentially the end of the source if no more
// were found). If there are no uniform buffers yet, we don't need to parse anything.
if (UniformBufferInfos.Num())
{
const FShaderSource::CharType* ParsePtr = EndOfPreviousUniformBuffer;
FCompoundIdentifierResult Result;
while (FindNextCompoundIdentifier(ParsePtr, UniformBufferFilter, Result))
{
// Check if the identifier corresponds to a uniform buffer
FShaderSource::FViewType IdentifierRoot(Result.Identifier, Result.IdentifierRootEnd - Result.Identifier);
for (int32 UniformInfoIndex = UniformBuffersByLength[IdentifierRoot.Len()]; UniformInfoIndex != INDEX_NONE; UniformInfoIndex = UniformBufferInfos[UniformInfoIndex].NextWithSameLength)
{
FUniformBufferInfoNew& Info = UniformBufferInfos[UniformInfoIndex];
if (IdentifierRoot.Equals(Info.Name, ESearchCase::CaseSensitive))
{
// Found the uniform buffer, clean up potential whitespace
Result.IdentifierEnd = CompactCompoundIdentifier(const_cast<FShaderSource::CharType*>(Result.Identifier), const_cast<FShaderSource::CharType*>(Result.IdentifierEnd));
// Now try to find a matching member. We need to check subsets of the full "identifier", to strip away function calls, components, or child structures.
bool bMatchFound = false;
for (; Result.IdentifierEnd > Result.IdentifierRootEnd; Result.IdentifierEnd = FindPreviousDot(Result.IdentifierEnd - 1, Result.IdentifierRootEnd))
{
FShaderSource::FViewType Identifier(Result.Identifier, Result.IdentifierEnd - Result.Identifier);
if (Identifier.Len() < Info.MembersByLength.Num())
{
const FUniformBufferMemberView& MemberView = Info.MembersByLength[Identifier.Len()];
for (int32 MemberIndex = MemberView.MemberOffset; MemberIndex < MemberView.MemberOffset + MemberView.MemberCount; MemberIndex++)
{
if (Info.Members[MemberIndex].NameAsStructMember.Equals(Identifier, ESearchCase::CaseSensitive))
{
bMatchFound = true;
const int32 OriginalTextLen = Info.Members[MemberIndex].NameAsStructMember.Len();
const int32 ReplacementTextLen = Info.Members[MemberIndex].GlobalName.Len();
const FShaderSource::CharType* GlobalNameStart = GetData(Info.Members[MemberIndex].GlobalName);
FShaderSource::CharType* IdentifierStart = const_cast<FShaderSource::CharType*>(Result.Identifier);
int32 Index = 0;
for (; Index < ReplacementTextLen; Index++)
{
IdentifierStart[Index] = GlobalNameStart[Index];
}
for (; Index < OriginalTextLen; Index++)
{
IdentifierStart[Index] = ' ';
}
break;
}
}
if (bMatchFound)
{
break;
}
}
}
break;
}
}
}
}
// Parse the current uniform buffer.
if (bUniformBufferFound)
{
// Unterminate the string (put the first character of the struct identifier back in place) and parse it
*SearchPtr = UniformBufferStructIdentifier[0];
const FShaderSource::CharType* ConstStructEndPtr = ParseUniformBufferDefinition(SearchPtr + UniformBufferStructIdentifier.Len(), UniformBufferInfos, UniformBufferFilter, UniformBuffersByLength);
FShaderSource::CharType* StructEndPtr = &SourceStart[ConstStructEndPtr - &SourceStart[0]];
// Comment out the uniform buffer struct and initializer
*SearchPtr = '/';
*(SearchPtr + 1) = '*';
*(StructEndPtr - 1) = '*';
*StructEndPtr = '/';
UniformBufferSpans.Add({ (int32)(SearchPtr - SourceStart), (int32)(StructEndPtr + 1 - SearchPtr) });
EndOfPreviousUniformBuffer = StructEndPtr + 1;
SearchPtr = StructEndPtr + 1;
}
} while (bUniformBufferFound);
// Compact commented out uniform buffers out of the output source. This costs around 10x less to do here than later in the minifier. Note that
// it's not necessary to add a line directive to fix up line numbers because uniform buffer declarations are always in generated files, and there
// will be a line directive already there for the transition from the generated file back to whatever file included it. The destination offset
// for the first move is the start of the first uniform buffer declaration we are overwriting, then advances as characters are copied.
int32 DestOffset = UniformBufferSpans.Num() ? UniformBufferSpans[0].Offset : PreprocessedShaderSource.Len();
for (int32 SpanIndex = 0; SpanIndex < UniformBufferSpans.Num(); SpanIndex++)
{
// The source code we are compacting down is from the end of one span to the start of the next span, or end of the string.
// We do not need to account for null terminator as the ShrinkToLen call below will null terminate for us.
int32 SourceOffset = UniformBufferSpans[SpanIndex].Offset + UniformBufferSpans[SpanIndex].Length;
int32 MoveCount = (SpanIndex < UniformBufferSpans.Num() - 1 ? UniformBufferSpans[SpanIndex + 1].Offset : PreprocessedShaderSource.Len()) - SourceOffset;
check(DestOffset >= 0 && DestOffset < SourceOffset && SourceOffset + MoveCount <= PreprocessedShaderSource.Len());
memmove(SourceStart + DestOffset, SourceStart + SourceOffset, MoveCount * sizeof(FShaderSource::CharType));
DestOffset += MoveCount;
}
PreprocessedShaderSource.ShrinkToLen(DestOffset, EAllowShrinking::No);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static FString CreateShaderConductorCommandLine(const FShaderCompilerInput& Input, const FString& SourceFilename, EShaderConductorTarget SCTarget)
{
const TCHAR* Stage = nullptr;
switch (Input.Target.GetFrequency())
{
case SF_Vertex: Stage = TEXT("vs"); break;
case SF_Pixel: Stage = TEXT("ps"); break;
case SF_Geometry: Stage = TEXT("gs"); break;
case SF_Compute: Stage = TEXT("cs"); break;
default: return FString();
}
const TCHAR* Target = nullptr;
switch (SCTarget)
{
case EShaderConductorTarget::Dxil: Target = TEXT("dxil"); break;
case EShaderConductorTarget::Spirv: Target = TEXT("spirv"); break;
default: return FString();
}
FString CmdLine = TEXT("-E ") + Input.EntryPointName;
//CmdLine += TEXT("-O ") + *(CompilerInfo.Input.D);
CmdLine += TEXT(" -S ") + FString(Stage);
CmdLine += TEXT(" -T ");
CmdLine += Target;
CmdLine += TEXT(" -I ") + (Input.DumpDebugInfoPath / SourceFilename);
return CmdLine;
}
SHADERCOMPILERCOMMON_API void WriteShaderConductorCommandLine(const FShaderCompilerInput& Input, const FString& SourceFilename, EShaderConductorTarget Target)
{
FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*(Input.DumpDebugInfoPath / TEXT("ShaderConductorCmdLine.txt")));
if (FileWriter)
{
FString CmdLine = CreateShaderConductorCommandLine(Input, SourceFilename, Target);
FileWriter->Serialize(TCHAR_TO_ANSI(*CmdLine), CmdLine.Len());
FileWriter->Close();
delete FileWriter;
}
}
static uint32 OfflineCompiler_ExtractStats(const FString& CompilerOutput, const TArray<FString>& InstructionStrings)
{
uint32 ReturnedNum = 0;
// Parse the instruction count
int32 InstructionStringLength = 0, InstructionsIndex = 0;
for (const FString& InstrStr : InstructionStrings)
{
InstructionStringLength = InstrStr.Len();
InstructionsIndex = CompilerOutput.Find(*InstrStr);
if (InstructionsIndex != INDEX_NONE)
{
break;
}
}
if (InstructionsIndex != INDEX_NONE && InstructionsIndex + InstructionStringLength < CompilerOutput.Len())
{
const int32 EndIndex = CompilerOutput.Find(TEXT("\n"), ESearchCase::IgnoreCase, ESearchDir::FromStart, InstructionsIndex + InstructionStringLength);
if (EndIndex != INDEX_NONE)
{
int32 StartIndex = InstructionsIndex + InstructionStringLength;
bool bFoundNrStart = false;
int32 NumberIndex = 0;
while (StartIndex < EndIndex)
{
if (FChar::IsDigit(CompilerOutput[StartIndex]) && !bFoundNrStart)
{
// found number's beginning
bFoundNrStart = true;
NumberIndex = StartIndex;
}
else if (FChar::IsWhitespace(CompilerOutput[StartIndex]) && bFoundNrStart)
{
// found number's end
bFoundNrStart = false;
const FString NumberString = CompilerOutput.Mid(NumberIndex, StartIndex - NumberIndex);
const float fNrInstructions = FCString::Atof(*NumberString);
ReturnedNum += (uint32)FMath::Max(0.0, ceil(fNrInstructions));
}
++StartIndex;
}
}
}
return ReturnedNum;
}
static FString OfflineCompiler_ExtractErrors(const FString& CompilerOutput)
{
FString ReturnedErrors;
const int32 GlobalErrorIndex = CompilerOutput.Find(TEXT("Compilation failed."));
// find each 'line' that begins with token "ERROR:" and copy it to the returned string
if (GlobalErrorIndex != INDEX_NONE)
{
int32 CompilationErrorIndex = CompilerOutput.Find(TEXT("ERROR:"));
while (CompilationErrorIndex != INDEX_NONE)
{
int32 EndLineIndex = CompilerOutput.Find(TEXT("\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, CompilationErrorIndex + 1);
EndLineIndex = EndLineIndex == INDEX_NONE ? CompilerOutput.Len() - 1 : EndLineIndex;
ReturnedErrors += CompilerOutput.Mid(CompilationErrorIndex, EndLineIndex - CompilationErrorIndex + 1);
CompilationErrorIndex = CompilerOutput.Find(TEXT("ERROR:"), ESearchCase::CaseSensitive, ESearchDir::FromStart, EndLineIndex);
}
}
return ReturnedErrors;
}
/**
* OfflineShaderCompiler's compilation command line options.
* Each OfflineShaderCompiler should specify its own FOfflineShaderCompilerOptions. If one option is not supported by this OfflineShaderCompiler, leave it empty.
* Frequency (VS/PS/etc.) here is called as stage sometime, too.
*/
class FOfflineShaderCompilerOptions
{
public:
/** Options applied to all shaders. */
FString CommonOptions;
/** MultiView option if it's enabled. */
FString MultiViewOption;
/** GPUTarget option*/
FString GPUTargetOption;
/** Default GPUTarget*/
FString DefaultGPUTarget;
/** Dump All*/
FString DumpAll;
/** SpirV file extension name*/
FString SpirVExt;
/** Default file extension name*/
FString DefaultGLSLExt;
/** GLSL source file extension used to specify which shader stage is being compiled. */
TMap<EShaderFrequency, FString> FrequencyGLSLExts;
/** Option to specify which shader stage is being compiled. */
TMap<EShaderFrequency, FString> FrequencyOptions;
/** Entrypoint option used to specify the entry point of each shader frequency. */
TMap<EShaderFrequency, FString> FrequencyEntryPoints;
/** Extra option of each shader frequency. */
TMap<EShaderFrequency, FString> FrequencyExtraOption;
/** Entrypoint option used to specify the entry point of all shader frequencies. */
TCHAR* DefaultEntryPoint;
/** Used to parse stats output to find total instruction count. Using array to support multiple compiler versions*/
TArray<FString> NumInstructionNames;
/** Used to parse stats output to find each stat. Each item of this array is for one stat AND it is also an array to support multiple compiler versions. */
TArray<TArray<FString>> StatsNames;
static FString GetFrequenceName(EShaderFrequency Freq)
{
static FString FrequencyName[SF_NumFrequencies] =
{
TEXT("VS"), // SF_Vertex
TEXT("MS"), // SF_Mesh
TEXT("AS"), // SF_Amplification
TEXT("FS"), // SF_Pixel
TEXT("GS"), // SF_Geometry
TEXT("CS") // SF_Compute
};
if (Freq <= SF_Compute)
{
return FrequencyName[Freq];
}
else
{
return FString("Unknown");
}
}
};
void CompileShaderOffline(const FShaderCompilerInput& Input,
FShaderCompilerOutput& ShaderOutput,
const ANSICHAR* ShaderSource,
const int32 SourceSize,
bool bVulkanSpirV,
const FOfflineShaderCompilerOptions& Options,
const ANSICHAR* VulkanSpirVEntryPoint)
{
const auto Frequency = (EShaderFrequency)Input.Target.Frequency;
const FString WorkingDir(FPlatformProcess::ShaderDir());
FString CompilerPath = Input.ExtraSettings.OfflineCompilerPath;
// add process and thread ids to the file name to avoid collision between workers
auto ProcID = FPlatformProcess::GetCurrentProcessId();
auto ThreadID = FPlatformTLS::GetCurrentThreadId();
auto GetFileName = [&](FString FileType, FString Ext, int NumInst = 0)
{
return WorkingDir
/ FOfflineShaderCompilerOptions::GetFrequenceName(Frequency) + (NumInst ? TEXT("-") + FString::FromInt(NumInst) : TEXT(""))
+ FString::Printf(TEXT("-%s"), (VulkanSpirVEntryPoint ? ANSI_TO_TCHAR(VulkanSpirVEntryPoint) : TEXT("")))
+ *FileType + TEXT("-") + FString::FromInt(ProcID) + TEXT("-") + FString::FromInt(ThreadID)
+ *Ext;
};
FString ShaderSrcExt;
if (bVulkanSpirV)
{
ShaderSrcExt = Options.SpirVExt;
}
else
{
if (const FString* Ext = Options.FrequencyGLSLExts.Find(Frequency))
{
ShaderSrcExt = *Ext;
}
else
{
ShaderSrcExt = Options.DefaultGLSLExt;
}
}
FString ShaderSourceFile = GetFileName(FString("-Source"), ShaderSrcExt);
FString CompilerCommand = Options.CommonOptions;
if (!Options.GPUTargetOption.IsEmpty())
{
FString GPUTarget = Input.ExtraSettings.GPUTarget;
if (GPUTarget.IsEmpty())
{
GPUTarget = Options.DefaultGPUTarget;
}
CompilerCommand += FString::Printf(TEXT("%s=%s"), *Options.GPUTargetOption, *GPUTarget);
}
if (Input.ExtraSettings.bMobileMultiView)
{
CompilerCommand += Options.MultiViewOption;
}
CompilerCommand += Options.FrequencyOptions[Frequency];
if (bVulkanSpirV)
{
CompilerCommand += FString::Printf(TEXT("%s %s"), *Options.FrequencyEntryPoints[Frequency], ANSI_TO_TCHAR(VulkanSpirVEntryPoint));
}
if (const FString* ExtraOption = Options.FrequencyExtraOption.Find(Frequency))
{
CompilerCommand += *ExtraOption;
}
FArchive* Ar = IFileManager::Get().CreateFileWriter(*ShaderSourceFile, FILEWRITE_EvenIfReadOnly);
if (Ar == nullptr)
{
return;
}
// write out the shader source to a file and use it below as input for the compiler
Ar->Serialize((void*)ShaderSource, SourceSize);
delete Ar;
FString StdOut;
FString StdErr;
int32 ReturnCode = 0;
// Since v6.2.0, Mali compiler needs to be started in the executable folder or it won't find "external/glslangValidator" for Vulkan
FString CompilerWorkingDirectory = FPaths::GetPath(CompilerPath);
if (!CompilerWorkingDirectory.IsEmpty() && FPaths::DirectoryExists(CompilerWorkingDirectory))
{
// compiler command line contains flags and the GLSL source file name
CompilerCommand += " " + FPaths::ConvertRelativePathToFull(ShaderSourceFile);
// Run shader compiler and wait for completion
FPlatformProcess::ExecProcess(*CompilerPath, *CompilerCommand, &ReturnCode, &StdOut, &StdErr, *CompilerWorkingDirectory);
}
else
{
StdErr = "Couldn't find offline compiler at " + CompilerPath;
}
// parse Mali's output and extract instruction count or eventual errors
ShaderOutput.bSucceeded = (ReturnCode >= 0);
if (ShaderOutput.bSucceeded)
{
// check for errors
if (StdErr.Len())
{
ShaderOutput.bSucceeded = false;
FShaderCompilerError& NewError = ShaderOutput.Errors.AddDefaulted_GetRef();
NewError.StrippedErrorMessage = TEXT("[Offline Complier]\n") + StdErr;
}
else
{
FString Errors = OfflineCompiler_ExtractErrors(StdOut);
if (Errors.Len())
{
FShaderCompilerError& NewError = ShaderOutput.Errors.AddDefaulted_GetRef();
NewError.StrippedErrorMessage = TEXT("[Offline Complier]\n") + Errors;
ShaderOutput.bSucceeded = false;
}
}
// extract instruction count
if (ShaderOutput.bSucceeded)
{
ShaderOutput.NumInstructions = OfflineCompiler_ExtractStats(StdOut, Options.NumInstructionNames);
FString OutputStatsFile = GetFileName(FString("-Stats"), FString(".txt"), ShaderOutput.NumInstructions);
for (auto& StatNames : Options.StatsNames)
{
if(StatNames.Num())
{
ShaderOutput.AddStatistic<uint32>(*StatNames[0], OfflineCompiler_ExtractStats(StdOut, StatNames));
}
}
if (Input.ExtraSettings.bSaveCompilerStatsFiles)
{
FArchive* ArOutput = IFileManager::Get().CreateFileWriter(*OutputStatsFile, FILEWRITE_EvenIfReadOnly);
if (ArOutput == nullptr)
{
return;
}
if (!Options.DumpAll.IsEmpty())
{
CompilerCommand += Options.DumpAll;
//TODO: It's expensive to run the process twice. Better to run it once with DumpAll and parse the StdOut to get Stats.
// But to do that, we need to know the preserved keyword for Stats.
FPlatformProcess::ExecProcess(*CompilerPath, *CompilerCommand, &ReturnCode, &StdOut, &StdErr, *CompilerWorkingDirectory);
}
FString StatsOutput = CompilerCommand + FString("\n") + StdOut;
const int32 StatsLen = StatsOutput.Len();
TSharedPtr<ANSICHAR> ShaderStats = MakeShareable(new ANSICHAR[StatsLen + 1]);
FCStringAnsi::Strncpy(ShaderStats.Get(), TCHAR_TO_ANSI(*StatsOutput), StatsLen + 1);
ArOutput->Serialize((void*)ShaderStats.Get(), StatsLen);
delete ArOutput;
}
}
}
// we're done so delete the shader file
if (Input.ExtraSettings.bSaveCompilerStatsFiles)
{
FString DstShaderSourceFile = GetFileName(FString("-Source"), ShaderSrcExt, ShaderOutput.NumInstructions);
IFileManager::Get().Move(*DstShaderSourceFile, *ShaderSourceFile, true, true);
IFileManager::Get().Delete(*ShaderSourceFile, true, true);
}
IFileManager::Get().Delete(*ShaderSourceFile, true, true);
}
void CompileShaderOffline_Mali(const FShaderCompilerInput& Input,
FShaderCompilerOutput& ShaderOutput,
const ANSICHAR* ShaderSource,
const int32 SourceSize,
bool bVulkanSpirV,
const ANSICHAR* VulkanSpirVEntryPoint)
{
static FOfflineShaderCompilerOptions Options;
if (bVulkanSpirV)
{
Options.CommonOptions = TEXT(" -p");
}
else
{
Options.CommonOptions = TEXT(" -s");
}
if (Options.SpirVExt.IsEmpty())
{
Options.SpirVExt = TEXT(".spv");
Options.DefaultGLSLExt = TEXT(".shd");
Options.FrequencyGLSLExts.Emplace(SF_Vertex, TEXT(".vert"));
Options.FrequencyGLSLExts.Emplace(SF_Pixel, TEXT(".frag"));
Options.FrequencyGLSLExts.Emplace(SF_Geometry, TEXT(".geom"));
Options.FrequencyGLSLExts.Emplace(SF_Compute, TEXT(".comp"));
Options.FrequencyOptions.Emplace(SF_Vertex, FString(" -v"));
Options.FrequencyOptions.Emplace(SF_Pixel, FString(" -f"));
Options.FrequencyOptions.Emplace(SF_Geometry, FString(" -g"));
Options.FrequencyOptions.Emplace(SF_Compute, FString(" -C"));
Options.FrequencyEntryPoints.Emplace(SF_Vertex, TEXT(" -y"));
Options.FrequencyEntryPoints.Emplace(SF_Pixel, TEXT(" -y"));
Options.FrequencyEntryPoints.Emplace(SF_Geometry, TEXT(" -y"));
Options.FrequencyEntryPoints.Emplace(SF_Compute, TEXT(" -y"));
Options.NumInstructionNames.Add("Instructions Emitted:");
Options.NumInstructionNames.Add("Total instruction cycles:");
}
CompileShaderOffline(Input, ShaderOutput, ShaderSource, SourceSize, bVulkanSpirV, Options, VulkanSpirVEntryPoint);
}
void CompileShaderOffline_Adreno(const FShaderCompilerInput& Input,
FShaderCompilerOutput& ShaderOutput,
const ANSICHAR* ShaderSource,
const int32 SourceSize,
bool bVulkanSpirV,
const ANSICHAR* VulkanSpirVEntryPoint)
{
static FOfflineShaderCompilerOptions Options;
if (bVulkanSpirV)
{
Options.CommonOptions = TEXT(" -api=Vulkan");
}
if (Options.MultiViewOption.IsEmpty())
{
Options.MultiViewOption = TEXT(" -view_mask=0x3");
Options.GPUTargetOption = TEXT(" -arch");
Options.DefaultGPUTarget = TEXT("a650");
Options.SpirVExt = TEXT(".spv");
Options.DefaultGLSLExt = TEXT(".shd");
Options.FrequencyGLSLExts.Emplace(SF_Vertex, TEXT(".vert"));
Options.FrequencyGLSLExts.Emplace(SF_Pixel, TEXT(".frag"));
Options.FrequencyGLSLExts.Emplace(SF_Geometry, TEXT(".geom"));
Options.FrequencyGLSLExts.Emplace(SF_Compute, TEXT(".comp"));
Options.FrequencyOptions.Emplace(SF_Vertex, FString(" -vs"));
Options.FrequencyOptions.Emplace(SF_Pixel, FString(" -fs"));
Options.FrequencyOptions.Emplace(SF_Geometry, FString(" -gs"));
Options.FrequencyOptions.Emplace(SF_Compute, FString(" -cs"));
Options.FrequencyEntryPoints.Emplace(SF_Vertex, TEXT(" -entry_point_vs"));
Options.FrequencyEntryPoints.Emplace(SF_Pixel, TEXT(" -entry_point_ps"));
Options.FrequencyEntryPoints.Emplace(SF_Geometry, TEXT(" -entry_point_gs"));
Options.FrequencyEntryPoints.Emplace(SF_Compute, TEXT(" -entry_point_cs"));
Options.FrequencyExtraOption.Emplace(SF_Vertex, TEXT(" -link_with_fs"));
Options.NumInstructionNames.Add("Total instruction count");
Options.StatsNames.Add(TArray<FString>{TEXT("ALU instruction count - 32 bit")});
Options.StatsNames.Add(TArray<FString>{TEXT("ALU instruction count - 16 bit")});
Options.StatsNames.Add(TArray<FString>{TEXT("Complex instruction count - 32 bit")});
Options.StatsNames.Add(TArray<FString>{TEXT("Complex instruction count - 16 bit")});
Options.StatsNames.Add(TArray<FString>{TEXT("Flow control instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Barrier and fence Instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Short latency sync instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Long latency sync instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Texture read instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Memory read instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Memory write instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Miscellaneous instruction count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Full precision register footprint per shader instance")});
Options.StatsNames.Add(TArray<FString>{TEXT("Half precision register footprint per shader instance")});
Options.StatsNames.Add(TArray<FString>{TEXT("Overall register footprint per shader instance")});
Options.StatsNames.Add(TArray<FString>{TEXT("Scratch memory usage per shader instance")});
Options.StatsNames.Add(TArray<FString>{TEXT("Loop count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Output component count")});
Options.StatsNames.Add(TArray<FString>{TEXT("Input component count")});
Options.StatsNames.Add(TArray<FString>{TEXT("ALU fiber occupancy percentage")});
}
if (Input.ExtraSettings.bDumpAll)
{
Options.DumpAll = " -dump=all";
}
CompileShaderOffline(Input, ShaderOutput, ShaderSource, SourceSize, bVulkanSpirV, Options, VulkanSpirVEntryPoint);
}
void CompileShaderOffline(const FShaderCompilerInput& Input,
FShaderCompilerOutput& ShaderOutput,
const ANSICHAR* ShaderSource,
const int32 SourceSize,
bool bVulkanSpirV,
const ANSICHAR* VulkanSpirVEntryPoint)
{
const bool bCompilerExecutableExists = FPaths::FileExists(Input.ExtraSettings.OfflineCompilerPath);
if (!bCompilerExecutableExists)
{
return;
}
EOfflineShaderCompilerType OfflineCompiler = Input.ExtraSettings.OfflineCompiler;
switch (OfflineCompiler)
{
case EOfflineShaderCompilerType::Mali:
CompileShaderOffline_Mali(Input, ShaderOutput, ShaderSource, SourceSize, bVulkanSpirV, VulkanSpirVEntryPoint);
break;
case EOfflineShaderCompilerType::Adreno:
CompileShaderOffline_Adreno(Input, ShaderOutput, ShaderSource, SourceSize, bVulkanSpirV, VulkanSpirVEntryPoint);
break;
case EOfflineShaderCompilerType::Num:
break;
default:
break;
}
}
// sensible default path size; TStringBuilder will allocate if it needs to
const FString GetDebugFileName(
const FShaderCompilerInput& Input,
const UE::ShaderCompilerCommon::FDebugShaderDataOptions& Options,
const TCHAR* BaseFilename,
const TCHAR* Suffix = nullptr)
{
TStringBuilder<512> PathBuilder;
const TCHAR* Prefix = (Options.FilenamePrefix && *Options.FilenamePrefix) ? Options.FilenamePrefix : TEXT("");
FStringView Filename = (BaseFilename && *BaseFilename) ? BaseFilename : Input.GetSourceFilenameView();
FStringView Ext = FPathViews::GetExtension(Filename, true);
FStringView FilenameNoExt = Filename.LeftChop(Ext.Len());
FPathViews::Append(PathBuilder, Input.DumpDebugInfoPath, Prefix);
PathBuilder << FilenameNoExt;
if (Suffix)
{
PathBuilder << Suffix;
}
PathBuilder << Ext;
return PathBuilder.ToString();
}
namespace UE::ShaderCompilerCommon
{
bool ExecuteShaderPreprocessingSteps(
FShaderPreprocessOutput& PreprocessOutput,
const FShaderCompilerInput& Input,
const FShaderCompilerEnvironment& Environment,
const FShaderCompilerDefinitions& AdditionalDefines
)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBaseShaderFormat_PreprocessShader);
check(CheckVirtualShaderFilePath(Input.VirtualSourceFilePath));
bool bSuccess = ::PreprocessShader(PreprocessOutput, Input, Environment, AdditionalDefines);
if (bSuccess)
{
CleanupUniformBufferCode(Environment, PreprocessOutput.EditSource());
if (Input.Environment.CompilerFlags.Contains(CFLAG_RemoveDeadCode))
{
const TArray<FStringView> RequiredSymbols(MakeArrayView(Input.RequiredSymbols));
UE::ShaderCompilerCommon::RemoveDeadCode(PreprocessOutput.EditSource(), Input.EntryPointName, RequiredSymbols, PreprocessOutput.EditErrors());
}
}
return bSuccess;
}
bool ExecuteShaderPreprocessingSteps(
FShaderPreprocessOutput& PreprocessOutput,
const FShaderCompilerInput& Input,
const FShaderCompilerEnvironment& Environment
)
{
// overloaded function rather than defaulting definitions parameter to avoid including internal header in public header
return ExecuteShaderPreprocessingSteps(PreprocessOutput, Input, Environment, FShaderCompilerDefinitions());
}
FString FDebugShaderDataOptions::GetDebugShaderPath(const FShaderCompilerInput& Input, const TCHAR* Suffix) const
{
return GetDebugFileName(Input, *this, OverrideBaseFilename, Suffix);
}
bool FBaseShaderFormat::PreprocessShader(
const FShaderCompilerInput& Input,
const FShaderCompilerEnvironment& Environment,
FShaderPreprocessOutput& PreprocessOutput) const
{
return ExecuteShaderPreprocessingSteps(PreprocessOutput, Input, Environment);
}
void FBaseShaderFormat::OutputDebugData(
const FShaderCompilerInput& Input,
const FShaderPreprocessOutput& PreprocessOutput,
const FShaderCompilerOutput& Output,
FShaderDebugDataContext& Ctx) const
{
DumpExtendedDebugShaderData(Input, PreprocessOutput, Output, Ctx);
}
static void DumpDebugShaderDataInternal(const FShaderCompilerInput& Input, FStringView PreprocessedSource, FShaderDebugDataContext& Ctx, const FDebugShaderDataOptions& Options, const TCHAR* Suffix)
{
if (!Input.DumpDebugInfoEnabled())
{
return;
}
FString Contents = UE::ShaderCompilerCommon::GetDebugShaderContents(Input, PreprocessedSource, Options, Suffix);
FString DebugSourcePath = Options.GetDebugShaderPath(Input, Suffix);
FFileHelper::SaveStringToFile(Contents, *DebugSourcePath);
Ctx.DebugSourceFiles.Add(MoveTemp(DebugSourcePath));
}
void DumpDebugShaderData(const FShaderCompilerInput& Input, const FString& PreprocessedSource, const FDebugShaderDataOptions& Options)
{
// deprecated
}
void FBaseShaderFormat::OutputDebugDataMinimal(const FShaderCompilerInput& Input, FShaderDebugDataContext& Ctx) const
{
const FStringView FailedSourceStr = TEXTVIEW(R"(
// Preprocessing failed for shader; defines used can be seen above.
// ShaderCompileWorker can be run via the debugger using the commandline args in DebugCompileArgs.txt to inspect the contents of the FShaderCompilerInput/FShaderCompilerEnvironment.)");
DumpDebugShaderDataInternal(Input, FailedSourceStr, Ctx, FDebugShaderDataOptions(), nullptr);
}
void DumpExtendedDebugShaderData(
const FShaderCompilerInput& Input,
const FShaderPreprocessOutput& PreprocessOutput,
const FShaderCompilerOutput& Output,
const FDebugShaderDataOptions& Options)
{
// deprecated
}
void DumpExtendedDebugShaderData(
const FShaderCompilerInput& Input,
const FShaderPreprocessOutput& PreprocessOutput,
const FShaderCompilerOutput& Output,
FShaderDebugDataContext& Ctx,
const FDebugShaderDataOptions& Options)
{
if (!Input.Environment.CompilerFlags.Contains(CFLAG_DisableSourceStripping) && EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::DetailedSource))
{
const TCHAR* StrippedSuffix = TEXT("_Stripped");
FFileHelper::SaveStringToFile(GetDebugShaderContents(Input, PreprocessOutput.GetSourceViewWide(), Options, StrippedSuffix), *Options.GetDebugShaderPath(Input, StrippedSuffix));
}
bool bHasModifiedSource = !Output.ModifiedShaderSource.IsEmpty();
if (bHasModifiedSource)
{
// If the compile step applies modifications to the source, output this as the "default" USF; it's not compatible with SCW debug compile mode but backends
// which output compile batch files rely on this being the copy of the source that can be passed directly to the platform compiler.
FFileHelper::SaveStringToFile(Output.ModifiedShaderSource, *Options.GetDebugShaderPath(Input));
}
// if no modifications to source are made in the compile step, output just the single usf which is the unstripped version compatible with launching
// SCW in debug compile mode (the stripped version is less useful for debugging via this mechanism, so is only output in "detailed source" mode)
// if modifications were made, this is output as an additional artifact, appending "_DebugCompile" to the path to indicate that it can be used as such.
DumpDebugShaderDataInternal(Input, PreprocessOutput.GetUnstrippedSourceView(), Ctx, Options, bHasModifiedSource ? TEXT("_DebugCompile") : nullptr);
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::ShaderCodePlatformHashes))
{
// if the platform has registered a CodeHash stat, output a file containing this as well
const FGenericShaderStat* Hash = Output.ShaderStatistics.FindByPredicate([](const FGenericShaderStat& Stat)
{
return Stat.StatName == kPlatformHashStatName;
});
if (Hash)
{
FFileHelper::SaveStringToFile(Hash->Value.Get<FString>(), *GetDebugFileName(Input, Options, TEXT("PlatformHash.txt")), FFileHelper::EEncodingOptions::ForceAnsi);
}
}
FFileHelper::SaveStringToFile(Output.OutputHash.ToString(), *GetDebugFileName(Input, Options, TEXT("OutputHash.txt")), FFileHelper::EEncodingOptions::ForceAnsi);
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::Diagnostics))
{
FString Merged;
for (const FShaderCompilerError& Diag : Output.Errors)
{
Merged += Diag.GetErrorStringWithLineMarker() + "\n";
}
if (!Merged.IsEmpty())
{
FFileHelper::SaveStringToFile(Merged, *GetDebugFileName(Input, Options, TEXT("Diagnostics.txt")), FFileHelper::EEncodingOptions::ForceAnsi);
}
}
// delete old DebugHash_* files so we don't clutter the debug info folder (these change every time the deadstripped source code changes)
IFileManager::Get().IterateDirectory(*Input.DumpDebugInfoPath, [](const TCHAR* FilenameOrDirectory, bool bIsDirectory)
{
if (!bIsDirectory)
{
FStringView Filename = FPathViews::GetCleanFilename(FilenameOrDirectory);
if (Filename.StartsWith(GetShaderSourceDebugHashPrefixWide()))
{
IFileManager::Get().Delete(FilenameOrDirectory);
}
}
return true;
});
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::InputHash) || Output.CompileTime > 0.0f)
{
// If compile time was > 0, this was the copy of the job that actually compiled; we write an empty file with the shader hash in it so it can be
// found easily using the ShaderHash comment printed on the first line of the stripped source code.
// We don't do this for cache hits or duplicate jobs unless explicitly requested (via the InputHash debug info flags), so that _all_ debug artifacts
// (including those only generated by the compile process) are available in the folder containing this file.
FString InputHashStr = LexToString(Input.Hash);
FFileHelper::SaveStringToFile(FStringView(), *GetDebugFileName(Input, Options, GetShaderSourceDebugHashPrefixWide().GetData(), *InputHashStr));
}
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::ShaderCodeBinary))
{
FString ShaderCodeFileName = *GetDebugFileName(Input, Options, TEXT("ShaderCode.bin"));
if (Output.ShaderCode.IsCompressed())
{
// always output decompressed code as it's slightly more useful for A/B comparisons
TArray<uint8> DecompressedCode;
DecompressedCode.SetNum(Output.ShaderCode.GetUncompressedSize());
bool bSucceed = FCompression::UncompressMemory(NAME_Oodle, DecompressedCode.GetData(), DecompressedCode.Num(), Output.ShaderCode.GetReadView().GetData(), Output.ShaderCode.GetShaderCodeSize());
FFileHelper::SaveArrayToFile(DecompressedCode, *ShaderCodeFileName);
}
else
{
FFileHelper::SaveArrayToFile(Output.ShaderCode.GetReadView(), *ShaderCodeFileName);
}
}
for (const FDebugShaderDataOptions::FAdditionalOutput& AdditionalOutput : Options.AdditionalOutputs)
{
FFileHelper::SaveStringToFile(AdditionalOutput.Data, *GetDebugFileName(Input, Options, AdditionalOutput.BaseFileName), FFileHelper::EEncodingOptions::ForceAnsi);
}
}
static const TCHAR* Base64EnvBegin = TEXT("/* BASE64_ENV\n");
static const int32 Base64EnvBeginLen = FCString::Strlen(Base64EnvBegin);
static const TCHAR* Base64EnvEnd = TEXT("\nBASE64_ENV */\n");
FString SerializeEnvironmentToBase64(const FShaderCompilerEnvironment& Env)
{
// deprecated
static FString Empty;
return Empty;
}
void SerializeEnvironmentFromBase64(FShaderCompilerEnvironment& Env, const FString& DebugShaderSource)
{
// deprecated
}
FString GetDebugShaderContents(const FShaderCompilerInput& Input, FStringView PreprocessedSource, const FDebugShaderDataOptions& Options, const TCHAR* Suffix)
{
// Debug dump occurs in the cook process, so we need to merge the env in Input.Environment with the shared env (this is done in the compile step as well)
FShaderCompilerEnvironment MergedEnvironment(Input.Environment);
if (IsValidRef(Input.SharedEnvironment))
{
MergedEnvironment.Merge(*Input.SharedEnvironment);
}
FString Contents = MergedEnvironment.GetDefinitionsAsCommentedCode();
if (Options.AppendPreSource)
{
Contents += Options.AppendPreSource();
}
Contents += PreprocessedSource;
if (Options.AppendPostSource)
{
Contents += Options.AppendPostSource();
}
Contents += TEXT("\n");
if (!Input.DebugDescription.IsEmpty())
{
Contents += TEXT("//");
Contents += Input.DebugDescription;
Contents += TEXT("\n");
}
return Contents;
}
}
void DumpDebugShaderText(const FShaderCompilerInput& Input, const FString& InSource, const FString& FileExtension)
{
FTCHARToUTF8 StringConverter(InSource.GetCharArray().GetData(), InSource.Len());
// Provide mutable container to pass string to FArchive inside inner function
TArray<ANSICHAR> SourceAnsi;
SourceAnsi.SetNum(InSource.Len() + 1);
FCStringAnsi::Strncpy(SourceAnsi.GetData(), (ANSICHAR*)StringConverter.Get(), SourceAnsi.Num());
// Forward temporary container to primary function
DumpDebugShaderText(Input, SourceAnsi.GetData(), InSource.Len(), FileExtension);
}
void DumpDebugShaderText(const FShaderCompilerInput& Input, ANSICHAR* InSource, int32 InSourceLength, const FString& FileExtension)
{
DumpDebugShaderBinary(Input, InSource, InSourceLength * sizeof(ANSICHAR), FileExtension);
}
void DumpDebugShaderText(const FShaderCompilerInput& Input, ANSICHAR* InSource, int32 InSourceLength, const FString& FileName, const FString& FileExtension)
{
DumpDebugShaderBinary(Input, InSource, InSourceLength * sizeof(ANSICHAR), FileName, FileExtension);
}
void DumpDebugShaderBinary(const FShaderCompilerInput& Input, void* InData, int32 InDataByteSize, const FString& FileExtension)
{
if (InData != nullptr && InDataByteSize > 0 && !FileExtension.IsEmpty())
{
const FString Filename = Input.DumpDebugInfoPath / FPaths::GetBaseFilename(Input.GetSourceFilename()) + TEXT(".") + FileExtension;
if (TUniquePtr<FArchive> FileWriter = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*Filename)))
{
FileWriter->Serialize(InData, InDataByteSize);
FileWriter->Close();
}
}
}
void DumpDebugShaderBinary(const FShaderCompilerInput& Input, void* InData, int32 InDataByteSize, const FString& FileName, const FString& FileExtension)
{
if (InData != nullptr && InDataByteSize > 0 && !FileExtension.IsEmpty())
{
const FString Filename = Input.DumpDebugInfoPath / FileName + TEXT(".") + FileExtension;
if (TUniquePtr<FArchive> FileWriter = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*Filename)))
{
FileWriter->Serialize(InData, InDataByteSize);
FileWriter->Close();
}
}
}
static void DumpDebugShaderDisassembled(const FShaderCompilerInput& Input, CrossCompiler::EShaderConductorIR Language, void* InData, int32 InDataByteSize, const FString& FileExtension)
{
if (InData != nullptr && InDataByteSize > 0 && !FileExtension.IsEmpty())
{
TArray<ANSICHAR> AssemblyText;
if (CrossCompiler::FShaderConductorContext::Disassemble(Language, InData, InDataByteSize, AssemblyText))
{
// Assembly text contains NUL terminator, so text lenght is |array|-1
DumpDebugShaderText(Input, AssemblyText.GetData(), AssemblyText.Num() - 1, FileExtension);
}
}
}
void DumpDebugShaderDisassembledSpirv(const FShaderCompilerInput& Input, void* InData, int32 InDataByteSize, const FString& FileExtension)
{
DumpDebugShaderDisassembled(Input, CrossCompiler::EShaderConductorIR::Spirv, InData, InDataByteSize, FileExtension);
}
void DumpDebugShaderDisassembledDxil(const FShaderCompilerInput& Input, void* InData, int32 InDataByteSize, const FString& FileExtension)
{
DumpDebugShaderDisassembled(Input, CrossCompiler::EShaderConductorIR::Dxil, InData, InDataByteSize, FileExtension);
}
namespace CrossCompiler
{
/**
* Parse an error emitted by the HLSL cross-compiler.
* @param OutErrors - Array into which compiler errors may be added.
* @param InLine - A line from the compile log.
*/
void ParseHlslccError(TArray<FShaderCompilerError>& OutErrors, const FString& InLine, bool bUseAbsolutePaths)
{
const TCHAR* p = *InLine;
FShaderCompilerError& Error = OutErrors.AddDefaulted_GetRef();
// Copy the filename.
while (*p && *p != TEXT('('))
{
Error.ErrorVirtualFilePath += (*p++);
}
if (!bUseAbsolutePaths)
{
Error.ErrorVirtualFilePath = ParseVirtualShaderFilename(Error.ErrorVirtualFilePath);
}
p++;
// Parse the line number.
int32 LineNumber = 0;
while (*p && *p >= TEXT('0') && *p <= TEXT('9'))
{
LineNumber = 10 * LineNumber + (*p++ - TEXT('0'));
}
Error.ErrorLineString = *FString::Printf(TEXT("%d"), LineNumber);
// Skip to the warning message.
while (*p && (*p == TEXT(')') || *p == TEXT(':') || *p == TEXT(' ') || *p == TEXT('\t')))
{
p++;
}
Error.StrippedErrorMessage = p;
}
/** Map shader frequency -> string for messages. */
static const TCHAR* FrequencyStringTable[] =
{
TEXT("Vertex"),
TEXT("Mesh"),
TEXT("Amplification"),
TEXT("Pixel"),
TEXT("Geometry"),
TEXT("Compute"),
TEXT("RayGen"),
TEXT("RayMiss"),
TEXT("RayHitGroup"),
TEXT("RayCallable"),
TEXT("WorkGraphRoot"),
TEXT("WorkGraphComputeNode"),
};
/** Compile time check to verify that the GL mapping tables are up-to-date. */
static_assert(SF_NumFrequencies == UE_ARRAY_COUNT(FrequencyStringTable), "NumFrequencies changed. Please update tables.");
const TCHAR* GetFrequencyName(EShaderFrequency Frequency)
{
check((int32)Frequency >= 0 && Frequency < SF_NumFrequencies);
return FrequencyStringTable[Frequency];
}
FHlslccHeader::FHlslccHeader() :
Name(TEXT(""))
{
NumThreads[0] = NumThreads[1] = NumThreads[2] = 0;
}
bool FHlslccHeader::Read(const ANSICHAR*& ShaderSource, int32 SourceLen)
{
#define DEF_PREFIX_STR(Str) \
static const ANSICHAR* Str##Prefix = "// @" #Str ": "; \
static const int32 Str##PrefixLen = FCStringAnsi::Strlen(Str##Prefix)
DEF_PREFIX_STR(Inputs);
DEF_PREFIX_STR(Outputs);
DEF_PREFIX_STR(UniformBlocks);
DEF_PREFIX_STR(Uniforms);
DEF_PREFIX_STR(PackedGlobals);
DEF_PREFIX_STR(PackedUB);
DEF_PREFIX_STR(PackedUBCopies);
DEF_PREFIX_STR(PackedUBGlobalCopies);
DEF_PREFIX_STR(Samplers);
DEF_PREFIX_STR(UAVs);
DEF_PREFIX_STR(SamplerStates);
DEF_PREFIX_STR(AccelerationStructures);
DEF_PREFIX_STR(NumThreads);
#undef DEF_PREFIX_STR
// Skip any comments that come before the signature.
while (FCStringAnsi::Strncmp(ShaderSource, "//", 2) == 0 &&
FCStringAnsi::Strncmp(ShaderSource + 2, " !", 2) != 0 &&
FCStringAnsi::Strncmp(ShaderSource + 2, " @", 2) != 0)
{
ShaderSource += 2;
while (*ShaderSource && *ShaderSource++ != '\n')
{
// Do nothing
}
}
// Read shader name if any
if (FCStringAnsi::Strncmp(ShaderSource, "// !", 4) == 0)
{
ShaderSource += 4;
while (*ShaderSource && *ShaderSource != '\n')
{
Name += (TCHAR)*ShaderSource;
++ShaderSource;
}
if (*ShaderSource == '\n')
{
++ShaderSource;
}
}
// Skip any comments that come before the signature.
while (FCStringAnsi::Strncmp(ShaderSource, "//", 2) == 0 &&
FCStringAnsi::Strncmp(ShaderSource + 2, " @", 2) != 0)
{
ShaderSource += 2;
while (*ShaderSource && *ShaderSource++ != '\n')
{
// Do nothing
}
}
if (FCStringAnsi::Strncmp(ShaderSource, InputsPrefix, InputsPrefixLen) == 0)
{
ShaderSource += InputsPrefixLen;
if (!ReadInOut(ShaderSource, Inputs))
{
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, OutputsPrefix, OutputsPrefixLen) == 0)
{
ShaderSource += OutputsPrefixLen;
if (!ReadInOut(ShaderSource, Outputs))
{
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, UniformBlocksPrefix, UniformBlocksPrefixLen) == 0)
{
ShaderSource += UniformBlocksPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FAttribute UniformBlock;
if (!ParseIdentifier(ShaderSource, UniformBlock.Name))
{
return false;
}
if (!Match(ShaderSource, '('))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, UniformBlock.Index))
{
return false;
}
if (!Match(ShaderSource, ')'))
{
return false;
}
UniformBlocks.Add(UniformBlock);
if (Match(ShaderSource, '\n'))
{
break;
}
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, UniformsPrefix, UniformsPrefixLen) == 0)
{
// @todo-mobile: Will we ever need to support this code path?
check(0);
return false;
/*
ShaderSource += UniformsPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
uint16 ArrayIndex = 0;
uint16 Offset = 0;
uint16 NumComponents = 0;
FString ParameterName = ParseIdentifier(ShaderSource);
verify(ParameterName.Len() > 0);
verify(Match(ShaderSource, '('));
ArrayIndex = ParseNumber(ShaderSource);
verify(Match(ShaderSource, ':'));
Offset = ParseNumber(ShaderSource);
verify(Match(ShaderSource, ':'));
NumComponents = ParseNumber(ShaderSource);
verify(Match(ShaderSource, ')'));
ParameterMap.AddParameterAllocation(
*ParameterName,
ArrayIndex,
Offset * BytesPerComponent,
NumComponents * BytesPerComponent
);
if (ArrayIndex < OGL_NUM_PACKED_UNIFORM_ARRAYS)
{
PackedUniformSize[ArrayIndex] = FMath::Max<uint16>(
PackedUniformSize[ArrayIndex],
BytesPerComponent * (Offset + NumComponents)
);
}
// Skip the comma.
if (Match(ShaderSource, '\n'))
{
break;
}
verify(Match(ShaderSource, ','));
}
Match(ShaderSource, '\n');
*/
}
// @PackedGlobals: Global0(h:0,1),Global1(h:4,1),Global2(h:8,1)
if (FCStringAnsi::Strncmp(ShaderSource, PackedGlobalsPrefix, PackedGlobalsPrefixLen) == 0)
{
ShaderSource += PackedGlobalsPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FPackedGlobal PackedGlobal;
if (!ParseIdentifier(ShaderSource, PackedGlobal.Name))
{
return false;
}
if (!Match(ShaderSource, '('))
{
return false;
}
PackedGlobal.PackedType = *ShaderSource++;
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedGlobal.Offset))
{
return false;
}
if (!Match(ShaderSource, ','))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedGlobal.Count))
{
return false;
}
if (!Match(ShaderSource, ')'))
{
return false;
}
PackedGlobals.Add(PackedGlobal);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
}
// Packed Uniform Buffers (Multiple lines)
// @PackedUB: CBuffer(0): CBMember0(0,1),CBMember1(1,1)
while (FCStringAnsi::Strncmp(ShaderSource, PackedUBPrefix, PackedUBPrefixLen) == 0)
{
ShaderSource += PackedUBPrefixLen;
FPackedUB PackedUB;
if (!ParseIdentifier(ShaderSource, PackedUB.Attribute.Name))
{
return false;
}
if (!Match(ShaderSource, '('))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedUB.Attribute.Index))
{
return false;
}
if (!Match(ShaderSource, ')'))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!Match(ShaderSource, ' '))
{
return false;
}
while (*ShaderSource && *ShaderSource != '\n')
{
FPackedUB::FMember Member;
ParseIdentifier(ShaderSource, Member.Name);
if (!Match(ShaderSource, '('))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, Member.Offset))
{
return false;
}
if (!Match(ShaderSource, ','))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, Member.Count))
{
return false;
}
if (!Match(ShaderSource, ')'))
{
return false;
}
PackedUB.Members.Add(Member);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
PackedUBs.Add(PackedUB);
}
// @PackedUBCopies: 0:0-0:h:0:1,0:1-0:h:4:1,1:0-1:h:0:1
if (FCStringAnsi::Strncmp(ShaderSource, PackedUBCopiesPrefix, PackedUBCopiesPrefixLen) == 0)
{
ShaderSource += PackedUBCopiesPrefixLen;
if (!ReadCopies(ShaderSource, false, PackedUBCopies))
{
return false;
}
}
// @PackedUBGlobalCopies: 0:0-h:12:1,0:1-h:16:1,1:0-h:20:1
if (FCStringAnsi::Strncmp(ShaderSource, PackedUBGlobalCopiesPrefix, PackedUBGlobalCopiesPrefixLen) == 0)
{
ShaderSource += PackedUBGlobalCopiesPrefixLen;
if (!ReadCopies(ShaderSource, true, PackedUBGlobalCopies))
{
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, SamplersPrefix, SamplersPrefixLen) == 0)
{
ShaderSource += SamplersPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FSampler Sampler;
if (!ParseIdentifier(ShaderSource, Sampler.Name))
{
return false;
}
if (!Match(ShaderSource, '('))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, Sampler.Offset))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, Sampler.Count))
{
return false;
}
if (Match(ShaderSource, '['))
{
// Sampler States
do
{
FString SamplerState;
if (!ParseIdentifier(ShaderSource, SamplerState))
{
return false;
}
Sampler.SamplerStates.Add(SamplerState);
}
while (Match(ShaderSource, ','));
if (!Match(ShaderSource, ']'))
{
return false;
}
}
if (!Match(ShaderSource, ')'))
{
return false;
}
Samplers.Add(Sampler);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, UAVsPrefix, UAVsPrefixLen) == 0)
{
ShaderSource += UAVsPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FUAV UAV;
if (!ParseIdentifier(ShaderSource, UAV.Name))
{
return false;
}
if (!Match(ShaderSource, '('))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, UAV.Offset))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, UAV.Count))
{
return false;
}
if (!Match(ShaderSource, ')'))
{
return false;
}
UAVs.Add(UAV);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, SamplerStatesPrefix, SamplerStatesPrefixLen) == 0)
{
ShaderSource += SamplerStatesPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FAttribute SamplerState;
if (!ParseIntegerNumber(ShaderSource, SamplerState.Index))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIdentifier(ShaderSource, SamplerState.Name))
{
return false;
}
SamplerStates.Add(SamplerState);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, AccelerationStructuresPrefix, AccelerationStructuresPrefixLen) == 0)
{
ShaderSource += AccelerationStructuresPrefixLen;
while (*ShaderSource && *ShaderSource != '\n')
{
FAccelerationStructure AccelerationStructure;
if (!ParseIntegerNumber(ShaderSource, AccelerationStructure.Offset))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIdentifier(ShaderSource, AccelerationStructure.Name))
{
return false;
}
AccelerationStructures.Add(AccelerationStructure);
if (Match(ShaderSource, '\n'))
{
break;
}
if (Match(ShaderSource, ','))
{
continue;
}
return false;
}
}
if (FCStringAnsi::Strncmp(ShaderSource, NumThreadsPrefix, NumThreadsPrefixLen) == 0)
{
ShaderSource += NumThreadsPrefixLen;
if (!ParseIntegerNumber(ShaderSource, NumThreads[0]))
{
return false;
}
if (!Match(ShaderSource, ','))
{
return false;
}
if (!Match(ShaderSource, ' '))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, NumThreads[1]))
{
return false;
}
if (!Match(ShaderSource, ','))
{
return false;
}
if (!Match(ShaderSource, ' '))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, NumThreads[2]))
{
return false;
}
if (!Match(ShaderSource, '\n'))
{
return false;
}
}
return ParseCustomHeaderEntries(ShaderSource);
}
bool FHlslccHeader::ReadCopies(const ANSICHAR*& ShaderSource, bool bGlobals, TArray<FPackedUBCopy>& OutCopies)
{
while (*ShaderSource && *ShaderSource != '\n')
{
FPackedUBCopy PackedUBCopy;
PackedUBCopy.DestUB = 0;
if (!ParseIntegerNumber(ShaderSource, PackedUBCopy.SourceUB))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedUBCopy.SourceOffset))
{
return false;
}
if (!Match(ShaderSource, '-'))
{
return false;
}
if (!bGlobals)
{
if (!ParseIntegerNumber(ShaderSource, PackedUBCopy.DestUB))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
}
PackedUBCopy.DestPackedType = *ShaderSource++;
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedUBCopy.DestOffset))
{
return false;
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIntegerNumber(ShaderSource, PackedUBCopy.Count))
{
return false;
}
OutCopies.Add(PackedUBCopy);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
break;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
return true;
}
bool FHlslccHeader::ReadInOut(const ANSICHAR*& ShaderSource, TArray<FInOut>& OutAttributes)
{
while (*ShaderSource && *ShaderSource != '\n')
{
FInOut Attribute;
if (!ParseIdentifier(ShaderSource, Attribute.Type))
{
return false;
}
if (Match(ShaderSource, '['))
{
if (!ParseIntegerNumber(ShaderSource, Attribute.ArrayCount))
{
return false;
}
if (!Match(ShaderSource, ']'))
{
return false;
}
}
else
{
Attribute.ArrayCount = 0;
}
if (Match(ShaderSource, ';'))
{
if (!ParseSignedNumber(ShaderSource, Attribute.Index))
{
return false;
}
}
if (!Match(ShaderSource, ':'))
{
return false;
}
if (!ParseIdentifier(ShaderSource, Attribute.Name))
{
return false;
}
// Optional array suffix
if (Match(ShaderSource, '['))
{
Attribute.Name += '[';
while (*ShaderSource)
{
Attribute.Name += *ShaderSource;
if (Match(ShaderSource, ']'))
{
break;
}
++ShaderSource;
}
}
OutAttributes.Add(Attribute);
// Break if EOL
if (Match(ShaderSource, '\n'))
{
return true;
}
// Has to be a comma!
if (Match(ShaderSource, ','))
{
continue;
}
//#todo-rco: Need a log here
//UE_LOG(ShaderCompilerCommon, Warning, TEXT("Invalid char '%c'"), *ShaderSource);
return false;
}
// Last character must be EOL
return Match(ShaderSource, '\n');
}
} // namespace CrossCompiler