Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

2568 lines
104 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NiagaraCompiler.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "EdGraphSchema_Niagara.h"
#include "EdGraphUtilities.h"
#include "Interfaces/IShaderFormat.h"
#include "INiagaraEditorTypeUtilities.h"
#include "Misc/FileHelper.h"
#include "Misc/PathViews.h"
#include "Modules/ModuleManager.h"
#include "NiagaraCompilationPrivate.h"
#include "NiagaraComponent.h"
#include "NiagaraDataInterface.h"
#include "NiagaraEditorModule.h"
#include "NiagaraEditorModule.h"
#include "NiagaraEditorUtilities.h"
#include "NiagaraFunctionLibrary.h"
#include "NiagaraGraph.h"
#include "NiagaraHlslTranslator.h"
#include "NiagaraNodeEmitter.h"
#include "NiagaraNodeFunctionCall.h"
#include "NiagaraNodeInput.h"
#include "NiagaraNodeOutput.h"
#include "NiagaraPrecompileContainer.h"
#include "NiagaraScript.h"
#include "NiagaraScriptSource.h"
#include "NiagaraShader.h"
#include "NiagaraSimulationStageBase.h"
#include "NiagaraSystem.h"
#include "NiagaraTrace.h"
#include "Serialization/MemoryReader.h"
#include "ShaderCompiler.h"
#include "ShaderCore.h"
#include "VectorVMTestCompile.h"
#define LOCTEXT_NAMESPACE "NiagaraCompiler"
DEFINE_LOG_CATEGORY_STATIC(LogNiagaraCompiler, All, All);
DECLARE_CYCLE_STAT(TEXT("Niagara - Module - CompileScript"), STAT_NiagaraEditor_Module_CompileScript, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - HlslCompiler - CompileScript"), STAT_NiagaraEditor_HlslCompiler_CompileScript, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - HlslCompiler - CompileShader_VectorVM"), STAT_NiagaraEditor_HlslCompiler_CompileShader_VectorVM, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - Module - CompileShader_VectorVMSucceeded"), STAT_NiagaraEditor_HlslCompiler_CompileShader_VectorVMSucceeded, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptSource - PreCompile"), STAT_NiagaraEditor_ScriptSource_PreCompile, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptSource - PreCompileDuplicate"), STAT_NiagaraEditor_ScriptSource_PreCompileDuplicate, STATGROUP_NiagaraEditor);
DECLARE_CYCLE_STAT(TEXT("Niagara - HlslCompiler - TestCompileShader_VectorVM"), STAT_NiagaraEditor_HlslCompiler_TestCompileShader_VectorVM, STATGROUP_NiagaraEditor);
static int32 GbForceNiagaraTranslatorSingleThreaded = 1;
static FAutoConsoleVariableRef CVarForceNiagaraTranslatorSingleThreaded(
TEXT("fx.ForceNiagaraTranslatorSingleThreaded"),
GbForceNiagaraTranslatorSingleThreaded,
TEXT("If > 0 all translation will occur one at a time, useful for debugging. \n"),
ECVF_Default
);
// Enable this to log out generated HLSL for debugging purposes.
static int32 GbForceNiagaraTranslatorDump = 0;
static FAutoConsoleVariableRef CVarForceNiagaraTranslatorDump(
TEXT("fx.ForceNiagaraTranslatorDump"),
GbForceNiagaraTranslatorDump,
TEXT("If > 0 all translation generated HLSL will be dumped \n"),
ECVF_Default
);
static int32 GbForceNiagaraVMBinaryDump = 0;
static FAutoConsoleVariableRef CVarForceNiagaraVMBinaryDump(
TEXT("fx.ForceNiagaraVMBinaryDump"),
GbForceNiagaraVMBinaryDump,
TEXT("If > 0 all translation generated binary text will be dumped \n"),
ECVF_Default
);
static int32 GbForceNiagaraCacheDump = 0;
static FAutoConsoleVariableRef CVarForceNiagaraCacheDump(
TEXT("fx.ForceNiagaraCacheDump"),
GbForceNiagaraCacheDump,
TEXT("If > 0 all cached graph traversal data will be dumped \n"),
ECVF_Default
);
static int32 GNiagaraEnablePrecompilerNamespaceFixup = 0;
static FAutoConsoleVariableRef CVarNiagaraEnablePrecompilerNamespaceFixup(
TEXT("fx.NiagaraEnablePrecompilerNamespaceFixup"),
GNiagaraEnablePrecompilerNamespaceFixup,
TEXT("Enable a precompiler stage to discover parameter name matches and convert matched parameter hlsl name tokens to appropriate namespaces. \n"),
ECVF_Default
);
namespace NiagaraCompileRequestHelper
{
using FDebugGroupNameBuilder = TStringBuilder<512>;
static void BuildScriptDebugGroupName(const FNiagaraCompileRequestData* InCompileRequest, const FNiagaraCompileOptions& InCompileOptions, FDebugGroupNameBuilder& NameBuilder)
{
FPathViews::Append(NameBuilder, InCompileRequest->SourceName);
FPathViews::Append(NameBuilder, InCompileRequest->EmitterUniqueName);
FPathViews::Append(NameBuilder, InCompileRequest->ENiagaraScriptUsageEnum->GetNameStringByValue((int64)InCompileOptions.TargetUsage));
if (InCompileOptions.TargetUsageId.IsValid())
{
NameBuilder << TEXT("_");
InCompileOptions.TargetUsageId.AppendString(NameBuilder, EGuidFormats::Digits);
}
}
};
static FCriticalSection TranslationCritSec;
void DumpHLSLText(const FString& SourceCode, const FString& DebugName)
{
FScopeLock Lock(&TranslationCritSec);
FNiagaraUtilities::DumpHLSLText(SourceCode, DebugName);
}
template< class T >
T* PrecompileDuplicateObject(T const* SourceObject, UObject* Outer, const FName Name = NAME_None)
{
//double StartTime = FPlatformTime::Seconds();
T* DupeObj = DuplicateObject<T>(SourceObject, Outer, Name);
//float DeltaTime = (float)(FPlatformTime::Seconds() - StartTime);
//if (DeltaTime > 0.01f)
//{
// UE_LOG(LogNiagaraEditor, Log, TEXT("\tPrecompile Duplicate %s took %f sec"), *SourceObject->GetPathName(), DeltaTime);
//}
return DupeObj;
}
void FNiagaraCompileRequestDuplicateData::DuplicateReferencedGraphs(UNiagaraGraph* InSrcGraph, UNiagaraGraph* InDupeGraph, ENiagaraScriptUsage InUsage, FCompileConstantResolver ConstantResolver, TMap<UNiagaraNodeFunctionCall*, ENiagaraScriptUsage> FunctionsWithUsage)
{
if (!InDupeGraph || !InSrcGraph)
{
return;
}
TArray<FDuplicatedGraphData>& DuplicatedGraphDataArray = SharedSourceGraphToDuplicatedGraphsMap->FindOrAdd(InSrcGraph);
FDuplicatedGraphData& DuplicatedGraphData = DuplicatedGraphDataArray.AddDefaulted_GetRef();
DuplicatedGraphData.ClonedScript = nullptr;
DuplicatedGraphData.ClonedGraph = InDupeGraph;
DuplicatedGraphData.Usage = InUsage;
DuplicatedGraphData.bHasNumericParameters = false;
bool bStandaloneScript = false;
TArray<UNiagaraNodeOutput*> OutputNodes;
InDupeGraph->FindOutputNodes(OutputNodes);
if (OutputNodes.Num() == 1 && UNiagaraScript::IsStandaloneScript(OutputNodes[0]->GetUsage()))
{
bStandaloneScript = true;
}
FNiagaraEditorUtilities::ResolveNumerics(InDupeGraph, bStandaloneScript, ChangedFromNumericVars);
DuplicateReferencedGraphsRecursive(InDupeGraph, ConstantResolver, FunctionsWithUsage);
}
void FNiagaraCompileRequestDuplicateData::DuplicateReferencedGraphsRecursive(UNiagaraGraph* InGraph, const FCompileConstantResolver& ConstantResolver, TMap<UNiagaraNodeFunctionCall*, ENiagaraScriptUsage> FunctionsWithUsage)
{
if (!InGraph)
{
return;
}
TArray<UNiagaraNode*> Nodes;
InGraph->GetNodesOfClass(Nodes);
const UEdGraphSchema_Niagara* Schema = GetDefault<UEdGraphSchema_Niagara>();
for (UEdGraphNode* Node : Nodes)
{
if (UNiagaraNode* InNode = Cast<UNiagaraNode>(Node))
{
UNiagaraNodeInput* InputNode = Cast<UNiagaraNodeInput>(InNode);
if (InputNode)
{
if (InputNode->Input.IsDataInterface())
{
UNiagaraDataInterface* DataInterface = InputNode->GetDataInterface();
bool bIsParameterMapDataInterface = false;
FName DIName = FNiagaraHlslTranslator::GetDataInterfaceName(InputNode->Input.GetName(), EmitterUniqueName, bIsParameterMapDataInterface);
UNiagaraDataInterface* Dupe = PrecompileDuplicateObject<UNiagaraDataInterface>(DataInterface, GetTransientPackage());
SharedNameToDuplicatedDataInterfaceMap->Add(DIName, Dupe);
}
continue;
}
UNiagaraNodeFunctionCall* FunctionCallNode = Cast<UNiagaraNodeFunctionCall>(InNode);
if (FunctionCallNode)
{
UNiagaraScript* FunctionScript = FunctionCallNode->FunctionScript;
bool bFunctionCallOwnsScript = FunctionScript != nullptr && FunctionScript->GetOuter() == FunctionCallNode;
if(FunctionCallNode->HasValidScriptAndGraph() && bFunctionCallOwnsScript == false)
{
// If the function call doesn't already own the script it's pointing at then script needs to be duplicated since it's a referenced
// script and will need to be preprocessed.
ENiagaraScriptUsage ScriptUsage = FunctionCallNode->GetCalledUsage();
FCompileConstantResolver FunctionConstantResolver = ConstantResolver;
if (FunctionsWithUsage.Contains(FunctionCallNode))
{
FunctionConstantResolver = ConstantResolver.WithUsage(FunctionsWithUsage[FunctionCallNode]);
}
UNiagaraGraph* FunctionGraph = FunctionCallNode->GetCalledGraph();
bool bHasNumericParams = FunctionGraph->HasNumericParameters();
if (bHasNumericParams || SharedSourceGraphToDuplicatedGraphsMap->Contains(FunctionGraph) == false)
{
// Duplicate the script, the source, and graph
UNiagaraScript* DupeScript = FunctionScript->CreateCompilationCopy();
TArray<ENiagaraScriptUsage> CompileUsages = { DupeScript->GetUsage() };
UNiagaraScriptSource* DupeScriptSource = CastChecked<UNiagaraScriptSource>(DupeScript->GetSource(FunctionCallNode->SelectedScriptVersion))->CreateCompilationCopy(CompileUsages);
TrackedScriptSourceCopies.Add(DupeScriptSource);
UNiagaraGraph* DupeGraph = DupeScriptSource->NodeGraph;
DupeScript->SetSource(DupeScriptSource, FunctionCallNode->SelectedScriptVersion);
// Do any preprocessing necessary
FEdGraphUtilities::MergeChildrenGraphsIn(DupeGraph, DupeGraph, /*bRequireSchemaMatch=*/ true);
FPinCollectorArray CallOutputs;
FPinCollectorArray CallInputs;
InNode->GetOutputPins(CallOutputs);
InNode->GetInputPins(CallInputs);
FNiagaraEditorUtilities::PreprocessFunctionGraph(Schema, DupeGraph, CallInputs, CallOutputs, ScriptUsage, FunctionConstantResolver);
// Record the data for this duplicate.
TArray<FDuplicatedGraphData>& DuplicatedGraphDataArray = SharedSourceGraphToDuplicatedGraphsMap->FindOrAdd(FunctionGraph);
FDuplicatedGraphData& DuplicatedGraphData = DuplicatedGraphDataArray.AddDefaulted_GetRef();
DuplicatedGraphData.ClonedScript = DupeScript;
DuplicatedGraphData.ClonedGraph = DupeGraph;
DuplicatedGraphData.CallInputs = CallInputs;
DuplicatedGraphData.CallOutputs = CallOutputs;
DuplicatedGraphData.Usage = ScriptUsage;
DuplicatedGraphData.bHasNumericParameters = bHasNumericParams;
// Assign the copied script and process any child scripts.
FunctionCallNode->FunctionScript = DupeScript;
DuplicateReferencedGraphsRecursive(DupeGraph, FunctionConstantResolver, FunctionsWithUsage);
}
else
{
// This graph was already processed and doesn't need per-call duplication so use the previous copy.
TArray<FDuplicatedGraphData>* DuplicatedGraphDataArray = SharedSourceGraphToDuplicatedGraphsMap->Find(FunctionGraph);
check(DuplicatedGraphDataArray != nullptr && DuplicatedGraphDataArray->Num() != 0);
FunctionCallNode->FunctionScript = (*DuplicatedGraphDataArray)[0].ClonedScript;
}
}
}
UNiagaraNodeEmitter* EmitterNode = Cast<UNiagaraNodeEmitter>(InNode);
if (EmitterNode)
{
for (TSharedPtr<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe>& Ptr : EmitterData)
{
if (Ptr->EmitterUniqueName == EmitterNode->GetEmitterUniqueName())
{
EmitterNode->SyncEnabledState(); // Just to be safe, sync here while we likely still have the handle source.
EmitterNode->SetOwnerSystem(nullptr);
EmitterNode->SetCachedVariablesForCompilation(*Ptr->EmitterUniqueName, Ptr->EmitterID, Ptr->NodeGraphDeepCopy.Get(), Ptr->SourceDeepCopy.Get());
}
}
}
}
}
}
const TMap<FName, UNiagaraDataInterface*>& FNiagaraCompileRequestDuplicateData::GetObjectNameMap()
{
return *SharedNameToDuplicatedDataInterfaceMap.Get();
}
const UNiagaraScriptSourceBase* FNiagaraCompileRequestDuplicateData::GetScriptSource() const
{
return SourceDeepCopy.Get();
}
UNiagaraDataInterface* FNiagaraCompileRequestDuplicateData::GetDuplicatedDataInterfaceCDOForClass(UClass* Class) const
{
if (SharedDataInterfaceClassToDuplicatedCDOMap.IsValid())
{
UNiagaraDataInterface*const* DuplicatedCDOPtr = SharedDataInterfaceClassToDuplicatedCDOMap->Find(Class);
if (DuplicatedCDOPtr != nullptr)
{
return *DuplicatedCDOPtr;
}
}
return nullptr;
}
void FNiagaraCompileRequestData::SortOutputNodesByDependencies(TArray<class UNiagaraNodeOutput*>& NodesToSort, const TArray<class UNiagaraSimulationStageBase*>* SimStages)
{
if (!SimStages)
return;
TArray<class UNiagaraNodeOutput*> NewArray;
NewArray.Reserve(NodesToSort.Num());
// First gather up the non-simstage items
bool bFoundAnySimStages = false;
for (class UNiagaraNodeOutput* OutputNode : NodesToSort)
{
// Add any non sim stage entries back to the array in the order of encounter
if (OutputNode->GetUsage() != ENiagaraScriptUsage::ParticleSimulationStageScript)
{
NewArray.Emplace(OutputNode);
}
else
{
bFoundAnySimStages = true;
}
}
// No Sim stages, no problem! Just return
if (!bFoundAnySimStages)
{
return;
}
ensure(SimStages->Num() == (NodesToSort.Num() - NewArray.Num()));
// Add any sim stage entries back to the array in the order of encounter in the SimStage entry list from the Emitter (Handles reordering)
for (const UNiagaraSimulationStageBase* Stage : *SimStages)
{
if (Stage && Stage->Script)
{
const FGuid & StageId = Stage->Script->GetUsageId();
for (class UNiagaraNodeOutput* OutputNode : NodesToSort)
{
if (OutputNode->GetUsage() == ENiagaraScriptUsage::ParticleSimulationStageScript && OutputNode->GetUsageId() == StageId)
{
NewArray.Emplace(OutputNode);
break;
}
}
}
}
ensure(NodesToSort.Num() == NewArray.Num());
// Copy out final results
NodesToSort = NewArray;
}
FName FNiagaraCompileRequestData::ResolveEmitterAlias(FName VariableName) const
{
return FNiagaraParameterUtilities::ResolveEmitterAlias(VariableName, EmitterUniqueName);
}
bool FNiagaraCompileRequestDuplicateData::IsDuplicateDataFor(UNiagaraSystem* InSystem, UNiagaraEmitter* InEmitter, UNiagaraScript* InScript) const
{
return OwningSystem.Get() == InSystem && OwningEmitter.Get() == InEmitter && ValidUsages.Contains(InScript->GetUsage());
}
void FNiagaraCompileRequestDuplicateData::GetDuplicatedObjects(TArray<UObject*>& Objects)
{
Objects.Add(SourceDeepCopy.Get());
Objects.Add(NodeGraphDeepCopy.Get());
if (SharedNameToDuplicatedDataInterfaceMap.IsValid())
{
TArray<UNiagaraDataInterface*> DIs;
SharedNameToDuplicatedDataInterfaceMap->GenerateValueArray(DIs);
for (UNiagaraDataInterface* DI : DIs)
{
Objects.Add(DI);
}
}
if (SharedDataInterfaceClassToDuplicatedCDOMap.IsValid())
{
auto Iter = SharedDataInterfaceClassToDuplicatedCDOMap->CreateIterator();
while (Iter)
{
Objects.Add(Iter.Value());
++Iter;
}
}
if (SharedSourceGraphToDuplicatedGraphsMap.IsValid())
{
auto Iter = SharedSourceGraphToDuplicatedGraphsMap->CreateIterator();
while (Iter)
{
for (int32 i = 0; i < Iter.Value().Num(); i++)
{
Objects.Add(Iter.Value()[i].ClonedScript);
Objects.Add(Iter.Value()[i].ClonedGraph);
}
++Iter;
}
}
}
void FNiagaraCompileRequestData::GatherPreCompiledVariables(const FString& InNamespaceFilter, TArray<FNiagaraVariable>& OutVars) const
{
if (InNamespaceFilter.Len() == 0)
{
OutVars.Append(EncounteredVariables);
}
else
{
for (const FNiagaraVariable& EncounteredVariable : EncounteredVariables)
{
if (FNiagaraParameterUtilities::IsInNamespace(EncounteredVariable, InNamespaceFilter))
{
FNiagaraVariable NewVar = EncounteredVariable;
if (NewVar.IsDataAllocated() == false && !NewVar.IsDataInterface() && !NewVar.IsUObject())
{
FNiagaraEditorUtilities::ResetVariableToDefaultValue(NewVar);
}
OutVars.AddUnique(NewVar);
}
}
}
}
void FNiagaraCompileRequestDuplicateData::DeepCopyGraphs(UNiagaraScriptSource* ScriptSource, ENiagaraScriptUsage InUsage, FCompileConstantResolver ConstantResolver)
{
// Clone the source graph so we can modify it as needed; merging in the child graphs
SourceDeepCopy = ScriptSource->CreateCompilationCopy(ValidUsages);
NodeGraphDeepCopy = SourceDeepCopy->NodeGraph;
FEdGraphUtilities::MergeChildrenGraphsIn(NodeGraphDeepCopy.Get(), NodeGraphDeepCopy.Get(), /*bRequireSchemaMatch=*/ true);
DuplicateReferencedGraphs(ScriptSource->NodeGraph, NodeGraphDeepCopy.Get(), InUsage, ConstantResolver);
}
void FNiagaraCompileRequestDuplicateData::DeepCopyGraphs(const FVersionedNiagaraEmitter& Emitter)
{
UNiagaraScriptSource* ScriptSource = CastChecked<UNiagaraScriptSource>(Emitter.GetEmitterData()->GraphSource);
SourceDeepCopy = ScriptSource->CreateCompilationCopy(ValidUsages);
NodeGraphDeepCopy = SourceDeepCopy->NodeGraph;
FEdGraphUtilities::MergeChildrenGraphsIn(NodeGraphDeepCopy.Get(), NodeGraphDeepCopy.Get(), /*bRequireSchemaMatch=*/ true);
TMap<UNiagaraNodeFunctionCall*, ENiagaraScriptUsage> FunctionsWithUsage;
TArray<UNiagaraNodeOutput*> OutputNodes;
NodeGraphDeepCopy->GetNodesOfClass(OutputNodes);
for (UNiagaraNodeOutput* OutputNode : OutputNodes)
{
TArray<UNiagaraNode*> TraversedNodes;
NodeGraphDeepCopy->BuildTraversal(TraversedNodes, OutputNode);
for (UNiagaraNode* TraversedNode : TraversedNodes)
{
UNiagaraNodeFunctionCall* FunctionCallNode = Cast<UNiagaraNodeFunctionCall>(TraversedNode);
if (FunctionCallNode != nullptr)
{
FunctionsWithUsage.Add(FunctionCallNode, OutputNode->GetUsage());
}
}
}
FCompileConstantResolver ConstantResolver(Emitter, ENiagaraScriptUsage::EmitterSpawnScript);
DuplicateReferencedGraphs(ScriptSource->NodeGraph, NodeGraphDeepCopy.Get(), ENiagaraScriptUsage::EmitterSpawnScript, ConstantResolver, FunctionsWithUsage);
}
void FNiagaraCompileRequestData::AddRapidIterationParameters(const FNiagaraParameterStore& InParamStore, FCompileConstantResolver InResolver)
{
TArray<FNiagaraVariable> StoreParams;
InParamStore.GetParameters(StoreParams);
for (int32 i = 0; i < StoreParams.Num(); i++)
{
// Only support POD data...
if (StoreParams[i].IsDataInterface() || StoreParams[i].IsUObject())
{
continue;
}
if (InResolver.ResolveConstant(StoreParams[i]))
{
continue;
}
// Check to see if we already have this RI var...
int32 OurFoundIdx = INDEX_NONE;
for (int32 OurIdx = 0; OurIdx < RapidIterationParams.Num(); OurIdx++)
{
if (RapidIterationParams[OurIdx].GetType() == StoreParams[i].GetType() && RapidIterationParams[OurIdx].GetName() == StoreParams[i].GetName())
{
OurFoundIdx = OurIdx;
break;
}
}
// If we don't already have it, add it with the up-to-date value.
if (OurFoundIdx == INDEX_NONE)
{
// In parameter stores, the data isn't always up-to-date in the variable, so make sure to get the most up-to-date data before passing in.
const int32* Index = InParamStore.FindParameterOffset(StoreParams[i]);
if (Index != nullptr)
{
StoreParams[i].SetData(InParamStore.GetParameterData(*Index, StoreParams[i].GetType())); // This will memcopy the data in.
RapidIterationParams.Add(StoreParams[i]);
}
}
else
{
FNiagaraVariable ExistingVar = RapidIterationParams[OurFoundIdx];
const int32* Index = InParamStore.FindParameterOffset(StoreParams[i]);
if (Index != nullptr)
{
StoreParams[i].SetData(InParamStore.GetParameterData(*Index, StoreParams[i].GetType())); // This will memcopy the data in.
if (StoreParams[i] != ExistingVar)
{
UE_LOG(LogNiagaraEditor, Display, TEXT("Mismatch in values for Rapid iteration param: %s vs %s"), *StoreParams[i].ToString(), *ExistingVar.ToString());
}
}
}
}
}
void FNiagaraCompileRequestDuplicateData::ReleaseCompilationCopies()
{
// clean up graph copies
if (SharedSourceGraphToDuplicatedGraphsMap.IsValid())
{
for (const TPair<const UNiagaraGraph*, TArray<FDuplicatedGraphData>>& SourceGraphToDuplicatedGraphs : *(SharedSourceGraphToDuplicatedGraphsMap.Get()))
{
for (const FDuplicatedGraphData& DuplicatedGraphData : SourceGraphToDuplicatedGraphs.Value)
{
DuplicatedGraphData.ClonedGraph->ReleaseCompilationCopy();
}
}
SharedSourceGraphToDuplicatedGraphsMap->Empty();
}
// clean up script sources
for (TWeakObjectPtr<UNiagaraScriptSource> Source : TrackedScriptSourceCopies)
{
if (Source.IsValid())
{
Source->ReleaseCompilationCopy();
}
}
TrackedScriptSourceCopies.Empty();
if (SourceDeepCopy.IsValid())
{
SourceDeepCopy->ReleaseCompilationCopy();
}
SourceDeepCopy = nullptr;
// clean up emitter data
for (TSharedPtr<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe> EmitterRequest : EmitterData)
{
EmitterRequest->ReleaseCompilationCopies();
}
}
void FNiagaraCompileRequestData::CompareAgainst(FNiagaraGraphCachedBuiltHistory* InCachedDataBase)
{
if (InCachedDataBase)
{
bool bDumpVars = false;
if (StaticVariables.Num() != InCachedDataBase->StaticVariables.Num())
{
UE_LOG(LogNiagaraEditor, Warning, TEXT("FNiagaraCompileRequestData::CompareAgainst> StaticVariables.Num() != InCachedDataBase->StaticVariables.Num()"));
bDumpVars = true;
}
for (const FNiagaraVariable& Var : StaticVariables)
{
bool bFound = false;
for (const FNiagaraVariable& OtherVar : InCachedDataBase->StaticVariables)
{
if (OtherVar == Var)
{
bFound = true;
break;
}
}
if (!bFound)
{
UE_LOG(LogNiagaraEditor, Warning, TEXT("FNiagaraCompileRequestData::CompareAgainst> Could not find %s"), *Var.ToString());
bDumpVars = true;
}
}
if (bDumpVars)
{
for (const FNiagaraVariable& Var : InCachedDataBase->StaticVariables)
{
UE_LOG(LogNiagaraEditor, Warning, TEXT("%s"), *Var.ToString());
}
}
}
}
void FNiagaraCompileRequestData::FinishPrecompile(const TArray<FNiagaraVariable>& EncounterableVariables, const TArray<FNiagaraVariable>& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray<ENiagaraScriptUsage>& UsagesToProcess, const TArray<class UNiagaraSimulationStageBase*>* SimStages, const TArray<FString> EmitterNames)
{
FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked<FNiagaraEditorModule>("NiagaraEditor");
{
ENiagaraScriptCompileStatusEnum = StaticEnum<ENiagaraScriptCompileStatus>();
ENiagaraScriptUsageEnum = StaticEnum<ENiagaraScriptUsage>();
TArray<UNiagaraNodeOutput*> OutputNodes;
if (Source.IsValid() && Source->NodeGraph != nullptr)
{
Source->NodeGraph->FindOutputNodes(OutputNodes);
}
SortOutputNodesByDependencies(OutputNodes, SimStages);
bool bFilterByEmitterAlias = true;
for (UNiagaraNodeOutput* FoundOutputNode : OutputNodes)
{
if (UNiagaraScript::IsSystemScript(FoundOutputNode->GetUsage()))
{
bFilterByEmitterAlias = false;
}
}
// Only use the static variables that match up with our expectations for this script. IE for emitters, filter things out for resolution.
FNiagaraParameterUtilities::FilterToRelevantStaticVariables(InStaticVariables, StaticVariables, *GetUniqueEmitterName(), TEXT("Emitter"), bFilterByEmitterAlias);
int32 NumSimStageNodes = 0;
for (UNiagaraNodeOutput* FoundOutputNode : OutputNodes)
{
if (UsagesToProcess.Contains(FoundOutputNode->GetUsage()) == false)
{
continue;
}
FName SimStageName;
bool bStageEnabled = true;
if (FoundOutputNode->GetUsage() == ENiagaraScriptUsage::ParticleSimulationStageScript && SimStages)
{
// Find the simulation stage for this output node.
const FGuid& UsageId = FoundOutputNode->GetUsageId();
UNiagaraSimulationStageBase*const* MatchingStagePtr = SimStages->FindByPredicate([UsageId](UNiagaraSimulationStageBase* SimStage)
{
return SimStage != nullptr && SimStage->Script != nullptr && SimStage->Script->GetUsageId() == UsageId;
});
// Set whether or not the stage is enabled, and get the iteration source name if available.
bStageEnabled = MatchingStagePtr != nullptr && (*MatchingStagePtr)->bEnabled;
if(bStageEnabled && (*MatchingStagePtr)->IsA<UNiagaraSimulationStageGeneric>())
{
UNiagaraSimulationStageGeneric* GenericStage = CastChecked<UNiagaraSimulationStageGeneric>(*MatchingStagePtr);
SimStageName = GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName();
}
}
if (bStageEnabled)
{
// Map all for this output node
TNiagaraParameterMapHistoryWithMetaDataBuilder<FNiagaraCompilationGraphBridge> Builder;
*Builder.ConstantResolver = ConstantResolver;
Builder.AddGraphToCallingGraphContextStack(Source->NodeGraph);
Builder.RegisterEncounterableVariables(EncounterableVariables);
Builder.RegisterExternalStaticVariables(StaticVariables);
FString TranslationName = TEXT("Emitter");// Note that this cannot be GetUniqueEmitterName() as it would break downstream logic for some reason for data interfaces.
Builder.BeginTranslation(TranslationName);
Builder.BeginUsage(FoundOutputNode->GetUsage(), SimStageName);
Builder.EnableScriptAllowList(true, FoundOutputNode->GetUsage());
Builder.BuildParameterMaps(FoundOutputNode, true);
Builder.EndUsage();
int HistoryIdx = 0;
for (FNiagaraParameterMapHistory& History : Builder.Histories)
{
History.OriginatingScriptUsage = FoundOutputNode->GetUsage();
History.UsageGuid = FoundOutputNode->ScriptTypeId;
History.UsageName = SimStageName;
for (int32 i = 0; i < History.Variables.Num(); i++)
{
FNiagaraVariable& Var = History.Variables[i];
EncounteredVariables.AddUnique(Var);
if (Var.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef())
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Invalid numeric parameter found! %s"), *Var.GetName().ToString())
}
if (Var.GetType().IsStatic())
{
int32 NumValues = 0;
int32 LastIndex = INDEX_NONE;
// The logic for the static variables array adds the full payload static variable to the builder list. This will result
// in duplicates with the same name and type, but *different* value payloads. We detect this and error out.
for (int32 StaticIdx = 0; StaticIdx < Builder.StaticVariables.Num(); StaticIdx++)
{
const FNiagaraVariable& BuilderVar = Builder.StaticVariables[StaticIdx];
if (Var == BuilderVar) // operator == ignores the value field, which is what we want here
{
if (NumValues == 0)
{
LastIndex = StaticIdx;
NumValues++;
}
else if (LastIndex != INDEX_NONE && !BuilderVar.HoldsSameData(Builder.StaticVariables[LastIndex]))
{
if (UNiagaraScript::LogCompileStaticVars > 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Mismatch in static vars %s: \"%s\" vs \"%s\""), *BuilderVar.GetName().ToString(),
*BuilderVar.ToString(), *Builder.StaticVariables[LastIndex].ToString());
}
StaticVariablesWithMultipleWrites.AddUnique(Var);
NumValues++;
break;
}
}
}
if (History.PerVariableConstantValue[i].Num() > 1)
{
StaticVariablesWithMultipleWrites.AddUnique(Var);
}
}
}
if (UNiagaraScript::LogCompileStaticVars > 0)
{
for (auto Iter : History.PinToConstantValues)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("History [%d] Pin: %s Value: %s"), HistoryIdx , *Iter.Key.ToString(), *Iter.Value);
}
}
PinToConstantValues.Append(History.PinToConstantValues);
++HistoryIdx;
}
if (FoundOutputNode->GetUsage() == ENiagaraScriptUsage::ParticleSimulationStageScript)
{
NumSimStageNodes++;
}
if (UNiagaraScript::LogCompileStaticVars > 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Builder.StaticVariables After Param Map Traversal............................"));
}
for (int32 StaticIdx = 0; StaticIdx < Builder.StaticVariables.Num(); StaticIdx++)
{
const FNiagaraVariable& Var = Builder.StaticVariables[StaticIdx];
bool bProcess = Builder.StaticVariableExportable[StaticIdx];
if (UNiagaraScript::LogCompileStaticVars > 0)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("%s > %s"), *Var.ToString(), bProcess ? TEXT("EXPORT") : TEXT("SkipExport"));
}
if (!bProcess)
{
continue;
}
StaticVariables.AddUnique(Var);
}
if (UNiagaraScript::LogCompileStaticVars > 0)
{
for (auto Iter : PinToConstantValues)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Pin: %s Value: %s"), *Iter.Key.ToString(), *Iter.Value);
}
}
Builder.EndTranslation(TranslationName);
// Collect data interface information.
TMap<UNiagaraDataInterface*, FNiagaraVariable> DataInterfaceToTopLevelNiagaraVariable;
TMap<FNiagaraVariable, TArray<FNiagaraVariable>> DataInterfaceParameterMapReferences;
for (const FNiagaraParameterMapHistory& History : Builder.Histories)
{
// Find the variable indices for data interfaces.
TArray<int32> DataInterfaceVariableIndices;
for (int32 i = 0; i < History.Variables.Num(); i++)
{
const FNiagaraVariable& Variable = History.Variables[i];
if (Variable.IsDataInterface())
{
DataInterfaceVariableIndices.Add(i);
}
}
// Find the data interface input nodes and collect data from the data interfaces.
for (int32 i = 0; i < DataInterfaceVariableIndices.Num(); i++)
{
int32 VariableIndex = DataInterfaceVariableIndices[i];
const FNiagaraVariable& Variable = History.Variables[VariableIndex];
for (const FNiagaraParameterMapHistory::FModuleScopedPin& WritePin : History.PerVariableWriteHistory[VariableIndex])
{
if (WritePin.Pin != nullptr && WritePin.Pin->LinkedTo.Num() == 1 && WritePin.Pin->LinkedTo[0] != nullptr)
{
UNiagaraNode* LinkedNode = Cast<UNiagaraNode>(WritePin.Pin->LinkedTo[0]->GetOwningNode());
if (LinkedNode != nullptr && LinkedNode->IsA<UNiagaraNodeInput>())
{
UNiagaraDataInterface* DataInterface = CastChecked<UNiagaraNodeInput>(LinkedNode)->GetDataInterface();
FCompileDataInterfaceData* DataInterfaceData = nullptr;
if (DataInterface != nullptr)
{
TArray<FString> EmitterReferences;
DataInterface->GetEmitterReferencesByName(EmitterReferences);
for (const FString& EmitterName : EmitterNames)
{
if (EmitterReferences.Contains(EmitterName))
{
if (DataInterfaceData == nullptr)
{
DataInterfaceData = &SharedCompileDataInterfaceData->AddDefaulted_GetRef();
DataInterfaceData->EmitterName = EmitterUniqueName;
DataInterfaceData->Usage = FoundOutputNode->GetUsage();
DataInterfaceData->UsageId = FoundOutputNode->GetUsageId();
DataInterfaceData->Variable = Variable;
}
DataInterfaceData->ReadsEmitterParticleData.Add(EmitterName);
}
}
}
}
}
}
}
}
}
}
if (SimStages && NumSimStageNodes)
{
CompileSimStageData.Reserve(NumSimStageNodes);
const int32 NumProvidedStages = SimStages->Num();
for (int32 i=0, ActiveStageCount = 0; ActiveStageCount < NumSimStageNodes && i < NumProvidedStages; ++i)
{
UNiagaraSimulationStageBase* SimStage = (*SimStages)[i];
if (SimStage == nullptr || !SimStage->bEnabled)
{
continue;
}
if ( SimStage->FillCompilationData(CompileSimStageData) )
{
++ActiveStageCount;
}
}
}
}
}
void FNiagaraCompileRequestDuplicateData::FinishPrecompileDuplicate(const TArray<FNiagaraVariable>& EncounterableVariables, const TArray<FNiagaraVariable>& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray<class UNiagaraSimulationStageBase*>* SimStages, const TArray<FNiagaraVariable>& InParamStore)
{
FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked<FNiagaraEditorModule>("NiagaraEditor");
PrecompiledHistories.Empty();
TArray<UNiagaraNodeOutput*> OutputNodes;
if (NodeGraphDeepCopy.IsValid())
{
NodeGraphDeepCopy->FindOutputNodes(OutputNodes);
}
FNiagaraCompileRequestData::SortOutputNodesByDependencies(OutputNodes, SimStages);
for (UNiagaraNodeOutput* FoundOutputNode : OutputNodes)
{
FName SimStageName;
bool bStageEnabled = true;
if (FoundOutputNode->GetUsage() == ENiagaraScriptUsage::ParticleSimulationStageScript && /*bSimulationStagesEnabled &&*/ SimStages)
{
// Find the simulation stage for this output node.
const FGuid& UsageId = FoundOutputNode->GetUsageId();
UNiagaraSimulationStageBase* const* MatchingStagePtr = SimStages->FindByPredicate([UsageId](UNiagaraSimulationStageBase* SimStage)
{
return SimStage != nullptr && SimStage->Script != nullptr && SimStage->Script->GetUsageId() == UsageId;
});
// Set whether or not the stage is enabled, and get the iteration source name if available.
bStageEnabled = MatchingStagePtr != nullptr && (*MatchingStagePtr)->bEnabled;
if (bStageEnabled && (*MatchingStagePtr)->IsA<UNiagaraSimulationStageGeneric>())
{
UNiagaraSimulationStageGeneric* GenericStage = CastChecked<UNiagaraSimulationStageGeneric>(*MatchingStagePtr);
SimStageName = GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName();
}
}
if (bStageEnabled)
{
// Map all for this output node
FNiagaraParameterMapHistoryWithMetaDataBuilder Builder;
*Builder.ConstantResolver = ConstantResolver;
Builder.AddGraphToCallingGraphContextStack(NodeGraphDeepCopy.Get());
Builder.RegisterEncounterableVariables(EncounterableVariables);
Builder.RegisterExternalStaticVariables(InStaticVariables);
FString TranslationName = TEXT("Emitter");
Builder.BeginTranslation(TranslationName);
Builder.BeginUsage(FoundOutputNode->GetUsage(), SimStageName);
Builder.EnableScriptAllowList(true, FoundOutputNode->GetUsage());
Builder.BuildParameterMaps(FoundOutputNode, true);
Builder.EndUsage();
for (FNiagaraParameterMapHistory& History : Builder.Histories)
{
History.OriginatingScriptUsage = FoundOutputNode->GetUsage();
History.UsageGuid = FoundOutputNode->ScriptTypeId;
History.UsageName = SimStageName;
for (int32 VarIdx = 0; VarIdx < History.Variables.Num(); VarIdx++)
{
const FNiagaraVariable& Var = History.Variables[VarIdx];
if (Var.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef())
{
UE_LOG(LogNiagaraEditor, Log, TEXT("Invalid numeric parameter found! %s"), *Var.GetName().ToString())
}
}
}
PrecompiledHistories.Append(Builder.Histories);
Builder.EndTranslation(TranslationName);
}
else
{
// Add in a blank spot
PrecompiledHistories.Emplace();
}
}
}
void FNiagaraCompileRequestDuplicateData::CreateDataInterfaceCDO(TArrayView<UClass*> VariableDataInterfaces)
{
// Collect classes for any data interfaces found in the duplicated graphs
TArray<UClass*> DataInterfaceClasses(VariableDataInterfaces);
for (const TPair<const UNiagaraGraph*, TArray<FDuplicatedGraphData>>& SourceGraphToDuplicatedGraphs : *(SharedSourceGraphToDuplicatedGraphsMap.Get()))
{
for (const FDuplicatedGraphData& DuplicatedGraphData : SourceGraphToDuplicatedGraphs.Value)
{
TArray<UNiagaraNodeInput*> InputNodes;
DuplicatedGraphData.ClonedGraph->FindInputNodes(InputNodes);
for (const UNiagaraNodeInput* InputNode : InputNodes)
{
if (InputNode->Input.IsDataInterface())
{
DataInterfaceClasses.AddUnique(InputNode->Input.GetType().GetClass());
}
}
}
}
// Generate copies of the CDOs for any encountered data interfaces.
for (UClass* DataInterfaceClass : DataInterfaceClasses)
{
UNiagaraDataInterface* DuplicateCDO = CastChecked<UNiagaraDataInterface>(PrecompileDuplicateObject(DataInterfaceClass->GetDefaultObject(true), GetTransientPackage()));
SharedDataInterfaceClassToDuplicatedCDOMap->Add(DataInterfaceClass, DuplicateCDO);
}
}
TSharedPtr<FNiagaraCompileRequestDataBase, ESPMode::ThreadSafe> FNiagaraEditorModule::Precompile(UObject* InObj, FGuid Version)
{
UNiagaraScript* Script = Cast<UNiagaraScript>(InObj);
UNiagaraPrecompileContainer* Container = Cast<UNiagaraPrecompileContainer>(InObj);
UNiagaraSystem* System = Cast<UNiagaraSystem>(InObj);
UPackage* LogPackage = nullptr;
if (Container)
{
System = Container->System;
if (System)
{
LogPackage = System->GetOutermost();
}
}
else if (Script)
{
LogPackage = Script->GetOutermost();
}
if (!LogPackage || (!Script && !System))
{
TSharedPtr<FNiagaraCompileRequestDataBase, ESPMode::ThreadSafe> InvalidPtr;
return InvalidPtr;
}
FString LogName = LogPackage ? LogPackage->GetName() : InObj->GetName();
TRACE_CPUPROFILER_EVENT_SCOPE(NiagaraPrecompile);
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_ON_CHANNEL(*LogName, NiagaraChannel);
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptSource_PreCompile);
double StartTime = FPlatformTime::Seconds();
TSharedPtr<FNiagaraCompileRequestData, ESPMode::ThreadSafe> BasePtr = MakeShared<FNiagaraCompileRequestData, ESPMode::ThreadSafe>();
BasePtr->SharedCompileDataInterfaceData = MakeShared<TArray<FNiagaraCompileRequestData::FCompileDataInterfaceData>>();
TArray<TSharedPtr<FNiagaraCompileRequestData, ESPMode::ThreadSafe>> DependentRequests;
FCompileConstantResolver EmptyResolver;
BasePtr->SourceName = LogName;
if (Script)
{
BasePtr->Source = Cast<UNiagaraScriptSource>(Script->GetSource(Version));
const TArray<FNiagaraVariable> EncounterableVariables;
const TArray<ENiagaraScriptUsage> ValidUsages = { ENiagaraScriptUsage::Function, ENiagaraScriptUsage::Module, ENiagaraScriptUsage::DynamicInput };
const TArray<FString> EmitterNames;
TArray<FNiagaraVariable> StaticVars;
BasePtr->FinishPrecompile(EncounterableVariables, StaticVars, EmptyResolver, ValidUsages, nullptr, EmitterNames);
}
else if (System)
{
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> CachedTraversalSystemData = System->GetCachedTraversalData();
check(System->GetSystemSpawnScript()->GetLatestSource() == System->GetSystemUpdateScript()->GetLatestSource());
BasePtr->Source = Cast<UNiagaraScriptSource>(System->GetSystemSpawnScript()->GetLatestSource());
BasePtr->bUseRapidIterationParams = System->ShouldUseRapidIterationParameters();
BasePtr->bDisableDebugSwitches = System->ShouldDisableDebugSwitches();
TArray<FString> EmitterNames;
// Store off the current variables in the exposed parameters list.
TArray<FNiagaraVariable> OriginalExposedParams;
System->GetExposedParameters().GetParameters(OriginalExposedParams);
// Create an array of variables that we might encounter when traversing the graphs (include the originally exposed vars above)
TArray<FNiagaraVariable> EncounterableVars(OriginalExposedParams);
// First deep copy all the emitter graphs referenced by the system so that we can later hook up emitter handles in the system traversal.
BasePtr->EmitterData.Empty();
for (int32 i = 0; i < System->GetEmitterHandles().Num(); i++)
{
const FNiagaraEmitterHandle& Handle = System->GetEmitterHandle(i);
TSharedPtr<FNiagaraCompileRequestData, ESPMode::ThreadSafe> EmitterPtr = MakeShared<FNiagaraCompileRequestData, ESPMode::ThreadSafe>();
EmitterPtr->EmitterUniqueName = Handle.GetUniqueInstanceName();
EmitterPtr->EmitterID = FNiagaraEmitterID(i);
EmitterPtr->SourceName = BasePtr->SourceName;
//-TODO:Stateless: We need to handle the stateless path here
EmitterPtr->Source = Handle.GetEmitterData() ? Cast<UNiagaraScriptSource>(Handle.GetEmitterData()->GraphSource) : nullptr;
EmitterPtr->bUseRapidIterationParams = BasePtr->bUseRapidIterationParams;
EmitterPtr->bDisableDebugSwitches = BasePtr->bDisableDebugSwitches;
EmitterPtr->SharedCompileDataInterfaceData = BasePtr->SharedCompileDataInterfaceData;
BasePtr->EmitterData.Add(EmitterPtr);
EmitterNames.Add(Handle.GetUniqueInstanceName());
}
// Now deep copy the system graphs, skipping traversal into any emitter references.
TArray<FNiagaraVariable> StaticVariablesFromSystem = ((FNiagaraGraphCachedBuiltHistory*)CachedTraversalSystemData.Get())->StaticVariables;
{
FCompileConstantResolver ConstantResolver(System, ENiagaraScriptUsage::SystemSpawnScript);
UNiagaraScriptSource* Source = Cast<UNiagaraScriptSource>(System->GetSystemSpawnScript()->GetLatestSource());
static TArray<ENiagaraScriptUsage> SystemUsages = { ENiagaraScriptUsage::SystemSpawnScript, ENiagaraScriptUsage::SystemUpdateScript };
BasePtr->FinishPrecompile(EncounterableVars, StaticVariablesFromSystem, ConstantResolver, SystemUsages, nullptr, EmitterNames);
BasePtr->CompareAgainst(((FNiagaraGraphCachedBuiltHistory*)CachedTraversalSystemData.Get()));
}
// Add the User and System variables that we did encounter to the list that emitters might also encounter.
BasePtr->GatherPreCompiledVariables(TEXT("User"), EncounterableVars);
BasePtr->GatherPreCompiledVariables(TEXT("System"), EncounterableVars);
// now that the scripts have been precompiled we can prepare the rapid iteration parameters, which we need to do before we
// actually generate the hlsl in the case of baking out the parameters
TArray<UNiagaraScript*> Scripts;
TMap<UNiagaraScript*, FVersionedNiagaraEmitter> ScriptToEmitterMap;
// Now we can finish off the emitters.
for (int32 i = 0; i < System->GetEmitterHandles().Num(); i++)
{
const FNiagaraEmitterHandle& Handle = System->GetEmitterHandle(i);
FCompileConstantResolver ConstantResolver(Handle.GetInstance(), ENiagaraScriptUsage::EmitterSpawnScript);
if (Handle.GetIsEnabled() && Handle.GetEmitterData()) // Don't pull in the emitter if it isn't going to be used.
{
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> CachedTraversalEmitterData = Handle.GetInstance().Emitter->GetCachedTraversalData(Handle.GetInstance().Version);
TArray<FNiagaraVariable> StaticVariablesFromEmitter = StaticVariablesFromSystem;
StaticVariablesFromEmitter.Append(((FNiagaraGraphCachedBuiltHistory*)CachedTraversalEmitterData.Get())->StaticVariables);
TArray<UNiagaraScript*> EmitterScripts;
FVersionedNiagaraEmitterData* EmitterData = Handle.GetEmitterData();
EmitterData->GetScripts(EmitterScripts, false, true);
for (int32 ScriptIdx = 0; ScriptIdx < EmitterScripts.Num(); ScriptIdx++)
{
if (EmitterScripts[ScriptIdx])
{
Scripts.AddUnique(EmitterScripts[ScriptIdx]);
ScriptToEmitterMap.Add(EmitterScripts[ScriptIdx], Handle.GetInstance());
}
}
static TArray<ENiagaraScriptUsage> EmitterUsages = { ENiagaraScriptUsage::EmitterSpawnScript, ENiagaraScriptUsage::EmitterUpdateScript };
BasePtr->EmitterData[i]->FinishPrecompile(EncounterableVars, StaticVariablesFromEmitter, ConstantResolver, EmitterUsages, nullptr, EmitterNames);
BasePtr->EmitterData[i]->CompareAgainst(((FNiagaraGraphCachedBuiltHistory*)CachedTraversalEmitterData.Get()));
// Then finish the precompile for the particle scripts once we've gathered the emitter vars which might be referenced.
TArray<FNiagaraVariable> ParticleEncounterableVars = EncounterableVars;
BasePtr->EmitterData[i]->GatherPreCompiledVariables(TEXT("Emitter"), ParticleEncounterableVars);
static TArray<ENiagaraScriptUsage> ParticleUsages = {
ENiagaraScriptUsage::ParticleSpawnScript,
ENiagaraScriptUsage::ParticleSpawnScriptInterpolated,
ENiagaraScriptUsage::ParticleUpdateScript,
ENiagaraScriptUsage::ParticleEventScript,
ENiagaraScriptUsage::ParticleGPUComputeScript,
ENiagaraScriptUsage::ParticleSimulationStageScript };
TArray<FNiagaraVariable> OldStaticVars = BasePtr->EmitterData[i]->StaticVariables;
BasePtr->EmitterData[i]->FinishPrecompile(ParticleEncounterableVars, OldStaticVars, ConstantResolver, ParticleUsages, &EmitterData->GetSimulationStages(), EmitterNames);
}
}
{
TMap<UNiagaraScript*, UNiagaraScript*> ScriptDependencyMap;
// Prepare rapid iteration parameters for execution.
TArray<TTuple<UNiagaraScript*, FVersionedNiagaraEmitter>> ScriptsToIterate;
for (const auto& Entry : ScriptToEmitterMap)
{
ScriptsToIterate.Add(Entry);
}
for (int Index = 0; Index < ScriptsToIterate.Num(); Index++)
{
TTuple<UNiagaraScript*, FVersionedNiagaraEmitter> ScriptEmitterPair = ScriptsToIterate[Index];
UNiagaraScript* CompiledScript = ScriptEmitterPair.Key;
FVersionedNiagaraEmitterData* EmitterData = ScriptEmitterPair.Value.GetEmitterData();
if (UNiagaraScript::IsEquivalentUsage(CompiledScript->GetUsage(), ENiagaraScriptUsage::EmitterSpawnScript))
{
UNiagaraScript* SystemSpawnScript = System->GetSystemSpawnScript();
Scripts.AddUnique(SystemSpawnScript);
ScriptDependencyMap.Add(CompiledScript, SystemSpawnScript);
if (!ScriptToEmitterMap.Contains(SystemSpawnScript))
{
ScriptToEmitterMap.Add(SystemSpawnScript);
ScriptsToIterate.Emplace(SystemSpawnScript, FVersionedNiagaraEmitter());
}
}
if (UNiagaraScript::IsEquivalentUsage(CompiledScript->GetUsage(), ENiagaraScriptUsage::EmitterUpdateScript))
{
UNiagaraScript* SystemUpdateScript = System->GetSystemUpdateScript();
Scripts.AddUnique(SystemUpdateScript);
ScriptDependencyMap.Add(CompiledScript, SystemUpdateScript);
ScriptToEmitterMap.Add(SystemUpdateScript);
if (!ScriptToEmitterMap.Contains(SystemUpdateScript))
{
ScriptToEmitterMap.Add(SystemUpdateScript);
ScriptsToIterate.Emplace(SystemUpdateScript, FVersionedNiagaraEmitter());
}
}
if (UNiagaraScript::IsEquivalentUsage(CompiledScript->GetUsage(), ENiagaraScriptUsage::ParticleSpawnScript))
{
if (EmitterData && EmitterData->SimTarget == ENiagaraSimTarget::GPUComputeSim)
{
UNiagaraScript* ComputeScript = EmitterData->GetGPUComputeScript();
Scripts.AddUnique(ComputeScript);
ScriptDependencyMap.Add(CompiledScript, ComputeScript);
if (!ScriptToEmitterMap.Contains(ComputeScript))
{
ScriptToEmitterMap.Add(ComputeScript, ScriptEmitterPair.Value);
ScriptsToIterate.Emplace(ComputeScript, ScriptEmitterPair.Value);
}
}
}
if (UNiagaraScript::IsEquivalentUsage(CompiledScript->GetUsage(), ENiagaraScriptUsage::ParticleUpdateScript))
{
if (EmitterData && EmitterData->SimTarget == ENiagaraSimTarget::GPUComputeSim)
{
UNiagaraScript* ComputeScript = EmitterData->GetGPUComputeScript();
Scripts.AddUnique(ComputeScript);
ScriptDependencyMap.Add(CompiledScript, ComputeScript);
if (!ScriptToEmitterMap.Contains(ComputeScript))
{
ScriptToEmitterMap.Add(ComputeScript, ScriptEmitterPair.Value);
ScriptsToIterate.Emplace(ComputeScript, ScriptEmitterPair.Value);
}
}
else if (EmitterData && EmitterData->UsesInterpolatedSpawning())
{
Scripts.AddUnique(EmitterData->SpawnScriptProps.Script);
ScriptDependencyMap.Add(CompiledScript, EmitterData->SpawnScriptProps.Script);
if (!ScriptToEmitterMap.Contains(EmitterData->SpawnScriptProps.Script))
{
ScriptToEmitterMap.Add(EmitterData->SpawnScriptProps.Script, ScriptEmitterPair.Value);
ScriptsToIterate.Emplace(EmitterData->SpawnScriptProps.Script, ScriptEmitterPair.Value);
}
}
}
}
FNiagaraUtilities::PrepareRapidIterationParameters(Scripts, ScriptDependencyMap, ScriptToEmitterMap);
BasePtr->AddRapidIterationParameters(System->GetSystemSpawnScript()->RapidIterationParameters, FCompileConstantResolver(System, ENiagaraScriptUsage::SystemSpawnScript));
BasePtr->AddRapidIterationParameters(System->GetSystemUpdateScript()->RapidIterationParameters, FCompileConstantResolver(System, ENiagaraScriptUsage::SystemUpdateScript));
// Now we can finish off the emitters.
for (int32 i = 0; i < System->GetEmitterHandles().Num(); i++)
{
const FNiagaraEmitterHandle& Handle = System->GetEmitterHandle(i);
FCompileConstantResolver ConstantResolver(Handle.GetInstance(), ENiagaraScriptUsage::EmitterSpawnScript);
if (Handle.GetIsEnabled() && Handle.GetEmitterData()) // Don't pull in the emitter if it isn't going to be used.
{
TArray<UNiagaraScript*> EmitterScripts;
Handle.GetEmitterData()->GetScripts(EmitterScripts, false);
for (int32 ScriptIdx = 0; ScriptIdx < EmitterScripts.Num(); ScriptIdx++)
{
if (EmitterScripts[ScriptIdx])
{
BasePtr->EmitterData[i]->AddRapidIterationParameters(EmitterScripts[ScriptIdx]->RapidIterationParameters, ConstantResolver);
}
}
}
}
}
}
UE_LOG(LogNiagaraEditor, Verbose, TEXT("'%s' Precompile took %f sec."), *LogName,
(float)(FPlatformTime::Seconds() - StartTime));
return BasePtr;
}
void GetUsagesToDuplicate(ENiagaraScriptUsage TargetUsage, TArray<ENiagaraScriptUsage>& DuplicateUsages)
{
/*switch (TargetUsage)
{
case ENiagaraScriptUsage::SystemSpawnScript:
DuplicateUsages.Add(ENiagaraScriptUsage::SystemSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::EmitterSpawnScript);
break;
case ENiagaraScriptUsage::SystemUpdateScript:
DuplicateUsages.Add(ENiagaraScriptUsage::SystemUpdateScript);
DuplicateUsages.Add(ENiagaraScriptUsage::EmitterUpdateScript);
break;
case ENiagaraScriptUsage::ParticleSpawnScript:
case ENiagaraScriptUsage::ParticleUpdateScript:
case ENiagaraScriptUsage::ParticleEventScript:
DuplicateUsages.Add(TargetUsage);
break;
case ENiagaraScriptUsage::ParticleSpawnScriptInterpolated:
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScriptInterpolated);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleUpdateScript);
break;
case ENiagaraScriptUsage::ParticleGPUComputeScript:
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScriptInterpolated);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleUpdateScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSimulationStageScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleGPUComputeScript);
break;
}*/
// For now we need to include both spawn and update for each target usage, otherwise attribute lists in the precompiled histories aren't generated correctly.
switch (TargetUsage)
{
case ENiagaraScriptUsage::SystemSpawnScript:
case ENiagaraScriptUsage::SystemUpdateScript:
DuplicateUsages.Add(ENiagaraScriptUsage::SystemSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::SystemUpdateScript);
DuplicateUsages.Add(ENiagaraScriptUsage::EmitterSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::EmitterUpdateScript);
break;
case ENiagaraScriptUsage::ParticleSpawnScript:
case ENiagaraScriptUsage::ParticleSpawnScriptInterpolated:
case ENiagaraScriptUsage::ParticleUpdateScript:
case ENiagaraScriptUsage::ParticleEventScript:
case ENiagaraScriptUsage::ParticleSimulationStageScript:
case ENiagaraScriptUsage::ParticleGPUComputeScript:
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSpawnScriptInterpolated);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleUpdateScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleEventScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleSimulationStageScript);
DuplicateUsages.Add(ENiagaraScriptUsage::ParticleGPUComputeScript);
break;
}
}
TSharedPtr<FNiagaraCompileRequestDuplicateDataBase, ESPMode::ThreadSafe> FNiagaraEditorModule::PrecompileDuplicate(
const FNiagaraCompileRequestDataBase* OwningSystemRequestData,
UNiagaraSystem* OwningSystem,
UNiagaraEmitter* OwningEmitter,
UNiagaraScript* TargetScript,
FGuid TargetVersion)
{
FString LogName;
if (OwningSystem != nullptr)
{
LogName = OwningSystem->GetOutermost() != nullptr ? OwningSystem->GetOutermost()->GetName() : OwningSystem->GetName();
}
else if (TargetScript != nullptr)
{
LogName = TargetScript->GetOutermost() != nullptr ? TargetScript->GetOutermost()->GetName() : TargetScript->GetName();
}
else
{
TSharedPtr<FNiagaraCompileRequestDuplicateDataBase, ESPMode::ThreadSafe> InvalidPtr;
return InvalidPtr;
}
TRACE_CPUPROFILER_EVENT_SCOPE(NiagaraPrecompileDuplicate);
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT_ON_CHANNEL(*LogName, NiagaraChannel);
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_ScriptSource_PreCompileDuplicate);
double StartTime = FPlatformTime::Seconds();
TSharedPtr<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe> BasePtr = MakeShared<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe>();
TArray<TSharedPtr<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe>> DependentRequests;
FCompileConstantResolver EmptyResolver;
BasePtr->SharedSourceGraphToDuplicatedGraphsMap = MakeShared<TMap<const UNiagaraGraph*, TArray<FNiagaraCompileRequestDuplicateData::FDuplicatedGraphData>>>();
BasePtr->SharedNameToDuplicatedDataInterfaceMap = MakeShared<TMap<FName, UNiagaraDataInterface*>>();
BasePtr->SharedDataInterfaceClassToDuplicatedCDOMap = MakeShared<TMap<UClass*, UNiagaraDataInterface*>>();
BasePtr->OwningSystem = OwningSystem;
BasePtr->OwningEmitter = OwningEmitter;
TArray<UClass*> DataInterfaceClasses;
auto CollectDataInterfaceClasses = [&](TConstArrayView<FNiagaraVariable> Variables)
{
// Collect classes for external encounterable variables
for (const FNiagaraVariable& EncounterableVariable : Variables)
{
if (EncounterableVariable.IsDataInterface())
{
DataInterfaceClasses.AddUnique(EncounterableVariable.GetType().GetClass());
}
}
};
if (OwningSystem == nullptr)
{
UNiagaraScriptSource* Source = CastChecked<UNiagaraScriptSource>(TargetScript->GetSource(TargetVersion));
BasePtr->ValidUsages.Add(TargetScript->GetUsage());
BasePtr->DeepCopyGraphs(Source, TargetScript->GetUsage(), EmptyResolver);
TArray<FNiagaraVariable> EncounterableScriptVariables;
OwningSystemRequestData->GatherPreCompiledVariables(FString(), EncounterableScriptVariables);
BasePtr->FinishPrecompileDuplicate(EncounterableScriptVariables, TArray<FNiagaraVariable>(), EmptyResolver, nullptr, ((FNiagaraCompileRequestData*)OwningSystemRequestData)->RapidIterationParams);
CollectDataInterfaceClasses(EncounterableScriptVariables);
}
else
{
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> CachedTraversalSystemData = OwningSystem->GetCachedTraversalData();
GetUsagesToDuplicate(TargetScript->GetUsage(), BasePtr->ValidUsages);
check(OwningSystem->GetSystemSpawnScript()->GetLatestSource() == OwningSystem->GetSystemUpdateScript()->GetLatestSource());
// First deep copy all the emitter graphs referenced by the system so that we can later hook up emitter handles in the system traversal.
BasePtr->EmitterData.Empty();
for (int32 i = 0; i < OwningSystem->GetEmitterHandles().Num(); i++)
{
const FNiagaraEmitterHandle& Handle = OwningSystem->GetEmitterHandle(i);
TSharedPtr<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe> EmitterPtr = MakeShared<FNiagaraCompileRequestDuplicateData, ESPMode::ThreadSafe>();
EmitterPtr->EmitterUniqueName = Handle.GetUniqueInstanceName();
EmitterPtr->EmitterID = FNiagaraEmitterID(i);
EmitterPtr->ValidUsages = BasePtr->ValidUsages;
EmitterPtr->SharedSourceGraphToDuplicatedGraphsMap = BasePtr->SharedSourceGraphToDuplicatedGraphsMap;
EmitterPtr->SharedNameToDuplicatedDataInterfaceMap = BasePtr->SharedNameToDuplicatedDataInterfaceMap;
EmitterPtr->SharedDataInterfaceClassToDuplicatedCDOMap = BasePtr->SharedDataInterfaceClassToDuplicatedCDOMap;
//EmitterPtr->bSimulationStagesEnabled = Handle.GetInstance()->bSimulationStagesEnabled;
if (Handle.GetIsEnabled() && Handle.GetInstance().Emitter && (OwningEmitter == nullptr || OwningEmitter == Handle.GetInstance().Emitter)) // Don't need to copy the graph if we aren't going to use it.
{
EmitterPtr->DeepCopyGraphs(Handle.GetInstance());
}
EmitterPtr->ValidUsages = BasePtr->ValidUsages;
BasePtr->EmitterData.Add(EmitterPtr);
}
// Now deep copy the system graphs, skipping traversal into any emitter references.
TArray<FNiagaraVariable> StaticVariablesFromSystem = ((FNiagaraGraphCachedBuiltHistory*)CachedTraversalSystemData.Get())->StaticVariables;
{
UNiagaraScriptSource* Source = Cast<UNiagaraScriptSource>(OwningSystem->GetSystemSpawnScript()->GetLatestSource());
TArray<FNiagaraVariable> EncounterableSystemVariables;
OwningSystemRequestData->GatherPreCompiledVariables(FString(), EncounterableSystemVariables);
// skip the deep copy if we're not compiling the system scripts
if (BasePtr->ValidUsages.Contains(ENiagaraScriptUsage::SystemSpawnScript))
{
FCompileConstantResolver ConstantResolver(OwningSystem, ENiagaraScriptUsage::SystemSpawnScript);
BasePtr->DeepCopyGraphs(Source, ENiagaraScriptUsage::SystemSpawnScript, ConstantResolver);
BasePtr->FinishPrecompileDuplicate(EncounterableSystemVariables, StaticVariablesFromSystem, ConstantResolver, nullptr, ((FNiagaraCompileRequestData*)OwningSystemRequestData)->RapidIterationParams);
}
CollectDataInterfaceClasses(EncounterableSystemVariables);
}
// Now we can finish off the emitters.
for (int32 i = 0; i < OwningSystem->GetEmitterHandles().Num(); i++)
{
const FNiagaraEmitterHandle& Handle = OwningSystem->GetEmitterHandle(i);
TArray<FNiagaraVariable> EncounterableEmitterVariables;
OwningSystemRequestData->GetDependentRequest(i)->GatherPreCompiledVariables(FString(), EncounterableEmitterVariables);
if (Handle.GetIsEnabled() && Handle.GetInstance().Emitter && (OwningEmitter == nullptr || OwningEmitter == Handle.GetInstance().Emitter))
{
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> CachedTraversalEmitterData = Handle.GetInstance().Emitter->GetCachedTraversalData(Handle.GetInstance().Version);
TArray<FNiagaraVariable> StaticVariablesFromEmitter = StaticVariablesFromSystem;
StaticVariablesFromEmitter.Append(((FNiagaraGraphCachedBuiltHistory*)CachedTraversalEmitterData.Get())->StaticVariables);
FCompileConstantResolver ConstantResolver(Handle.GetInstance(), ENiagaraScriptUsage::EmitterSpawnScript);
BasePtr->EmitterData[i]->FinishPrecompileDuplicate(EncounterableEmitterVariables, StaticVariablesFromEmitter, ConstantResolver, &Handle.GetEmitterData()->GetSimulationStages(), ((FNiagaraCompileRequestData*)OwningSystemRequestData)->EmitterData[i]->RapidIterationParams);
}
CollectDataInterfaceClasses(EncounterableEmitterVariables);
}
}
BasePtr->CreateDataInterfaceCDO(DataInterfaceClasses);
UE_LOG(LogNiagaraEditor, Verbose, TEXT("'%s' PrecompileDuplicate took %f sec."), *LogName,
(float)(FPlatformTime::Seconds() - StartTime));
return BasePtr;
}
int32 FNiagaraEditorModule::CompileScript(const FNiagaraCompileRequestDataBase* InCompileRequest, const FNiagaraCompileRequestDuplicateDataBase* InCompileRequestDuplicate, const FNiagaraCompileOptions& InCompileOptions)
{
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Module_CompileScript);
check(InCompileRequest != NULL);
const FNiagaraCompileRequestData* CompileRequest = (const FNiagaraCompileRequestData*)InCompileRequest;
const FNiagaraCompileRequestDuplicateData* CompileRequestDuplicate = (const FNiagaraCompileRequestDuplicateData*)InCompileRequestDuplicate;
TArray<FNiagaraVariable> CookedRapidIterationParams = CompileRequest->GetUseRapidIterationParams() ? TArray<FNiagaraVariable>() : CompileRequest->RapidIterationParams;
UE_LOG(LogNiagaraEditor, Verbose, TEXT("Compiling System %s ..................................................................."), *InCompileOptions.FullName);
FNiagaraCompileResults Results;
FActiveCompilation ActiveCompilation;
ActiveCompilation.Compiler = MakeShared<FHlslNiagaraCompiler>();
FNiagaraTranslateResults TranslateResults;
TUniquePtr<INiagaraHlslTranslator> Translator = INiagaraHlslTranslator::CreateTranslator(CompileRequest, CompileRequestDuplicate);
FHlslNiagaraTranslatorOptions TranslateOptions;
if (InCompileOptions.TargetUsage == ENiagaraScriptUsage::ParticleGPUComputeScript)
{
TranslateOptions.SimTarget = ENiagaraSimTarget::GPUComputeSim;
}
else
{
TranslateOptions.SimTarget = ENiagaraSimTarget::CPUSim;
}
TranslateOptions.OverrideModuleConstants = CookedRapidIterationParams;
TranslateOptions.bParameterRapidIteration = InCompileRequest->GetUseRapidIterationParams();
TranslateOptions.bDisableDebugSwitches = InCompileRequest->GetDisableDebugSwitches();
double TranslationStartTime = FPlatformTime::Seconds();
if (GbForceNiagaraTranslatorSingleThreaded > 0)
{
FScopeLock Lock(&TranslationCritSec);
TranslateResults = Translator->Translate(InCompileOptions, TranslateOptions);
}
else
{
TranslateResults = Translator->Translate(InCompileOptions, TranslateOptions);
}
ActiveCompilation.TranslationTime = (float) (FPlatformTime::Seconds() - TranslationStartTime);
UE_LOG(LogNiagaraEditor, Verbose, TEXT("Translating System %s took %f sec."), *InCompileOptions.FullName, (float)(FPlatformTime::Seconds() - TranslationStartTime));
if (GbForceNiagaraTranslatorDump != 0)
{
DumpHLSLText(Translator->GetTranslatedHLSL(), InCompileOptions.FullName);
if (GbForceNiagaraVMBinaryDump != 0 && Results.Data.IsValid())
{
DumpHLSLText(Results.Data->LastAssemblyTranslation, InCompileOptions.FullName);
}
}
NiagaraCompileRequestHelper::FDebugGroupNameBuilder DebugGroupName;
NiagaraCompileRequestHelper::BuildScriptDebugGroupName(CompileRequest, InCompileOptions, DebugGroupName);
int32 JobID = ActiveCompilation.Compiler->CompileScript(DebugGroupName, InCompileOptions, TranslateResults, Translator->GetTranslateOutput(), Translator->GetTranslatedHLSL());
ActiveCompilations.Add(JobID, ActiveCompilation);
return JobID;
}
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> FNiagaraEditorModule::CacheGraphTraversal(const UObject* Obj, FGuid Version)
{
TSharedPtr<FNiagaraGraphCachedBuiltHistory, ESPMode::ThreadSafe> CachedPtr = MakeShared<FNiagaraGraphCachedBuiltHistory>();
const UNiagaraSystem* System = Cast<UNiagaraSystem>(Obj);
const UNiagaraEmitter* Emitter = Cast<UNiagaraEmitter>(Obj);
/*
* const TArray<FNiagaraVariable>& EncounterableVariables, const TArray<FNiagaraVariable>& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray<ENiagaraScriptUsage>& UsagesToProcess, const
*/
const TArray<class UNiagaraSimulationStageBase*>* SimStages = nullptr;
const UNiagaraScriptSource* ScriptSource = nullptr;
FCompileConstantResolver ConstantResolver;
TArray<FNiagaraVariable> EncounterableVariables;
TArray<FNiagaraVariable> StaticVariablesFromSystem;
TArray<FNiagaraVariable> StaticVariablesFromSystemAndEmitters;
TArray<FNiagaraVariable> SrcStaticVariables;
TSharedPtr<FNiagaraGraphCachedDataBase, ESPMode::ThreadSafe> ParentCachedData;
FString SrcUniqueEmitterName;
if (System)
{
CachedPtr->SetSourceSystem(System);
ScriptSource = CastChecked<UNiagaraScriptSource>(System->GetSystemSpawnScript()->GetLatestSource());
ConstantResolver = FCompileConstantResolver(System, ENiagaraScriptUsage::SystemSpawnScript);
System->GatherStaticVariables(StaticVariablesFromSystem, StaticVariablesFromSystemAndEmitters);
SrcStaticVariables = StaticVariablesFromSystem;
for (const FNiagaraVariable& Var : StaticVariablesFromSystemAndEmitters)
SrcStaticVariables.AddUnique(Var);
}
else if (Emitter)
{
const FVersionedNiagaraEmitterData* EmitterData = Emitter->GetEmitterData(Version);
CachedPtr->SetSourceEmitter(EmitterData);
UNiagaraSystem* SysParent = Cast<UNiagaraSystem>(Emitter->GetOuter());
if (SysParent)
{
ParentCachedData = SysParent->GetCachedTraversalData();
if (ParentCachedData.IsValid())
{
SrcStaticVariables = ((FNiagaraGraphCachedBuiltHistory*)ParentCachedData.Get())->StaticVariables;
}
/*for (const FNiagaraEmitterHandle& Handle : SysParent->GetEmitterHandles())
{
if (Handle.GetInstance() == Emitter)
{
SrcUniqueEmitterName = Handle.GetUniqueEmitterName().ToString();
}
}*/
}
SrcUniqueEmitterName = Emitter->GetUniqueEmitterName();
EmitterData->GatherStaticVariables(SrcStaticVariables);
ScriptSource = CastChecked<UNiagaraScriptSource>(EmitterData->GraphSource);
SimStages = &EmitterData->GetSimulationStages();
ConstantResolver = FCompileConstantResolver(FVersionedNiagaraEmitter(const_cast<UNiagaraEmitter*>(Emitter), Version), ENiagaraScriptUsage::EmitterSpawnScript);
}
TArray<UNiagaraNodeOutput*> OutputNodes;
if (ScriptSource != nullptr && ScriptSource->NodeGraph != nullptr)
{
ScriptSource->NodeGraph->FindOutputNodes(OutputNodes);
}
bool bFilterByEmitterAlias = true;
for (UNiagaraNodeOutput* FoundOutputNode : OutputNodes)
{
if (UNiagaraScript::IsSystemScript(FoundOutputNode->GetUsage()))
{
bFilterByEmitterAlias = false;
}
}
// Only use the static variables that match up with our expectations for this script. IE for emitters, filter things out for resolution.
TArray<FNiagaraVariable> StaticVariables;
FNiagaraParameterUtilities::FilterToRelevantStaticVariables(SrcStaticVariables, StaticVariables, *SrcUniqueEmitterName, TEXT("Emitter"), bFilterByEmitterAlias);
int32 NumSimStageNodes = 0;
for (UNiagaraNodeOutput* FoundOutputNode : OutputNodes)
{
FName SimStageName;
bool bStageEnabled = true;
if (FoundOutputNode->GetUsage() == ENiagaraScriptUsage::ParticleSimulationStageScript && SimStages)
{
// Find the simulation stage for this output node.
const FGuid& UsageId = FoundOutputNode->GetUsageId();
UNiagaraSimulationStageBase* const* MatchingStagePtr = SimStages->FindByPredicate([UsageId](UNiagaraSimulationStageBase* SimStage)
{
return SimStage != nullptr && SimStage->Script != nullptr && SimStage->Script->GetUsageId() == UsageId;
});
// Set whether or not the stage is enabled, and get the iteration source name if available.
bStageEnabled = MatchingStagePtr != nullptr && (*MatchingStagePtr)->bEnabled;
if (bStageEnabled && (*MatchingStagePtr)->IsA<UNiagaraSimulationStageGeneric>())
{
UNiagaraSimulationStageGeneric* GenericStage = CastChecked<UNiagaraSimulationStageGeneric>(*MatchingStagePtr);
SimStageName = GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName();
}
}
if (bStageEnabled)
{
// Map all for this output node
FNiagaraParameterMapHistoryWithMetaDataBuilder Builder;
*Builder.ConstantResolver = ConstantResolver;
if (ScriptSource != nullptr)
{
Builder.AddGraphToCallingGraphContextStack(ScriptSource->NodeGraph);
}
Builder.RegisterEncounterableVariables(EncounterableVariables);
Builder.RegisterExternalStaticVariables(StaticVariables);
FString TranslationName = TEXT("Emitter");
Builder.BeginTranslation(TranslationName);
Builder.BeginUsage(FoundOutputNode->GetUsage(), SimStageName);
Builder.EnableScriptAllowList(true, FoundOutputNode->GetUsage());
Builder.IncludeStaticVariablesOnly();
Builder.BuildParameterMaps(FoundOutputNode, true);
Builder.EndUsage();
for (int32 BuilderVarIndex = 0; BuilderVarIndex < Builder.StaticVariables.Num() && BuilderVarIndex < Builder.StaticVariableExportable.Num(); ++BuilderVarIndex)
{
StaticVariables.AddUnique(Builder.StaticVariables[BuilderVarIndex]);
}
}
}
CachedPtr->StaticVariables = StaticVariables;
if (GbForceNiagaraCacheDump != 0 && Obj )
{
UE_LOG(LogNiagaraEditor, Log, TEXT("==================================================================\nCacheGraphTraversal %s\n=================================================================="), *Obj->GetPathName());
int32 i = 0;
UE_LOG(LogNiagaraEditor, Log, TEXT("Static Variables: %d"), StaticVariables.Num());
for (const FNiagaraVariable& Var : StaticVariables)
{
UE_LOG(LogNiagaraEditor, Log, TEXT("[%d] %s"), i, *Var.ToString());
++i;
}
}
return CachedPtr;
}
TSharedPtr<FNiagaraVMExecutableData> FNiagaraEditorModule::GetCompilationResult(int32 JobID, bool bWait, FNiagaraScriptCompileMetrics& ScriptMetrics)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FNiagaraEditorModule::GetCompilationResult);
FActiveCompilation* MapEntry = ActiveCompilations.Find(JobID);
check(MapEntry && MapEntry->Compiler.IsValid());
TSharedPtr<FHlslNiagaraCompiler> Compiler = MapEntry->Compiler;
TOptional<FNiagaraCompileResults> CompileResult = Compiler->GetCompileResult(JobID, bWait);
if (!CompileResult)
{
return TSharedPtr<FNiagaraVMExecutableData>();
}
ScriptMetrics.TranslateTime = MapEntry->TranslationTime;
ActiveCompilations.Remove(JobID);
FNiagaraCompileResults& Results = CompileResult.GetValue();
FString OutGraphLevelErrorMessages;
for (const FNiagaraCompileEvent& Message : Results.CompileEvents)
{
#if defined(NIAGARA_SCRIPT_COMPILE_LOGGING_MEDIUM)
UE_LOG(LogNiagaraCompiler, Log, TEXT("%s"), *Message.Message);
#endif
if (Message.Severity == FNiagaraCompileEventSeverity::Error)
{
// Write the error messages to the string as well so that they can be echoed up the chain.
if (OutGraphLevelErrorMessages.Len() > 0)
{
OutGraphLevelErrorMessages += "\n";
}
OutGraphLevelErrorMessages += Message.Message;
}
}
Results.Data->ErrorMsg = OutGraphLevelErrorMessages;
Results.Data->LastCompileStatus = (FNiagaraCompileResults::CompileResultsToSummary(&Results));
if (Results.Data->LastCompileStatus != ENiagaraScriptCompileStatus::NCS_Error)
{
// When there are no errors the compile events get emptied, so add them back here.
Results.Data->LastCompileEvents.Append(Results.CompileEvents);
}
ScriptMetrics.CompilerWallTime = CompileResult->CompilerWallTime;
ScriptMetrics.CompilerPreprocessTime = CompileResult->CompilerPreprocessTime;
ScriptMetrics.CompilerWorkerTime = CompileResult->CompilerWorkerTime;
return CompileResult->Data;
}
void FNiagaraEditorModule::TestCompileScriptFromConsole(const TArray<FString>& Arguments)
{
if (Arguments.Num() == 1)
{
FString TranslatedHLSL;
FFileHelper::LoadFileToString(TranslatedHLSL, *Arguments[0]);
if (TranslatedHLSL.Len() != 0)
{
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_HlslCompiler_TestCompileShader_VectorVM);
FShaderCompilerInput Input;
Input.Target = FShaderTarget(SF_Compute, SP_PCD3D_SM5);
Input.VirtualSourceFilePath = TEXT("/Plugin/FX/Niagara/Private/NiagaraEmitterInstanceShader.usf");
Input.EntryPointName = TEXT("SimulateMain");
Input.Environment.SetDefine(TEXT("VM_SIMULATION"), 1);
Input.Environment.SetDefine(TEXT("COMPUTESHADER"), 1);
Input.Environment.SetDefine(TEXT("PIXELSHADER"), 0);
Input.Environment.SetDefine(TEXT("DOMAINSHADER"), 0);
Input.Environment.SetDefine(TEXT("HULLSHADER"), 0);
Input.Environment.SetDefine(TEXT("VERTEXSHADER"), 0);
Input.Environment.SetDefine(TEXT("GEOMETRYSHADER"), 0);
Input.Environment.SetDefine(TEXT("MESHSHADER"), 0);
Input.Environment.SetDefine(TEXT("AMPLIFICATIONSHADER"), 0);
Input.Environment.IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/NiagaraEmitterInstance.ush"), TranslatedHLSL);
FVectorVMCompilationOutput CompilationOutput;
double StartTime = FPlatformTime::Seconds();
bool bSucceeded = TestCompileVectorVMShader(Input, FString(FPlatformProcess::ShaderDir()), CompilationOutput, GNiagaraSkipVectorVMBackendOptimizations != 0);
float DeltaTime = (float)(FPlatformTime::Seconds() - StartTime);
if (bSucceeded)
{
UE_LOG(LogNiagaraCompiler, Log, TEXT("Test compile of %s took %f seconds and succeeded."), *Arguments[0], DeltaTime);
}
else
{
UE_LOG(LogNiagaraCompiler, Error, TEXT("Test compile of %s took %f seconds and failed. Errors: %s"), *Arguments[0], DeltaTime, *CompilationOutput.Errors);
}
}
else
{
UE_LOG(LogNiagaraCompiler, Error, TEXT("Test compile of %s failed, the file could not be loaded or it was empty."), *Arguments[0]);
}
}
else
{
UE_LOG(LogNiagaraCompiler, Error, TEXT("Test compile failed, file name argument was missing."));
}
}
ENiagaraScriptCompileStatus FNiagaraCompileResults::CompileResultsToSummary(const FNiagaraCompileResults* CompileResults)
{
ENiagaraScriptCompileStatus SummaryStatus = ENiagaraScriptCompileStatus::NCS_Unknown;
if (CompileResults != nullptr)
{
if (CompileResults->NumErrors > 0)
{
SummaryStatus = ENiagaraScriptCompileStatus::NCS_Error;
}
else
{
if (CompileResults->bVMSucceeded)
{
if (CompileResults->NumWarnings)
{
SummaryStatus = ENiagaraScriptCompileStatus::NCS_UpToDateWithWarnings;
}
else
{
SummaryStatus = ENiagaraScriptCompileStatus::NCS_UpToDate;
}
}
if (CompileResults->bComputeSucceeded)
{
if (CompileResults->NumWarnings)
{
SummaryStatus = ENiagaraScriptCompileStatus::NCS_ComputeUpToDateWithWarnings;
}
else
{
SummaryStatus = ENiagaraScriptCompileStatus::NCS_UpToDate;
}
}
}
}
return SummaryStatus;
}
int32 FHlslNiagaraCompiler::CompileScript(const FNiagaraCompileRequestData* InCompileRequest, const FNiagaraCompileOptions& InOptions, const FNiagaraTranslateResults& InTranslateResults, FNiagaraTranslatorOutput* TranslatorOutput, FString& TranslatedHLSL)
{
NiagaraCompileRequestHelper::FDebugGroupNameBuilder DebugGroupName;
NiagaraCompileRequestHelper::BuildScriptDebugGroupName(InCompileRequest, InOptions, DebugGroupName);
return CompileScript(DebugGroupName, InOptions, InTranslateResults, TranslatorOutput ? *TranslatorOutput : FNiagaraTranslatorOutput(), TranslatedHLSL);
}
int32 FHlslNiagaraCompiler::CompileScript(const FStringView GroupName, const FNiagaraCompileOptions& InOptions, const FNiagaraTranslateResults& InTranslateResults, const FNiagaraTranslatorOutput& TranslatorOutput, const FString& TranslatedHLSL)
{
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_HlslCompiler_CompileScript);
CompileResults.Data = MakeShared<FNiagaraVMExecutableData>();
//TODO: This should probably be done via the same route that other shaders take through the shader compiler etc.
//But that adds the complexity of a new shader type, new shader class and a new shader map to contain them etc.
//Can do things simply for now.
CompileResults.Data->LastHlslTranslation = TEXT("");
FShaderCompilerInput Input;
Input.Target = FShaderTarget(SF_Compute, SP_PCD3D_SM5);
Input.VirtualSourceFilePath = TEXT("/Plugin/FX/Niagara/Private/NiagaraEmitterInstanceShader.usf");
Input.EntryPointName = TEXT("SimulateMain");
Input.Environment.SetDefine(TEXT("VM_SIMULATION"), 1);
Input.Environment.SetDefine(TEXT("COMPUTESHADER"), 1);
Input.Environment.SetDefine(TEXT("PIXELSHADER"), 0);
Input.Environment.SetDefine(TEXT("DOMAINSHADER"), 0);
Input.Environment.SetDefine(TEXT("HULLSHADER"), 0);
Input.Environment.SetDefine(TEXT("VERTEXSHADER"), 0);
Input.Environment.SetDefine(TEXT("GEOMETRYSHADER"), 0);
Input.Environment.SetDefine(TEXT("MESHSHADER"), 0);
Input.Environment.SetDefine(TEXT("AMPLIFICATIONSHADER"), 0);
Input.Environment.IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/NiagaraEmitterInstance.ush"), TranslatedHLSL);
Input.DebugInfoFlags = GShaderCompilingManager->GetDumpShaderDebugInfoFlags();
Input.DumpDebugInfoRootPath = GShaderCompilingManager->GetAbsoluteShaderDebugInfoDirectory() / TEXT("VM");
Input.DebugGroupName = GroupName;
Input.DebugExtension.Empty();
Input.DumpDebugInfoPath.Empty();
FName VVMFormatName = FName(TEXT("VVM_1_0"));
//TODO: This is normally invoked by GlobalBeginCompileShader, which is not called in this path. Should it be?
const IShaderFormat* VVMShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(VVMFormatName);
VVMShaderFormat->ModifyShaderCompilerInput(Input);
if (GShaderCompilingManager->GetDumpShaderDebugInfo() == FShaderCompilingManager::EDumpShaderDebugInfo::Always)
{
Input.DumpDebugInfoPath = GShaderCompilingManager->CreateShaderDebugInfoPath(Input);
}
CompileResults.DumpDebugInfoPath = Input.DumpDebugInfoPath;
uint32 JobID = FShaderCommonCompileJob::GetNextJobId();
CompilationJob = MakeUnique<FNiagaraCompilerJob>();
CompilationJob->TranslatorOutput = TranslatorOutput;
CompileResults.bVMSucceeded = (CompilationJob->TranslatorOutput.Errors.Len() == 0) && (TranslatedHLSL.Len() > 0) && !InTranslateResults.NumErrors;
// only issue jobs for VM compilation if we're going to be using the resulting byte code. This excludes particle scripts when we're using
// a GPU simulation
const bool bCompilingGPUParticleScript = InOptions.IsGpuScript() && UNiagaraScript::IsParticleScript(InOptions.TargetUsage);
if (bCompilingGPUParticleScript)
{
CompileResults.bComputeSucceeded = false;
if (CompileResults.bVMSucceeded)
{
//Clear out current contents of compile results.
*(CompileResults.Data) = CompilationJob->TranslatorOutput.ScriptData;
CompileResults.Data->ByteCode.Reset();
CompileResults.bComputeSucceeded = true;
}
}
CompileResults.AppendCompileEvents(MakeArrayView(InTranslateResults.CompileEvents));
CompileResults.Data->LastCompileEvents.Append(InTranslateResults.CompileEvents);
CompileResults.Data->ExternalDependencies = InTranslateResults.CompileDependencies;
CompileResults.Data->CompileTags = InTranslateResults.CompileTags;
CompileResults.Data->CompileTagsEditorOnly = InTranslateResults.CompileTagsEditorOnly;
// Early out if compiling a GPU particle script as we do not need to submit a CPU compile request.
// This must be done after we add in the translator errors etc so tha they are passed to the compile job correctly.
if (bCompilingGPUParticleScript)
{
CompileResults.Data->LastHlslTranslationGPU = TranslatedHLSL;
DumpDebugInfo(CompileResults, Input, true);
CompilationJob->CompileResults = CompileResults;
return JobID;
}
CompilationJob->TranslatorOutput.ScriptData.LastHlslTranslation = TranslatedHLSL;
CompilationJob->TranslatorOutput.ScriptData.ExternalDependencies = InTranslateResults.CompileDependencies;
CompilationJob->TranslatorOutput.ScriptData.CompileTags = InTranslateResults.CompileTags;
CompilationJob->TranslatorOutput.ScriptData.CompileTagsEditorOnly = InTranslateResults.CompileTagsEditorOnly;
bool bJobScheduled = false;
if (CompileResults.bVMSucceeded)
{
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_HlslCompiler_CompileShader_VectorVM);
CompilationJob->StartTime = FPlatformTime::Seconds();
FShaderType* NiagaraShaderType = nullptr;
for (TLinkedList<FShaderType*>::TIterator ShaderTypeIt(FShaderType::GetTypeList()); ShaderTypeIt; ShaderTypeIt.Next())
{
if (FNiagaraShaderType* ShaderType = ShaderTypeIt->GetNiagaraShaderType())
{
NiagaraShaderType = ShaderType;
break;
}
}
if (NiagaraShaderType)
{
TRefCountPtr<FShaderCompileJob> Job = GShaderCompilingManager->PrepareShaderCompileJob(JobID, FShaderCompileJobKey(NiagaraShaderType), EShaderCompileJobPriority::Normal);
if (Job)
{
TArray<FShaderCommonCompileJobPtr> NewJobs;
CompilationJob->ShaderCompileJob = Job;
Input.ShaderFormat = VVMFormatName;
if (GNiagaraSkipVectorVMBackendOptimizations != 0)
{
Input.Environment.CompilerFlags.Add(CFLAG_SkipOptimizations);
}
Job->Input = Input;
NewJobs.Add(FShaderCommonCompileJobPtr(Job));
GShaderCompilingManager->SubmitJobs(NewJobs, FString(), FString());
}
bJobScheduled = true;
}
}
CompileResults.Data->LastHlslTranslation = TranslatedHLSL;
if (!bJobScheduled)
{
CompileResults.Data->ByteCode.Reset();
CompileResults.Data->Attributes.Empty();
CompileResults.Data->Parameters.Empty();
CompileResults.Data->InternalParameters.Empty();
CompileResults.Data->DataInterfaceInfo.Empty();
CompileResults.Data->UObjectInfos.Empty();
}
CompilationJob->CompileResults = CompileResults;
return JobID;
}
uint32 FHlslNiagaraCompiler::CompileScriptVM(const FStringView GroupName, const FNiagaraCompileOptions& InOptions, const FNiagaraTranslateResults& InTranslateResults, const FNiagaraTranslatorOutput& TranslatorOutput, const FString& TranslatedHLSL, FNiagaraShaderType* NiagaraShaderType)
{
check(!InOptions.IsGpuScript() || !UNiagaraScript::IsParticleScript(InOptions.TargetUsage));
CompileResults.Data = MakeShared<FNiagaraVMExecutableData>();
CompileResults.bVMSucceeded = (TranslatorOutput.Errors.Len() == 0) && (TranslatedHLSL.Len() > 0) && !InTranslateResults.NumErrors;
CompileResults.AppendCompileEvents(MakeArrayView(InTranslateResults.CompileEvents));
CompileResults.Data->LastCompileEvents.Append(InTranslateResults.CompileEvents);
CompileResults.Data->ExternalDependencies = InTranslateResults.CompileDependencies;
CompileResults.Data->CompileTags = InTranslateResults.CompileTags;
CompileResults.Data->CompileTagsEditorOnly = InTranslateResults.CompileTagsEditorOnly;
CompileResults.Data->LastHlslTranslation = TranslatedHLSL;
CompileResults.DumpDebugInfoPath.Reset();
CompilationJob = MakeUnique<FNiagaraCompilerJob>();
CompilationJob->TranslatorOutput = TranslatorOutput;
CompilationJob->TranslatorOutput.ScriptData.LastHlslTranslation = TranslatedHLSL;
CompilationJob->TranslatorOutput.ScriptData.ExternalDependencies = InTranslateResults.CompileDependencies;
CompilationJob->TranslatorOutput.ScriptData.CompileTags = InTranslateResults.CompileTags;
CompilationJob->TranslatorOutput.ScriptData.CompileTagsEditorOnly = InTranslateResults.CompileTagsEditorOnly;
CompilationJob->StartTime = FPlatformTime::Seconds();
CompilationJob->CompileResults = CompileResults;
if (CompileResults.bVMSucceeded && NiagaraShaderType)
{
const uint32 JobID = FShaderCommonCompileJob::GetNextJobId();
TRefCountPtr<FShaderCompileJob> Job = GShaderCompilingManager->PrepareShaderCompileJob(JobID, FShaderCompileJobKey(NiagaraShaderType), EShaderCompileJobPriority::Normal);
if (Job)
{
Job->Input.Target = FShaderTarget(SF_Compute, SP_PCD3D_SM5);
Job->Input.VirtualSourceFilePath = TEXT("/Plugin/FX/Niagara/Private/NiagaraEmitterInstanceShader.usf");
Job->Input.EntryPointName = TEXT("SimulateMain");
Job->Input.Environment.SetDefine(TEXT("VM_SIMULATION"), 1);
Job->Input.Environment.SetDefine(TEXT("COMPUTESHADER"), 1);
Job->Input.Environment.SetDefine(TEXT("PIXELSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("DOMAINSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("HULLSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("VERTEXSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("GEOMETRYSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("MESHSHADER"), 0);
Job->Input.Environment.SetDefine(TEXT("AMPLIFICATIONSHADER"), 0);
Job->Input.Environment.IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/NiagaraEmitterInstance.ush"), TranslatedHLSL);
Job->Input.DebugInfoFlags = GShaderCompilingManager->GetDumpShaderDebugInfoFlags();
Job->Input.DumpDebugInfoRootPath = GShaderCompilingManager->GetAbsoluteShaderDebugInfoDirectory() / TEXT("VM");
Job->Input.DumpDebugInfoPath = CompileResults.DumpDebugInfoPath;
Job->Input.DebugGroupName = GroupName;
Job->Input.DebugExtension.Empty();
Job->Input.ShaderFormat = FName(TEXT("VVM_1_0"));
//TODO: This is normally invoked by GlobalBeginCompileShader, which is not called in this path. Should it be?
if (const IShaderFormat* VVMShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(Job->Input.ShaderFormat))
{
VVMShaderFormat->ModifyShaderCompilerInput(Job->Input);
}
if (GNiagaraSkipVectorVMBackendOptimizations != 0)
{
Job->Input.Environment.CompilerFlags.Add(CFLAG_SkipOptimizations);
}
if (GShaderCompilingManager->GetDumpShaderDebugInfo() == FShaderCompilingManager::EDumpShaderDebugInfo::Always)
{
Job->Input.DumpDebugInfoPath = GShaderCompilingManager->CreateShaderDebugInfoPath(Job->Input);
CompileResults.DumpDebugInfoPath = Job->Input.DumpDebugInfoPath;
}
CompilationJob->ShaderCompileJob = Job;
TArray<FShaderCommonCompileJobPtr> NewJobs;
NewJobs.Emplace(Job);
GShaderCompilingManager->SubmitJobs(NewJobs, FString(), FString());
return JobID;
}
}
return INDEX_NONE;
}
int32 FHlslNiagaraCompiler::CreateShaderIntermediateData(const FStringView GroupName, const FNiagaraCompileOptions& InOptions, const FNiagaraTranslateResults& InTranslateResults, const FNiagaraTranslatorOutput& TranslatorOutput, const FString& TranslatedHLSL)
{
check(InOptions.IsGpuScript() && UNiagaraScript::IsParticleScript(InOptions.TargetUsage));
CompileResults.Data = MakeShared<FNiagaraVMExecutableData>();
//TODO: This should probably be done via the same route that other shaders take through the shader compiler etc.
//But that adds the complexity of a new shader type, new shader class and a new shader map to contain them etc.
//Can do things simply for now.
CompileResults.Data->LastHlslTranslation = TEXT("");
FShaderCompilerInput Input;
Input.Target = FShaderTarget(SF_Compute, SP_PCD3D_SM5);
Input.VirtualSourceFilePath = TEXT("/Plugin/FX/Niagara/Private/NiagaraEmitterInstanceShader.usf");
Input.EntryPointName = TEXT("SimulateMain");
Input.Environment.SetDefine(TEXT("VM_SIMULATION"), 1);
Input.Environment.SetDefine(TEXT("COMPUTESHADER"), 1);
Input.Environment.SetDefine(TEXT("PIXELSHADER"), 0);
Input.Environment.SetDefine(TEXT("DOMAINSHADER"), 0);
Input.Environment.SetDefine(TEXT("HULLSHADER"), 0);
Input.Environment.SetDefine(TEXT("VERTEXSHADER"), 0);
Input.Environment.SetDefine(TEXT("GEOMETRYSHADER"), 0);
Input.Environment.SetDefine(TEXT("MESHSHADER"), 0);
Input.Environment.SetDefine(TEXT("AMPLIFICATIONSHADER"), 0);
Input.Environment.IncludeVirtualPathToContentsMap.Add(TEXT("/Engine/Generated/NiagaraEmitterInstance.ush"), TranslatedHLSL);
Input.DebugInfoFlags = GShaderCompilingManager->GetDumpShaderDebugInfoFlags();
Input.DumpDebugInfoRootPath = GShaderCompilingManager->GetAbsoluteShaderDebugInfoDirectory() / TEXT("VM");
Input.DebugGroupName = GroupName;
Input.DebugExtension.Empty();
Input.DumpDebugInfoPath.Empty();
CompileResults.DumpDebugInfoPath = Input.DumpDebugInfoPath;
uint32 JobID = FShaderCommonCompileJob::GetNextJobId();
CompilationJob = MakeUnique<FNiagaraCompilerJob>();
CompilationJob->TranslatorOutput = TranslatorOutput;
CompileResults.bVMSucceeded = (CompilationJob->TranslatorOutput.Errors.Len() == 0) && (TranslatedHLSL.Len() > 0) && !InTranslateResults.NumErrors;
// only issue jobs for VM compilation if we're going to be using the resulting byte code. This excludes particle scripts when we're using
// a GPU simulation
CompileResults.bComputeSucceeded = false;
if (CompileResults.bVMSucceeded)
{
//Clear out current contents of compile results.
*(CompileResults.Data) = CompilationJob->TranslatorOutput.ScriptData;
CompileResults.Data->ByteCode.Reset();
CompileResults.bComputeSucceeded = true;
}
CompileResults.AppendCompileEvents(MakeArrayView(InTranslateResults.CompileEvents));
CompileResults.Data->LastCompileEvents.Append(InTranslateResults.CompileEvents);
CompileResults.Data->ExternalDependencies = InTranslateResults.CompileDependencies;
CompileResults.Data->CompileTags = InTranslateResults.CompileTags;
CompileResults.Data->CompileTagsEditorOnly = InTranslateResults.CompileTagsEditorOnly;
// Early out if compiling a GPU particle script as we do not need to submit a CPU compile request.
// This must be done after we add in the translator errors etc so tha they are passed to the compile job correctly.
CompileResults.Data->LastHlslTranslationGPU = TranslatedHLSL;
DumpDebugInfo(CompileResults, Input, true);
CompilationJob->CompileResults = CompileResults;
return JobID;
}
void FHlslNiagaraCompiler::FixupVMAssembly(FString& Asm)
{
const TCHAR* OpTag = TEXT("__OP__");
const TCHAR* OpDelimTags[] = { TEXT("("), TEXT(";") };
const int32 OpTagLen = FCString::Strlen(OpTag);
const int32 OpCount = VectorVM::GetNumOpCodes();
constexpr int32 MaxDigitLength = 8;
const FString OriginalAsm = MoveTemp(Asm);
const FStringView OriginalAsmView(OriginalAsm);
Asm.Reserve(OriginalAsm.Len());
int32 SearchStartLocation = 0;
while (SearchStartLocation != INDEX_NONE)
{
const int32 OpStartLocation = OriginalAsmView.Find(OpTag, SearchStartLocation);
// append the string leading up to the OpCode
const int32 SubStringLength = OpStartLocation == INDEX_NONE ? MAX_int32 : OpStartLocation - SearchStartLocation;
if (SubStringLength)
{
Asm.Append(OriginalAsmView.Mid(SearchStartLocation, SubStringLength));
SearchStartLocation = OpStartLocation;
}
if (OpStartLocation == INDEX_NONE)
{
break;
}
// find the next delimiter
const int32 OpEndLocation = OpStartLocation + OpTagLen;
FStringView DelimSearchView = OriginalAsmView.Mid(OpEndLocation, MaxDigitLength + 1);
int32 DelimOffset = INDEX_NONE;
for (const TCHAR* DelimTag : OpDelimTags)
{
int32 NextLocation = DelimSearchView.Find(DelimTag);
if (NextLocation != INDEX_NONE && (DelimOffset == INDEX_NONE || NextLocation < DelimOffset))
{
DelimOffset = NextLocation;
}
}
// check if the intervening spaces are digits (op index)
int32 OpIndex = INDEX_NONE;
if (DelimOffset != INDEX_NONE)
{
FStringView OpIndexStrView = OriginalAsmView.Mid(OpEndLocation, DelimOffset);
if (OpIndexStrView.Len() <= MaxDigitLength)
{
TStringBuilder<MaxDigitLength + 1> OpIndexStr;
OpIndexStr.Append(OpIndexStrView);
if (!LexTryParseString<int32>(OpIndex, *OpIndexStr))
{
OpIndex = INDEX_NONE;
}
}
}
if (OpIndex != INDEX_NONE && OpIndex < OpCount)
{
Asm.Append(VectorVM::GetOpName(EVectorVMOp(OpIndex)));
SearchStartLocation = OpEndLocation + DelimOffset;
}
else
{
Asm.Append(OriginalAsmView.Mid(OpStartLocation, OpTagLen));
SearchStartLocation = OpEndLocation;
}
}
}
//TODO: Map Lines of HLSL to their source Nodes and flag those nodes with errors associated with their lines.
void FHlslNiagaraCompiler::DumpDebugInfo(const FNiagaraCompileResults& CompileResult, const FShaderCompilerInput& Input, bool bGPUScript)
{
if (CompileResults.Data.IsValid())
{
// Support dumping debug info only on failure or warnings
FString DumpDebugInfoPath = CompileResult.DumpDebugInfoPath;
if (DumpDebugInfoPath.IsEmpty())
{
const FShaderCompilingManager::EDumpShaderDebugInfo DumpShaderDebugInfo = GShaderCompilingManager->GetDumpShaderDebugInfo();
bool bDumpDebugInfo = false;
if (DumpShaderDebugInfo == FShaderCompilingManager::EDumpShaderDebugInfo::OnError)
{
bDumpDebugInfo = !CompileResult.bVMSucceeded;
}
else if (DumpShaderDebugInfo == FShaderCompilingManager::EDumpShaderDebugInfo::OnErrorOrWarning)
{
bDumpDebugInfo = !CompileResult.bVMSucceeded || (CompileResult.NumErrors + CompileResult.NumWarnings) > 0;
}
if (bDumpDebugInfo)
{
DumpDebugInfoPath = GShaderCompilingManager->CreateShaderDebugInfoPath(Input);
}
}
if (!DumpDebugInfoPath.IsEmpty())
{
FString ExportText = CompileResults.Data->LastHlslTranslation;
FString ExportTextAsm = CompileResults.Data->LastAssemblyTranslation;
if (bGPUScript)
{
ExportText = CompileResults.Data->LastHlslTranslationGPU;
ExportTextAsm = "";
}
FString ExportTextParams;
for (const FNiagaraVariable& Var : CompileResults.Data->Parameters.Parameters)
{
ExportTextParams += Var.ToString();
ExportTextParams += "\n";
}
FNiagaraEditorUtilities::WriteTextFileToDisk(DumpDebugInfoPath, TEXT("NiagaraEmitterInstance.ush"), ExportText, true);
FNiagaraEditorUtilities::WriteTextFileToDisk(DumpDebugInfoPath, TEXT("NiagaraEmitterInstance.asm"), ExportTextAsm, true);
FNiagaraEditorUtilities::WriteTextFileToDisk(DumpDebugInfoPath, TEXT("NiagaraEmitterInstance.params"), ExportTextParams, true);
}
}
}
TOptional<FNiagaraCompileResults> FHlslNiagaraCompiler::GetCompileResult(int32 JobID, bool bWait /*= false*/)
{
check(IsInGameThread());
if (!CompilationJob)
{
return TOptional<FNiagaraCompileResults>();
}
if (!CompilationJob->ShaderCompileJob)
{
// In case we did not schedule any compile jobs but have a static result (e.g. in case of previous translator errors)
FNiagaraCompileResults Results = CompilationJob->CompileResults;
CompilationJob.Reset();
return Results;
}
TArray<int32> ShaderMapIDs;
ShaderMapIDs.Add(JobID);
if (bWait && !CompilationJob->ShaderCompileJob->bReleased)
{
GShaderCompilingManager->FinishCompilation(NULL, ShaderMapIDs);
check(CompilationJob->ShaderCompileJob->bReleased);
}
if (!CompilationJob->ShaderCompileJob->bReleased)
{
return TOptional<FNiagaraCompileResults>();
}
else
{
// We do this because otherwise the shader compiling manager might still reference the deleted job at the end of this method.
// The finalization flag is set by another thread, so the manager might not have had a change to process the result.
GShaderCompilingManager->FinishCompilation(NULL, ShaderMapIDs);
}
FNiagaraCompileResults Results = CompilationJob->CompileResults;
Results.bVMSucceeded = false;
FVectorVMCompilationOutput CompilationOutput;
if (CompilationJob->ShaderCompileJob->bSucceeded)
{
TConstArrayView<uint8> Code = CompilationJob->ShaderCompileJob->Output.ShaderCode.GetReadView();
FShaderCodeReader ShaderCode(Code);
FMemoryReaderView Ar(Code, true);
Ar.SetLimitSize(ShaderCode.GetActualShaderCodeSize());
Ar << CompilationOutput;
if (!CompilationOutput.Errors.IsEmpty())
{
Warning(FText::Format(LOCTEXT("VectorVMCompileWarningMessageFormat", "The Vector VM compile generated warnings:\n{0}"), FText::FromString(CompilationOutput.Errors)));
}
Results.bVMSucceeded = true;
}
else if (CompilationJob->ShaderCompileJob->Output.Errors.Num() > 0)
{
FString Errors;
for (FShaderCompilerError ShaderError : CompilationJob->ShaderCompileJob->Output.Errors)
{
Errors += ShaderError.StrippedErrorMessage + "\n";
}
Error(FText::Format(LOCTEXT("VectorVMCompileErrorMessageFormat", "The Vector VM compile failed. Errors:\n{0}"), FText::FromString(Errors)));
DumpHLSLText(Results.Data->LastHlslTranslation, CompilationJob->CompileResults.DumpDebugInfoPath);
}
if (Results.bVMSucceeded)
{
//Build internal parameters
SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_HlslCompiler_CompileShader_VectorVMSucceeded);
*Results.Data = CompilationJob->TranslatorOutput.ScriptData;
Results.Data->ByteCode.SetData(CompilationOutput.ByteCode);
Results.Data->NumTempRegisters = CompilationOutput.MaxTempRegistersUsed + 1;
Results.Data->LastAssemblyTranslation = CompilationOutput.AssemblyAsString;
FixupVMAssembly(Results.Data->LastAssemblyTranslation);
Results.Data->LastOpCount = CompilationOutput.NumOps;
if (GbForceNiagaraVMBinaryDump != 0 && Results.Data.IsValid())
{
DumpHLSLText(Results.Data->LastAssemblyTranslation, CompilationJob->CompileResults.DumpDebugInfoPath);
}
Results.Data->InternalParameters.Empty();
bool bAllFloatsFinite = true;
for (int32 i = 0; i < CompilationOutput.InternalConstantOffsets.Num(); ++i)
{
const FName ConstantName(TEXT("InternalConstant"), i);
EVectorVMBaseTypes Type = CompilationOutput.InternalConstantTypes[i];
int32 Offset = CompilationOutput.InternalConstantOffsets[i];
switch (Type)
{
case EVectorVMBaseTypes::Float:
{
float Val = *(float*)(CompilationOutput.InternalConstantData.GetData() + Offset);
Results.Data->InternalParameters.SetOrAdd(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), ConstantName))->SetValue(Val);
bAllFloatsFinite &= FMath::IsFinite(Val);
}
break;
case EVectorVMBaseTypes::Int:
{
int32 Val = *(int32*)(CompilationOutput.InternalConstantData.GetData() + Offset);
Results.Data->InternalParameters.SetOrAdd(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), ConstantName))->SetValue(Val);
}
break;
case EVectorVMBaseTypes::Bool:
{
int32 Val = *(int32*)(CompilationOutput.InternalConstantData.GetData() + Offset);
Results.Data->InternalParameters.SetOrAdd(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), ConstantName))->SetValue(Val);
}
break;
}
}
if (!bAllFloatsFinite)
{
Warning(LOCTEXT("FloatConstantsNanOrInf", "Float constant table contains NaN or Inf, this may result in invalid simulation results."));
}
Results.CompilerWallTime = (float)(FPlatformTime::Seconds() - CompilationJob->StartTime);
Results.CompilerWorkerTime = (float)CompilationJob->ShaderCompileJob->Output.CompileTime;
Results.CompilerPreprocessTime = (float)CompilationJob->ShaderCompileJob->Output.PreprocessTime;
Results.Data->CalledVMExternalFunctions.Empty(CompilationOutput.CalledVMFunctionTable.Num());
for (FCalledVMFunction& FuncInfo : CompilationOutput.CalledVMFunctionTable)
{
//Extract the external function call table binding info.
const FNiagaraFunctionSignature* Sig = nullptr;
for (FNiagaraScriptDataInterfaceCompileInfo& NDIInfo : CompilationJob->TranslatorOutput.ScriptData.DataInterfaceInfo)
{
Sig = NDIInfo.RegisteredFunctions.FindByPredicate([&](const FNiagaraFunctionSignature& CheckSig)
{
FString SigSymbol = FNiagaraHlslTranslator::GetFunctionSignatureSymbol(CheckSig);
return SigSymbol == FuncInfo.Name;
});
if (Sig)
{
break;
}
}
// Look in function library
if (Sig == nullptr)
{
Sig = UNiagaraFunctionLibrary::GetVectorVMFastPathOps(true).FindByPredicate(
[&](const FNiagaraFunctionSignature& CheckSig)
{
FString SigSymbol = FNiagaraHlslTranslator::GetFunctionSignatureSymbol(CheckSig);
return SigSymbol == FuncInfo.Name;
}
);
}
if (Sig)
{
FVMExternalFunctionBindingInfo& NewBinding = Results.Data->CalledVMExternalFunctions.AddDefaulted_GetRef();
NewBinding.Name = Sig->Name;
NewBinding.OwnerName = Sig->OwnerName;
NewBinding.InputParamLocations = FuncInfo.InputParamLocations;
NewBinding.NumOutputs = FuncInfo.NumOutputs;
for (auto it = Sig->FunctionSpecifiers.CreateConstIterator(); it; ++it)
{
// we convert the map into an array here to save runtime memory
NewBinding.FunctionSpecifiers.Emplace(it->Key, it->Value);
}
//Write out our variadic parameters to allow proper binding for VM external functions.
Sig->GetVariadicInputs(NewBinding.VariadicInputs);
Sig->GetVariadicOutputs(NewBinding.VariadicOutputs);
}
else
{
Error(FText::Format(LOCTEXT("VectorVMExternalFunctionBindingError", "Failed to bind the external function call: {0}"), FText::FromString(FuncInfo.Name)));
Results.bVMSucceeded = false;
}
}
}
if (!Results.bVMSucceeded)
{
Results.Data->ByteCode.Reset();
Results.Data->Attributes.Empty();
Results.Data->Parameters.Empty();
Results.Data->InternalParameters.Empty();
Results.Data->DataInterfaceInfo.Empty();
Results.Data->UObjectInfos.Empty();
}
DumpDebugInfo(CompileResults, CompilationJob->ShaderCompileJob->Input, false);
//Seems like Results is a bit of a cobbled together mess at this point.
//Ideally we can tidy this up in future.
//Doing this as a minimal risk free fix for not having errors passed through into the compile results.
Results.NumErrors = CompileResults.NumErrors;
Results.CompileEvents = CompileResults.CompileEvents;
Results.Data->CompileTags = CompileResults.Data->CompileTags;
Results.Data->CompileTagsEditorOnly = CompileResults.Data->CompileTagsEditorOnly;
CompilationJob.Reset();
return Results;
}
FHlslNiagaraCompiler::FHlslNiagaraCompiler()
: CompileResults()
{
}
void FHlslNiagaraCompiler::Error(FText ErrorText)
{
FString ErrorString = FString::Printf(TEXT("%s"), *ErrorText.ToString());
CompileResults.Data->LastCompileEvents.Add(FNiagaraCompileEvent(FNiagaraCompileEventSeverity::Error, ErrorString));
CompileResults.CompileEvents.Add(FNiagaraCompileEvent(FNiagaraCompileEventSeverity::Error, ErrorString));
CompileResults.NumErrors++;
}
void FHlslNiagaraCompiler::Warning(FText WarningText)
{
FString WarnString = FString::Printf(TEXT("%s"), *WarningText.ToString());
CompileResults.Data->LastCompileEvents.Add(FNiagaraCompileEvent(FNiagaraCompileEventSeverity::Warning, WarnString));
CompileResults.CompileEvents.Add(FNiagaraCompileEvent(FNiagaraCompileEventSeverity::Warning, WarnString));
CompileResults.NumWarnings++;
}
FNiagaraShaderMapCompiler::FNiagaraShaderMapCompiler(
const FNiagaraShaderType* InShaderType,
TSharedPtr<FNiagaraShaderScriptParametersMetadata> InShaderParameters)
: ShaderType(InShaderType)
, ShaderParameters(InShaderParameters)
{
}
void FNiagaraShaderMapCompiler::AddShaderPlatform(const FNiagaraShaderMapId& ShaderMapId, EShaderPlatform ShaderPlatform)
{
FActiveCompilation& ActiveCompilation = ActiveCompilations.AddDefaulted_GetRef();
ActiveCompilation.ShaderMapId = ShaderMapId;
ActiveCompilation.ShaderPlatform = ShaderPlatform;
ActiveCompilation.ShaderMap = new FNiagaraShaderMap(FNiagaraShaderMap::WorkerThread);
}
void FNiagaraShaderMapCompiler::CompileScript(
const FNiagaraVMExecutableDataId& ScriptCompileId,
const FStringView SourceName,
const FStringView DebugGroupName,
const FNiagaraCompileOptions& CompileOptions,
const FNiagaraTranslateResults& TranslateResults,
const FNiagaraTranslatorOutput& TranslatorOutput,
const FString& TranslatedHLSL,
TConstArrayView<UNiagaraDataInterface*> DataInterfaces)
{
TArray<TRefCountPtr<FShaderCommonCompileJob>> CompileJobs;
for (FActiveCompilation& ActiveCompilation : ActiveCompilations)
{
TRefCountPtr<FSharedShaderCompilerEnvironment> CompilationEnvironment = new FSharedShaderCompilerEnvironment();
CompilationEnvironment->SetDefine(TEXT("GPU_SIMULATION_SHADER"), TEXT("1"));
CompilationEnvironment->SetDefine(TEXT("NIAGARA_COMPRESSED_ATTRIBUTES_ENABLED"),
ScriptCompileId.AdditionalDefines.Contains(TEXT("CompressAttributes")) ? 1 : 0);
// Fast math breaks The ExecGrid layout script because floor(x/y) returns a bad value if x == y. Yay.
if (IsMetalPlatform(ActiveCompilation.ShaderPlatform))
{
CompilationEnvironment->CompilerFlags.Add(CFLAG_NoFastMath);
}
for (UNiagaraDataInterface* DataInterface : DataInterfaces)
{
DataInterface->ModifyCompilationEnvironment(ActiveCompilation.ShaderPlatform, *CompilationEnvironment.GetReference());
}
ActiveCompilation.ShaderMap->CreateCompileJobs(
ShaderType,
DebugGroupName,
ActiveCompilation.ShaderMapId,
TranslatedHLSL,
CompilationEnvironment,
TranslatorOutput.ScriptData.SimulationStageMetaData,
TranslatorOutput.ScriptData.SimulationStageMetaData.Num(),
ActiveCompilation.ShaderPlatform,
ShaderParameters,
ActiveCompilation.ShaderCompileJobs
);
CompileJobs.Append(ActiveCompilation.ShaderCompileJobs);
}
// we also need to populate the ExeData for the script based on the translator results. This handles all the meta data of the script.
ScriptExeData = MakeShared<FNiagaraVMExecutableData>(TranslatorOutput.ScriptData);
ScriptExeData->LastCompileEvents.Append(TranslateResults.CompileEvents);
ScriptExeData->ExternalDependencies = TranslateResults.CompileDependencies;
ScriptExeData->CompileTags = TranslateResults.CompileTags;
ScriptExeData->CompileTagsEditorOnly = TranslateResults.CompileTagsEditorOnly;
ScriptExeData->LastHlslTranslationGPU = TranslatedHLSL;
GShaderCompilingManager->SubmitJobs(CompileJobs, FString(SourceName));
}
bool FNiagaraShaderMapCompiler::ProcessCompileResults(bool bWait)
{
check(!bWait); // not currently implemented
check(IsInGameThread());
for (TArray<FActiveCompilation>::TIterator CompileIt = ActiveCompilations.CreateIterator(); CompileIt; ++CompileIt)
{
auto IsCompileJobIncomplete = [](const FShaderCommonCompileJobPtr& CompileJob) -> bool
{
return !CompileJob.IsValid() || !CompileJob->bReleased || !CompileJob->bFinalized;
};
auto IsCompileJobError = [](const FShaderCommonCompileJobPtr& CompileJob) -> bool
{
return !CompileJob->bSucceeded;
};
// make sure that all of the shader compile jobs have been released and finalized
const bool bReadyToProcess = !CompileIt->ShaderCompileJobs.ContainsByPredicate(IsCompileJobIncomplete);
if (!bReadyToProcess)
{
// todo - it might be worth keeping track of jobs that aren't getting handled because of the above
// condition. Either it's taking a long time and so it could be worth reporting, or because the
// job is lost and we'll never complete.
continue;
}
FActiveCompilation& CurrentCompilation = *CompileIt;
FCompletedCompilation& CompletedCompilation = CompletedCompilations.AddDefaulted_GetRef();
// do a first pass over all of the ShaderCompileJobs to see if any of them failed. If it did, then we don't need
// to worry about pushing out our incomplete ShaderMap and we should just report the errors
const bool bSuccessfulCompilation = !CurrentCompilation.ShaderCompileJobs.ContainsByPredicate(IsCompileJobError);
if (bSuccessfulCompilation)
{
// for now we'll process all shaders at once (need to measure the cost here)
for (const FShaderCommonCompileJobPtr& ShaderCompileJob : CurrentCompilation.ShaderCompileJobs)
{
CurrentCompilation.ShaderMap->ProcessAndFinalizeShaderCompileJob(ShaderCompileJob);
}
}
else
{
CurrentCompilation.ShaderMap->SetCompiledSuccessfully(false);
}
// pass on error/warning info
for (const FShaderCommonCompileJobPtr& ShaderCompileJob : CurrentCompilation.ShaderCompileJobs)
{
if (const FShaderCompileJob* SingleShaderJob = ShaderCompileJob->GetSingleShaderJob())
{
CompletedCompilation.CompilationErrors.Append(SingleShaderJob->Output.Errors);
}
}
// now that we've added all the results into the shader map we can move it over to CompletedCompilations
CompletedCompilation.ShaderMap = CurrentCompilation.ShaderMap;
// and remove it from the ActiveCompilations
CompileIt.RemoveCurrentSwap();
}
return ActiveCompilations.IsEmpty();
}
bool FNiagaraShaderMapCompiler::GetShaderMap(const FNiagaraShaderMapId& ShaderMapId, FNiagaraShaderMapRef& OutShaderMap, TArray<FShaderCompilerError>& OutCompilationErrors) const
{
for (const FCompletedCompilation& CompletedCompilation : CompletedCompilations)
{
if (CompletedCompilation.ShaderMap->GetShaderMapId() == ShaderMapId)
{
OutShaderMap = CompletedCompilation.ShaderMap;
OutCompilationErrors = CompletedCompilation.CompilationErrors;
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE