// 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(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 FunctionsWithUsage) { if (!InDupeGraph || !InSrcGraph) { return; } TArray& 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 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 FunctionsWithUsage) { if (!InGraph) { return; } TArray Nodes; InGraph->GetNodesOfClass(Nodes); const UEdGraphSchema_Niagara* Schema = GetDefault(); for (UEdGraphNode* Node : Nodes) { if (UNiagaraNode* InNode = Cast(Node)) { UNiagaraNodeInput* InputNode = Cast(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(DataInterface, GetTransientPackage()); SharedNameToDuplicatedDataInterfaceMap->Add(DIName, Dupe); } continue; } UNiagaraNodeFunctionCall* FunctionCallNode = Cast(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 CompileUsages = { DupeScript->GetUsage() }; UNiagaraScriptSource* DupeScriptSource = CastChecked(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& 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* DuplicatedGraphDataArray = SharedSourceGraphToDuplicatedGraphsMap->Find(FunctionGraph); check(DuplicatedGraphDataArray != nullptr && DuplicatedGraphDataArray->Num() != 0); FunctionCallNode->FunctionScript = (*DuplicatedGraphDataArray)[0].ClonedScript; } } } UNiagaraNodeEmitter* EmitterNode = Cast(InNode); if (EmitterNode) { for (TSharedPtr& 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& 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& NodesToSort, const TArray* SimStages) { if (!SimStages) return; TArray 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& Objects) { Objects.Add(SourceDeepCopy.Get()); Objects.Add(NodeGraphDeepCopy.Get()); if (SharedNameToDuplicatedDataInterfaceMap.IsValid()) { TArray 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& 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(Emitter.GetEmitterData()->GraphSource); SourceDeepCopy = ScriptSource->CreateCompilationCopy(ValidUsages); NodeGraphDeepCopy = SourceDeepCopy->NodeGraph; FEdGraphUtilities::MergeChildrenGraphsIn(NodeGraphDeepCopy.Get(), NodeGraphDeepCopy.Get(), /*bRequireSchemaMatch=*/ true); TMap FunctionsWithUsage; TArray OutputNodes; NodeGraphDeepCopy->GetNodesOfClass(OutputNodes); for (UNiagaraNodeOutput* OutputNode : OutputNodes) { TArray TraversedNodes; NodeGraphDeepCopy->BuildTraversal(TraversedNodes, OutputNode); for (UNiagaraNode* TraversedNode : TraversedNodes) { UNiagaraNodeFunctionCall* FunctionCallNode = Cast(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 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>& SourceGraphToDuplicatedGraphs : *(SharedSourceGraphToDuplicatedGraphsMap.Get())) { for (const FDuplicatedGraphData& DuplicatedGraphData : SourceGraphToDuplicatedGraphs.Value) { DuplicatedGraphData.ClonedGraph->ReleaseCompilationCopy(); } } SharedSourceGraphToDuplicatedGraphsMap->Empty(); } // clean up script sources for (TWeakObjectPtr Source : TrackedScriptSourceCopies) { if (Source.IsValid()) { Source->ReleaseCompilationCopy(); } } TrackedScriptSourceCopies.Empty(); if (SourceDeepCopy.IsValid()) { SourceDeepCopy->ReleaseCompilationCopy(); } SourceDeepCopy = nullptr; // clean up emitter data for (TSharedPtr 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& EncounterableVariables, const TArray& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray& UsagesToProcess, const TArray* SimStages, const TArray EmitterNames) { FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked("NiagaraEditor"); { ENiagaraScriptCompileStatusEnum = StaticEnum(); ENiagaraScriptUsageEnum = StaticEnum(); TArray 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* GenericStage = CastChecked(*MatchingStagePtr); SimStageName = GenericStage->IterationSource == ENiagaraIterationSource::DataInterface ? GenericStage->DataInterface.BoundVariable.GetName() : FName(); } } if (bStageEnabled) { // Map all for this output node TNiagaraParameterMapHistoryWithMetaDataBuilder 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 DataInterfaceToTopLevelNiagaraVariable; TMap> DataInterfaceParameterMapReferences; for (const FNiagaraParameterMapHistory& History : Builder.Histories) { // Find the variable indices for data interfaces. TArray 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(WritePin.Pin->LinkedTo[0]->GetOwningNode()); if (LinkedNode != nullptr && LinkedNode->IsA()) { UNiagaraDataInterface* DataInterface = CastChecked(LinkedNode)->GetDataInterface(); FCompileDataInterfaceData* DataInterfaceData = nullptr; if (DataInterface != nullptr) { TArray 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& EncounterableVariables, const TArray& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray* SimStages, const TArray& InParamStore) { FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked("NiagaraEditor"); PrecompiledHistories.Empty(); TArray 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* GenericStage = CastChecked(*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 VariableDataInterfaces) { // Collect classes for any data interfaces found in the duplicated graphs TArray DataInterfaceClasses(VariableDataInterfaces); for (const TPair>& SourceGraphToDuplicatedGraphs : *(SharedSourceGraphToDuplicatedGraphsMap.Get())) { for (const FDuplicatedGraphData& DuplicatedGraphData : SourceGraphToDuplicatedGraphs.Value) { TArray 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(PrecompileDuplicateObject(DataInterfaceClass->GetDefaultObject(true), GetTransientPackage())); SharedDataInterfaceClassToDuplicatedCDOMap->Add(DataInterfaceClass, DuplicateCDO); } } TSharedPtr FNiagaraEditorModule::Precompile(UObject* InObj, FGuid Version) { UNiagaraScript* Script = Cast(InObj); UNiagaraPrecompileContainer* Container = Cast(InObj); UNiagaraSystem* System = Cast(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 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 BasePtr = MakeShared(); BasePtr->SharedCompileDataInterfaceData = MakeShared>(); TArray> DependentRequests; FCompileConstantResolver EmptyResolver; BasePtr->SourceName = LogName; if (Script) { BasePtr->Source = Cast(Script->GetSource(Version)); const TArray EncounterableVariables; const TArray ValidUsages = { ENiagaraScriptUsage::Function, ENiagaraScriptUsage::Module, ENiagaraScriptUsage::DynamicInput }; const TArray EmitterNames; TArray StaticVars; BasePtr->FinishPrecompile(EncounterableVariables, StaticVars, EmptyResolver, ValidUsages, nullptr, EmitterNames); } else if (System) { TSharedPtr CachedTraversalSystemData = System->GetCachedTraversalData(); check(System->GetSystemSpawnScript()->GetLatestSource() == System->GetSystemUpdateScript()->GetLatestSource()); BasePtr->Source = Cast(System->GetSystemSpawnScript()->GetLatestSource()); BasePtr->bUseRapidIterationParams = System->ShouldUseRapidIterationParameters(); BasePtr->bDisableDebugSwitches = System->ShouldDisableDebugSwitches(); TArray EmitterNames; // Store off the current variables in the exposed parameters list. TArray 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 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 EmitterPtr = MakeShared(); 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(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 StaticVariablesFromSystem = ((FNiagaraGraphCachedBuiltHistory*)CachedTraversalSystemData.Get())->StaticVariables; { FCompileConstantResolver ConstantResolver(System, ENiagaraScriptUsage::SystemSpawnScript); UNiagaraScriptSource* Source = Cast(System->GetSystemSpawnScript()->GetLatestSource()); static TArray 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 Scripts; TMap 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 CachedTraversalEmitterData = Handle.GetInstance().Emitter->GetCachedTraversalData(Handle.GetInstance().Version); TArray StaticVariablesFromEmitter = StaticVariablesFromSystem; StaticVariablesFromEmitter.Append(((FNiagaraGraphCachedBuiltHistory*)CachedTraversalEmitterData.Get())->StaticVariables); TArray 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 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 ParticleEncounterableVars = EncounterableVars; BasePtr->EmitterData[i]->GatherPreCompiledVariables(TEXT("Emitter"), ParticleEncounterableVars); static TArray ParticleUsages = { ENiagaraScriptUsage::ParticleSpawnScript, ENiagaraScriptUsage::ParticleSpawnScriptInterpolated, ENiagaraScriptUsage::ParticleUpdateScript, ENiagaraScriptUsage::ParticleEventScript, ENiagaraScriptUsage::ParticleGPUComputeScript, ENiagaraScriptUsage::ParticleSimulationStageScript }; TArray OldStaticVars = BasePtr->EmitterData[i]->StaticVariables; BasePtr->EmitterData[i]->FinishPrecompile(ParticleEncounterableVars, OldStaticVars, ConstantResolver, ParticleUsages, &EmitterData->GetSimulationStages(), EmitterNames); } } { TMap ScriptDependencyMap; // Prepare rapid iteration parameters for execution. TArray> ScriptsToIterate; for (const auto& Entry : ScriptToEmitterMap) { ScriptsToIterate.Add(Entry); } for (int Index = 0; Index < ScriptsToIterate.Num(); Index++) { TTuple 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 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& 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 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 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 BasePtr = MakeShared(); TArray> DependentRequests; FCompileConstantResolver EmptyResolver; BasePtr->SharedSourceGraphToDuplicatedGraphsMap = MakeShared>>(); BasePtr->SharedNameToDuplicatedDataInterfaceMap = MakeShared>(); BasePtr->SharedDataInterfaceClassToDuplicatedCDOMap = MakeShared>(); BasePtr->OwningSystem = OwningSystem; BasePtr->OwningEmitter = OwningEmitter; TArray DataInterfaceClasses; auto CollectDataInterfaceClasses = [&](TConstArrayView 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(TargetScript->GetSource(TargetVersion)); BasePtr->ValidUsages.Add(TargetScript->GetUsage()); BasePtr->DeepCopyGraphs(Source, TargetScript->GetUsage(), EmptyResolver); TArray EncounterableScriptVariables; OwningSystemRequestData->GatherPreCompiledVariables(FString(), EncounterableScriptVariables); BasePtr->FinishPrecompileDuplicate(EncounterableScriptVariables, TArray(), EmptyResolver, nullptr, ((FNiagaraCompileRequestData*)OwningSystemRequestData)->RapidIterationParams); CollectDataInterfaceClasses(EncounterableScriptVariables); } else { TSharedPtr 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 EmitterPtr = MakeShared(); 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 StaticVariablesFromSystem = ((FNiagaraGraphCachedBuiltHistory*)CachedTraversalSystemData.Get())->StaticVariables; { UNiagaraScriptSource* Source = Cast(OwningSystem->GetSystemSpawnScript()->GetLatestSource()); TArray 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 EncounterableEmitterVariables; OwningSystemRequestData->GetDependentRequest(i)->GatherPreCompiledVariables(FString(), EncounterableEmitterVariables); if (Handle.GetIsEnabled() && Handle.GetInstance().Emitter && (OwningEmitter == nullptr || OwningEmitter == Handle.GetInstance().Emitter)) { TSharedPtr CachedTraversalEmitterData = Handle.GetInstance().Emitter->GetCachedTraversalData(Handle.GetInstance().Version); TArray 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 CookedRapidIterationParams = CompileRequest->GetUseRapidIterationParams() ? TArray() : CompileRequest->RapidIterationParams; UE_LOG(LogNiagaraEditor, Verbose, TEXT("Compiling System %s ..................................................................."), *InCompileOptions.FullName); FNiagaraCompileResults Results; FActiveCompilation ActiveCompilation; ActiveCompilation.Compiler = MakeShared(); FNiagaraTranslateResults TranslateResults; TUniquePtr 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 FNiagaraEditorModule::CacheGraphTraversal(const UObject* Obj, FGuid Version) { TSharedPtr CachedPtr = MakeShared(); const UNiagaraSystem* System = Cast(Obj); const UNiagaraEmitter* Emitter = Cast(Obj); /* * const TArray& EncounterableVariables, const TArray& InStaticVariables, FCompileConstantResolver ConstantResolver, const TArray& UsagesToProcess, const */ const TArray* SimStages = nullptr; const UNiagaraScriptSource* ScriptSource = nullptr; FCompileConstantResolver ConstantResolver; TArray EncounterableVariables; TArray StaticVariablesFromSystem; TArray StaticVariablesFromSystemAndEmitters; TArray SrcStaticVariables; TSharedPtr ParentCachedData; FString SrcUniqueEmitterName; if (System) { CachedPtr->SetSourceSystem(System); ScriptSource = CastChecked(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(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(EmitterData->GraphSource); SimStages = &EmitterData->GetSimulationStages(); ConstantResolver = FCompileConstantResolver(FVersionedNiagaraEmitter(const_cast(Emitter), Version), ENiagaraScriptUsage::EmitterSpawnScript); } TArray 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 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* GenericStage = CastChecked(*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 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 Compiler = MapEntry->Compiler; TOptional CompileResult = Compiler->GetCompileResult(JobID, bWait); if (!CompileResult) { return TSharedPtr(); } 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& 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(); //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(); 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::TIterator ShaderTypeIt(FShaderType::GetTypeList()); ShaderTypeIt; ShaderTypeIt.Next()) { if (FNiagaraShaderType* ShaderType = ShaderTypeIt->GetNiagaraShaderType()) { NiagaraShaderType = ShaderType; break; } } if (NiagaraShaderType) { TRefCountPtr Job = GShaderCompilingManager->PrepareShaderCompileJob(JobID, FShaderCompileJobKey(NiagaraShaderType), EShaderCompileJobPriority::Normal); if (Job) { TArray 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(); 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(); 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 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 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(); //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(); 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 OpIndexStr; OpIndexStr.Append(OpIndexStrView); if (!LexTryParseString(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 FHlslNiagaraCompiler::GetCompileResult(int32 JobID, bool bWait /*= false*/) { check(IsInGameThread()); if (!CompilationJob) { return TOptional(); } 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 ShaderMapIDs; ShaderMapIDs.Add(JobID); if (bWait && !CompilationJob->ShaderCompileJob->bReleased) { GShaderCompilingManager->FinishCompilation(NULL, ShaderMapIDs); check(CompilationJob->ShaderCompileJob->bReleased); } if (!CompilationJob->ShaderCompileJob->bReleased) { return TOptional(); } 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 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 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 DataInterfaces) { TArray> CompileJobs; for (FActiveCompilation& ActiveCompilation : ActiveCompilations) { TRefCountPtr 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(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::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& 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