// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #include "NiagaraCompiler.h" #include "NiagaraEditorModule.h" #include "NiagaraComponent.h" #include "NiagaraGraph.h" #include "NiagaraScriptSource.h" #include "EdGraphUtilities.h" #include "UObject/UObjectHash.h" #include "ComponentReregisterContext.h" #include "NiagaraNode.h" #include "NiagaraNodeFunctionCall.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeReadDataSet.h" #include "NiagaraNodeWriteDataSet.h" #include "NiagaraNodeOp.h" #include "NiagaraNodeConvert.h" #include "EdGraphSchema_Niagara.h" #include "ShaderFormatVectorVM.h" #include "NiagaraConstants.h" #include "NiagaraEffect.h" #include "NiagaraNodeEmitter.h" #include "NiagaraDataInterface.h" #include "NiagaraDataInterfaceStaticMesh.h" #include "NiagaraDataInterfaceCurlNoise.h" #include "ShaderCore.h" #define LOCTEXT_NAMESPACE "NiagaraCompiler" DEFINE_LOG_CATEGORY_STATIC(LogNiagaraCompiler, All, All); template void TraverseGraphFromOutputDepthFirst(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraNode* Node, Action& VisitAction, TSet VisitedNodes) { if (VisitedNodes.Contains(Node) == false) { VisitedNodes.Add(Node); TArray InputPins; Node->GetInputPins(InputPins); for (UEdGraphPin* InputPin : InputPins) { // TODO: Error here if there are multiple links or non-niagara nodes? if (InputPin->LinkedTo.Num() == 1) { UNiagaraNode* LinkedNiagaraNode = Cast(InputPin->LinkedTo[0]->GetOwningNode()); if (LinkedNiagaraNode != nullptr) { TraverseGraphFromOutputDepthFirst(Compiler, Schema, LinkedNiagaraNode, VisitAction, VisitedNodes); } } } VisitAction(Schema, Node); } } void FixUpNumericPinsVisitor(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraNode* Node) { // Fix up numeric input pins and keep track of numeric types to decide the output type. TArray InputTypes; TArray InputPins; Node->GetInputPins(InputPins); for (UEdGraphPin* InputPin : InputPins) { if (InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) { FNiagaraTypeDefinition InputPinType = Schema->PinToTypeDefinition(InputPin); // If the input pin is the generic numeric type set it to the type of the linked output pin which should have been processed already. if (InputPinType == FNiagaraTypeDefinition::GetGenericNumericDef() && InputPin->LinkedTo.Num() == 1) { UEdGraphPin* InputPinLinkedPin = InputPin->LinkedTo[0]; FNiagaraTypeDefinition InputPinLinkedPinType = Schema->PinToTypeDefinition(InputPinLinkedPin); if (InputPinLinkedPinType.IsValid()) { // Only update the input pin type if the linked pin type is valid. InputPin->PinType = Schema->TypeDefinitionToPinType(InputPinLinkedPinType); InputPinType = InputPinLinkedPinType; } } if (InputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) { Compiler.Error(LOCTEXT("NumericPinError", "Unable to deduce type for numeric input pin."), Node, InputPin); } InputTypes.Add(InputPinType); } } // Fix up numeric outputs based on the inputs. if (InputTypes.Num() > 0 && Node->GetNumericOutputTypeSelectionMode() != ENiagaraNumericOutputTypeSelectionMode::None) { FNiagaraTypeDefinition OutputNumericType = FNiagaraTypeDefinition::GetNumericOutputType(InputTypes, Node->GetNumericOutputTypeSelectionMode()); if (OutputNumericType != FNiagaraTypeDefinition::GetGenericNumericDef()) { TArray OutputPins; Node->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { FNiagaraTypeDefinition OutputPinType = Schema->PinToTypeDefinition(OutputPin); if (OutputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) { OutputPin->PinType = Schema->TypeDefinitionToPinType(OutputNumericType); } } } } } void FixUpNumericPins(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraNode* Node) { auto FixUpVisitor = [&](const UEdGraphSchema_Niagara* LSchema, UNiagaraNode* LNode) { FixUpNumericPinsVisitor(Compiler, LSchema, LNode); }; TSet VisitedNodes; TraverseGraphFromOutputDepthFirst(Compiler, Schema, Node, FixUpVisitor, VisitedNodes); } /* Go through the graph and attempt to auto-detect the type of any numeric pins by working back from the leaves of the graph. Only change the types of pins, not FNiagaraVariables.*/ void PreprocessGraph(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraGraph* Graph) { UNiagaraNodeOutput* OutputNode = Graph->FindOutputNode(); FixUpNumericPins(Compiler, Schema, OutputNode); } /* Go through the graph and force any input nodes with Numeric types to a hard-coded type of float. This will allow modules and functions to compile properly.*/ void PreProcessGraphForInputNumerics(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraGraph* Graph, TArray& OutChangedNumericParams) { // Visit all input nodes TArray InputNodes; Graph->FindInputNodes(InputNodes); for (UNiagaraNodeInput* InputNode : InputNodes) { // See if any of the output pins are of Numeric type. If so, force to floats. TArray OutputPins; InputNode->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { FNiagaraTypeDefinition OutputPinType = Schema->PinToTypeDefinition(OutputPin); if (OutputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) { OutputPin->PinType = Schema->TypeDefinitionToPinType(FNiagaraTypeDefinition::GetFloatDef()); } } // Record that we touched this variable for later cleanup and make sure that the // variable's type now matches the pin. if (InputNode->Input.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()) { OutChangedNumericParams.Add(InputNode->Input); InputNode->Input.SetType(FNiagaraTypeDefinition::GetFloatDef()); } } } /* Should be called after all pins have been successfully auto-detected for type. This goes through and synchronizes any Numeric FNiagaraVarible outputs with the deduced pin type. This will allow modules and functions to compile properly.*/ void PreProcessGraphForAttributeNumerics(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraGraph* Graph, TArray& OutChangedNumericParams) { // Visit the output node UNiagaraNodeOutput* OutputNode = Graph->FindOutputNode(); if (OutputNode != nullptr) { // For each pin, make sure that if it has a valid type, but the associated variable is still Numeric, // force the variable to match the pin's new type. Record that we touched this variable for later cleanup. TArray InputPins; OutputNode->GetInputPins(InputPins); check(OutputNode->Outputs.Num() == InputPins.Num()); for (int32 i = 0; i < InputPins.Num(); i++) { FNiagaraVariable& Param = OutputNode->Outputs[i]; UEdGraphPin* InputPin = InputPins[i]; FNiagaraTypeDefinition InputPinType = Schema->PinToTypeDefinition(InputPin); if (Param.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef() && InputPinType != FNiagaraTypeDefinition::GetGenericNumericDef()) { OutChangedNumericParams.Add(Param); Param.SetType(InputPinType); } } } } /* Clean up the lingering effects of PreProcessGraphForInputNumerics and PreProcessGraphForAttributeNumerics by resetting the FNiagaraVariables back to their original types.*/ void RevertParametersToNumerics(FHlslNiagaraCompiler& Compiler, UNiagaraScript* Script, const TArray& ChangedNumericParams) { // We either changed an input node variable or an output node variable in the prior functions, let's // check where those ended up and fixup any discrepencies. for (const FNiagaraVariable& ChangedVariable : ChangedNumericParams) { // Check input variables... we use id's here because id's are synchronized and valid for input parameters. FNiagaraVariable* CorrespondingVariable = Script->Parameters.FindParameter(ChangedVariable.GetId()); if (CorrespondingVariable == nullptr) { // Check output variables... we use names instead of id's b/c output variables don't currently have valid id's. // @TODO update to id's when we eventually go to id's. CorrespondingVariable = Script->Attributes.FindByPredicate([&](FNiagaraVariable& Attribute) { return Attribute.GetName() == ChangedVariable.GetName(); }); } // Convert back to Numeric so that we maintain consistency externally. if (CorrespondingVariable != nullptr) { check(ChangedVariable.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()); CorrespondingVariable->SetType(ChangedVariable.GetType()); } else { // This should never happen! TArray Args; Args.Add(ChangedVariable.ToString()); FString ErrorText = FString::Format(TEXT("Unable to find parameter '{0}' in outputs!"), Args); Compiler.Error(FText::FromString(ErrorText), nullptr, nullptr); } } } void PreprocessFunctionGraph(FHlslNiagaraCompiler& Compiler, const UEdGraphSchema_Niagara* Schema, UNiagaraGraph* Graph, const UNiagaraNodeFunctionCall* FunctionCall) { // Change any numeric inputs or outputs to match the types from the call node. TArray InputNodes; Graph->FindInputNodes(InputNodes); TArray CallInputs; FunctionCall->GetInputPins(CallInputs); for (UNiagaraNodeInput* InputNode : InputNodes) { FNiagaraVariable& Input = InputNode->Input; if (Input.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()) { UEdGraphPin** MatchingPin = CallInputs.FindByPredicate([&](UEdGraphPin* Pin) { return *(Pin->PinName) == Input.GetName(); }); if (MatchingPin != nullptr) { FNiagaraTypeDefinition PinType = Schema->PinToTypeDefinition(*MatchingPin); Input.SetType(PinType); TArray OutputPins; InputNode->GetOutputPins(OutputPins); check(OutputPins.Num() == 1); OutputPins[0]->PinType = (*MatchingPin)->PinType; } } } UNiagaraNodeOutput* OutputNode = Graph->FindOutputNode(); TArray InputPins; OutputNode->GetInputPins(InputPins); TArray CallOutputs; FunctionCall->GetOutputPins(CallOutputs); for (FNiagaraVariable& Output : OutputNode->Outputs) { if (Output.GetType() == FNiagaraTypeDefinition::GetGenericNumericDef()) { UEdGraphPin** MatchingPin = CallOutputs.FindByPredicate([&](UEdGraphPin* Pin) { return *(Pin->PinName) == Output.GetName(); }); if (MatchingPin != nullptr) { FNiagaraTypeDefinition PinType = Schema->PinToTypeDefinition(*MatchingPin); Output.SetType(PinType); } } } FixUpNumericPins(Compiler, Schema, OutputNode); } ENiagaraScriptCompileStatus FNiagaraCompileResults::CompileResultsToSummary(const FNiagaraCompileResults* CompileResults) { ENiagaraScriptCompileStatus SummaryStatus = ENiagaraScriptCompileStatus::NCS_Unknown; if (CompileResults != nullptr) { if (CompileResults->MessageLog->NumErrors > 0) { SummaryStatus = ENiagaraScriptCompileStatus::NCS_Error; } else if (CompileResults->bSuceeded) { if (CompileResults->MessageLog->NumWarnings) { SummaryStatus = ENiagaraScriptCompileStatus::NCS_UpToDateWithWarnings; } else { SummaryStatus = ENiagaraScriptCompileStatus::NCS_UpToDate; } } } return SummaryStatus; } ENiagaraScriptCompileStatus FNiagaraEditorModule::CompileEffectScript(UNiagaraScript* ScriptToCompile, FString& OutGraphLevelErrorMessages) { ENiagaraScriptCompileStatus Results = ENiagaraScriptCompileStatus::NCS_Error; if (ScriptToCompile == nullptr) { OutGraphLevelErrorMessages = TEXT("Missing script"); return Results; } UNiagaraEffect* Effect = Cast(ScriptToCompile->GetOuter()); if (Effect == nullptr) { OutGraphLevelErrorMessages = TEXT("Missing Effect"); return Results; } UNiagaraGraph* EffectGraph = Cast(ScriptToCompile->Source)->NodeGraph; TArray InputNodes; if (EffectGraph) { EffectGraph->GetNodesOfClass(InputNodes); } // Update existing parameters and add new parameters to the script since it's not compiled. TSet HandledParameterIds; TSet HandledDataSourceIds; bool bParameterAdded = false; for (UNiagaraNodeInput* InputNode : InputNodes) { if (InputNode->Usage == ENiagaraInputNodeUsage::Parameter && (HandledParameterIds.Contains(InputNode->Input.GetId()) == false && HandledDataSourceIds.Contains(InputNode->Input.GetId()) == false)) { const UClass* InputClass = InputNode->Input.GetType().GetClass(); bool isDataSource = InputClass != nullptr && InputClass->IsChildOf(UNiagaraDataInterface::StaticClass()); bool ParameterFound = false; if (isDataSource) { for (FNiagaraScriptDataInterfaceInfo& DataInterfaceInfo : ScriptToCompile->DataInterfaceInfo) { if (DataInterfaceInfo.Id == InputNode->Input.GetId()) { ParameterFound = true; FNiagaraTypeDefinition TypeDef(InputClass); // If graph node's type has changed, update the parameter's type and value. if (TypeDef != InputNode->Input.GetType()) { DataInterfaceInfo.DataInterface = Cast(StaticDuplicateObject(InputNode->DataInterface, ScriptToCompile, NAME_None, ~RF_Transient)); //DataInterfaceInfo.ExternalFunctions = DataInterfaceInfo.ExternalFunctions; } // Copy over the value if it has changed. if (InputNode->DataInterface != nullptr && false == InputNode->DataInterface->Equals(DataInterfaceInfo.DataInterface)) { DataInterfaceInfo.DataInterface = Cast(StaticDuplicateObject(InputNode->DataInterface, ScriptToCompile, NAME_None, ~RF_Transient)); } #if WITH_EDITORONLY_DATA if (DataInterfaceInfo.Name != InputNode->Input.GetName()) { DataInterfaceInfo.Name = InputNode->Input.GetName(); } #endif } } // Otherwise add a new one. if (ParameterFound == false) { int32 Index = ScriptToCompile->DataInterfaceInfo.AddDefaulted(); FNiagaraScriptDataInterfaceInfo& DataInterfaceInfo = ScriptToCompile->DataInterfaceInfo[Index]; DataInterfaceInfo.DataInterface = Cast(StaticDuplicateObject(InputNode->DataInterface, ScriptToCompile, NAME_None, ~RF_Transient)); //DataInterfaceInfo.ExternalFunctions = DataInterfaceInfo.ExternalFunctions; DataInterfaceInfo.Id = InputNode->Input.GetId(); #if WITH_EDITORONLY_DATA DataInterfaceInfo.Name = InputNode->Input.GetName(); #endif bParameterAdded = true; } HandledDataSourceIds.Add(InputNode->Input.GetId()); } else { // Try to find an existing parameter. for (FNiagaraVariable& Parameter : ScriptToCompile->Parameters.Parameters) { if (Parameter.GetId() == InputNode->Input.GetId()) { ParameterFound = true; // If graph node's type has changed, update the parameter's type and value. if (Parameter.GetType() != InputNode->Input.GetType()) { Parameter.SetType(InputNode->Input.GetType()); Parameter.AllocateData(); Parameter.SetData(InputNode->Input.GetData()); } if (Parameter.GetName() != InputNode->Input.GetName()) { Parameter.SetName(InputNode->Input.GetName()); } } } // Otherwise add a new one. if (ParameterFound == false) { ScriptToCompile->Parameters.Parameters.Add(InputNode->Input); bParameterAdded = true; } HandledParameterIds.Add(InputNode->Input.GetId()); } } } // Remove parameters which are no longer relevant auto RemovePredicate = [&](const FNiagaraVariable& Parameter) { return HandledParameterIds.Contains(Parameter.GetId()) == false; }; ScriptToCompile->Parameters.Parameters.RemoveAll(RemovePredicate); auto RemoveDataSourcePredicate = [&](const FNiagaraScriptDataInterfaceInfo& Info) { return HandledDataSourceIds.Contains(Info.Id) == false; }; ScriptToCompile->DataInterfaceInfo.RemoveAll(RemoveDataSourcePredicate); // Rebuild the parameter bindings based on the graph Effect->ClearParameterBindings(); Effect->ClearDataInterfaceBindings(); FString Errors = ""; for (UNiagaraNodeInput* InputNode : InputNodes) { TArray OutputPins; InputNode->GetOutputPins(OutputPins); for (UEdGraphPin* OutputPin : OutputPins) { for (UEdGraphPin* LinkedPin : OutputPin->LinkedTo) { UNiagaraNodeEmitter* LinkedEmitter = Cast(LinkedPin->GetOwningNode()); if (LinkedEmitter != nullptr) { const UClass* InputClass = InputNode->Input.GetType().GetClass(); bool IsDataSource = InputClass != nullptr && InputClass->IsChildOf(UNiagaraDataInterface::StaticClass()); if (UNiagaraNodeEmitter::IsEmitterInternalParameter(LinkedPin->PinName)) { Effect->AddEmitterInternalVariableBinding(InputNode->Input.GetId(), LinkedEmitter->GetEmitterHandleId(), LinkedPin->PinName); continue; } if (IsDataSource) { Effect->AddDataInterfaceBinding(FNiagaraParameterBinding(InputNode->Input.GetId(), LinkedEmitter->GetEmitterHandleId(), LinkedPin->PersistentGuid)); } else { Effect->AddParameterBinding(FNiagaraParameterBinding(InputNode->Input.GetId(), LinkedEmitter->GetEmitterHandleId(), LinkedPin->PersistentGuid)); } UNiagaraEmitterProperties* Emitter = nullptr; UNiagaraEffect* OwnerEffect = LinkedEmitter->GetOwnerEffect(); check(OwnerEffect == Effect); for (int32 i = 0; i < OwnerEffect->GetNumEmitters(); ++i) { if (OwnerEffect->GetEmitterHandle(i).GetId() == LinkedEmitter->GetEmitterHandleId()) { Emitter = OwnerEffect->GetEmitterHandle(i).GetInstance(); } } if (Emitter != nullptr) { TArray ScriptsToValidate; FNiagaraTypeDefinition TypeDefinition; if (Emitter->UpdateScriptProps.Script != nullptr) { ScriptsToValidate.Add(Emitter->UpdateScriptProps.Script); } if (Emitter->SpawnScriptProps.Script != nullptr) { ScriptsToValidate.Add(Emitter->SpawnScriptProps.Script); } if (Emitter->EventHandlerScriptProps.Script != nullptr) { ScriptsToValidate.Add(Emitter->EventHandlerScriptProps.Script); } for (UNiagaraScript* NiagaraScript : ScriptsToValidate) { FNiagaraVariable* VariableToValidate = NiagaraScript->Parameters.FindParameter(LinkedPin->PersistentGuid); const FNiagaraScriptDataInterfaceInfo* DataSrcToValidate = NiagaraScript->DataInterfaceInfo.FindByPredicate([&](const FNiagaraScriptDataInterfaceInfo& Info) { return Info.Id == LinkedPin->PersistentGuid; }); if (!IsDataSource) { if (VariableToValidate != nullptr) { TypeDefinition = VariableToValidate->GetType(); if (InputNode->Input.GetType() != TypeDefinition) { TArray Args; Args.Add(InputNode->Input.GetName().ToString()); Args.Add(InputNode->Input.GetType().GetName()); Args.Add(VariableToValidate->GetName().ToString()); Args.Add(TypeDefinition.GetName()); Errors += FString::Format(TEXT("Cannot convert '{0}' of type {1} to '{2}' of type {3}! The runtime will fall back to the default of the emitter.\n"), Args); } } else if (DataSrcToValidate != nullptr) { TArray Args; Args.Add(InputNode->Input.GetName().ToString()); Args.Add(InputNode->Input.GetType().GetName()); Args.Add(LinkedPin->GetDisplayName().ToString()); Errors += FString::Format(TEXT("Cannot convert '{0}' of type {1} to '{2}', possible parameter to data source mismatch! The runtime will fall back to the default of the emitter.\n"), Args); } } else { if (DataSrcToValidate != nullptr && DataSrcToValidate->DataInterface != nullptr) { TypeDefinition = FNiagaraTypeDefinition(DataSrcToValidate->DataInterface->GetClass()); if (InputNode->Input.GetType() != TypeDefinition) { TArray Args; Args.Add(InputNode->Input.GetName().ToString()); Args.Add(InputNode->Input.GetType().GetName()); Args.Add(DataSrcToValidate->Name.ToString()); Args.Add(TypeDefinition.GetName()); Errors += FString::Format(TEXT("Cannot convert '{0}' of type {1} to '{2}' of type {3}! The runtime will fall back to the default of the emitter.\n"), Args); } } else if (VariableToValidate != nullptr) { TArray Args; Args.Add(InputNode->Input.GetName().ToString()); Args.Add(InputNode->Input.GetType().GetName()); Args.Add(LinkedPin->GetDisplayName().ToString()); Errors += FString::Format(TEXT("Cannot convert '{0}' of type {1} to '{2}', possible parameter to data source mismatch! The runtime will fall back to the default of the emitter.\n"), Args); } } } } } } } } if (Errors.Len() != 0) { UE_LOG(LogNiagaraEditor, Warning, TEXT("Compile errors: %s"), *Errors); OutGraphLevelErrorMessages = Errors; } else { UE_LOG(LogNiagaraEditor, Log, TEXT("Compile succeeded: %s"), *ScriptToCompile->GetPathName()); OutGraphLevelErrorMessages.Empty(); Results = ENiagaraScriptCompileStatus::NCS_UpToDate; } return Results; } ENiagaraScriptCompileStatus FNiagaraEditorModule::CompileScript(UNiagaraScript* ScriptToCompile, FString& OutGraphLevelErrorMessages) { check(ScriptToCompile != NULL); UNiagaraGraph* Graph = Cast(ScriptToCompile->Source)->NodeGraph; OutGraphLevelErrorMessages.Empty(); FNiagaraCompileResults Results; FHlslNiagaraCompiler Compiler; if (ScriptToCompile && ScriptToCompile->Usage == ENiagaraScriptUsage::EffectScript) { ENiagaraScriptCompileStatus Status = CompileEffectScript(ScriptToCompile, OutGraphLevelErrorMessages); Graph->MarkOtherSynchronized(ScriptToCompile->ChangeId); ScriptToCompile->LastCompileStatus = Status; return Status; } else { Results = Compiler.CompileScript(ScriptToCompile); Graph->MarkOtherSynchronized(ScriptToCompile->ChangeId); } if (Results.bSuceeded) { UE_LOG(LogNiagaraCompiler, Log, TEXT("Compile succeeded: %s"), *ScriptToCompile->GetPathName()); } else { UE_LOG(LogNiagaraCompiler, Error, TEXT("Compile failed: %s"), *ScriptToCompile->GetPathName()); } for (TSharedRef Message : Results.MessageLog->Messages) { if (Message->GetSeverity() == EMessageSeverity::Info) { UE_LOG(LogNiagaraCompiler, Log, TEXT("%s"), *Message->ToText().ToString()); } else if (Message->GetSeverity() == EMessageSeverity::Warning || Message->GetSeverity() == EMessageSeverity::PerformanceWarning) { UE_LOG(LogNiagaraCompiler, Warning, TEXT("%s"), *Message->ToText().ToString()); } else if (Message->GetSeverity() == EMessageSeverity::Error || Message->GetSeverity() == EMessageSeverity::CriticalError) { UE_LOG(LogNiagaraCompiler, Error, TEXT("%s"), *Message->ToText().ToString()); // 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->ToText().ToString(); } } UE_LOG(LogNiagaraCompiler, Log, TEXT("Compile output as text:")); UE_LOG(LogNiagaraCompiler, Log, TEXT("===================================================================================")); TArray OutputByLines; Results.OutputHLSL.ParseIntoArrayLines(OutputByLines, false); for (int32 i = 0; i < OutputByLines.Num(); i++) { UE_LOG(LogNiagaraCompiler, Log, TEXT("/*%d*/\t\t%s"), i, *OutputByLines[i]); } UE_LOG(LogNiagaraCompiler, Log, TEXT("===================================================================================")); ScriptToCompile->LastCompileStatus = FNiagaraCompileResults::CompileResultsToSummary(&Results); return ScriptToCompile->LastCompileStatus; } FString FHlslNiagaraCompiler::GetCode(int32 ChunkIdx) { FNiagaraCodeChunk& Chunk = CodeChunks[ChunkIdx]; return GetCode(Chunk); } FString FHlslNiagaraCompiler::GetCode(FNiagaraCodeChunk& Chunk) { TArray Args; for (int32 i = 0; i < Chunk.SourceChunks.Num(); ++i) { Args.Add(GetCodeAsSource(Chunk.SourceChunks[i])); } FString DefinitionString = FString::Format(*Chunk.Definition, Args); FString FinalString; if (Chunk.Mode == ENiagaraCodeChunkMode::Body) { FinalString += TEXT("\t"); } if (Chunk.SymbolName.IsEmpty()) { check(!DefinitionString.IsEmpty()); FinalString += DefinitionString + TEXT(";\n"); } else { if (DefinitionString.IsEmpty()) { check(Chunk.bDecl);//Otherwise, we're doing nothing here. FinalString += GetStructHlslTypeName(Chunk.Type) + TEXT(" ") + Chunk.SymbolName + TEXT(";\n"); } else { if (Chunk.bDecl) { FinalString += GetStructHlslTypeName(Chunk.Type) + TEXT(" ") + Chunk.SymbolName + TEXT(" = ") + DefinitionString + TEXT(";\n"); } else { FinalString += Chunk.SymbolName + TEXT(" = ") + DefinitionString + TEXT(";\n"); } } } return FinalString; } FString FHlslNiagaraCompiler::GetCodeAsSource(int32 ChunkIdx) { if(ChunkIdx>=0 && ChunkIdxGetAllPins()) { if (Pin->PinType.PinCategory == "") { Error(LOCTEXT("InvalidPinTypeError", "Node pin has an undefined type."), NodeToValidate, Pin); bPinsAreValid = false; } else if (Pin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) { FNiagaraTypeDefinition Type = Schema->PinToTypeDefinition(Pin); if (Type.IsValid() == false) { Error(LOCTEXT("InvalidPinTypeError", "Node pin has an undefined type."), NodeToValidate, Pin); bPinsAreValid = false; } } } return bPinsAreValid; } void FHlslNiagaraCompiler::GenerateFunctionSignature(UNiagaraScript* FunctionScript, UNiagaraGraph* FuncGraph, TArray& Inputs, FNiagaraFunctionSignature& OutSig)const { TArray InputVars; TArray InputsNodes; UNiagaraGraph::FFindInputNodeOptions Options; Options.bSort = true; Options.bFilterDuplicates = true; FuncGraph->FindInputNodes(InputsNodes, Options); check(Inputs.Num() == InputsNodes.Num()); for (int32 i = 0; i < InputsNodes.Num(); ++i) { //Only add to the signature if the caller has provided it, otherwise we use a local default. if (Inputs[i] != INDEX_NONE) { InputVars.Add(InputsNodes[i]->Input); } } //Now actually remove the missing inputs so they match the signature. Inputs.Remove(INDEX_NONE); TArray OutputVars; if (UNiagaraNodeOutput* OutputNode = FuncGraph->FindOutputNode()) { for (FNiagaraVariable& Var : OutputNode->Outputs) { OutputVars.AddUnique(Var); } } OutSig = FNiagaraFunctionSignature(*FunctionScript->GetName(), InputVars, OutputVars, *FunctionScript->GetFullName(), true, false); } ////////////////////////////////////////////////////////////////////////// FHlslNiagaraCompiler::FHlslNiagaraCompiler() : Script(nullptr) , Schema(nullptr) , CompileResults(&MessageLog) , bInsideInterpolatedSpawnScript(false) { // Make the message log silent so we're not spamming the blueprint log. MessageLog.bSilentMode = true; } FString FHlslNiagaraCompiler::GetFunctionDefinitions() { FString FwdDeclString; FString DefinitionsString; for (TPair FuncPair : Functions) { FString Sig = GetFunctionSignature(FuncPair.Key); FwdDeclString += Sig + TEXT(";\n"); if (!FuncPair.Value.IsEmpty()) { DefinitionsString += Sig + TEXT("\n{\n") + FuncPair.Value + TEXT("}\n\n"); } } return FwdDeclString + TEXT("\n") + DefinitionsString; } UNiagaraGraph* FHlslNiagaraCompiler::CloneGraphAndPrepareForCompilation(UNiagaraScript* InScript, bool bClearErrors) { UNiagaraScriptSource* Source = Cast(InScript->Source); if (Source == nullptr) { Error(LOCTEXT("NoSourceErrorMessage", "Script has no source."), nullptr, nullptr); return nullptr; } if (bClearErrors) { //Clear previous graph errors. bool bHasClearedGraphErrors = false; for (UEdGraphNode* Node : Source->NodeGraph->Nodes) { if (Node->bHasCompilerMessage) { Node->ErrorMsg.Empty(); Node->ErrorType = EMessageSeverity::Info; Node->bHasCompilerMessage = false; Node->Modify(true); bHasClearedGraphErrors = true; } } if (bHasClearedGraphErrors) { Source->NodeGraph->NotifyGraphChanged(); } } // Clone the source graph so we can modify it as needed; merging in the child graphs UNiagaraGraph* SourceGraph = CastChecked(FEdGraphUtilities::CloneGraph(Source->NodeGraph, Source, &MessageLog)); FEdGraphUtilities::MergeChildrenGraphsIn(SourceGraph, SourceGraph, /*bRequireSchemaMatch=*/ true); TArray ChangedNumericParams; // In the case of functions or modules, we may not have enough information at this time to fully resolve the type. In that case, // we circumvent the resulting errors by forcing a type. This gives the user an appropriate level of type checking. We will, however need to clean this up in // the parameters that we output. bool bForceParametersToResolveNumerics = InScript->IsFunctionScript() || InScript->IsModuleScript(); if (bForceParametersToResolveNumerics) { PreProcessGraphForInputNumerics(*this, Schema, SourceGraph, ChangedNumericParams); } // Auto-deduce the input types for numerics in the graph and overwrite the types on the pins. If PreProcessGraphForInputNumerics occurred, then // we will have pre-populated the inputs with valid types. PreprocessGraph(*this, Schema, SourceGraph); // Now that we've auto-deduced the types, we need to handle any lingering Numerics in the Output's FNiagaraVariable outputs. // We use the pin's deduced type to temporarily overwrite the variable's type. if (bForceParametersToResolveNumerics) { PreProcessGraphForAttributeNumerics(*this, Schema, SourceGraph, ChangedNumericParams); } return SourceGraph; } const FNiagaraCompileResults& FHlslNiagaraCompiler::CompileScript(UNiagaraScript* InScript) { check(InScript); CompileResults.bSuceeded = false; CompileResults.OutputHLSL = ""; Script = InScript; //Should we roll our own message/error log and put it in a window somewhere? MessageLog.SetSourcePath(InScript->GetPathName()); UNiagaraGraph* SourceGraph = CloneGraphAndPrepareForCompilation(InScript, true); if (!SourceGraph) { CompileResults.bSuceeded = false; return CompileResults; } // Find the output node and compile it. UNiagaraNodeOutput* OutputNode = SourceGraph->FindOutputNode(); ValidateTypePins(OutputNode); //Create main scope pin cache. PinToCodeChunks.AddDefaulted(1); Script->StatScopes.Empty(); EnterStatsScope(FNiagaraStatScope(*Script->GetFullName(), FText::FromString(Script->GetFullName()))); INiagaraCompiler* ThisCompiler = this; TArray OutputChunks; if (Script->IsInterpolatedSpawnScript()) { //Here we compile the spawn script but write to temporary outputs in the context. AddBodyChunk(TEXT(""), TEXT("//Interpolated Spawn Script!"), FNiagaraTypeDefinition::GetIntDef(), false); AddBodyChunk(TEXT(""), TEXT("//Begin Spawn Script!\n"), FNiagaraTypeDefinition::GetIntDef(), false); bInsideInterpolatedSpawnScript = true; OutputNode->Compile(ThisCompiler, OutputChunks); bInsideInterpolatedSpawnScript = false; AddBodyChunk(TEXT(""), TEXT("//End Spawn Script!\n"), FNiagaraTypeDefinition::GetIntDef(), false); AddBodyChunk(TEXT(""), TEXT("//Begin Update Script!\n"), FNiagaraTypeDefinition::GetIntDef(), false); //Now we compile the update script (with partial dt) and read from the temp values written above. UNiagaraScript* UpdateScript = Script->GetCompanionUpdateScript(); check(UpdateScript); UNiagaraGraph* UpdateSourceGraph = CloneGraphAndPrepareForCompilation(UpdateScript, false); UNiagaraNodeOutput* UpdateOutput = UpdateSourceGraph->FindOutputNode(); check(UpdateOutput); UpdateOutput->Compile(ThisCompiler, OutputChunks); AddBodyChunk(TEXT(""), TEXT("//End Update Script!\n"), FNiagaraTypeDefinition::GetIntDef(), false); } else { OutputNode->Compile(ThisCompiler, OutputChunks); } ExitStatsScope(); CompileResults.bSuceeded = MessageLog.NumErrors == 0; //If we're compiling a function then we have all we need already, we don't want to actually generate shader/vm code. if (FunctionCtx()) return CompileResults; //Now evaluate all the code chunks to generate the shader code. FString HlslOutput; if (CompileResults.bSuceeded) { //TODO: Declare all used structures up here too. InScript->ReadDataSets.Empty(); InScript->WriteDataSets.Empty(); for (FNiagaraTypeDefinition Type : StructsToDefine) { HlslOutput += BuildHLSLStructDecl(Type); } //Generate function definitions FString FunctionDefinitionString = GetFunctionDefinitions(); FunctionDefinitionString += TEXT("\n"); WriteDataSetStructDeclarations(DataSetReadInfo[0], true, HlslOutput); WriteDataSetStructDeclarations(DataSetWriteInfo[0], false, HlslOutput); //Declare parameters. //TODO: Separate Cbuffer for Global, Effect and Emitter parameters. { HlslOutput += TEXT("cbuffer FEmitterParameters\n{\n"); if (Script->IsInterpolatedSpawnScript()) { //Define the params from the previous frame. //ensure the interpolated spawn constants are part of the parameter set. GetParameter(SYS_PARAM_DELTA_TIME); GetParameter(SYS_PARAM_INV_DELTA_TIME); GetParameter(SYS_PARAM_EXEC_COUNT); GetParameter(SYS_PARAM_SPAWNRATE); GetParameter(SYS_PARAM_SPAWN_INTERVAL); GetParameter(SYS_PARAM_INTERP_SPAWN_START_DT); for (int32 i = 0; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Num(); ++i) { //Copy the chunk so we can fiddle it's symbol name. FNiagaraCodeChunk Chunk = CodeChunks[ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform][i]]; Chunk.SymbolName = TEXT("PREV__") + Chunk.SymbolName; HlslOutput += TEXT("\t") + GetCode(Chunk); } } for (int32 i = 0; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Num(); ++i) { FNiagaraCodeChunk& Chunk = CodeChunks[ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform][i]]; HlslOutput += TEXT("\t") + GetCode(ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform][i]); } HlslOutput += TEXT("}\n\n"); } //TEMP HACK: REMOVE IMMEDIATELY //Get full list of instance data accessed by the script as the VM binding assumes same for input and output. //Easy enough to keep them separate but is a bit of extra plumbing work and i'm just gettting the sim working again here :D for (FNiagaraVariable& Var : InstanceRead.Variables) { CompilationOutput.Attributes.AddUnique(Var); } for (FNiagaraVariable& Var : InstanceWrite.Variables) { CompilationOutput.Attributes.AddUnique(Var); } //Map of all variables accessed by all datasets. TMap> DataSetReads; TMap> DataSetWrites; TArray& InstanceReadVars = DataSetReads.Add(GetInstanceDatSetID()); TArray& InstanceWriteVars = DataSetWrites.Add(GetInstanceDatSetID()); //Define the attrib structs { HlslOutput += TEXT("struct FInstanceInput\n{\n"); //for (FNiagaraVariable& Var : InstanceRead.Variables) //TEMPORARILY USING THE ATTRIBUTES HERE //The VM register binding assumes the same inputs as outputs which is obviously not always the case. //We should separate inputs and outputs in the script. for (FNiagaraVariable& Var : CompilationOutput.Attributes) { InstanceReadVars.AddUnique(Var); HlslOutput += TEXT("\t") + GetStructHlslTypeName(Var.GetType()) + TEXT(" ") + GetSanitizedSymbolName(Var.GetName().ToString()) + TEXT(";\n"); } HlslOutput += TEXT("};\n\n"); HlslOutput += TEXT("struct FInstanceOutput\n{\n"); //for (FNiagaraVariable& Var : InstanceWrite.Variables) //TEMPORARILY USING THE ATTRIBUTES HERE //The VM register binding assumes the same inputs as outputs which is obviously not always the case. //We should separate inputs and outputs in the script. for (FNiagaraVariable& Var : CompilationOutput.Attributes) { InstanceWriteVars.AddUnique(Var); HlslOutput += TEXT("\t") + GetStructHlslTypeName(Var.GetType()) + TEXT(" ") + GetSanitizedSymbolName(Var.GetName().ToString()) + TEXT(";\n"); } HlslOutput += TEXT("};\n\n"); } //Define the simulation context. Which is a helper struct containing all the input, result and intermediate data needed for a single simulation. //Allows us to reuse the same simulate function but provide different wrappers for final IO between GPU and CPU sims. { HlslOutput += TEXT("struct FSimulationContext") TEXT("\n{\n"); HlslOutput += TEXT("\tFInstanceInput Input;\n"); HlslOutput += TEXT("\tFInstanceOutput Output;\n"); HlslOutput += TEXT("\tbool bInstanceAlive;\n"); WriteDataSetContextVars(DataSetReadInfo[0], true, HlslOutput); WriteDataSetContextVars(DataSetWriteInfo[0], false, HlslOutput); //If we're an interpolated spawn script we cache off the interpolated parameters in the context if (Script->IsInterpolatedSpawnScript()) { HlslOutput += TEXT("\tint InterpSpawn_Index;\n"); HlslOutput += TEXT("\tfloat InterpSpawn_SpawnTime;\n"); HlslOutput += TEXT("\tfloat InterpSpawn_UpdateTime;\n"); HlslOutput += TEXT("\tfloat InterpSpawn_InvSpawnTime;\n"); HlslOutput += TEXT("\tfloat InterpSpawn_InvUpdateTime;\n"); HlslOutput += TEXT("\tFInstanceInput InterpSpawnResult;\n"); //Write temp variables to store the interpolated parameters. for (int32 i = 0; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Num(); ++i) { //Copy the chunk so we can fiddle it's symbol name. FNiagaraCodeChunk Chunk = CodeChunks[ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform][i]]; Chunk.SymbolName = TEXT("INTERP__") + Chunk.SymbolName; HlslOutput += TEXT("\t") + GetCode(Chunk); } } HlslOutput += TEXT("};\n\n"); } HlslOutput += FunctionDefinitionString; TArray WriteConditionVars; // copy the accessed data sets over to the script, so we can grab them during sim for (TPair > InfoPair : DataSetReadInfo[0]) { Script->ReadDataSets.Add(InfoPair.Key); } for (TPair > InfoPair : DataSetWriteInfo[0]) { FNiagaraDataSetProperties SetProps; SetProps.ID = InfoPair.Key; for (TPair IndexPair : InfoPair.Value) { SetProps.Variables = IndexPair.Value.Variables; } // Script->WriteDataSets.Add(InfoPair.Key); Script->WriteDataSets.Add(SetProps); int32* ConditionalWriteChunkIdxPtr = DataSetWriteConditionalInfo[0].Find(InfoPair.Key); if (ConditionalWriteChunkIdxPtr == nullptr) { WriteConditionVars.Add(INDEX_NONE); } else { WriteConditionVars.Add(*ConditionalWriteChunkIdxPtr); } } DefineInterpolatedParametersFunction(HlslOutput); // define functions for reading and writing all secondary data sets DefineDataSetReadFunction(HlslOutput, InScript->ReadDataSets); DefineDataSetWriteFunction(HlslOutput, InScript->WriteDataSets, WriteConditionVars); //Define the shared per instance simulation function { HlslOutput += TEXT("void Simulate(inout FSimulationContext Context)\n{\n"); for (int32 i = 0; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Body].Num(); ++i) { HlslOutput += GetCode(ChunksByMode[(int32)ENiagaraCodeChunkMode::Body][i]); } HlslOutput += TEXT("}\n"); } //And finally, define the actual main function that handles the reading and writing of data and calls the shared per instance simulate function. //TODO: Different wrappers for GPU and CPU sims. DefineMain(HlslOutput, InstanceReadVars, InstanceWriteVars); //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. FShaderCompilerInput Input; Input.SourceFilename = TEXT("NiagaraSimulationShader"); Input.EntryPointName = TEXT("SimulateMain"); //Input.Target = FShaderTarget(SF_Vertex, SP_VECTORVM_1_0); Input.Environment.IncludeFileNameToContentsMap.Add(TEXT("NiagaraSimulation.usf"), StringToArray(*HlslOutput, HlslOutput.Len() + 1)); FShaderCompilerOutput Output; CompileShader_VectorVM(Input, Output, FString(FPlatformProcess::ShaderDir()), 0, CompilationOutput); if (CompilationOutput.Errors.Len() > 0) { //TODO: Map Lines of HLSL to their source Nodes and flag those nodes with errors associated with their lines. Error(FText::Format(LOCTEXT("VectorVMCompileErrorMessageFormat", "The Vector VM compile failed. Errors:\n{0}"), FText::FromString(CompilationOutput.Errors)), nullptr, nullptr); CompileResults.bSuceeded = false; } //For now we just copy the shader code over into the script. //Eventually Niagara will have all the shader plumbing and do things like materials. if (CompileResults.bSuceeded) { Script->ByteCode = CompilationOutput.ByteCode; Script->Attributes = CompilationOutput.Attributes;//TODO: NEED TO SEPARATE INPUTS AND OUTPUTS IN SCRIPT Script->Parameters = CompilationOutput.Parameters; Script->DataUsage.bReadsAttriubteData = InstanceRead.Variables.Num() != 0;// CompilationOutput.DataUsage; Script->InternalParameters = CompilationOutput.InternalParameters; Script->DataInterfaceInfo.Empty(); for (FNiagaraScriptDataInterfaceInfo& DataInterfaceInfo : CompilationOutput.DataInterfaceInfo) { int32 Idx = Script->DataInterfaceInfo.AddDefaulted(); Script->DataInterfaceInfo[Idx].DataInterface = Cast(StaticDuplicateObject(DataInterfaceInfo.DataInterface, Script, NAME_None, ~RF_Transient)); Script->DataInterfaceInfo[Idx].Id = DataInterfaceInfo.Id; #if WITH_EDITORONLY_DATA Script->DataInterfaceInfo[Idx].Name = DataInterfaceInfo.Name; #endif } //Extract the external function call table binding info. Script->CalledVMExternalFunctions.Empty(CompilationOutput.CalledVMFunctionTable.Num()); for (FNiagaraCompilationOutput::FCalledVMFunction& FuncInfo : CompilationOutput.CalledVMFunctionTable) { FString& Func = FuncInfo.Name; int32 OwnerIdLoc; verify(Func.FindLastChar(TEXT('_'), OwnerIdLoc)); FString OwnerIdStr = Func.RightChop(OwnerIdLoc + 1); int32 NewBindingIdx = Script->CalledVMExternalFunctions.AddDefaulted(); Script->CalledVMExternalFunctions[NewBindingIdx].Name = *Func.Left(OwnerIdLoc); FGuid::Parse(OwnerIdStr, Script->CalledVMExternalFunctions[NewBindingIdx].OwnerId); Script->CalledVMExternalFunctions[NewBindingIdx].InputParamLocations = FuncInfo.InputParamLocations; Script->CalledVMExternalFunctions[NewBindingIdx].NumOutputs = FuncInfo.NumOutputs; } } CompileResults.OutputHLSL = HlslOutput; } if (CompileResults.bSuceeded == false) { //Some error. Clear script and exit. Script->ByteCode.Empty(); Script->Attributes.Empty(); Script->Parameters.Empty(); Script->InternalParameters.Empty(); Script->DataInterfaceInfo.Empty(); } return CompileResults; } void FHlslNiagaraCompiler::GenerateCodeForProperties(const UScriptStruct* Struct, FString Format, FString VariableSymbol, int32& Counter, int32 DataSetIndex, FString InstanceIdxSymbol, bool bMatrixRoot, FString &HlslOutput) { TArray FormatArgs; bool bIsVector = IsHlslBuiltinVector(FNiagaraTypeDefinition(Struct)); bool bIsScalar = FNiagaraTypeDefinition::IsScalarDefinition(Struct); bool bIsMatrix = FNiagaraTypeDefinition(Struct) == FNiagaraTypeDefinition::GetMatrix4Def(); if (bIsMatrix) { bMatrixRoot = true; } for (TFieldIterator PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { const UProperty* Property = *PropertyIt; if (const UStructProperty* StructProp = Cast(Property)) { if (bMatrixRoot && FNiagaraTypeDefinition(StructProp->Struct) == FNiagaraTypeDefinition::GetFloatDef()) { GenerateCodeForProperties(StructProp->Struct, Format, VariableSymbol + ComputeMatrixColumnAccess(Property->GetName()), Counter, DataSetIndex, InstanceIdxSymbol, bMatrixRoot, HlslOutput); } else if (bMatrixRoot && FNiagaraTypeDefinition(StructProp->Struct) == FNiagaraTypeDefinition::GetVec4Def()) { GenerateCodeForProperties(StructProp->Struct, Format, VariableSymbol + ComputeMatrixRowAccess(Property->GetName()), Counter, DataSetIndex, InstanceIdxSymbol, bMatrixRoot, HlslOutput); } else { GenerateCodeForProperties(StructProp->Struct, Format, VariableSymbol + TEXT(".") + Property->GetName(), Counter, DataSetIndex, InstanceIdxSymbol, bMatrixRoot, HlslOutput); } } else { FString VarName = VariableSymbol; if (bMatrixRoot) { if (bIsVector && Property->IsA(UFloatProperty::StaticClass())) // Parent is a vector type, we are a float type { VarName += ComputeMatrixColumnAccess(Property->GetName()); } } else if (!bIsScalar) { VarName += TEXT("."); VarName += bIsVector ? Property->GetName().ToLower() : Property->GetName(); } FormatArgs.Empty(4); FormatArgs.Add(VarName); if (Property->IsA(UFloatProperty::StaticClass())) { FormatArgs.Add(TEXT("Float")); } else if (Property->IsA(UIntProperty::StaticClass())) { FormatArgs.Add(TEXT("Int")); } else if (Property->IsA(UBoolProperty::StaticClass())) { FormatArgs.Add(TEXT("Int")); } // none for the output op (data set comes from acquireindex op) if (DataSetIndex != INDEX_NONE) { FormatArgs.Add(DataSetIndex); } FormatArgs.Add(Counter++); if (!InstanceIdxSymbol.IsEmpty()) { FormatArgs.Add(InstanceIdxSymbol); } HlslOutput += FString::Format(*Format, FormatArgs); } } }; void FHlslNiagaraCompiler::DefineInterpolatedParametersFunction(FString &HlslOutput) { if (Script->IsInterpolatedSpawnScript()) { HlslOutput += TEXT("void InterpolateParameters(inout FSimulationContext Context)\n{\n"); HlslOutput += TEXT("\tContext.InterpSpawn_Index = ExecIndex();\n"); HlslOutput += TEXT("\tContext.InterpSpawn_SpawnTime = InterpSpawnStartDt + (SpawnInterval * Context.InterpSpawn_Index);\n"); HlslOutput += TEXT("\tContext.InterpSpawn_UpdateTime = DeltaTime - Context.InterpSpawn_SpawnTime;\n"); HlslOutput += TEXT("\tContext.InterpSpawn_InvSpawnTime = 1.0 / Context.InterpSpawn_SpawnTime;\n"); HlslOutput += TEXT("\tContext.InterpSpawn_InvUpdateTime = 1.0 / Context.InterpSpawn_UpdateTime;\n"); HlslOutput += TEXT("\tfloat SpawnInterp = Context.InterpSpawn_SpawnTime * InvDeltaTime ;\n"); for (int32 i = 0; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Num(); ++i) { FNiagaraCodeChunk& Chunk = CodeChunks[ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform][i]]; HlslOutput += TEXT("\tContext.INTERP__") + Chunk.SymbolName + TEXT(" = lerp(PREV__") + Chunk.SymbolName + TEXT(", ") + Chunk.SymbolName + TEXT(", SpawnInterp);\n"); } HlslOutput += TEXT("}\n\n"); } } void FHlslNiagaraCompiler::DefineDataSetReadFunction(FString &HlslOutput, TArray &ReadDataSets) { HlslOutput += TEXT("void ReadDataSets(inout FSimulationContext Context)\n{\n"); for (TPair>& DataSetInfoPair : DataSetReadInfo[0]) { FNiagaraDataSetID DataSet = DataSetInfoPair.Key; int32 OffsetCounter = 0; int32 DataSetIndex = 1; for (TPair& IndexInfoPair : DataSetInfoPair.Value) { FString Symbol = GetDataSetAccessSymbol(DataSet, IndexInfoPair.Value.CodeChunks[0], true); for (FNiagaraVariable &Var : IndexInfoPair.Value.Variables) { // TODO: currently always emitting a non-advancing read, needs to be changed for some of the use cases FString Fmt = TEXT("\tContext.") + DataSet.Name.ToString() + "Read." + Var.GetName().ToString() + TEXT("{0} = InputDataNoadvance{1}({2}, {3});\n"); GenerateCodeForProperties(Var.GetType().GetScriptStruct(), Fmt, TEXT(""), OffsetCounter, DataSetIndex, TEXT(""), false, HlslOutput); } } } HlslOutput += TEXT("}\n\n"); } void FHlslNiagaraCompiler::DefineDataSetWriteFunction(FString &HlslOutput, TArray &WriteDataSets, TArray& WriteConditionVarIndices) { HlslOutput += TEXT("void WriteDataSets(inout FSimulationContext Context)\n{\n"); for (TPair>& DataSetInfoPair : DataSetWriteInfo[0]) { FNiagaraDataSetID DataSet = DataSetInfoPair.Key; int32 OffsetCounter = 0; int32 DataSetIndex = 1; HlslOutput += "\tint TmpWriteIndex;\n"; int32* ConditionalWriteIdxPtr = DataSetWriteConditionalInfo[0].Find(DataSet); if (nullptr == ConditionalWriteIdxPtr || INDEX_NONE == *ConditionalWriteIdxPtr) { HlslOutput += "\tbool bValid = true;\n"; } else { HlslOutput += "\tbool bValid = " + FString("Context.") + DataSet.Name.ToString() + "Write_Valid;\n"; } int32 WriteOffset = 0; // grab the current ouput index; currently pass true, but should use an arbitrary bool to determine whether write should happen or not HlslOutput += "\tTmpWriteIndex = AcquireIndex(1, bValid);\n"; for (TPair& IndexInfoPair : DataSetInfoPair.Value) { // FString Symbol = GetDataSetAccessSymbol(DataSet, IndexInfoPair.Value.CodeChunks[0], false); FString Symbol = FString("Context.") + DataSet.Name.ToString() + "Write"; // TODO: HACK - need to get the real symbol name here for (FNiagaraVariable &Var : IndexInfoPair.Value.Variables) { // TODO: data set index is always 1; need to increase each set FString Fmt = TEXT("\tOutputData{1}(1, {2}, {3}, ") + Symbol + "." + Var.GetName().ToString() + TEXT("{0});\n"); GenerateCodeForProperties(Var.GetType().GetScriptStruct(), Fmt, TEXT(""), WriteOffset, -1, TEXT("TmpWriteIndex"), false, HlslOutput); } } } HlslOutput += TEXT("}\n\n"); } void FHlslNiagaraCompiler::DefineMain( FString &HlslOutput, TArray &InstanceReadVars, TArray &InstanceWriteVars ) { HlslOutput += TEXT("void SimulateMain()\n{\n\tFSimulationContext Context;\n"); TMap InputRegisterAllocations; TMap OutputRegisterAllocations; ReadIdx = 0; WriteIdx = 0; //Decomposes each variable into its constituent register accesses. //DecomposeVariableAccess(const UStruct* Struct, bool bRead, FString IndexSymbol, FString HLSLString); /* int32 InputIndex = 0; for (TPair >& DataSetVariables : DataSetReads) { for (TPair>& DataSetInfoPair : DataSetReadInfo[0]) { FNiagaraDataSetID DataSet = DataSetInfoPair.Key; for (TPair& IndexInfoPair : DataSetInfoPair.Value) { FString Symbol = GetDataSetAccessSymbol(DataSet, IndexInfoPair.Value.CodeChunks[0], true); } } } */ //TODO: Grab indices for reading data sets and do the read. //read input. int32 ReadOffset = 0; int32 DataSetIndex = 0; //for (FNiagaraVariable &Var : InstanceReadVars) //TEMPORARILY USING THE ATTRIBUTES HERE //The VM register binding assumes the same inputs as outputs which is obviously not always the case. for (FNiagaraVariable &Var : CompilationOutput.Attributes) { FString Fmt = TEXT("\tContext.Input.") + GetSanitizedSymbolName(Var.GetName().ToString()) + TEXT("{0} = InputData{1}({2}, {3});\n"); GenerateCodeForProperties(Var.GetType().GetScriptStruct(), Fmt, TEXT(""), ReadOffset, DataSetIndex, TEXT(""), false, HlslOutput); } // call the read data set function HlslOutput += TEXT("\tReadDataSets(Context);\n"); //Interpolate between prev and current parameters for interpolated spawning. if (Script->IsInterpolatedSpawnScript()) { HlslOutput += TEXT("\tInterpolateParameters(Context);\n"); } //Call simulate. HlslOutput += TEXT("\tSimulate(Context);\n"); // write secondary data sets HlslOutput += TEXT("\tWriteDataSets(Context);\n"); //TODO Grab indices for data set writes (inc output) and do the write. currently always using reg 0 //write output HlslOutput += "\tint TmpWriteIndex;\n"; HlslOutput += "\tbool bValid = true;\n"; int32 WriteOffset = 0; // grab the current ouput index; currently pass true, but should use an arbitrary bool to determine whether write should happen or not HlslOutput += "\tTmpWriteIndex = AcquireIndex(0, bValid);\n"; //for (FNiagaraVariable &Var : InstanceWriteVars) //TEMPORARILY USING THE ATTRIBUTES HERE //The VM register binding assumes the same inputs as outputs which is obviously not always the case. //We should separate inputs and outputs in the script. for (FNiagaraVariable &Var : CompilationOutput.Attributes) { FString Fmt = TEXT("\tOutputData{1}(0, {2}, {3}, Context.Output.") + GetSanitizedSymbolName(Var.GetName().ToString()) + TEXT("{0});\n"); GenerateCodeForProperties(Var.GetType().GetScriptStruct(), Fmt , TEXT(""), WriteOffset, -1, TEXT("TmpWriteIndex"), false, HlslOutput); } HlslOutput += TEXT("}\n"); } void FHlslNiagaraCompiler::WriteDataSetContextVars(TMap>& DataSetAccessInfo, bool bRead, FString &HlslOutput) { //Now the intermediate storage for the data set reads and writes. uint32 DataSetIndex = 0; for (TPair>& DataSetInfoPair : DataSetAccessInfo) { FNiagaraDataSetID DataSet = DataSetInfoPair.Key; if (!bRead) { HlslOutput += TEXT("\tbool ") + DataSet.Name.ToString() + TEXT("Write_Valid; \n"); } HlslOutput += TEXT("\tF") + DataSet.Name.ToString() + "DataSet " + DataSet.Name.ToString() + (bRead ? TEXT("Read") : TEXT("Write")) + TEXT(";\n"); /* for (TPair& IndexInfoPair : DataSetInfoPair.Value) { FString Symbol = GetDataSetAccessSymbol(DataSet, IndexInfoPair.Value.CodeChunks[0], bRead); HlslOutput += TEXT("\tF") + DataSet.Name.ToString() + TEXT(" ") + Symbol + TEXT(";\n"); HlslOutput += TEXT("\t") + Symbol + TEXT("_Valid;\n"); } */ } }; void FHlslNiagaraCompiler::WriteDataSetStructDeclarations(TMap>& DataSetAccessInfo, bool bRead, FString &HlslOutput) { uint32 DataSetIndex = 0; for (TPair>& DataSetInfoPair : DataSetAccessInfo) { FNiagaraDataSetID DataSet = DataSetInfoPair.Key; HlslOutput += TEXT("struct F") + DataSet.Name.ToString() + "DataSet" + TEXT("\n{\n"); for (TPair& IndexInfoPair : DataSetInfoPair.Value) { for (FNiagaraVariable Var : IndexInfoPair.Value.Variables) { HlslOutput += TEXT("\t") + GetStructHlslTypeName(Var.GetType().GetScriptStruct()) + TEXT(" ") + Var.GetName().ToString() + ";\n"; } } HlslOutput += TEXT("};\n"); } } //Decomposes each variable into its constituent register accesses. void FHlslNiagaraCompiler::DecomposeVariableAccess(const UStruct* Struct, bool bRead, FString IndexSymbol, FString HLSLString) { FString AccessStr; for (TFieldIterator PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; if (UStructProperty* StructProp = CastChecked(Property)) { FNiagaraTypeDefinition PropDef(StructProp->Struct); if (!IsHlslBuiltinVector(PropDef)) { DecomposeVariableAccess(StructProp->Struct, bRead, IndexSymbol, AccessStr); return; } } int32 Index = INDEX_NONE; if (bRead) { Index = ReadIdx++; AccessStr = TEXT("ReadInput("); AccessStr += FString::FromInt(ReadIdx); AccessStr += ");\n"; } else { Index = WriteIdx++; AccessStr = TEXT("WriteOutput("); AccessStr += FString::FromInt(WriteIdx); AccessStr += ");\n"; } HLSLString += AccessStr; FNiagaraTypeDefinition StructDef(Cast(Struct)); FString TypeName = GetStructHlslTypeName(StructDef); } }; FString FHlslNiagaraCompiler::GetSanitizedSymbolName(FString SymbolName) { FString Ret = SymbolName.Replace(TEXT(" "), TEXT("")); Ret = Ret.Replace(TEXT("\\"), TEXT("_")); Ret = Ret.Replace(TEXT("/"), TEXT("_")); Ret = Ret.Replace(TEXT(","), TEXT("_")); Ret = Ret.Replace(TEXT("-"), TEXT("_")); Ret = Ret.Replace(TEXT(":"), TEXT("_")); return Ret; } FString FHlslNiagaraCompiler::GetUniqueSymbolName(FName BaseName) { uint32* NameCount = SymbolCounts.Find(BaseName); if (NameCount == nullptr) { SymbolCounts.Add(BaseName) = 1; return GetSanitizedSymbolName(BaseName.ToString()); } FString Ret = GetSanitizedSymbolName(BaseName.ToString()); if(*NameCount > 0) { Ret += LexicalConversion::ToString(*NameCount); } ++(*NameCount); return Ret; } void FHlslNiagaraCompiler::EnterFunction(UNiagaraScript* FunctonScript, FNiagaraFunctionSignature& Signature, TArray& Inputs) { UNiagaraScript* CurrFuncScript = GetFunctionScript(); UNiagaraScript* Caller = CurrFuncScript ? CurrFuncScript : Script; FunctionContextStack.Emplace(Caller, FunctonScript, Signature, Inputs); //May need some more heavy and scoped symbol tracking? //Add new scope for pin reuse. PinToCodeChunks.AddDefaulted(1); } void FHlslNiagaraCompiler::ExitFunction() { FunctionContextStack.Pop(); //May need some more heavy and scoped symbol tracking? //Pop pin reuse scope. PinToCodeChunks.Pop(); } FString FHlslNiagaraCompiler::GeneratedConstantString(float Constant) { return LexicalConversion::ToString(Constant); } void FHlslNiagaraCompiler::EnterStatsScope(FNiagaraStatScope StatScope) { int32 ScopeIdx = Script->StatScopes.AddUnique(StatScope); AddBodyChunk(TEXT(""), FString::Printf(TEXT("EnterStatScope(%d /**%s*/)"), ScopeIdx, *StatScope.FullName.ToString()), FNiagaraTypeDefinition::GetFloatDef(), false); StatScopeStack.Push(ScopeIdx); } void FHlslNiagaraCompiler::ExitStatsScope() { int32 ScopeIdx = StatScopeStack.Pop(); AddBodyChunk(TEXT(""), FString::Printf(TEXT("ExitStatScope(/**%s*/)"), *Script->StatScopes[ScopeIdx].FullName.ToString()), FNiagaraTypeDefinition::GetFloatDef(), false); } FString FHlslNiagaraCompiler::GetCallstack() { FString Callstack = Script->GetName(); for (FFunctionContext& Ctx : FunctionContextStack) { Callstack += TEXT(".") + Ctx.FunctionScript->GetName(); } return Callstack; } FString FHlslNiagaraCompiler::GeneratedConstantString(FVector4 Constant) { TArray Args; Args.Add(LexicalConversion::ToString(Constant.X)); Args.Add(LexicalConversion::ToString(Constant.Y)); Args.Add(LexicalConversion::ToString(Constant.Z)); Args.Add(LexicalConversion::ToString(Constant.W)); return FString::Format(TEXT("float4({0}, {1}, {2}, {3})"), Args); } int32 FHlslNiagaraCompiler::AddUniformChunk(FString SymbolName, const FNiagaraTypeDefinition& Type) { int32 Ret = CodeChunks.IndexOfByPredicate( [&](const FNiagaraCodeChunk& Chunk) { return Chunk.Mode == ENiagaraCodeChunkMode::Uniform && Chunk.SymbolName == SymbolName && Chunk.Type == Type; } ); if (Ret == INDEX_NONE) { Ret = CodeChunks.AddDefaulted(); FNiagaraCodeChunk& Chunk = CodeChunks[Ret]; Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Type = Type; Chunk.Mode = ENiagaraCodeChunkMode::Uniform; ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Add(Ret); } return Ret; } int32 FHlslNiagaraCompiler::AddSourceChunk(FString SymbolName, const FNiagaraTypeDefinition& Type) { int32 Ret = CodeChunks.IndexOfByPredicate( [&](const FNiagaraCodeChunk& Chunk) { return Chunk.Mode == ENiagaraCodeChunkMode::Source && Chunk.SymbolName == SymbolName && Chunk.Type == Type; } ); if (Ret == INDEX_NONE) { Ret = CodeChunks.AddDefaulted(); FNiagaraCodeChunk& Chunk = CodeChunks[Ret]; Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Type = Type; Chunk.Mode = ENiagaraCodeChunkMode::Source; ChunksByMode[(int32)ENiagaraCodeChunkMode::Source].Add(Ret); } return Ret; } int32 FHlslNiagaraCompiler::AddBodyChunk(FString SymbolName, FString Definition, const FNiagaraTypeDefinition& Type, TArray& SourceChunks, bool bDecl) { int32 Ret = CodeChunks.AddDefaulted(); FNiagaraCodeChunk& Chunk = CodeChunks[Ret]; Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Definition = Definition; Chunk.Type = Type; Chunk.bDecl = bDecl; Chunk.Mode = ENiagaraCodeChunkMode::Body; Chunk.SourceChunks = SourceChunks; ChunksByMode[(int32)ENiagaraCodeChunkMode::Body].Add(Ret); return Ret; } int32 FHlslNiagaraCompiler::AddBodyChunk(FString SymbolName, FString Definition, const FNiagaraTypeDefinition& Type, int32 SourceChunk, bool bDecl) { int32 Ret = CodeChunks.AddDefaulted(); FNiagaraCodeChunk& Chunk = CodeChunks[Ret]; Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Definition = Definition; Chunk.Type = Type; Chunk.bDecl = bDecl; Chunk.Mode = ENiagaraCodeChunkMode::Body; Chunk.SourceChunks.Add(SourceChunk); ChunksByMode[(int32)ENiagaraCodeChunkMode::Body].Add(Ret); return Ret; } int32 FHlslNiagaraCompiler::AddBodyChunk(FString SymbolName, FString Definition, const FNiagaraTypeDefinition& Type, bool bDecl) { int32 Ret = CodeChunks.AddDefaulted(); FNiagaraCodeChunk& Chunk = CodeChunks[Ret]; Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Definition = Definition; Chunk.Type = Type; Chunk.bDecl = bDecl; Chunk.Mode = ENiagaraCodeChunkMode::Body; ChunksByMode[(int32)ENiagaraCodeChunkMode::Body].Add(Ret); return Ret; } bool FHlslNiagaraCompiler::ShouldInterpolateParameter(const FNiagaraVariable& Parameter) { //Skip interpolation for some system constants. if (Parameter == SYS_PARAM_DELTA_TIME || Parameter == SYS_PARAM_INV_DELTA_TIME || Parameter == SYS_PARAM_EXEC_COUNT || Parameter == SYS_PARAM_SPAWNRATE || Parameter == SYS_PARAM_SPAWN_INTERVAL || Parameter == SYS_PARAM_INTERP_SPAWN_START_DT ) { return false; } return true; } int32 FHlslNiagaraCompiler::GetParameter(const FNiagaraVariable& Parameter) { if (!AddStructToDefinitionSet(Parameter.GetType())) { Error(FText::Format(LOCTEXT("GetParameterFail", "Cannot handle type {0}! Variable: {1}"), Parameter.GetType().GetNameText(), FText::FromName(Parameter.GetName())), nullptr, nullptr); } int32 FuncParam = INDEX_NONE; if (GetFunctionParameter(Parameter, FuncParam)) { if (FuncParam != INDEX_NONE) { //If this is a valid funciton parameter, use that. FString SymbolName = TEXT("In_") + GetSanitizedSymbolName(Parameter.GetName().ToString()); return AddSourceChunk(SymbolName, Parameter.GetType()); } } //Not a in a function or not a valid function parameter so grab from the main uniforms. CompilationOutput.Parameters.SetOrAdd(Parameter); FString SymbolName = GetSanitizedSymbolName(Parameter.GetName().ToString()); int32 UniformChunk = AddUniformChunk(SymbolName, Parameter.GetType()); if (Script->IsInterpolatedSpawnScript()) { if (Parameter == SYS_PARAM_DELTA_TIME) { return bInsideInterpolatedSpawnScript ? AddSourceChunk(TEXT("Context.InterpSpawn_SpawnTime"), Parameter.GetType()) : AddSourceChunk(TEXT("Context.InterpSpawn_UpdateTime"), Parameter.GetType()); } else if (Parameter == SYS_PARAM_INV_DELTA_TIME) { return bInsideInterpolatedSpawnScript ? AddSourceChunk(TEXT("Context.InterpSpawn_InvSpawnTime"), Parameter.GetType()) : AddSourceChunk(TEXT("Context.InterpSpawn_InvUpdateTime"), Parameter.GetType()); } else if (ShouldInterpolateParameter(Parameter)) { return AddSourceChunk(TEXT("Context.INTERP__") + SymbolName, Parameter.GetType()); } } return UniformChunk; } int32 FHlslNiagaraCompiler::GetConstant(const FNiagaraVariable& Constant) { FNiagaraTypeDefinition Type = Constant.GetType(); if (!AddStructToDefinitionSet(Type)) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Type.GetNameText(), FText::FromName(Constant.GetName())), nullptr, nullptr); } FString ConstantStr = GetStructHlslTypeName(Type) + GetHlslDefaultForType(Type); if (Constant.IsDataAllocated()) { if (Type == FNiagaraTypeDefinition::GetFloatDef()) { float* ValuePtr = (float*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("%g"), *ValuePtr); } else if (Type == FNiagaraTypeDefinition::GetVec2Def()) { float* ValuePtr = (float*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("float2(%g,%g)"), *ValuePtr, *(ValuePtr + 1)); } else if (Type == FNiagaraTypeDefinition::GetVec3Def()) { float* ValuePtr = (float*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("float3(%g,%g,%g)"), *ValuePtr, *(ValuePtr + 1), *(ValuePtr + 2)); } else if (Type == FNiagaraTypeDefinition::GetVec4Def()) { float* ValuePtr = (float*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("float4(%g,%g,%g,%g)"), *ValuePtr, *(ValuePtr + 1), *(ValuePtr + 2), *(ValuePtr + 3)); } else if (Type == FNiagaraTypeDefinition::GetColorDef()) { float* ValuePtr = (float*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("float4(%g,%g,%g,%g)"), *ValuePtr, *(ValuePtr + 1), *(ValuePtr + 2), *(ValuePtr + 3)); } else if (Type == FNiagaraTypeDefinition::GetIntDef()) { int32* ValuePtr = (int32*)Constant.GetData(); ConstantStr = FString::Printf(TEXT("%d"), *ValuePtr); } else if (Type == FNiagaraTypeDefinition::GetBoolDef()) { int32* ValuePtr = (int32*)Constant.GetData(); ConstantStr = *ValuePtr == INDEX_NONE ? "true" : "false"; } else { //This is easily doable, just need to keep track of all structs used and define them as well as a ctor function signature with all values decomposed into float1/2/3/4 etc //Then call said function here with the same decomposition literal values. Error(LOCTEXT("StructContantsUnsupportedError", "Constants of struct types are currently unsupported."), nullptr, nullptr); return INDEX_NONE; } } return AddBodyChunk(GetUniqueSymbolName(TEXT("Constant")), ConstantStr, Constant.GetType()); } void FHlslNiagaraCompiler::Output(const TArray& Attributes, const TArray& Inputs) { //CompilationOutput.Attributes.Add(Attribute); if (FunctionCtx()) { for (int32 i = 0; i < Attributes.Num(); ++i) { if (!AddStructToDefinitionSet(Attributes[i].GetType())) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Attributes[i].GetType().GetNameText(), FText::FromName(Attributes[i].GetName())), nullptr, nullptr); } FString SymbolName = *(TEXT("Out_") + GetSanitizedSymbolName(Attributes[i].GetName().ToString())); AddBodyChunk(SymbolName, TEXT("{0}"), Attributes[i].GetType(), Inputs[i], false); } } else { if (bInsideInterpolatedSpawnScript) { //Write to the temporary spawn outputs in the context. for (int32 i = 0; i < Attributes.Num(); ++i) { if (!AddStructToDefinitionSet(Attributes[i].GetType())) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Attributes[i].GetType().GetNameText(), FText::FromName(Attributes[i].GetName())), nullptr, nullptr); } FString SymbolName = *(TEXT("Context.InterpSpawnResult.") + GetSanitizedSymbolName(Attributes[i].GetName().ToString())); AddBodyChunk(SymbolName, TEXT("{0}"), Attributes[i].GetType(), Inputs[i], false); } } else { check(InstanceWrite.CodeChunks.Num() == 0);//Should only hit one output node. FString DataSetAccessName = GetDataSetAccessSymbol(GetInstanceDatSetID(), INDEX_NONE, false); //First chunk for a write is always the condition pin. //InstanceWrite.CodeChunks.Add(AddBodyChunk(TEXT("Context.bInstanceAlive"), TEXT("{0}"), FNiagaraTypeDefinition::GetIntDef(), Inputs[0], false)); for (int32 i = 0; i < Attributes.Num(); ++i) { const FNiagaraVariable& Var = Attributes[i]; if (!AddStructToDefinitionSet(Var.GetType())) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"),Var.GetType().GetNameText(), FText::FromName(Var.GetName())), nullptr, nullptr); } //DATASET TODO: add and treat input 0 as the 'valid' input for conditional write int32 Input = Inputs[i]; InstanceWrite.CodeChunks.Add(AddBodyChunk(TEXT("Context.Output.") + GetSanitizedSymbolName(Var.GetName().ToString()), TEXT("{0}"), Var.GetType(), Input, false)); InstanceWrite.Variables.Add(Var); } } } } int32 FHlslNiagaraCompiler::GetAttribute(const FNiagaraVariable& Attribute) { if (!AddStructToDefinitionSet(Attribute.GetType())) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Attribute.GetType().GetNameText(), FText::FromName(Attribute.GetName())), nullptr, nullptr); } if (Script->IsSpawnScript()) { if (Script->IsInterpolatedSpawnScript() && !bInsideInterpolatedSpawnScript) { //This is a special case where we allow the grabbing of attributes in the update section of an interpolated spawn script. //But we return the results of the previously ran spawn script. FString SymbolName = *(TEXT("Context.InterpSpawnResult.") + GetSanitizedSymbolName(Attribute.GetName().ToString())); return AddSourceChunk(SymbolName, Attribute.GetType()); } else { Error(LOCTEXT("AttrReadInSpawnError","Cannot read attribute in a spawn script as it's value is not yet initialized."), nullptr, nullptr); return INDEX_NONE; } } else { check(!Script->IsSpawnScript());//Can't be reading attributes in a spawn script. CompilationOutput.DataUsage.bReadsAttriubteData = true; int32 Idx = InstanceRead.Variables.Find(Attribute); if (Idx != INDEX_NONE) { return InstanceRead.CodeChunks[Idx]; } else { int32 Chunk = AddSourceChunk(TEXT("Context.Input.") + GetSanitizedSymbolName(Attribute.GetName().ToString()), Attribute.GetType()); InstanceRead.CodeChunks.Add(Chunk); InstanceRead.Variables.Add(Attribute); return Chunk; } } } FString FHlslNiagaraCompiler::GetDataSetAccessSymbol(FNiagaraDataSetID DataSet, int32 IndexChunk, bool bRead) { FString Ret = TEXT("Context.") + DataSet.Name.ToString(); Ret += bRead ? TEXT("Read") : TEXT("Write"); Ret += IndexChunk != INDEX_NONE ? TEXT("_") + CodeChunks[IndexChunk].SymbolName : TEXT(""); return Ret; } void FHlslNiagaraCompiler::ReadDataSet(const FNiagaraDataSetID DataSet, const TArray& Variables, ENiagaraDataSetAccessMode AccessMode, int32 InputChunk, TArray& Outputs) { //Eventually may allow events that take in a direct index or condition but for now we don't check(InputChunk == INDEX_NONE); TMap& Reads = DataSetReadInfo[(int32)AccessMode].FindOrAdd(DataSet); FDataSetAccessInfo* DataSetReadForInput = Reads.Find(InputChunk); if (!DataSetReadForInput) { DataSetReadForInput = &Reads.Add(InputChunk); DataSetReadForInput->Variables = Variables; DataSetReadForInput->CodeChunks.Reserve(Variables.Num()+1); FString DataSetAccessSymbol = GetDataSetAccessSymbol(DataSet, InputChunk, true); //Add extra output to indicate if event read is valid data. //DataSetReadForInput->CodeChunks.Add(AddSourceChunk(DataSetAccessSymbol + TEXT("_Valid"), FNiagaraTypeDefinition::GetIntDef())); for (int32 i=0; iCodeChunks.Add(AddSourceChunk(DataSetAccessSymbol + TEXT(".") + GetSanitizedSymbolName(Var.GetName().ToString()), Var.GetType())); } Outputs = DataSetReadForInput->CodeChunks; } else { check(Variables.Num() == DataSetReadForInput->Variables.Num()); Outputs = DataSetReadForInput->CodeChunks; } } void FHlslNiagaraCompiler::WriteDataSet(const FNiagaraDataSetID DataSet, const TArray& Variables, ENiagaraDataSetAccessMode AccessMode, const TArray& Inputs) { int32 ConditionalChunk = Inputs[0]; int32 InputChunk = Inputs[1]; TMap& Writes = DataSetWriteInfo[(int32)AccessMode].FindOrAdd(DataSet); FDataSetAccessInfo* DataSetWriteForInput = Writes.Find(InputChunk); int32& DataSetWriteConditionalIndex = DataSetWriteConditionalInfo[(int32)AccessMode].FindOrAdd(DataSet); //We should never try to write to the exact same dataset at the same index/condition twice. //This is still possible but we can catch easy cases here. if (DataSetWriteForInput) { //TODO: improve error report. Error(LOCTEXT("WritingToSameDataSetError", "Writing to the same dataset with the same condition/index."), NULL, NULL); return; } DataSetWriteConditionalIndex = ConditionalChunk; DataSetWriteForInput = &Writes.Add(InputChunk); DataSetWriteForInput->Variables = Variables; //FString DataSetAccessName = GetDataSetAccessSymbol(DataSet, InputChunk, false); FString DataSetAccessName = FString("Context.") + DataSet.Name.ToString() + "Write"; // TODO: HACK - need to get the real symbol name here //First chunk for a write is always the condition pin. //We always write the event payload into the temp storage but we can access this condition to pass to the final actual write to the buffer. DataSetWriteForInput->CodeChunks.Add(AddBodyChunk(DataSetAccessName + TEXT("_Valid"), TEXT("{0}"), FNiagaraTypeDefinition::GetBoolDef(), Inputs[0], false)); for (int32 i = 0; i < Variables.Num(); ++i) { const FNiagaraVariable& Var = Variables[i]; int32 Input = Inputs[i + 1];//input 0 is the valid input (no entry in variables array), so we need of offset all other inputs by 1. DataSetWriteForInput->CodeChunks.Add(AddBodyChunk(DataSetAccessName + TEXT(".") + GetSanitizedSymbolName(Var.GetName().ToString()), TEXT("{0}"), Var.GetType(), Input, false)); } } int32 FHlslNiagaraCompiler::RegisterDataInterface(FNiagaraVariable& Var, UNiagaraDataInterface* DataInterface) { int32 FuncParam; if (GetFunctionParameter(Var, FuncParam)) { if (FuncParam != INDEX_NONE) { //This data interface param has been overridden by the function call so use that index. return FuncParam; } } //If we get here then this is a new data interface. int32 Idx = CompilationOutput.DataInterfaceInfo.AddDefaulted(); CompilationOutput.DataInterfaceInfo[Idx].DataInterface = DataInterface; CompilationOutput.DataInterfaceInfo[Idx].Id = Var.GetId(); #if WITH_EDITORONLY_DATA CompilationOutput.DataInterfaceInfo[Idx].Name = Var.GetName(); #endif return Idx; } void FHlslNiagaraCompiler::Operation(class UNiagaraNodeOp* Operation, TArray& Inputs, TArray& Outputs) { // Use the pins to determine the output type here since they may have been changed due to numeric pin fix up. const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(Operation->OpName); EnterStatsScope(FNiagaraStatScope(*Operation->GetFullName(), OpInfo->FriendlyName)); TArray OutputPins; Operation->GetOutputPins(OutputPins); for (int32 OutputIndex = 0; OutputIndex < OutputPins.Num(); OutputIndex++) { FNiagaraTypeDefinition OutputType = Schema->PinToTypeDefinition(OutputPins[OutputIndex]); if (!AddStructToDefinitionSet(OutputType)) { FText PinNameText = OutputPins[OutputIndex]->PinFriendlyName.IsEmpty() ? FText::FromString(OutputPins[OutputIndex]->PinName) : OutputPins[OutputIndex]->PinFriendlyName; Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Output Pin: {1}"), OutputType.GetNameText(), PinNameText), Operation, OutputPins[OutputIndex]); } const FNiagaraOpInOutInfo& IOInfo = OpInfo->Outputs[OutputIndex]; check(!IOInfo.HlslSnippet.IsEmpty()); Outputs.Add(AddBodyChunk(GetUniqueSymbolName(IOInfo.Name), IOInfo.HlslSnippet, OutputType, Inputs)); } ExitStatsScope(); } void FHlslNiagaraCompiler::FunctionCall(UNiagaraNodeFunctionCall* FunctionNode, TArray& Inputs, TArray& Outputs) { FNiagaraFunctionSignature Signature; if (FunctionNode->FunctionScript == nullptr && !FunctionNode->Signature.IsValid()) { Error(LOCTEXT("FunctionCallNonexistant", "Function call missing FunctionScript and invalid signature"), FunctionNode, nullptr); return; } RegisterFunctionCall(FunctionNode, Inputs, Signature); GenerateFunctionCall(Signature, Inputs, Outputs); } void FHlslNiagaraCompiler::RegisterFunctionCall(UNiagaraNodeFunctionCall* FunctionNode, TArray& Inputs, FNiagaraFunctionSignature& OutSignature) { ////////////////////////////////////////////////////////////////////////// if (FunctionNode->FunctionScript) { UNiagaraScriptSource* Source = CastChecked(FunctionNode->FunctionScript->Source); UNiagaraGraph* SourceGraph = CastChecked(Source->NodeGraph); if (SourceGraph->HasNumericParameters()) { //We have to clone and preprocess the function graph to ensure it's numeric types have been fixed up to real types. UNiagaraGraph* PreprocessedGraph = CastChecked(FEdGraphUtilities::CloneGraph(Source->NodeGraph, Source, &MessageLog)); FEdGraphUtilities::MergeChildrenGraphsIn(PreprocessedGraph, PreprocessedGraph, /*bRequireSchemaMatch=*/ true); PreprocessFunctionGraph(*this, Schema, PreprocessedGraph, FunctionNode); SourceGraph = PreprocessedGraph; } else { //If we don't have numeric inputs then we can cache the preprocessed graphs. UNiagaraGraph** CachedGraph = PreprocessedFunctions.Find(SourceGraph); if (!CachedGraph) { CachedGraph = &PreprocessedFunctions.Add(SourceGraph); *CachedGraph = CastChecked(FEdGraphUtilities::CloneGraph(Source->NodeGraph, Source, &MessageLog)); FEdGraphUtilities::MergeChildrenGraphsIn(*CachedGraph, *CachedGraph, /*bRequireSchemaMatch=*/ true); PreprocessFunctionGraph(*this, Schema, *CachedGraph, FunctionNode); } SourceGraph = *CachedGraph; } GenerateFunctionSignature(FunctionNode->FunctionScript, SourceGraph, Inputs, OutSignature); // //Sort the input and outputs to match the sorted parameters. They may be different. // TArray OrderedInputs; // TArray OrderedOutputs; // SourceGraph->GetParameters(OrderedInputs, OrderedOutputs); // TArray InPins; // FunctionNode->GetInputPins(InPins); // // TArray OrderedInputChunks; // OrderedInputChunks.SetNumUninitialized(Inputs.Num()); // for (int32 i = 0; i < InPins.Num(); ++i) // { // FNiagaraVariable PinVar(Schema->PinToTypeDefinition(InPins[i]), *InPins[i]->PinName); // int32 InputIdx = OrderedInputs.IndexOfByKey(PinVar); // check(InputIdx != INDEX_NONE); // OrderedInputChunks[i] = Inputs[InputIdx]; // } // Inputs = OrderedInputChunks; FString* FuncBody = Functions.Find(OutSignature); if (!FuncBody) { //We've not compiled this function yet so compile it now. EnterFunction(FunctionNode->FunctionScript, OutSignature, Inputs); UNiagaraNodeOutput* FuncOutput = SourceGraph->FindOutputNode(); check(FuncOutput); //Track the start of this funciton in the chunks so we can remove them after we grab the funcitons hlsl. int32 ChunkStart = CodeChunks.Num(); int32 ChunkStartsByMode[(int32)ENiagaraCodeChunkMode::Num]; for (int32 i = 0; i < (int32)ENiagaraCodeChunkMode::Num; ++i) { ChunkStartsByMode[i] = ChunksByMode[i].Num(); } INiagaraCompiler* ThisCompiler = this; TArray FuncOutputChunks; FuncOutput->Compile(ThisCompiler, FuncOutputChunks); //TODO: Write Datasets for events etc. TArrayWriteNodes; SourceGraph->FindWriteDataSetNodes(WriteNodes); for (UNiagaraNodeWriteDataSet *WriteNode : WriteNodes) { WriteNode->Compile(ThisCompiler, FuncOutputChunks); } //Grab all the body chunks for this function. FString FunctionDefStr; for (int32 i = ChunkStartsByMode[(int32)ENiagaraCodeChunkMode::Body]; i < ChunksByMode[(int32)ENiagaraCodeChunkMode::Body].Num(); ++i) { FunctionDefStr += GetCode(ChunksByMode[(int32)ENiagaraCodeChunkMode::Body][i]); } //Now remove all chunks for the function again. //This is super hacky. Should move chunks etc into a proper scoping system. TArray FuncUniforms; for (int32 i = 0; i < (int32)ENiagaraCodeChunkMode::Num; ++i) { //Keep uniform chunks. if (i == (int32)ENiagaraCodeChunkMode::Uniform) { for (int32 ChunkIdx = ChunkStartsByMode[i]; ChunkIdx < ChunksByMode[i].Num(); ++ChunkIdx) { FuncUniforms.Add(CodeChunks[ChunksByMode[i][ChunkIdx]]); } } ChunksByMode[i].RemoveAt(ChunkStartsByMode[i], ChunksByMode[i].Num() - ChunkStartsByMode[i]); } CodeChunks.RemoveAt(ChunkStart, CodeChunks.Num() - ChunkStart, false); //Re-add the uniforms. Really this is horrible. Rework soon. for (FNiagaraCodeChunk& Chunk : FuncUniforms) { ChunksByMode[(int32)ENiagaraCodeChunkMode::Uniform].Add(CodeChunks.Add(Chunk)); } Functions.Add(OutSignature, FunctionDefStr); ExitFunction(); } } else { check(FunctionNode->Signature.IsValid()); check(FunctionNode->Signature.bMemberFunction); check(Inputs.Num() > 0); OutSignature = FunctionNode->Signature; //First input for these is the owner of the function. int32 OwnerIdx = Inputs[0]; if (OwnerIdx < 0 || OwnerIdx >= CompilationOutput.DataInterfaceInfo.Num()) { Error(LOCTEXT("FunctionCallDataInterfaceMissingRegistration", "Function call signature does not match to a registered DataInterface. Valid DataInterfaces should be wired into a DataInterface function call."), FunctionNode, nullptr); return; } FNiagaraScriptDataInterfaceInfo& Info = CompilationOutput.DataInterfaceInfo[OwnerIdx]; // Double-check to make sure that the signature matches those specified by the data // interface. It could be that the existing node has been removed and the graph // needs to be refactored. If that's the case, emit an error. if (Info.DataInterface != nullptr && OutSignature.bMemberFunction) { TArray DataInterfaceFunctions; Info.DataInterface->GetFunctions(DataInterfaceFunctions); bool bFoundMatch = false; for (const FNiagaraFunctionSignature& Sig : DataInterfaceFunctions) { if (Sig == OutSignature) { bFoundMatch = true; } } if (!bFoundMatch) { Error(LOCTEXT("FunctionCallDataInterfaceMissing", "Function call signature does not match DataInterface possible signatures?"), FunctionNode, nullptr); } } //Override the owner id of the signature with the actual caller. OutSignature.OwnerId = Info.Id; Functions.FindOrAdd(OutSignature); } } void FHlslNiagaraCompiler::GenerateFunctionCall(FNiagaraFunctionSignature& FunctionSignature, TArray& Inputs, TArray& Outputs) { EnterStatsScope(FNiagaraStatScope(*GetFunctionSignatureSymbol(FunctionSignature), FText::FromString(FunctionSignature.GetName()))); TArray MissingParameters; int32 ParamIdx = 0; TArray Params; Params.Reserve(Inputs.Num() + Outputs.Num()); FString DefStr = GetFunctionSignatureSymbol(FunctionSignature) + TEXT("("); for (int32 i = 0; i < FunctionSignature.Inputs.Num(); ++i) { FNiagaraTypeDefinition Type = FunctionSignature.Inputs[i].GetType(); //We don't write class types as real params in the hlsl if (!Type.GetClass()) { if (!AddStructToDefinitionSet(Type)) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Type.GetNameText(), FText::FromName(FunctionSignature.Inputs[i].GetName())), nullptr, nullptr); } if (ParamIdx != 0) { DefStr += TEXT(", "); } int32 Input = Inputs[i]; Params.Add(Input); if (Input == INDEX_NONE) { MissingParameters.Add(FunctionSignature.Inputs[i].GetName().ToString()); } else { DefStr += FString::Printf(TEXT("{%d}"), ParamIdx); } ++ParamIdx; } } for (int32 i = 0; i < FunctionSignature.Outputs.Num(); ++i) { FNiagaraVariable& OutVar = FunctionSignature.Outputs[i]; FNiagaraTypeDefinition Type = OutVar.GetType(); //We don't write class types as real params in the hlsl if (!Type.GetClass()) { if (!AddStructToDefinitionSet(Type)) { Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Type.GetNameText(), FText::FromName(FunctionSignature.Outputs[i].GetName())), nullptr, nullptr); } if (ParamIdx > 0) { DefStr += TEXT(", "); } FString OutputStr = FString::Printf(TEXT("%sOutput_%s"), *GetFunctionSignatureSymbol(FunctionSignature), *OutVar.GetName().ToString()); int32 Output = AddBodyChunk(GetUniqueSymbolName(*OutputStr), TEXT(""), OutVar.GetType()); Outputs.Add(Output); Params.Add(Output); if (Output == INDEX_NONE) { MissingParameters.Add(OutVar.GetName().ToString()); } else { DefStr += FString::Printf(TEXT("{%d}"), ParamIdx); } ++ParamIdx; } } if (FunctionSignature.bRequiresContext) { DefStr += ", Context"; } DefStr += TEXT(")"); if (MissingParameters.Num() > 0) { for (FString MissingParam : MissingParameters) { FText Fmt = LOCTEXT("ErrorCompilingParameterFmt", "Error compiling parameter {0} in function call {1}"); FText ErrorText = FText::Format(Fmt, FText::FromString(MissingParam), FText::FromString(GetFunctionSignatureSymbol(FunctionSignature))); Error(ErrorText, nullptr, nullptr); } return; } AddBodyChunk(TEXT(""), DefStr, FNiagaraTypeDefinition::GetFloatDef(), Params); ExitStatsScope(); } FString FHlslNiagaraCompiler::GetFunctionSignatureSymbol(FNiagaraFunctionSignature& Sig) { FString SigStr = GetSanitizedSymbolName(Sig.GetName()); if (Sig.OwnerId.IsValid()) { SigStr += TEXT("_") + Sig.OwnerId.ToString(); } else { SigStr += TEXT("_Func_"); } return SigStr; } FString FHlslNiagaraCompiler::GetFunctionSignature(FNiagaraFunctionSignature& Sig) { FString SigStr = TEXT("void ") + GetFunctionSignatureSymbol(Sig); SigStr += TEXT("("); int32 ParamIdx = 0; for (int32 i = 0; i < Sig.Inputs.Num(); ++i) { const FNiagaraVariable& Input = Sig.Inputs[i]; //We don't write class types as real params in the hlsl if (Input.GetType().GetClass() == nullptr) { if (ParamIdx > 0) { SigStr += TEXT(", "); } SigStr += FHlslNiagaraCompiler::GetStructHlslTypeName(Input.GetType()) + TEXT(" In_") + FHlslNiagaraCompiler::GetSanitizedSymbolName(Input.GetName().ToString()); ++ParamIdx; } } for (int32 i = 0; i < Sig.Outputs.Num(); ++i) { const FNiagaraVariable& Output = Sig.Outputs[i]; //We don't write class types as real params in the hlsl if (Output.GetType().GetClass() == nullptr) { if (ParamIdx > 0) { SigStr += TEXT(", "); } SigStr += TEXT("out ") + FHlslNiagaraCompiler::GetStructHlslTypeName(Output.GetType()) + TEXT(" Out_") + FHlslNiagaraCompiler::GetSanitizedSymbolName(Output.GetName().ToString()); ++ParamIdx; } } if (Sig.bRequiresContext) { SigStr += TEXT(", inout FSimulationContext Context"); } return SigStr + TEXT(")"); } int32 GetPinIndexById(const TArray& Pins, FGuid PinId) { for (int32 i = 0; i < Pins.Num(); ++i) { if (Pins[i]->PinId == PinId) { return i; } } return INDEX_NONE; } FNiagaraTypeDefinition FHlslNiagaraCompiler::GetChildType(const FNiagaraTypeDefinition& BaseType, const FName& PropertyName) { const UScriptStruct* Struct = BaseType.GetScriptStruct(); if (Struct != nullptr) { // Dig through properties to find the matching property native type (if it exists) for (TFieldIterator PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { const UProperty* Property = *PropertyIt; if (Property->GetName() == PropertyName.ToString()) { if (Property->IsA(UFloatProperty::StaticClass())) { return FNiagaraTypeDefinition::GetFloatDef(); } else if (Property->IsA(UIntProperty::StaticClass())) { return FNiagaraTypeDefinition::GetIntDef(); } else if (Property->IsA(UBoolProperty::StaticClass())) { return FNiagaraTypeDefinition::GetBoolDef(); } else if (const UStructProperty* StructProp = CastChecked(Property)) { return FNiagaraTypeDefinition(StructProp->Struct); } } } } return FNiagaraTypeDefinition(); } FString FHlslNiagaraCompiler::ComputeMatrixColumnAccess(const FString& Name) { FString Value; int32 Column = -1; if (Name.Find("X", ESearchCase::IgnoreCase) != -1) Column = 0; else if (Name.Find("Y", ESearchCase::IgnoreCase) != -1) Column = 1; else if (Name.Find("Z", ESearchCase::IgnoreCase) != -1) Column = 2; else if (Name.Find("W", ESearchCase::IgnoreCase) != -1) Column = 3; if (Column != -1) { Value.Append("["); Value.AppendInt(Column); Value.Append("]"); } else { Error(FText::FromString("Failed to generate type for " + Name + " up to path " + Value), nullptr, nullptr); } return Value; } FString FHlslNiagaraCompiler::ComputeMatrixRowAccess(const FString& Name) { FString Value; int32 Row = -1; if (Name.Find("Row0", ESearchCase::IgnoreCase) != -1) Row = 0; else if (Name.Find("Row1", ESearchCase::IgnoreCase) != -1) Row = 1; else if (Name.Find("Row2", ESearchCase::IgnoreCase) != -1) Row = 2; else if (Name.Find("Row3", ESearchCase::IgnoreCase) != -1) Row = 3; if (Row != -1) { Value.Append("["); Value.AppendInt(Row); Value.Append("]"); } else { Error(FText::FromString("Failed to generate type for " + Name + " up to path " + Value), nullptr, nullptr); } return Value; } FString FHlslNiagaraCompiler::NamePathToString(const FString& Prefix, const FNiagaraTypeDefinition& RootType, const TArray& NamePath) { // We need to deal with matrix parameters differently than any other type by using array syntax. // As we recurse down the tree, we stay away of when we're dealing with a matrix and adjust // accordingly. FString Value = Prefix; FNiagaraTypeDefinition CurrentType = RootType; bool bParentWasMatrix = (RootType == FNiagaraTypeDefinition::GetMatrix4Def()); int32 ParentMatrixRow = -1; for (int32 i = 0; i < NamePath.Num(); i++) { FString Name = NamePath[i].ToString(); CurrentType = GetChildType(CurrentType, NamePath[i]); // Found a matrix... brackets from here on out. if (CurrentType == FNiagaraTypeDefinition::GetMatrix4Def()) { bParentWasMatrix = true; Value.Append("." + Name); } // Parent was a matrix, determine row.. else if (bParentWasMatrix && CurrentType == FNiagaraTypeDefinition::GetVec4Def()) { Value.Append(ComputeMatrixRowAccess(Name)); } // Parent was a matrix, determine column... else if (bParentWasMatrix && CurrentType == FNiagaraTypeDefinition::GetFloatDef()) { Value.Append(ComputeMatrixColumnAccess(Name)); } // Handle all other valid types by just using "." else if (CurrentType.IsValid()) { Value.Append("." + Name); } else { Error(FText::FromString("Failed to generate type for " + Name + " up to path " + Value), nullptr, nullptr); } } return Value; } FString FHlslNiagaraCompiler::GenerateAssignment(const FNiagaraTypeDefinition& SrcPinType, const TArray& ConditionedSourcePath, const FNiagaraTypeDefinition& DestPinType, const TArray& ConditionedDestinationPath) { FString SourceDefinition = NamePathToString("{1}", SrcPinType, ConditionedSourcePath); FString DestinationDefinition = NamePathToString("{0}", DestPinType, ConditionedDestinationPath); return DestinationDefinition + " = " + SourceDefinition; } void FHlslNiagaraCompiler::Convert(class UNiagaraNodeConvert* Convert, TArray & Inputs, TArray& Outputs) { if (ValidateTypePins(Convert) == false) { return; } TArray InputPins; Convert->GetInputPins(InputPins); TArray OutputPins; Convert->GetOutputPins(OutputPins); // Generate outputs. for (UEdGraphPin* OutputPin : OutputPins) { if (OutputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) { FNiagaraTypeDefinition Type = Schema->PinToTypeDefinition(OutputPin); int32 OutChunk = AddBodyChunk(GetUniqueSymbolName(*OutputPin->PinName), TEXT(""), Type); Outputs.Add(OutChunk); } } // Add an additional invalid output for the add pin which doesn't get compiled. Outputs.Add(INDEX_NONE); // Set output values based on connections. for (FNiagaraConvertConnection& Connection : Convert->GetConnections()) { int32 SourceIndex = GetPinIndexById(InputPins, Connection.SourcePinId); int32 DestinationIndex = GetPinIndexById(OutputPins, Connection.DestinationPinId); if (SourceIndex != INDEX_NONE && SourceIndex < Inputs.Num() && DestinationIndex != INDEX_NONE && DestinationIndex < Outputs.Num()) { FNiagaraTypeDefinition SrcPinType = Schema->PinToTypeDefinition(InputPins[SourceIndex]); TArray ConditionedSourcePath = ConditionPropertyPath(SrcPinType, Connection.SourcePath); FNiagaraTypeDefinition DestPinType = Schema->PinToTypeDefinition(OutputPins[DestinationIndex]); TArray ConditionedDestinationPath = ConditionPropertyPath(DestPinType, Connection.DestinationPath); FString ConvertDefinition = GenerateAssignment(SrcPinType, ConditionedSourcePath, DestPinType, ConditionedDestinationPath); TArray SourceChunks; SourceChunks.Add(Outputs[DestinationIndex]); SourceChunks.Add(Inputs[SourceIndex]); AddBodyChunk(TEXT(""), ConvertDefinition, FNiagaraTypeDefinition::GetFloatDef(), SourceChunks); } } } void FHlslNiagaraCompiler::If(TArray& Vars, int32 Condition, TArray& PathA, TArray& PathB, TArray& Outputs) { int32 NumVars = Vars.Num(); check(PathA.Num() == NumVars); check(PathB.Num() == NumVars); TArray OutSymbols; OutSymbols.Reserve(Vars.Num()); for (FNiagaraVariable& Var : Vars) { OutSymbols.Add(GetUniqueSymbolName(*(Var.GetName().ToString() + TEXT("_IfResult")))); Outputs.Add(AddBodyChunk(OutSymbols.Last(), TEXT(""), Var.GetType(), true)); } AddBodyChunk(TEXT(""), TEXT("if({0})\n\t{"), FNiagaraTypeDefinition::GetFloatDef(), Condition, false); for (int32 i = 0; i < NumVars; ++i) { FNiagaraCodeChunk& OutChunk = CodeChunks[Outputs[i]]; FNiagaraCodeChunk& BranchChunk = CodeChunks[AddBodyChunk(OutSymbols[i], TEXT("{0}"), OutChunk.Type, false)]; BranchChunk.AddSourceChunk(PathA[i]); } AddBodyChunk(TEXT(""), TEXT("}\n\telse\n\t{"), FNiagaraTypeDefinition::GetFloatDef(), false); for (int32 i = 0; i < NumVars; ++i) { FNiagaraCodeChunk& OutChunk = CodeChunks[Outputs[i]]; FNiagaraCodeChunk& BranchChunk = CodeChunks[AddBodyChunk(OutSymbols[i], TEXT("{0}"), OutChunk.Type, false)]; BranchChunk.AddSourceChunk(PathB[i]); } AddBodyChunk(TEXT(""), TEXT("}"), FNiagaraTypeDefinition::GetFloatDef(), false); // Add an additional invalid output for the add pin which doesn't get compiled. Outputs.Add(INDEX_NONE); } int32 FHlslNiagaraCompiler::CompilePin(UEdGraphPin* Pin) { check(Pin); int32 Ret = INDEX_NONE; if (Pin->Direction == EGPD_Input) { if (Pin->LinkedTo.Num() > 0) { Ret = CompileOutputPin(Pin->LinkedTo[0]); } else if (!Pin->bDefaultValueIsIgnored && Pin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) { //No connections to this input so add the default as a const expression. FNiagaraVariable PinVar = Schema->PinToNiagaraVariable(Pin, true); return GetConstant(PinVar); } } else { Ret = CompileOutputPin(Pin); } return Ret; } int32 FHlslNiagaraCompiler::CompileOutputPin(UEdGraphPin* Pin) { check(Pin->Direction == EGPD_Output); int32 Ret = INDEX_NONE; int32* Chunk = PinToCodeChunks.Last().Find(Pin); if (Chunk) { Ret = *Chunk; //We've compiled this pin before. Return it's chunk. } else { //Otherwise we need to compile the node to get its output pins. UNiagaraNode* Node = Cast(Pin->GetOwningNode()); if (ValidateTypePins(Node)) { TArray Outputs; TArray OutputPins; Node->GetOutputPins(OutputPins); INiagaraCompiler* ThisCompiler = this; Node->Compile(ThisCompiler, Outputs); if (OutputPins.Num() == Outputs.Num()) { for (int32 i = 0; i < Outputs.Num(); ++i) { //Cache off the pin. //Can we allow the caching of local defaults in numerous function calls? PinToCodeChunks.Last().Add(OutputPins[i], Outputs[i]); if (Outputs[i] != INDEX_NONE) { //Grab the expression for the pin we're currently interested in. Otherwise we'd have to search the map for it. Ret = OutputPins[i] == Pin ? Outputs[i] : Ret; } } } else { Error(LOCTEXT("IncorrectNumOutputsError", "Incorect number of outputs. Can possibly be fixed with a graph refresh."), Node, Pin); } } } return Ret; } void FHlslNiagaraCompiler::Error(FText ErrorText, UNiagaraNode* Node, UEdGraphPin* Pin) { FString ErrorString = FString::Printf(TEXT("Node: @@ - Pin: @@ - %s - Callstack: %s"), *ErrorText.ToString(), *GetCallstack()); MessageLog.Error(*ErrorString, Node, Pin); } void FHlslNiagaraCompiler::Warning(FText WarningText, UNiagaraNode* Node, UEdGraphPin* Pin) { FString WarnString = FString::Printf(TEXT("Node: @@ - Pin: @@ - %s - Callstack: %s"), *WarningText.ToString(), *GetCallstack()); MessageLog.Warning(*WarnString, Node, Pin); } UNiagaraScript* FHlslNiagaraCompiler::GetFunctionScript() { if (FunctionCtx()) { return FunctionCtx()->FunctionScript; } return nullptr; } bool FHlslNiagaraCompiler::GetFunctionParameter(const FNiagaraVariable& Parameter, int32& OutParam)const { // Assume that it wasn't bound by default. OutParam = INDEX_NONE; if (const FFunctionContext* FunctionContext = FunctionCtx()) { int32 ParamIdx = FunctionContext->Signature.Inputs.IndexOfByPredicate([&](const FNiagaraVariable& InVar) { return InVar.IsEquivalent(Parameter); }); if (ParamIdx != INDEX_NONE) { OutParam = FunctionContext->Inputs[ParamIdx]; } return true; } return false; } bool FHlslNiagaraCompiler::CanReadAttributes()const { if (Script->IsUpdateScript() || (Script->IsInterpolatedSpawnScript() && !bInsideInterpolatedSpawnScript)) { return true; } return false; } ////////////////////////////////////////////////////////////////////////// FString FHlslNiagaraCompiler::GetHlslDefaultForType(FNiagaraTypeDefinition Type) { if (Type == FNiagaraTypeDefinition::GetFloatDef()) { return "(0.0)"; } else if (Type == FNiagaraTypeDefinition::GetVec2Def()) { return "(0.0,0.0)"; } else if (Type == FNiagaraTypeDefinition::GetVec3Def()) { return "(0.0,0.0,0.0)"; } else if (Type == FNiagaraTypeDefinition::GetVec4Def()) { return "(0.0,0.0,0.0,0.0)"; } else if (Type == FNiagaraTypeDefinition::GetColorDef()) { return "(1.0,1.0,1.0,1.0)"; } else if (Type == FNiagaraTypeDefinition::GetIntDef()) { return "(0)"; } else if (Type == FNiagaraTypeDefinition::GetBoolDef()) { return "(true)"; } else { return Type.GetName(); } } bool FHlslNiagaraCompiler::IsBuiltInHlslType(FNiagaraTypeDefinition Type) { return Type == FNiagaraTypeDefinition::GetFloatDef() || Type == FNiagaraTypeDefinition::GetVec2Def() || Type == FNiagaraTypeDefinition::GetVec3Def() || Type == FNiagaraTypeDefinition::GetVec4Def() || Type == FNiagaraTypeDefinition::GetColorDef() || Type == FNiagaraTypeDefinition::GetMatrix4Def() || Type == FNiagaraTypeDefinition::GetIntDef() || Type == FNiagaraTypeDefinition::GetBoolDef(); } FString FHlslNiagaraCompiler::GetStructHlslTypeName(FNiagaraTypeDefinition Type) { if (Type.IsValid() == false) { return "undefined"; } else if (Type == FNiagaraTypeDefinition::GetFloatDef()) { return "float"; } else if (Type == FNiagaraTypeDefinition::GetVec2Def()) { return "float2"; } else if (Type == FNiagaraTypeDefinition::GetVec3Def()) { return "float3"; } else if (Type == FNiagaraTypeDefinition::GetVec4Def() || Type == FNiagaraTypeDefinition::GetColorDef()) { return "float4"; } else if (Type == FNiagaraTypeDefinition::GetMatrix4Def()) { return "float4x4"; } else if (Type == FNiagaraTypeDefinition::GetIntDef()) { return "int"; } else if (Type == FNiagaraTypeDefinition::GetBoolDef()) { return "bool"; } else { return Type.GetName(); } } FString FHlslNiagaraCompiler::GetPropertyHlslTypeName(const UProperty* Property) { if (Property->IsA(UFloatProperty::StaticClass())) { return "float"; } else if (Property->IsA(UIntProperty::StaticClass())) { return "int"; } else if (Property->IsA(UUInt32Property::StaticClass())) { return "int"; } else if (const UStructProperty* StructProp = Cast(Property)) { return GetStructHlslTypeName(StructProp->Struct); } else { check(false); // unknown type return TEXT("UnknownType"); } } FString FHlslNiagaraCompiler::BuildHLSLStructDecl(FNiagaraTypeDefinition Type) { if (!IsBuiltInHlslType(Type)) { FString StructName = GetStructHlslTypeName(Type); FString Decl = "struct " + StructName + "\n{\n"; for (TFieldIterator PropertyIt(Type.GetStruct(), EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; Decl += TEXT("\t") + GetPropertyHlslTypeName(Property) + TEXT(" ") + Property->GetName() + TEXT(";\n"); } Decl += "};\n\n"; return Decl; } return TEXT(""); } bool FHlslNiagaraCompiler::IsHlslBuiltinVector(FNiagaraTypeDefinition Type) { if ((Type == FNiagaraTypeDefinition::GetVec2Def()) || (Type == FNiagaraTypeDefinition::GetVec3Def()) || (Type == FNiagaraTypeDefinition::GetVec4Def()) || (Type == FNiagaraTypeDefinition::GetColorDef())) { return true; } return false; } bool FHlslNiagaraCompiler::AddStructToDefinitionSet(const FNiagaraTypeDefinition& TypeDef) { // First make sure that this is a type that we do need to define... if (IsBuiltInHlslType(TypeDef)) { return true; } if (TypeDef == FNiagaraTypeDefinition::GetGenericNumericDef()) { return false; } // Now make sure that we don't have any other struct types within our struct. Add them prior to the struct in question to make sure // that the syntax works out properly. const UScriptStruct* Struct = TypeDef.GetScriptStruct(); if (Struct != nullptr) { // We need to recursively dig through the struct to get at the lowest level of the input struct, which // could be a native type. for (TFieldIterator PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { const UProperty* Property = *PropertyIt; const UStructProperty* StructProp = Cast(Property); if (StructProp) { if (!AddStructToDefinitionSet(StructProp->Struct)) { return false; } } } } // Add the new type def StructsToDefine.Add(TypeDef); return true; } TArray FHlslNiagaraCompiler::ConditionPropertyPath(const FNiagaraTypeDefinition& Type, const TArray& InPath) { // TODO: Build something more extensible and less hard coded for path conditioning. const UScriptStruct* Struct = Type.GetScriptStruct(); if (InPath.Num() == 0) // Pointing to the root { return TArray(); } else if (IsHlslBuiltinVector(Type)) { checkf(InPath.Num() == 1, TEXT("Invalid path for vector")); TArray ConditionedPath; ConditionedPath.Add(*InPath[0].ToString().ToLower()); return ConditionedPath; } else if (FNiagaraTypeDefinition::IsScalarDefinition(Struct)) { return TArray(); } else if (Struct != nullptr) { // We need to recursively dig through the struct to get at the lowest level of the input path specified, which // could be a native type. for (TFieldIterator PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { const UProperty* Property = *PropertyIt; const UStructProperty* StructProp = Cast(Property); // The names match, but even then things might not match up properly.. if (InPath[0].ToString() == Property->GetName()) { // The names match and this is a nested type, so we can keep digging... if (StructProp != nullptr) { // If our path continues onward, keep recursively digging. Otherwise, just return where we've gotten to so far. if (InPath.Num() > 1) { TArray ReturnPath; ReturnPath.Add(InPath[0]); TArray Subset = InPath; Subset.RemoveAt(0); TArray Children = ConditionPropertyPath(StructProp->Struct, Subset); for (const FName& Child : Children) { ReturnPath.Add(Child); } return ReturnPath; } else { TArray ConditionedPath; ConditionedPath.Add(InPath[0]); return ConditionedPath; } } } } return InPath; } return InPath; } ////////////////////////////////////////////////////////////////////////// FString FHlslNiagaraCompiler::CompileDataInterfaceFunction(UNiagaraDataInterface* DataInterface, FNiagaraFunctionSignature& Signature) { //For now I'm compiling data interface functions like this. //Not the prettiest thing in the world but it'll suffice for now. if (UNiagaraDataInterfaceCurve* Curve = Cast(DataInterface)) { //For now, VM only which needs no body. GPU will need a body. return TEXT(""); } else if (UNiagaraDataInterfaceVectorCurve* VecCurve = Cast(DataInterface)) { //For now, VM only which needs no body. GPU will need a body. return TEXT(""); } else if (UNiagaraDataInterfaceColorCurve* ColorCurve = Cast(DataInterface)) { //For now, VM only which needs no body. GPU will need a body. return TEXT(""); } else if (UNiagaraDataInterfaceStaticMesh* Mesh = Cast(DataInterface)) { return TEXT(""); } else if (UNiagaraDataInterfaceCurlNoise* Noise = Cast(DataInterface)) { return TEXT(""); } else { return TEXT(""); check(0); } } ////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE