Refactor SPIR-V patching and strip debug instructions for Vulkan mobile except OpName instructions in --strip-reflect pass as UE always needs this reflection information.

Rebuild ShaderConductor for Win64, Mac, Linux.

#rb Carl.Lloyd, Rolando.Caloca, Ryan.Vance
#fyi Mihnea.Balta, Will.Damon, Dmitriy.Dyomin, Michael.Sartain, Brandon.Schaefer
#jira none
#rnx

[CL 16186169 by Lukas Hermanns in ue5-main branch]
This commit is contained in:
Lukas Hermanns
2021-05-03 15:22:38 -04:00
parent a21dd65e3c
commit c2eda93a8d
10 changed files with 378 additions and 229 deletions

View File

@@ -24,7 +24,6 @@ THIRD_PARTY_INCLUDES_START
#include "SPIRV/GLSL.std.450.h"
#include "SPIRV/doc.h"
#include "SPIRV/disassemble.h"
#include "SPIRV/spirv.hpp"
THIRD_PARTY_INCLUDES_END
#if defined(_MSC_VER) && _MSC_VER >= 1800
@@ -166,33 +165,8 @@ static EShLanguage GetStage(EHlslShaderFrequency Frequency)
return EShLangCount;
}
static void ComputeMovableWordIndices(FVulkanSpirv& Spirv)
void PatchSpirvReflectionEntries(FVulkanSpirv& Spirv)
{
// SPIRV Header
const uint32* PtrStart = Spirv.Data.GetData();
const uint32* Ptr = PtrStart;
const uint32* PtrEnd = Spirv.Data.GetData() + Spirv.Data.Num();
const uint32_t MagicNumberValue = *Ptr++;
check(MagicNumberValue == spv::MagicNumber);
uint32_t Version = *Ptr++;
uint32_t Generator = *Ptr++;
uint32_t Bound = *Ptr++;
const uint32_t ZeroCheckValue = *Ptr++;
check(ZeroCheckValue == 0);
auto ReadLiteralString = [](const uint32_t* Ptr)
{
FString S;
const char* Str = (const char*)Ptr;
// Empty string is allowed...
while (*Str)
{
S += *Str;
++Str;
}
return S;
};
TMap<uint32, FString> Names;
struct FDecorations
{
@@ -204,57 +178,38 @@ static void ComputeMovableWordIndices(FVulkanSpirv& Spirv)
TMap<uint32, FDecorations> Decorations;
TMap<uint32, uint32> TypePointerUniforms;
TMap<uint32, uint32> VariableUniformTypes;
bool bFoundEntry = false;
while (Ptr < PtrEnd)
bool bDone = false;
for (FSpirvConstIterator Iter = Spirv.cbegin(); Iter != Spirv.cend() && !bDone; ++Iter)
{
uint32_t WordCount = (*Ptr >> spv::WordCountShift) & spv::OpCodeMask;
spv::Op OpCode = (spv::Op)(*Ptr & spv::OpCodeMask);
switch (OpCode)
switch (Iter.Opcode())
{
case spv::OpEntryPoint:
{
uint32 ExecModel = Ptr[1];
uint32 EntryPoint = Ptr[2];
FString Name = ReadLiteralString(Ptr + 3);
if (Name == TEXT("main_00000000_00000000"))
{
check(Spirv.OffsetToEntryPoint == 0);
Spirv.OffsetToEntryPoint = (uint32)(&Ptr[3] - PtrStart);
bFoundEntry = true;
}
}
break;
case spv::OpName:
{
uint32 TargetId = Ptr[1];
FString Name = ReadLiteralString(Ptr + 2);
if (Name == TEXT("main_00000000_00000000"))
{
check(Spirv.OffsetToMainName == 0);
Spirv.OffsetToMainName = (uint32)(&Ptr[2] - PtrStart);
}
Names.Add(TargetId, Name);
}
break;
case spv::OpDecorate:
case SpvOpName:
{
uint32 TargetId = Ptr[1];
spv::Decoration Decoration = (spv::Decoration)Ptr[2];
uint32 TargetId = Iter.Operand(1);
FString Name = ANSI_TO_TCHAR(Iter.OperandAsString(2));
Names.Add(TargetId, MoveTemp(Name));
}
break;
case SpvOpDecorate:
{
uint32 TargetId = Iter.Operand(1);
SpvDecoration Decoration = Iter.OperandAs<SpvDecoration>(2);
switch (Decoration)
{
case spv::DecorationDescriptorSet:
case SpvDecorationDescriptorSet:
{
uint32 Value = Ptr[3];
uint32 WordValueIndex = (uint32)(&Ptr[3] - PtrStart);
uint32 Value = Iter.Operand(3);
uint32 WordValueIndex = Spirv.GetWordOffset(Iter, 3);
FDecorations& UBDecoration = Decorations.FindOrAdd(TargetId);
UBDecoration.DescriptorSet = Value;
UBDecoration.WordDescriptorSet = WordValueIndex;
break;
}
case spv::DecorationBinding:
case SpvDecorationBinding:
{
uint32 Value = Ptr[3];
uint32 WordValueIndex = (uint32)(&Ptr[3] - PtrStart);
uint32 Value = Iter.Operand(3);
uint32 WordValueIndex = Spirv.GetWordOffset(Iter, 3);
FDecorations& UBDecoration = Decorations.FindOrAdd(TargetId);
UBDecoration.BindingIndex = Value;
UBDecoration.WordBindingIndex = WordValueIndex;
@@ -265,37 +220,36 @@ static void ComputeMovableWordIndices(FVulkanSpirv& Spirv)
}
}
break;
case spv::OpTypePointer:
case SpvOpTypePointer:
{
uint32 Result = Ptr[1];
spv::StorageClass Storage = (spv::StorageClass)Ptr[2];
if (Storage == spv::StorageClassUniform || Storage == spv::StorageClassUniformConstant)
uint32 Result = Iter.Operand(1);
SpvStorageClass Storage = Iter.OperandAs<SpvStorageClass>(2);
if (Storage == SpvStorageClassUniform || Storage == SpvStorageClassUniformConstant)
{
uint32 Type = Ptr[3];
uint32 Type = Iter.Operand(3);
TypePointerUniforms.Add(Result, Type);
}
}
break;
case spv::OpVariable:
case SpvOpVariable:
{
uint32 Type = Ptr[1];
uint32 Id = Ptr[2];
spv::StorageClass Storage = (spv::StorageClass)Ptr[3];
if (Storage == spv::StorageClassUniform || Storage == spv::StorageClassUniformConstant)
uint32 Type = Iter.Operand(1);
uint32 Id = Iter.Operand(2);
SpvStorageClass Storage = Iter.OperandAs<SpvStorageClass>(3);
if (Storage == SpvStorageClassUniform || Storage == SpvStorageClassUniformConstant)
{
VariableUniformTypes.Add(Id, Type);
}
}
break;
case SpvOpFunction:
bDone = true;
break;
default:
break;
}
Ptr += WordCount;
}
check(bFoundEntry);
// Go through all found uniform variables and make sure we found the right info
for (const auto& Pair : VariableUniformTypes)
{
@@ -335,25 +289,6 @@ static void ComputeMovableWordIndices(FVulkanSpirv& Spirv)
}
}
static void PatchSpirvEntryPoint(FVulkanSpirv& OutSpirv, uint32 OffsetToName)
{
char* EntryPointName = (char*)(OutSpirv.Data.GetData() + OffsetToName);
check(!FCStringAnsi::Strcmp(EntryPointName, "main_00000000_00000000"));
FCStringAnsi::Sprintf(EntryPointName, "main_%0.8x_%0.8x", OutSpirv.Data.Num() * sizeof(uint32), OutSpirv.CRC);
};
bool PatchSpirvReflectionEntriesAndEntryPoint(FVulkanSpirv& OutSpirv)
{
// Re-compute movable word indices and update CRC code
ComputeMovableWordIndices(OutSpirv);
OutSpirv.CRC = FCrc::MemCrc32(OutSpirv.Data.GetData(), OutSpirv.Data.Num() * sizeof(uint32));
// Patch the entry point name
PatchSpirvEntryPoint(OutSpirv, OutSpirv.OffsetToMainName);
PatchSpirvEntryPoint(OutSpirv, OutSpirv.OffsetToEntryPoint);
return true;
}
bool GenerateSpirv(const ANSICHAR* Source, FCompilerInfo& CompilerInfo, FString& OutErrors, const FString& DumpDebugInfoPath, FVulkanSpirv& OutSpirv)
{
glslang::TProgram* Program = new glslang::TProgram;
@@ -444,7 +379,8 @@ bool GenerateSpirv(const ANSICHAR* Source, FCompilerInfo& CompilerInfo, FString&
OutSpirv.ReflectionInfo.Add(Entry);
}
PatchSpirvReflectionEntriesAndEntryPoint(OutSpirv);
PatchSpirvReflectionEntries(OutSpirv);
OutSpirv.EntryPointName = PatchSpirvEntryPointWithCRC(OutSpirv, OutSpirv.CRC);
// Copy back to original spirv data as it is used for dumping information
FMemory::Memcpy(&Spirv[0], OutSpirv.Data.GetData(), SizeInWords * sizeof(uint32));

View File

@@ -9,21 +9,6 @@
#include "hlslcc.h"
#include "SpirvReflectCommon.h"
#if PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
THIRD_PARTY_INCLUDES_START
#include "SPIRV/GlslangToSpv.h"
#include "SPIRV/doc.h"
#include "SPIRV/disassemble.h"
THIRD_PARTY_INCLUDES_END
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
// For excpt.h
#include <D3Dcompiler.h>
#include "Windows/HideWindowsPlatformTypes.h"
#endif
#endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX
#if PLATFORM_MAC
// Horrible hack as we need the enum available but the Vulkan headers do not compile on Mac
enum VkDescriptorType {
@@ -1563,7 +1548,7 @@ static void BuildShaderOutput(
{
if (IsVulkanMobilePlatform((EShaderPlatform)ShaderInput.Target.Platform))
{
CompileOfflineMali(ShaderInput, ShaderOutput, (const ANSICHAR*)Spirv.GetByteData(), Spirv.GetByteSize(), true, (const ANSICHAR*)(Spirv.GetByteData() + Spirv.OffsetToMainName));
CompileOfflineMali(ShaderInput, ShaderOutput, (const ANSICHAR*)Spirv.GetByteData(), Spirv.GetByteSize(), true, Spirv.EntryPointName);
}
}
@@ -1919,15 +1904,17 @@ static void GatherSpirvReflectionBindings(
}
}
static void BuildShaderOutputFromSpirv(
FVulkanSpirv& Spirv,
const FShaderCompilerInput& Input,
FShaderCompilerOutput& Output,
FVulkanBindingTable& BindingTable,
const FString& EntryPointName,
bool bHasRealUBs,
bool bDebugDump,
bool bIsRayTracingShader)
static bool BuildShaderOutputFromSpirv(
CrossCompiler::FShaderConductorContext& CompilerContext,
FVulkanSpirv& Spirv,
const FShaderCompilerInput& Input,
FShaderCompilerOutput& Output,
FVulkanBindingTable& BindingTable,
const FString& EntryPointName,
bool bHasRealUBs,
bool bStripReflect,
bool bIsRayTracingShader,
bool bDebugDump)
{
FShaderParameterMap& ParameterMap = Output.ParameterMap;
@@ -2299,7 +2286,20 @@ static void BuildShaderOutputFromSpirv(
// Overwrite updated SPIRV code
Spirv.Data = TArray<uint32>(Reflection.GetCode(), Reflection.GetCodeSize() / 4);
PatchSpirvReflectionEntriesAndEntryPoint(Spirv);
// We have to strip out most debug instructions (except OpName) for Vulkan mobile
if (bStripReflect)
{
const char* OptArgs[] = { "--strip-reflect" };
if (!CompilerContext.OptimizeSpirv(Spirv.Data, OptArgs, UE_ARRAY_COUNT(OptArgs)))
{
UE_LOG(LogVulkanShaderCompiler, Error, TEXT("Failed to strip debug instructions from SPIR-V module"));
return false;
}
}
PatchSpirvReflectionEntries(Spirv);
Spirv.EntryPointName = PatchSpirvEntryPointWithCRC(Spirv, Spirv.CRC);
BuildShaderOutput(
Output,
@@ -2321,6 +2321,8 @@ static void BuildShaderOutputFromSpirv(
DumpDebugShaderBinary(Input, Spirv.GetByteData(), Spirv.GetByteSize(), TEXT("spv"));
DumpDebugShaderDisassembledSpirv(Input, Spirv.GetByteData(), Spirv.GetByteSize(), TEXT("spvasm"));
}
return true;
}
static bool CompileWithShaderConductor(
@@ -2330,7 +2332,8 @@ static bool CompileWithShaderConductor(
EHlslCompileTarget HlslCompilerTarget,
FShaderCompilerOutput& Output,
FVulkanBindingTable& BindingTable,
bool bHasRealUBs)
bool bHasRealUBs,
bool bStripReflect)
{
const FShaderCompilerInput& Input = CompilerInfo.Input;
@@ -2384,15 +2387,13 @@ static bool CompileWithShaderConductor(
}
// Build shader output and binding table
BuildShaderOutputFromSpirv(Spirv, Input, Output, BindingTable, EntryPointName, bHasRealUBs, bDebugDump, bIsRayTracingShader);
Output.bSucceeded = BuildShaderOutputFromSpirv(CompilerContext, Spirv, Input, Output, BindingTable, EntryPointName, bHasRealUBs, bStripReflect, bIsRayTracingShader, bDebugDump);
if (Input.Environment.CompilerFlags.Contains(CFLAG_KeepDebugInfo))
{
Output.ShaderCode.AddOptionalData(FShaderCodeName::Key, TCHAR_TO_UTF8(*Input.GenerateShaderName()));
}
Output.bSucceeded = true;
if (bDebugDump)
{
DumpDebugShaderBinary(Input, Spirv.GetByteData(), Spirv.GetByteSize(), TEXT("spv"));
@@ -2414,6 +2415,7 @@ void DoCompileVulkanShader(const FShaderCompilerInput& Input, FShaderCompilerOut
const bool bHasRealUBs = !Input.Environment.CompilerFlags.Contains(CFLAG_UseEmulatedUB);
const bool bIsSM5 = (Version == EVulkanShaderVersion::SM5);
const bool bIsMobile = (Version == EVulkanShaderVersion::ES3_1 || Version == EVulkanShaderVersion::ES3_1_ANDROID);
const bool bStripReflect = (IsVulkanMobilePlatform(ShaderPlatform) || IsVulkanMobileSM5Platform(ShaderPlatform));
const bool bForceDXC = Input.Environment.CompilerFlags.Contains(CFLAG_ForceDXC);
const EHlslShaderFrequency FrequencyTable[] =
@@ -2564,7 +2566,7 @@ void DoCompileVulkanShader(const FShaderCompilerInput& Input, FShaderCompilerOut
if (bForceDXC)
{
// Cross-compile shader via ShaderConductor (DXC, SPIRV-Tools, SPIRV-Cross)
bSuccess = CompileWithShaderConductor(PreprocessedShaderSource, EntryPointName, CompilerInfo, HlslCompilerTarget, Output, BindingTable, bHasRealUBs);
bSuccess = CompileWithShaderConductor(PreprocessedShaderSource, EntryPointName, CompilerInfo, HlslCompilerTarget, Output, BindingTable, bHasRealUBs, bStripReflect);
}
else
#endif // PLATFORM_MAC || PLATFORM_WINDOWS || PLATFORM_LINUX

View File

@@ -52,10 +52,10 @@ struct FVulkanSpirv : FSpirv
// Index into the Spirv Word containing the binding index decoration
uint32 WordBindingIndex = UINT32_MAX;
};
TArray<FEntry> ReflectionInfo;
uint32 OffsetToMainName = 0;
uint32 OffsetToEntryPoint = 0;
uint32 CRC = 0;
const ANSICHAR* EntryPointName = nullptr;
int32 FindBinding(const FString& Name, bool bOuter = false) const
{
@@ -135,7 +135,7 @@ struct FVulkanSpirv : FSpirv
};
// Updates all reflection entries in the specified SPIR-V module.
extern bool PatchSpirvReflectionEntriesAndEntryPoint(FVulkanSpirv& OutSpirv);
extern void PatchSpirvReflectionEntries(FVulkanSpirv& OutSpirv);
// Generates SPIR-V out of the specified GLSL source code.
extern bool GenerateSpirv(const ANSICHAR* Source, FCompilerInfo& CompilerInfo, FString& OutErrors, const FString& DumpDebugInfoPath, FVulkanSpirv& OutSpirv);