You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Doesn't produce bit identical preprocessed output, as it compacts whitespace from compound identifiers before testing them, whereas the original implementation compacts whitespace after matching. Compacting first makes testing more efficient and simpler, since it doesn't need to take into account whitespace. For A/B validation, I did add temporary debug code that stored and reverted un-matched identifiers, mimicking the old behavior, and it was identical across the board. #rnx #rb dan.elksnitis chris.waters jason.nadro [CL 28313553 by jason hoerner in ue5-main branch]
3398 lines
99 KiB
C++
3398 lines
99 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ShaderCompilerCommon.h"
|
|
#include "ShaderParameterParser.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 "String/RemoveFrom.h"
|
|
#include "ShaderPreprocessTypes.h"
|
|
#include "ShaderSymbolExport.h"
|
|
#include "ShaderMinifier.h"
|
|
#include "Algo/Sort.h"
|
|
|
|
static TAutoConsoleVariable<bool> CVarShaderCompilerCleanupUniformBufferCodeNew(
|
|
TEXT("r.ShaderCompiler.CleanupUniformBufferCodeNew"),
|
|
true,
|
|
TEXT("Run new optimized version of CleanupUniformBufferCode. Temporary chicken switch in case there are issues, before we remove old version."),
|
|
ECVF_Default
|
|
);
|
|
|
|
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));
|
|
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());
|
|
}
|
|
}
|
|
|
|
|
|
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.FindParameterAllocation(Name))
|
|
{
|
|
const EShaderParameterType ParameterType = Allocation->Type;
|
|
const bool bBindlessParameter = (ParameterType == EShaderParameterType::BindlessResourceIndex || ParameterType == EShaderParameterType::BindlessSamplerIndex);
|
|
|
|
// Force bindless "indices" to zero since they're not needed in SetResourcesFromTables
|
|
const uint16 BaseIndex = bBindlessParameter ? 0 : Allocation->BaseIndex;
|
|
|
|
ParameterMap.RemoveParameterAllocation(*Name);
|
|
|
|
uint16 UniformBufferIndex = INDEX_NONE;
|
|
uint16 UBBaseIndex, UBSize;
|
|
|
|
// Add the UB itself as a parameter if not there
|
|
FString UniformBufferName(Entry.GetUniformBufferName());
|
|
if (!ParameterMap.FindParameterAllocation(*UniformBufferName, UniformBufferIndex, UBBaseIndex, UBSize))
|
|
{
|
|
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_BUFFER_SRV:
|
|
OutSRT.ShaderResourceViewMap.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;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
// Deprecated version of function
|
|
bool BuildResourceTableMapping(
|
|
const TMap<FString, FResourceTableEntry>& ResourceTableMap,
|
|
const TMap<FString, FUniformBufferEntry>& UniformBufferMap,
|
|
TBitArray<>& UsedUniformBufferSlots,
|
|
FShaderParameterMap& ParameterMap,
|
|
FShaderCompilerResourceTable& OutSRT)
|
|
{
|
|
UE_LOG(LogShaders, Error, TEXT("Using unimplemented deprecated version of BuildResourceTableMapping -- use version that accepts FShaderResourceTableMap instead."));
|
|
return false;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool IsSpaceOrTabOrEOL(TCHAR Char)
|
|
{
|
|
return Char == ' ' || Char == '\t' || Char == '\n' || Char == '\r';
|
|
}
|
|
|
|
static const TCHAR* FindNextChar(const TCHAR* ReadStart, TCHAR SearchChar)
|
|
{
|
|
const TCHAR* SearchPtr = ReadStart;
|
|
while (*SearchPtr && *SearchPtr != SearchChar)
|
|
{
|
|
SearchPtr++;
|
|
}
|
|
return SearchPtr;
|
|
}
|
|
|
|
const TCHAR* FindNextWhitespace(const TCHAR* StringPtr)
|
|
{
|
|
while (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
|
|
{
|
|
StringPtr++;
|
|
}
|
|
|
|
if (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr))
|
|
{
|
|
return StringPtr;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
const TCHAR* FindNextNonWhitespace(const TCHAR* StringPtr)
|
|
{
|
|
while (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr))
|
|
{
|
|
StringPtr++;
|
|
}
|
|
|
|
if (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
|
|
{
|
|
return StringPtr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const TCHAR* FindPreviousNonWhitespace(const TCHAR* StringPtr)
|
|
{
|
|
do
|
|
{
|
|
StringPtr--;
|
|
} while (*StringPtr && IsSpaceOrTabOrEOL(*StringPtr));
|
|
|
|
if (*StringPtr && !IsSpaceOrTabOrEOL(*StringPtr))
|
|
{
|
|
return StringPtr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const TCHAR* FindMatchingBlock(const TCHAR* OpeningCharPtr, char OpenChar, char CloseChar)
|
|
{
|
|
const TCHAR* SearchPtr = OpeningCharPtr;
|
|
int32 Depth = 0;
|
|
|
|
while (*SearchPtr)
|
|
{
|
|
if (*SearchPtr == OpenChar)
|
|
{
|
|
Depth++;
|
|
}
|
|
else if (*SearchPtr == CloseChar)
|
|
{
|
|
if (Depth == 0)
|
|
{
|
|
return SearchPtr;
|
|
}
|
|
|
|
Depth--;
|
|
}
|
|
SearchPtr++;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
const TCHAR* FindMatchingClosingBrace(const TCHAR* OpeningCharPtr) { return FindMatchingBlock(OpeningCharPtr, '{', '}'); };
|
|
const TCHAR* FindMatchingClosingParenthesis(const TCHAR* OpeningCharPtr) { return FindMatchingBlock(OpeningCharPtr, '(', ')'); };
|
|
|
|
// See MSDN HLSL 'Symbol Name Restrictions' doc
|
|
inline bool IsValidHLSLIdentifierCharacter(TCHAR 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);
|
|
}
|
|
|
|
FStringView ParseHLSLSymbolName(const TCHAR* SearchString)
|
|
{
|
|
const TCHAR* SymbolNameStartPtr = FindNextNonWhitespace(SearchString);
|
|
check(SymbolNameStartPtr);
|
|
|
|
const TCHAR* SymbolNameEndPtr = SymbolNameStartPtr;
|
|
while (*SymbolNameEndPtr && IsValidHLSLIdentifierCharacter(*SymbolNameEndPtr))
|
|
{
|
|
SymbolNameEndPtr++;
|
|
}
|
|
|
|
return FStringView(SymbolNameStartPtr, SymbolNameEndPtr - SymbolNameStartPtr);
|
|
}
|
|
|
|
const TCHAR* ParseHLSLSymbolName(const TCHAR* SearchString, FString& SymbolName)
|
|
{
|
|
FStringView Result = ParseHLSLSymbolName(SearchString);
|
|
|
|
SymbolName = FString(Result);
|
|
|
|
return Result.GetData() + Result.Len();
|
|
}
|
|
|
|
const TCHAR* MatchStructMemberName(const FString& SymbolName, const TCHAR* SearchPtr)
|
|
{
|
|
for (int32 i = 0; i < SymbolName.Len(); i++)
|
|
{
|
|
if (*SearchPtr != SymbolName[i])
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
SearchPtr++;
|
|
|
|
if (i < SymbolName.Len() - 1)
|
|
{
|
|
// Skip whitespace within the struct member reference before the end
|
|
// eg 'View. ViewToClip'
|
|
while (IsSpaceOrTabOrEOL(*SearchPtr))
|
|
{
|
|
SearchPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only match whole symbol
|
|
if (IsValidHLSLIdentifierCharacter(*SearchPtr))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return SearchPtr;
|
|
}
|
|
|
|
EShaderParameterType UE::ShaderCompilerCommon::ParseParameterType(
|
|
FStringView InType,
|
|
TArrayView<const TCHAR* const> InExtraSRVTypes,
|
|
TArrayView<const TCHAR* const> InExtraUAVTypes)
|
|
{
|
|
return FShaderParameterParser::ParseParameterType(InType, InExtraSRVTypes, InExtraUAVTypes);
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
EShaderParameterType UE::ShaderCompilerCommon::ParseAndRemoveBindlessParameterPrefix(FStringView& InName)
|
|
{
|
|
return FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(InName);
|
|
}
|
|
|
|
EShaderParameterType UE::ShaderCompilerCommon::ParseAndRemoveBindlessParameterPrefix(FString& InName)
|
|
{
|
|
return FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(InName);
|
|
}
|
|
|
|
bool UE::ShaderCompilerCommon::RemoveBindlessParameterPrefix(FString& InName)
|
|
{
|
|
return FShaderParameterParser::RemoveBindlessParameterPrefix(InName);
|
|
}
|
|
|
|
bool UE::ShaderCompilerCommon::ValidatePackedResourceCounts(FShaderCompilerOutput& Output, const FShaderCodePackedResourceCounts& PackedResourceCounts)
|
|
{
|
|
if (Output.bSucceeded)
|
|
{
|
|
auto GetAllResourcesOfType = [&](EShaderParameterType InType)
|
|
{
|
|
const TArray<FString> 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 FString& Input, FString& OutMain, FString& OutAnyHit, FString& OutIntersection)
|
|
{
|
|
auto ParseEntry = [&Input](const TCHAR* Marker)
|
|
{
|
|
FString Result;
|
|
const int32 BeginIndex = Input.Find(Marker, ESearchCase::IgnoreCase, ESearchDir::FromStart);
|
|
if (BeginIndex != INDEX_NONE)
|
|
{
|
|
int32 EndIndex = Input.Find(TEXT(" "), ESearchCase::IgnoreCase, ESearchDir::FromStart, BeginIndex);
|
|
if (EndIndex == INDEX_NONE)
|
|
{
|
|
EndIndex = Input.Len() + 1;
|
|
}
|
|
const int32 MarkerLen = FCString::Strlen(Marker);
|
|
const int32 Count = EndIndex - BeginIndex;
|
|
Result = Input.Mid(BeginIndex + MarkerLen, Count - MarkerLen);
|
|
}
|
|
return Result;
|
|
};
|
|
|
|
OutMain = ParseEntry(TEXT("closesthit="));
|
|
OutAnyHit = ParseEntry(TEXT("anyhit="));
|
|
OutIntersection = ParseEntry(TEXT("intersection="));
|
|
|
|
// If complex hit group entry is not specified, assume a single verbatim entry point
|
|
if (OutMain.IsEmpty() && OutAnyHit.IsEmpty() && OutIntersection.IsEmpty())
|
|
{
|
|
OutMain = Input;
|
|
}
|
|
}
|
|
|
|
bool UE::ShaderCompilerCommon::RemoveDeadCode(FString& InOutPreprocessedShaderSource, TConstArrayView<FStringView> RequiredSymbols, TArray<FShaderCompilerError>& OutErrors)
|
|
{
|
|
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
|
|
|
|
UE::ShaderMinifier::FMinifiedShader Minified = UE::ShaderMinifier::Minify(InOutPreprocessedShaderSource, RequiredSymbols,
|
|
UE::ShaderMinifier::EMinifyShaderFlags::OutputCommentLines // Preserve comments that were left after preprocessing
|
|
| UE::ShaderMinifier::EMinifyShaderFlags::OutputLines // Emit #line directives
|
|
| ExtraFlags);
|
|
|
|
if (Minified.Success())
|
|
{
|
|
Swap(InOutPreprocessedShaderSource, Minified.Code);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OutErrors.Add(TEXT("warning: Shader minification failed."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool UE::ShaderCompilerCommon::RemoveDeadCode(FString& InOutPreprocessedShaderSource, const FString& EntryPoint, TArray<FShaderCompilerError>& OutErrors)
|
|
{
|
|
TArray<FStringView> RequiredSymbols;
|
|
|
|
FString EntryMain;
|
|
FString EntryAnyHit;
|
|
FString EntryIntersection;
|
|
UE::ShaderCompilerCommon::ParseRayTracingEntryPoint(EntryPoint, EntryMain, EntryAnyHit, EntryIntersection);
|
|
|
|
RequiredSymbols.Add(EntryMain);
|
|
|
|
if (!EntryAnyHit.IsEmpty())
|
|
{
|
|
RequiredSymbols.Add(EntryAnyHit);
|
|
}
|
|
|
|
if (!EntryIntersection.IsEmpty())
|
|
{
|
|
RequiredSymbols.Add(EntryIntersection);
|
|
}
|
|
|
|
return UE::ShaderCompilerCommon::RemoveDeadCode(InOutPreprocessedShaderSource, RequiredSymbols, OutErrors);
|
|
}
|
|
|
|
void HandleReflectedGlobalConstantBufferMember(
|
|
const FString& InMemberName,
|
|
uint32 ConstantBufferIndex,
|
|
int32 ReflectionOffset,
|
|
int32 ReflectionSize,
|
|
FShaderCompilerOutput& Output
|
|
)
|
|
{
|
|
FString MemberName = InMemberName;
|
|
const EShaderParameterType ParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(MemberName);
|
|
|
|
Output.ParameterMap.AddParameterAllocation(
|
|
*MemberName,
|
|
ConstantBufferIndex,
|
|
ReflectionOffset,
|
|
ReflectionSize,
|
|
ParameterType);
|
|
}
|
|
|
|
void HandleReflectedUniformBufferConstantBufferMember(
|
|
int32 UniformBufferSlot,
|
|
const FString& InMemberName,
|
|
int32 ReflectionOffset,
|
|
int32 ReflectionSize,
|
|
FShaderCompilerOutput& Output
|
|
)
|
|
{
|
|
FString MemberName = InMemberName;
|
|
const EShaderParameterType ParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(MemberName);
|
|
|
|
if (ParameterType != EShaderParameterType::LooseData)
|
|
{
|
|
Output.ParameterMap.AddParameterAllocation(
|
|
*MemberName,
|
|
UniformBufferSlot,
|
|
ReflectionOffset,
|
|
1,
|
|
ParameterType
|
|
);
|
|
}
|
|
}
|
|
|
|
void HandleReflectedRootConstantBufferMember(
|
|
const FShaderCompilerInput& Input,
|
|
const FShaderParameterParser& ShaderParameterParser,
|
|
const FString& MemberName,
|
|
int32 ReflectionOffset,
|
|
int32 ReflectionSize,
|
|
FShaderCompilerOutput& Output
|
|
)
|
|
{
|
|
ShaderParameterParser.ValidateShaderParameterType(Input, MemberName, ReflectionOffset, ReflectionSize, Output);
|
|
|
|
HandleReflectedUniformBufferConstantBufferMember(
|
|
FShaderParametersMetadata::kRootCBufferBindingIndex,
|
|
MemberName,
|
|
ReflectionOffset,
|
|
ReflectionSize,
|
|
Output
|
|
);
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void RemoveUniformBuffersFromSource(const FShaderCompilerEnvironment& Environment, FString& PreprocessedShaderSource)
|
|
{
|
|
CleanupUniformBufferCode(Environment, PreprocessedShaderSource);
|
|
}
|
|
|
|
struct FUniformBufferMemberInfo
|
|
{
|
|
// eg View.WorldToClip
|
|
FString NameAsStructMember;
|
|
// eg View_WorldToClip
|
|
FString GlobalName;
|
|
};
|
|
|
|
struct FUniformBufferInfo
|
|
{
|
|
int32 DefinitionEndOffset;
|
|
TArray<FUniformBufferMemberInfo> Members;
|
|
};
|
|
|
|
struct FUniformBufferMemberInfoNew
|
|
{
|
|
// eg View.WorldToClip
|
|
FStringView NameAsStructMember;
|
|
// eg View_WorldToClip
|
|
FStringView 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
|
|
{
|
|
FStringView 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
|
|
};
|
|
|
|
static void FillUniformBufferDefinition(TStringBuilder<128>& Builder, const TCHAR* Start, const TCHAR* End)
|
|
{
|
|
Builder.Reset();
|
|
|
|
const TCHAR* Cursor = Start;
|
|
|
|
while (Cursor != End)
|
|
{
|
|
if (!IsSpaceOrTabOrEOL(*Cursor))
|
|
{
|
|
Builder.AppendChar(*Cursor);
|
|
}
|
|
|
|
Cursor++;
|
|
}
|
|
}
|
|
|
|
const TCHAR* ParseUniformBufferDefinition(const TCHAR* SourceStart, const TCHAR* ReadStart, TMap<FStringView, FUniformBufferInfo>& UniformBufferInfos)
|
|
{
|
|
const FStringView UniformBufferName(ParseHLSLSymbolName(ReadStart));
|
|
|
|
const TCHAR* OpeningBrace = FindNextChar(ReadStart, '{');
|
|
const TCHAR* ClosingBrace = FindMatchingClosingBrace(OpeningBrace + 1);
|
|
|
|
const TCHAR* CurrentParseStart = OpeningBrace + 1;
|
|
const TCHAR* NextSemicolon = FindNextChar(CurrentParseStart, ';');
|
|
|
|
TStringBuilder<128> StructName;
|
|
TStringBuilder<128> GlobalName;
|
|
|
|
while (NextSemicolon < ClosingBrace)
|
|
{
|
|
const TCHAR* NextSeparator = FindNextChar(CurrentParseStart, '=');
|
|
if (NextSeparator < NextSemicolon)
|
|
{
|
|
const TCHAR* StructStart = CurrentParseStart;
|
|
const TCHAR* StructEnd = NextSeparator - 1;
|
|
|
|
const TCHAR* GlobalStart = NextSeparator + 1;
|
|
const TCHAR* GlobalEnd = NextSemicolon - 1;
|
|
|
|
FillUniformBufferDefinition(StructName, StructStart, StructEnd);
|
|
FillUniformBufferDefinition(GlobalName, GlobalStart, GlobalEnd);
|
|
|
|
// Avoid unnecessary conversions
|
|
if (StructName.Len() == GlobalName.Len() && FCString::Strncmp(*StructName, *GlobalName, StructName.Len()) != 0)
|
|
{
|
|
FUniformBufferMemberInfo NewMemberInfo;
|
|
NewMemberInfo.NameAsStructMember = StructName;
|
|
NewMemberInfo.GlobalName = GlobalName;
|
|
|
|
// Add this member to the map
|
|
if (FUniformBufferInfo* Info = UniformBufferInfos.Find(UniformBufferName))
|
|
{
|
|
Info->Members.Add(MoveTemp(NewMemberInfo));
|
|
}
|
|
else
|
|
{
|
|
FUniformBufferInfo NewInfo;
|
|
NewInfo.DefinitionEndOffset = ClosingBrace - SourceStart;
|
|
NewInfo.Members.Add(MoveTemp(NewMemberInfo));
|
|
|
|
UniformBufferInfos.Emplace(UniformBufferName, MoveTemp(NewInfo));
|
|
}
|
|
}
|
|
}
|
|
|
|
CurrentParseStart = NextSemicolon + 1;
|
|
NextSemicolon = FindNextChar(CurrentParseStart, ';');
|
|
}
|
|
|
|
const TCHAR* EndPtr = ClosingBrace;
|
|
|
|
// Skip to the end of the UniformBuffer
|
|
while (*EndPtr && *EndPtr != ';')
|
|
{
|
|
EndPtr++;
|
|
}
|
|
|
|
return EndPtr;
|
|
}
|
|
|
|
inline bool IsValidSymbolStart(const TCHAR* SearchPtr, int32 UniformBufferNameLen)
|
|
{
|
|
// Make sure this isn't the end of another symbol or a sub-member of a different struct
|
|
// ex: "OpaqueBasePass.Substrate.MaxBytesPerPixel" when matching "Substrate"
|
|
if (IsValidHLSLIdentifierCharacter(*(SearchPtr - 1)) || *(SearchPtr - 1) == '.')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Avoid lines like '#line 1 "/Engine/Generated/UniformBuffers/View.ush"'
|
|
if (FCString::Strncmp(SearchPtr + UniformBufferNameLen, TEXT("ush"), 3) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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 TCHAR* CompactCompoundIdentifier(TCHAR* Start, TCHAR* End)
|
|
{
|
|
// Find first whitespace in the identifier, if present
|
|
TCHAR* 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.
|
|
TCHAR* 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 TCHAR* ParseUniformBufferDefinitionNew(const TCHAR* 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(ReadStart);
|
|
check(Info.Name.Len() < 64);
|
|
|
|
const TCHAR* OpeningBrace = FindNextChar(ReadStart, '{');
|
|
const TCHAR* ClosingBrace = FindMatchingClosingBrace(OpeningBrace + 1);
|
|
|
|
const TCHAR* CurrentParseStart = OpeningBrace + 1;
|
|
const TCHAR* NextSemicolon = FindNextChar(CurrentParseStart, ';');
|
|
|
|
while (NextSemicolon < ClosingBrace)
|
|
{
|
|
const TCHAR* NextSeparator = FindNextChar(CurrentParseStart, '=');
|
|
if (NextSeparator < NextSemicolon)
|
|
{
|
|
const TCHAR* StructStart = CurrentParseStart;
|
|
const TCHAR* StructEnd = NextSeparator - 1;
|
|
|
|
const TCHAR* GlobalStart = NextSeparator + 1;
|
|
const TCHAR* GlobalEnd = NextSemicolon - 1;
|
|
|
|
while (IsSpaceOrTabOrEOL(*StructStart))
|
|
{
|
|
StructStart++;
|
|
}
|
|
while (IsSpaceOrTabOrEOL(*GlobalStart))
|
|
{
|
|
GlobalStart++;
|
|
}
|
|
|
|
StructEnd = CompactCompoundIdentifier(const_cast<TCHAR*>(StructStart), const_cast<TCHAR*>(StructEnd));
|
|
GlobalEnd = CompactCompoundIdentifier(const_cast<TCHAR*>(GlobalStart), const_cast<TCHAR*>(GlobalEnd));
|
|
|
|
FStringView StructName(StructStart, StructEnd - StructStart);
|
|
FStringView GlobalName(GlobalStart, GlobalEnd - GlobalStart);
|
|
|
|
// Avoid unnecessary conversions
|
|
if (StructName.Len() == GlobalName.Len() && FCString::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 TCHAR* 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 TCHAR* Identifier; // Start of identifier
|
|
const TCHAR* IdentifierEnd; // End of entire identifier
|
|
const TCHAR* 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 TCHAR*& Search, const uint64 RootIdentifierFilter[64], FCompoundIdentifierResult& OutResult)
|
|
{
|
|
const TCHAR* 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)
|
|
SearchChar++;
|
|
while (*SearchChar && *SearchChar != TEXT('\"'))
|
|
{
|
|
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 == TEXT('/'))
|
|
{
|
|
// 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] == TEXT('*') && 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) == TEXT('*'))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
TCHAR* FindNextUniformBufferDefinition(TCHAR* SearchPtr, TCHAR* SourceStart, const TCHAR* UniformBufferStructIdentifier, int32 StructIdentifierLen)
|
|
{
|
|
while (SearchPtr)
|
|
{
|
|
SearchPtr = FCString::Strstr(SearchPtr, UniformBufferStructIdentifier);
|
|
|
|
if (SearchPtr)
|
|
{
|
|
if (SearchPtr > SourceStart && IsSpaceOrTabOrEOL(*(SearchPtr - 1)) && IsSpaceOrTabOrEOL(*(SearchPtr + StructIdentifierLen)))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
SearchPtr = SearchPtr + 1;
|
|
}
|
|
}
|
|
}
|
|
return SearchPtr;
|
|
}
|
|
|
|
const TCHAR* FindPreviousDot(const TCHAR* SearchPtr, const TCHAR* SearchMin)
|
|
{
|
|
while ((SearchPtr > SearchMin) && (*SearchPtr != TEXT('.')))
|
|
{
|
|
SearchPtr--;
|
|
}
|
|
return SearchPtr;
|
|
}
|
|
|
|
// Unlike the original version, this parses uniform buffers and replaces symbols in a single pass.
|
|
void CleanupUniformBufferCodeNew(const FShaderCompilerEnvironment& Environment, FString& PreprocessedShaderSource)
|
|
{
|
|
TArray<FUniformBufferInfoNew> UniformBufferInfos;
|
|
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());
|
|
memset(UniformBuffersByLength, 0xff, sizeof(UniformBuffersByLength));
|
|
|
|
const TCHAR* UniformBufferStructIdentifier = TEXT("UniformBuffer");
|
|
const int32 StructIdentifierLen = FCString::Strlen(UniformBufferStructIdentifier);
|
|
|
|
TCHAR* SourceStart = &PreprocessedShaderSource[0];
|
|
TCHAR* SearchPtr = SourceStart;
|
|
TCHAR* EndOfPreviousUniformBuffer = SourceStart;
|
|
bool bUniformBufferFound;
|
|
|
|
do
|
|
{
|
|
// Find the next uniform buffer definition
|
|
SearchPtr = FindNextUniformBufferDefinition(SearchPtr, SourceStart, UniformBufferStructIdentifier, StructIdentifierLen);
|
|
|
|
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 TCHAR* ParsePtr = EndOfPreviousUniformBuffer;
|
|
|
|
FCompoundIdentifierResult Result;
|
|
while (FindNextCompoundIdentifier(ParsePtr, UniformBufferFilter, Result))
|
|
{
|
|
// Check if the identifier corresponds to a uniform buffer
|
|
FStringView 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<TCHAR*>(Result.Identifier), const_cast<TCHAR*>(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))
|
|
{
|
|
FStringView 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 TCHAR* GlobalNameStart = GetData(Info.Members[MemberIndex].GlobalName);
|
|
TCHAR* IdentifierStart = const_cast<TCHAR*>(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 TCHAR* ConstStructEndPtr = ParseUniformBufferDefinitionNew(SearchPtr + StructIdentifierLen, UniformBufferInfos, UniformBufferFilter, UniformBuffersByLength);
|
|
TCHAR* StructEndPtr = &PreprocessedShaderSource[ConstStructEndPtr - &PreprocessedShaderSource[0]];
|
|
|
|
// Comment out the uniform buffer struct and initializer
|
|
*SearchPtr = '/';
|
|
*(SearchPtr + 1) = '*';
|
|
*(StructEndPtr - 1) = '*';
|
|
*StructEndPtr = '/';
|
|
|
|
EndOfPreviousUniformBuffer = StructEndPtr + 1;
|
|
SearchPtr = StructEndPtr + 1;
|
|
}
|
|
|
|
} while (bUniformBufferFound);
|
|
}
|
|
|
|
// 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, FString& PreprocessedShaderSource)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(CleanupUniformBufferCode);
|
|
|
|
// Putting new code on a chicken switch in case there are issues
|
|
static const bool bRunCleanupUniformBufferCodeNew = CVarShaderCompilerCleanupUniformBufferCodeNew.GetValueOnAnyThread();
|
|
if (bRunCleanupUniformBufferCodeNew)
|
|
{
|
|
CleanupUniformBufferCodeNew(Environment, PreprocessedShaderSource);
|
|
return;
|
|
}
|
|
|
|
TMap<FStringView, FUniformBufferInfo> UniformBufferInfos;
|
|
UniformBufferInfos.Reserve(Environment.UniformBufferMap.Num());
|
|
|
|
// Build a mapping from uniform buffer name to its members
|
|
{
|
|
const TCHAR* UniformBufferStructIdentifier = TEXT("UniformBuffer");
|
|
const int32 StructIdentifierLen = FCString::Strlen(UniformBufferStructIdentifier);
|
|
|
|
const TCHAR* SourceStart = &PreprocessedShaderSource[0];
|
|
TCHAR* SearchPtr = FCString::Strstr(&PreprocessedShaderSource[0], UniformBufferStructIdentifier);
|
|
|
|
while (SearchPtr)
|
|
{
|
|
const ptrdiff_t Offset = SearchPtr - SourceStart;
|
|
if (Offset > 0 && IsSpaceOrTabOrEOL(*(SearchPtr - 1)) && IsSpaceOrTabOrEOL(*(SearchPtr + StructIdentifierLen)))
|
|
{
|
|
const TCHAR* ConstStructEndPtr = ParseUniformBufferDefinition(&PreprocessedShaderSource[0], SearchPtr + StructIdentifierLen, UniformBufferInfos);
|
|
TCHAR* StructEndPtr = &PreprocessedShaderSource[ConstStructEndPtr - &PreprocessedShaderSource[0]];
|
|
|
|
// Comment out the uniform buffer struct and initializer
|
|
*SearchPtr = '/';
|
|
*(SearchPtr + 1) = '*';
|
|
*(StructEndPtr - 1) = '*';
|
|
*StructEndPtr = '/';
|
|
|
|
SearchPtr = FCString::Strstr(StructEndPtr, UniformBufferStructIdentifier);
|
|
}
|
|
else
|
|
{
|
|
SearchPtr = FCString::Strstr(SearchPtr + 1, UniformBufferStructIdentifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
const TCHAR* const SearchSuffixes[] = { TEXT("."), TEXT(" .") };
|
|
TStringBuilder<64> UniformBufferName;
|
|
|
|
// Replace all uniform buffer struct member references (View.WorldToClip) with a flattened name that removes the struct dependency (View_WorldToClip)
|
|
for (TMap<FStringView, FUniformBufferInfo>::TConstIterator It(UniformBufferInfos); It; ++It)
|
|
{
|
|
const FUniformBufferInfo& UniformBufferInfo = It.Value();
|
|
|
|
// Start after the definition of this uniform buffer
|
|
TCHAR* const SearchStart = &PreprocessedShaderSource[UniformBufferInfo.DefinitionEndOffset];
|
|
|
|
// Trying "." and " ." separately can be faster than checking for each case as we iterate
|
|
for (const TCHAR* SearchSuffix : SearchSuffixes)
|
|
{
|
|
UniformBufferName.Reset();
|
|
UniformBufferName.Append(It.Key());
|
|
UniformBufferName.Append(SearchSuffix);
|
|
|
|
// Search for the uniform buffer name first, as an optimization (instead of searching the entire source for every member)
|
|
TCHAR* SearchPtr = FCString::Strstr(SearchStart, *UniformBufferName);
|
|
|
|
while (SearchPtr)
|
|
{
|
|
// Only match whole symbol
|
|
if (IsValidSymbolStart(SearchPtr, UniformBufferName.Len()))
|
|
{
|
|
// Find the matching member we are replacing
|
|
for (const FUniformBufferMemberInfo& MemberInfo : UniformBufferInfo.Members)
|
|
{
|
|
if (const TCHAR* SearchEnd = MatchStructMemberName(MemberInfo.NameAsStructMember, SearchPtr))
|
|
{
|
|
const int32 FoundTextLen = SearchEnd - SearchPtr;
|
|
const int32 ReplacementTextLen = MemberInfo.GlobalName.Len();
|
|
|
|
check(FoundTextLen >= ReplacementTextLen);
|
|
|
|
const TCHAR* SourceStart = GetData(MemberInfo.GlobalName);
|
|
TCHAR* const DestinationStart = SearchPtr;
|
|
|
|
int32 Index = 0;
|
|
for (; Index < ReplacementTextLen; Index++)
|
|
{
|
|
DestinationStart[Index] = SourceStart[Index];
|
|
}
|
|
|
|
// The shader preprocessor inserts spaces after defines
|
|
// #define ReflectionStruct OpaqueBasePass.Shared.Reflection
|
|
// 'ReflectionStruct.SkyLightCubemapBrightness' becomes 'OpaqueBasePass.Shared.Reflection .SkyLightCubemapBrightness' after MCPP
|
|
// In order to convert this struct member reference into a globally unique variable we move the spaces to the end
|
|
// 'OpaqueBasePass.Shared.Reflection .SkyLightCubemapBrightness' -> 'OpaqueBasePass_Shared_Reflection_SkyLightCubemapBrightness '
|
|
|
|
for (; Index < FoundTextLen; Index++)
|
|
{
|
|
// If we passed MatchStructMemberName, it should not be possible to overwrite the null terminator
|
|
check(DestinationStart[Index] != '\0');
|
|
DestinationStart[Index] = ' ';
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SearchPtr = FCString::Strstr(SearchPtr + UniformBufferName.Len(), *UniformBufferName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Process TEXT() macro to convert them into GPU ASCII characters
|
|
|
|
static FString ParseText(const TCHAR* StartPtr, const TCHAR*& EndPtr)
|
|
{
|
|
const TCHAR* OpeningBracePtr = FindNextChar(StartPtr, '(');
|
|
check(OpeningBracePtr);
|
|
|
|
const TCHAR* ClosingBracePtr = FindMatchingClosingParenthesis(OpeningBracePtr + 1);
|
|
check(ClosingBracePtr);
|
|
|
|
FString Out;
|
|
if (OpeningBracePtr && ClosingBracePtr)
|
|
{
|
|
const TCHAR* CurrPtr = OpeningBracePtr;
|
|
do
|
|
{
|
|
Out += *CurrPtr;
|
|
CurrPtr++;
|
|
} while (CurrPtr != ClosingBracePtr+1);
|
|
}
|
|
EndPtr = ClosingBracePtr;
|
|
return Out;
|
|
}
|
|
|
|
static void ConvertTextToAsciiCharacter(const FString& InText, FString& OutText, FString& OutEncodedText)
|
|
{
|
|
const uint32 CharCount = InText.Len();
|
|
OutEncodedText.Reserve(CharCount * 3); // ~2 digits per character + a comma
|
|
OutText = InText;
|
|
for (uint32 CharIt = 0; CharIt < CharCount; ++CharIt)
|
|
{
|
|
const char C = InText[CharIt];
|
|
OutEncodedText.AppendInt(uint8(C));
|
|
if (CharIt + 1 != CharCount)
|
|
{
|
|
OutEncodedText += ',';
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FAssertParsingStack
|
|
{
|
|
bool IsAssert() const { return BeginPtr != nullptr && EndPtr != nullptr; }
|
|
const TCHAR* BeginPtr = nullptr;
|
|
const TCHAR* EndPtr = nullptr;
|
|
};
|
|
|
|
static bool SearchText(const TCHAR*& InOut, FAssertParsingStack& OutAssertStack)
|
|
{
|
|
const TCHAR* TextIdentifier = TEXT("TEXT(");
|
|
const TCHAR* AssertIdentifier = TEXT("UEReportAssertWithPayload(");
|
|
|
|
// Find the next TEXT() or UEReportAssertWithPayload()
|
|
const TCHAR* PrintfPtr = FCString::Strstr(InOut, TextIdentifier);
|
|
const TCHAR* AssertPtr = FCString::Strstr(InOut, AssertIdentifier);
|
|
|
|
// 1. Default is current TEXT parsing
|
|
InOut = PrintfPtr;
|
|
|
|
// 2. If current pointer is beyond the current assert context, reset the context
|
|
if (InOut >= OutAssertStack.EndPtr)
|
|
{
|
|
OutAssertStack.BeginPtr = nullptr;
|
|
OutAssertStack.EndPtr = nullptr;
|
|
}
|
|
|
|
// 3. If we are within an assert context, continue to part TEXT within that context
|
|
if (OutAssertStack.IsAssert())
|
|
{
|
|
// Nothing to do. InOut will be initialized by PrintfPtr, which is the next TEXT block.
|
|
}
|
|
// 4. If a new assert is detected, start a new assert context.
|
|
else if (AssertPtr && AssertPtr < PrintfPtr)
|
|
{
|
|
// Sanity check
|
|
check(!OutAssertStack.IsAssert());
|
|
|
|
// Check if the current assert is valid, i.e., containt a TEXT() argument
|
|
const TCHAR* EndPtr = nullptr;
|
|
const FString Tmp = ParseText(AssertPtr, EndPtr);
|
|
const bool bIsValid = FCString::Strstr(&Tmp[0], TextIdentifier) != nullptr;
|
|
if (bIsValid)
|
|
{
|
|
OutAssertStack.BeginPtr = AssertPtr;
|
|
OutAssertStack.EndPtr = EndPtr;
|
|
InOut = FCString::Strstr(AssertPtr, TextIdentifier);
|
|
}
|
|
else
|
|
{
|
|
const uint32 LenAssertIdentifier = FString(AssertIdentifier).Len();
|
|
AssertPtr += LenAssertIdentifier;
|
|
return SearchText(AssertPtr, OutAssertStack);
|
|
}
|
|
}
|
|
return InOut != nullptr;
|
|
}
|
|
|
|
// Simple token matching and expansion to replace TEXT macro into supported character string
|
|
void TransformStringIntoCharacterArray(FString& PreprocessedShaderSource, TArray<FShaderDiagnosticData>* OutDiagnosticDatas)
|
|
{
|
|
// Early out if input is empty; '&PreprocessedShaderSource[0]' below does not return a valid pointer for empty FString
|
|
if (PreprocessedShaderSource.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct FTextEntry
|
|
{
|
|
uint32 Index;
|
|
uint32 Hash;
|
|
uint32 Offset;
|
|
bool bIsAssert;
|
|
FString SourceText;
|
|
FString ConvertedText;
|
|
FString EncodedText;
|
|
};
|
|
TArray<FTextEntry> Entries;
|
|
|
|
// 1. Find all TEXT strings
|
|
// 2. Add a text entry
|
|
// 3. Replace TEXT by its entry number
|
|
uint32 GlobalCount = 0;
|
|
uint32 AssertCount = 0;
|
|
uint32 PrintfCount = 0;
|
|
{
|
|
const FString InitHashBegin(TEXT("InitShaderPrintText("));
|
|
const FString InitHashEnd(TEXT(")"));
|
|
|
|
FAssertParsingStack AssertParsingStack;
|
|
const TCHAR* SearchPtr = &PreprocessedShaderSource[0];
|
|
while (SearchText(SearchPtr, AssertParsingStack))
|
|
{
|
|
const TCHAR* EndPtr = nullptr;
|
|
FString Text = ParseText(SearchPtr, EndPtr);
|
|
if (EndPtr)
|
|
{
|
|
// Trim enclosing
|
|
Text.RemoveFromEnd("\")");
|
|
Text.RemoveFromStart("(\"");
|
|
|
|
// Register entry and convert text
|
|
const uint32 EntryIndex = Entries.Num();
|
|
uint32 ValidCharCount = 0;
|
|
FTextEntry& Entry = Entries.AddDefaulted_GetRef();
|
|
Entry.Index = EntryIndex;
|
|
Entry.Offset = GlobalCount;
|
|
Entry.SourceText = Text;
|
|
ConvertTextToAsciiCharacter(Entry.SourceText, Entry.ConvertedText, Entry.EncodedText);
|
|
Entry.Hash = CityHash32((const char*)Entry.SourceText.GetCharArray().GetData(), sizeof(FString::ElementType) * Entry.SourceText.Len());
|
|
Entry.bIsAssert = AssertParsingStack.IsAssert();
|
|
if (Entry.bIsAssert)
|
|
{
|
|
++AssertCount;
|
|
}
|
|
else
|
|
{
|
|
++PrintfCount;
|
|
}
|
|
|
|
// Sanity check
|
|
uint32 HCheck = CityHash32((const char*)Entry.SourceText.GetCharArray().GetData(), sizeof(FString::ElementType) * Entry.SourceText.Len());
|
|
check(HCheck == Entry.Hash);
|
|
|
|
GlobalCount += Entry.ConvertedText.Len();
|
|
|
|
// Replace string
|
|
const TCHAR* StartPtr = &PreprocessedShaderSource[0];
|
|
const uint32 StartIndex = SearchPtr - StartPtr;
|
|
const uint32 CharCount = (EndPtr - SearchPtr) + 1;
|
|
PreprocessedShaderSource.RemoveAt(StartIndex, CharCount);
|
|
|
|
// * If this is an assert, replace the text with a simple hash
|
|
// * If this is a regular print, replace the text with IniShaderPrintText for runtime lookup
|
|
if (Entry.bIsAssert)
|
|
{
|
|
const FString HashString = FString::Printf(TEXT("%u"), Entry.Hash);
|
|
PreprocessedShaderSource.InsertAt(StartIndex, HashString);
|
|
}
|
|
else
|
|
{
|
|
const FString HashText = InitHashBegin + FString::FromInt(EntryIndex) + InitHashEnd;
|
|
PreprocessedShaderSource.InsertAt(StartIndex, HashText);
|
|
}
|
|
|
|
// Update SearchPtr, as PreprocessedShaderSource has been modified, and its memory could have been reallocated, causing SearchPtr to be invalid.
|
|
SearchPtr = &PreprocessedShaderSource[0] + StartIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Write a global struct containing all the entries
|
|
// 5. Write the function for fetching character for a given entry index
|
|
const uint32 EntryCount = Entries.Num();
|
|
FString TextChars;
|
|
if (PrintfCount>0 && EntryCount>0 && GlobalCount>0)
|
|
{
|
|
// 1. Encoded character for each text entry within a single global char array
|
|
TextChars = FString::Printf(TEXT("static const uint TEXT_CHARS[%d] = {\n"), GlobalCount);
|
|
for (FTextEntry& Entry : Entries)
|
|
{
|
|
TextChars += FString::Printf(TEXT("\t%s%s // %d: \"%s\"\n"), *Entry.EncodedText, Entry.Index < EntryCount - 1 ? TEXT(",") : TEXT(""), Entry.Index, * Entry.SourceText);
|
|
}
|
|
TextChars += TEXT("};\n\n");
|
|
|
|
// 2. Offset within the global array
|
|
TextChars += FString::Printf(TEXT("static const uint TEXT_OFFSETS[%d] = {\n"), EntryCount+1);
|
|
for (FTextEntry& Entry : Entries)
|
|
{
|
|
TextChars += FString::Printf(TEXT("\t%d, // %d: \"%s\"\n"), Entry.Offset, Entry.Index, *Entry.SourceText);
|
|
}
|
|
TextChars += FString::Printf(TEXT("\t%d // end\n"), GlobalCount);
|
|
TextChars += TEXT("};\n\n");
|
|
|
|
// 3. Entry hashes
|
|
TextChars += TEXT("// Hashes are computed using the CityHash32 function\n");
|
|
TextChars += FString::Printf(TEXT("static const uint TEXT_HASHES[%d] = {\n"), EntryCount);
|
|
for (FTextEntry& Entry : Entries)
|
|
{
|
|
TextChars += FString::Printf(TEXT("\t0x%x%s // %d: \"%s\"\n"), Entry.Hash, Entry.Index < EntryCount - 1 ? TEXT(",") : TEXT(""), Entry.Index, * Entry.SourceText);
|
|
}
|
|
TextChars += TEXT("};\n\n");
|
|
|
|
TextChars += TEXT("uint ShaderPrintGetChar(uint InIndex) { return TEXT_CHARS[InIndex]; }\n");
|
|
TextChars += TEXT("uint ShaderPrintGetOffset(FShaderPrintText InText) { return TEXT_OFFSETS[InText.Index]; }\n");
|
|
TextChars += TEXT("uint ShaderPrintGetHash(FShaderPrintText InText) { return TEXT_HASHES[InText.Index]; }\n");
|
|
}
|
|
else
|
|
{
|
|
TextChars += TEXT("uint ShaderPrintGetChar(uint Index) { return 0; }\n");
|
|
TextChars += TEXT("uint ShaderPrintGetOffset(FShaderPrintText InText) { return 0; }\n");
|
|
TextChars += TEXT("uint ShaderPrintGetHash(FShaderPrintText InText) { return 0; }\n");
|
|
}
|
|
|
|
// 6. Insert global struct data + print function
|
|
{
|
|
const TCHAR* InsertToken = TEXT("GENERATED_SHADER_PRINT");
|
|
const TCHAR* SearchPtr = FCString::Strstr(&PreprocessedShaderSource[0], InsertToken);
|
|
if (SearchPtr)
|
|
{
|
|
// Replace string
|
|
const TCHAR* StartPtr = &PreprocessedShaderSource[0];
|
|
const uint32 StartIndex = SearchPtr - StartPtr;
|
|
const uint32 CharCount = FCString::Strlen(InsertToken);
|
|
PreprocessedShaderSource.RemoveAt(StartIndex, CharCount);
|
|
PreprocessedShaderSource.InsertAt(StartIndex, TextChars);
|
|
}
|
|
}
|
|
|
|
// 7. Insert assert data into shader compilation output for runtime CPU lookup
|
|
if (OutDiagnosticDatas && AssertCount > 0)
|
|
{
|
|
OutDiagnosticDatas->Reserve(OutDiagnosticDatas->Num() + AssertCount);
|
|
for (const FTextEntry& E : Entries)
|
|
{
|
|
if (E.bIsAssert)
|
|
{
|
|
FShaderDiagnosticData& Data = OutDiagnosticDatas->AddDefaulted_GetRef();
|
|
Data.Hash = E.Hash;
|
|
Data.Message = E.SourceText;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FString CreateShaderCompilerWorkerDirectCommandLine(const FShaderCompilerInput& Input)
|
|
{
|
|
FString Text(TEXT("-directcompile -format="));
|
|
Text += Input.ShaderFormat.GetPlainNameString();
|
|
Text += TEXT(" -entry=\"");
|
|
Text += Input.EntryPointName;
|
|
|
|
Text += TEXT("\" -shaderPlatformName=");
|
|
Text += Input.ShaderPlatformName.GetPlainNameString();
|
|
|
|
switch (Input.Target.Frequency)
|
|
{
|
|
case SF_Vertex: Text += TEXT(" -vs"); break;
|
|
case SF_Mesh: Text += TEXT(" -ms"); break;
|
|
case SF_Amplification: Text += TEXT(" -as"); break;
|
|
case SF_Geometry: Text += TEXT(" -gs"); break;
|
|
case SF_Pixel: Text += TEXT(" -ps"); break;
|
|
case SF_Compute: Text += TEXT(" -cs"); break;
|
|
#if RHI_RAYTRACING
|
|
case SF_RayGen: Text += TEXT(" -rgs"); break;
|
|
case SF_RayMiss: Text += TEXT(" -rms"); break;
|
|
case SF_RayHitGroup: Text += TEXT(" -rhs"); break;
|
|
case SF_RayCallable: Text += TEXT(" -rcs"); break;
|
|
#endif // RHI_RAYTRACING
|
|
default: break;
|
|
}
|
|
if (Input.bCompilingForShaderPipeline)
|
|
{
|
|
Text += TEXT(" -pipeline");
|
|
}
|
|
if (Input.bIncludeUsedOutputs)
|
|
{
|
|
Text += TEXT(" -usedoutputs=");
|
|
for (int32 Index = 0; Index < Input.UsedOutputs.Num(); ++Index)
|
|
{
|
|
if (Index != 0)
|
|
{
|
|
Text += TEXT("+");
|
|
}
|
|
Text += Input.UsedOutputs[Index];
|
|
}
|
|
}
|
|
|
|
Text += TEXT(" ");
|
|
Text += Input.DumpDebugInfoPath / Input.GetSourceFilename();
|
|
|
|
Text += TEXT(" -cflags=");
|
|
Text += FString::Printf(TEXT("%llu"), Input.Environment.CompilerFlags.GetData());
|
|
|
|
// When we're running in directcompile mode, we don't to spam the crash reporter
|
|
Text += TEXT(" -nocrashreports");
|
|
return Text;
|
|
}
|
|
|
|
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 Mali_ExtractNumberInstructions(const FString &MaliOutput)
|
|
{
|
|
uint32 ReturnedNum = 0;
|
|
|
|
// Parse the instruction count
|
|
int32 InstructionStringLength = FPlatformString::Strlen(TEXT("Instructions Emitted:"));
|
|
int32 InstructionsIndex = MaliOutput.Find(TEXT("Instructions Emitted:"));
|
|
|
|
// new version of mali offline compiler uses a different string in its output
|
|
if (InstructionsIndex == INDEX_NONE)
|
|
{
|
|
InstructionStringLength = FPlatformString::Strlen(TEXT("Total instruction cycles:"));
|
|
InstructionsIndex = MaliOutput.Find(TEXT("Total instruction cycles:"));
|
|
}
|
|
|
|
if (InstructionsIndex != INDEX_NONE && InstructionsIndex + InstructionStringLength < MaliOutput.Len())
|
|
{
|
|
const int32 EndIndex = MaliOutput.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(MaliOutput[StartIndex]) && !bFoundNrStart)
|
|
{
|
|
// found number's beginning
|
|
bFoundNrStart = true;
|
|
NumberIndex = StartIndex;
|
|
}
|
|
else if (FChar::IsWhitespace(MaliOutput[StartIndex]) && bFoundNrStart)
|
|
{
|
|
// found number's end
|
|
bFoundNrStart = false;
|
|
const FString NumberString = MaliOutput.Mid(NumberIndex, StartIndex - NumberIndex);
|
|
const float fNrInstructions = FCString::Atof(*NumberString);
|
|
ReturnedNum += (uint32)FMath::Max(0.0, ceil(fNrInstructions));
|
|
}
|
|
|
|
++StartIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReturnedNum;
|
|
}
|
|
|
|
static FString Mali_ExtractErrors(const FString &MaliOutput)
|
|
{
|
|
FString ReturnedErrors;
|
|
|
|
const int32 GlobalErrorIndex = MaliOutput.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 = MaliOutput.Find(TEXT("ERROR:"));
|
|
while (CompilationErrorIndex != INDEX_NONE)
|
|
{
|
|
int32 EndLineIndex = MaliOutput.Find(TEXT("\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, CompilationErrorIndex + 1);
|
|
EndLineIndex = EndLineIndex == INDEX_NONE ? MaliOutput.Len() - 1 : EndLineIndex;
|
|
|
|
ReturnedErrors += MaliOutput.Mid(CompilationErrorIndex, EndLineIndex - CompilationErrorIndex + 1);
|
|
|
|
CompilationErrorIndex = MaliOutput.Find(TEXT("ERROR:"), ESearchCase::CaseSensitive, ESearchDir::FromStart, EndLineIndex);
|
|
}
|
|
}
|
|
|
|
return ReturnedErrors;
|
|
}
|
|
|
|
void CompileOfflineMali(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)
|
|
{
|
|
const auto Frequency = (EShaderFrequency)Input.Target.Frequency;
|
|
const FString WorkingDir(FPlatformProcess::ShaderDir());
|
|
|
|
FString CompilerPath = Input.ExtraSettings.OfflineCompilerPath;
|
|
|
|
FString CompilerCommand = "";
|
|
|
|
// add process and thread ids to the file name to avoid collision between workers
|
|
auto ProcID = FPlatformProcess::GetCurrentProcessId();
|
|
auto ThreadID = FPlatformTLS::GetCurrentThreadId();
|
|
FString GLSLSourceFile = WorkingDir / TEXT("GLSLSource#") + FString::FromInt(ProcID) + TEXT("#") + FString::FromInt(ThreadID);
|
|
|
|
// setup compilation arguments
|
|
TCHAR *FileExt = nullptr;
|
|
switch (Frequency)
|
|
{
|
|
case SF_Vertex:
|
|
GLSLSourceFile += bVulkanSpirV ? TEXT(".spv") : TEXT(".vert");
|
|
CompilerCommand += TEXT(" -v");
|
|
break;
|
|
case SF_Pixel:
|
|
GLSLSourceFile += bVulkanSpirV ? TEXT(".spv") : TEXT(".frag");
|
|
CompilerCommand += TEXT(" -f");
|
|
break;
|
|
case SF_Geometry:
|
|
GLSLSourceFile += bVulkanSpirV ? TEXT(".spv") : TEXT(".geom");
|
|
CompilerCommand += TEXT(" -g");
|
|
break;
|
|
case SF_Compute:
|
|
GLSLSourceFile += bVulkanSpirV ? TEXT(".spv") : TEXT(".comp");
|
|
CompilerCommand += TEXT(" -C");
|
|
break;
|
|
|
|
default:
|
|
GLSLSourceFile += TEXT(".shd");
|
|
break;
|
|
}
|
|
|
|
if (bVulkanSpirV)
|
|
{
|
|
CompilerCommand += FString::Printf(TEXT(" -y %s -p"), ANSI_TO_TCHAR(VulkanSpirVEntryPoint));
|
|
}
|
|
else
|
|
{
|
|
CompilerCommand += TEXT(" -s");
|
|
}
|
|
|
|
FArchive* Ar = IFileManager::Get().CreateFileWriter(*GLSLSourceFile, 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(GLSLSourceFile);
|
|
|
|
// Run Mali shader compiler and wait for completion
|
|
FPlatformProcess::ExecProcess(*CompilerPath, *CompilerCommand, &ReturnCode, &StdOut, &StdErr, *CompilerWorkingDirectory);
|
|
}
|
|
else
|
|
{
|
|
StdErr = "Couldn't find Mali 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("[Mali Offline Complier]\n") + StdErr;
|
|
}
|
|
else
|
|
{
|
|
FString Errors = Mali_ExtractErrors(StdOut);
|
|
|
|
if (Errors.Len())
|
|
{
|
|
FShaderCompilerError& NewError = ShaderOutput.Errors.AddDefaulted_GetRef();
|
|
NewError.StrippedErrorMessage = TEXT("[Mali Offline Complier]\n") + Errors;
|
|
ShaderOutput.bSucceeded = false;
|
|
}
|
|
}
|
|
|
|
// extract instruction count
|
|
if (ShaderOutput.bSucceeded)
|
|
{
|
|
ShaderOutput.NumInstructions = Mali_ExtractNumberInstructions(StdOut);
|
|
}
|
|
}
|
|
|
|
// we're done so delete the shader file
|
|
IFileManager::Get().Delete(*GLSLSourceFile, true, true);
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
TStringBuilder<512> PathBuilder;
|
|
const TCHAR* Prefix = (Options.FilenamePrefix && *Options.FilenamePrefix) ? Options.FilenamePrefix : TEXT("");
|
|
FStringView Filename = (BaseFilename && *BaseFilename) ? BaseFilename : Input.GetSourceFilenameView();
|
|
FPathViews::Append(PathBuilder, Input.DumpDebugInfoPath, Prefix);
|
|
PathBuilder << Filename;
|
|
return PathBuilder.ToString();
|
|
}
|
|
|
|
namespace UE::ShaderCompilerCommon
|
|
{
|
|
FString FDebugShaderDataOptions::GetDebugShaderPath(const FShaderCompilerInput& Input) const
|
|
{
|
|
return GetDebugFileName(Input, *this, OverrideBaseFilename);
|
|
}
|
|
|
|
void FBaseShaderFormat::OutputDebugData(
|
|
const FShaderCompilerInput& Input,
|
|
const FShaderPreprocessOutput& PreprocessOutput,
|
|
const FShaderCompilerOutput& Output) const
|
|
{
|
|
DumpExtendedDebugShaderData(Input, PreprocessOutput, Output);
|
|
}
|
|
|
|
void DumpDebugShaderData(const FShaderCompilerInput& Input, const FString& PreprocessedSource, const FDebugShaderDataOptions& Options)
|
|
{
|
|
if (!Input.DumpDebugInfoEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString Contents = UE::ShaderCompilerCommon::GetDebugShaderContents(Input, PreprocessedSource, Options);
|
|
FFileHelper::SaveStringToFile(Contents, *Options.GetDebugShaderPath(Input));
|
|
|
|
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::DirectCompileCommandLine) && !Options.bSourceOnly)
|
|
{
|
|
FFileHelper::SaveStringToFile(CreateShaderCompilerWorkerDirectCommandLine(Input), *GetDebugFileName(Input, Options, TEXT("DirectCompile.txt")));
|
|
}
|
|
}
|
|
|
|
void DumpExtendedDebugShaderData(
|
|
const FShaderCompilerInput& Input,
|
|
const FShaderPreprocessOutput& PreprocessOutput,
|
|
const FShaderCompilerOutput& Output,
|
|
const FDebugShaderDataOptions& Options)
|
|
{
|
|
if (Input.bCachePreprocessed && EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::DetailedSource))
|
|
{
|
|
FDebugShaderDataOptions PrefixedOptions(Options);
|
|
uint32 SlackLen = Options.FilenamePrefix ? FCString::Strlen(Options.FilenamePrefix) : 0;
|
|
FString StrippedPrefix(TEXT("Stripped_"), SlackLen);
|
|
FString PreprocessedPrefix(TEXT("Preprocessed_"), SlackLen);
|
|
if (Options.FilenamePrefix)
|
|
{
|
|
StrippedPrefix += Options.FilenamePrefix;
|
|
PreprocessedPrefix += Options.FilenamePrefix;
|
|
}
|
|
|
|
PrefixedOptions.FilenamePrefix = *StrippedPrefix;
|
|
FFileHelper::SaveStringToFile(PreprocessOutput.GetSource(), *PrefixedOptions.GetDebugShaderPath(Input));
|
|
|
|
PrefixedOptions.FilenamePrefix = *PreprocessedPrefix;
|
|
FFileHelper::SaveStringToFile(PreprocessOutput.GetUnstrippedSource(), *PrefixedOptions.GetDebugShaderPath(Input));
|
|
}
|
|
|
|
const FString& SourceToDump = Output.ModifiedShaderSource.IsEmpty() ? PreprocessOutput.GetSource() : Output.ModifiedShaderSource;
|
|
DumpDebugShaderData(Input, SourceToDump, Options);
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (EnumHasAnyFlags(Input.DebugInfoFlags, EShaderDebugInfoFlags::InputHash))
|
|
{
|
|
FFileHelper::SaveStringToFile(LexToString(Input.Hash), *GetDebugFileName(Input, Options, TEXT("InputHash.txt")), FFileHelper::EEncodingOptions::ForceAnsi);
|
|
}
|
|
|
|
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.GetReadAccess().GetData(), Output.ShaderCode.GetShaderCodeSize());
|
|
FFileHelper::SaveArrayToFile(DecompressedCode, *ShaderCodeFileName);
|
|
}
|
|
else
|
|
{
|
|
FFileHelper::SaveArrayToFile(Output.ShaderCode.GetReadAccess(), *ShaderCodeFileName);
|
|
}
|
|
}
|
|
|
|
for (const FDebugShaderDataOptions::FAdditionalOutput& AdditionalOutput : Options.AdditionalOutputs)
|
|
{
|
|
FFileHelper::SaveStringToFile(AdditionalOutput.Data, *GetDebugFileName(Input, Options, AdditionalOutput.BaseFileName), FFileHelper::EEncodingOptions::ForceAnsi);
|
|
}
|
|
}
|
|
|
|
FString GetDebugShaderContents(const FShaderCompilerInput& Input, const FString& PreprocessedSource, const FDebugShaderDataOptions& Options)
|
|
{
|
|
FString Contents = Options.AppendPreSource ? Options.AppendPreSource() : FString();
|
|
Contents += PreprocessedSource;
|
|
if (Options.AppendPostSource)
|
|
{
|
|
Contents += Options.AppendPostSource();
|
|
}
|
|
Contents += TEXT("\n");
|
|
Contents += CrossCompiler::CreateResourceTableFromEnvironment(Input.Environment);
|
|
Contents += TEXT("#if 0 /*DIRECT COMPILE*/\n");
|
|
Contents += CreateShaderCompilerWorkerDirectCommandLine(Input);
|
|
Contents += TEXT("\n#endif /*DIRECT COMPILE*/\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
|
|
{
|
|
FString CreateResourceTableFromEnvironment(const FShaderCompilerEnvironment& Environment)
|
|
{
|
|
FString Line = TEXT("\n#if 0 /*BEGIN_RESOURCE_TABLES*/\n");
|
|
for (auto Pair : Environment.UniformBufferMap)
|
|
{
|
|
Line += FString::Printf(TEXT("%s, %d\n"), *Pair.Key, Pair.Value.LayoutHash);
|
|
}
|
|
Line += TEXT("NULL, 0\n");
|
|
for (const FUniformResourceEntry& Entry : Environment.ResourceTableMap.Resources)
|
|
{
|
|
Line += FString::Printf(TEXT("%s, %s, %d, %d\n"), Entry.UniformBufferMemberName, *FString(Entry.GetUniformBufferName()), Entry.Type, Entry.ResourceIndex);
|
|
}
|
|
Line += TEXT("NULL, NULL, 0, 0\n");
|
|
|
|
Line += TEXT("#endif /*END_RESOURCE_TABLES*/\n");
|
|
return Line;
|
|
}
|
|
|
|
void CreateEnvironmentFromResourceTable(const FString& String, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FString Prolog = TEXT("#if 0 /*BEGIN_RESOURCE_TABLES*/");
|
|
int32 FoundBegin = String.Find(Prolog, ESearchCase::CaseSensitive);
|
|
if (FoundBegin == INDEX_NONE)
|
|
{
|
|
return;
|
|
}
|
|
int32 FoundEnd = String.Find(TEXT("#endif /*END_RESOURCE_TABLES*/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, FoundBegin);
|
|
if (FoundEnd == INDEX_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// +1 for EOL
|
|
const TCHAR* Ptr = &String[FoundBegin + 1 + Prolog.Len()];
|
|
while (*Ptr == '\r' || *Ptr == '\n')
|
|
{
|
|
++Ptr;
|
|
}
|
|
const TCHAR* PtrEnd = &String[FoundEnd];
|
|
while (Ptr < PtrEnd)
|
|
{
|
|
FString UB;
|
|
if (!CrossCompiler::ParseIdentifier(Ptr, UB))
|
|
{
|
|
return;
|
|
}
|
|
if (!CrossCompiler::Match(Ptr, TEXT(", ")))
|
|
{
|
|
return;
|
|
}
|
|
int32 Hash;
|
|
if (!CrossCompiler::ParseSignedNumber(Ptr, Hash))
|
|
{
|
|
return;
|
|
}
|
|
// Optional \r
|
|
CrossCompiler::Match(Ptr, '\r');
|
|
if (!CrossCompiler::Match(Ptr, '\n'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UB == TEXT("NULL") && Hash == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
FUniformBufferEntry& UniformBufferEntry = OutEnvironment.UniformBufferMap.FindOrAdd(UB);
|
|
UniformBufferEntry.LayoutHash = (uint32)Hash;
|
|
|
|
if (!UniformBufferEntry.MemberNameBuffer)
|
|
{
|
|
TArray<TCHAR>* MemberNameBuffer = new TArray<TCHAR>();
|
|
UniformBufferEntry.MemberNameBuffer = MakeShareable(MemberNameBuffer);
|
|
}
|
|
}
|
|
|
|
// Need to iterate through Uniform Buffer Map to add strings to correct MemberNameBuffer storage
|
|
auto UniformBufferMapIt = OutEnvironment.UniformBufferMap.begin();
|
|
|
|
// If we exit parse early due to error, we still want to fixup the string names for the members we found,
|
|
// so the partial data isn't corrupt.
|
|
struct FFixupOnExit
|
|
{
|
|
FFixupOnExit(FShaderCompilerEnvironment& OutEnvironment) : Environment(OutEnvironment) {}
|
|
~FFixupOnExit()
|
|
{
|
|
Environment.ResourceTableMap.FixupOnLoad(Environment.UniformBufferMap);
|
|
}
|
|
|
|
FShaderCompilerEnvironment& Environment;
|
|
};
|
|
FFixupOnExit FixupOnExit(OutEnvironment);
|
|
|
|
while (Ptr < PtrEnd)
|
|
{
|
|
FString Name;
|
|
if (!CrossCompiler::ParseIdentifier(Ptr, Name))
|
|
{
|
|
return;
|
|
}
|
|
if (!CrossCompiler::Match(Ptr, TEXT(", ")))
|
|
{
|
|
return;
|
|
}
|
|
FString UB;
|
|
if (!CrossCompiler::ParseIdentifier(Ptr, UB))
|
|
{
|
|
return;
|
|
}
|
|
if (!CrossCompiler::Match(Ptr, TEXT(", ")))
|
|
{
|
|
return;
|
|
}
|
|
int32 Type;
|
|
if (!CrossCompiler::ParseSignedNumber(Ptr, Type))
|
|
{
|
|
return;
|
|
}
|
|
if (!CrossCompiler::Match(Ptr, TEXT(", ")))
|
|
{
|
|
return;
|
|
}
|
|
int32 ResourceIndex;
|
|
if (!CrossCompiler::ParseSignedNumber(Ptr, ResourceIndex))
|
|
{
|
|
return;
|
|
}
|
|
// Optional
|
|
CrossCompiler::Match(Ptr, '\r');
|
|
if (!CrossCompiler::Match(Ptr, '\n'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Name == TEXT("NULL") && UB == TEXT("NULL") && Type == 0 && ResourceIndex == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Advance the uniform buffer map if this is a different UB name
|
|
while (UniformBufferMapIt.Key() != UB)
|
|
{
|
|
++UniformBufferMapIt;
|
|
if (UniformBufferMapIt == OutEnvironment.UniformBufferMap.end())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Append the Name we parsed to the member name buffer
|
|
TArray<TCHAR>& Buffer = *UniformBufferMapIt.Value().MemberNameBuffer.Get();
|
|
uint32 MemberNameLength = Name.Len();
|
|
|
|
Buffer.Append(*Name, MemberNameLength + 1);
|
|
|
|
// The member name field of the entries is initialized at the end of parsing by the FixupOnLoad call from FFixupOnExit, so we can set it to nullptr here
|
|
OutEnvironment.ResourceTableMap.Resources.Add({
|
|
nullptr,
|
|
(uint8)UB.Len(),
|
|
(uint8)Type,
|
|
(uint16)ResourceIndex
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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"),
|
|
};
|
|
|
|
/** 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
|