Files
UnrealEngine/Engine/Source/Developer/VulkanShaderFormat/Private/SpirVShaderCompiler.inl
2025-05-18 13:04:45 +08:00

1747 lines
64 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// .
#include "SpirvCommon.h"
#include "Serialization/MemoryWriter.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
// A collection of states and data that is locked in at the top level call and doesn't change throughout the compilation process
class FSpirvShaderCompilerInternalState
{
public:
FSpirvShaderCompilerInternalState(const FShaderCompilerInput& InInput, const FShaderParameterParser* InParameterParser)
: Input(InInput)
, ParameterParser(InParameterParser)
, bUseBindlessUniformBuffer(InInput.IsRayTracingShader() && ((EShaderFrequency)InInput.Target.Frequency != SF_RayGen))
, bIsRayHitGroupShader(InInput.IsRayTracingShader() && ((EShaderFrequency)InInput.Target.Frequency == SF_RayHitGroup))
, bSupportsBindless(InInput.IsBindlessEnabled())
, 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"));
}
}
virtual ~FSpirvShaderCompilerInternalState()
{
}
const FShaderCompilerInput& Input;
const FShaderParameterParser* ParameterParser;
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;
uint32 ShaderRecordGlobalsSize = 0;
// 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 bool IsRayTracingShader() const
{
return Input.IsRayTracingShader();
}
inline bool UseRootParametersStructure() const
{
// Only supported for RayGen currently
return (GetShaderFrequency() == SF_RayGen) && (Input.RootParametersStructure != nullptr);
}
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");
};
}
inline bool ShouldStripReflect() const
{
return (IsRayTracingShader() || (IsAndroid() && Input.Environment.GetCompileArgument(TEXT("STRIP_REFLECT_ANDROID"), true)));
}
// Provided by the platform that is compiling Spirv
virtual bool IsSM6() const = 0;
virtual bool IsSM5() const = 0;
virtual bool IsMobileES31() const = 0;
virtual CrossCompiler::FShaderConductorOptions::ETargetEnvironment GetMinimumTargetEnvironment() const = 0;
virtual bool IsAndroid() const = 0;
virtual bool SupportsOfflineCompiler() const = 0;
};
// Data structures that will get serialized into ShaderCompilerOutput
struct SpirvShaderCompilerSerializedOutput
{
SpirvShaderCompilerSerializedOutput()
: Header(FVulkanShaderHeader::EZero)
{
FMemory::Memzero(PackedResourceCounts);
}
FVulkanShaderHeader Header; // TODO: Convert descriptors into more generic Spirv information
FShaderResourceTable ShaderResourceTable;
FSpirv Spirv;
uint32 SpirvCRC = 0;
const ANSICHAR* SpirvEntryPointName = nullptr;
FShaderCodePackedResourceCounts PackedResourceCounts;
TSet<FString> UsedBindlessUB;
};
// --------------------------
namespace SpirvShaderCompiler
{
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());
}
static uint32 GetUBLayoutHash(const FShaderCompilerInput& ShaderInput, const FString& UBName)
{
uint32 LayoutHash = 0;
const FUniformBufferEntry* UniformBufferEntry = ShaderInput.Environment.UniformBufferMap.Find(UBName);
if (UniformBufferEntry)
{
LayoutHash = UniformBufferEntry->LayoutHash;
}
else if ((UBName == FShaderParametersMetadata::kRootUniformBufferBindingName) && ShaderInput.RootParametersStructure)
{
LayoutHash = ShaderInput.RootParametersStructure->GetLayoutHash();
}
return LayoutHash;
}
// Types of Global Samplers (see Common.ush for types)
// Must match EGlobalSamplerType in VulkanShaderResources.h
// and declarations in VulkanCommon.ush
static FVulkanShaderHeader::EGlobalSamplerType GetGlobalSamplerType(const FString& ResourceName)
{
#define VULKAN_GLOBAL_SAMPLER_NAME(FilterWrapName) if (ResourceName.EndsWith(TEXT(#FilterWrapName))) return FVulkanShaderHeader::EGlobalSamplerType::FilterWrapName
if (ResourceName.StartsWith(TEXT("VulkanGlobal")))
{
VULKAN_GLOBAL_SAMPLER_NAME(PointClampedSampler);
VULKAN_GLOBAL_SAMPLER_NAME(PointWrappedSampler);
VULKAN_GLOBAL_SAMPLER_NAME(BilinearClampedSampler);
VULKAN_GLOBAL_SAMPLER_NAME(BilinearWrappedSampler);
VULKAN_GLOBAL_SAMPLER_NAME(TrilinearClampedSampler);
VULKAN_GLOBAL_SAMPLER_NAME(TrilinearWrappedSampler);
}
return FVulkanShaderHeader::EGlobalSamplerType::Invalid;
#undef VULKAN_GLOBAL_SAMPLER_NAME
}
static bool HasDerivatives(const FSpirv& Spirv)
{
for (FSpirvConstIterator Iter = Spirv.begin(); Iter != Spirv.end(); ++Iter)
{
switch (Iter.Opcode())
{
case SpvOpCapability:
{
const uint32 Capability = Iter.Operand(1);
if ((Capability == SpvCapabilityComputeDerivativeGroupLinearNV) ||
(Capability == SpvCapabilityComputeDerivativeGroupQuadsNV))
{
return true;
}
}
break;
case SpvOpExtension:
case SpvOpEntryPoint:
// By the time we've reached extensions/entrypoints, we're done listing capabilities
return false;
default:
break;
}
}
return false;
}
static void FillShaderResourceUsageFlags(const FSpirvShaderCompilerInternalState& InternalState, SpirvShaderCompilerSerializedOutput& SerializedOutput)
{
FShaderCodePackedResourceCounts& PackedResourceCounts = SerializedOutput.PackedResourceCounts;
if (InternalState.Input.Target.GetFrequency() == SF_Compute &&
InternalState.Input.Environment.CompilerFlags.Contains(CFLAG_CheckForDerivativeOps))
{
if (!HasDerivatives(SerializedOutput.Spirv))
{
PackedResourceCounts.UsageFlags |= EShaderResourceUsageFlags::NoDerivativeOps;
}
}
if (InternalState.bSupportsBindless)
{
PackedResourceCounts.UsageFlags |= EShaderResourceUsageFlags::BindlessResources;
PackedResourceCounts.UsageFlags |= EShaderResourceUsageFlags::BindlessSamplers;
}
if (InternalState.Input.Environment.CompilerFlags.Contains(CFLAG_ShaderBundle))
{
PackedResourceCounts.UsageFlags |= EShaderResourceUsageFlags::ShaderBundle;
}
// TODO: When DiagnosticBuffer is supported:
// PackedResourceCounts.UsageFlags |= EShaderResourceUsageFlags::DiagnosticBuffer;
}
static void BuildShaderOutput(
SpirvShaderCompilerSerializedOutput& SerializedOutput,
FShaderCompilerOutput& ShaderOutput,
const FSpirvShaderCompilerInternalState& InternalState,
const FSpirvReflectBindings& SpirvReflectBindings,
const FString& DebugName,
TBitArray<>& UsedUniformBufferSlots
)
{
auto ParseNumber = []<typename T>(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 0u;
}
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;
};
const FShaderCompilerInput& ShaderInput = InternalState.Input;
const EShaderFrequency Frequency = InternalState.GetShaderFrequency();
FVulkanShaderHeader& Header = SerializedOutput.Header;
Header.SpirvCRC = SerializedOutput.SpirvCRC;
Header.RayTracingPayloadType = ShaderInput.Environment.GetCompileArgument(TEXT("RT_PAYLOAD_TYPE"), 0u);
Header.RayTracingPayloadSize = ShaderInput.Environment.GetCompileArgument(TEXT("RT_PAYLOAD_MAX_SIZE"), 0u);
// :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*)&Header.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);
Header.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);
Header.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);
Header.InOutMask |= (1u << BitIndex);
}
}
}
}
}
// Build the SRT for this shader.
{
checkf(Header.UniformBufferInfos.Num() == (UsedUniformBufferSlots.FindLast(true) + 1),
TEXT("Some of the Uniform Buffers containing constants weren't flag as in-use. This might lead to duplicate indices being assigned."));
FShaderCompilerResourceTable CompilerSRT;
if (!BuildResourceTableMapping(ShaderInput.Environment.ResourceTableMap, ShaderInput.Environment.UniformBufferMap, UsedUniformBufferSlots, ShaderOutput.ParameterMap, CompilerSRT))
{
ShaderOutput.Errors.Add(TEXT("Internal error on BuildResourceTableMapping."));
return;
}
UE::ShaderCompilerCommon::BuildShaderResourceTable(CompilerSRT, SerializedOutput.ShaderResourceTable);
// The previous step also added resource only UBs starting at the first free slot in UsedUniformBufferSlots
// We need to add the hashes for their layouts in the same slots of our UniformBufferInfos in the header
{
const int32 NumUBSlots = CompilerSRT.MaxBoundResourceTable + 1;
if (Header.UniformBufferInfos.Num() < NumUBSlots)
{
Header.UniformBufferInfos.SetNumZeroed(NumUBSlots);
}
TArray<FStringView> UBParameterNames = ShaderOutput.ParameterMap.GetAllParameterNamesOfType(EShaderParameterType::UniformBuffer);
for (const FStringView& ParameterName : UBParameterNames)
{
TOptional<FParameterAllocation> Allocation = ShaderOutput.ParameterMap.FindParameterAllocation(ParameterName);
check(Allocation.IsSet());
const uint32 UniformBufferIndex = Allocation.GetValue().BufferIndex;
FVulkanShaderHeader::FUniformBufferInfo& UniformBufferInfo = Header.UniformBufferInfos[UniformBufferIndex];
UniformBufferInfo.bHasResources = 1;
const bool bIsRootParamStructure = (ParameterName == FShaderParametersMetadata::kRootUniformBufferBindingName) && ShaderInput.RootParametersStructure;
if (bIsRootParamStructure)
{
check(UniformBufferIndex == FShaderParametersMetadata::kRootCBufferBindingIndex);
const uint32 UBLayoutHash = CompilerSRT.ResourceTableLayoutHashes[UniformBufferIndex];
checkf(!UBLayoutHash || (UBLayoutHash == ShaderInput.RootParametersStructure->GetLayoutHash()),
TEXT("Resource table layout hash for RootParametersStructure (0x%08X) should be unset (0x0) or identical to shader input (0x%08X)!"),
UBLayoutHash, ShaderInput.RootParametersStructure->GetLayoutHash());
CompilerSRT.ResourceTableLayoutHashes[UniformBufferIndex] = ShaderInput.RootParametersStructure->GetLayoutHash();
}
else
{
const uint32 UBLayoutHash = CompilerSRT.ResourceTableLayoutHashes[UniformBufferIndex];
checkf(!UniformBufferInfo.LayoutHash || (UniformBufferInfo.LayoutHash == UBLayoutHash),
TEXT("Existing layout hash (0x%08X) should be unset (resource only UB) or identical to resource table (0x%08X)!"),
UniformBufferInfo.LayoutHash, UBLayoutHash);
UniformBufferInfo.LayoutHash = UBLayoutHash;
}
}
}
}
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 (InternalState.SupportsOfflineCompiler())
{
CompileShaderOffline(ShaderInput, ShaderOutput, (const ANSICHAR*)SerializedOutput.Spirv.GetByteData(), SerializedOutput.Spirv.GetByteSize(), true, SerializedOutput.SpirvEntryPointName);
}
}
// 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 VULKAN_ENABLE_BINDING_DEBUG_NAMES
Header.DebugName = DebugName;
#else
if (ShaderInput.Environment.CompilerFlags.Contains(CFLAG_ExtraShaderData))
{
Header.DebugName = ShaderInput.GenerateShaderName();
}
#endif
}
#if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
static void GatherSpirvReflectionBindings(
spv_reflect::ShaderModule& Reflection,
FSpirvReflectBindings& OutBindings,
TSet<FString>& OutBindlessUB,
const FSpirvShaderCompilerInternalState& 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::MaxNumSets + 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(FSpirv& 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 FSpirvShaderCompilerInternalState& InternalState,
SpirvShaderCompilerSerializedOutput& SerializedOutput,
FShaderCompilerOutput& Output
)
{
// 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);
const FString UBOGlobalsNameSpv(ANSI_TO_TCHAR(CrossCompiler::FShaderConductorContext::GetIdentifierTable().GlobalsUniformBuffer));
const FString UBORootParamNameSpv(FShaderParametersMetadata::kRootUniformBufferBindingName);
TBitArray<> UsedUniformBufferSlots;
const int32 MaxNumBits = VulkanBindless::MaxUniformBuffersPerStage * SF_NumFrequencies;
UsedUniformBufferSlots.Init(false, MaxNumBits);
// Final descriptor binding numbers for all other resource types
{
const ShaderStage::EStage UEStage = ShaderStage::GetStageForFrequency(InternalState.GetShaderFrequency());
const int32 StageOffset = InternalState.bSupportsBindless ? (UEStage * VulkanBindless::MaxUniformBuffersPerStage) : 0;
const uint32_t DescSetNumber = InternalState.bSupportsBindless ? (uint32_t)VulkanBindless::BindlessSingleUseUniformBufferSet : (uint32_t)UEStage;
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);
}
}*/
};
auto AddReflectionInfos = [&](TArray<SpvReflectDescriptorBinding*>& BindingArray, const VkDescriptorType DescriptorType, int32 BindingTypeCount, bool bIsPackedUniformBuffer=false)
{
for (const SpvReflectDescriptorBinding* Binding : BindingArray)
{
checkf(!InternalState.bSupportsBindless || (DescriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER),
TEXT("Bindless shaders should only have uniform buffers."));
const FString ResourceName(ANSI_TO_TCHAR(Binding->name));
const bool bIsGlobalOrRootBuffer = ((UBOGlobalsNameSpv == ResourceName) || (UBORootParamNameSpv == ResourceName));
if ((bIsPackedUniformBuffer && !bIsGlobalOrRootBuffer) || ((!bIsPackedUniformBuffer) && bIsGlobalOrRootBuffer))
{
continue;
}
const int32 BindingSlot = SerializedOutput.Header.Bindings.Num();
const int32 BindingIndex = StageOffset + BindingSlot;
FVulkanShaderHeader::FBindingInfo& BindingInfo = SerializedOutput.Header.Bindings.AddZeroed_GetRef();
BindingInfo.DescriptorType = DescriptorType;
#if VULKAN_ENABLE_BINDING_DEBUG_NAMES
BindingInfo.DebugName = ResourceName;
#endif
const SpvReflectResult SpvResult = Reflection.ChangeDescriptorBindingNumbers(Binding, BindingIndex, DescSetNumber);
check(SpvResult == SPV_REFLECT_RESULT_SUCCESS);
const int32 ReflectionSlot = BindingSlot;
check(InternalState.ParameterParser);
const FShaderParameterParser::FParsedShaderParameter* ParsedParam = InternalState.ParameterParser->FindParameterInfosUnsafe(ResourceName);
switch (DescriptorType)
{
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
HandleReflectedShaderUAV(ResourceName, BindingTypeCount, ReflectionSlot, 1, Output);
AddShaderValidationType(BindingTypeCount, ParsedParam, Output);
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
HandleReflectedShaderResource(ResourceName, BindingTypeCount, ReflectionSlot, 1, Output);
AddShaderValidationType(BindingTypeCount, ParsedParam, Output);
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
{
// Regular samplers need reflection to get bindings, global samplers get bound automagically.
FVulkanShaderHeader::EGlobalSamplerType GlobalSamplerType = GetGlobalSamplerType(ResourceName);
if (GlobalSamplerType == FVulkanShaderHeader::EGlobalSamplerType::Invalid)
{
HandleReflectedShaderSampler(ResourceName, ReflectionSlot, Output);
}
else
{
FVulkanShaderHeader::FGlobalSamplerInfo& GlobalSamplerInfo = SerializedOutput.Header.GlobalSamplerInfos.AddZeroed_GetRef();
GlobalSamplerInfo.BindingIndex = BindingSlot;
GlobalSamplerInfo.Type = GlobalSamplerType;
}
}
break;
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
HandleReflectedShaderResource(ResourceName, BindingTypeCount, ReflectionSlot, 1, Output);
AddShaderValidationType(BindingTypeCount, ParsedParam, Output);
break;
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
{
SerializedOutput.Header.InputAttachmentsMask |= (1u << Binding->input_attachment_index);
FVulkanShaderHeader::FInputAttachmentInfo& InputAttachmentInfo = SerializedOutput.Header.InputAttachmentInfos.AddZeroed_GetRef();
InputAttachmentInfo.BindingIndex = BindingSlot;
InputAttachmentInfo.Type = (FVulkanShaderHeader::EAttachmentType)Binding->input_attachment_index;
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
{
if (bIsPackedUniformBuffer)
{
// Use the given global ResourceName instead of patching it to _Globals_h
if (InternalState.UseRootParametersStructure())
{
check(ReflectionSlot == FShaderParametersMetadata::kRootCBufferBindingIndex);
HandleReflectedUniformBuffer(ResourceName, ReflectionSlot, Output);
}
// 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(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)
{
check(BindingTypeCount == 0); // Global constants should always be the first UB
HandleReflectedGlobalConstantBufferMember(
MemberName,
BindingTypeCount,
Member.absolute_offset,
Member.size,
Output
);
}
SerializedOutput.Header.PackedGlobalsSize = FMath::Max<uint32>((Member.absolute_offset + Member.size), SerializedOutput.Header.PackedGlobalsSize);
SerializedOutput.Header.PackedGlobalsSize = Align(SerializedOutput.Header.PackedGlobalsSize, 16u);
}
}
else
{
check(BindingTypeCount == ReflectionSlot);
check(!UsedUniformBufferSlots[ReflectionSlot]);
HandleReflectedUniformBuffer(ResourceName, ReflectionSlot, Output);
AddShaderValidationUBSize(BindingTypeCount, Binding->block.padded_size, Output);
const EUniformBufferMemberReflectionReason Reason = ShouldReflectUniformBufferMembers(InternalState.Input, ResourceName);
if (Reason != EUniformBufferMemberReflectionReason::None)
{
// Register uniform buffer members that are in use
for (uint32 MemberIndex = 0; MemberIndex < Binding->block.member_count; ++MemberIndex)
{
const SpvReflectBlockVariable& Member = Binding->block.members[MemberIndex];
if ((Member.flags & SPV_REFLECT_VARIABLE_FLAGS_UNUSED) != 0)
{
continue;
}
const FString MemberName(Member.name);
HandleReflectedUniformBufferConstantBufferMember(
Reason,
ResourceName,
ReflectionSlot,
MemberName,
Member.absolute_offset,
Member.size,
Output
);
}
}
}
check(!UsedUniformBufferSlots[ReflectionSlot]);
UsedUniformBufferSlots[ReflectionSlot] = true;
FVulkanShaderHeader::FUniformBufferInfo& UniformBufferInfo = SerializedOutput.Header.UniformBufferInfos.AddZeroed_GetRef();
UniformBufferInfo.LayoutHash = GetUBLayoutHash(InternalState.Input, ResourceName);
check(SerializedOutput.Header.Bindings.Num() == SerializedOutput.Header.UniformBufferInfos.Num());
}
break;
default:
check(false);
break;
};
BindingTypeCount++;
}
return BindingTypeCount;
};
// Process Globals first (PackedUniformBuffer) and then regular UBs
const int32 GlobalUBCount = AddReflectionInfos(Bindings.UniformBuffers, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, true);
const int32 UBOBindings = AddReflectionInfos(Bindings.UniformBuffers, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, GlobalUBCount);
SerializedOutput.Header.NumBoundUniformBuffers = UBOBindings;
SerializedOutput.PackedResourceCounts.NumCBs = (uint8)UBOBindings;
AddReflectionInfos(Bindings.InputAttachments, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0);
int32 UAVBindings = 0;
UAVBindings = AddReflectionInfos(Bindings.TBufferUAVs, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, UAVBindings);
UAVBindings = AddReflectionInfos(Bindings.SBufferUAVs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, UAVBindings);
UAVBindings = AddReflectionInfos(Bindings.TextureUAVs, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, UAVBindings);
SerializedOutput.PackedResourceCounts.NumUAVs = (uint8)UAVBindings;
int32 SRVBindings = 0;
SRVBindings = AddReflectionInfos(Bindings.TBufferSRVs, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, SRVBindings);
checkf(Bindings.SBufferSRVs.IsEmpty(), TEXT("GatherSpirvReflectionBindings should have dumped all SBufferSRVs into SBufferUAVs."));
SRVBindings = AddReflectionInfos(Bindings.TextureSRVs, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, SRVBindings);
SerializedOutput.PackedResourceCounts.NumSRVs = (uint8)SRVBindings;
Output.NumTextureSamplers = AddReflectionInfos(Bindings.Samplers, VK_DESCRIPTOR_TYPE_SAMPLER, 0);
SerializedOutput.PackedResourceCounts.NumSamplers = (uint8)Output.NumTextureSamplers;
AddReflectionInfos(Bindings.AccelerationStructures, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 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.ShouldStripReflect())
{
const char* OptArgs[] = { "--strip-reflect", "-O"};
if (!CompilerContext.OptimizeSpirv(SerializedOutput.Spirv.Data, OptArgs, UE_ARRAY_COUNT(OptArgs)))
{
Output.Errors.Add(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 (InternalState.IsAndroid())
{
const char* OptArgs[] = {
"--android-driver-patch",
// FORT-733360: Some Adreno drivers have bugs for interpolators, which are arrays,
// hence we need to get rid of them.
"--adv-interface-variable-scalar-replacement=skip-matrices"
};
if (!CompilerContext.OptimizeSpirv(SerializedOutput.Spirv.Data, OptArgs, UE_ARRAY_COUNT(OptArgs)))
{
Output.Errors.Add(TEXT("Failed to apply driver patches for Android"));
return false;
}
}
// :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 != FSpirvShaderCompilerInternalState::EHitGroupShaderType::ClosestHit))
{
SerializedOutput.SpirvEntryPointName = "main_00000000_00000000";
}
else
{
SerializedOutput.SpirvEntryPointName = PatchSpirvEntryPointWithCRC(SerializedOutput.Spirv, SerializedOutput.SpirvCRC);
}
Output.NumInstructions = CalculateSpirvInstructionCount(SerializedOutput.Spirv);
BuildShaderOutput(
SerializedOutput,
Output,
InternalState,
Bindings,
InternalState.GetDebugName(),
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(FSpirv& 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 SpirvCreateDXCCompileBatchFiles(
const CrossCompiler::FShaderConductorContext& CompilerContext,
const FSpirvShaderCompilerInternalState& 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 macros resolved and comments removed, it makes this easier...
static FString ParseEntrypointDecl(FShaderSource::FViewType PreprocessedShader, FStringView Entrypoint)
{
FShaderSource::FStringType EntrypointConverted(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(EntrypointConverted);
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(EntrypointConverted, 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] != '(')
{
EntrypointIndex = PreprocessedShader.Find(EntrypointConverted, ParamsStart);
continue;
}
int32 ParamsEnd = PreprocessedShader.Find(ANSITEXTVIEW(")"), ParamsStart + 1);
check(ParamsEnd != INDEX_NONE);
if (ParamsEnd == INDEX_NONE)
{
// Suspicious
EntrypointIndex = PreprocessedShader.Find(EntrypointConverted, ParamsStart);
continue;
}
// Make sure to grab everything up to the function content
int32 DeclEnd = ParamsEnd + 1;
while (PreprocessedShader[DeclEnd] != '{' && (PreprocessedShader[DeclEnd] != ';'))
{
++DeclEnd;
}
if (PreprocessedShader[DeclEnd] != '{')
{
EntrypointIndex = PreprocessedShader.Find(EntrypointConverted, 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] != ';') && (PreprocessedShader[DeclBegin] != '}'))
{
--DeclBegin;
}
++DeclBegin;
EntryPointDecl = FString::ConstructFromPtrSize(&PreprocessedShader[DeclBegin], DeclEnd - DeclBegin);
EraseDebugLines(EntryPointDecl);
EntryPointDecl.TrimStartAndEndInline();
break;
}
return EntryPointDecl;
}
static uint8 ParseWaveSize(
const FSpirvShaderCompilerInternalState& InternalState,
FShaderSource::FViewType 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 = FString::ConstructFromPtrSize(&EntrypointDecl[StartNumber], EndNumber - 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 FSpirvShaderCompilerInternalState& InternalState,
FShaderSource::FViewType PreprocessedShader,
SpirvShaderCompilerSerializedOutput& SerializedOutput,
FShaderCompilerOutput& Output
)
{
const FShaderCompilerInput& Input = InternalState.Input;
CrossCompiler::FShaderConductorContext CompilerContext;
// Inject additional macro definitions to circumvent missing features: external textures
FShaderCompilerDefinitions AdditionalDefines;
TArray<FString> ExtraDxcArgs;
if (InternalState.IsSM6())
{
ExtraDxcArgs.Add(TEXT("-fvk-allow-rwstructuredbuffer-arrays"));
}
// Fix issues when reading matrices directly for ByteAddrBuffer
// By default the compiler will emit column-major loads and this flag makes sure to revert to the original behavior of row-major.
ExtraDxcArgs.Add(TEXT("-fspv-use-legacy-buffer-matrix-order"));
// Load shader source into compiler context
CompilerContext.LoadSource(PreprocessedShader, Input.VirtualSourceFilePath, InternalState.GetEntryPointName(), InternalState.GetShaderFrequency(), &AdditionalDefines, &ExtraDxcArgs);
// Initialize compilation options for ShaderConductor
CrossCompiler::FShaderConductorOptions Options;
Options.TargetEnvironment = InternalState.GetMinimumTargetEnvironment();
Options.bWarningsAsErrors = Input.Environment.CompilerFlags.Contains(CFLAG_WarningsAsErrors);
// 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 (InternalState.IsRayTracingShader() || InternalState.IsSM6())
{
// Use SM 6.6 as the baseline for Vulkan SM6 shaders
Options.ShaderModel.Major = 6;
Options.ShaderModel.Minor = 6;
}
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)
{
SpirvCreateDXCCompileBatchFiles(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);
// 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);
// Flush warnings
CompilerContext.FlushErrors(Output.Errors);
// Return code reflection if requested for shader analysis
if (Input.Environment.CompilerFlags.Contains(CFLAG_OutputAnalysisArtifacts) && Output.bSucceeded)
{
const TArray<uint32>& SpirvData = SerializedOutput.Spirv.Data;
FGenericShaderStat ShaderReflection;
if (CrossCompiler::FShaderConductorContext::Disassemble(CrossCompiler::EShaderConductorIR::Spirv, SpirvData.GetData(), SpirvData.Num() * SpirvData.GetTypeSize(), ShaderReflection))
{
ShaderReflection.StatName = FName(FString::Printf(TEXT("%s (%s)"), *ShaderReflection.StatName.ToString(), *InternalState.Input.EntryPointName));
Output.ShaderStatistics.Add(MoveTemp(ShaderReflection));
}
}
return true;
}
#endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
static void ModifyCompilerInput(FSpirvShaderCompilerInternalState& InternalState, FShaderCompilerInput& Input)
{
Input.Environment.SetDefine(TEXT("COMPILER_HLSLCC"), 1);
Input.Environment.SetDefine(TEXT("COMPILER_VULKAN"), 1);
if (InternalState.IsMobileES31())
{
Input.Environment.SetDefine(TEXT("ES3_1_PROFILE"), 1);
Input.Environment.SetDefine(TEXT("VULKAN_PROFILE"), 1);
}
else if (InternalState.IsSM6())
{
Input.Environment.SetDefine(TEXT("VULKAN_PROFILE_SM6"), 1);
Input.Environment.SetDefine(TEXT("PLATFORM_SUPPORTS_CALLABLE_SHADERS"), 1);
}
else if (InternalState.IsSM5())
{
Input.Environment.SetDefine(TEXT("VULKAN_PROFILE_SM5"), 1);
}
Input.Environment.SetDefine(TEXT("row_major"), TEXT(""));
Input.Environment.SetDefine(TEXT("COMPILER_SUPPORTS_ATTRIBUTES"), (uint32)1);
Input.Environment.SetDefine(TEXT("COMPILER_SUPPORTS_DUAL_SOURCE_BLENDING_SLOT_DECORATION"), (uint32)1);
Input.Environment.SetDefine(TEXT("PLATFORM_SUPPORTS_ROV"), 0); // Disabled until DXC->SPRIV ROV support is implemented
if (Input.Environment.FullPrecisionInPS || (Input.SharedEnvironment.IsValid() && Input.SharedEnvironment->FullPrecisionInPS))
{
Input.Environment.SetDefine(TEXT("FORCE_FLOATS"), (uint32)1);
}
if (Input.Environment.CompilerFlags.Contains(CFLAG_InlineRayTracing))
{
Input.Environment.SetDefine(TEXT("PLATFORM_SUPPORTS_INLINE_RAY_TRACING"), 1);
// Support is only garanteed on desktop currently
Input.Environment.SetDefine(TEXT("VULKAN_SUPPORTS_RAY_TRACING_POSITION_FETCH"), InternalState.IsAndroid() ? 0 : 1);
}
if (Input.Environment.CompilerFlags.Contains(CFLAG_AllowRealTypes))
{
Input.Environment.SetDefine(TEXT("PLATFORM_SUPPORTS_REAL_TYPES"), 1);
}
// We have ETargetEnvironment::Vulkan_1_1 by default as a min spec now
{
Input.Environment.SetDefine(TEXT("PLATFORM_SUPPORTS_SM6_0_WAVE_OPERATIONS"), 1);
Input.Environment.SetDefine(TEXT("VULKAN_SUPPORTS_SUBGROUP_SIZE_CONTROL"), 1);
}
Input.Environment.SetDefine(TEXT("BINDLESS_SRV_ARRAY_PREFIX"), FShaderParameterParser::kBindlessSRVArrayPrefix);
Input.Environment.SetDefine(TEXT("BINDLESS_UAV_ARRAY_PREFIX"), FShaderParameterParser::kBindlessUAVArrayPrefix);
Input.Environment.SetDefine(TEXT("BINDLESS_SAMPLER_ARRAY_PREFIX"), FShaderParameterParser::kBindlessSamplerArrayPrefix);
if (InternalState.IsAndroid())
{
// On most Android devices uint64_t is unsupported so we emulate as 2 uint32_t's
Input.Environment.SetDefine(TEXT("EMULATE_VKDEVICEADRESS"), 1);
}
if (Input.IsRayTracingShader())
{
// Name of the structure in raytracing shader records in VulkanCommon.usf
Input.RequiredSymbols.Add(TEXT("HitGroupSystemRootConstants"));
// Always remove dead code for ray tracing shaders regardless of cvar settings,
// we can't support multiple entrypoints remaining in the binaries
Input.Environment.CompilerFlags.Add(CFLAG_RemoveDeadCode);
}
}
static void UpdateBindlessUBs(const FSpirvShaderCompilerInternalState& InternalState, SpirvShaderCompilerSerializedOutput& SerializedOutput, FShaderCompilerOutput& Output)
{
checkf(SerializedOutput.Header.Bindings.Num() == 0, TEXT("Shaders using bindless UBs should have no other bindings."));
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& Info = SerializedOutput.Header.UniformBufferInfos.AddZeroed_GetRef();
Info.LayoutHash = SpirvShaderCompiler::GetUBLayoutHash(InternalState.Input, CBName);
Info.BindlessCBIndex = CBIndex;
const int32 UBIndex = SerializedOutput.Header.UniformBufferInfos.Num() - 1;
Output.ParameterMap.AddParameterAllocation(CBName, UBIndex, 0, 1, EShaderParameterType::UniformBuffer);
}
}
}
// :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"), *SpirvShaderCompiler::kBindlessCBPrefix, CBIndex, *CBName);
const FString BindlessCBType = PrefixedCBName + TEXT("_Type");
const FString BindlessCBHeapName = PrefixedCBName + SpirvShaderCompiler::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 = FString::ConstructFromPtrSize(MemberTypeStartPtr, MemberTypeEndPtr - 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 = FString::ConstructFromPtrSize(OpeningBracePtr + 1, ClosingBracePtr - OpeningBracePtr - 1);
const FString NewDecl = GenerateNewDecl(CBIndex, Members, StructName);
const int32 OldDeclLen = ClosingBraceIndex - SearchIndex + 1;
PreprocessedShaderSource.RemoveAt(SearchIndex, OldDeclLen, EAllowShrinking::No);
PreprocessedShaderSource.InsertAt(SearchIndex, NewDecl);
SearchIndex = PreprocessedShaderSource.Find(UniformBufferDeclIdentifier, ESearchCase::CaseSensitive, ESearchDir::FromStart, SearchIndex + NewDecl.Len());
}
}
return BindlessUBs;
}
static bool CompileShaderGroup(
FSpirvShaderCompilerInternalState& InternalState,
const FShaderSource::FStringType& 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](
FSpirvShaderCompilerInternalState::EHitGroupShaderType HitGroupShaderType,
const TCHAR* PartialFileExtension,
SpirvShaderCompilerSerializedOutput& PartialSerializedOutput)
{
InternalState.HitGroupShaderType = HitGroupShaderType;
FShaderCompilerOutput TempOutput;
const bool bIsClosestHit = (HitGroupShaderType == FSpirvShaderCompilerInternalState::EHitGroupShaderType::ClosestHit);
FShaderCompilerOutput& PartialOutput = bIsClosestHit ? MergedOutput : TempOutput;
FShaderSource::FViewType OrigSourceView(OriginalPreprocessedShaderSource);
FShaderSource PartialPreprocessedShaderSource(OrigSourceView);
UE::ShaderCompilerCommon::RemoveDeadCode(PartialPreprocessedShaderSource, InternalState.GetEntryPointName(), PartialOutput.Errors);
if (InternalState.bDebugDump)
{
DumpDebugShaderText(InternalState.Input, PartialPreprocessedShaderSource.GetView().GetData(), *FString::Printf(TEXT("%s.hlsl"), PartialFileExtension));
}
const bool bPartialSuccess = SpirvShaderCompiler::CompileWithShaderConductor(InternalState, PartialPreprocessedShaderSource.GetView(), 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
SpirvShaderCompilerSerializedOutput ClosestHitSerializedOutput;
{
bSuccess = CompilePartialExport(FSpirvShaderCompilerInternalState::EHitGroupShaderType::ClosestHit, TEXT("closest"), ClosestHitSerializedOutput);
}
// Any Hit Module, optional
const bool bHasAnyHitModule = !InternalState.AnyHitEntry.IsEmpty();
SpirvShaderCompilerSerializedOutput AnyHitSerializedOutput;
if (bSuccess && bHasAnyHitModule)
{
bSuccess = CompilePartialExport(FSpirvShaderCompilerInternalState::EHitGroupShaderType::AnyHit, TEXT("anyhit"), AnyHitSerializedOutput);
}
// Intersection Module, optional
const bool bHasIntersectionModule = !InternalState.IntersectionEntry.IsEmpty();
SpirvShaderCompilerSerializedOutput IntersectionSerializedOutput;
if (bSuccess && bHasIntersectionModule)
{
bSuccess = CompilePartialExport(FSpirvShaderCompilerInternalState::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);
}
}
// Return code reflection if requested for shader analysis
if (InternalState.Input.Environment.CompilerFlags.Contains(CFLAG_OutputAnalysisArtifacts) && bSuccess)
{
{
const TArray<uint32>& SpirvData = ClosestHitSerializedOutput.Spirv.Data;
FGenericShaderStat ClosestHitReflection;
if (CrossCompiler::FShaderConductorContext::Disassemble(CrossCompiler::EShaderConductorIR::Spirv, SpirvData.GetData(), SpirvData.Num() * SpirvData.GetTypeSize(), ClosestHitReflection))
{
ClosestHitReflection.StatName = FName(FString::Printf(TEXT("%s (%s)"), *ClosestHitReflection.StatName.ToString(), *InternalState.GetEntryPointName()));
MergedOutput.ShaderStatistics.Add(MoveTemp(ClosestHitReflection));
}
}
if (bHasAnyHitModule)
{
const TArray<uint32>& SpirvData = AnyHitSerializedOutput.Spirv.Data;
FGenericShaderStat AnyHitReflection;
if (CrossCompiler::FShaderConductorContext::Disassemble(CrossCompiler::EShaderConductorIR::Spirv, SpirvData.GetData(), SpirvData.Num() * SpirvData.GetTypeSize(), AnyHitReflection))
{
AnyHitReflection.StatName = FName(FString::Printf(TEXT("%s (%s)"), *AnyHitReflection.StatName.ToString(), *InternalState.AnyHitEntry));
MergedOutput.ShaderStatistics.Add(MoveTemp(AnyHitReflection));
}
}
if (bHasIntersectionModule)
{
const TArray<uint32>& SpirvData = IntersectionSerializedOutput.Spirv.Data;
FGenericShaderStat IntersectionReflection;
if (CrossCompiler::FShaderConductorContext::Disassemble(CrossCompiler::EShaderConductorIR::Spirv, SpirvData.GetData(), SpirvData.Num()* SpirvData.GetTypeSize(), IntersectionReflection))
{
IntersectionReflection.StatName = FName(FString::Printf(TEXT("%s (%s)"), *IntersectionReflection.StatName.ToString(), *InternalState.IntersectionEntry));
MergedOutput.ShaderStatistics.Add(MoveTemp(IntersectionReflection));
}
}
}
MergedOutput.bSucceeded = bSuccess;
return bSuccess;
}
}; // SpirvShaderCompiler