You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2604 lines
94 KiB
C++
2604 lines
94 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
// .
|
|
|
|
#include "VulkanShaderFormat.h"
|
|
|
|
#include "hlslcc.h"
|
|
#include "RHIShaderFormatDefinitions.inl"
|
|
#include "ShaderCompilerCommon.h"
|
|
#include "ShaderCompilerDefinitions.h"
|
|
#include "ShaderParameterParser.h"
|
|
#include "ShaderPreprocessor.h"
|
|
#include "ShaderPreprocessTypes.h"
|
|
#include "SpirvReflectCommon.h"
|
|
#include "VulkanCommon.h"
|
|
|
|
#if PLATFORM_MAC
|
|
// Horrible hack as we need the enum available but the Vulkan headers do not compile on Mac
|
|
typedef enum VkDescriptorType {
|
|
VK_DESCRIPTOR_TYPE_SAMPLER = 0,
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1,
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9,
|
|
VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10,
|
|
VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK = 1000138000,
|
|
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR = 1000150000,
|
|
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV = 1000165000,
|
|
VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM = 1000440000,
|
|
VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM = 1000440001,
|
|
VK_DESCRIPTOR_TYPE_MUTABLE_EXT = 1000351000,
|
|
VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK,
|
|
VK_DESCRIPTOR_TYPE_MUTABLE_VALVE = VK_DESCRIPTOR_TYPE_MUTABLE_EXT,
|
|
VK_DESCRIPTOR_TYPE_MAX_ENUM = 0x7FFFFFFF
|
|
} VkDescriptorType;
|
|
#else
|
|
#include "IVulkanDynamicRHI.h"
|
|
#endif
|
|
#include "VulkanBackend.h"
|
|
#include "VulkanShaderResources.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
|
|
inline bool IsVulkanShaderFormat(FName ShaderFormat)
|
|
{
|
|
return ShaderFormat == NAME_VULKAN_ES3_1_ANDROID
|
|
|| ShaderFormat == NAME_VULKAN_ES3_1
|
|
|| ShaderFormat == NAME_VULKAN_SM5
|
|
|| ShaderFormat == NAME_VULKAN_SM6
|
|
|| ShaderFormat == NAME_VULKAN_SM5_ANDROID;
|
|
}
|
|
|
|
inline bool IsAndroidShaderFormat(FName ShaderFormat)
|
|
{
|
|
return ShaderFormat == NAME_VULKAN_ES3_1_ANDROID
|
|
|| ShaderFormat == NAME_VULKAN_SM5_ANDROID;
|
|
}
|
|
|
|
inline bool SupportsOfflineCompiler(FName ShaderFormat)
|
|
{
|
|
return ShaderFormat == NAME_VULKAN_ES3_1_ANDROID
|
|
|| ShaderFormat == NAME_VULKAN_ES3_1
|
|
|| ShaderFormat == NAME_VULKAN_SM5_ANDROID;
|
|
}
|
|
|
|
enum class EVulkanShaderVersion
|
|
{
|
|
ES3_1,
|
|
ES3_1_ANDROID,
|
|
SM5,
|
|
SM5_ANDROID,
|
|
SM6,
|
|
Invalid,
|
|
};
|
|
|
|
inline EVulkanShaderVersion FormatToVersion(FName Format)
|
|
{
|
|
if (Format == NAME_VULKAN_ES3_1)
|
|
{
|
|
return EVulkanShaderVersion::ES3_1;
|
|
}
|
|
else if (Format == NAME_VULKAN_ES3_1_ANDROID)
|
|
{
|
|
return EVulkanShaderVersion::ES3_1_ANDROID;
|
|
}
|
|
else if (Format == NAME_VULKAN_SM5_ANDROID)
|
|
{
|
|
return EVulkanShaderVersion::SM5_ANDROID;
|
|
}
|
|
else if (Format == NAME_VULKAN_SM5)
|
|
{
|
|
return EVulkanShaderVersion::SM5;
|
|
}
|
|
else if (Format == NAME_VULKAN_SM6)
|
|
{
|
|
return EVulkanShaderVersion::SM6;
|
|
}
|
|
else
|
|
{
|
|
FString FormatStr = Format.ToString();
|
|
checkf(0, TEXT("Invalid shader format passed to Vulkan shader compiler: %s"), *FormatStr);
|
|
return EVulkanShaderVersion::Invalid;
|
|
}
|
|
}
|
|
|
|
inline CrossCompiler::FShaderConductorOptions::ETargetEnvironment GetMinimumTargetEnvironment(const FShaderCompilerInput& Input)
|
|
{
|
|
const EVulkanShaderVersion ShaderVersion = FormatToVersion(Input.ShaderFormat);
|
|
if (ShaderVersion == EVulkanShaderVersion::SM6)
|
|
{
|
|
return CrossCompiler::FShaderConductorOptions::ETargetEnvironment::Vulkan_1_3;
|
|
}
|
|
else if (Input.IsRayTracingShader() || Input.Environment.CompilerFlags.Contains(CFLAG_InlineRayTracing))
|
|
{
|
|
return CrossCompiler::FShaderConductorOptions::ETargetEnvironment::Vulkan_1_2;
|
|
}
|
|
else
|
|
{
|
|
return CrossCompiler::FShaderConductorOptions::ETargetEnvironment::Vulkan_1_1;
|
|
}
|
|
}
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogVulkanShaderCompiler, Log, All);
|
|
|
|
static bool Match(const ANSICHAR* &Str, ANSICHAR Char)
|
|
{
|
|
if (*Str == Char)
|
|
{
|
|
++Str;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
uint32 ParseNumber(const T* Str, bool bEmptyIsZero = false)
|
|
{
|
|
check(Str);
|
|
|
|
uint32 Num = 0;
|
|
|
|
int32 Len = 0;
|
|
// Find terminating character
|
|
for(int32 Index=0; Index<128; Index++)
|
|
{
|
|
if(Str[Index] == 0)
|
|
{
|
|
Len = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Len == 0)
|
|
{
|
|
if (bEmptyIsZero)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
check(0);
|
|
}
|
|
}
|
|
|
|
// Find offset to integer type
|
|
int32 Offset = -1;
|
|
for(int32 Index=0; Index<Len; Index++)
|
|
{
|
|
if (*(Str + Index) >= '0' && *(Str + Index) <= '9')
|
|
{
|
|
Offset = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if we found a number
|
|
check(Offset >= 0);
|
|
|
|
Str += Offset;
|
|
|
|
while (*(Str) && *Str >= '0' && *Str <= '9')
|
|
{
|
|
Num = Num * 10 + *Str++ - '0';
|
|
}
|
|
|
|
return Num;
|
|
}
|
|
|
|
static bool ContainsBinding(const FVulkanBindingTable& BindingTable, const FString& Name)
|
|
{
|
|
for (const FVulkanBindingTable::FBinding& Binding : BindingTable.GetBindings())
|
|
{
|
|
if (Binding.Name == Name)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void GetResourceEntryFromUBMember(const FShaderResourceTableMap& ResourceTableMap, const FString& UBName, uint16 ResourceIndex, FUniformResourceEntry& OutEntry)
|
|
{
|
|
for (const FUniformResourceEntry& Entry : ResourceTableMap.Resources)
|
|
{
|
|
if (Entry.GetUniformBufferName() == UBName && Entry.ResourceIndex == ResourceIndex)
|
|
{
|
|
OutEntry = Entry;
|
|
return;
|
|
}
|
|
}
|
|
|
|
check(0);
|
|
}
|
|
|
|
|
|
struct FPatchType
|
|
{
|
|
int32 HeaderGlobalIndex;
|
|
uint16 CombinedAliasIndex;
|
|
};
|
|
|
|
|
|
static const FString kBindlessCBPrefix = TEXT("__BindlessCB");
|
|
static const FString kBindlessHeapSuffix = TEXT("_Heap");
|
|
static FString GetBindlessUBNameFromHeap(const FString& HeapName)
|
|
{
|
|
check(HeapName.StartsWith(kBindlessCBPrefix));
|
|
check(HeapName.EndsWith(kBindlessHeapSuffix));
|
|
|
|
int32 NameStart = HeapName.Find(TEXT("_"), ESearchCase::IgnoreCase, ESearchDir::FromStart, kBindlessCBPrefix.Len() + 1);
|
|
check(NameStart != INDEX_NONE);
|
|
NameStart++;
|
|
return HeapName.Mid(NameStart, HeapName.Len() - NameStart - kBindlessHeapSuffix.Len());
|
|
}
|
|
|
|
// Name of the structure in raytracing shader records in VulkanCommon.usf
|
|
static const FString kHitGroupSystemRootConstantsSymbolName = TEXT("HitGroupSystemRootConstants");
|
|
|
|
// A collection of states and data that is locked in at the top level call and doesn't change throughout the compilation process
|
|
struct FVulkanShaderCompilerInternalState
|
|
{
|
|
FVulkanShaderCompilerInternalState(const FShaderCompilerInput& InInput, const FShaderParameterParser* InParameterParser)
|
|
: Input(InInput)
|
|
, ParameterParser(InParameterParser)
|
|
, Version(FormatToVersion(Input.ShaderFormat))
|
|
, MinimumTargetEnvironment(GetMinimumTargetEnvironment(InInput))
|
|
, bStripReflect(InInput.IsRayTracingShader() || (IsAndroidShaderFormat(Input.ShaderFormat) && InInput.Environment.GetCompileArgument(TEXT("STRIP_REFLECT_ANDROID"), true)))
|
|
, bUseBindlessUniformBuffer(InInput.IsRayTracingShader() && ((EShaderFrequency)InInput.Target.Frequency != SF_RayGen))
|
|
, bIsRayHitGroupShader(InInput.IsRayTracingShader() && ((EShaderFrequency)InInput.Target.Frequency == SF_RayHitGroup))
|
|
, bSupportsBindless(InInput.Environment.CompilerFlags.Contains(CFLAG_BindlessResources) || InInput.Environment.CompilerFlags.Contains(CFLAG_BindlessSamplers))
|
|
, bDebugDump(InInput.DumpDebugInfoEnabled())
|
|
{
|
|
if (bIsRayHitGroupShader)
|
|
{
|
|
UE::ShaderCompilerCommon::ParseRayTracingEntryPoint(Input.EntryPointName, ClosestHitEntry, AnyHitEntry, IntersectionEntry);
|
|
checkf(!ClosestHitEntry.IsEmpty(), TEXT("All hit groups must contain at least a closest hit shader module"));
|
|
}
|
|
}
|
|
|
|
const FShaderCompilerInput& Input;
|
|
const FShaderParameterParser* ParameterParser;
|
|
|
|
const EVulkanShaderVersion Version;
|
|
const CrossCompiler::FShaderConductorOptions::ETargetEnvironment MinimumTargetEnvironment;
|
|
|
|
const bool bStripReflect;
|
|
const bool bUseBindlessUniformBuffer;
|
|
const bool bIsRayHitGroupShader;
|
|
|
|
const bool bSupportsBindless;
|
|
const bool bDebugDump;
|
|
|
|
// Ray tracing specific states
|
|
enum class EHitGroupShaderType
|
|
{
|
|
None,
|
|
ClosestHit,
|
|
AnyHit,
|
|
Intersection
|
|
};
|
|
EHitGroupShaderType HitGroupShaderType = EHitGroupShaderType::None;
|
|
FString ClosestHitEntry;
|
|
FString AnyHitEntry;
|
|
FString IntersectionEntry;
|
|
|
|
TArray<FString> AllBindlessUBs;
|
|
|
|
// Forwarded calls for convenience
|
|
inline EShaderFrequency GetShaderFrequency() const
|
|
{
|
|
return static_cast<EShaderFrequency>(Input.Target.Frequency);
|
|
}
|
|
inline const FString& GetEntryPointName() const
|
|
{
|
|
if (bIsRayHitGroupShader)
|
|
{
|
|
switch (HitGroupShaderType)
|
|
{
|
|
case EHitGroupShaderType::AnyHit:
|
|
return AnyHitEntry;
|
|
case EHitGroupShaderType::Intersection:
|
|
return IntersectionEntry;
|
|
case EHitGroupShaderType::ClosestHit:
|
|
return ClosestHitEntry;
|
|
|
|
case EHitGroupShaderType::None:
|
|
[[fallthrough]];
|
|
default:
|
|
return Input.EntryPointName;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return Input.EntryPointName;
|
|
}
|
|
}
|
|
inline TArray<FStringView> GetRequiredSymbols() const
|
|
{
|
|
TArray<FStringView> RequiredSymbols;
|
|
|
|
if (bIsRayHitGroupShader)
|
|
{
|
|
check(!ClosestHitEntry.IsEmpty());
|
|
|
|
RequiredSymbols.Add(ClosestHitEntry);
|
|
|
|
if (!AnyHitEntry.IsEmpty())
|
|
{
|
|
RequiredSymbols.Add(AnyHitEntry);
|
|
}
|
|
|
|
if (!IntersectionEntry.IsEmpty())
|
|
{
|
|
RequiredSymbols.Add(IntersectionEntry);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RequiredSymbols.Add(Input.EntryPointName);
|
|
}
|
|
|
|
if (IsRayTracingShader())
|
|
{
|
|
RequiredSymbols.Add(kHitGroupSystemRootConstantsSymbolName);
|
|
}
|
|
|
|
return RequiredSymbols;
|
|
}
|
|
inline bool IsRayTracingShader() const
|
|
{
|
|
return Input.IsRayTracingShader();
|
|
}
|
|
inline bool UseRootParametersStructure() const
|
|
{
|
|
// Only supported for RayGen currently
|
|
return (GetShaderFrequency() == SF_RayGen) && (Input.RootParametersStructure != nullptr);
|
|
}
|
|
inline bool IsSM6() const
|
|
{
|
|
return (Version == EVulkanShaderVersion::SM6);
|
|
}
|
|
inline bool IsSM5() const
|
|
{
|
|
return (Version == EVulkanShaderVersion::SM5) || (Version == EVulkanShaderVersion::SM5_ANDROID);
|
|
}
|
|
inline bool IsMobileES31() const
|
|
{
|
|
return (Version == EVulkanShaderVersion::ES3_1 || Version == EVulkanShaderVersion::ES3_1_ANDROID);
|
|
}
|
|
inline EHlslShaderFrequency GetHlslShaderFrequency() const
|
|
{
|
|
const EHlslShaderFrequency FrequencyTable[] =
|
|
{
|
|
HSF_VertexShader,
|
|
HSF_InvalidFrequency,
|
|
HSF_InvalidFrequency,
|
|
HSF_PixelShader,
|
|
(IsSM5() || IsSM6()) ? HSF_GeometryShader : HSF_InvalidFrequency,
|
|
HSF_ComputeShader,
|
|
(IsSM5() || IsSM6()) ? HSF_RayGen : HSF_InvalidFrequency,
|
|
(IsSM5() || IsSM6()) ? HSF_RayMiss : HSF_InvalidFrequency,
|
|
(IsSM5() || IsSM6()) ? HSF_RayHitGroup : HSF_InvalidFrequency,
|
|
(IsSM5() || IsSM6()) ? HSF_RayCallable : HSF_InvalidFrequency,
|
|
};
|
|
return FrequencyTable[Input.Target.Frequency];
|
|
}
|
|
inline FString GetDebugName() const
|
|
{
|
|
return Input.DumpDebugInfoPath.Right(Input.DumpDebugInfoPath.Len() - Input.DumpDebugInfoRootPath.Len());
|
|
}
|
|
inline bool HasMultipleEntryPoints() const
|
|
{
|
|
return !ClosestHitEntry.IsEmpty() && (!AnyHitEntry.IsEmpty() || !IntersectionEntry.IsEmpty());
|
|
}
|
|
inline FString GetSPVExtension() const
|
|
{
|
|
switch (HitGroupShaderType)
|
|
{
|
|
case EHitGroupShaderType::AnyHit:
|
|
return TEXT("anyhit.spv");
|
|
case EHitGroupShaderType::Intersection:
|
|
return TEXT("intersection.spv");
|
|
case EHitGroupShaderType::ClosestHit:
|
|
return TEXT("closesthit.spv");
|
|
case EHitGroupShaderType::None:
|
|
[[fallthrough]];
|
|
default:
|
|
return TEXT("spv");
|
|
};
|
|
}
|
|
};
|
|
|
|
// Data structures that will get serialized into ShaderCompilerOutput
|
|
struct VulkanShaderCompilerSerializedOutput
|
|
{
|
|
VulkanShaderCompilerSerializedOutput()
|
|
: Header(FVulkanShaderHeader::EZero)
|
|
{
|
|
}
|
|
|
|
FVulkanShaderHeader Header;
|
|
FShaderResourceTable ShaderResourceTable;
|
|
FVulkanSpirv Spirv;
|
|
|
|
TSet<FString> UsedBindlessUB;
|
|
};
|
|
|
|
|
|
static int32 AddGlobal( const TArray<VkDescriptorType>& DescriptorTypes,
|
|
const FString& ParameterName,
|
|
uint16 BindingIndex,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
const TArray<FString>& GlobalNames
|
|
)
|
|
{
|
|
const int32 HeaderGlobalIndex = GlobalNames.Find(ParameterName);
|
|
check(HeaderGlobalIndex != INDEX_NONE);
|
|
check(GlobalNames[HeaderGlobalIndex] == ParameterName);
|
|
|
|
FVulkanShaderHeader::FGlobalInfo& GlobalInfo = SerializedOutput.Header.Globals[HeaderGlobalIndex];
|
|
const FVulkanSpirv::FEntry* Entry = SerializedOutput.Spirv.GetEntry(ParameterName);
|
|
if (Entry)
|
|
{
|
|
if (Entry->Binding == -1)
|
|
{
|
|
// Texel buffers get put into a uniform block
|
|
Entry = SerializedOutput.Spirv.GetEntry(ParameterName + TEXT("_BUFFER"));
|
|
check(Entry);
|
|
check(Entry->Binding != -1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Entry = SerializedOutput.Spirv.GetEntryByBindingIndex(BindingIndex);
|
|
check(Entry);
|
|
check(Entry->Binding != -1);
|
|
if (!Entry->Name.EndsWith(TEXT("_BUFFER")))
|
|
{
|
|
checkf(false, TEXT("CombinedSamplers should not be used anymore"))
|
|
}
|
|
}
|
|
|
|
const VkDescriptorType DescriptorType = DescriptorTypes[Entry->Binding];
|
|
|
|
GlobalInfo.OriginalBindingIndex = Entry->Binding;
|
|
SerializedOutput.Header.GlobalSpirvInfos[HeaderGlobalIndex] = FVulkanShaderHeader::FSpirvInfo(Entry->WordDescriptorSetIndex, Entry->WordBindingIndex);
|
|
|
|
const int32 GlobalDescriptorTypeIndex = SerializedOutput.Header.GlobalDescriptorTypes.Add(DescriptorTypeToBinding(DescriptorType));
|
|
GlobalInfo.TypeIndex = GlobalDescriptorTypeIndex;
|
|
GlobalInfo.CombinedSamplerStateAliasIndex = UINT16_MAX;
|
|
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
GlobalInfo.DebugName = ParameterName;
|
|
#endif
|
|
|
|
return HeaderGlobalIndex;
|
|
}
|
|
|
|
static void AddUBResources( const FString& UBName,
|
|
const FShaderResourceTableMap& ResourceTableMap,
|
|
uint32 BufferIndex,
|
|
const TArray<uint32>& BindingArray,
|
|
const TArray<VkDescriptorType>& DescriptorTypes,
|
|
FVulkanShaderHeader::FUniformBufferInfo& OutUBInfo,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
TArray<FString>& GlobalNames)
|
|
{
|
|
if (BindingArray.Num() > 0)
|
|
{
|
|
uint32 BufferOffset = BindingArray[BufferIndex];
|
|
if (BufferOffset > 0)
|
|
{
|
|
// Extract all resources related to the current BufferIndex
|
|
const uint32* ResourceInfos = &BindingArray[BufferOffset];
|
|
uint32 ResourceInfo = *ResourceInfos++;
|
|
do
|
|
{
|
|
// Verify that we have correct buffer index
|
|
check(FRHIResourceTableEntry::GetUniformBufferIndex(ResourceInfo) == BufferIndex);
|
|
|
|
// Extract binding index from ResourceInfo
|
|
const uint32 BindingIndex = FRHIResourceTableEntry::GetBindIndex(ResourceInfo);
|
|
|
|
// Extract index of the resource stored in the resource table from ResourceInfo
|
|
const uint16 ResourceIndex = FRHIResourceTableEntry::GetResourceIndex(ResourceInfo);
|
|
|
|
FUniformResourceEntry ResourceTableEntry;
|
|
GetResourceEntryFromUBMember(ResourceTableMap, UBName, ResourceIndex, ResourceTableEntry);
|
|
|
|
FVulkanShaderHeader::FUBResourceInfo& UBResourceInfo = OutUBInfo.ResourceEntries.AddZeroed_GetRef();;
|
|
|
|
const int32 HeaderGlobalIndex = AddGlobal(DescriptorTypes, ResourceTableEntry.UniformBufferMemberName, BindingIndex, SerializedOutput, GlobalNames);
|
|
UBResourceInfo.SourceUBResourceIndex = ResourceIndex;
|
|
UBResourceInfo.OriginalBindingIndex = BindingIndex;
|
|
UBResourceInfo.GlobalIndex = HeaderGlobalIndex;
|
|
UBResourceInfo.UBBaseType = (EUniformBufferBaseType)ResourceTableEntry.Type;
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
UBResourceInfo.DebugName = ResourceTableEntry.UniformBufferMemberName;
|
|
#endif
|
|
// Iterate to next info
|
|
ResourceInfo = *ResourceInfos++;
|
|
}
|
|
while (FRHIResourceTableEntry::GetUniformBufferIndex(ResourceInfo) == BufferIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void AddUniformBuffer(
|
|
const FShaderCompilerResourceTable& ShaderResourceTable,
|
|
const TArray<VkDescriptorType>& DescriptorTypes,
|
|
const FShaderCompilerInput& ShaderInput,
|
|
const FString& UBName,
|
|
uint16 BindingIndex,
|
|
FShaderParameterMap& InOutParameterMap,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
TArray<FString>& GlobalNames
|
|
)
|
|
{
|
|
FVulkanShaderHeader& OutHeader = SerializedOutput.Header;
|
|
|
|
const int32 HeaderUBIndex = OutHeader.UniformBuffers.AddZeroed();
|
|
FVulkanShaderHeader::FUniformBufferInfo& UBInfo = OutHeader.UniformBuffers[HeaderUBIndex];
|
|
|
|
const FUniformBufferEntry* UniformBufferEntry = ShaderInput.Environment.UniformBufferMap.Find(UBName);
|
|
if (UniformBufferEntry)
|
|
{
|
|
UBInfo.LayoutHash = UniformBufferEntry->LayoutHash;
|
|
}
|
|
else if ((UBName == FShaderParametersMetadata::kRootUniformBufferBindingName) && ShaderInput.RootParametersStructure)
|
|
{
|
|
UBInfo.LayoutHash = ShaderInput.RootParametersStructure->GetLayoutHash();
|
|
}
|
|
else
|
|
{
|
|
UBInfo.LayoutHash = 0;
|
|
}
|
|
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
UBInfo.DebugName = UBName;
|
|
#endif
|
|
|
|
const FVulkanSpirv::FEntry* Entry = SerializedOutput.Spirv.GetEntry(UBName);
|
|
if (Entry)
|
|
{
|
|
UBInfo.bOnlyHasResources = false;
|
|
UBInfo.ConstantDataOriginalBindingIndex = BindingIndex;
|
|
|
|
int32 SpirvInfoIndex = OutHeader.UniformBufferSpirvInfos.Add(FVulkanShaderHeader::FSpirvInfo(Entry->WordDescriptorSetIndex, Entry->WordBindingIndex));
|
|
check(SpirvInfoIndex == HeaderUBIndex);
|
|
}
|
|
else
|
|
{
|
|
UBInfo.bOnlyHasResources = true;
|
|
UBInfo.ConstantDataOriginalBindingIndex = UINT16_MAX;
|
|
|
|
int32 SpirvInfoIndex = OutHeader.UniformBufferSpirvInfos.Add(FVulkanShaderHeader::FSpirvInfo());
|
|
check(SpirvInfoIndex == HeaderUBIndex);
|
|
}
|
|
|
|
// Add used resources...
|
|
if (ShaderResourceTable.ResourceTableBits & (1 << BindingIndex))
|
|
{
|
|
// Make sure to process in the same order as when gathering names below
|
|
AddUBResources(UBName, ShaderInput.Environment.ResourceTableMap, BindingIndex, ShaderResourceTable.TextureMap, DescriptorTypes, UBInfo, SerializedOutput, GlobalNames);
|
|
AddUBResources(UBName, ShaderInput.Environment.ResourceTableMap, BindingIndex, ShaderResourceTable.SamplerMap, DescriptorTypes, UBInfo, SerializedOutput, GlobalNames);
|
|
AddUBResources(UBName, ShaderInput.Environment.ResourceTableMap, BindingIndex, ShaderResourceTable.ShaderResourceViewMap, DescriptorTypes, UBInfo, SerializedOutput, GlobalNames);
|
|
AddUBResources(UBName, ShaderInput.Environment.ResourceTableMap, BindingIndex, ShaderResourceTable.UnorderedAccessViewMap, DescriptorTypes, UBInfo, SerializedOutput, GlobalNames);
|
|
}
|
|
else
|
|
{
|
|
// If we're using real uniform buffers we have to have resources at least
|
|
checkf(!UBInfo.bOnlyHasResources, TEXT("UBName = %s, BindingIndex = %d"), *UBName, (int32)BindingIndex);
|
|
}
|
|
|
|
// Currently we don't support mismatched uniform buffer layouts/cbuffers with resources!
|
|
check(UniformBufferEntry || UBInfo.ResourceEntries.Num() == 0);
|
|
|
|
InOutParameterMap.RemoveParameterAllocation(*UBName);
|
|
InOutParameterMap.AddParameterAllocation(*UBName, HeaderUBIndex, (uint16)FVulkanShaderHeader::UniformBuffer, 1, EShaderParameterType::UniformBuffer);
|
|
}
|
|
|
|
static int32 DoAddGlobal(const FString& Name, FVulkanShaderHeader& OutHeader, TArray<FString>& OutGlobalNames)
|
|
{
|
|
check(!OutGlobalNames.Contains(Name));
|
|
const int32 NameIndex = OutGlobalNames.Add(Name);
|
|
const int32 GlobalIndex = OutHeader.Globals.AddDefaulted();
|
|
check(NameIndex == GlobalIndex);
|
|
const int32 GlobalSpirvIndex = OutHeader.GlobalSpirvInfos.AddDefaulted();
|
|
check(GlobalSpirvIndex == GlobalIndex);
|
|
return GlobalIndex;
|
|
}
|
|
|
|
static void PrepareUBResourceEntryGlobals(const TArray<uint32>& BindingArray, const FShaderResourceTableMap& ResourceTableMap,
|
|
int32 BufferIndex, const FString& UBName, TArray<FString>& OutGlobalNames, FVulkanShaderHeader& OutHeader)
|
|
{
|
|
if (BindingArray.Num() > 0)
|
|
{
|
|
uint32 BufferOffset = BindingArray[BufferIndex];
|
|
if (BufferOffset > 0)
|
|
{
|
|
// Extract all resources related to the current BufferIndex
|
|
const uint32* ResourceInfos = &BindingArray[BufferOffset];
|
|
uint32 ResourceInfo = *ResourceInfos++;
|
|
do
|
|
{
|
|
// Verify that we have correct buffer index
|
|
check(FRHIResourceTableEntry::GetUniformBufferIndex(ResourceInfo) == BufferIndex);
|
|
|
|
// Extract index of the resource stored in the resource table from ResourceInfo
|
|
const uint16 ResourceIndex = FRHIResourceTableEntry::GetResourceIndex(ResourceInfo);
|
|
|
|
FUniformResourceEntry ResourceTableEntry;
|
|
GetResourceEntryFromUBMember(ResourceTableMap, UBName, ResourceIndex, ResourceTableEntry);
|
|
|
|
DoAddGlobal(ResourceTableEntry.UniformBufferMemberName, OutHeader, OutGlobalNames);
|
|
|
|
// Iterate to next info
|
|
ResourceInfo = *ResourceInfos++;
|
|
}
|
|
while (FRHIResourceTableEntry::GetUniformBufferIndex(ResourceInfo) == BufferIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void PrepareGlobals(const FVulkanBindingTable& BindingTable, const FSpirvReflectBindings& SpirvReflectBindings, const FShaderCompilerResourceTable& SRT, const TMap<FString, FVulkanShaderHeader::EType>& EntryTypes, const FShaderCompilerInput& ShaderInput, const TArray<FString>& ParameterNames, FShaderParameterMap& ParameterMap, TArray<FString>& OutGlobalNames, FVulkanShaderHeader& OutHeader)
|
|
{
|
|
auto IsSamplerState = [&SpirvReflectBindings](const FString& ParameterName)
|
|
{
|
|
for (SpvReflectDescriptorBinding* DescriptorBinding : SpirvReflectBindings.Samplers)
|
|
{
|
|
if (ParameterName == DescriptorBinding->name)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// First pass, gather names for all the Globals that are NOT Samplers
|
|
{
|
|
auto AddGlobalNamesForUB = [&](const FString& ParameterName)
|
|
{
|
|
TOptional<FParameterAllocation> ParameterAllocation = ParameterMap.FindParameterAllocation(*ParameterName);
|
|
checkf(ParameterAllocation.IsSet(), TEXT("PrepareGlobals failed to find resource ParameterName=%s"), *ParameterName);
|
|
|
|
// Add used resources...
|
|
if (SRT.ResourceTableBits & (1 << ParameterAllocation->BufferIndex))
|
|
{
|
|
PrepareUBResourceEntryGlobals(SRT.TextureMap, ShaderInput.Environment.ResourceTableMap, ParameterAllocation->BufferIndex, ParameterName, OutGlobalNames, OutHeader);
|
|
PrepareUBResourceEntryGlobals(SRT.ShaderResourceViewMap, ShaderInput.Environment.ResourceTableMap, ParameterAllocation->BufferIndex, ParameterName, OutGlobalNames, OutHeader);
|
|
PrepareUBResourceEntryGlobals(SRT.UnorderedAccessViewMap, ShaderInput.Environment.ResourceTableMap, ParameterAllocation->BufferIndex, ParameterName, OutGlobalNames, OutHeader);
|
|
}
|
|
};
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < ParameterNames.Num(); ++ParameterIndex)
|
|
{
|
|
const FString& ParameterName = ParameterNames[ParameterIndex];
|
|
const FVulkanShaderHeader::EType* FoundType = EntryTypes.Find(ParameterName);
|
|
if (FoundType)
|
|
{
|
|
switch (*FoundType)
|
|
{
|
|
case FVulkanShaderHeader::Global:
|
|
if (!IsSamplerState(ParameterName))
|
|
{
|
|
DoAddGlobal(ParameterName, OutHeader, OutGlobalNames);
|
|
}
|
|
break;
|
|
case FVulkanShaderHeader::UniformBuffer:
|
|
AddGlobalNamesForUB(ParameterName);
|
|
break;
|
|
case FVulkanShaderHeader::PackedGlobal:
|
|
// Ignore
|
|
break;
|
|
default:
|
|
check(0);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddGlobalNamesForUB(ParameterName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass, add all samplers
|
|
{
|
|
auto AddGlobalNamesForUB = [&](const FString& ParameterName)
|
|
{
|
|
TOptional<FParameterAllocation> ParameterAllocation = ParameterMap.FindParameterAllocation(*ParameterName);
|
|
checkf(ParameterAllocation.IsSet(), TEXT("PrepareGlobals failed to find sampler ParameterName=%s"), *ParameterName);
|
|
|
|
// Add used resources...
|
|
if (SRT.ResourceTableBits & (1 << ParameterAllocation->BufferIndex))
|
|
{
|
|
PrepareUBResourceEntryGlobals(SRT.SamplerMap, ShaderInput.Environment.ResourceTableMap, ParameterAllocation->BufferIndex, ParameterName, OutGlobalNames, OutHeader);
|
|
}
|
|
};
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < ParameterNames.Num(); ++ParameterIndex)
|
|
{
|
|
const FString& ParameterName = ParameterNames[ParameterIndex];
|
|
const FVulkanShaderHeader::EType* FoundType = EntryTypes.Find(ParameterName);
|
|
if (FoundType)
|
|
{
|
|
switch (*FoundType)
|
|
{
|
|
case FVulkanShaderHeader::Global:
|
|
if (IsSamplerState(ParameterName))
|
|
{
|
|
DoAddGlobal(ParameterName, OutHeader, OutGlobalNames);
|
|
}
|
|
break;
|
|
case FVulkanShaderHeader::UniformBuffer:
|
|
AddGlobalNamesForUB(ParameterName);
|
|
break;
|
|
case FVulkanShaderHeader::PackedGlobal:
|
|
break;
|
|
default:
|
|
check(0);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddGlobalNamesForUB(ParameterName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now input attachments
|
|
if (BindingTable.InputAttachmentsMask != 0)
|
|
{
|
|
uint32 InputAttachmentsMask = BindingTable.InputAttachmentsMask;
|
|
for (int32 Index = 0; InputAttachmentsMask != 0; ++Index, InputAttachmentsMask>>= 1)
|
|
{
|
|
if (InputAttachmentsMask & 1)
|
|
{
|
|
DoAddGlobal(VULKAN_SUBPASS_FETCH_VAR_W[Index], OutHeader, OutGlobalNames);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ConvertToHeader(
|
|
FShaderCompilerResourceTable& ShaderResourceTable,
|
|
const FVulkanBindingTable& BindingTable,
|
|
const TArray<VkDescriptorType>& DescriptorTypes,
|
|
const TMap<FString, FVulkanShaderHeader::EType>& EntryTypes,
|
|
const FShaderCompilerInput& ShaderInput,
|
|
const FSpirvReflectBindings& SpirvReflectBindings,
|
|
FShaderParameterMap& InOutParameterMap,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput
|
|
)
|
|
{
|
|
FVulkanShaderHeader& OutHeader = SerializedOutput.Header;
|
|
|
|
// Names that match the Header.Globals array
|
|
TArray<FString> GlobalNames;
|
|
|
|
TArray<FString> ParameterNames;
|
|
InOutParameterMap.GetAllParameterNames(ParameterNames);
|
|
|
|
PrepareGlobals(BindingTable, SpirvReflectBindings, ShaderResourceTable, EntryTypes, ShaderInput, ParameterNames, InOutParameterMap, GlobalNames, OutHeader);
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < ParameterNames.Num(); ++ParameterIndex)
|
|
{
|
|
uint16 BufferIndex;
|
|
uint16 BaseIndex;
|
|
uint16 Size;
|
|
const FString& ParameterName = *ParameterNames[ParameterIndex];
|
|
const bool bFoundParam = InOutParameterMap.FindParameterAllocation(*ParameterName, BufferIndex, BaseIndex, Size);
|
|
check(bFoundParam);
|
|
|
|
const FVulkanShaderHeader::EType* FoundType = EntryTypes.Find(ParameterName);
|
|
if (FoundType)
|
|
{
|
|
switch (*FoundType)
|
|
{
|
|
case FVulkanShaderHeader::Global:
|
|
{
|
|
const int32 HeaderGlobalIndex = AddGlobal(DescriptorTypes, ParameterName, BaseIndex, SerializedOutput, GlobalNames);
|
|
|
|
const FParameterAllocation* ParameterAllocation = InOutParameterMap.GetParameterMap().Find(*ParameterName);
|
|
check(ParameterAllocation);
|
|
const EShaderParameterType ParamType = ParameterAllocation->Type;
|
|
|
|
InOutParameterMap.RemoveParameterAllocation(*ParameterName);
|
|
InOutParameterMap.AddParameterAllocation(*ParameterName, (uint16)FVulkanShaderHeader::Global, HeaderGlobalIndex, Size, ParamType);
|
|
}
|
|
break;
|
|
case FVulkanShaderHeader::PackedGlobal:
|
|
{
|
|
FVulkanShaderHeader::FPackedGlobalInfo& PackedGlobalInfo = OutHeader.PackedGlobals.AddZeroed_GetRef();
|
|
PackedGlobalInfo.PackedTypeIndex = CrossCompiler::EPackedTypeIndex::HighP;
|
|
PackedGlobalInfo.PackedUBIndex = BufferIndex;
|
|
checkf(Size > 0, TEXT("Assertion failed for shader parameter: %s"), *ParameterName);
|
|
PackedGlobalInfo.ConstantDataSizeInFloats = Size / sizeof(float);
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
PackedGlobalInfo.DebugName = ParameterName;
|
|
#endif
|
|
// Keep the original parameter info from InOutParameterMap as it's a shortcut into the packed global array!
|
|
}
|
|
break;
|
|
case FVulkanShaderHeader::UniformBuffer:
|
|
AddUniformBuffer(ShaderResourceTable, DescriptorTypes, ShaderInput, ParameterName, BufferIndex, InOutParameterMap, SerializedOutput, GlobalNames);
|
|
break;
|
|
default:
|
|
check(0);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not found means it's a new resource-only UniformBuffer
|
|
AddUniformBuffer(ShaderResourceTable, DescriptorTypes, ShaderInput, ParameterName, BufferIndex, InOutParameterMap, SerializedOutput, GlobalNames);
|
|
}
|
|
}
|
|
|
|
// Finally check for subpass/input attachments
|
|
if (BindingTable.InputAttachmentsMask != 0)
|
|
{
|
|
const static FVulkanShaderHeader::EAttachmentType AttachmentTypes[] =
|
|
{
|
|
FVulkanShaderHeader::EAttachmentType::Depth,
|
|
FVulkanShaderHeader::EAttachmentType::Color0,
|
|
FVulkanShaderHeader::EAttachmentType::Color1,
|
|
FVulkanShaderHeader::EAttachmentType::Color2,
|
|
FVulkanShaderHeader::EAttachmentType::Color3,
|
|
FVulkanShaderHeader::EAttachmentType::Color4,
|
|
FVulkanShaderHeader::EAttachmentType::Color5,
|
|
FVulkanShaderHeader::EAttachmentType::Color6,
|
|
FVulkanShaderHeader::EAttachmentType::Color7
|
|
};
|
|
|
|
uint32 InputAttachmentsMask = BindingTable.InputAttachmentsMask;
|
|
for (int32 Index = 0; InputAttachmentsMask != 0; ++Index, InputAttachmentsMask>>=1)
|
|
{
|
|
if ((InputAttachmentsMask & 1) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString AttachmentName(VULKAN_SUBPASS_FETCH_VAR_W[Index]);
|
|
const VkDescriptorType DescriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
|
const FVulkanShaderHeader::EAttachmentType AttachmentType = AttachmentTypes[Index];
|
|
{
|
|
const int32 HeaderGlobalIndex = GlobalNames.Find(AttachmentName);
|
|
check(HeaderGlobalIndex != INDEX_NONE);
|
|
check(GlobalNames[HeaderGlobalIndex] == AttachmentName);
|
|
FVulkanShaderHeader::FGlobalInfo& GlobalInfo = OutHeader.Globals[HeaderGlobalIndex];
|
|
const FVulkanSpirv::FEntry* Entry = SerializedOutput.Spirv.GetEntry(AttachmentName);
|
|
check(Entry);
|
|
check(Entry->Binding != -1);
|
|
|
|
GlobalInfo.OriginalBindingIndex = Entry->Binding;
|
|
OutHeader.GlobalSpirvInfos[HeaderGlobalIndex] = FVulkanShaderHeader::FSpirvInfo(Entry->WordDescriptorSetIndex, Entry->WordBindingIndex);
|
|
const int32 GlobalDescriptorTypeIndex = OutHeader.GlobalDescriptorTypes.Add(DescriptorTypeToBinding(DescriptorType));
|
|
GlobalInfo.TypeIndex = GlobalDescriptorTypeIndex;
|
|
GlobalInfo.CombinedSamplerStateAliasIndex = UINT16_MAX;
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
GlobalInfo.DebugName = AttachmentName;
|
|
#endif
|
|
FVulkanShaderHeader::FInputAttachment& AttachmentInfo = OutHeader.InputAttachments.AddZeroed_GetRef();
|
|
AttachmentInfo.GlobalIndex = HeaderGlobalIndex;
|
|
AttachmentInfo.Type = AttachmentType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Fills the SRT using final values kept in the FVulkanShaderHeader.
|
|
// NOTE: Uses GlobalIndex so it can be consumed directly at runtime.
|
|
// NOTE: Keep in sync with BuildResourceTableMapping.
|
|
static FShaderResourceTable BuildSRTFromHeader(const FVulkanShaderHeader& NEWHeader)
|
|
{
|
|
FShaderResourceTable ShaderResourceTable;
|
|
|
|
TArray<uint32> TextureMap;
|
|
TArray<uint32> ShaderResourceViewMap;
|
|
TArray<uint32> SamplerMap;
|
|
TArray<uint32> UnorderedAccessViewMap;
|
|
|
|
for (int32 UBIndex = 0; UBIndex < NEWHeader.UniformBuffers.Num(); ++UBIndex)
|
|
{
|
|
const FVulkanShaderHeader::FUniformBufferInfo& UBHeader = NEWHeader.UniformBuffers[UBIndex];
|
|
|
|
ShaderResourceTable.ResourceTableLayoutHashes.Emplace(UBHeader.LayoutHash);
|
|
if (UBHeader.ResourceEntries.Num() > 0)
|
|
{
|
|
ShaderResourceTable.ResourceTableBits |= 1 << UBIndex;
|
|
|
|
for (const FVulkanShaderHeader::FUBResourceInfo& UBRes : UBHeader.ResourceEntries)
|
|
{
|
|
uint32 ResourceMap = FRHIResourceTableEntry::Create(UBIndex, UBRes.SourceUBResourceIndex, UBRes.GlobalIndex);
|
|
switch (UBRes.UBBaseType)
|
|
{
|
|
case UBMT_TEXTURE:
|
|
case UBMT_RDG_TEXTURE:
|
|
TextureMap.Add(ResourceMap);
|
|
break;
|
|
case UBMT_SAMPLER:
|
|
SamplerMap.Add(ResourceMap);
|
|
break;
|
|
case UBMT_SRV:
|
|
case UBMT_RDG_TEXTURE_SRV:
|
|
case UBMT_RDG_BUFFER_SRV:
|
|
ShaderResourceViewMap.Add(ResourceMap);
|
|
break;
|
|
case UBMT_UAV:
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
case UBMT_RDG_BUFFER_UAV:
|
|
UnorderedAccessViewMap.Add(ResourceMap);
|
|
break;
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 MaxBoundResourceTable = NEWHeader.UniformBuffers.Num();
|
|
BuildResourceTableTokenStream(TextureMap, MaxBoundResourceTable, ShaderResourceTable.TextureMap);
|
|
BuildResourceTableTokenStream(ShaderResourceViewMap, MaxBoundResourceTable, ShaderResourceTable.ShaderResourceViewMap);
|
|
BuildResourceTableTokenStream(SamplerMap, MaxBoundResourceTable, ShaderResourceTable.SamplerMap);
|
|
BuildResourceTableTokenStream(UnorderedAccessViewMap, MaxBoundResourceTable, ShaderResourceTable.UnorderedAccessViewMap);
|
|
|
|
return ShaderResourceTable;
|
|
}
|
|
|
|
|
|
|
|
static void BuildShaderOutput(
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
FShaderCompilerOutput& ShaderOutput,
|
|
const FVulkanShaderCompilerInternalState& InternalState,
|
|
const FSpirvReflectBindings& SpirvReflectBindings,
|
|
TMap<FString, FVulkanShaderHeader::EType>& EntryTypes,
|
|
const FVulkanBindingTable& BindingTable,
|
|
const FString& DebugName,
|
|
uint32 PackedGlobalArraySize,
|
|
TBitArray<>& UsedUniformBufferSlots
|
|
)
|
|
{
|
|
const FShaderCompilerInput& ShaderInput = InternalState.Input;
|
|
const EShaderFrequency Frequency = InternalState.GetShaderFrequency();
|
|
|
|
FVulkanShaderHeader& NEWHeader = SerializedOutput.Header;
|
|
|
|
NEWHeader.SpirvCRC = SerializedOutput.Spirv.CRC;
|
|
NEWHeader.RayTracingPayloadType = ShaderInput.Environment.GetCompileArgument(TEXT("RT_PAYLOAD_TYPE"), 0u);
|
|
NEWHeader.RayTracingPayloadSize = ShaderInput.Environment.GetCompileArgument(TEXT("RT_PAYLOAD_MAX_SIZE"), 0u);
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
NEWHeader.DebugName = DebugName;
|
|
#endif
|
|
|
|
// :todo-jn: Hash entire SPIRV for now, could eventually be removed since we use ShaderKeys
|
|
FSHA1::HashBuffer(SerializedOutput.Spirv.Data.GetData(), SerializedOutput.Spirv.GetByteSize(), (uint8*)&NEWHeader.SourceHash);
|
|
|
|
|
|
// Flattens the array dimensions of the interface variable (aka shader attribute), e.g. from float4[2][3] -> float4[6]
|
|
auto FlattenAttributeArrayDimension = [](const SpvReflectInterfaceVariable& Attribute, uint32 FirstArrayDim = 0)
|
|
{
|
|
uint32 FlattenedArrayDim = 1;
|
|
for (uint32 ArrayDimIndex = FirstArrayDim; ArrayDimIndex < Attribute.array.dims_count; ++ArrayDimIndex)
|
|
{
|
|
FlattenedArrayDim *= Attribute.array.dims[ArrayDimIndex];
|
|
}
|
|
return FlattenedArrayDim;
|
|
};
|
|
|
|
|
|
// Only process input attributes for vertex shaders.
|
|
if (Frequency == SF_Vertex)
|
|
{
|
|
static const FString AttributePrefix = TEXT("ATTRIBUTE");
|
|
|
|
for (const SpvReflectInterfaceVariable* Attribute : SpirvReflectBindings.InputAttributes)
|
|
{
|
|
if (CrossCompiler::FShaderConductorContext::IsIntermediateSpirvOutputVariable(Attribute->name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Attribute->semantic)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString InputAttrName(ANSI_TO_TCHAR(Attribute->semantic));
|
|
if (InputAttrName.StartsWith(AttributePrefix))
|
|
{
|
|
const uint32 AttributeIndex = ParseNumber(*InputAttrName + AttributePrefix.Len(), /*bEmptyIsZero:*/ true);
|
|
const uint32 FlattenedArrayDim = FlattenAttributeArrayDimension(*Attribute);
|
|
for (uint32 Index = 0; Index < FlattenedArrayDim; ++Index)
|
|
{
|
|
const uint32 BitIndex = (AttributeIndex + Index);
|
|
NEWHeader.InOutMask |= (1u << BitIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only process output attributes for pixel shaders.
|
|
if (Frequency == SF_Pixel)
|
|
{
|
|
static const FString TargetPrefix = "SV_Target";
|
|
|
|
for (const SpvReflectInterfaceVariable* Attribute : SpirvReflectBindings.OutputAttributes)
|
|
{
|
|
// Only depth writes for pixel shaders must be tracked.
|
|
if (Attribute->built_in == SpvBuiltInFragDepth)
|
|
{
|
|
const uint32 BitIndex = (CrossCompiler::FShaderBindingInOutMask::DepthStencilMaskIndex);
|
|
NEWHeader.InOutMask |= (1u << BitIndex);
|
|
}
|
|
else
|
|
{
|
|
// Only targets for pixel shaders must be tracked.
|
|
const FString OutputAttrName(ANSI_TO_TCHAR(Attribute->semantic));
|
|
if (OutputAttrName.StartsWith(TargetPrefix))
|
|
{
|
|
const uint32 TargetIndex = ParseNumber(*OutputAttrName + TargetPrefix.Len(), /*bEmptyIsZero:*/ true);
|
|
|
|
const uint32 FlattenedArrayDim = FlattenAttributeArrayDimension(*Attribute);
|
|
for (uint32 Index = 0; Index < FlattenedArrayDim; ++Index)
|
|
{
|
|
const uint32 BitIndex = (TargetIndex + Index);
|
|
NEWHeader.InOutMask |= (1u << BitIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 StageOffset = InternalState.bSupportsBindless ? (ShaderStage::GetStageForFrequency(Frequency) * VulkanBindless::MaxUniformBuffersPerStage) : 0;
|
|
|
|
TArray<VkDescriptorType> DescriptorTypes;
|
|
const TArray<FVulkanBindingTable::FBinding>& HlslccBindings = BindingTable.GetBindings();
|
|
for (int32 Index = 0; Index < HlslccBindings.Num(); ++Index)
|
|
{
|
|
const FVulkanBindingTable::FBinding& Binding = HlslccBindings[Index];
|
|
DescriptorTypes.Add(BindingToDescriptorType(Binding.Type));
|
|
}
|
|
|
|
//#todo-rco: When using regular UBs, also set UsedUniformBufferSlots[] = 1
|
|
|
|
TArray<FString> OriginalParameters;
|
|
ShaderOutput.ParameterMap.GetAllParameterNames(OriginalParameters);
|
|
|
|
// Build the SRT for this shader.
|
|
FShaderCompilerResourceTable ShaderResourceTable;
|
|
{
|
|
// Build the generic SRT for this shader.
|
|
FShaderCompilerResourceTable GenericSRT;
|
|
if (!BuildResourceTableMapping(ShaderInput.Environment.ResourceTableMap, ShaderInput.Environment.UniformBufferMap, UsedUniformBufferSlots, ShaderOutput.ParameterMap, /*MaxBoundResourceTable, */GenericSRT))
|
|
{
|
|
ShaderOutput.Errors.Add(TEXT("Internal error on BuildResourceTableMapping."));
|
|
return;
|
|
}
|
|
|
|
// Copy over the bits indicating which resource tables are active.
|
|
ShaderResourceTable.ResourceTableBits = GenericSRT.ResourceTableBits;
|
|
ShaderResourceTable.ResourceTableLayoutHashes = GenericSRT.ResourceTableLayoutHashes;
|
|
|
|
// Now build our token streams.
|
|
BuildResourceTableTokenStream(GenericSRT.TextureMap, GenericSRT.MaxBoundResourceTable, ShaderResourceTable.TextureMap, true);
|
|
BuildResourceTableTokenStream(GenericSRT.ShaderResourceViewMap, GenericSRT.MaxBoundResourceTable, ShaderResourceTable.ShaderResourceViewMap, true);
|
|
BuildResourceTableTokenStream(GenericSRT.SamplerMap, GenericSRT.MaxBoundResourceTable, ShaderResourceTable.SamplerMap, true);
|
|
BuildResourceTableTokenStream(GenericSRT.UnorderedAccessViewMap, GenericSRT.MaxBoundResourceTable, ShaderResourceTable.UnorderedAccessViewMap, true);
|
|
}
|
|
|
|
TArray<FString> NewParameters;
|
|
ShaderOutput.ParameterMap.GetAllParameterNames(NewParameters);
|
|
|
|
// Mark all used uniform buffer indices; however some are empty (eg GBuffers) so gather those as NewParameters
|
|
uint16 NumParams = 0;
|
|
for (int32 Index = NewParameters.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
uint16 OutIndex, OutBase, OutSize;
|
|
const bool bFound = ShaderOutput.ParameterMap.FindParameterAllocation(*NewParameters[Index], OutIndex, OutBase, OutSize);
|
|
ensure(bFound);
|
|
NumParams = FMath::Max((uint16)(OutIndex + 1), NumParams);
|
|
if (OriginalParameters.Contains(NewParameters[Index]))
|
|
{
|
|
NewParameters.RemoveAtSwap(Index, 1, false);
|
|
}
|
|
}
|
|
|
|
if (PackedGlobalArraySize > 0)
|
|
{
|
|
FVulkanShaderHeader::FPackedUBInfo& PackedUB = NEWHeader.PackedUBs.AddZeroed_GetRef();
|
|
PackedUB.OriginalBindingIndex = StageOffset;
|
|
PackedUB.PackedTypeIndex = CrossCompiler::EPackedTypeIndex::HighP;
|
|
PackedUB.SizeInBytes = Align(PackedGlobalArraySize, 16u);
|
|
|
|
const FVulkanSpirv::FEntry* Entry = SerializedOutput.Spirv.GetEntryByBindingIndex(StageOffset);
|
|
check(Entry);
|
|
PackedUB.SPIRVDescriptorSetOffset = Entry->WordDescriptorSetIndex;
|
|
PackedUB.SPIRVBindingIndexOffset = Entry->WordBindingIndex;
|
|
}
|
|
|
|
ConvertToHeader(ShaderResourceTable, BindingTable, DescriptorTypes, EntryTypes, ShaderInput, SpirvReflectBindings, ShaderOutput.ParameterMap, SerializedOutput);
|
|
check(NEWHeader.EmulatedUBsCopyInfo.Num() == 0);
|
|
|
|
if (ShaderInput.Environment.CompilerFlags.Contains(CFLAG_ExtraShaderData))
|
|
{
|
|
NEWHeader.DebugName = ShaderInput.GenerateShaderName();
|
|
}
|
|
|
|
// Build the SRT for this shader from the NEWHeader
|
|
SerializedOutput.ShaderResourceTable = BuildSRTFromHeader(NEWHeader);
|
|
|
|
ShaderOutput.bSucceeded = true;
|
|
|
|
// guard disassembly of SPIRV code on bExtractShaderSource setting since presumably this isn't that cheap.
|
|
// this roughly will maintain existing behaviour, except the debug usf will be this version of the code
|
|
// instead of the output of preprocessing if this setting is enabled (which is probably fine since this is only
|
|
// ever set in editor)
|
|
if (ShaderInput.ExtraSettings.bExtractShaderSource)
|
|
{
|
|
TArray<ANSICHAR> AssemblyText;
|
|
if (CrossCompiler::FShaderConductorContext::Disassemble(CrossCompiler::EShaderConductorIR::Spirv, SerializedOutput.Spirv.GetByteData(), SerializedOutput.Spirv.GetByteSize(), AssemblyText))
|
|
{
|
|
ShaderOutput.ModifiedShaderSource = FString(AssemblyText.GetData());
|
|
}
|
|
}
|
|
if (ShaderInput.ExtraSettings.OfflineCompilerPath.Len() > 0)
|
|
{
|
|
if (SupportsOfflineCompiler(ShaderInput.ShaderFormat))
|
|
{
|
|
CompileOfflineMali(ShaderInput, ShaderOutput, (const ANSICHAR*)SerializedOutput.Spirv.GetByteData(), SerializedOutput.Spirv.GetByteSize(), true, SerializedOutput.Spirv.EntryPointName);
|
|
}
|
|
}
|
|
|
|
// Ray generation shaders rely on a different binding model that aren't compatible with global uniform buffers.
|
|
if (!InternalState.IsRayTracingShader())
|
|
{
|
|
CullGlobalUniformBuffers(ShaderInput.Environment.UniformBufferMap, ShaderOutput.ParameterMap);
|
|
}
|
|
}
|
|
|
|
|
|
#if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
|
|
|
|
static void GatherSpirvReflectionBindings(
|
|
spv_reflect::ShaderModule& Reflection,
|
|
FSpirvReflectBindings& OutBindings,
|
|
TSet<FString>& OutBindlessUB,
|
|
const FVulkanShaderCompilerInternalState& InternalState)
|
|
{
|
|
// Change descriptor set numbers
|
|
TArray<SpvReflectDescriptorSet*> DescriptorSets;
|
|
uint32 NumDescriptorSets = 0;
|
|
|
|
// If bindless is supported, then offset the descriptor set to fit the bindless heaps at the beginning
|
|
const EShaderFrequency ShaderFrequency = InternalState.GetShaderFrequency();
|
|
const uint32 StageIndex = (uint32)ShaderStage::GetStageForFrequency(ShaderFrequency);
|
|
const uint32 DescSetNo = InternalState.bSupportsBindless ? VulkanBindless::NumBindlessSets + StageIndex : StageIndex;
|
|
|
|
SpvReflectResult SpvResult = Reflection.EnumerateDescriptorSets(&NumDescriptorSets, nullptr);
|
|
check(SpvResult == SPV_REFLECT_RESULT_SUCCESS);
|
|
if (NumDescriptorSets > 0)
|
|
{
|
|
DescriptorSets.SetNum(NumDescriptorSets);
|
|
SpvResult = Reflection.EnumerateDescriptorSets(&NumDescriptorSets, DescriptorSets.GetData());
|
|
check(SpvResult == SPV_REFLECT_RESULT_SUCCESS);
|
|
|
|
for (const SpvReflectDescriptorSet* DescSet : DescriptorSets)
|
|
{
|
|
Reflection.ChangeDescriptorSetNumber(DescSet, DescSetNo);
|
|
}
|
|
}
|
|
|
|
OutBindings.GatherInputAttributes(Reflection);
|
|
OutBindings.GatherOutputAttributes(Reflection);
|
|
OutBindings.GatherDescriptorBindings(Reflection);
|
|
|
|
// Storage buffers always occupy a UAV binding slot, so move all SBufferSRVs into the SBufferUAVs array
|
|
OutBindings.SBufferUAVs.Append(OutBindings.SBufferSRVs);
|
|
OutBindings.SBufferSRVs.Empty();
|
|
|
|
// Change indices of input attributes by their name suffix. Only in the vertex shader stage, "ATTRIBUTE" semantics have a special meaning for shader attributes.
|
|
if (ShaderFrequency == SF_Vertex)
|
|
{
|
|
OutBindings.AssignInputAttributeLocationsBySemanticIndex(Reflection, CrossCompiler::FShaderConductorContext::GetIdentifierTable().InputAttribute);
|
|
}
|
|
|
|
// Patch resource heaps descriptor set numbers
|
|
if (InternalState.bSupportsBindless)
|
|
{
|
|
// Move the bindless heap to its dedicated descriptor set and remove it from our regular binding arrays
|
|
auto MoveBindlessHeaps = [&](TArray<SpvReflectDescriptorBinding*>& BindingArray, const TCHAR* HeapPrefix, uint32 BinldessDescSetNo)
|
|
{
|
|
for (int32 Index = BindingArray.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
const SpvReflectDescriptorBinding* pBinding = BindingArray[Index];
|
|
const FString BindingName(ANSI_TO_TCHAR(pBinding->name));
|
|
if (BindingName.StartsWith(HeapPrefix))
|
|
{
|
|
const uint32 Binding = 0; // single bindless heap per descriptor set
|
|
Reflection.ChangeDescriptorBindingNumbers(pBinding, Binding, BinldessDescSetNo);
|
|
BindingArray.RemoveAtSwap(Index);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Remove sampler heaps from binding arrays
|
|
MoveBindlessHeaps(OutBindings.Samplers, FShaderParameterParser::kBindlessSamplerArrayPrefix, VulkanBindless::BindlessSamplerSet);
|
|
|
|
// Remove resource heaps from binding arrays
|
|
MoveBindlessHeaps(OutBindings.SBufferUAVs, FShaderParameterParser::kBindlessUAVArrayPrefix, VulkanBindless::BindlessStorageBufferSet);
|
|
MoveBindlessHeaps(OutBindings.SBufferUAVs, FShaderParameterParser::kBindlessSRVArrayPrefix, VulkanBindless::BindlessStorageBufferSet); // try with both prefixes, they were merged earlier
|
|
MoveBindlessHeaps(OutBindings.TextureSRVs, FShaderParameterParser::kBindlessSRVArrayPrefix, VulkanBindless::BindlessSampledImageSet);
|
|
MoveBindlessHeaps(OutBindings.TextureUAVs, FShaderParameterParser::kBindlessUAVArrayPrefix, VulkanBindless::BindlessStorageImageSet);
|
|
MoveBindlessHeaps(OutBindings.TextureUAVs, FShaderParameterParser::kBindlessSRVArrayPrefix, VulkanBindless::BindlessStorageImageSet); // try with both prefixes, R64 SRV textures are read as storage images
|
|
MoveBindlessHeaps(OutBindings.TBufferSRVs, FShaderParameterParser::kBindlessSRVArrayPrefix, VulkanBindless::BindlessUniformTexelBufferSet);
|
|
MoveBindlessHeaps(OutBindings.TBufferUAVs, FShaderParameterParser::kBindlessUAVArrayPrefix, VulkanBindless::BindlessStorageTexelBufferSet);
|
|
MoveBindlessHeaps(OutBindings.AccelerationStructures, FShaderParameterParser::kBindlessSRVArrayPrefix, VulkanBindless::BindlessAccelerationStructureSet);
|
|
|
|
// Move uniform buffers to the correct set
|
|
{
|
|
const uint32 BindingOffset = (StageIndex * VulkanBindless::MaxUniformBuffersPerStage);
|
|
for (int32 Index = OutBindings.UniformBuffers.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
const SpvReflectDescriptorBinding* pBinding = OutBindings.UniformBuffers[Index];
|
|
const FString BindingName(ANSI_TO_TCHAR(pBinding->name));
|
|
if (BindingName.StartsWith(kBindlessCBPrefix))
|
|
{
|
|
check(InternalState.bUseBindlessUniformBuffer);
|
|
Reflection.ChangeDescriptorBindingNumbers(pBinding, 0, VulkanBindless::BindlessUniformBufferSet);
|
|
const FString BindlessUBName = GetBindlessUBNameFromHeap(BindingName);
|
|
checkf(InternalState.AllBindlessUBs.Contains(BindlessUBName), TEXT("Bindless Uniform Buffer was found in SPIRV but not tracked in internal state"));
|
|
OutBindlessUB.Add(BindlessUBName);
|
|
OutBindings.UniformBuffers.RemoveAtSwap(Index);
|
|
}
|
|
else
|
|
{
|
|
Reflection.ChangeDescriptorBindingNumbers(pBinding, BindingOffset + pBinding->binding, VulkanBindless::BindlessSingleUseUniformBufferSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32 CalculateSpirvInstructionCount(FVulkanSpirv& Spirv)
|
|
{
|
|
// Count instructions inside functions
|
|
bool bInsideFunction = false;
|
|
uint32 ApproxInstructionCount = 0;
|
|
for (FSpirvConstIterator Iter = Spirv.cbegin(); Iter != Spirv.cend(); ++Iter)
|
|
{
|
|
switch (Iter.Opcode())
|
|
{
|
|
|
|
case SpvOpFunction:
|
|
{
|
|
check(!bInsideFunction);
|
|
bInsideFunction = true;
|
|
}
|
|
break;
|
|
|
|
case SpvOpFunctionEnd:
|
|
{
|
|
check(bInsideFunction);
|
|
bInsideFunction = false;
|
|
}
|
|
break;
|
|
|
|
case SpvOpLabel:
|
|
case SpvOpAccessChain:
|
|
case SpvOpSelectionMerge:
|
|
case SpvOpCompositeConstruct:
|
|
case SpvOpCompositeInsert:
|
|
case SpvOpCompositeExtract:
|
|
// Skip a few ops that show up often but don't result in much work on their own
|
|
break;
|
|
|
|
default:
|
|
{
|
|
if (bInsideFunction)
|
|
{
|
|
++ApproxInstructionCount;
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
check(!bInsideFunction);
|
|
|
|
return ApproxInstructionCount;
|
|
}
|
|
|
|
static bool BuildShaderOutputFromSpirv(
|
|
CrossCompiler::FShaderConductorContext& CompilerContext,
|
|
const FVulkanShaderCompilerInternalState& InternalState,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
FShaderCompilerOutput& Output,
|
|
FVulkanBindingTable& BindingTable
|
|
)
|
|
{
|
|
// Reflect SPIR-V module with SPIRV-Reflect library
|
|
const size_t SpirvDataSize = SerializedOutput.Spirv.GetByteSize();
|
|
spv_reflect::ShaderModule Reflection(SpirvDataSize, SerializedOutput.Spirv.GetByteData(), SPV_REFLECT_RETURN_FLAG_SAMPLER_IMAGE_USAGE);
|
|
check(Reflection.GetResult() == SPV_REFLECT_RESULT_SUCCESS);
|
|
|
|
// Ray tracing shaders are not being rewritten to remove unreferenced entry points due to a bug in dxc.
|
|
// An issue prevents multiple entrypoints in the same spirv module, so limit ourselves to one entrypoint at a time
|
|
// Change final entry point name in SPIR-V module
|
|
{
|
|
checkf(Reflection.GetEntryPointCount() == 1, TEXT("Too many entry points in SPIR-V module: Expected 1, but got %d"), Reflection.GetEntryPointCount());
|
|
const SpvReflectResult Result = Reflection.ChangeEntryPointName(0, "main_00000000_00000000");
|
|
check(Result == SPV_REFLECT_RESULT_SUCCESS);
|
|
}
|
|
|
|
FSpirvReflectBindings Bindings;
|
|
GatherSpirvReflectionBindings(Reflection, Bindings, SerializedOutput.UsedBindlessUB, InternalState);
|
|
|
|
// Build binding table
|
|
TMap<const SpvReflectDescriptorBinding*, int32> BindingToIndexMap;
|
|
|
|
const FString UBOGlobalsNameSpv(ANSI_TO_TCHAR(CrossCompiler::FShaderConductorContext::GetIdentifierTable().GlobalsUniformBuffer));
|
|
const FString UBORootParamNameSpv(FShaderParametersMetadata::kRootUniformBufferBindingName);
|
|
|
|
auto RegisterBindings = [&BindingTable, &BindingToIndexMap, &UBOGlobalsNameSpv, &UBORootParamNameSpv]
|
|
(TArray<SpvReflectDescriptorBinding*>& Bindings, const char* BlockName, EVulkanBindingType::EType BindingType)
|
|
{
|
|
for (const SpvReflectDescriptorBinding* Binding : Bindings)
|
|
{
|
|
const bool bIsGlobalOrRootBuffer = ((UBOGlobalsNameSpv == Binding->name) || (UBORootParamNameSpv == Binding->name));
|
|
if (((BindingType == EVulkanBindingType::PackedUniformBuffer) && !bIsGlobalOrRootBuffer) ||
|
|
((BindingType == EVulkanBindingType::UniformBuffer) && bIsGlobalOrRootBuffer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 BindingIndex = BindingTable.RegisterBinding(Binding->name, BlockName, BindingType);
|
|
BindingToIndexMap.Add(Binding, BindingIndex);
|
|
|
|
if (BindingType == EVulkanBindingType::InputAttachment)
|
|
{
|
|
BindingTable.InputAttachmentsMask |= (1u << Binding->input_attachment_index);
|
|
}
|
|
}
|
|
};
|
|
|
|
RegisterBindings(Bindings.UniformBuffers, "h", EVulkanBindingType::PackedUniformBuffer);
|
|
RegisterBindings(Bindings.UniformBuffers, "u", EVulkanBindingType::UniformBuffer);
|
|
RegisterBindings(Bindings.InputAttachments, "a", EVulkanBindingType::InputAttachment);
|
|
|
|
RegisterBindings(Bindings.TBufferUAVs, "u", EVulkanBindingType::StorageTexelBuffer);
|
|
RegisterBindings(Bindings.SBufferUAVs, "u", EVulkanBindingType::StorageBuffer);
|
|
RegisterBindings(Bindings.TextureUAVs, "u", EVulkanBindingType::StorageImage);
|
|
|
|
RegisterBindings(Bindings.TBufferSRVs, "s", EVulkanBindingType::UniformTexelBuffer);
|
|
checkf(Bindings.SBufferSRVs.IsEmpty(), TEXT("GatherSpirvReflectionBindings should have dumped all SBufferSRVs into SBufferUAVs."));
|
|
RegisterBindings(Bindings.TextureSRVs, "s", EVulkanBindingType::Image);
|
|
|
|
RegisterBindings(Bindings.Samplers, "z", EVulkanBindingType::Sampler);
|
|
RegisterBindings(Bindings.AccelerationStructures, "r", EVulkanBindingType::AccelerationStructure);
|
|
|
|
// Sort binding table
|
|
BindingTable.SortBindings();
|
|
|
|
uint32 PackedGlobalArraySize = 0;
|
|
TBitArray<> UsedUniformBufferSlots;
|
|
const int32 MaxNumBits = VulkanBindless::MaxUniformBuffersPerStage * SF_NumFrequencies;
|
|
UsedUniformBufferSlots.Init(false, MaxNumBits);
|
|
|
|
TMap<FString, FVulkanShaderHeader::EType> EntryTypes;
|
|
|
|
// Final descriptor binding numbers for all other resource types
|
|
{
|
|
const int32 StageOffset = InternalState.bSupportsBindless ? (ShaderStage::GetStageForFrequency(InternalState.GetShaderFrequency()) * VulkanBindless::MaxUniformBuffersPerStage) : 0;
|
|
const uint32_t DescSetNumber = InternalState.bSupportsBindless ? (uint32_t)VulkanBindless::BindlessSingleUseUniformBufferSet : (uint32_t)SPV_REFLECT_SET_NUMBER_DONT_CHANGE;
|
|
|
|
auto AddReflectionInfos = [&](TArray<SpvReflectDescriptorBinding*>& BindingArray, EVulkanBindingType::EType BindingType, int32 BindingOffset)
|
|
{
|
|
for (const SpvReflectDescriptorBinding* Binding : BindingArray)
|
|
{
|
|
checkf(!InternalState.bSupportsBindless || (BindingType == EVulkanBindingType::UniformBuffer) || (BindingType == EVulkanBindingType::PackedUniformBuffer),
|
|
TEXT("Bindless shaders should only have uniform buffers."));
|
|
|
|
const FString ResourceName(ANSI_TO_TCHAR(Binding->name));
|
|
|
|
const bool bIsGlobalOrRootBuffer = ((UBOGlobalsNameSpv == ResourceName) || (UBORootParamNameSpv == ResourceName));
|
|
if (((BindingType == EVulkanBindingType::PackedUniformBuffer) && !bIsGlobalOrRootBuffer) ||
|
|
((BindingType == EVulkanBindingType::UniformBuffer) && bIsGlobalOrRootBuffer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 BindingIndex = BindingTable.GetRealBindingIndex(BindingToIndexMap[Binding]) + StageOffset;
|
|
|
|
const SpvReflectResult SpvResult = Reflection.ChangeDescriptorBindingNumbers(Binding, BindingIndex, DescSetNumber);
|
|
check(SpvResult == SPV_REFLECT_RESULT_SUCCESS);
|
|
|
|
const int32 ReflectionSlot = SerializedOutput.Spirv.ReflectionInfo.Add(FVulkanSpirv::FEntry(ResourceName, BindingIndex));
|
|
check(InternalState.ParameterParser);
|
|
const FShaderParameterParser::FParsedShaderParameter* ParsedParam = InternalState.ParameterParser->FindParameterInfosUnsafe(ResourceName);
|
|
|
|
auto AddShaderValidationType = [] (uint32_t VulkanBindingIndex, const FShaderParameterParser::FParsedShaderParameter* ParsedParam, FShaderCompilerOutput& Output) {
|
|
/*if (ParsedParam)
|
|
{
|
|
if (IsResourceBindingTypeSRV(ParsedParam->ParsedTypeDecl))
|
|
{
|
|
AddShaderValidationSRVType(VulkanBindingIndex, ParsedParam->ParsedTypeDecl, Output);
|
|
}
|
|
else
|
|
{
|
|
AddShaderValidationUAVType(VulkanBindingIndex, ParsedParam->ParsedTypeDecl, Output);
|
|
}
|
|
}*/
|
|
};
|
|
|
|
switch (BindingType)
|
|
{
|
|
case EVulkanBindingType::StorageTexelBuffer:
|
|
case EVulkanBindingType::StorageBuffer:
|
|
case EVulkanBindingType::StorageImage:
|
|
HandleReflectedShaderUAV(ResourceName, BindingOffset, ReflectionSlot, 1, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::Global);
|
|
|
|
AddShaderValidationType(BindingOffset, ParsedParam, Output);
|
|
break;
|
|
|
|
case EVulkanBindingType::Image:
|
|
// todo-jn: Could verify that we have the samplers...
|
|
//for (uint32 UsageIndex = 0; UsageIndex < Binding->usage_binding_count; ++UsageIndex)
|
|
//{
|
|
// const SpvReflectDescriptorBinding* AssociatedResource = Binding->usage_bindings[UsageIndex];
|
|
// AssociatedResourceNames[UsageIndex] = ANSI_TO_TCHAR(AssociatedResource->name);
|
|
//}
|
|
[[fallthrough]];
|
|
|
|
case EVulkanBindingType::UniformTexelBuffer:
|
|
HandleReflectedShaderResource(ResourceName, BindingOffset, ReflectionSlot, 1, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::Global);
|
|
|
|
AddShaderValidationType(BindingOffset, ParsedParam, Output);
|
|
break;
|
|
|
|
case EVulkanBindingType::Sampler:
|
|
HandleReflectedShaderSampler(ResourceName, ReflectionSlot, Output);
|
|
//HandleReflectedShaderSampler(ResourceName, BindingOffset, ReflectionSlot, 1, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::Global);
|
|
break;
|
|
|
|
case EVulkanBindingType::AccelerationStructure:
|
|
HandleReflectedShaderResource(ResourceName, BindingOffset, ReflectionSlot, 1, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::Global);
|
|
|
|
AddShaderValidationType(BindingOffset, ParsedParam, Output);
|
|
break;
|
|
|
|
case EVulkanBindingType::InputAttachment:
|
|
// Do Nothing
|
|
break;
|
|
|
|
case EVulkanBindingType::PackedUniformBuffer:
|
|
{
|
|
// Use the given global ResourceName instead of patching it to _Globals_h
|
|
check(!UsedUniformBufferSlots[ReflectionSlot]);
|
|
UsedUniformBufferSlots[ReflectionSlot] = true;
|
|
|
|
if (InternalState.UseRootParametersStructure())
|
|
{
|
|
check(ReflectionSlot == FShaderParametersMetadata::kRootCBufferBindingIndex);
|
|
HandleReflectedUniformBuffer(ResourceName, ReflectionSlot, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::UniformBuffer);
|
|
}
|
|
|
|
// Register all uniform buffer members of Globals as loose data
|
|
for (uint32 MemberIndex = 0; MemberIndex < Binding->block.member_count; ++MemberIndex)
|
|
{
|
|
const SpvReflectBlockVariable& Member = Binding->block.members[MemberIndex];
|
|
|
|
FString MemberName(ANSI_TO_TCHAR(Member.name));
|
|
FStringView AdjustedMemberName(MemberName);
|
|
|
|
const EShaderParameterType BindlessParameterType = FShaderParameterParser::ParseAndRemoveBindlessParameterPrefix(AdjustedMemberName);
|
|
|
|
// Add all members of global ub, and only bindless samplers/resources for root param
|
|
if (!InternalState.UseRootParametersStructure() || BindlessParameterType != EShaderParameterType::LooseData)
|
|
{
|
|
HandleReflectedGlobalConstantBufferMember(
|
|
MemberName,
|
|
BindingOffset,
|
|
Member.absolute_offset,
|
|
Member.size,
|
|
Output
|
|
);
|
|
|
|
EntryTypes.Add(FString(AdjustedMemberName), FVulkanShaderHeader::PackedGlobal);
|
|
}
|
|
|
|
PackedGlobalArraySize = FMath::Max<uint32>((Member.absolute_offset + Member.size), PackedGlobalArraySize);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVulkanBindingType::UniformBuffer:
|
|
{
|
|
check(!UsedUniformBufferSlots[ReflectionSlot]);
|
|
UsedUniformBufferSlots[ReflectionSlot] = true;
|
|
HandleReflectedUniformBuffer(ResourceName, ReflectionSlot, Output);
|
|
EntryTypes.Add(ResourceName, FVulkanShaderHeader::UniformBuffer);
|
|
|
|
AddShaderValidationUBSize(BindingOffset, Binding->block.padded_size, Output);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
break;
|
|
};
|
|
|
|
BindingOffset++;
|
|
}
|
|
return BindingOffset;
|
|
};
|
|
|
|
// Process Globals first (PackedUniformBuffer) and then regular UBs
|
|
const int32 GlobalUBCount = AddReflectionInfos(Bindings.UniformBuffers, EVulkanBindingType::PackedUniformBuffer, 0);
|
|
int32 UBOBindings = AddReflectionInfos(Bindings.UniformBuffers, EVulkanBindingType::UniformBuffer, 0) + GlobalUBCount;
|
|
|
|
AddReflectionInfos(Bindings.InputAttachments, EVulkanBindingType::InputAttachment, 0);
|
|
|
|
int32 UAVBindings = 0;
|
|
UAVBindings = AddReflectionInfos(Bindings.TBufferUAVs, EVulkanBindingType::StorageTexelBuffer, UAVBindings);
|
|
UAVBindings = AddReflectionInfos(Bindings.SBufferUAVs, EVulkanBindingType::StorageBuffer, UAVBindings);
|
|
UAVBindings = AddReflectionInfos(Bindings.TextureUAVs, EVulkanBindingType::StorageImage, UAVBindings);
|
|
|
|
int32 SRVBindings = 0;
|
|
SRVBindings = AddReflectionInfos(Bindings.TBufferSRVs, EVulkanBindingType::UniformTexelBuffer, SRVBindings);
|
|
checkf(Bindings.SBufferSRVs.IsEmpty(), TEXT("GatherSpirvReflectionBindings should have dumped all SBufferSRVs into SBufferUAVs."));
|
|
SRVBindings = AddReflectionInfos(Bindings.TextureSRVs, EVulkanBindingType::Image, SRVBindings);
|
|
|
|
Output.NumTextureSamplers = AddReflectionInfos(Bindings.Samplers, EVulkanBindingType::Sampler, 0);
|
|
AddReflectionInfos(Bindings.AccelerationStructures, EVulkanBindingType::AccelerationStructure, 0);
|
|
}
|
|
|
|
Output.Target = InternalState.Input.Target;
|
|
|
|
// Overwrite updated SPIRV code
|
|
SerializedOutput.Spirv.Data = TArray<uint32>(Reflection.GetCode(), Reflection.GetCodeSize() / 4);
|
|
|
|
// We have to strip out most debug instructions (except OpName) for Vulkan mobile
|
|
if (InternalState.bStripReflect)
|
|
{
|
|
const char* OptArgs[] = { "--strip-reflect", "-O"};
|
|
if (!CompilerContext.OptimizeSpirv(SerializedOutput.Spirv.Data, OptArgs, UE_ARRAY_COUNT(OptArgs)))
|
|
{
|
|
UE_LOG(LogVulkanShaderCompiler, Error, TEXT("Failed to strip debug instructions from SPIR-V module"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// For Android run an additional pass to patch spirv to be compatible across drivers
|
|
if (IsAndroidShaderFormat(InternalState.Input.ShaderFormat))
|
|
{
|
|
const char* OptArgs[] = { "--android-driver-patch" };
|
|
if (!CompilerContext.OptimizeSpirv(SerializedOutput.Spirv.Data, OptArgs, UE_ARRAY_COUNT(OptArgs)))
|
|
{
|
|
UE_LOG(LogVulkanShaderCompiler, Error, TEXT("Failed to apply driver patches for Android"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
PatchSpirvReflectionEntries(SerializedOutput.Spirv);
|
|
|
|
// :todo-jn: We don't store the CRC of each member of the hit group, leave the entrypoint untouched on the extra modules
|
|
if (InternalState.HasMultipleEntryPoints() && (InternalState.HitGroupShaderType != FVulkanShaderCompilerInternalState::EHitGroupShaderType::ClosestHit))
|
|
{
|
|
SerializedOutput.Spirv.EntryPointName = "main_00000000_00000000";
|
|
}
|
|
else
|
|
{
|
|
SerializedOutput.Spirv.EntryPointName = PatchSpirvEntryPointWithCRC(SerializedOutput.Spirv, SerializedOutput.Spirv.CRC);
|
|
}
|
|
|
|
Output.NumInstructions = CalculateSpirvInstructionCount(SerializedOutput.Spirv);
|
|
|
|
BuildShaderOutput(
|
|
SerializedOutput,
|
|
Output,
|
|
InternalState,
|
|
Bindings,
|
|
EntryTypes,
|
|
BindingTable,
|
|
InternalState.GetDebugName(),
|
|
PackedGlobalArraySize,
|
|
UsedUniformBufferSlots
|
|
);
|
|
|
|
if (InternalState.bDebugDump)
|
|
{
|
|
FString SPVExt(InternalState.GetSPVExtension());
|
|
FString SPVASMExt(SPVExt + TEXT("asm"));
|
|
|
|
// Write meta data to debug output file and write SPIR-V dump in binary and text form
|
|
DumpDebugShaderBinary(InternalState.Input, SerializedOutput.Spirv.GetByteData(), SerializedOutput.Spirv.GetByteSize(), SPVExt);
|
|
DumpDebugShaderDisassembledSpirv(InternalState.Input, SerializedOutput.Spirv.GetByteData(), SerializedOutput.Spirv.GetByteSize(), SPVASMExt);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Replaces OpImageFetch with OpImageRead for 64bit samplers
|
|
static void Patch64bitSamplers(FVulkanSpirv& Spirv)
|
|
{
|
|
uint32_t ULongSampledTypeId = 0;
|
|
uint32_t LongSampledTypeId = 0;
|
|
|
|
TArray<uint32_t, TInlineAllocator<2>> ImageTypeIDs;
|
|
TArray<uint32_t, TInlineAllocator<2>> LoadedIDs;
|
|
|
|
|
|
// Count instructions inside functions
|
|
for (FSpirvIterator Iter = Spirv.begin(); Iter != Spirv.end(); ++Iter)
|
|
{
|
|
switch (Iter.Opcode())
|
|
{
|
|
|
|
case SpvOpTypeInt:
|
|
{
|
|
// Operands:
|
|
// 1 - Result Id
|
|
// 2 - Width specifies how many bits wide the type is
|
|
// 3 - Signedness: 0 indicates unsigned
|
|
|
|
const uint32_t IntWidth = Iter.Operand(2);
|
|
if (IntWidth == 64)
|
|
{
|
|
const uint32_t IntSignedness = Iter.Operand(3);
|
|
if (IntSignedness == 1)
|
|
{
|
|
check(LongSampledTypeId == 0);
|
|
LongSampledTypeId = Iter.Operand(1);
|
|
}
|
|
else
|
|
{
|
|
check(ULongSampledTypeId == 0);
|
|
ULongSampledTypeId = Iter.Operand(1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SpvOpTypeImage:
|
|
{
|
|
// Operands:
|
|
// 1 - Result Id
|
|
// 2 - Sampled Type is the type of the components that result from sampling or reading from this image type
|
|
// 3 - Dim is the image dimensionality (Dim).
|
|
// 4 - Depth : 0 indicates not a depth image, 1 indicates a depth image, 2 means no indication as to whether this is a depth or non-depth image
|
|
// 5 - Arrayed : 0 indicates non-arrayed content, 1 indicates arrayed content
|
|
// 6 - MS : 0 indicates single-sampled content, 1 indicates multisampled content
|
|
// 7 - Sampled : 0 indicates this is only known at run time, not at compile time, 1 indicates used with sampler, 2 indicates used without a sampler (a storage image)
|
|
// 8 - Image Format
|
|
|
|
if ((Iter.Operand(7) == 1) && (Iter.Operand(6) == 0) && (Iter.Operand(5) == 0))
|
|
{
|
|
// Patch the node info and the SPIRV
|
|
const uint32_t SampledTypeId = Iter.Operand(2);
|
|
const uint32_t WithoutSampler = 2;
|
|
if (SampledTypeId == LongSampledTypeId)
|
|
{
|
|
uint32* CurrentOpPtr = *Iter;
|
|
CurrentOpPtr[7] = WithoutSampler;
|
|
CurrentOpPtr[8] = (uint32_t)SpvImageFormatR64i;
|
|
ImageTypeIDs.Add(Iter.Operand(1));
|
|
}
|
|
else if (SampledTypeId == ULongSampledTypeId)
|
|
{
|
|
uint32* CurrentOpPtr = *Iter;
|
|
CurrentOpPtr[7] = WithoutSampler;
|
|
CurrentOpPtr[8] = (uint32_t)SpvImageFormatR64ui;
|
|
ImageTypeIDs.Add(Iter.Operand(1));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SpvOpLoad:
|
|
{
|
|
// Operands:
|
|
// 1 - Result Type Id
|
|
// 2 - Result Id
|
|
// 3 - Pointer
|
|
|
|
// Find loaded images of this type
|
|
if (ImageTypeIDs.Find(Iter.Operand(1)) != INDEX_NONE)
|
|
{
|
|
LoadedIDs.Add(Iter.Operand(2));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SpvOpImageFetch:
|
|
{
|
|
// Operands:
|
|
// 1 - Result Type Id
|
|
// 2 - Result Id
|
|
// 3 - Image Id
|
|
// 4 - Coordinate
|
|
// 5 - Image Operands
|
|
|
|
// If this is one of the modified images, patch the node and the SPIRV.
|
|
if (LoadedIDs.Find(Iter.Operand(3)) != INDEX_NONE)
|
|
{
|
|
const uint32_t OldWordCount = Iter.WordCount();
|
|
const uint32_t NewWordCount = 5;
|
|
check(OldWordCount >= NewWordCount);
|
|
const uint32_t EncodedOpImageRead = (NewWordCount << 16) | ((uint32_t)SpvOpImageRead & 0xFFFF);
|
|
uint32* CurrentOpPtr = *Iter;
|
|
(*CurrentOpPtr) = EncodedOpImageRead;
|
|
|
|
// Remove unsupported image operands (mostly force LOD 0)
|
|
const uint32_t NopWordCount = 1;
|
|
const uint32_t EncodedOpNop = (NopWordCount << 16) | ((uint32_t)SpvOpNop & 0xFFFF);
|
|
for (uint32_t ImageOperandIndex = NewWordCount; ImageOperandIndex < OldWordCount; ++ImageOperandIndex)
|
|
{
|
|
CurrentOpPtr[ImageOperandIndex] = EncodedOpNop;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void VulkanCreateDXCCompileBatchFiles(
|
|
const CrossCompiler::FShaderConductorContext& CompilerContext,
|
|
const FVulkanShaderCompilerInternalState& InternalState,
|
|
const CrossCompiler::FShaderConductorOptions& Options)
|
|
{
|
|
const FString USFFilename = InternalState.Input.GetSourceFilename();
|
|
const FString SPVFilename = FPaths::GetBaseFilename(USFFilename) + TEXT(".DXC.spv");
|
|
const FString GLSLFilename = FPaths::GetBaseFilename(USFFilename) + TEXT(".SPV.glsl");
|
|
|
|
FString DxcPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir());
|
|
|
|
DxcPath = FPaths::Combine(DxcPath, TEXT("Binaries/ThirdParty/ShaderConductor/Win64"));
|
|
FPaths::MakePlatformFilename(DxcPath);
|
|
|
|
FString DxcFilename = FPaths::Combine(DxcPath, TEXT("dxc.exe"));
|
|
FPaths::MakePlatformFilename(DxcFilename);
|
|
|
|
// CompileDXC.bat
|
|
{
|
|
const FString DxcArguments = CompilerContext.GenerateDxcArguments(Options);
|
|
|
|
FString BatchFileContents = FString::Printf(
|
|
TEXT(
|
|
"@ECHO OFF\n"
|
|
"SET DXC=\"%s\"\n"
|
|
"SET SPIRVCROSS=\"spirv-cross.exe\"\n"
|
|
"IF NOT EXIST %%DXC%% (\n"
|
|
"\tECHO Couldn't find dxc.exe under \"%s\"\n"
|
|
"\tGOTO :END\n"
|
|
")\n"
|
|
"ECHO Compiling with DXC...\n"
|
|
"%%DXC%% %s -Fo %s %s\n"
|
|
"WHERE %%SPIRVCROSS%%\n"
|
|
"IF %%ERRORLEVEL%% NEQ 0 (\n"
|
|
"\tECHO spirv-cross.exe not found in Path environment variable, please build it from source https://github.com/KhronosGroup/SPIRV-Cross\n"
|
|
"\tGOTO :END\n"
|
|
")\n"
|
|
"ECHO Translating SPIRV back to glsl...\n"
|
|
"%%SPIRVCROSS%% --vulkan-semantics --output %s %s\n"
|
|
":END\n"
|
|
"PAUSE\n"
|
|
),
|
|
*DxcFilename,
|
|
*DxcPath,
|
|
*DxcArguments,
|
|
*SPVFilename,
|
|
*USFFilename,
|
|
*GLSLFilename,
|
|
*SPVFilename
|
|
);
|
|
|
|
FFileHelper::SaveStringToFile(BatchFileContents, *(InternalState.Input.DumpDebugInfoPath / TEXT("CompileDXC.bat")));
|
|
}
|
|
}
|
|
|
|
// Quick and dirty way to get the location of the entrypoint in the source
|
|
// NOTE: Preprocessed shaders have mcros resolves and comments removed, it makes this easier...
|
|
static FString ParseEntrypointDecl(FStringView PreprocessedShader, FStringView Entrypoint)
|
|
{
|
|
auto SkipWhitespace = [&](int32& Index)
|
|
{
|
|
while (FChar::IsWhitespace(PreprocessedShader[Index]))
|
|
{
|
|
++Index;
|
|
}
|
|
};
|
|
|
|
auto EraseDebugLines = [](FString& EntryPointDecl)
|
|
{
|
|
int32 HashIndex;
|
|
while (EntryPointDecl.FindChar(TEXT('#'), HashIndex))
|
|
{
|
|
while ((HashIndex < EntryPointDecl.Len()) && (!FChar::IsLinebreak(EntryPointDecl[HashIndex])))
|
|
{
|
|
EntryPointDecl[HashIndex] = TEXT(' ');
|
|
++HashIndex;
|
|
}
|
|
}
|
|
};
|
|
|
|
FString EntryPointDecl;
|
|
|
|
// Go through all the case sensitive matches in the source
|
|
int32 EntrypointIndex = PreprocessedShader.Find(Entrypoint);
|
|
check(EntrypointIndex != INDEX_NONE);
|
|
while (EntrypointIndex != INDEX_NONE)
|
|
{
|
|
// This should be the beginning of a new word
|
|
if ((EntrypointIndex == 0) || !FChar::IsWhitespace(PreprocessedShader[EntrypointIndex - 1]))
|
|
{
|
|
EntrypointIndex = PreprocessedShader.Find(Entrypoint, EntrypointIndex + 1);
|
|
continue;
|
|
}
|
|
|
|
// The next thing after the entrypoint should its parameters
|
|
// White space is allowed, so skip any that is found
|
|
|
|
int32 ParamsStart = EntrypointIndex + Entrypoint.Len();
|
|
SkipWhitespace(ParamsStart);
|
|
if (PreprocessedShader[ParamsStart] != TEXT('('))
|
|
{
|
|
EntrypointIndex = PreprocessedShader.Find(Entrypoint, ParamsStart);
|
|
continue;
|
|
}
|
|
|
|
int32 ParamsEnd = PreprocessedShader.Find(TEXT(")"), ParamsStart+1);
|
|
check(ParamsEnd != INDEX_NONE);
|
|
if (ParamsEnd == INDEX_NONE)
|
|
{
|
|
// Suspicious
|
|
EntrypointIndex = PreprocessedShader.Find(Entrypoint, ParamsStart);
|
|
continue;
|
|
}
|
|
|
|
// Make sure to grab everything up to the function content
|
|
|
|
int32 DeclEnd = ParamsEnd + 1;
|
|
while (PreprocessedShader[DeclEnd] != TEXT('{') && (PreprocessedShader[DeclEnd] != TEXT(';')))
|
|
{
|
|
++DeclEnd;
|
|
}
|
|
if (PreprocessedShader[DeclEnd] != TEXT('{'))
|
|
{
|
|
EntrypointIndex = PreprocessedShader.Find(Entrypoint, DeclEnd);
|
|
continue;
|
|
}
|
|
|
|
// Now back up to pick up the return value, the attributes and everything else that can come with it, like "[numthreads(1,1,1)]"
|
|
|
|
int32 DeclBegin = EntrypointIndex - 1;
|
|
while ( (DeclBegin > 0) && (PreprocessedShader[DeclBegin] != TEXT(';')) && (PreprocessedShader[DeclBegin] != TEXT('}')))
|
|
{
|
|
--DeclBegin;
|
|
}
|
|
++DeclBegin;
|
|
|
|
EntryPointDecl = FString(DeclEnd - DeclBegin, &PreprocessedShader[DeclBegin]);
|
|
EraseDebugLines(EntryPointDecl);
|
|
EntryPointDecl.TrimStartAndEndInline();
|
|
break;
|
|
}
|
|
|
|
return EntryPointDecl;
|
|
}
|
|
|
|
uint8 ParseWaveSize(
|
|
const FVulkanShaderCompilerInternalState& InternalState,
|
|
const FString& PreprocessedShader
|
|
)
|
|
{
|
|
uint8 WaveSize = 0;
|
|
if (!InternalState.IsRayTracingShader())
|
|
{
|
|
const FString EntrypointDecl = ParseEntrypointDecl(PreprocessedShader, InternalState.GetEntryPointName());
|
|
|
|
const FString WaveSizeMacro(TEXT("VULKAN_WAVESIZE("));
|
|
int32 WaveSizeIndex = EntrypointDecl.Find(*WaveSizeMacro, ESearchCase::CaseSensitive);
|
|
while (WaveSizeIndex != INDEX_NONE)
|
|
{
|
|
const int32 StartNumber = WaveSizeIndex + WaveSizeMacro.Len();
|
|
const int32 EndNumber = EntrypointDecl.Find(TEXT(")"), ESearchCase::CaseSensitive, ESearchDir::FromStart, StartNumber);
|
|
check(EndNumber != INDEX_NONE);
|
|
|
|
FString WaveSizeValue(EndNumber - StartNumber, &EntrypointDecl[StartNumber]);
|
|
WaveSizeValue.RemoveSpacesInline();
|
|
if (WaveSizeValue != TEXT("N")) // skip the macro decl
|
|
{
|
|
float FloatResult = 0.0;
|
|
if (FMath::Eval(WaveSizeValue, FloatResult))
|
|
{
|
|
checkf((FloatResult >= 0.0f) && (FloatResult < (float)MAX_uint8), TEXT("Specified wave size is too large for 8bit uint!"));
|
|
WaveSize = static_cast<uint8>(FloatResult);
|
|
|
|
}
|
|
else
|
|
{
|
|
check(WaveSizeValue.IsNumeric());
|
|
const int32 ConvertedWaveSize = FCString::Atoi(*WaveSizeValue);
|
|
checkf((ConvertedWaveSize > 0) && (ConvertedWaveSize < MAX_uint8), TEXT("Specified wave size is too large for 8bit uint!"));
|
|
WaveSize = (uint8)ConvertedWaveSize;
|
|
}
|
|
break;
|
|
}
|
|
|
|
WaveSizeIndex = EntrypointDecl.Find(*WaveSizeMacro, ESearchCase::CaseSensitive, ESearchDir::FromStart, EndNumber);
|
|
}
|
|
}
|
|
|
|
// Take note of preferred wave size flag if none was specified in HLSL
|
|
if ((WaveSize == 0) && InternalState.Input.Environment.CompilerFlags.Contains(CFLAG_Wave32))
|
|
{
|
|
WaveSize = 32;
|
|
}
|
|
|
|
return WaveSize;
|
|
}
|
|
|
|
static bool CompileWithShaderConductor(
|
|
const FVulkanShaderCompilerInternalState& InternalState,
|
|
const FString& PreprocessedShader,
|
|
VulkanShaderCompilerSerializedOutput& SerializedOutput,
|
|
FShaderCompilerOutput& Output
|
|
)
|
|
{
|
|
const FShaderCompilerInput& Input = InternalState.Input;
|
|
|
|
FVulkanBindingTable BindingTable(InternalState.GetHlslShaderFrequency());
|
|
CrossCompiler::FShaderConductorContext CompilerContext;
|
|
|
|
// Inject additional macro definitions to circumvent missing features: external textures
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS // FShaderCompilerDefinitions will be made internal in the future, marked deprecated until then
|
|
FShaderCompilerDefinitions AdditionalDefines;
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// Load shader source into compiler context
|
|
CompilerContext.LoadSource(PreprocessedShader, Input.VirtualSourceFilePath, InternalState.GetEntryPointName(), InternalState.GetShaderFrequency(), &AdditionalDefines);
|
|
|
|
// Initialize compilation options for ShaderConductor
|
|
CrossCompiler::FShaderConductorOptions Options;
|
|
Options.TargetEnvironment = InternalState.MinimumTargetEnvironment;
|
|
|
|
// VK_EXT_scalar_block_layout is required by raytracing and by Nanite (so expect it to be present in SM6/Vulkan_1_3)
|
|
Options.bDisableScalarBlockLayout = !(InternalState.IsRayTracingShader() || InternalState.IsSM6());
|
|
|
|
if (Input.Environment.CompilerFlags.Contains(CFLAG_AllowRealTypes))
|
|
{
|
|
Options.bEnable16bitTypes = true;
|
|
}
|
|
|
|
// Enable HLSL 2021 if specified
|
|
if (Input.Environment.CompilerFlags.Contains(CFLAG_HLSL2021))
|
|
{
|
|
Options.HlslVersion = 2021;
|
|
}
|
|
|
|
if (InternalState.bDebugDump)
|
|
{
|
|
VulkanCreateDXCCompileBatchFiles(CompilerContext, InternalState, Options);
|
|
}
|
|
|
|
// Before the shader rewritter removes all traces of it, pull any WAVESIZE directives from the shader source
|
|
SerializedOutput.Header.WaveSize = ParseWaveSize(InternalState, PreprocessedShader);
|
|
|
|
const bool bRewriteHlslSource = !InternalState.IsRayTracingShader();
|
|
if (bRewriteHlslSource)
|
|
{
|
|
// Rewrite HLSL source code to remove unused global resources and variables
|
|
FString RewrittenHlslSource;
|
|
|
|
Options.bRemoveUnusedGlobals = true;
|
|
if (!CompilerContext.RewriteHlsl(Options, (InternalState.bDebugDump ? &RewrittenHlslSource : nullptr)))
|
|
{
|
|
CompilerContext.FlushErrors(Output.Errors);
|
|
return false;
|
|
}
|
|
Options.bRemoveUnusedGlobals = false;
|
|
|
|
if (InternalState.bDebugDump)
|
|
{
|
|
DumpDebugShaderText(Input, RewrittenHlslSource, TEXT("rewritten.hlsl"));
|
|
}
|
|
}
|
|
|
|
// Compile HLSL source to SPIR-V binary
|
|
if (!CompilerContext.CompileHlslToSpirv(Options, SerializedOutput.Spirv.Data))
|
|
{
|
|
CompilerContext.FlushErrors(Output.Errors);
|
|
return false;
|
|
}
|
|
|
|
// If this shader samples R64 image formats, they need to get converted to STORAGE_IMAGE
|
|
// todo-jnmo: Scope this with a CFLAG if it affects compilation times
|
|
Patch64bitSamplers(SerializedOutput.Spirv);
|
|
|
|
// Build shader output and binding table
|
|
Output.bSucceeded = BuildShaderOutputFromSpirv(CompilerContext, InternalState, SerializedOutput, Output, BindingTable);
|
|
|
|
// Flush warnings
|
|
CompilerContext.FlushErrors(Output.Errors);
|
|
return true;
|
|
}
|
|
|
|
#endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
|
|
|
|
|
|
|
|
static void RemoveUnusedBindlessHeaps(FString& PreprocessedShaderSource, const TCHAR* HeapType)
|
|
{
|
|
const FString HeapIdentifier = TEXT("VULKAN_") + FString(HeapType) + TEXT("_HEAP(");
|
|
|
|
int32 SearchIndex = PreprocessedShaderSource.Find(HeapIdentifier, ESearchCase::CaseSensitive, ESearchDir::FromStart, -1);
|
|
while (SearchIndex != INDEX_NONE)
|
|
{
|
|
const int32 TypeNameStartIndex = SearchIndex + HeapIdentifier.Len();
|
|
const int32 TypeNameEndIndex = PreprocessedShaderSource.Find(TEXT(")"), ESearchCase::CaseSensitive, ESearchDir::FromStart, TypeNameStartIndex);
|
|
FString TypeName(TypeNameEndIndex - TypeNameStartIndex, &PreprocessedShaderSource[TypeNameStartIndex]);
|
|
TypeName.TrimStartAndEndInline();
|
|
|
|
// Make sure it's one of our automatically generated types
|
|
if (TypeName.StartsWith(TEXT("SafeType")))
|
|
{
|
|
// Ugly and fast way to make sure ity's a heap declaration (this should catch the generated ones at least)
|
|
if ((PreprocessedShaderSource[TypeNameEndIndex + 1] == '[') &&
|
|
(PreprocessedShaderSource[TypeNameEndIndex + 2] == ']') &&
|
|
(PreprocessedShaderSource[TypeNameEndIndex + 3] == ';'))
|
|
{
|
|
int32 FirstUseIndex = PreprocessedShaderSource.Find(TypeName, ESearchCase::CaseSensitive, ESearchDir::FromStart, TypeNameEndIndex + 4);
|
|
while (FirstUseIndex != INDEX_NONE)
|
|
{
|
|
const int32 NextChar = FirstUseIndex + TypeName.Len();
|
|
if (FChar::IsWhitespace(PreprocessedShaderSource[NextChar]) || PreprocessedShaderSource[NextChar] == ')')
|
|
{
|
|
break;
|
|
}
|
|
FirstUseIndex = PreprocessedShaderSource.Find(TypeName, ESearchCase::CaseSensitive, ESearchDir::FromStart, NextChar);
|
|
}
|
|
|
|
if (FirstUseIndex == INDEX_NONE)
|
|
{
|
|
const int32 DeclarationBeginIndex = PreprocessedShaderSource.Find(TypeName, ESearchCase::CaseSensitive, ESearchDir::FromEnd, SearchIndex);
|
|
if ((DeclarationBeginIndex != INDEX_NONE) && ((SearchIndex - DeclarationBeginIndex) < (TypeName.Len() + 4)))
|
|
{
|
|
for (int32 Idx = DeclarationBeginIndex; Idx <= (TypeNameEndIndex + 3); Idx++)
|
|
{
|
|
PreprocessedShaderSource[Idx] = ' ';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SearchIndex = PreprocessedShaderSource.Find(HeapIdentifier, ESearchCase::CaseSensitive, ESearchDir::FromStart, TypeNameEndIndex + 4);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool PreprocessVulkanShader(const FShaderCompilerInput& Input, const FShaderCompilerEnvironment& Environment, FShaderPreprocessOutput& PreprocessOutput)
|
|
{
|
|
const FVulkanShaderCompilerInternalState InternalState(Input, nullptr);
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS // FShaderCompilerDefinitions will be made internal in the future, marked deprecated until then
|
|
|
|
FShaderCompilerDefinitions AdditionalDefines;
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_HLSLCC"), 1);
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_VULKAN"), 1);
|
|
if (InternalState.IsMobileES31())
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("ES3_1_PROFILE"), 1);
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_PROFILE"), 1);
|
|
}
|
|
else if (InternalState.IsSM6())
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_PROFILE_SM6"), 1);
|
|
}
|
|
else if (InternalState.IsSM5())
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_PROFILE_SM5"), 1);
|
|
}
|
|
AdditionalDefines.SetDefine(TEXT("row_major"), TEXT(""));
|
|
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_SUPPORTS_ATTRIBUTES"), (uint32)1);
|
|
AdditionalDefines.SetDefine(TEXT("COMPILER_SUPPORTS_DUAL_SOURCE_BLENDING_SLOT_DECORATION"), (uint32)1);
|
|
AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_ROV"), 0); // Disabled until DXC->SPRIV ROV support is implemented
|
|
|
|
if (Input.Environment.FullPrecisionInPS)
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("FORCE_FLOATS"), (uint32)1);
|
|
}
|
|
|
|
if (Input.Environment.CompilerFlags.Contains(CFLAG_InlineRayTracing))
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_INLINE_RAY_TRACING"), 1);
|
|
}
|
|
|
|
if (Input.Environment.CompilerFlags.Contains(CFLAG_AllowRealTypes))
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_REAL_TYPES"), 1);
|
|
}
|
|
|
|
// We have ETargetEnvironment::Vulkan_1_1 by default as a min spec now
|
|
{
|
|
AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_SM6_0_WAVE_OPERATIONS"), 1);
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_SUPPORTS_SUBGROUP_SIZE_CONTROL"), 1);
|
|
}
|
|
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_BINDLESS_SRV_ARRAY_PREFIX"), FShaderParameterParser::kBindlessSRVArrayPrefix);
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_BINDLESS_UAV_ARRAY_PREFIX"), FShaderParameterParser::kBindlessUAVArrayPrefix);
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_BINDLESS_SAMPLER_ARRAY_PREFIX"), FShaderParameterParser::kBindlessSamplerArrayPrefix);
|
|
AdditionalDefines.SetDefine(TEXT("VULKAN_MAX_BINDLESS_UNIFORM_BUFFERS_PER_STAGE"), VulkanBindless::MaxUniformBuffersPerStage);
|
|
|
|
if (IsAndroidShaderFormat(Input.ShaderFormat))
|
|
{
|
|
// On most Android devices uint64_t is unsupported so we emulate as 2 uint32_t's
|
|
AdditionalDefines.SetDefine(TEXT("EMULATE_VKDEVICEADRESS"), 1);
|
|
}
|
|
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// Preprocess the shader.
|
|
FString& PreprocessedShaderSource = PreprocessOutput.EditSource();
|
|
const bool bDirectCompile = FParse::Param(FCommandLine::Get(), TEXT("directcompile"));
|
|
if (bDirectCompile)
|
|
{
|
|
if (!FFileHelper::LoadFileToString(PreprocessedShaderSource, *Input.VirtualSourceFilePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Remove const as we are on debug-only mode
|
|
CrossCompiler::CreateEnvironmentFromResourceTable(PreprocessedShaderSource, (FShaderCompilerEnvironment&)Input.Environment);
|
|
}
|
|
else
|
|
{
|
|
if (!PreprocessShader(PreprocessOutput, Input, Environment, AdditionalDefines))
|
|
{
|
|
// The preprocessing stage will add any relevant errors.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CleanupUniformBufferCode(Input.Environment, PreprocessedShaderSource);
|
|
|
|
// Run the shader minifier
|
|
if (InternalState.IsRayTracingShader() || InternalState.bUseBindlessUniformBuffer)
|
|
{
|
|
// Always needed in ray tracing shaders to ensure bindless UB count is kept low
|
|
UE::ShaderCompilerCommon::RemoveDeadCode(PreprocessedShaderSource, InternalState.GetRequiredSymbols(), PreprocessOutput.EditErrors());
|
|
}
|
|
#if UE_VULKAN_SHADER_COMPILER_ALLOW_DEAD_CODE_REMOVAL
|
|
else if (Input.Environment.CompilerFlags.Contains(CFLAG_RemoveDeadCode))
|
|
{
|
|
UE::ShaderCompilerCommon::RemoveDeadCode(PreprocessedShaderSource, InternalState.GetEntryPointName(), PreprocessOutput.EditErrors());
|
|
}
|
|
#endif // UE_VULKAN_SHADER_COMPILER_ALLOW_DEAD_CODE_REMOVAL
|
|
|
|
// Clean up the code a bit, it's unreadable otherwise with all the unused heaps left around
|
|
if (InternalState.bSupportsBindless)
|
|
{
|
|
RemoveUnusedBindlessHeaps(PreprocessedShaderSource, TEXT("SAMPLER"));
|
|
RemoveUnusedBindlessHeaps(PreprocessedShaderSource, TEXT("RESOURCE"));
|
|
UE::ShaderCompilerCommon::RemoveDeadCode(PreprocessedShaderSource, InternalState.GetRequiredSymbols(), PreprocessOutput.EditErrors());
|
|
|
|
if (InternalState.bDebugDump)
|
|
{
|
|
DumpDebugShaderText(Input, PreprocessedShaderSource, TEXT("bindless.final.hlsl"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// :todo-jn: TEMPORARY EXPERIMENT - will eventually move into preprocessing step
|
|
static TArray<FString> ConvertUBToBindless(FString& PreprocessedShaderSource)
|
|
{
|
|
// Fill a map so we pull our bindless sampler/resource indices from the right struct
|
|
// :todo-jn: Do we not have the layout somewhere instead of calculating offsets? there must be a better way...
|
|
auto GenerateNewDecl = [](const int32 CBIndex, const FString& Members, const FString& CBName)
|
|
{
|
|
const FString PrefixedCBName = FString::Printf(TEXT("%s%d_%s"), *kBindlessCBPrefix, CBIndex, *CBName);
|
|
const FString BindlessCBType = PrefixedCBName + TEXT("_Type");
|
|
const FString BindlessCBHeapName = PrefixedCBName + kBindlessHeapSuffix;
|
|
const FString PaddingName = FString::Printf(TEXT("%s_Padding"), *CBName);
|
|
|
|
FString CBDecl;
|
|
CBDecl.Reserve(Members.Len() * 3); // start somewhere approx less bad
|
|
|
|
// Declare the struct
|
|
CBDecl += TEXT("struct ") + BindlessCBType + TEXT(" \n{\n") + Members + TEXT("\n};\n");
|
|
|
|
// Declare the safetype and bindless array for this cb
|
|
CBDecl += FString::Printf(TEXT("ConstantBuffer<%s> %s[];\n"), *BindlessCBType, *BindlessCBHeapName);
|
|
|
|
// Now bring in the CB
|
|
CBDecl += FString::Printf(TEXT("static const %s %s = %s[VulkanHitGroupSystemParameters.BindlessUniformBuffers[%d]];\n"),
|
|
*BindlessCBType, *PrefixedCBName, *BindlessCBHeapName, CBIndex);
|
|
|
|
// Now create a global scope var for each value (as the cbuffer would provide) to patch in seemlessly with the rest of the code
|
|
uint32 MemberOffset = 0;
|
|
const TCHAR* MemberSearchPtr = *Members;
|
|
const uint32 LastMemberSemicolonIndex = Members.Find(TEXT(";"), ESearchCase::CaseSensitive, ESearchDir::FromEnd, -1);
|
|
check(LastMemberSemicolonIndex != INDEX_NONE);
|
|
const TCHAR* LastMemberSemicolon = &Members[LastMemberSemicolonIndex];
|
|
|
|
do
|
|
{
|
|
const TCHAR* MemberTypeStartPtr = nullptr;
|
|
const TCHAR* MemberTypeEndPtr = nullptr;
|
|
ParseHLSLTypeName(MemberSearchPtr, MemberTypeStartPtr, MemberTypeEndPtr);
|
|
const FString MemberTypeName(MemberTypeEndPtr - MemberTypeStartPtr, MemberTypeStartPtr);
|
|
|
|
FString MemberName;
|
|
MemberSearchPtr = ParseHLSLSymbolName(MemberTypeEndPtr, MemberName);
|
|
check(MemberName.Len() > 0);
|
|
|
|
if (MemberName.StartsWith(PaddingName))
|
|
{
|
|
while (*MemberSearchPtr && *MemberSearchPtr != ';')
|
|
{
|
|
MemberSearchPtr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Skip over trailing tokens and pick up arrays
|
|
FString ArrayDecl;
|
|
while (*MemberSearchPtr && *MemberSearchPtr != ';')
|
|
{
|
|
if (*MemberSearchPtr == '[')
|
|
{
|
|
ArrayDecl.AppendChar(*MemberSearchPtr);
|
|
|
|
MemberSearchPtr++;
|
|
while (*MemberSearchPtr && *MemberSearchPtr != ']')
|
|
{
|
|
ArrayDecl.AppendChar(*MemberSearchPtr);
|
|
MemberSearchPtr++;
|
|
}
|
|
|
|
ArrayDecl.AppendChar(*MemberSearchPtr);
|
|
}
|
|
|
|
MemberSearchPtr++;
|
|
}
|
|
|
|
CBDecl += FString::Printf(TEXT("static const %s %s%s = %s.%s;\n"), *MemberTypeName, *MemberName, *ArrayDecl, *PrefixedCBName, *MemberName);
|
|
}
|
|
|
|
MemberSearchPtr++;
|
|
|
|
} while (MemberSearchPtr < LastMemberSemicolon);
|
|
|
|
return CBDecl;
|
|
};
|
|
|
|
// replace "cbuffer" decl with a struct filled from bindless constant buffer
|
|
TArray<FString> BindlessUBs;
|
|
{
|
|
const FString UniformBufferDeclIdentifier = TEXT("cbuffer");
|
|
|
|
int32 SearchIndex = PreprocessedShaderSource.Find(UniformBufferDeclIdentifier, ESearchCase::CaseSensitive, ESearchDir::FromStart, -1);
|
|
while (SearchIndex != INDEX_NONE)
|
|
{
|
|
FString StructName;
|
|
const TCHAR* StructNameEndPtr = ParseHLSLSymbolName(&PreprocessedShaderSource[SearchIndex + UniformBufferDeclIdentifier.Len()], StructName);
|
|
check(StructName.Len() > 0);
|
|
|
|
const int32 CBIndex = BindlessUBs.Add(StructName);
|
|
check(CBIndex < 16);
|
|
|
|
const TCHAR* OpeningBracePtr = FCString::Strstr(&PreprocessedShaderSource[SearchIndex + UniformBufferDeclIdentifier.Len()], TEXT("{"));
|
|
check(OpeningBracePtr);
|
|
const TCHAR* ClosingBracePtr = FindMatchingClosingBrace(OpeningBracePtr + 1);
|
|
check(ClosingBracePtr);
|
|
const int32 ClosingBraceIndex = ClosingBracePtr - (*PreprocessedShaderSource);
|
|
|
|
const FString Members(ClosingBracePtr - OpeningBracePtr - 1, OpeningBracePtr + 1);
|
|
const FString NewDecl = GenerateNewDecl(CBIndex, Members, StructName);
|
|
|
|
const int32 OldDeclLen = ClosingBraceIndex - SearchIndex + 1;
|
|
PreprocessedShaderSource.RemoveAt(SearchIndex, OldDeclLen, false);
|
|
PreprocessedShaderSource.InsertAt(SearchIndex, NewDecl);
|
|
|
|
SearchIndex = PreprocessedShaderSource.Find(UniformBufferDeclIdentifier, ESearchCase::CaseSensitive, ESearchDir::FromStart, SearchIndex + NewDecl.Len());
|
|
}
|
|
}
|
|
return BindlessUBs;
|
|
}
|
|
|
|
|
|
static void UpdateBindlessUBs(const FVulkanShaderCompilerInternalState& InternalState, VulkanShaderCompilerSerializedOutput& SerializedOutput, FShaderCompilerOutput& Output)
|
|
{
|
|
auto GetLayoutHash = [&InternalState](const FString& UBName)
|
|
{
|
|
uint32 LayoutHash = 0;
|
|
const FUniformBufferEntry* UniformBufferEntry = InternalState.Input.Environment.UniformBufferMap.Find(UBName);
|
|
if (UniformBufferEntry)
|
|
{
|
|
LayoutHash = UniformBufferEntry->LayoutHash;
|
|
}
|
|
else if ((UBName == FShaderParametersMetadata::kRootUniformBufferBindingName) && InternalState.Input.RootParametersStructure)
|
|
{
|
|
LayoutHash = InternalState.Input.RootParametersStructure->GetLayoutHash();
|
|
}
|
|
else
|
|
{
|
|
LayoutHash = 0;
|
|
}
|
|
return LayoutHash;
|
|
};
|
|
|
|
SerializedOutput.Header.UniformBuffers.Empty();
|
|
for (int32 CBIndex = 0; CBIndex < InternalState.AllBindlessUBs.Num(); CBIndex++)
|
|
{
|
|
const FString& CBName = InternalState.AllBindlessUBs[CBIndex];
|
|
|
|
// It's possible SPIRV compilation has optimized out a buffer from every shader in the group
|
|
if (SerializedOutput.UsedBindlessUB.Contains(CBName))
|
|
{
|
|
FVulkanShaderHeader::FUniformBufferInfo& UBInfo = SerializedOutput.Header.UniformBuffers.AddZeroed_GetRef();
|
|
UBInfo.LayoutHash = GetLayoutHash(CBName);
|
|
UBInfo.ConstantDataOriginalBindingIndex = CBIndex;
|
|
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
|
|
UBInfo.DebugName = CBName;
|
|
#endif
|
|
|
|
const int32 UBIndex = SerializedOutput.Header.UniformBuffers.Num() - 1;
|
|
Output.ParameterMap.AddParameterAllocation(*CBName, UBIndex, (uint16)FVulkanShaderHeader::UniformBuffer, 1, EShaderParameterType::UniformBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static bool CompileShaderGroup(
|
|
FVulkanShaderCompilerInternalState& InternalState,
|
|
const FString& OriginalPreprocessedShaderSource,
|
|
FShaderCompilerOutput& MergedOutput
|
|
)
|
|
{
|
|
checkf(InternalState.bSupportsBindless && InternalState.bUseBindlessUniformBuffer, TEXT("Ray tracing requires full bindless in Vulkan."));
|
|
|
|
// Compile each one of the shader modules seperately and create one big blob for the engine
|
|
auto CompilePartialExport = [&OriginalPreprocessedShaderSource, &InternalState, &MergedOutput](
|
|
FVulkanShaderCompilerInternalState::EHitGroupShaderType HitGroupShaderType,
|
|
const TCHAR* PartialFileExtension,
|
|
VulkanShaderCompilerSerializedOutput& PartialSerializedOutput)
|
|
{
|
|
InternalState.HitGroupShaderType = HitGroupShaderType;
|
|
|
|
FShaderCompilerOutput TempOutput;
|
|
const bool bIsClosestHit = (HitGroupShaderType == FVulkanShaderCompilerInternalState::EHitGroupShaderType::ClosestHit);
|
|
FShaderCompilerOutput& PartialOutput = bIsClosestHit ? MergedOutput : TempOutput;
|
|
|
|
FString PartialPreprocessedShaderSource = OriginalPreprocessedShaderSource;
|
|
UE::ShaderCompilerCommon::RemoveDeadCode(PartialPreprocessedShaderSource, InternalState.GetEntryPointName(), PartialOutput.Errors);
|
|
|
|
if (InternalState.bDebugDump)
|
|
{
|
|
DumpDebugShaderText(InternalState.Input, PartialPreprocessedShaderSource, *FString::Printf(TEXT("%s.hlsl"), PartialFileExtension));
|
|
}
|
|
|
|
const bool bPartialSuccess = CompileWithShaderConductor(InternalState, PartialPreprocessedShaderSource, PartialSerializedOutput, PartialOutput);
|
|
|
|
if (!bIsClosestHit)
|
|
{
|
|
MergedOutput.NumInstructions = FMath::Max(MergedOutput.NumInstructions, PartialOutput.NumInstructions);
|
|
MergedOutput.NumTextureSamplers = FMath::Max(MergedOutput.NumTextureSamplers, PartialOutput.NumTextureSamplers);
|
|
MergedOutput.Errors.Append(MoveTemp(PartialOutput.Errors));
|
|
}
|
|
|
|
return bPartialSuccess;
|
|
};
|
|
|
|
bool bSuccess = false;
|
|
|
|
// Closest Hit Module, always present
|
|
VulkanShaderCompilerSerializedOutput ClosestHitSerializedOutput;
|
|
{
|
|
bSuccess = CompilePartialExport(FVulkanShaderCompilerInternalState::EHitGroupShaderType::ClosestHit, TEXT("closest"), ClosestHitSerializedOutput);
|
|
}
|
|
|
|
// Any Hit Module, optional
|
|
const bool bHasAnyHitModule = !InternalState.AnyHitEntry.IsEmpty();
|
|
VulkanShaderCompilerSerializedOutput AnyHitSerializedOutput;
|
|
if (bSuccess && bHasAnyHitModule)
|
|
{
|
|
bSuccess = CompilePartialExport(FVulkanShaderCompilerInternalState::EHitGroupShaderType::AnyHit, TEXT("anyhit"), AnyHitSerializedOutput);
|
|
}
|
|
|
|
// Intersection Module, optional
|
|
const bool bHasIntersectionModule = !InternalState.IntersectionEntry.IsEmpty();
|
|
VulkanShaderCompilerSerializedOutput IntersectionSerializedOutput;
|
|
if (bSuccess && bHasIntersectionModule)
|
|
{
|
|
bSuccess = CompilePartialExport(FVulkanShaderCompilerInternalState::EHitGroupShaderType::Intersection, TEXT("intersection"), IntersectionSerializedOutput);
|
|
}
|
|
|
|
// Collapse the bindless UB usage into one set and then update the headers
|
|
ClosestHitSerializedOutput.UsedBindlessUB.Append(AnyHitSerializedOutput.UsedBindlessUB);
|
|
ClosestHitSerializedOutput.UsedBindlessUB.Append(IntersectionSerializedOutput.UsedBindlessUB);
|
|
UpdateBindlessUBs(InternalState, ClosestHitSerializedOutput, MergedOutput);
|
|
|
|
{
|
|
// :todo-jn: Having multiple entrypoints in a single SPIRV blob crashes on FLumenHardwareRayTracingMaterialHitGroup for some reason
|
|
// Adjust the header before we write it out
|
|
ClosestHitSerializedOutput.Header.RayGroupAnyHit = bHasAnyHitModule ? FVulkanShaderHeader::ERayHitGroupEntrypoint::SeparateBlob : FVulkanShaderHeader::ERayHitGroupEntrypoint::NotPresent;
|
|
ClosestHitSerializedOutput.Header.RayGroupIntersection = bHasIntersectionModule ? FVulkanShaderHeader::ERayHitGroupEntrypoint::SeparateBlob : FVulkanShaderHeader::ERayHitGroupEntrypoint::NotPresent;
|
|
|
|
check(ClosestHitSerializedOutput.Spirv.Data.Num() != 0);
|
|
FMemoryWriter Ar(MergedOutput.ShaderCode.GetWriteAccess(), true);
|
|
Ar << ClosestHitSerializedOutput.Header;
|
|
Ar << ClosestHitSerializedOutput.ShaderResourceTable;
|
|
|
|
{
|
|
uint32 SpirvCodeSizeBytes = ClosestHitSerializedOutput.Spirv.GetByteSize();
|
|
Ar << SpirvCodeSizeBytes;
|
|
Ar.Serialize((uint8*)ClosestHitSerializedOutput.Spirv.Data.GetData(), SpirvCodeSizeBytes);
|
|
}
|
|
|
|
if (bHasAnyHitModule)
|
|
{
|
|
uint32 SpirvCodeSizeBytes = AnyHitSerializedOutput.Spirv.GetByteSize();
|
|
Ar << SpirvCodeSizeBytes;
|
|
Ar.Serialize((uint8*)AnyHitSerializedOutput.Spirv.Data.GetData(), SpirvCodeSizeBytes);
|
|
}
|
|
|
|
if (bHasIntersectionModule)
|
|
{
|
|
uint32 SpirvCodeSizeBytes = IntersectionSerializedOutput.Spirv.GetByteSize();
|
|
Ar << SpirvCodeSizeBytes;
|
|
Ar.Serialize((uint8*)IntersectionSerializedOutput.Spirv.Data.GetData(), SpirvCodeSizeBytes);
|
|
}
|
|
}
|
|
|
|
MergedOutput.bSucceeded = bSuccess;
|
|
return bSuccess;
|
|
}
|
|
|
|
struct FPS5ShaderParameterParserPlatformConfiguration : public FShaderParameterParser::FPlatformConfiguration
|
|
{
|
|
FPS5ShaderParameterParserPlatformConfiguration(const FShaderCompilerInput& Input)
|
|
: FShaderParameterParser::FPlatformConfiguration()
|
|
{
|
|
EnumAddFlags(Flags, EShaderParameterParserConfigurationFlags::SupportsBindless | EShaderParameterParserConfigurationFlags::BindlessUsesArrays);
|
|
|
|
// Create a _RootShaderParameters and bind it in slot 0 like any other uniform buffer
|
|
if (Input.Target.GetFrequency() == SF_RayGen && Input.RootParametersStructure != nullptr)
|
|
{
|
|
ConstantBufferType = TEXTVIEW("cbuffer");
|
|
EnumAddFlags(Flags, EShaderParameterParserConfigurationFlags::UseStableConstantBuffer);
|
|
}
|
|
}
|
|
|
|
virtual FString GenerateBindlessAccess(EBindlessConversionType BindlessType, FStringView ShaderTypeString, FStringView IndexString) const final
|
|
{
|
|
checkf(false, TEXT("Vulkan does not use GenerateBindlessAccess"));
|
|
return FString();
|
|
}
|
|
};
|
|
|
|
void CompileVulkanShader(const FShaderCompilerInput& Input, const FString& InPreprocessedSource, FShaderCompilerOutput& Output, const class FString& WorkingDirectory)
|
|
{
|
|
check(IsVulkanShaderFormat(Input.ShaderFormat));
|
|
|
|
FString EntryPointName = Input.EntryPointName;
|
|
FString PreprocessedSource = InPreprocessedSource;
|
|
|
|
FPS5ShaderParameterParserPlatformConfiguration PlatformConfiguration(Input);
|
|
FShaderParameterParser ShaderParameterParser(PlatformConfiguration);
|
|
if (!ShaderParameterParser.ParseAndModify(Input, Output.Errors, PreprocessedSource))
|
|
{
|
|
// The FShaderParameterParser will add any relevant errors.
|
|
return;
|
|
}
|
|
|
|
FVulkanShaderCompilerInternalState InternalState(Input, &ShaderParameterParser);
|
|
|
|
const EHlslShaderFrequency HlslFrequency = InternalState.GetHlslShaderFrequency();
|
|
if (HlslFrequency == HSF_InvalidFrequency)
|
|
{
|
|
Output.bSucceeded = false;
|
|
FShaderCompilerError& NewError = Output.Errors.AddDefaulted_GetRef();
|
|
NewError.StrippedErrorMessage = FString::Printf(
|
|
TEXT("%s shaders not supported for use in Vulkan."),
|
|
CrossCompiler::GetFrequencyName(InternalState.GetShaderFrequency()));
|
|
return;
|
|
}
|
|
|
|
if (InternalState.bUseBindlessUniformBuffer)
|
|
{
|
|
InternalState.AllBindlessUBs = ConvertUBToBindless(PreprocessedSource);
|
|
}
|
|
|
|
if (ShaderParameterParser.DidModifyShader() || InternalState.AllBindlessUBs.Num() > 0)
|
|
{
|
|
Output.ModifiedShaderSource = PreprocessedSource;
|
|
}
|
|
|
|
bool bSuccess = false;
|
|
|
|
#if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
|
|
// HitGroup shaders might have multiple entrypoints that we combine into a single blob
|
|
if (InternalState.HasMultipleEntryPoints())
|
|
{
|
|
bSuccess = CompileShaderGroup(InternalState, PreprocessedSource, Output);
|
|
}
|
|
else
|
|
{
|
|
// Compile regular shader via ShaderConductor (DXC)
|
|
VulkanShaderCompilerSerializedOutput SerializedOutput;
|
|
bSuccess = CompileWithShaderConductor(InternalState, PreprocessedSource, SerializedOutput, Output);
|
|
|
|
if (InternalState.bUseBindlessUniformBuffer)
|
|
{
|
|
UpdateBindlessUBs(InternalState, SerializedOutput, Output);
|
|
}
|
|
|
|
// Write out the header and shader source code (except for the extra shaders in hit groups)
|
|
checkf(!(bSuccess && SerializedOutput.Spirv.Data.Num() == 0), TEXT("shader compilation was reported as successful but SPIR-V module is empty"));
|
|
FMemoryWriter Ar(Output.ShaderCode.GetWriteAccess(), true);
|
|
Ar << SerializedOutput.Header;
|
|
Ar << SerializedOutput.ShaderResourceTable;
|
|
|
|
uint32 SpirvCodeSizeBytes = SerializedOutput.Spirv.GetByteSize();
|
|
Ar << SpirvCodeSizeBytes;
|
|
if (SerializedOutput.Spirv.Data.Num() > 0)
|
|
{
|
|
Ar.Serialize((uint8*)SerializedOutput.Spirv.Data.GetData(), SpirvCodeSizeBytes);
|
|
}
|
|
}
|
|
#endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
|
|
|
|
if (Input.Environment.CompilerFlags.Contains(CFLAG_ExtraShaderData))
|
|
{
|
|
Output.ShaderCode.AddOptionalData(FShaderCodeName::Key, TCHAR_TO_UTF8(*Input.GenerateShaderName()));
|
|
}
|
|
|
|
Output.SerializeShaderCodeValidation();
|
|
|
|
ShaderParameterParser.ValidateShaderParameterTypes(Input, InternalState.IsMobileES31(), Output);
|
|
|
|
const bool bDirectCompile = FParse::Param(FCommandLine::Get(), TEXT("directcompile"));
|
|
if (bDirectCompile)
|
|
{
|
|
for (const auto& Error : Output.Errors)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%s\n"), *Error.GetErrorStringWithLineMarker());
|
|
}
|
|
ensure(bSuccess);
|
|
}
|
|
}
|
|
|
|
void OutputVulkanDebugData(const FShaderCompilerInput& Input, const FShaderPreprocessOutput& PreprocessOutput, const FShaderCompilerOutput& Output)
|
|
{
|
|
UE::ShaderCompilerCommon::DumpExtendedDebugShaderData(Input, PreprocessOutput, Output);
|
|
}
|