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

749 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "D3D12RHI.h"
#include "RHIShaderBindingLayout.h"
#include <functional>
struct FD3DShaderCompileData
{
FD3DShaderCompileData()
: UsedUniformBufferSlots(false, 32)
{
}
TArray<FShaderCodeVendorExtension> VendorExtensions;
TArray<FString> ShaderInputs;
TArray<FString> UniformBufferNames;
TBitArray<> UsedUniformBufferSlots;
bool bBindlessEnabled = false;
bool bGlobalUniformBufferUsed = false;
bool bDiagnosticBufferUsed = false;
uint32 NumInstructions = 0;
uint32 NumSamplers = 0;
uint32 NumSRVs = 0;
uint32 NumCBs = 0;
uint32 NumUAVs = 0;
uint32 MaxSamplers = 0;
uint32 MaxSRVs = 0;
uint32 MaxCBs = 0;
uint32 MaxUAVs = 0;
};
template <typename D3D1x_SHADER_INPUT_BIND_DESC>
EShaderCodeResourceBindingType D3DBindDescToShaderCodeResourceBinding(const D3D1x_SHADER_INPUT_BIND_DESC& Binding)
{
switch (Binding.Type)
{
case D3D_SIT_SAMPLER:
return EShaderCodeResourceBindingType::SamplerState;
case D3D_SIT_TBUFFER:
case D3D_SIT_CBUFFER:
return EShaderCodeResourceBindingType::Buffer;
case D3D_SIT_TEXTURE:
switch (Binding.Dimension)
{
case D3D_SRV_DIMENSION_BUFFER: return EShaderCodeResourceBindingType::Buffer;
case D3D_SRV_DIMENSION_TEXTURE2D: return EShaderCodeResourceBindingType::Texture2D;
case D3D_SRV_DIMENSION_TEXTURE2DARRAY: return EShaderCodeResourceBindingType::Texture2DArray;
case D3D_SRV_DIMENSION_TEXTURE2DMS: return EShaderCodeResourceBindingType::Texture2DMS;
case D3D_SRV_DIMENSION_TEXTURE3D: return EShaderCodeResourceBindingType::Texture3D;
case D3D_SRV_DIMENSION_TEXTURECUBE: return EShaderCodeResourceBindingType::TextureCube;
default:
return EShaderCodeResourceBindingType::Invalid;
}
case D3D_SIT_UAV_RWTYPED:
switch (Binding.Dimension)
{
case D3D_SRV_DIMENSION_BUFFER: return EShaderCodeResourceBindingType::RWBuffer;
case D3D_SRV_DIMENSION_TEXTURE2D: return EShaderCodeResourceBindingType::RWTexture2D;
case D3D_SRV_DIMENSION_TEXTURE2DARRAY: return EShaderCodeResourceBindingType::RWTexture2DArray;
case D3D_SRV_DIMENSION_TEXTURE3D: return EShaderCodeResourceBindingType::RWTexture3D;
case D3D_SRV_DIMENSION_TEXTURECUBE: return EShaderCodeResourceBindingType::RWTextureCube;
default:
return EShaderCodeResourceBindingType::Invalid;
}
case D3D_SIT_STRUCTURED: return EShaderCodeResourceBindingType::StructuredBuffer;
case D3D_SIT_UAV_RWSTRUCTURED: return EShaderCodeResourceBindingType::RWStructuredBuffer;
case D3D_SIT_BYTEADDRESS: return EShaderCodeResourceBindingType::ByteAddressBuffer;
case D3D_SIT_UAV_RWBYTEADDRESS: return EShaderCodeResourceBindingType::RWByteAddressBuffer;
default:
return EShaderCodeResourceBindingType::Invalid;
}
}
template <typename ID3D1xShaderReflection, typename D3D1x_SHADER_DESC, typename D3D1x_SHADER_INPUT_BIND_DESC,
typename ID3D1xShaderReflectionConstantBuffer, typename D3D1x_SHADER_BUFFER_DESC,
typename ID3D1xShaderReflectionVariable, typename D3D1x_SHADER_VARIABLE_DESC>
inline void ExtractParameterMapFromD3DShader(
const FShaderCompilerInput& Input,
const FShaderParameterParser& ShaderParameterParser,
uint32 BindingSpace,
ID3D1xShaderReflection* Reflector, const D3D1x_SHADER_DESC& ShaderDesc,
FD3DShaderCompileData& CompileData,
FShaderCompilerOutput& Output
)
{
// Add parameters for shader resources (constant buffers, textures, samplers, etc. */
for (uint32 ResourceIndex = 0; ResourceIndex < ShaderDesc.BoundResources; ResourceIndex++)
{
D3D1x_SHADER_INPUT_BIND_DESC BindDesc;
Reflector->GetResourceBindingDesc(ResourceIndex, &BindDesc);
if (!IsCompatibleBinding(BindDesc, BindingSpace))
{
continue;
}
if (BindDesc.Type == D3D_SIT_CBUFFER || BindDesc.Type == D3D_SIT_TBUFFER)
{
const uint32 CBIndex = BindDesc.BindPoint;
ID3D1xShaderReflectionConstantBuffer* ConstantBuffer = Reflector->GetConstantBufferByName(BindDesc.Name);
D3D1x_SHADER_BUFFER_DESC CBDesc;
ConstantBuffer->GetDesc(&CBDesc);
const FString ConstantBufferName(CBDesc.Name);
const bool bGlobalCB = (ConstantBufferName == TEXT("$Globals"));
const bool bRootConstantsCB = (ConstantBufferName == TEXT("UERootConstants"));
const bool bIsRootCB = (ConstantBufferName == FShaderParametersMetadata::kRootUniformBufferBindingName);
if (bGlobalCB)
{
if (Input.ShouldUseStableConstantBuffer())
{
// Each member found in the global constant buffer means it was not in RootParametersStructure or
// it would have been moved by ShaderParameterParser.ParseAndModify().
for (uint32 ConstantIndex = 0; ConstantIndex < CBDesc.Variables; ConstantIndex++)
{
ID3D1xShaderReflectionVariable* Variable = ConstantBuffer->GetVariableByIndex(ConstantIndex);
D3D1x_SHADER_VARIABLE_DESC VariableDesc;
Variable->GetDesc(&VariableDesc);
if (VariableDesc.uFlags & D3D_SVF_USED)
{
AddUnboundShaderParameterError(
Input,
ShaderParameterParser,
ANSI_TO_TCHAR(VariableDesc.Name),
Output);
}
}
}
else
{
// Track all of the variables in this constant buffer.
for (uint32 ConstantIndex = 0; ConstantIndex < CBDesc.Variables; ConstantIndex++)
{
ID3D1xShaderReflectionVariable* Variable = ConstantBuffer->GetVariableByIndex(ConstantIndex);
D3D1x_SHADER_VARIABLE_DESC VariableDesc;
Variable->GetDesc(&VariableDesc);
if (VariableDesc.uFlags & D3D_SVF_USED)
{
CompileData.bGlobalUniformBufferUsed = true;
HandleReflectedGlobalConstantBufferMember(
FString(VariableDesc.Name),
CBIndex,
VariableDesc.StartOffset,
VariableDesc.Size,
Output
);
CompileData.UsedUniformBufferSlots[CBIndex] = true;
}
}
}
}
else if (bRootConstantsCB)
{
// For the UERootConstants root constant CB, we want to fully skip adding it to the parameter map, or
// updating the used slots or num CBs (all those assume space0).
}
else if (bIsRootCB && Input.ShouldUseStableConstantBuffer())
{
if (CBIndex == FShaderParametersMetadata::kRootCBufferBindingIndex)
{
int32 ConstantBufferSize = 0;
// Track all of the variables in this constant buffer.
for (uint32 ConstantIndex = 0; ConstantIndex < CBDesc.Variables; ConstantIndex++)
{
ID3D1xShaderReflectionVariable* Variable = ConstantBuffer->GetVariableByIndex(ConstantIndex);
D3D1x_SHADER_VARIABLE_DESC VariableDesc;
Variable->GetDesc(&VariableDesc);
if (VariableDesc.uFlags & D3D_SVF_USED)
{
HandleReflectedRootConstantBufferMember(
Input,
ShaderParameterParser,
FString(VariableDesc.Name),
VariableDesc.StartOffset,
VariableDesc.Size,
Output
);
ConstantBufferSize = FMath::Max<int32>(ConstantBufferSize, VariableDesc.StartOffset + VariableDesc.Size);
}
}
if (ConstantBufferSize > 0)
{
HandleReflectedRootConstantBuffer(ConstantBufferSize, Output);
CompileData.bGlobalUniformBufferUsed = true;
CompileData.UsedUniformBufferSlots[CBIndex] = true;
}
}
else
{
FString ErrorMessage = FString::Printf(
TEXT("Error: %s is expected to always be in the API slot %d, but is actually in slot %d."),
FShaderParametersMetadata::kRootUniformBufferBindingName,
FShaderParametersMetadata::kRootCBufferBindingIndex,
CBIndex);
Output.Errors.Add(FShaderCompilerError(*ErrorMessage));
Output.bSucceeded = false;
}
}
else
{
// Track just the constant buffer itself.
AddShaderValidationUBSize(CBIndex, CBDesc.Size, Output);
HandleReflectedUniformBuffer(ConstantBufferName, CBIndex, Output);
CompileData.UsedUniformBufferSlots[CBIndex] = true;
const EUniformBufferMemberReflectionReason Reason = ShouldReflectUniformBufferMembers(Input, ConstantBufferName);
if (Reason != EUniformBufferMemberReflectionReason::None)
{
for (uint32 ConstantIndex = 0; ConstantIndex < CBDesc.Variables; ConstantIndex++)
{
ID3D1xShaderReflectionVariable* Variable = ConstantBuffer->GetVariableByIndex(ConstantIndex);
D3D1x_SHADER_VARIABLE_DESC VariableDesc;
Variable->GetDesc(&VariableDesc);
if (VariableDesc.uFlags & D3D_SVF_USED)
{
const FString MemberName(VariableDesc.Name);
HandleReflectedUniformBufferConstantBufferMember(
Reason,
ConstantBufferName,
CBIndex,
MemberName,
VariableDesc.StartOffset,
VariableDesc.Size,
Output
);
}
}
}
}
if (CompileData.UniformBufferNames.Num() <= (int32)CBIndex)
{
CompileData.UniformBufferNames.AddDefaulted(CBIndex - CompileData.UniformBufferNames.Num() + 1);
}
CompileData.UniformBufferNames[CBIndex] = UE::ShaderCompilerCommon::RemoveConstantBufferPrefix(ConstantBufferName);
CompileData.NumCBs = FMath::Max(CompileData.NumCBs, BindDesc.BindPoint + BindDesc.BindCount);
}
else if (BindDesc.Type == D3D_SIT_TEXTURE || BindDesc.Type == D3D_SIT_SAMPLER)
{
check(BindDesc.BindCount == 1);
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/master/ags_lib/hlsl/ags_shader_intrinsics_dx11.hlsl
const bool bIsAMDTexExtension = (FCStringAnsi::Strcmp(BindDesc.Name, "AmdDxExtShaderIntrinsicsResource") == 0);
const bool bIsAMDSmpExtension = (FCStringAnsi::Strcmp(BindDesc.Name, "AmdDxExtShaderIntrinsicsSamplerState") == 0);
const bool bIsVendorParameter = bIsAMDTexExtension || bIsAMDSmpExtension;
const uint32 BindCount = 1;
const EShaderParameterType ParameterType = (BindDesc.Type == D3D_SIT_SAMPLER) ? EShaderParameterType::Sampler : EShaderParameterType::SRV;
if (bIsVendorParameter)
{
CompileData.VendorExtensions.Emplace(EGpuVendorId::Amd, 0, BindDesc.BindPoint, BindCount, ParameterType);
}
else if (ParameterType == EShaderParameterType::Sampler)
{
HandleReflectedShaderSampler(FString(BindDesc.Name), BindDesc.BindPoint, Output);
CompileData.NumSamplers = FMath::Max(CompileData.NumSamplers, BindDesc.BindPoint + BindCount);
}
else
{
EShaderCodeResourceBindingType ResourceBindingType = D3DBindDescToShaderCodeResourceBinding(BindDesc);
AddShaderValidationSRVType(BindDesc.BindPoint, ResourceBindingType, Output);
HandleReflectedShaderResource(FString(BindDesc.Name), BindDesc.BindPoint, Output);
CompileData.NumSRVs = FMath::Max(CompileData.NumSRVs, BindDesc.BindPoint + BindCount);
}
}
else if (BindDesc.Type == D3D_SIT_UAV_RWTYPED || BindDesc.Type == D3D_SIT_UAV_RWSTRUCTURED ||
BindDesc.Type == D3D_SIT_UAV_RWBYTEADDRESS || BindDesc.Type == D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER ||
BindDesc.Type == D3D_SIT_UAV_APPEND_STRUCTURED)
{
check(BindDesc.BindCount == 1);
// https://developer.nvidia.com/unlocking-gpu-intrinsics-hlsl
const bool bIsNVExtension = (FCStringAnsi::Strcmp(BindDesc.Name, "g_NvidiaExt") == 0);
// https://github.com/intel/intel-graphics-compiler/blob/master/inc/IntelExtensions.hlsl
const bool bIsIntelExtension = (FCStringAnsi::Strcmp(BindDesc.Name, "g_IntelExt") == 0);
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/master/ags_lib/hlsl/ags_shader_intrinsics_dx11.hlsl
const bool bIsAMDExtensionDX11 = (FCStringAnsi::Strcmp(BindDesc.Name, "AmdDxExtShaderIntrinsicsUAV") == 0);
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/master/ags_lib/hlsl/ags_shader_intrinsics_dx12.hlsl
const bool bIsAMDExtensionDX12 = (FCStringAnsi::Strcmp(BindDesc.Name, "AmdExtD3DShaderIntrinsicsUAV") == 0);
const bool bIsVendorParameter = bIsNVExtension || bIsIntelExtension || bIsAMDExtensionDX11 || bIsAMDExtensionDX12;
// See D3DCommon.ush
const bool bIsDiagnosticBufferParameter = (FCStringAnsi::Strcmp(BindDesc.Name, "UEDiagnosticBuffer") == 0);
const uint32 BindCount = 1;
if (bIsVendorParameter)
{
const EGpuVendorId VendorId =
bIsNVExtension ? EGpuVendorId::Nvidia :
(bIsAMDExtensionDX11 || bIsAMDExtensionDX12) ? EGpuVendorId::Amd :
bIsIntelExtension ? EGpuVendorId::Intel :
EGpuVendorId::Unknown;
CompileData.VendorExtensions.Emplace(VendorId, 0, BindDesc.BindPoint, BindCount, EShaderParameterType::UAV);
}
else if (bIsDiagnosticBufferParameter)
{
CompileData.bDiagnosticBufferUsed = true;
}
else
{
EShaderCodeResourceBindingType ResourceBindingType = D3DBindDescToShaderCodeResourceBinding(BindDesc);
AddShaderValidationUAVType(BindDesc.BindPoint, ResourceBindingType, Output);
HandleReflectedShaderUAV(FString(BindDesc.Name), BindDesc.BindPoint, Output);
CompileData.NumUAVs = FMath::Max(CompileData.NumUAVs, BindDesc.BindPoint + BindCount);
}
}
else if (BindDesc.Type == D3D_SIT_STRUCTURED || BindDesc.Type == D3D_SIT_BYTEADDRESS)
{
check(BindDesc.BindCount == 1);
FString BindDescName(BindDesc.Name);
EShaderCodeResourceBindingType ResourceBindingType = D3DBindDescToShaderCodeResourceBinding(BindDesc);
AddShaderValidationSRVType(BindDesc.BindPoint, ResourceBindingType, Output);
HandleReflectedShaderResource(BindDescName, BindDesc.BindPoint, Output);
// https://learn.microsoft.com/en-us/windows/win32/api/d3d12shader/ns-d3d12shader-d3d12_shader_input_bind_desc
// If the shader resource is a structured buffer, the field contains the stride of the type in bytes
if ( BindDesc.Type == D3D_SIT_STRUCTURED)
{
UpdateStructuredBufferStride(Input, BindDescName, BindDesc.BindPoint, BindDesc.NumSamples, Output);
}
CompileData.NumSRVs = FMath::Max(CompileData.NumSRVs, BindDesc.BindPoint + 1);
}
else if (BindDesc.Type == D3D_SIT_RTACCELERATIONSTRUCTURE)
{
// Acceleration structure resources are treated as SRVs.
check(BindDesc.BindCount == 1);
EShaderCodeResourceBindingType ResourceBindingType = D3DBindDescToShaderCodeResourceBinding(BindDesc);
AddShaderValidationSRVType(BindDesc.BindPoint, ResourceBindingType, Output);
HandleReflectedShaderResource(FString(BindDesc.Name), BindDesc.BindPoint, Output);
CompileData.NumSRVs = FMath::Max(CompileData.NumSRVs, BindDesc.BindPoint + 1);
}
}
CompileData.NumInstructions = ShaderDesc.InstructionCount;
}
// Validate that we are not going over to maximum amount of resource bindings support by the default root signature on DX12
// Currently limited for hard-coded root signature setup (see: FD3D12Adapter::StaticGraphicsRootSignature)
// In theory this limitation is only required for DX12, but we don't want a shader to compile on DX11 while not working on DX12.
// (DX11 has an API limit on 128 SRVs, 16 Samplers, 8 UAVs and 14 CBs but if you go over these values then the shader won't compile)
inline bool ValidateResourceCounts(const FD3DShaderCompileData& CompileData, TArray<FString>& OutFilteredErrors)
{
const bool bTooManySRVs = !CompileData.bBindlessEnabled && CompileData.NumSRVs > CompileData.MaxSRVs;
const bool bTooManyUAVs = !CompileData.bBindlessEnabled && CompileData.NumUAVs > CompileData.MaxUAVs;
const bool bTooManySamplers = !CompileData.bBindlessEnabled && CompileData.NumSamplers > CompileData.MaxSamplers;
const bool bTooManyCBs = CompileData.NumCBs > CompileData.MaxCBs;
if (bTooManySRVs || bTooManySamplers || bTooManyUAVs || bTooManyCBs)
{
if (bTooManySRVs)
{
OutFilteredErrors.Add(FString::Printf(TEXT("Shader is using too many SRVs: %d (only %d supported)"), CompileData.NumSRVs, CompileData.MaxSRVs));
}
if (bTooManySamplers)
{
OutFilteredErrors.Add(FString::Printf(TEXT("Shader is using too many Samplers: %d (only %d supported)"), CompileData.NumSamplers, CompileData.MaxSamplers));
}
if (bTooManyUAVs)
{
OutFilteredErrors.Add(FString::Printf(TEXT("Shader is using too many UAVs: %d (only %d supported)"), CompileData.NumUAVs, CompileData.MaxUAVs));
}
if (bTooManyCBs)
{
OutFilteredErrors.Add(FString::Printf(TEXT("Shader is using too many Constant Buffers: %d (only %d supported)"), CompileData.NumCBs, CompileData.MaxCBs));
}
return false;
}
return true;
}
inline FShaderCodePackedResourceCounts InitPackedResourceCounts(const FD3DShaderCompileData& CompileData)
{
FShaderCodePackedResourceCounts PackedResourceCounts{};
if (CompileData.bGlobalUniformBufferUsed)
{
EnumAddFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::GlobalUniformBuffer);
}
if (CompileData.bBindlessEnabled)
{
EnumAddFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::BindlessResources);
EnumAddFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::BindlessSamplers);
}
if (CompileData.bDiagnosticBufferUsed)
{
EnumAddFlags(PackedResourceCounts.UsageFlags, EShaderResourceUsageFlags::DiagnosticBuffer);
}
PackedResourceCounts.NumSamplers = static_cast<uint8>(CompileData.NumSamplers);
PackedResourceCounts.NumSRVs = static_cast<uint8>(CompileData.NumSRVs);
PackedResourceCounts.NumCBs = static_cast<uint8>(CompileData.NumCBs);
PackedResourceCounts.NumUAVs = static_cast<uint8>(CompileData.NumUAVs);
return PackedResourceCounts;
}
template <typename TBlob>
inline void GenerateFinalOutput(
TRefCountPtr<TBlob>& CompressedData,
const FShaderCompilerInput& Input,
ED3DShaderModel ShaderModel,
bool bProcessingSecondTime,
FD3DShaderCompileData& CompileData,
const FShaderCodePackedResourceCounts& PackedResourceCounts,
FShaderCompilerOutput& Output,
TFunction<void(FMemoryWriter&)> PostSRTWriterCallback,
TFunction<void(FShaderCode&)> AddOptionalDataCallback)
{
const uint32 NumBindlessResources = CompileData.bBindlessEnabled ? Output.ParameterMap.CountParametersOfType(EShaderParameterType::BindlessSRV) : 0;
const uint32 NumBindlessSamplers = CompileData.bBindlessEnabled ? Output.ParameterMap.CountParametersOfType(EShaderParameterType::BindlessSampler) : 0;
// Build the SRT for this shader.
FShaderResourceTable SRT;
TArray<uint8> UniformBufferNameBytes;
{
// Build the generic SRT for this shader.
FShaderCompilerResourceTable GenericSRT;
BuildResourceTableMapping(Input.Environment.ResourceTableMap, Input.Environment.UniformBufferMap, CompileData.UsedUniformBufferSlots, Output.ParameterMap, GenericSRT);
// Ray generation shaders rely on a different binding model that aren't compatible with global uniform buffers.
if (Input.Target.Frequency != SF_RayGen)
{
CullGlobalUniformBuffers(Input.Environment.UniformBufferMap, Output.ParameterMap);
}
if (CompileData.UniformBufferNames.Num() < GenericSRT.ResourceTableLayoutHashes.Num())
{
CompileData.UniformBufferNames.AddDefaulted(GenericSRT.ResourceTableLayoutHashes.Num() - CompileData.UniformBufferNames.Num());
}
for (int32 Index = 0; Index < GenericSRT.ResourceTableLayoutHashes.Num(); ++Index)
{
if (GenericSRT.ResourceTableLayoutHashes[Index] != 0 && CompileData.UniformBufferNames[Index].Len() == 0)
{
for (const auto& KeyValue : Input.Environment.UniformBufferMap)
{
const FUniformBufferEntry& UniformBufferEntry = KeyValue.Value;
if (UniformBufferEntry.LayoutHash == GenericSRT.ResourceTableLayoutHashes[Index])
{
CompileData.UniformBufferNames[Index] = KeyValue.Key;
break;
}
}
}
}
FMemoryWriter UniformBufferNameWriter(UniformBufferNameBytes);
UniformBufferNameWriter << CompileData.UniformBufferNames;
UE::ShaderCompilerCommon::BuildShaderResourceTable(GenericSRT, SRT);
}
if (Input.Environment.CompilerFlags.Contains(CFLAG_ForceRemoveUnusedInterpolators) && Input.Target.Frequency == SF_Pixel && Input.bCompilingForShaderPipeline && bProcessingSecondTime)
{
Output.bSupportsQueryingUsedAttributes = true;
Output.UsedAttributes = CompileData.ShaderInputs;
}
// Generate the final Output
FMemoryWriter Ar(Output.ShaderCode.GetWriteAccess(), true);
Ar << SRT;
PostSRTWriterCallback(Ar);
Ar.Serialize(CompressedData->GetBufferPointer(), CompressedData->GetBufferSize());
// Append data that is generate from the shader code and assist the usage, mostly needed for DX12
{
Output.ShaderCode.AddOptionalData(PackedResourceCounts);
Output.ShaderCode.AddOptionalData(FShaderCodeUniformBuffers::Key, UniformBufferNameBytes.GetData(), UniformBufferNameBytes.Num());
AddOptionalDataCallback(Output.ShaderCode);
}
// Append the shader binding layout hash used for validation
{
uint32 ShaderBindingLayoutHash = Input.Environment.RHIShaderBindingLayout.GetHash();
TArray<uint8> WriterBytes;
FMemoryWriter Writer(WriterBytes);
Writer << ShaderBindingLayoutHash;
if (WriterBytes.Num() > 0)
{
Output.ShaderCode.AddOptionalData(FShaderCodeShaderResourceTableDataDesc::Key, WriterBytes.GetData(), WriterBytes.Num());
}
}
// Append information about optional hardware vendor extensions
if (CompileData.VendorExtensions.Num() > 0)
{
TArray<uint8> WriterBytes;
FMemoryWriter Writer(WriterBytes);
Writer << CompileData.VendorExtensions;
if (WriterBytes.Num() > 0)
{
Output.ShaderCode.AddOptionalData(FShaderCodeVendorExtension::Key, WriterBytes.GetData(), WriterBytes.Num());
}
}
if (Input.Environment.CompilerFlags.Contains(CFLAG_ExtraShaderData))
{
Output.ShaderCode.AddOptionalData(FShaderCodeName::Key, TCHAR_TO_UTF8(*Input.GenerateShaderName()));
}
Output.SerializeShaderCodeValidation();
Output.SerializeShaderDiagnosticData();
// Set the number of instructions.
Output.NumInstructions = CompileData.NumInstructions;
Output.NumTextureSamplers = PackedResourceCounts.NumSamplers;
// Pass the target through to the output.
Output.Target = Input.Target;
// SRV Limits
{
if (CompileData.bBindlessEnabled)
{
Output.AddStatistic<uint32>(TEXT("Bindless Resources"), NumBindlessResources);
Output.AddStatistic<uint32>(TEXT("Bindless Samplers"), NumBindlessSamplers);
}
else
{
Output.AddStatistic<uint32>(TEXT("Resources Used"), CompileData.NumSRVs);
Output.AddStatistic<uint32>(TEXT("Resource Limit"), CompileData.MaxSRVs);
Output.AddStatistic<uint32>(TEXT("Samplers Used"), CompileData.NumSamplers);
Output.AddStatistic<uint32>(TEXT("Sampler Limit"), CompileData.MaxSamplers);
}
}
}
enum class ShaderCompilerType
{
FXC,
DXC,
};
using ShaderCompileLambdaType = std::function<bool(
const FShaderCompilerInput& Input,
const FString& PreprocessedShaderSource,
const FString& EntryPointName,
const FShaderParameterParser& ShaderParameterParser,
const TCHAR* ShaderProfile,
const ED3DShaderModel ShaderModel,
const bool bProcessingSecondTime,
FShaderCompilerOutput& Output)>;
template<ShaderCompilerType LambdaPlatform, typename TShaderCompileLambdaType,
typename ID3D1xShaderReflection, typename D3D1x_SHADER_DESC, typename D3D1x_SIGNATURE_PARAMETER_DESC>
bool RemoveUnusedInterpolators(
const FShaderCompilerInput& Input,
const FString& PreprocessedShaderSource,
const FString& EntryPointName,
const FShaderParameterParser& ShaderParameterParser,
const TCHAR* ShaderProfile,
const ED3DShaderModel ShaderModel,
const bool bProcessingSecondTime,
FD3DShaderCompileData& CompileData,
TRefCountPtr<ID3D1xShaderReflection> Reflector,
TShaderCompileLambdaType ShaderCompileLambda,
FShaderCompilerOutput& Output,
bool& CompileResult)
{
if (Input.Target.Frequency == SF_Pixel)
{
if (Reflector)
{
// Read the constant table description.
D3D1x_SHADER_DESC ShaderDesc;
Reflector->GetDesc(&ShaderDesc);
bool bFoundUnused = false;
for (uint32 Index = 0; Index < ShaderDesc.InputParameters; ++Index)
{
// VC++ horrible hack: Runtime ESP checks get confused and fail for some reason calling Reflector->GetInputParameterDesc() (because it comes from another DLL?)
// so "guard it" using the middle of an array; it's been confirmed NO corruption is really happening.
D3D1x_SIGNATURE_PARAMETER_DESC ParamDescs[3];
D3D1x_SIGNATURE_PARAMETER_DESC& ParamDesc = ParamDescs[1];
Reflector->GetInputParameterDesc(Index, &ParamDesc);
if (ParamDesc.SystemValueType == D3D_NAME_UNDEFINED)
{
if (ParamDesc.ReadWriteMask != 0)
{
FString SemanticName = ANSI_TO_TCHAR(ParamDesc.SemanticName);
CompileData.ShaderInputs.AddUnique(SemanticName);
// Add the number (for the case of TEXCOORD)
FString SemanticIndexName = FString::Printf(TEXT("%s%d"), *SemanticName, ParamDesc.SemanticIndex);
CompileData.ShaderInputs.AddUnique(SemanticIndexName);
// Add _centroid
CompileData.ShaderInputs.AddUnique(SemanticName + TEXT("_centroid"));
CompileData.ShaderInputs.AddUnique(SemanticIndexName + TEXT("_centroid"));
}
else
{
bFoundUnused = true;
}
}
else
{
// if (ParamDesc.ReadWriteMask != 0)
{
// Keep system values
CompileData.ShaderInputs.AddUnique(FString(ANSI_TO_TCHAR(ParamDesc.SemanticName)));
}
}
}
if constexpr (LambdaPlatform == ShaderCompilerType::DXC)
{
//Seems like there is bug in DXC compiler which prevents SV_Coverage semantic
//from being detected as Input Parameter in the shader reflection data.
//Other system interpolators are fine...
//Can compile GizmoMaterial to repro the issue for example: -run=CookShadersCommandlet -targetPlatform=Windows -material=GizmoMaterial
//Always adding SV_Coverage as Used interpolator to mitigate this issue.
CompileData.ShaderInputs.AddUnique(TEXT("SV_Coverage"));
}
if (Input.Environment.CompilerFlags.Contains(CFLAG_ForceRemoveUnusedInterpolators) && Input.bCompilingForShaderPipeline && bFoundUnused && !bProcessingSecondTime)
{
// Rewrite the source removing the unused inputs so the bindings will match.
// We may need to do this more than once if unused inputs change after the removal. Ie. for complex shaders, what can happen is:
// pass1 detects that input A is not used, but input B and C are. Input A is removed, and we recompile (pass2). After the recompilation, we see that Input B is now also unused in pass2
// (it became simpler and the compiler could see through that).
// Since unused inputs are passed to the next stage, that will cause us to generate a vertex shader that does not output B, but our pixel shader will still be expecting B on input,
// as it was rewritten based on the pass1 results.
FShaderCompilerOutput OriginalOutput = Output;
const int kMaxReasonableAttempts = 64;
for (int32 Attempt = 0; Attempt < kMaxReasonableAttempts; ++Attempt)
{
TArray<FString> RemoveErrors;
FString ModifiedShaderSource = PreprocessedShaderSource;
FString ModifiedEntryPointName = Input.EntryPointName;
if (RemoveUnusedInputs(ModifiedShaderSource, CompileData.ShaderInputs, ModifiedEntryPointName, RemoveErrors))
{
Output = OriginalOutput;
CompileResult = ShaderCompileLambda(Input, ModifiedShaderSource, ModifiedEntryPointName, ShaderParameterParser, ShaderProfile, ShaderModel, true, Output);
if (!CompileResult)
{
// if we failed to compile the shader, propagate the error up
return true;
}
// check if the ShaderInputs changed - if not, we're done here
if (Output.UsedAttributes.Num() == CompileData.ShaderInputs.Num())
{
Output.ModifiedShaderSource = MoveTemp(ModifiedShaderSource);
Output.ModifiedEntryPointName = MoveTemp(ModifiedEntryPointName);
return true;
}
// second pass cannot use more attributes than previously
if (Output.UsedAttributes.Num() > CompileData.ShaderInputs.Num())
{
UE_LOG(LogD3DShaderCompiler, Warning, TEXT("Second pass had more used attributes (%d) than first pass (%d)"), Output.UsedAttributes.Num(), CompileData.ShaderInputs.Num());
FShaderCompilerError NewError;
NewError.StrippedErrorMessage = FString::Printf(TEXT("Second pass had more used attributes (%d) than first pass (%d)"), Output.UsedAttributes.Num(), CompileData.ShaderInputs.Num());
Output = OriginalOutput;
Output.Errors.Add(NewError);
break;
}
// if we're about to run out of attempts, report
if (Attempt >= kMaxReasonableAttempts - 1)
{
UE_LOG(LogD3DShaderCompiler, Warning, TEXT("Unable to determine unused inputs after %d attempts (last number of used attributes: %d, previous step:%d)!"),
Attempt + 1,
Output.UsedAttributes.Num(),
CompileData.ShaderInputs.Num());
FShaderCompilerError NewError;
NewError.StrippedErrorMessage = FString::Printf(TEXT("Unable to determine unused inputs after %d attempts (last number of used attributes: %d, previous step:%d)!"),
Attempt + 1,
Output.UsedAttributes.Num(),
CompileData.ShaderInputs.Num());
Output = OriginalOutput;
Output.Errors.Add(NewError);
break;
}
CompileData.ShaderInputs = Output.UsedAttributes;
// go around to remove newly identified unused inputs
}
else
{
UE_LOG(LogD3DShaderCompiler, Warning, TEXT("Failed to remove unused inputs from shader: %s"), *Input.GenerateShaderName());
for (const FString& ErrorMessage : RemoveErrors)
{
// Add error to shader output but also make sure the error shows up on build farm by emitting a log entry
UE_LOG(LogD3DShaderCompiler, Warning, TEXT("%s"), *ErrorMessage);
FShaderCompilerError NewError;
NewError.StrippedErrorMessage = ErrorMessage;
Output.Errors.Add(NewError);
}
break;
}
}
}
}
}
return false;
}