You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2445 lines
76 KiB
C++
2445 lines
76 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
Shader.cpp: Shader implementation.
|
|
=============================================================================*/
|
|
|
|
#include "Shader.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Stats/StatsMisc.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "VertexFactory.h"
|
|
#include "ProfilingDebugging/DiagnosticTable.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "Interfaces/ITargetPlatformManagerModule.h"
|
|
#include "ShaderCodeLibrary.h"
|
|
#include "ShaderCore.h"
|
|
#include "RenderUtils.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "UObject/RenderingObjectVersion.h"
|
|
#include "UObject/FortniteMainBranchObjectVersion.h"
|
|
#include "Misc/ScopeLock.h"
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
#include "Interfaces/IShaderFormat.h"
|
|
#endif
|
|
|
|
DEFINE_LOG_CATEGORY(LogShaders);
|
|
|
|
RENDERCORE_API bool UsePreExposure(EShaderPlatform Platform)
|
|
{
|
|
// Mobile platforms are excluded because they use a different pre-exposure logic in MobileBasePassPixelShader.usf
|
|
static const auto CVarUsePreExposure = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.UsePreExposure"));
|
|
return CVarUsePreExposure->GetValueOnAnyThread() != 0 && !IsMobilePlatform(Platform) && IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
|
|
static const FName ShaderCompressionFormat = NAME_Zlib;
|
|
|
|
static TAutoConsoleVariable<int32> CVarUsePipelines(
|
|
TEXT("r.ShaderPipelines"),
|
|
1,
|
|
TEXT("Enable using Shader pipelines."));
|
|
|
|
static TLinkedList<FShaderType*>* GShaderTypeList = nullptr;
|
|
static TLinkedList<FShaderPipelineType*>* GShaderPipelineList = nullptr;
|
|
static TMap<FName, FShaderType*>* GShaderNameToTypeMap = nullptr;
|
|
|
|
static FSHAHash ShaderSourceDefaultHash; //will only be read (never written) for the cooking case
|
|
|
|
/**
|
|
* Find the shader pipeline type with the given name.
|
|
* @return NULL if no type matched.
|
|
*/
|
|
inline const FShaderPipelineType* FindShaderPipelineType(FName TypeName)
|
|
{
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator ShaderPipelineTypeIt(FShaderPipelineType::GetTypeList()); ShaderPipelineTypeIt; ShaderPipelineTypeIt.Next())
|
|
{
|
|
if (ShaderPipelineTypeIt->GetFName() == TypeName)
|
|
{
|
|
return *ShaderPipelineTypeIt;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/**
|
|
* Serializes a reference to a shader pipeline type.
|
|
*/
|
|
FArchive& operator<<(FArchive& Ar, const FShaderPipelineType*& TypeRef)
|
|
{
|
|
if (Ar.IsSaving())
|
|
{
|
|
FName TypeName = TypeRef ? FName(TypeRef->Name) : NAME_None;
|
|
Ar << TypeName;
|
|
}
|
|
else if (Ar.IsLoading())
|
|
{
|
|
FName TypeName = NAME_None;
|
|
Ar << TypeName;
|
|
TypeRef = FindShaderPipelineType(TypeName);
|
|
}
|
|
return Ar;
|
|
}
|
|
|
|
|
|
void FShaderParameterMap::VerifyBindingsAreComplete(const TCHAR* ShaderTypeName, FShaderTarget Target, FVertexFactoryType* InVertexFactoryType) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
// Only people working on shaders (and therefore have LogShaders unsuppressed) will want to see these errors
|
|
if (UE_LOG_ACTIVE(LogShaders, Warning))
|
|
{
|
|
const TCHAR* VertexFactoryName = InVertexFactoryType ? InVertexFactoryType->GetName() : TEXT("?");
|
|
|
|
bool bBindingsComplete = true;
|
|
FString UnBoundParameters = TEXT("");
|
|
for (TMap<FString,FParameterAllocation>::TConstIterator ParameterIt(ParameterMap);ParameterIt;++ParameterIt)
|
|
{
|
|
const FString& ParamName = ParameterIt.Key();
|
|
const FParameterAllocation& ParamValue = ParameterIt.Value();
|
|
if(!ParamValue.bBound)
|
|
{
|
|
// Only valid parameters should be in the shader map
|
|
checkSlow(ParamValue.Size > 0);
|
|
bBindingsComplete = bBindingsComplete && ParamValue.bBound;
|
|
UnBoundParameters += FString(TEXT(" Parameter ")) + ParamName + TEXT(" not bound!\n");
|
|
}
|
|
}
|
|
|
|
if (!bBindingsComplete)
|
|
{
|
|
FString ErrorMessage = FString(TEXT("Found unbound parameters being used in shadertype ")) + ShaderTypeName + TEXT(" (VertexFactory: ") + VertexFactoryName + TEXT(")\n") + UnBoundParameters;
|
|
|
|
// There will be unbound parameters for Metal's "Hull" shader stage as it is merely a placeholder to provide binding indices to the RHI
|
|
if(!IsMetalPlatform((EShaderPlatform)Target.Platform) || Target.Frequency != SF_Hull)
|
|
{
|
|
// We use a non-Slate message box to avoid problem where we haven't compiled the shaders for Slate.
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *ErrorMessage, TEXT("Error"));
|
|
}
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
|
|
void FShaderParameterMap::UpdateHash(FSHA1& HashState) const
|
|
{
|
|
for(TMap<FString,FParameterAllocation>::TConstIterator ParameterIt(ParameterMap);ParameterIt;++ParameterIt)
|
|
{
|
|
const FString& ParamName = ParameterIt.Key();
|
|
const FParameterAllocation& ParamValue = ParameterIt.Value();
|
|
HashState.Update((const uint8*)*ParamName, ParamName.Len() * sizeof(TCHAR));
|
|
HashState.Update((const uint8*)&ParamValue.BufferIndex, sizeof(ParamValue.BufferIndex));
|
|
HashState.Update((const uint8*)&ParamValue.BaseIndex, sizeof(ParamValue.BaseIndex));
|
|
HashState.Update((const uint8*)&ParamValue.Size, sizeof(ParamValue.Size));
|
|
}
|
|
}
|
|
|
|
bool FShaderType::bInitializedSerializationHistory = false;
|
|
|
|
FShaderType::FShaderType(
|
|
EShaderTypeForDynamicCast InShaderTypeForDynamicCast,
|
|
const TCHAR* InName,
|
|
const TCHAR* InSourceFilename,
|
|
const TCHAR* InFunctionName,
|
|
uint32 InFrequency,
|
|
int32 InTotalPermutationCount,
|
|
ConstructSerializedType InConstructSerializedRef,
|
|
GetStreamOutElementsType InGetStreamOutElementsRef,
|
|
const FShaderParametersMetadata* InRootParametersMetadata
|
|
):
|
|
ShaderTypeForDynamicCast(InShaderTypeForDynamicCast),
|
|
Name(InName),
|
|
TypeName(InName),
|
|
SourceFilename(InSourceFilename),
|
|
FunctionName(InFunctionName),
|
|
Frequency(InFrequency),
|
|
TotalPermutationCount(InTotalPermutationCount),
|
|
ConstructSerializedRef(InConstructSerializedRef),
|
|
GetStreamOutElementsRef(InGetStreamOutElementsRef),
|
|
RootParametersMetadata(InRootParametersMetadata),
|
|
GlobalListLink(this)
|
|
{
|
|
bCachedUniformBufferStructDeclarations = false;
|
|
|
|
// This will trigger if an IMPLEMENT_SHADER_TYPE was in a module not loaded before InitializeShaderTypes
|
|
// Shader types need to be implemented in modules that are loaded before that
|
|
checkf(!bInitializedSerializationHistory, TEXT("Shader type was loaded after engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier."));
|
|
|
|
//make sure the name is shorter than the maximum serializable length
|
|
check(FCString::Strlen(InName) < NAME_SIZE);
|
|
|
|
// Make sure the format of the source file path is right.
|
|
check(CheckVirtualShaderFilePath(InSourceFilename));
|
|
|
|
// register this shader type
|
|
GlobalListLink.LinkHead(GetTypeList());
|
|
GetNameToTypeMap().Add(TypeName, this);
|
|
|
|
// Assign the shader type the next unassigned hash index.
|
|
static uint32 NextHashIndex = 0;
|
|
HashIndex = NextHashIndex++;
|
|
}
|
|
|
|
FShaderType::~FShaderType()
|
|
{
|
|
GlobalListLink.Unlink();
|
|
GetNameToTypeMap().Remove(TypeName);
|
|
}
|
|
|
|
TLinkedList<FShaderType*>*& FShaderType::GetTypeList()
|
|
{
|
|
return GShaderTypeList;
|
|
}
|
|
|
|
FShaderType* FShaderType::GetShaderTypeByName(const TCHAR* Name)
|
|
{
|
|
for(TLinkedList<FShaderType*>::TIterator It(GetTypeList()); It; It.Next())
|
|
{
|
|
FShaderType* Type = *It;
|
|
|
|
if (FPlatformString::Strcmp(Name, Type->GetName()) == 0)
|
|
{
|
|
return Type;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TArray<FShaderType*> FShaderType::GetShaderTypesByFilename(const TCHAR* Filename)
|
|
{
|
|
TArray<FShaderType*> OutShaders;
|
|
for(TLinkedList<FShaderType*>::TIterator It(GetTypeList()); It; It.Next())
|
|
{
|
|
FShaderType* Type = *It;
|
|
|
|
if (FPlatformString::Strcmp(Filename, Type->GetShaderFilename()) == 0)
|
|
{
|
|
OutShaders.Add(Type);
|
|
}
|
|
}
|
|
return OutShaders;
|
|
}
|
|
|
|
TMap<FName, FShaderType*>& FShaderType::GetNameToTypeMap()
|
|
{
|
|
if(!GShaderNameToTypeMap)
|
|
{
|
|
GShaderNameToTypeMap = new TMap<FName, FShaderType*>();
|
|
}
|
|
return *GShaderNameToTypeMap;
|
|
}
|
|
|
|
inline bool FShaderType::GetOutdatedCurrentType(TArray<FShaderType*>& OutdatedShaderTypes, TArray<const FVertexFactoryType*>& OutdatedFactoryTypes) const
|
|
{
|
|
bool bOutdated = false;
|
|
#if WITH_EDITOR
|
|
for (TMap<FShaderId, FShader*>::TConstIterator ShaderIt(ShaderIdMap);ShaderIt;++ShaderIt)
|
|
{
|
|
FShader* Shader = ShaderIt.Value();
|
|
const FVertexFactoryParameterRef* VFParameterRef = Shader->GetVertexFactoryParameterRef();
|
|
const FSHAHash& SavedHash = Shader->GetHash();
|
|
const FSHAHash& CurrentHash = GetSourceHash(Shader->GetShaderPlatform());
|
|
const bool bOutdatedShader = SavedHash != CurrentHash;
|
|
const bool bOutdatedVertexFactory =
|
|
VFParameterRef && VFParameterRef->GetVertexFactoryType() && VFParameterRef->GetVertexFactoryType()->GetSourceHash(VFParameterRef->GetShaderPlatform()) != VFParameterRef->GetHash();
|
|
|
|
if (bOutdatedShader)
|
|
{
|
|
OutdatedShaderTypes.AddUnique(Shader->Type);
|
|
bOutdated = true;
|
|
}
|
|
|
|
if (bOutdatedVertexFactory)
|
|
{
|
|
OutdatedFactoryTypes.AddUnique(VFParameterRef->GetVertexFactoryType());
|
|
bOutdated = true;
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
return bOutdated;
|
|
}
|
|
|
|
void FShaderType::GetOutdatedTypes(TArray<FShaderType*>& OutdatedShaderTypes, TArray<const FVertexFactoryType*>& OutdatedFactoryTypes)
|
|
{
|
|
for(TLinkedList<FShaderType*>::TIterator It(GetTypeList()); It; It.Next())
|
|
{
|
|
FShaderType* Type = *It;
|
|
Type->GetOutdatedCurrentType(OutdatedShaderTypes, OutdatedFactoryTypes);
|
|
}
|
|
|
|
for (int32 TypeIndex = 0; TypeIndex < OutdatedShaderTypes.Num(); TypeIndex++)
|
|
{
|
|
UE_LOG(LogShaders, Warning, TEXT(" Recompiling %s"), OutdatedShaderTypes[TypeIndex]->GetName());
|
|
}
|
|
for (int32 TypeIndex = 0; TypeIndex < OutdatedFactoryTypes.Num(); TypeIndex++)
|
|
{
|
|
UE_LOG(LogShaders, Warning, TEXT(" Recompiling %s"), OutdatedFactoryTypes[TypeIndex]->GetName());
|
|
}
|
|
}
|
|
|
|
|
|
FArchive& operator<<(FArchive& Ar,FShaderType*& Ref)
|
|
{
|
|
if(Ar.IsSaving())
|
|
{
|
|
FName ShaderTypeName = Ref ? FName(Ref->Name) : NAME_None;
|
|
Ar << ShaderTypeName;
|
|
}
|
|
else if(Ar.IsLoading())
|
|
{
|
|
FName ShaderTypeName = NAME_None;
|
|
Ar << ShaderTypeName;
|
|
|
|
Ref = NULL;
|
|
|
|
if(ShaderTypeName != NAME_None)
|
|
{
|
|
// look for the shader type in the global name to type map
|
|
FShaderType** ShaderType = FShaderType::GetNameToTypeMap().Find(ShaderTypeName);
|
|
if (ShaderType)
|
|
{
|
|
// if we found it, use it
|
|
Ref = *ShaderType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogShaders, Verbose, TEXT("ShaderType '%s' dependency was not found."), *ShaderTypeName.ToString());
|
|
}
|
|
}
|
|
}
|
|
return Ar;
|
|
}
|
|
|
|
|
|
FShader* FShaderType::FindShaderById(const FShaderId& Id)
|
|
{
|
|
check(IsInGameThread());
|
|
FShader* Result = ShaderIdMap.FindRef(Id);
|
|
check(!Result || Result->GetId() == Id);
|
|
return Result;
|
|
}
|
|
|
|
FShader* FShaderType::ConstructForDeserialization() const
|
|
{
|
|
return (*ConstructSerializedRef)();
|
|
}
|
|
|
|
const FSHAHash& FShaderType::GetSourceHash(EShaderPlatform ShaderPlatform) const
|
|
{
|
|
return GetShaderFileHash(GetShaderFilename(), ShaderPlatform);
|
|
}
|
|
|
|
void FShaderType::Initialize(const TMap<FString, TArray<const TCHAR*> >& ShaderFileToUniformBufferVariables)
|
|
{
|
|
//#todo-rco: Need to call this only when Initializing from a Pipeline once it's removed from the global linked list
|
|
if (!FPlatformProperties::RequiresCookedData())
|
|
{
|
|
#if UE_BUILD_DEBUG
|
|
TArray<FShaderType*> UniqueShaderTypes;
|
|
#endif
|
|
for(TLinkedList<FShaderType*>::TIterator It(FShaderType::GetTypeList()); It; It.Next())
|
|
{
|
|
FShaderType* Type = *It;
|
|
#if UE_BUILD_DEBUG
|
|
UniqueShaderTypes.Add(Type);
|
|
#endif
|
|
GenerateReferencedUniformBuffers(Type->SourceFilename, Type->Name, ShaderFileToUniformBufferVariables, Type->ReferencedUniformBufferStructsCache);
|
|
|
|
// Cache serialization history for each shader type
|
|
// This history is used to detect when shader serialization changes without a corresponding .usf change
|
|
{
|
|
// Construct a temporary shader, which is initialized to safe values for serialization
|
|
FShader* TempShader = Type->ConstructForDeserialization();
|
|
check(TempShader != NULL);
|
|
TempShader->Type = Type;
|
|
|
|
// Serialize the temp shader to memory and record the number and sizes of serializations
|
|
TArray<uint8> TempData;
|
|
FMemoryWriter Ar(TempData, true);
|
|
FShaderSaveArchive SaveArchive(Ar, Type->SerializationHistory);
|
|
TempShader->SerializeBase(SaveArchive, false, false);
|
|
|
|
// Destroy the temporary shader
|
|
delete TempShader;
|
|
}
|
|
}
|
|
|
|
#if UE_BUILD_DEBUG
|
|
// Check for duplicated shader type names
|
|
UniqueShaderTypes.Sort([](const FShaderType& A, const FShaderType& B) { return (SIZE_T)&A < (SIZE_T)&B; });
|
|
for (int32 Index = 1; Index < UniqueShaderTypes.Num(); ++Index)
|
|
{
|
|
checkf(UniqueShaderTypes[Index - 1] != UniqueShaderTypes[Index], TEXT("Duplicated FShader type name %s found, please rename one of them!"), UniqueShaderTypes[Index]->GetName());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bInitializedSerializationHistory = true;
|
|
}
|
|
|
|
void FShaderType::Uninitialize()
|
|
{
|
|
for(TLinkedList<FShaderType*>::TIterator It(FShaderType::GetTypeList()); It; It.Next())
|
|
{
|
|
FShaderType* Type = *It;
|
|
Type->SerializationHistory = FSerializationHistory();
|
|
}
|
|
|
|
bInitializedSerializationHistory = false;
|
|
}
|
|
|
|
TMap<FShaderResourceId, FShaderResource*> FShaderResource::ShaderResourceIdMap;
|
|
|
|
#if RHI_RAYTRACING
|
|
TArray<uint32> FShaderResource::GlobalUnusedIndicies;
|
|
TArray<FRHIRayTracingShader*> FShaderResource::GlobalRayTracingMaterialLibrary;
|
|
FCriticalSection FShaderResource::GlobalRayTracingMaterialLibraryCS;
|
|
|
|
void FShaderResource::GetRayTracingMaterialLibrary(TArray<FRHIRayTracingShader*>& RayTracingMaterials, FRHIRayTracingShader* DefaultShader)
|
|
{
|
|
FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS);
|
|
RayTracingMaterials = GlobalRayTracingMaterialLibrary;
|
|
|
|
for (uint32 Index : GlobalUnusedIndicies)
|
|
{
|
|
RayTracingMaterials[Index] = DefaultShader;
|
|
}
|
|
}
|
|
|
|
uint32 FShaderResource::AddToRayTracingLibrary(FRHIRayTracingShader* Shader)
|
|
{
|
|
FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS);
|
|
|
|
if (GlobalUnusedIndicies.Num() != 0)
|
|
{
|
|
uint32 Index = GlobalUnusedIndicies.Pop(false);
|
|
checkSlow(GlobalRayTracingMaterialLibrary[Index] == nullptr);
|
|
GlobalRayTracingMaterialLibrary[Index] = Shader;
|
|
return Index;
|
|
}
|
|
else
|
|
{
|
|
GlobalRayTracingMaterialLibrary.Add(Shader);
|
|
return GlobalRayTracingMaterialLibrary.Num() - 1;
|
|
}
|
|
}
|
|
|
|
void FShaderResource::RemoveFromRayTracingLibrary(uint32 Index)
|
|
{
|
|
FScopeLock Lock(&GlobalRayTracingMaterialLibraryCS);
|
|
GlobalUnusedIndicies.Push(Index);
|
|
GlobalRayTracingMaterialLibrary[Index] = nullptr;
|
|
}
|
|
#endif
|
|
|
|
FShaderResource::FShaderResource()
|
|
: SpecificType(NULL)
|
|
, SpecificPermutationId(0)
|
|
, NumRefs(0)
|
|
, NumInstructions(0)
|
|
#if WITH_EDITORONLY_DATA
|
|
, NumTextureSamplers(0)
|
|
#endif
|
|
, bCodeInSharedLocation(false)
|
|
, bCodeInSharedLocationRequested(false)
|
|
{
|
|
INC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1);
|
|
}
|
|
|
|
FShaderResource::FShaderResource(const FShaderCompilerOutput& Output, FShaderType* InSpecificType, int32 InSpecificPermutationId)
|
|
: SpecificType(InSpecificType)
|
|
, SpecificPermutationId(InSpecificPermutationId)
|
|
, NumRefs(0)
|
|
, NumInstructions(Output.NumInstructions)
|
|
#if WITH_EDITORONLY_DATA
|
|
, NumTextureSamplers(Output.NumTextureSamplers)
|
|
#endif
|
|
, bCodeInSharedLocation(false)
|
|
, bCodeInSharedLocationRequested(false)
|
|
|
|
{
|
|
BuildParameterMapInfo(Output.ParameterMap.GetParameterMap());
|
|
|
|
check(!(SpecificPermutationId != 0 && SpecificType == nullptr));
|
|
|
|
Target = Output.Target;
|
|
CompressCode(Output.ShaderCode.GetReadAccess());
|
|
|
|
check(Code.Num() > 0);
|
|
|
|
OutputHash = Output.OutputHash;
|
|
checkSlow(OutputHash != FSHAHash());
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
PlatformDebugData = Output.PlatformDebugData;
|
|
#endif
|
|
|
|
{
|
|
check(IsInGameThread());
|
|
ShaderResourceIdMap.Add(GetId(), this);
|
|
}
|
|
|
|
INC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num());
|
|
INC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes());
|
|
INC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1);
|
|
}
|
|
|
|
|
|
FShaderResource::~FShaderResource()
|
|
{
|
|
check(NumRefs == 0);
|
|
|
|
DEC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num());
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes());
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_NumShaderResourcesLoaded, 1);
|
|
}
|
|
|
|
void FShaderResource::BuildParameterMapInfo(const TMap<FString, FParameterAllocation>& ParameterMap)
|
|
{
|
|
for (int32 ParameterTypeIndex = 0; ParameterTypeIndex < (int32)EShaderParameterType::Num; ParameterTypeIndex++)
|
|
{
|
|
EShaderParameterType CurrentParameterType = (EShaderParameterType)ParameterTypeIndex;
|
|
|
|
if (CurrentParameterType == EShaderParameterType::LooseData)
|
|
{
|
|
for (TMap<FString, FParameterAllocation>::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt)
|
|
{
|
|
const FParameterAllocation& ParamValue = ParameterIt.Value();
|
|
|
|
if (ParamValue.Type == CurrentParameterType)
|
|
{
|
|
bool bAddedToExistingBuffer = false;
|
|
|
|
for (int32 LooseParameterBufferIndex = 0; LooseParameterBufferIndex < ParameterMapInfo.LooseParameterBuffers.Num(); LooseParameterBufferIndex++)
|
|
{
|
|
FShaderLooseParameterBufferInfo& LooseParameterBufferInfo = ParameterMapInfo.LooseParameterBuffers[LooseParameterBufferIndex];
|
|
|
|
if (LooseParameterBufferInfo.BufferIndex == ParamValue.BufferIndex)
|
|
{
|
|
FShaderParameterInfo ParameterInfo(ParamValue.BaseIndex, ParamValue.Size);
|
|
LooseParameterBufferInfo.Parameters.Add(ParameterInfo);
|
|
LooseParameterBufferInfo.BufferSize += ParamValue.Size;
|
|
bAddedToExistingBuffer = true;
|
|
}
|
|
}
|
|
|
|
if (!bAddedToExistingBuffer)
|
|
{
|
|
FShaderLooseParameterBufferInfo NewParameterBufferInfo(ParamValue.BufferIndex, ParamValue.Size);
|
|
|
|
FShaderParameterInfo ParameterInfo(ParamValue.BaseIndex, ParamValue.Size);
|
|
NewParameterBufferInfo.Parameters.Add(ParameterInfo);
|
|
|
|
ParameterMapInfo.LooseParameterBuffers.Add(NewParameterBufferInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (CurrentParameterType != EShaderParameterType::UAV)
|
|
{
|
|
int32 NumParameters = 0;
|
|
|
|
for (TMap<FString, FParameterAllocation>::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt)
|
|
{
|
|
const FParameterAllocation& ParamValue = ParameterIt.Value();
|
|
|
|
if (ParamValue.Type == CurrentParameterType)
|
|
{
|
|
NumParameters++;
|
|
}
|
|
}
|
|
|
|
TArray<FShaderParameterInfo>* ParameterInfoArray = &ParameterMapInfo.UniformBuffers;
|
|
|
|
if (CurrentParameterType == EShaderParameterType::Sampler)
|
|
{
|
|
ParameterInfoArray = &ParameterMapInfo.TextureSamplers;
|
|
}
|
|
else if (CurrentParameterType == EShaderParameterType::SRV)
|
|
{
|
|
ParameterInfoArray = &ParameterMapInfo.SRVs;
|
|
}
|
|
else
|
|
{
|
|
check(CurrentParameterType == EShaderParameterType::UniformBuffer);
|
|
}
|
|
|
|
ParameterInfoArray->Empty(NumParameters);
|
|
|
|
for (TMap<FString, FParameterAllocation>::TConstIterator ParameterIt(ParameterMap); ParameterIt; ++ParameterIt)
|
|
{
|
|
const FParameterAllocation& ParamValue = ParameterIt.Value();
|
|
|
|
if (ParamValue.Type == CurrentParameterType)
|
|
{
|
|
const uint16 BaseIndex = CurrentParameterType == EShaderParameterType::UniformBuffer ? ParamValue.BufferIndex : ParamValue.BaseIndex;
|
|
FShaderParameterInfo ParameterInfo(BaseIndex, ParamValue.Size);
|
|
ParameterInfoArray->Add(ParameterInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FShaderResource::UncompressCode(TArray<uint8>& UncompressedCode) const
|
|
{
|
|
if (Code.Num() != UncompressedCodeSize)
|
|
{
|
|
UncompressedCode.SetNum(UncompressedCodeSize);
|
|
auto bSucceed = FCompression::UncompressMemory(ShaderCompressionFormat, UncompressedCode.GetData(), UncompressedCodeSize, Code.GetData(), Code.Num());
|
|
check(bSucceed);
|
|
}
|
|
else
|
|
{
|
|
UncompressedCode = Code;
|
|
}
|
|
}
|
|
|
|
void FShaderResource::CompressCode(const TArray<uint8>& UncompressedCode)
|
|
{
|
|
UncompressedCodeSize = UncompressedCode.Num();
|
|
Code = UncompressedCode;
|
|
int32 CompressedSize = Code.Num();
|
|
if (FCompression::CompressMemory(ShaderCompressionFormat, Code.GetData(), CompressedSize, UncompressedCode.GetData(), UncompressedCode.Num()))
|
|
{
|
|
Code.SetNum(CompressedSize);
|
|
}
|
|
Code.Shrink();
|
|
}
|
|
|
|
void FShaderResource::Register()
|
|
{
|
|
check(IsInGameThread());
|
|
ShaderResourceIdMap.Add(GetId(), this);
|
|
}
|
|
|
|
// Note: this is derived data. Bump guid in ShaderVersion.ush if changing the format, no backwards compat is necessary
|
|
void FShaderResource::Serialize(FArchive& Ar, bool bLoadedByCookedMaterial)
|
|
{
|
|
check(!(SpecificPermutationId != 0 && SpecificType == nullptr));
|
|
|
|
Ar.UsingCustomVersion(FRenderingObjectVersion::GUID);
|
|
|
|
Ar << SpecificType;
|
|
if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId)
|
|
{
|
|
Ar << SpecificPermutationId;
|
|
}
|
|
Ar << Target;
|
|
|
|
if (Ar.CustomVer(FRenderingObjectVersion::GUID) < FRenderingObjectVersion::ShaderResourceCodeSharing)
|
|
{
|
|
Ar << Code;
|
|
}
|
|
Ar << OutputHash;
|
|
Ar << NumInstructions;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if ((!Ar.IsCooking() || Ar.CookingTarget()->HasEditorOnlyData()) && !bLoadedByCookedMaterial)
|
|
{
|
|
Ar << NumTextureSamplers;
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
Ar << ParameterMapInfo;
|
|
|
|
if (Ar.UE4Ver() >= VER_UE4_COMPRESSED_SHADER_RESOURCES)
|
|
{
|
|
Ar << UncompressedCodeSize;
|
|
}
|
|
|
|
if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderResourceCodeSharing)
|
|
{
|
|
SerializeShaderCode(Ar);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!bLoadedByCookedMaterial)
|
|
{
|
|
SerializePlatformDebugData(Ar);
|
|
}
|
|
#endif
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
INC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), (int64)Code.Num());
|
|
INC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes());
|
|
}
|
|
}
|
|
|
|
void FShaderResource::SerializeShaderCode(FArchive& Ar)
|
|
{
|
|
// To not pollute the DDC we don't change the state of this object in memory, just the state of the object in the serialised archive.
|
|
bool bCodeShared = bCodeInSharedLocation;
|
|
|
|
#if WITH_EDITOR
|
|
// in case shader code sharing is enabled, code will be saved outside of material asset
|
|
if(Ar.IsSaving() && Ar.IsCooking() && Ar.IsPersistent() && !Ar.IsObjectReferenceCollector() && !bCodeInSharedLocation)
|
|
{
|
|
bCodeShared = FShaderCodeLibrary::AddShaderCode((EShaderPlatform)Target.Platform, (EShaderFrequency)Target.Frequency, OutputHash, Code, UncompressedCodeSize);
|
|
}
|
|
#endif
|
|
|
|
Ar << bCodeShared;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
bCodeInSharedLocation = bCodeShared;
|
|
|
|
if (bCodeInSharedLocation)
|
|
{
|
|
if (!GRHILazyShaderCodeLoading)
|
|
{
|
|
if (FShaderCodeLibrary::RequestShaderCode(OutputHash, &Ar))
|
|
{
|
|
bCodeInSharedLocationRequested = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FShaderCodeLibrary::LazyRequestShaderCode(OutputHash, &Ar);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bCodeShared)
|
|
{
|
|
Ar << Code;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
void FShaderResource::SerializePlatformDebugData(FArchive& Ar)
|
|
{
|
|
#if WITH_ENGINE
|
|
if (Ar.IsCooking())
|
|
{
|
|
// Notify the platform shader format that this particular shader is being used in the cook.
|
|
// We discard this data in cooked builds unless Ar.CookingTarget()->HasEditorOnlyData() is true.
|
|
if (PlatformDebugData.Num())
|
|
{
|
|
TArray<FName> ShaderFormatNames;
|
|
Ar.CookingTarget()->GetAllTargetedShaderFormats(ShaderFormatNames);
|
|
|
|
for (FName FormatName : ShaderFormatNames)
|
|
{
|
|
const IShaderFormat* ShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(FormatName);
|
|
if (ShaderFormat)
|
|
{
|
|
ShaderFormat->NotifyShaderCooked(PlatformDebugData, FormatName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Ar.IsCooking() || Ar.CookingTarget()->HasEditorOnlyData())
|
|
#endif
|
|
{
|
|
// Always serialize if we're not cooking, the cook target requires editor only data, or we don't have the engine (i.e. we're SCW).
|
|
Ar << PlatformDebugData;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FShaderResource::AddRef()
|
|
{
|
|
checkSlow(IsInGameThread());
|
|
++NumRefs;
|
|
}
|
|
|
|
|
|
void FShaderResource::Release()
|
|
{
|
|
checkSlow(IsInGameThread());
|
|
check(NumRefs != 0);
|
|
if(--NumRefs == 0)
|
|
{
|
|
ShaderResourceIdMap.Remove(GetId());
|
|
|
|
// Send a release message to the rendering thread when the shader loses its last reference.
|
|
BeginReleaseResource(this);
|
|
BeginCleanup(this);
|
|
|
|
if (bCodeInSharedLocation)
|
|
{
|
|
if (bCodeInSharedLocationRequested)
|
|
{
|
|
FShaderCodeLibrary::ReleaseShaderCode(OutputHash);
|
|
}
|
|
else
|
|
{
|
|
FShaderCodeLibrary::LazyReleaseShaderCode(OutputHash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FShaderResource* FShaderResource::FindShaderResourceById(const FShaderResourceId& Id)
|
|
{
|
|
check(IsInGameThread());
|
|
FShaderResource* Result = ShaderResourceIdMap.FindRef(Id);
|
|
return Result;
|
|
}
|
|
|
|
|
|
FShaderResource* FShaderResource::FindOrCreateShaderResource(const FShaderCompilerOutput& Output, FShaderType* SpecificType, int32 SpecificPermutationId)
|
|
{
|
|
const FShaderResourceId ResourceId(Output.Target, Output.OutputHash, SpecificType ? SpecificType->GetName() : nullptr, SpecificPermutationId);
|
|
FShaderResource* Resource = FindShaderResourceById(ResourceId);
|
|
if (!Resource)
|
|
{
|
|
Resource = new FShaderResource(Output, SpecificType, SpecificPermutationId);
|
|
}
|
|
|
|
return Resource;
|
|
}
|
|
|
|
void FShaderResource::GetAllShaderResourceId(TArray<FShaderResourceId>& Ids)
|
|
{
|
|
check(IsInGameThread());
|
|
ShaderResourceIdMap.GetKeys(Ids);
|
|
}
|
|
|
|
bool FShaderResource::ArePlatformsCompatible(EShaderPlatform CurrentPlatform, EShaderPlatform TargetPlatform)
|
|
{
|
|
bool bFeatureLevelCompatible = CurrentPlatform == TargetPlatform;
|
|
|
|
if (!bFeatureLevelCompatible && IsPCPlatform(CurrentPlatform) && IsPCPlatform(TargetPlatform) )
|
|
{
|
|
bFeatureLevelCompatible = GetMaxSupportedFeatureLevel(CurrentPlatform) >= GetMaxSupportedFeatureLevel(TargetPlatform);
|
|
|
|
bool const bIsTargetD3D = TargetPlatform == SP_PCD3D_SM5 ||
|
|
TargetPlatform == SP_PCD3D_SM4 ||
|
|
TargetPlatform == SP_PCD3D_ES3_1 ||
|
|
TargetPlatform == SP_PCD3D_ES2;
|
|
|
|
bool const bIsCurrentPlatformD3D = CurrentPlatform == SP_PCD3D_SM5 ||
|
|
CurrentPlatform == SP_PCD3D_SM4 ||
|
|
TargetPlatform == SP_PCD3D_ES3_1 ||
|
|
CurrentPlatform == SP_PCD3D_ES2;
|
|
|
|
// For Metal in Editor we can switch feature-levels, but not in cooked projects when using Metal shader librariss.
|
|
bool const bIsCurrentMetal = IsMetalPlatform(CurrentPlatform);
|
|
bool const bIsTargetMetal = IsMetalPlatform(TargetPlatform);
|
|
bool const bIsMetalCompatible = (bIsCurrentMetal == bIsTargetMetal)
|
|
#if !WITH_EDITOR // Static analysis doesn't like (|| WITH_EDITOR)
|
|
&& (!IsMetalPlatform(CurrentPlatform) || (CurrentPlatform == TargetPlatform))
|
|
#endif
|
|
;
|
|
|
|
bool const bIsCurrentOpenGL = IsOpenGLPlatform(CurrentPlatform);
|
|
bool const bIsTargetOpenGL = IsOpenGLPlatform(TargetPlatform);
|
|
|
|
bFeatureLevelCompatible = bFeatureLevelCompatible && (bIsCurrentPlatformD3D == bIsTargetD3D && bIsMetalCompatible && bIsCurrentOpenGL == bIsTargetOpenGL);
|
|
}
|
|
|
|
return bFeatureLevelCompatible;
|
|
}
|
|
|
|
FSHAHash &FShaderResource::FilterShaderSourceHashForSerialization(const FArchive& Ar, FSHAHash &HashToSerialize)
|
|
{
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
return (!Ar.IsCooking()) ? HashToSerialize : ShaderSourceDefaultHash;
|
|
#else
|
|
return ShaderSourceDefaultHash;
|
|
#endif
|
|
}
|
|
|
|
static void SafeAssignHash(FRHIShader* InShader, const FSHAHash& Hash)
|
|
{
|
|
if (InShader)
|
|
{
|
|
InShader->SetHash(Hash);
|
|
}
|
|
}
|
|
|
|
void FShaderResource::InitRHI()
|
|
{
|
|
checkf(bCodeInSharedLocation || Code.Num() > 0, TEXT("FShaderResource::InitRHI was called with empty bytecode, which can happen if the resource is initialized multiple times on platforms with no editor data."));
|
|
|
|
// we can't have this called on the wrong platform's shaders
|
|
if (!ArePlatformsCompatible(GMaxRHIShaderPlatform, (EShaderPlatform)Target.Platform))
|
|
{
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
UE_LOG(LogShaders, Fatal, TEXT("FShaderResource::InitRHI got platform %s but it is not compatible with %s"),
|
|
*LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString(), *LegacyShaderPlatformToShaderFormat(GMaxRHIShaderPlatform).ToString());
|
|
}
|
|
return;
|
|
}
|
|
|
|
TArray<uint8> UncompressedCode;
|
|
if (!bCodeInSharedLocation)
|
|
{
|
|
UncompressCode(UncompressedCode);
|
|
}
|
|
|
|
INC_DWORD_STAT_BY(STAT_Shaders_NumShadersUsedForRendering, 1);
|
|
SCOPE_CYCLE_COUNTER(STAT_Shaders_RTShaderLoadTime);
|
|
|
|
if(Target.Frequency == SF_Vertex)
|
|
{
|
|
Shader = FShaderCodeLibrary::CreateVertexShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
else if(Target.Frequency == SF_Pixel)
|
|
{
|
|
Shader = FShaderCodeLibrary::CreatePixelShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
else if(Target.Frequency == SF_Hull)
|
|
{
|
|
Shader = FShaderCodeLibrary::CreateHullShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
else if(Target.Frequency == SF_Domain)
|
|
{
|
|
Shader = FShaderCodeLibrary::CreateDomainShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
else if(Target.Frequency == SF_Geometry)
|
|
{
|
|
if (SpecificType)
|
|
{
|
|
FStreamOutElementList ElementList;
|
|
TArray<uint32> StreamStrides;
|
|
int32 RasterizedStream = -1;
|
|
SpecificType->GetStreamOutElements(ElementList, StreamStrides, RasterizedStream);
|
|
checkf(ElementList.Num(), TEXT("Shader type %s was given GetStreamOutElements implementation that had no elements!"), SpecificType->GetName());
|
|
|
|
//@todo - not using the cache
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
Shader = FShaderCodeLibrary::CreateGeometryShaderWithStreamOutput((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode, ElementList, StreamStrides.Num(), StreamStrides.GetData(), RasterizedStream);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
else
|
|
{
|
|
Shader = FShaderCodeLibrary::CreateGeometryShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
}
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
else if(Target.Frequency == SF_Compute)
|
|
{
|
|
Shader = FShaderCodeLibrary::CreateComputeShader((EShaderPlatform)Target.Platform, OutputHash, UncompressedCode);
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(Shader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
}
|
|
#if RHI_RAYTRACING
|
|
else if (Target.Frequency == SF_RayGen || Target.Frequency == SF_RayMiss || Target.Frequency == SF_RayHitGroup || Target.Frequency == SF_RayCallable)
|
|
{
|
|
if (GRHISupportsRayTracing)
|
|
{
|
|
RayTracingShader = RHICreateRayTracingShader(UncompressedCode, Target.GetFrequency());
|
|
UE_CLOG((bCodeInSharedLocation && !IsValidRef(RayTracingShader)), LogShaders, Fatal, TEXT("FShaderResource::SerializeShaderCode can't find shader code for: [%s]"), *LegacyShaderPlatformToShaderFormat((EShaderPlatform)Target.Platform).ToString());
|
|
|
|
if (Target.Frequency == SF_RayHitGroup)
|
|
{
|
|
RayTracingMaterialLibraryIndex = AddToRayTracingLibrary(RayTracingShader);
|
|
}
|
|
|
|
RayTracingShader->SetHash(OutputHash);
|
|
}
|
|
}
|
|
#endif // RHI_RAYTRACING
|
|
else
|
|
{
|
|
checkNoEntry(); // Unexpected shader target frequency
|
|
}
|
|
|
|
if (Target.Frequency != SF_Geometry)
|
|
{
|
|
checkf(!SpecificType, TEXT("Only geometry shaders can use GetStreamOutElements, shader type %s"), SpecificType->GetName());
|
|
}
|
|
|
|
if (!FPlatformProperties::HasEditorOnlyData())
|
|
{
|
|
DEC_DWORD_STAT_BY_FName(GetMemoryStatType((EShaderFrequency)Target.Frequency).GetName(), Code.Num());
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, Code.GetAllocatedSize());
|
|
Code.Empty();
|
|
|
|
if (bCodeInSharedLocation)
|
|
{
|
|
if (bCodeInSharedLocationRequested)
|
|
{
|
|
FShaderCodeLibrary::ReleaseShaderCode(OutputHash);
|
|
}
|
|
else
|
|
{
|
|
FShaderCodeLibrary::LazyReleaseShaderCode(OutputHash);
|
|
}
|
|
}
|
|
bCodeInSharedLocation = false;
|
|
bCodeInSharedLocationRequested = false;
|
|
}
|
|
}
|
|
|
|
|
|
void FShaderResource::ReleaseRHI()
|
|
{
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_NumShadersUsedForRendering, 1);
|
|
|
|
#if RHI_RAYTRACING
|
|
if (IsInitialized() && RayTracingMaterialLibraryIndex != UINT_MAX)
|
|
{
|
|
RemoveFromRayTracingLibrary(RayTracingMaterialLibraryIndex);
|
|
RayTracingMaterialLibraryIndex = UINT_MAX;
|
|
}
|
|
#endif
|
|
|
|
Shader.SafeRelease();
|
|
|
|
#if RHI_RAYTRACING
|
|
RayTracingShader.SafeRelease();
|
|
#endif // RHI_RAYTRACING
|
|
}
|
|
|
|
void FShaderResource::InitializeShaderRHI()
|
|
{
|
|
if (!IsInitialized())
|
|
{
|
|
STAT(double ShaderInitializationTime = 0);
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_Shaders_FrameRTShaderInitForRenderingTime);
|
|
SCOPE_SECONDS_COUNTER(ShaderInitializationTime);
|
|
|
|
InitResourceFromPossiblyParallelRendering();
|
|
}
|
|
|
|
INC_FLOAT_STAT_BY(STAT_Shaders_TotalRTShaderInitForRenderingTime,(float)ShaderInitializationTime);
|
|
}
|
|
|
|
checkSlow(IsInitialized());
|
|
}
|
|
|
|
FShaderResourceId FShaderResource::GetId() const
|
|
{
|
|
return FShaderResourceId(Target, OutputHash, SpecificType ? SpecificType->GetName() : nullptr, SpecificPermutationId);
|
|
}
|
|
|
|
FShaderId::FShaderId(const FSHAHash& InMaterialShaderMapHash, const FShaderPipelineType* InShaderPipeline, FVertexFactoryType* InVertexFactoryType, FShaderType* InShaderType, int32 InPermutationId, FShaderTarget InTarget)
|
|
: MaterialShaderMapHash(InMaterialShaderMapHash)
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
, SourceHash(InShaderType->GetSourceHash(InTarget.GetPlatform()))
|
|
#endif
|
|
, Target(InTarget)
|
|
, ShaderPipeline(InShaderPipeline)
|
|
, ShaderType(InShaderType)
|
|
, PermutationId(InPermutationId)
|
|
, SerializationHistory(InShaderType->GetSerializationHistory())
|
|
{
|
|
if (InVertexFactoryType)
|
|
{
|
|
VFSerializationHistory = InVertexFactoryType->GetSerializationHistory(InTarget.GetFrequency());
|
|
VertexFactoryType = InVertexFactoryType;
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
VFSourceHash = InVertexFactoryType->GetSourceHash(InTarget.GetPlatform());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
VFSerializationHistory = nullptr;
|
|
VertexFactoryType = nullptr;
|
|
}
|
|
}
|
|
|
|
FSelfContainedShaderId::FSelfContainedShaderId() :
|
|
Target(FShaderTarget(SF_NumFrequencies, SP_NumPlatforms))
|
|
{}
|
|
|
|
FSelfContainedShaderId::FSelfContainedShaderId(const FShaderId& InShaderId)
|
|
{
|
|
MaterialShaderMapHash = InShaderId.MaterialShaderMapHash;
|
|
VertexFactoryTypeName = InShaderId.VertexFactoryType ? InShaderId.VertexFactoryType->GetName() : TEXT("");
|
|
ShaderPipelineName = InShaderId.ShaderPipeline ? InShaderId.ShaderPipeline->GetName() : TEXT("");
|
|
VFSerializationHistory = InShaderId.VFSerializationHistory ? *InShaderId.VFSerializationHistory : FSerializationHistory();
|
|
ShaderTypeName = InShaderId.ShaderType->GetName();
|
|
PermutationId = InShaderId.PermutationId;
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
SourceHash = InShaderId.SourceHash;
|
|
VFSourceHash = InShaderId.VFSourceHash;
|
|
#endif
|
|
SerializationHistory = InShaderId.SerializationHistory;
|
|
Target = InShaderId.Target;
|
|
}
|
|
|
|
bool FSelfContainedShaderId::IsValid()
|
|
{
|
|
FShaderType** TypePtr = FShaderType::GetNameToTypeMap().Find(FName(*ShaderTypeName));
|
|
if (TypePtr
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
&& SourceHash == (*TypePtr)->GetSourceHash(Target.GetPlatform())
|
|
#endif
|
|
&& SerializationHistory == (*TypePtr)->GetSerializationHistory())
|
|
{
|
|
FVertexFactoryType* VFTypePtr = FVertexFactoryType::GetVFByName(VertexFactoryTypeName);
|
|
|
|
if (VertexFactoryTypeName == TEXT("")
|
|
|| (VFTypePtr
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
&& VFSourceHash == VFTypePtr->GetSourceHash(Target.GetPlatform())
|
|
#endif
|
|
&& VFSerializationHistory == *VFTypePtr->GetSerializationHistory(Target.GetFrequency())))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar,class FSelfContainedShaderId& Ref)
|
|
{
|
|
Ar.UsingCustomVersion(FRenderingObjectVersion::GUID);
|
|
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
FSHAHash& VFHash = Ref.VFSourceHash;
|
|
FSHAHash& Hash = Ref.SourceHash;
|
|
#else
|
|
FSHAHash VFHash, Hash;
|
|
#endif
|
|
|
|
Ar << Ref.MaterialShaderMapHash
|
|
<< Ref.VertexFactoryTypeName
|
|
<< Ref.ShaderPipelineName
|
|
<< FShaderResource::FilterShaderSourceHashForSerialization(Ar, VFHash)
|
|
<< Ref.VFSerializationHistory
|
|
<< Ref.ShaderTypeName
|
|
<< FShaderResource::FilterShaderSourceHashForSerialization(Ar, Hash)
|
|
<< Ref.SerializationHistory
|
|
<< Ref.Target;
|
|
|
|
if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId)
|
|
{
|
|
Ar << Ref.PermutationId;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
|
|
/**
|
|
* Used to construct a shader for deserialization.
|
|
* This still needs to initialize members to safe values since FShaderType::GenerateSerializationHistory uses this constructor.
|
|
*/
|
|
FShader::FShader()
|
|
: SerializedResource(nullptr)
|
|
, ShaderPipeline(nullptr)
|
|
, VFType(nullptr)
|
|
, Type(nullptr)
|
|
, PermutationId(0)
|
|
, NumRefs(0)
|
|
{
|
|
// set to undefined (currently shared with SF_Vertex)
|
|
Target.Frequency = 0;
|
|
Target.Platform = GShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel];
|
|
}
|
|
|
|
/**
|
|
* Construct a shader from shader compiler output.
|
|
*/
|
|
FShader::FShader(const CompiledShaderInitializerType& Initializer)
|
|
: MaterialShaderMapHash(Initializer.MaterialShaderMapHash)
|
|
, SerializedResource(nullptr)
|
|
, ShaderPipeline(Initializer.ShaderPipeline)
|
|
, VFType(Initializer.VertexFactoryType)
|
|
, Type(Initializer.Type)
|
|
, PermutationId(Initializer.PermutationId)
|
|
, Target(Initializer.Target)
|
|
, NumRefs(0)
|
|
{
|
|
check(Type);
|
|
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
OutputHash = Initializer.OutputHash;
|
|
checkSlow(OutputHash != FSHAHash());
|
|
|
|
// Store off the source hash that this shader was compiled with
|
|
// This will be used as part of the shader key in order to identify when shader files have been changed and a recompile is needed
|
|
SourceHash = Type->GetSourceHash(Target.GetPlatform());
|
|
|
|
if (VFType)
|
|
{
|
|
// Store off the VF source hash that this shader was compiled with
|
|
VFSourceHash = VFType->GetSourceHash(Target.GetPlatform());
|
|
}
|
|
#endif
|
|
|
|
// Bind uniform buffer parameters automatically
|
|
for (TLinkedList<FShaderParametersMetadata*>::TIterator StructIt(FShaderParametersMetadata::GetStructList()); StructIt; StructIt.Next())
|
|
{
|
|
if (Initializer.ParameterMap.ContainsParameterAllocation(StructIt->GetShaderVariableName()))
|
|
{
|
|
UniformBufferParameterStructs.Add(*StructIt);
|
|
UniformBufferParameters.Add(new FShaderUniformBufferParameter());
|
|
FShaderUniformBufferParameter* Parameter = UniformBufferParameters.Last();
|
|
Parameter->Bind(Initializer.ParameterMap, StructIt->GetShaderVariableName(), SPF_Mandatory);
|
|
}
|
|
}
|
|
|
|
SetResource(Initializer.Resource);
|
|
|
|
// Register the shader now that it is valid, so that it can be reused
|
|
Register(false);
|
|
}
|
|
|
|
|
|
FShader::~FShader()
|
|
{
|
|
check(NumRefs == 0);
|
|
|
|
for (int32 StructIndex = 0; StructIndex < UniformBufferParameters.Num(); StructIndex++)
|
|
{
|
|
delete UniformBufferParameters[StructIndex];
|
|
}
|
|
}
|
|
|
|
|
|
const FSHAHash& FShader::GetHash() const
|
|
{
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
return SourceHash;
|
|
#else
|
|
return ShaderSourceDefaultHash;
|
|
#endif
|
|
}
|
|
|
|
EShaderPlatform FShader::GetShaderPlatform() const
|
|
{
|
|
return Target.GetPlatform();
|
|
}
|
|
|
|
|
|
bool FShader::SerializeBase(FArchive& Ar, bool bShadersInline, bool bLoadedByCookedMaterial)
|
|
{
|
|
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
|
|
|
|
Serialize(Ar);
|
|
|
|
Ar.UsingCustomVersion(FRenderingObjectVersion::GUID);
|
|
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
FSHAHash& VFHash = VFSourceHash;
|
|
FSHAHash& Hash = SourceHash;
|
|
#else
|
|
FSHAHash VFHash, Hash, OutputHash;
|
|
#endif
|
|
|
|
Ar << OutputHash;
|
|
Ar << MaterialShaderMapHash;
|
|
Ar << ShaderPipeline;
|
|
Ar << VFType;
|
|
Ar << FShaderResource::FilterShaderSourceHashForSerialization(Ar, VFHash);
|
|
Ar << Type;
|
|
if (Ar.CustomVer(FRenderingObjectVersion::GUID) >= FRenderingObjectVersion::ShaderPermutationId)
|
|
{
|
|
Ar << PermutationId;
|
|
}
|
|
Ar << FShaderResource::FilterShaderSourceHashForSerialization(Ar, Hash);
|
|
Ar << Target;
|
|
|
|
// TODO(RDG): Kill that once all shaders are refactored.
|
|
if (Ar.IsLoading())
|
|
{
|
|
int32 NumUniformParameters;
|
|
Ar << NumUniformParameters;
|
|
|
|
UniformBufferParameterStructs.Empty(NumUniformParameters);
|
|
UniformBufferParameters.Empty(NumUniformParameters);
|
|
|
|
for (int32 ParameterIndex = 0; ParameterIndex < NumUniformParameters; ParameterIndex++)
|
|
{
|
|
FShaderParametersMetadata* Struct = nullptr;
|
|
|
|
if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::MaterialInstanceSerializeOptimization_ShaderFName)
|
|
{
|
|
FString StructName;
|
|
Ar << StructName;
|
|
Struct = FindUniformBufferStructByName(*StructName);
|
|
checkf(Struct, TEXT("Uniform Buffer Struct %s no longer exists, which shader of type %s was compiled with. Modify ShaderVersion.ush to invalidate old shaders."), *StructName, Type->GetName());
|
|
}
|
|
else
|
|
{
|
|
FName StructFName;
|
|
Ar << StructFName;
|
|
Struct = FindUniformBufferStructByFName(StructFName);
|
|
checkf(Struct, TEXT("Uniform Buffer Struct %s no longer exists, which shader of type %s was compiled with. Modify ShaderVersion.ush to invalidate old shaders."), *StructFName.ToString(), Type->GetName());
|
|
}
|
|
|
|
FShaderUniformBufferParameter* Parameter = new FShaderUniformBufferParameter();
|
|
|
|
Ar << *Parameter;
|
|
|
|
UniformBufferParameterStructs.Add(Struct);
|
|
UniformBufferParameters.Add(Parameter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 NumUniformParameters = UniformBufferParameters.Num();
|
|
Ar << NumUniformParameters;
|
|
|
|
for (int32 StructIndex = 0; StructIndex < UniformBufferParameters.Num(); StructIndex++)
|
|
{
|
|
FString StructName(UniformBufferParameterStructs[StructIndex]->GetStructTypeName());
|
|
|
|
if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::MaterialInstanceSerializeOptimization_ShaderFName)
|
|
{
|
|
Ar << StructName;
|
|
}
|
|
else
|
|
{
|
|
FName StructFName(*StructName);
|
|
Ar << StructFName;
|
|
}
|
|
|
|
Ar << *UniformBufferParameters[StructIndex];
|
|
}
|
|
}
|
|
|
|
if (bShadersInline)
|
|
{
|
|
// Save the shader resource if we are inlining shaders
|
|
if (Ar.IsSaving())
|
|
{
|
|
check(Resource->Target == Target);
|
|
Resource->Serialize(Ar, false);
|
|
}
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
// Load the inlined shader resource
|
|
SerializedResource = new FShaderResource();
|
|
SerializedResource->Serialize(Ar, bLoadedByCookedMaterial);
|
|
checkSlow(OutputHash == SerializedResource->OutputHash);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if saving, there's nothing to, the required data is already saved above to look it up at load time
|
|
if (Ar.IsLoading())
|
|
{
|
|
// generate a resource id
|
|
FShaderResourceId ResourceId(Target, OutputHash, Type->LimitShaderResourceToThisType() ? Type->GetName() : nullptr, Type->LimitShaderResourceToThisType() ? PermutationId : 0);
|
|
|
|
// use it to look up in the registered resource map
|
|
FShaderResource* ExistingResource = FShaderResource::FindShaderResourceById(ResourceId);
|
|
SetResource(ExistingResource);
|
|
}
|
|
}
|
|
|
|
Ar << Bindings;
|
|
|
|
return false;
|
|
}
|
|
|
|
void FShader::AddRef()
|
|
{
|
|
++NumRefs;
|
|
if (NumRefs == 1)
|
|
{
|
|
INC_DWORD_STAT_BY(STAT_Shaders_ShaderMemory, GetSizeBytes());
|
|
INC_DWORD_STAT_BY(STAT_Shaders_NumShadersLoaded,1);
|
|
}
|
|
}
|
|
|
|
void FShader::Release()
|
|
{
|
|
if(--NumRefs == 0)
|
|
{
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_ShaderMemory, GetSizeBytes());
|
|
DEC_DWORD_STAT_BY(STAT_Shaders_NumShadersLoaded,1);
|
|
|
|
// Deregister the shader now to eliminate references to it by the type's ShaderIdMap
|
|
Deregister();
|
|
BeginCleanup(this);
|
|
}
|
|
}
|
|
|
|
|
|
void FShader::Register(bool bLoadedByCookedMaterial)
|
|
{
|
|
FShaderId ShaderId = GetId();
|
|
check(ShaderId.MaterialShaderMapHash != FSHAHash());
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
check(ShaderId.SourceHash != FSHAHash() || FPlatformProperties::RequiresCookedData() || bLoadedByCookedMaterial);
|
|
#endif
|
|
check(Resource);
|
|
Type->AddToShaderIdMap(ShaderId, this);
|
|
}
|
|
|
|
void FShader::Deregister()
|
|
{
|
|
Type->RemoveFromShaderIdMap(GetId());
|
|
}
|
|
|
|
FShaderId FShader::GetId() const
|
|
{
|
|
FShaderId ShaderId(Type->GetSerializationHistory());
|
|
ShaderId.MaterialShaderMapHash = MaterialShaderMapHash;
|
|
ShaderId.ShaderPipeline = ShaderPipeline;
|
|
ShaderId.VertexFactoryType = VFType;
|
|
ShaderId.VFSerializationHistory = VFType ? VFType->GetSerializationHistory((EShaderFrequency)GetTarget().Frequency) : NULL;
|
|
ShaderId.ShaderType = Type;
|
|
ShaderId.PermutationId = PermutationId;
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
ShaderId.SourceHash = SourceHash;
|
|
ShaderId.VFSourceHash = VFSourceHash;
|
|
#endif
|
|
ShaderId.Target = Target;
|
|
return ShaderId;
|
|
}
|
|
|
|
void FShader::RegisterSerializedResource()
|
|
{
|
|
if (SerializedResource)
|
|
{
|
|
FShaderResource* ExistingResource = FShaderResource::FindShaderResourceById(SerializedResource->GetId());
|
|
|
|
// Reuse an existing shader resource if a matching one already exists in memory
|
|
if (ExistingResource)
|
|
{
|
|
delete SerializedResource;
|
|
SerializedResource = ExistingResource;
|
|
}
|
|
else
|
|
{
|
|
// Register the newly loaded shader resource so it can be reused by other shaders
|
|
SerializedResource->Register();
|
|
}
|
|
|
|
SetResource(SerializedResource);
|
|
}
|
|
}
|
|
|
|
void FShader::SetResource(FShaderResource* InResource)
|
|
{
|
|
check(InResource && InResource->Target == Target);
|
|
Resource = InResource;
|
|
}
|
|
|
|
void FShader::DumpDebugInfo()
|
|
{
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" FShader :MaterialShaderMapHash %s"), *MaterialShaderMapHash.ToString());
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :Target %s"), GetShaderFrequencyString((EShaderFrequency)Target.Frequency));
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :Target %s"), *LegacyShaderPlatformToShaderFormat(EShaderPlatform(Target.Platform)).ToString());
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :VFType %s"), VFType ? VFType->GetName() : TEXT("null"));
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :Type %s"), Type->GetName());
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :PermutationId %d"), PermutationId);
|
|
#if KEEP_SHADER_SOURCE_HASHES
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :SourceHash %s"), *SourceHash.ToString());
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :VFSourceHash %s"), *VFSourceHash.ToString());
|
|
UE_LOG(LogConsoleResponse, Display, TEXT(" :OutputHash %s"), *OutputHash.ToString());
|
|
#endif
|
|
}
|
|
|
|
void FShader::SaveShaderStableKeys(EShaderPlatform TargetShaderPlatform, const FStableShaderKeyAndValue& InSaveKeyVal)
|
|
{
|
|
#if WITH_EDITOR
|
|
if ((TargetShaderPlatform == EShaderPlatform::SP_NumPlatforms || EShaderPlatform(Target.Platform) == TargetShaderPlatform)
|
|
&& FShaderCodeLibrary::NeedsShaderStableKeys(TargetShaderPlatform))
|
|
{
|
|
FStableShaderKeyAndValue SaveKeyVal(InSaveKeyVal);
|
|
SaveKeyVal.TargetFrequency = FName(GetShaderFrequencyString((EShaderFrequency)Target.Frequency));
|
|
SaveKeyVal.TargetPlatform = FName(*LegacyShaderPlatformToShaderFormat(EShaderPlatform(Target.Platform)).ToString());
|
|
SaveKeyVal.VFType = FName(VFType ? VFType->GetName() : TEXT("null"));
|
|
SaveKeyVal.PermutationId = FName(*FString::Printf(TEXT("Perm_%d"), PermutationId));
|
|
SaveKeyVal.OutputHash = OutputHash;
|
|
if (Type)
|
|
{
|
|
Type->GetShaderStableKeyParts(SaveKeyVal);
|
|
}
|
|
FShaderCodeLibrary::AddShaderStableKeyValue(EShaderPlatform(Target.Platform), SaveKeyVal);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool FShaderPipelineType::bInitialized = false;
|
|
|
|
FShaderPipelineType::FShaderPipelineType(
|
|
const TCHAR* InName,
|
|
const FShaderType* InVertexShader,
|
|
const FShaderType* InHullShader,
|
|
const FShaderType* InDomainShader,
|
|
const FShaderType* InGeometryShader,
|
|
const FShaderType* InPixelShader,
|
|
bool bInShouldOptimizeUnusedOutputs) :
|
|
Name(InName),
|
|
TypeName(Name),
|
|
GlobalListLink(this),
|
|
bShouldOptimizeUnusedOutputs(bInShouldOptimizeUnusedOutputs)
|
|
{
|
|
checkf(Name && *Name, TEXT("Shader Pipeline Type requires a valid Name!"));
|
|
|
|
checkf(InVertexShader, TEXT("A Shader Pipeline always requires a Vertex Shader"));
|
|
|
|
checkf((InHullShader == nullptr && InDomainShader == nullptr) || (InHullShader != nullptr && InDomainShader != nullptr), TEXT("Both Hull & Domain shaders are needed for tessellation on Pipeline %s"), Name);
|
|
|
|
//make sure the name is shorter than the maximum serializable length
|
|
check(FCString::Strlen(InName) < NAME_SIZE);
|
|
|
|
FMemory::Memzero(AllStages);
|
|
|
|
if (InPixelShader)
|
|
{
|
|
Stages.Add(InPixelShader);
|
|
AllStages[SF_Pixel] = InPixelShader;
|
|
}
|
|
if (InGeometryShader)
|
|
{
|
|
Stages.Add(InGeometryShader);
|
|
AllStages[SF_Geometry] = InGeometryShader;
|
|
}
|
|
if (InDomainShader)
|
|
{
|
|
Stages.Add(InDomainShader);
|
|
AllStages[SF_Domain] = InDomainShader;
|
|
|
|
Stages.Add(InHullShader);
|
|
AllStages[SF_Hull] = InHullShader;
|
|
}
|
|
Stages.Add(InVertexShader);
|
|
AllStages[SF_Vertex] = InVertexShader;
|
|
|
|
for (uint32 FrequencyIndex = 0; FrequencyIndex < SF_NumStandardFrequencies; ++FrequencyIndex)
|
|
{
|
|
if (const FShaderType* ShaderType = AllStages[FrequencyIndex])
|
|
{
|
|
checkf(ShaderType->GetPermutationCount() == 1, TEXT("Shader '%s' has multiple shader permutations. Shader pipelines only support a single permutation."), ShaderType->GetName())
|
|
}
|
|
}
|
|
|
|
static uint32 TypeHashCounter = 0;
|
|
++TypeHashCounter;
|
|
HashIndex = TypeHashCounter;
|
|
|
|
GlobalListLink.LinkHead(GetTypeList());
|
|
GetNameToTypeMap().Add(TypeName, this);
|
|
|
|
// This will trigger if an IMPLEMENT_SHADER_TYPE was in a module not loaded before InitializeShaderTypes
|
|
// Shader types need to be implemented in modules that are loaded before that
|
|
checkf(!bInitialized, TEXT("Shader Pipeline was loaded after Engine init, use ELoadingPhase::PostConfigInit on your module to cause it to load earlier."));
|
|
}
|
|
|
|
FShaderPipelineType::~FShaderPipelineType()
|
|
{
|
|
GetNameToTypeMap().Remove(TypeName);
|
|
GlobalListLink.Unlink();
|
|
}
|
|
|
|
TMap<FName, FShaderPipelineType*>& FShaderPipelineType::GetNameToTypeMap()
|
|
{
|
|
static TMap<FName, FShaderPipelineType*>* GShaderPipelineNameToTypeMap = NULL;
|
|
if (!GShaderPipelineNameToTypeMap)
|
|
{
|
|
GShaderPipelineNameToTypeMap = new TMap<FName, FShaderPipelineType*>();
|
|
}
|
|
return *GShaderPipelineNameToTypeMap;
|
|
}
|
|
|
|
TLinkedList<FShaderPipelineType*>*& FShaderPipelineType::GetTypeList()
|
|
{
|
|
return GShaderPipelineList;
|
|
}
|
|
|
|
TArray<const FShaderPipelineType*> FShaderPipelineType::GetShaderPipelineTypesByFilename(const TCHAR* Filename)
|
|
{
|
|
TArray<const FShaderPipelineType*> PipelineTypes;
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next())
|
|
{
|
|
auto* PipelineType = *It;
|
|
for (auto* ShaderType : PipelineType->Stages)
|
|
{
|
|
if (FPlatformString::Strcmp(Filename, ShaderType->GetShaderFilename()) == 0)
|
|
{
|
|
PipelineTypes.AddUnique(PipelineType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return PipelineTypes;
|
|
}
|
|
|
|
void FShaderPipelineType::Initialize()
|
|
{
|
|
check(!bInitialized);
|
|
|
|
TSet<FName> UsedNames;
|
|
|
|
#if UE_BUILD_DEBUG
|
|
TArray<const FShaderPipelineType*> UniqueShaderPipelineTypes;
|
|
#endif
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next())
|
|
{
|
|
const auto* PipelineType = *It;
|
|
|
|
#if UE_BUILD_DEBUG
|
|
UniqueShaderPipelineTypes.Add(PipelineType);
|
|
#endif
|
|
|
|
// Validate stages
|
|
for (int32 Index = 0; Index < SF_NumFrequencies; ++Index)
|
|
{
|
|
check(!PipelineType->AllStages[Index] || PipelineType->AllStages[Index]->GetFrequency() == (EShaderFrequency)Index);
|
|
}
|
|
|
|
auto& Stages = PipelineType->GetStages();
|
|
|
|
// #todo-rco: Do we allow mix/match of global/mesh/material stages?
|
|
// Check all shaders are the same type, start from the top-most stage
|
|
const FGlobalShaderType* GlobalType = Stages[0]->GetGlobalShaderType();
|
|
const FMeshMaterialShaderType* MeshType = Stages[0]->GetMeshMaterialShaderType();
|
|
const FMaterialShaderType* MateriallType = Stages[0]->GetMaterialShaderType();
|
|
for (int32 Index = 1; Index < Stages.Num(); ++Index)
|
|
{
|
|
if (GlobalType)
|
|
{
|
|
checkf(Stages[Index]->GetGlobalShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name);
|
|
}
|
|
else if (MeshType)
|
|
{
|
|
checkf(Stages[Index]->GetMeshMaterialShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name);
|
|
}
|
|
else if (MateriallType)
|
|
{
|
|
checkf(Stages[Index]->GetMaterialShaderType(), TEXT("Invalid combination of Shader types on Pipeline %s"), PipelineType->Name);
|
|
}
|
|
}
|
|
|
|
FName PipelineName = PipelineType->GetFName();
|
|
checkf(!UsedNames.Contains(PipelineName), TEXT("Two Pipelines with the same name %s found!"), PipelineType->Name);
|
|
UsedNames.Add(PipelineName);
|
|
}
|
|
|
|
#if UE_BUILD_DEBUG
|
|
// Check for duplicated shader pipeline type names
|
|
UniqueShaderPipelineTypes.Sort([](const FShaderPipelineType& A, const FShaderPipelineType& B) { return (SIZE_T)&A < (SIZE_T)&B; });
|
|
for (int32 Index = 1; Index < UniqueShaderPipelineTypes.Num(); ++Index)
|
|
{
|
|
checkf(UniqueShaderPipelineTypes[Index - 1] != UniqueShaderPipelineTypes[Index], TEXT("Duplicated FShaderPipeline type name %s found, please rename one of them!"), UniqueShaderPipelineTypes[Index]->GetName());
|
|
}
|
|
#endif
|
|
|
|
bInitialized = true;
|
|
}
|
|
|
|
void FShaderPipelineType::Uninitialize()
|
|
{
|
|
check(bInitialized);
|
|
|
|
bInitialized = false;
|
|
}
|
|
|
|
void FShaderPipelineType::GetOutdatedTypes(TArray<FShaderType*>& OutdatedShaderTypes, TArray<const FShaderPipelineType*>& OutdatedShaderPipelineTypes, TArray<const FVertexFactoryType*>& OutdatedFactoryTypes)
|
|
{
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next())
|
|
{
|
|
const auto* PipelineType = *It;
|
|
auto& Stages = PipelineType->GetStages();
|
|
bool bOutdated = false;
|
|
for (const FShaderType* ShaderType : Stages)
|
|
{
|
|
bOutdated = ShaderType->GetOutdatedCurrentType(OutdatedShaderTypes, OutdatedFactoryTypes) || bOutdated;
|
|
}
|
|
|
|
if (bOutdated)
|
|
{
|
|
OutdatedShaderPipelineTypes.AddUnique(PipelineType);
|
|
}
|
|
}
|
|
|
|
for (int32 TypeIndex = 0; TypeIndex < OutdatedShaderPipelineTypes.Num(); TypeIndex++)
|
|
{
|
|
UE_LOG(LogShaders, Warning, TEXT(" Recompiling Pipeline %s"), OutdatedShaderPipelineTypes[TypeIndex]->GetName());
|
|
}
|
|
}
|
|
|
|
const FShaderPipelineType* FShaderPipelineType::GetShaderPipelineTypeByName(FName Name)
|
|
{
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator It(GetTypeList()); It; It.Next())
|
|
{
|
|
const FShaderPipelineType* Type = *It;
|
|
if (Name == Type->GetFName())
|
|
{
|
|
return Type;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const FSHAHash& FShaderPipelineType::GetSourceHash(EShaderPlatform ShaderPlatform) const
|
|
{
|
|
TArray<FString> Filenames;
|
|
for (const FShaderType* ShaderType : Stages)
|
|
{
|
|
Filenames.Add(ShaderType->GetShaderFilename());
|
|
}
|
|
return GetShaderFilesHash(Filenames, ShaderPlatform);
|
|
}
|
|
|
|
|
|
FShaderPipeline::FShaderPipeline(
|
|
const FShaderPipelineType* InPipelineType,
|
|
FShader* InVertexShader,
|
|
FShader* InHullShader,
|
|
FShader* InDomainShader,
|
|
FShader* InGeometryShader,
|
|
FShader* InPixelShader) :
|
|
PipelineType(InPipelineType),
|
|
VertexShader(InVertexShader),
|
|
HullShader(InHullShader),
|
|
DomainShader(InDomainShader),
|
|
GeometryShader(InGeometryShader),
|
|
PixelShader(InPixelShader)
|
|
{
|
|
check(InPipelineType);
|
|
Validate();
|
|
}
|
|
|
|
FShaderPipeline::FShaderPipeline(const FShaderPipelineType* InPipelineType, const TArray<FShader*>& InStages) :
|
|
PipelineType(InPipelineType),
|
|
VertexShader(nullptr),
|
|
HullShader(nullptr),
|
|
DomainShader(nullptr),
|
|
GeometryShader(nullptr),
|
|
PixelShader(nullptr)
|
|
{
|
|
check(InPipelineType);
|
|
for (FShader* Shader : InStages)
|
|
{
|
|
if (Shader)
|
|
{
|
|
switch (Shader->GetType()->GetFrequency())
|
|
{
|
|
case SF_Vertex:
|
|
check(!VertexShader);
|
|
VertexShader = Shader;
|
|
break;
|
|
case SF_Pixel:
|
|
check(!PixelShader);
|
|
PixelShader = Shader;
|
|
break;
|
|
case SF_Hull:
|
|
check(!HullShader);
|
|
HullShader = Shader;
|
|
break;
|
|
case SF_Domain:
|
|
check(!DomainShader);
|
|
DomainShader = Shader;
|
|
break;
|
|
case SF_Geometry:
|
|
check(!GeometryShader);
|
|
GeometryShader = Shader;
|
|
break;
|
|
default:
|
|
checkf(0, TEXT("Invalid stage %u found!"), (uint32)Shader->GetType()->GetFrequency());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Validate();
|
|
}
|
|
|
|
FShaderPipeline::FShaderPipeline(const FShaderPipelineType* InPipelineType, const TArray< TRefCountPtr<FShader> >& InStages) :
|
|
PipelineType(InPipelineType),
|
|
VertexShader(nullptr),
|
|
HullShader(nullptr),
|
|
DomainShader(nullptr),
|
|
GeometryShader(nullptr),
|
|
PixelShader(nullptr)
|
|
{
|
|
check(InPipelineType);
|
|
for (FShader* Shader : InStages)
|
|
{
|
|
if (Shader)
|
|
{
|
|
switch (Shader->GetType()->GetFrequency())
|
|
{
|
|
case SF_Vertex:
|
|
check(!VertexShader);
|
|
VertexShader = Shader;
|
|
break;
|
|
case SF_Pixel:
|
|
check(!PixelShader);
|
|
PixelShader = Shader;
|
|
break;
|
|
case SF_Hull:
|
|
check(!HullShader);
|
|
HullShader = Shader;
|
|
break;
|
|
case SF_Domain:
|
|
check(!DomainShader);
|
|
DomainShader = Shader;
|
|
break;
|
|
case SF_Geometry:
|
|
check(!GeometryShader);
|
|
GeometryShader = Shader;
|
|
break;
|
|
default:
|
|
checkf(0, TEXT("Invalid stage %u found!"), (uint32)Shader->GetType()->GetFrequency());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Validate();
|
|
}
|
|
|
|
FShaderPipeline::~FShaderPipeline()
|
|
{
|
|
// Manually set references to nullptr, helps debugging
|
|
VertexShader = nullptr;
|
|
HullShader = nullptr;
|
|
DomainShader = nullptr;
|
|
GeometryShader = nullptr;
|
|
PixelShader = nullptr;
|
|
}
|
|
|
|
void FShaderPipeline::Validate()
|
|
{
|
|
for (const FShaderType* Stage : PipelineType->GetStages())
|
|
{
|
|
switch (Stage->GetFrequency())
|
|
{
|
|
case SF_Vertex:
|
|
check(VertexShader && VertexShader->GetType() == Stage);
|
|
break;
|
|
case SF_Pixel:
|
|
check(PixelShader && PixelShader->GetType() == Stage);
|
|
break;
|
|
case SF_Hull:
|
|
check(HullShader && HullShader->GetType() == Stage);
|
|
break;
|
|
case SF_Domain:
|
|
check(DomainShader && DomainShader->GetType() == Stage);
|
|
break;
|
|
case SF_Geometry:
|
|
check(GeometryShader && GeometryShader->GetType() == Stage);
|
|
break;
|
|
default:
|
|
// Can never happen :)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FShaderPipeline::CookPipeline(FShaderPipeline* Pipeline)
|
|
{
|
|
#if WITH_EDITOR
|
|
FShaderCodeLibrary::AddShaderPipeline(Pipeline);
|
|
#endif
|
|
}
|
|
|
|
void DumpShaderStats(EShaderPlatform Platform, EShaderFrequency Frequency)
|
|
{
|
|
#if ALLOW_DEBUG_FILES
|
|
FDiagnosticTableViewer ShaderTypeViewer(*FDiagnosticTableViewer::GetUniqueTemporaryFilePath(TEXT("ShaderStats")));
|
|
|
|
// Iterate over all shader types and log stats.
|
|
int32 TotalShaderCount = 0;
|
|
int32 TotalTypeCount = 0;
|
|
int32 TotalInstructionCount = 0;
|
|
int32 TotalSize = 0;
|
|
int32 TotalPipelineCount = 0;
|
|
float TotalSizePerType = 0;
|
|
|
|
// Write a row of headings for the table's columns.
|
|
ShaderTypeViewer.AddColumn(TEXT("Type"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Instances"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Average instructions"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Size"));
|
|
ShaderTypeViewer.AddColumn(TEXT("AvgSizePerInstance"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Pipelines"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Shared Pipelines"));
|
|
ShaderTypeViewer.CycleRow();
|
|
|
|
for( TLinkedList<FShaderType*>::TIterator It(FShaderType::GetTypeList()); It; It.Next() )
|
|
{
|
|
const FShaderType* Type = *It;
|
|
if (Type->GetNumShaders())
|
|
{
|
|
// Calculate the average instruction count and total size of instances of this shader type.
|
|
float AverageNumInstructions = 0.0f;
|
|
int32 NumInitializedInstructions = 0;
|
|
int32 Size = 0;
|
|
int32 NumShaders = 0;
|
|
int32 NumPipelines = 0;
|
|
int32 NumSharedPipelines = 0;
|
|
for (TMap<FShaderId,FShader*>::TConstIterator ShaderIt(Type->ShaderIdMap);ShaderIt;++ShaderIt)
|
|
{
|
|
const FShader* Shader = ShaderIt.Value();
|
|
// Skip shaders that don't match frequency.
|
|
if( Shader->GetTarget().Frequency != Frequency && Frequency != SF_NumFrequencies )
|
|
{
|
|
continue;
|
|
}
|
|
// Skip shaders that don't match platform.
|
|
if( Shader->GetTarget().Platform != Platform && Platform != SP_NumPlatforms )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
NumInitializedInstructions += Shader->GetNumInstructions();
|
|
Size += Shader->GetCode().Num();
|
|
NumShaders++;
|
|
}
|
|
AverageNumInstructions = (float)NumInitializedInstructions / (float)Type->GetNumShaders();
|
|
|
|
for (TLinkedList<FShaderPipelineType*>::TConstIterator PipelineIt(FShaderPipelineType::GetTypeList()); PipelineIt; PipelineIt.Next())
|
|
{
|
|
const FShaderPipelineType* PipelineType = *PipelineIt;
|
|
bool bFound = false;
|
|
if (Frequency == SF_NumFrequencies)
|
|
{
|
|
if (PipelineType->GetShader(Type->GetFrequency()) == Type)
|
|
{
|
|
++NumPipelines;
|
|
bFound = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PipelineType->GetShader(Frequency) == Type)
|
|
{
|
|
++NumPipelines;
|
|
bFound = true;
|
|
}
|
|
}
|
|
|
|
if (!PipelineType->ShouldOptimizeUnusedOutputs(Platform) && bFound)
|
|
{
|
|
++NumSharedPipelines;
|
|
}
|
|
}
|
|
|
|
// Only add rows if there is a matching shader.
|
|
if( NumShaders )
|
|
{
|
|
// Write a row for the shader type.
|
|
ShaderTypeViewer.AddColumn(Type->GetName());
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"),NumShaders);
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),AverageNumInstructions);
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"),Size);
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),Size / (float)NumShaders);
|
|
ShaderTypeViewer.AddColumn(TEXT("%d"), NumPipelines);
|
|
ShaderTypeViewer.AddColumn(TEXT("%d"), NumSharedPipelines);
|
|
ShaderTypeViewer.CycleRow();
|
|
|
|
TotalShaderCount += NumShaders;
|
|
TotalPipelineCount += NumPipelines;
|
|
TotalInstructionCount += NumInitializedInstructions;
|
|
TotalTypeCount++;
|
|
TotalSize += Size;
|
|
TotalSizePerType += Size / (float)NumShaders;
|
|
}
|
|
}
|
|
}
|
|
|
|
// go through non shared pipelines
|
|
|
|
// Write a total row.
|
|
ShaderTypeViewer.AddColumn(TEXT("Total"));
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"),TotalShaderCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"),TotalInstructionCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"),TotalSize);
|
|
ShaderTypeViewer.AddColumn(TEXT("0"));
|
|
ShaderTypeViewer.AddColumn(TEXT("%u"), TotalPipelineCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("-"));
|
|
ShaderTypeViewer.CycleRow();
|
|
|
|
// Write an average row.
|
|
ShaderTypeViewer.AddColumn(TEXT("Average"));
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalShaderCount / (float)TotalTypeCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),(float)TotalInstructionCount / TotalShaderCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalSize / (float)TotalShaderCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("%.1f"),TotalSizePerType / TotalTypeCount);
|
|
ShaderTypeViewer.AddColumn(TEXT("-"));
|
|
ShaderTypeViewer.AddColumn(TEXT("-"));
|
|
ShaderTypeViewer.CycleRow();
|
|
#endif
|
|
}
|
|
|
|
void DumpShaderPipelineStats(EShaderPlatform Platform)
|
|
{
|
|
#if ALLOW_DEBUG_FILES
|
|
FDiagnosticTableViewer ShaderTypeViewer(*FDiagnosticTableViewer::GetUniqueTemporaryFilePath(TEXT("ShaderPipelineStats")));
|
|
|
|
int32 TotalNumPipelines = 0;
|
|
int32 TotalSize = 0;
|
|
float TotalSizePerType = 0;
|
|
|
|
// Write a row of headings for the table's columns.
|
|
ShaderTypeViewer.AddColumn(TEXT("Type"));
|
|
ShaderTypeViewer.AddColumn(TEXT("Shared/Unique"));
|
|
|
|
// Exclude compute
|
|
for (int32 Index = 0; Index < SF_NumFrequencies - 1; ++Index)
|
|
{
|
|
ShaderTypeViewer.AddColumn(GetShaderFrequencyString((EShaderFrequency)Index));
|
|
}
|
|
ShaderTypeViewer.CycleRow();
|
|
|
|
int32 TotalTypeCount = 0;
|
|
for (TLinkedList<FShaderPipelineType*>::TIterator It(FShaderPipelineType::GetTypeList()); It; It.Next())
|
|
{
|
|
const FShaderPipelineType* Type = *It;
|
|
|
|
// Write a row for the shader type.
|
|
ShaderTypeViewer.AddColumn(Type->GetName());
|
|
ShaderTypeViewer.AddColumn(Type->ShouldOptimizeUnusedOutputs(Platform) ? TEXT("U") : TEXT("S"));
|
|
|
|
for (int32 Index = 0; Index < SF_NumFrequencies - 1; ++Index)
|
|
{
|
|
const FShaderType* ShaderType = Type->GetShader((EShaderFrequency)Index);
|
|
ShaderTypeViewer.AddColumn(ShaderType ? ShaderType->GetName() : TEXT(""));
|
|
}
|
|
|
|
ShaderTypeViewer.CycleRow();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FShaderType* FindShaderTypeByName(FName ShaderTypeName)
|
|
{
|
|
FShaderType** FoundShader = FShaderType::GetNameToTypeMap().Find(ShaderTypeName);
|
|
if (FoundShader)
|
|
{
|
|
return *FoundShader;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void DispatchComputeShader(
|
|
FRHICommandList& RHICmdList,
|
|
FShader* Shader,
|
|
uint32 ThreadGroupCountX,
|
|
uint32 ThreadGroupCountY,
|
|
uint32 ThreadGroupCountZ)
|
|
{
|
|
RHICmdList.DispatchComputeShader(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ);
|
|
}
|
|
|
|
void DispatchComputeShader(
|
|
FRHIAsyncComputeCommandListImmediate& RHICmdList,
|
|
FShader* Shader,
|
|
uint32 ThreadGroupCountX,
|
|
uint32 ThreadGroupCountY,
|
|
uint32 ThreadGroupCountZ)
|
|
{
|
|
RHICmdList.DispatchComputeShader(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ);
|
|
}
|
|
|
|
void DispatchIndirectComputeShader(
|
|
FRHICommandList& RHICmdList,
|
|
FShader* Shader,
|
|
FRHIVertexBuffer* ArgumentBuffer,
|
|
uint32 ArgumentOffset)
|
|
{
|
|
RHICmdList.DispatchIndirectComputeShader(ArgumentBuffer, ArgumentOffset);
|
|
}
|
|
|
|
|
|
void ShaderMapAppendKeyString(EShaderPlatform Platform, FString& KeyString)
|
|
{
|
|
// Globals that should cause all shaders to recompile when changed must be appended to the key here
|
|
// Key should be kept as short as possible while being somewhat human readable for debugging
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("Compat.UseDXT5NormalMaps"));
|
|
KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_DXTN") : TEXT("_BC5N");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.ClearCoatNormal"));
|
|
KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_CCBN") : TEXT("_NoCCBN");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.IrisNormal"));
|
|
KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_Iris") : TEXT("_NoIris");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.CompileShadersForDevelopment"));
|
|
KeyString += (CVar && CVar->GetValueOnAnyThread() != 0) ? TEXT("_DEV") : TEXT("_NoDEV");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
|
|
const bool bValue = CVar ? CVar->GetValueOnAnyThread() != 0 : true;
|
|
KeyString += bValue ? TEXT("_SL") : TEXT("_NoSL");
|
|
}
|
|
|
|
{
|
|
KeyString += IsUsingBasePassVelocity(Platform) ? TEXT("_GV") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static const auto CVarInstancedStereo = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.InstancedStereo"));
|
|
static const auto CVarMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MultiView"));
|
|
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
|
|
static const auto CVarODSCapture = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.ODSCapture"));
|
|
|
|
const bool bIsInstancedStereo = (RHISupportsInstancedStereo(Platform) && (CVarInstancedStereo && CVarInstancedStereo->GetValueOnGameThread() != 0));
|
|
const bool bIsMultiView = (RHISupportsMultiView(Platform) && (CVarMultiView && CVarMultiView->GetValueOnGameThread() != 0));
|
|
|
|
const bool bIsAndroidGLES = RHISupportsMobileMultiView(Platform);
|
|
const bool bIsMobileMultiView = (bIsAndroidGLES && (CVarMobileMultiView && CVarMobileMultiView->GetValueOnGameThread() != 0));
|
|
|
|
const bool bIsODSCapture = CVarODSCapture && (CVarODSCapture->GetValueOnGameThread() != 0);
|
|
|
|
if (bIsInstancedStereo)
|
|
{
|
|
KeyString += TEXT("_VRIS");
|
|
|
|
if (bIsMultiView)
|
|
{
|
|
KeyString += TEXT("_MVIEW");
|
|
}
|
|
}
|
|
|
|
if (bIsMobileMultiView)
|
|
{
|
|
KeyString += TEXT("_MMVIEW");
|
|
}
|
|
|
|
if (bIsODSCapture)
|
|
{
|
|
KeyString += TEXT("_ODSC");
|
|
}
|
|
}
|
|
|
|
{
|
|
KeyString += IsUsingSelectiveBasePassOutputs(Platform) ? TEXT("_SO") : TEXT("");
|
|
}
|
|
|
|
{
|
|
KeyString += UsePreExposure(Platform) ? TEXT("_PreExp") : TEXT("");
|
|
}
|
|
|
|
{
|
|
KeyString += IsUsingDBuffers(Platform) ? TEXT("_DBuf") : TEXT("_NoDBuf");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AllowGlobalClipPlane"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_ClipP") : TEXT("");
|
|
}
|
|
|
|
{
|
|
KeyString += ShouldKeepShaderDebugInfo(Platform) ? TEXT("_NoStrip") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.Optimize"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("") : TEXT("_NoOpt");
|
|
}
|
|
|
|
{
|
|
// Always default to fast math unless specified
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FastMath"));
|
|
KeyString += (CVar && CVar->GetInt() == 0) ? TEXT("_NoFastMath") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.FlowControlMode"));
|
|
if (CVar)
|
|
{
|
|
switch(CVar->GetInt())
|
|
{
|
|
case 2:
|
|
KeyString += TEXT("_AvoidFlow");
|
|
break;
|
|
case 1:
|
|
KeyString += TEXT("_PreferFlow");
|
|
break;
|
|
case 0:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!AllowPixelDepthOffset(Platform))
|
|
{
|
|
KeyString += TEXT("_NoPDO");
|
|
}
|
|
|
|
if (IsD3DPlatform(Platform, false))
|
|
{
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.D3D.RemoveUnusedInterpolators"));
|
|
if (CVar && CVar->GetInt() != 0)
|
|
{
|
|
KeyString += TEXT("_UnInt");
|
|
}
|
|
}
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.D3D.ForceDXC"));
|
|
if (CVar && CVar->GetInt() != 0)
|
|
{
|
|
KeyString += TEXT("_DXC");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsMobilePlatform(Platform))
|
|
{
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.DisableVertexFog"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_NoVFog") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static const auto* CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shadow.CSM.MaxMobileCascades"));
|
|
KeyString += (CVar) ? FString::Printf(TEXT("MMC%d"), CVar->GetValueOnAnyThread()) : TEXT("");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.UseLegacyShadingModel"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_legshad") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.ForceFullPrecisionInPS"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_highp") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.AllowDitheredLODTransition"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_DLODT") : TEXT("");
|
|
}
|
|
|
|
if (IsOpenGLPlatform(Platform))
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("OpenGL.UseEmulatedUBs"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_NoUB") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.EnableMovableSpotlights"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_MSPTL") : TEXT("");
|
|
}
|
|
|
|
{
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.UseHWsRGBEncoding"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_HWsRGB") : TEXT("");
|
|
}
|
|
|
|
{
|
|
// make it per shader platform ?
|
|
static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.SupportGPUScene"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_MobGPUSc") : TEXT("");
|
|
}
|
|
|
|
}
|
|
|
|
if (Platform == SP_PS4)
|
|
{
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4MixedModeShaderDebugInfo"));
|
|
if (CVar && CVar->GetValueOnAnyThread() != 0)
|
|
{
|
|
KeyString += TEXT("_MMDBG");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4ShaderSDBMode"));
|
|
switch (CVar ? CVar->GetValueOnAnyThread() : 0)
|
|
{
|
|
case 1: KeyString += TEXT("_SDB1"); break;
|
|
case 2: KeyString += TEXT("_SDB2"); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PS4UseTTrace"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
KeyString += FString::Printf(TEXT("TT%d"), CVar->GetValueOnAnyThread());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Encode the Metal standard into the shader compile options so that they recompile if the settings change.
|
|
if (IsMetalPlatform(Platform))
|
|
{
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.ZeroInitialise"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_ZeroInit") : TEXT("");
|
|
}
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.BoundsChecking"));
|
|
KeyString += (CVar && CVar->GetInt() != 0) ? TEXT("_BoundsChecking") : TEXT("");
|
|
}
|
|
{
|
|
KeyString += RHISupportsManualVertexFetch(Platform) ? TEXT("_MVF_") : TEXT("");
|
|
}
|
|
|
|
uint32 ShaderVersion = RHIGetShaderLanguageVersion(Platform);
|
|
KeyString += FString::Printf(TEXT("_MTLSTD%u_"), ShaderVersion);
|
|
|
|
bool bAllowFastIntrinsics = false;
|
|
bool bEnableMathOptimisations = true;
|
|
bool bForceFloats = false;
|
|
if (IsPCPlatform(Platform))
|
|
{
|
|
GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("UseFastIntrinsics"), bAllowFastIntrinsics, GEngineIni);
|
|
GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("EnableMathOptimisations"), bEnableMathOptimisations, GEngineIni);
|
|
GConfig->GetBool(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("ForceFloats"), bForceFloats, GEngineIni);
|
|
}
|
|
else
|
|
{
|
|
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("UseFastIntrinsics"), bAllowFastIntrinsics, GEngineIni);
|
|
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("EnableMathOptimisations"), bEnableMathOptimisations, GEngineIni);
|
|
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("ForceFloats"), bForceFloats, GEngineIni);
|
|
}
|
|
|
|
if (bAllowFastIntrinsics)
|
|
{
|
|
KeyString += TEXT("_MTLSL_FastIntrin");
|
|
}
|
|
|
|
// Same as console-variable above, but that's global and this is per-platform, per-project
|
|
if (!bEnableMathOptimisations)
|
|
{
|
|
KeyString += TEXT("_NoFastMath");
|
|
}
|
|
|
|
if (bForceFloats)
|
|
{
|
|
KeyString += TEXT("_FP32");
|
|
}
|
|
|
|
// Shaders built for archiving - for Metal that requires compiling the code in a different way so that we can strip it later
|
|
bool bArchive = false;
|
|
GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("bSharedMaterialNativeLibraries"), bArchive, GGameIni);
|
|
if (bArchive)
|
|
{
|
|
KeyString += TEXT("_ARCHIVE");
|
|
}
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Metal.ForceDXC"));
|
|
if (CVar && CVar->GetInt() != 0)
|
|
{
|
|
KeyString += TEXT("_DXC");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4))
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.StencilForLODDither"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
KeyString += TEXT("_SD");
|
|
}
|
|
}
|
|
|
|
{
|
|
bool bForwardShading = false;
|
|
ITargetPlatform* TargetPlatform = GetTargetPlatformManager()->FindTargetPlatform(ShaderPlatformToPlatformName(Platform).ToString());
|
|
if (TargetPlatform)
|
|
{
|
|
// if there is a specific target platform that matches our shader platform, use that to drive forward shading
|
|
bForwardShading = TargetPlatform->UsesForwardShading();
|
|
}
|
|
else
|
|
{
|
|
// shader platform doesn't match a specific target platform, use cvar setting for forward shading
|
|
static IConsoleVariable* CVarForwardShadingLocal = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForwardShading"));
|
|
bForwardShading = CVarForwardShadingLocal ? (CVarForwardShadingLocal->GetInt() != 0) : false;
|
|
}
|
|
|
|
if (bForwardShading)
|
|
{
|
|
KeyString += TEXT("_FS");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PostProcessing.PropagateAlpha"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
if (CVar->GetValueOnAnyThread() == 2)
|
|
{
|
|
KeyString += TEXT("_SA2");
|
|
}
|
|
else
|
|
{
|
|
KeyString += TEXT("_SA");
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VertexFoggingForOpaque"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
KeyString += TEXT("_VFO");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.EarlyZPassOnlyMaterialMasking"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
KeyString += TEXT("_EZPMM");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.EightBit"));
|
|
if (CVar && CVar->GetValueOnAnyThread() > 0)
|
|
{
|
|
KeyString += TEXT("_8u");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GPUSkin.Limit2BoneInfluences"));
|
|
if (CVar && CVar->GetValueOnAnyThread() != 0)
|
|
{
|
|
KeyString += TEXT("_2bi");
|
|
}
|
|
}
|
|
{
|
|
if(UseGPUScene(Platform, GetMaxSupportedFeatureLevel(Platform)))
|
|
{
|
|
KeyString += TEXT("_gs1");
|
|
}
|
|
else
|
|
{
|
|
KeyString += TEXT("_gs0");
|
|
}
|
|
}
|
|
|
|
{
|
|
static const auto CVarVirtualTextureLightmaps = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VirtualTexturedLightmaps"));
|
|
const bool VTLightmaps = CVarVirtualTextureLightmaps && CVarVirtualTextureLightmaps->GetValueOnAnyThread() != 0;
|
|
|
|
static const auto CVarVirtualTexture = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VirtualTextures"));
|
|
const bool VTTextures = CVarVirtualTexture && CVarVirtualTexture->GetValueOnAnyThread() != 0;
|
|
|
|
static const auto CVarVTFactor = IConsoleManager::Get().FindConsoleVariable(TEXT("r.vt.FeedbackFactor")); check(CVarVTFactor);
|
|
const int32 VTFeedbackFactor = CVarVTFactor->GetInt();
|
|
|
|
|
|
ITargetPlatformManagerModule* TPM = GetTargetPlatformManager();
|
|
check(TPM);
|
|
auto TargetPlatform = TPM->GetRunningTargetPlatform();
|
|
check(TargetPlatform);
|
|
const bool VTSupported = TargetPlatform->SupportsFeature(ETargetPlatformFeatures::VirtualTextureStreaming);
|
|
|
|
auto tt = FString::Printf(TEXT("_VT-%d-%d-%d-%d"), VTLightmaps, VTTextures, VTFeedbackFactor, VTSupported);
|
|
KeyString += tt;
|
|
}
|
|
}
|