void FNiagaraEditorModule::CompileScript(UNiagaraScript* ScriptToCompile) { check(ScriptToCompile != NULL); if (ScriptToCompile->Source == NULL) { UE_LOG(LogNiagaraCompiler, Error, TEXT("No source for Niagara script: %s"), *ScriptToCompile->GetPathName()); return; } TComponentReregisterContext<UNiagaraComponent> ComponentReregisterContext; UNiagaraScriptSource* Source = CastChecked<UNiagaraScriptSource>(ScriptToCompile->Source); check(Source->UpdateGraph); // Results log. FCompilerResultsLog MessageLog; // Clone the source graph so we can modify it as needed; merging in the child graphs UEdGraph* UpdateGraph = FEdGraphUtilities::CloneGraph(Source->UpdateGraph, Source, &MessageLog, true); FEdGraphUtilities::MergeChildrenGraphsIn(UpdateGraph, UpdateGraph, /*bRequireSchemaMatch=*/ true); // Find the output node. UNiagaraNodeOutputUpdate* OutputNode = NULL; { TArray<UNiagaraNodeOutputUpdate*> OutputNodes; UpdateGraph->GetNodesOfClass(OutputNodes); if (OutputNodes.Num() != 1) { UE_LOG(LogNiagaraCompiler, Error, TEXT("Script contains %s output nodes: %s"), OutputNodes.Num() == 0 ? TEXT("no") : TEXT("too many"), *ScriptToCompile->GetPathName() ); return; } OutputNode = OutputNodes[0]; } check(OutputNode); // Traverse the node graph for each output and generate expressions as we go. FNiagaraCompilerContext Context(MessageLog); Source->GetParticleAttributes(Context.Attributes); TArray<int32> OutputExpressions; OutputExpressions.AddUninitialized(Context.Attributes.Num()); FMemory::Memset(OutputExpressions.GetData(), 0xff, Context.Attributes.Num() * sizeof(int32)); for (int32 PinIndex = 0; PinIndex < OutputNode->Pins.Num(); ++PinIndex) { UEdGraphPin* Pin = OutputNode->Pins[PinIndex]; int32 AttrIndex = Context.Attributes.Find(FName(*Pin->PinName)); if (AttrIndex != INDEX_NONE) { int32 ExpressionIndex = EvaluateGraph(Context, Pin); OutputExpressions[AttrIndex] = ExpressionIndex; } } // Generate pass-thru ops for any outputs that are not connected. for (int32 AttrIndex = 0; AttrIndex < OutputExpressions.Num(); ++AttrIndex) { if (OutputExpressions[AttrIndex] == INDEX_NONE) { // Generate a pass-thru op. FNiagaraExpr PassThru; PassThru.OpIndex = VectorVM::EOp::add; PassThru.Src[0] = AttrIndex | 0x80000000; PassThru.Src[1] = 0 | 0x40000000; PassThru.Src[2] = INDEX_NONE; PassThru.SrcOperandTypeVector = 0x2; OutputExpressions[AttrIndex] = Context.Expressions.Add(PassThru); } } // Figure out the lifetime of each expression. TArray<int32> ExpressionLifetimes; ExpressionLifetimes.AddUninitialized(Context.Expressions.Num()); for (int32 i = 0; i < Context.Expressions.Num(); ++i) { ExpressionLifetimes[i] = i; FNiagaraExpr& Expr = Context.Expressions[i]; VectorVM::FVectorVMOpInfo const& OpInfo = VectorVM::GetOpCodeInfo(Expr.OpIndex); for (int32 k = 0; k < 3; ++k) { //if (OpInfo.SrcTypes[k] == VectorVM::EOpSrc::Register if ((Expr.SrcOperandTypeVector & (1 << k)) == 0 && OpInfo.SrcTypes[k] != VectorVM::EOpSrc::Invalid && !IsAttrIndex(Expr.Src[k])) { check(Context.Expressions.IsValidIndex(Expr.Src[k])); check(Expr.Src[k] < i); ExpressionLifetimes[Expr.Src[k]] = i; } } } // Allocate temporary registers for the output of each expression. int32 Registers[VectorVM::NumTempRegisters]; FMemory::Memset(Registers, 0xff, sizeof(Registers)); TArray<int32> RegisterAssignments; RegisterAssignments.AddUninitialized(Context.Expressions.Num()); for (int32 i = 0; i < Context.Expressions.Num(); ++i) { FNiagaraExpr& Expr = Context.Expressions[i]; VectorVM::FVectorVMOpInfo const& OpInfo = VectorVM::GetOpCodeInfo(Expr.OpIndex); for (int32 j = 0; j < 3; ++j) { //if (OpInfo.SrcTypes[j] == VectorVM::EOpSrc::Register) if ((Expr.SrcOperandTypeVector & (1 << j)) == 0 && OpInfo.SrcTypes[j] != VectorVM::EOpSrc::Invalid) { if (IsAttrIndex(Expr.Src[j])) { Expr.Src[j] = VectorVM::FirstInputRegister + AttrIndexFromExpressionIndex(Expr.Src[j]); } else { Expr.Src[j] = RegisterAssignments[Expr.Src[j]]; } } //else if (OpInfo.SrcTypes[j] == VectorVM::EOpSrc::Const) else if ((Expr.SrcOperandTypeVector & (1 << j)) == 1) { // VectorVM::EOpSrc::Const. Expr.Src[j] = ConstIndexFromExpressionIndex(Expr.Src[j]); //Expr.SrcOperandTypeVector |= 1<<j; } } // now that we've figured out register/constant assignments, we revert back to the base opcode; the kernel Exec function will handle passing // the data in correctly based on the SrcOperandTypeVector. It would be good to change the compiler so we don't need the permutation // opcodes at all, i.e. figure out source operand types without needing them in the OpInfo. Expr.OpIndex = OpInfo.BaseOpcode; int32 AttrIndex = INDEX_NONE; if (OutputExpressions.Find(i, AttrIndex)) { // Assign to an output register. RegisterAssignments[i] = VectorVM::FirstOutputRegister + AttrIndex; } else { int32 AssignedRegister = INDEX_NONE; for (int32 j = 0; j < VectorVM::NumTempRegisters; ++j) { if (Registers[j] == INDEX_NONE || ExpressionLifetimes[Registers[j]] < i) { AssignedRegister = j; break; } } check(AssignedRegister != INDEX_NONE && AssignedRegister < VectorVM::NumTempRegisters); Registers[AssignedRegister] = i; RegisterAssignments[i] = AssignedRegister; } } // Generate bytecode! TArray<uint8>& Code = ScriptToCompile->ByteCode; Code.Empty(); for (int32 i = 0; i < Context.Expressions.Num(); ++i) { FNiagaraExpr& Expr = Context.Expressions[i]; VectorVM::FVectorVMOpInfo const& OpInfo = VectorVM::GetOpCodeInfo(Expr.OpIndex); int32 DestRegister = RegisterAssignments[i]; check(DestRegister < VectorVM::MaxRegisters); check(Expr.OpIndex < VectorVM::EOp::NumOpcodes); check(Expr.OpIndex == OpInfo.BaseOpcode); Code.Add(Expr.OpIndex); Code.Add(DestRegister); Code.Add(Expr.SrcOperandTypeVector); for (int32 j = 0; j < 3; ++j) { if (OpInfo.SrcTypes[j] == VectorVM::EOpSrc::Invalid) break; Code.Add(Expr.Src[j]); } // If the expression is output to multiple attributes, copy them here. if (DestRegister >= VectorVM::FirstOutputRegister) { int32 AttrIndex = DestRegister - VectorVM::FirstOutputRegister; for (int32 j = AttrIndex+1; j < OutputExpressions.Num(); ++j) { if (OutputExpressions[j] == i) { Code.Add(VectorVM::EOp::add); Code.Add(VectorVM::FirstOutputRegister + j); Code.Add(0x0); // all inputs are registers Code.Add(DestRegister); Code.Add(0); } } } } // Terminate with the 'done' opcode. Code.Add(VectorVM::EOp::done); // And copy the constant table and attributes. ScriptToCompile->ConstantTable = Context.Constants; ScriptToCompile->Attributes = Context.Attributes; }
void UEdGraphSchema_Niagara::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const { const UNiagaraGraph* NiagaraGraph = CastChecked<UNiagaraGraph>(ContextMenuBuilder.CurrentGraph); UNiagaraScriptSource* Source = NiagaraGraph->GetSource(); TArray<FName> InputAttributeNames; Source->GetParticleAttributes(InputAttributeNames); for(int32 i=0; i<InputAttributeNames.Num(); i++) { const FName AttrName = InputAttributeNames[i]; FFormatNamedArguments Args; Args.Add(TEXT("Attribute"), FText::FromName(AttrName)); const FText MenuDesc = FText::Format(NSLOCTEXT("Niagara", "GetAttribute", "Get {Attribute}"), Args); TSharedPtr<FNiagaraSchemaAction_NewNode> GetAttrAction = AddNewNodeAction(ContextMenuBuilder, TEXT("Get Attribute"), MenuDesc, TEXT("")); UNiagaraNodeGetAttr* GetAttrNode = NewObject<UNiagaraNodeGetAttr>(ContextMenuBuilder.OwnerOfTemporaries); GetAttrNode->AttrName = AttrName; GetAttrAction->NodeTemplate = GetAttrNode; } TArray<FName> EmitterConstantNames_Vectors; TArray<FName> EmitterConstantNames_Matrices; Source->GetEmitterAttributes(EmitterConstantNames_Vectors, EmitterConstantNames_Matrices); for (int32 i = 0; i < EmitterConstantNames_Vectors.Num(); i++) { const FName ConstName = EmitterConstantNames_Vectors[i]; FFormatNamedArguments Args; Args.Add(TEXT("Constant"), FText::FromName(ConstName)); const FText MenuDesc = FText::Format(NSLOCTEXT("Niagara", "GetConstant", "Get {Constant}"), Args); TSharedPtr<FNiagaraSchemaAction_NewNode> GetAttrAction = AddNewNodeAction(ContextMenuBuilder, TEXT("Get Constant"), MenuDesc, TEXT("")); UNiagaraNodeConstant* GetConstNode = NewObject<UNiagaraNodeConstant>(ContextMenuBuilder.OwnerOfTemporaries); GetConstNode->ConstName = ConstName; GetConstNode->DataType = ENiagaraDataType::Vector; GetConstNode->bNeedsDefault = false; GetAttrAction->NodeTemplate = GetConstNode; } for (int32 i = 0; i < EmitterConstantNames_Matrices.Num(); i++) { const FName ConstName = EmitterConstantNames_Matrices[i]; FFormatNamedArguments Args; Args.Add(TEXT("Constant"), FText::FromName(ConstName)); const FText MenuDesc = FText::Format(NSLOCTEXT("Niagara", "GetConstant", "Get {Constant}"), Args); TSharedPtr<FNiagaraSchemaAction_NewNode> GetAttrAction = AddNewNodeAction(ContextMenuBuilder, TEXT("Get Constant"), MenuDesc, TEXT("")); UNiagaraNodeConstant* GetConstNode = NewObject<UNiagaraNodeConstant>(ContextMenuBuilder.OwnerOfTemporaries); GetConstNode->ConstName = ConstName; GetConstNode->DataType = ENiagaraDataType::Matrix; GetConstNode->bNeedsDefault = false; GetAttrAction->NodeTemplate = GetConstNode; } #define NiagaraOp(OPNAME) \ if(const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(FNiagaraOpInfo::OPNAME))\ {\ TSharedPtr<FNiagaraSchemaAction_NewNode> AddOpAction = AddNewNodeAction(ContextMenuBuilder, TEXT("Add Operation"), OpInfo->FriendlyName, TEXT(""));\ UNiagaraNodeOp* OpNode = NewObject<UNiagaraNodeOp>(ContextMenuBuilder.OwnerOfTemporaries); \ OpNode->OpName = OpInfo->Name; \ AddOpAction->NodeTemplate = OpNode; \ } NiagaraOpList #undef NiagaraOp UNiagaraNodeConstant* ConstantNode = NewObject<UNiagaraNodeConstant>(ContextMenuBuilder.OwnerOfTemporaries); ConstantNode->DataType = ENiagaraDataType::Vector; ConstantNode->bNeedsDefault = true; TSharedPtr<FNiagaraSchemaAction_NewNode> AddOpAction = AddNewNodeAction(ContextMenuBuilder, TEXT("Constants"), ConstantNode->GetNodeTitle(ENodeTitleType::ListView), TEXT("")); AddOpAction->NodeTemplate = ConstantNode; }
UObject* UNiagaraScriptFactoryNew::FactoryCreateNew(UClass* Class,UObject* InParent,FName Name,EObjectFlags Flags,UObject* Context,FFeedbackContext* Warn) { check(Class->IsChildOf(UNiagaraScript::StaticClass())); // First allocate runtime script UNiagaraScript* NewScript = ConstructObject<UNiagaraScript>(Class, InParent, Name, Flags); if(NewScript != NULL) { // Then allocate editor-only 'source' object UNiagaraScriptSource* Source = ConstructObject<UNiagaraScriptSource>(UNiagaraScriptSource::StaticClass(), NewScript, NAME_None, RF_Transactional); if(Source) { // Create 'update graph' UNiagaraGraph* CreatedGraph = ConstructObject<UNiagaraGraph>(UNiagaraGraph::StaticClass(), Source, NAME_None, RF_Transactional); Source->UpdateGraph = CreatedGraph; FGraphNodeCreator<UNiagaraNodeOutputUpdate> OutputNodeCreator(*CreatedGraph); UNiagaraNodeOutputUpdate* OutputNode = OutputNodeCreator.CreateNode(); OutputNodeCreator.Finalize(); FGraphNodeCreator<UNiagaraNodeGetAttr> TimeNodeCreator(*CreatedGraph); UNiagaraNodeGetAttr* DeltaTimeNode = TimeNodeCreator.CreateNode(); DeltaTimeNode->AttrName = FName(TEXT("DeltaTime")); TimeNodeCreator.Finalize(); UEdGraphPin* DeltaTimePin = DeltaTimeNode->FindPinChecked(DeltaTimeNode->AttrName.ToString()); TArray<FName> OutputNames; Source->GetUpdateOutputs(OutputNames); // Simple Euler integration. for(int32 i=0; i<3; i++) { FGraphNodeCreator<UNiagaraNodeGetAttr> PosNodeCreator(*CreatedGraph); UNiagaraNodeGetAttr* PosAttrNode = PosNodeCreator.CreateNode(); PosAttrNode->AttrName = OutputNames[i]; PosNodeCreator.Finalize(); FGraphNodeCreator<UNiagaraNodeGetAttr> VelNodeCreator(*CreatedGraph); UNiagaraNodeGetAttr* VelAttrNode = VelNodeCreator.CreateNode(); VelAttrNode->AttrName = OutputNames[i+3]; VelNodeCreator.Finalize(); UEdGraphPin* PosAttrOutputPin = PosAttrNode->FindPinChecked(OutputNames[i].ToString()); UEdGraphPin* VelAttrOutputPin = VelAttrNode->FindPinChecked(OutputNames[i+3].ToString()); UEdGraphPin* OutputPin = OutputNode->FindPinChecked(OutputNames[i].ToString()); UNiagaraNodeOp* MulNode = NULL; { FGraphNodeCreator<UNiagaraNodeOp> NodeCreator(*CreatedGraph); MulNode = NodeCreator.CreateNode(); MulNode->OpIndex = VectorVM::EOp::mul; NodeCreator.Finalize(); UEdGraphPin* APin = MulNode->FindPinChecked(TEXT("A")); UEdGraphPin* BPin = MulNode->FindPinChecked(TEXT("B")); APin->MakeLinkTo(VelAttrOutputPin); BPin->MakeLinkTo(DeltaTimePin); } { FGraphNodeCreator<UNiagaraNodeOp> NodeCreator(*CreatedGraph); UNiagaraNodeOp* AddNode = NodeCreator.CreateNode(); AddNode->OpIndex = VectorVM::EOp::add; NodeCreator.Finalize(); UEdGraphPin* APin = AddNode->FindPinChecked(TEXT("A")); UEdGraphPin* BPin = AddNode->FindPinChecked(TEXT("B")); UEdGraphPin* AddResultPin = AddNode->FindPinChecked(TEXT("Result")); UEdGraphPin* MulResultPin = MulNode->FindPinChecked(TEXT("Result")); APin->MakeLinkTo(PosAttrOutputPin); BPin->MakeLinkTo(MulResultPin); OutputPin->MakeLinkTo(AddResultPin); } } // Update relative time. { FGraphNodeCreator<UNiagaraNodeGetAttr> RelTimeNodeCreator(*Source->UpdateGraph); UNiagaraNodeGetAttr* RelTimeAttrNode = RelTimeNodeCreator.CreateNode(); RelTimeAttrNode->AttrName = FName(TEXT("RelativeTime")); RelTimeNodeCreator.Finalize(); FGraphNodeCreator<UNiagaraNodeOp> MadNodeCreator(*Source->UpdateGraph); UNiagaraNodeOp* MadNode = MadNodeCreator.CreateNode(); MadNode->OpIndex = VectorVM::EOp::mad; MadNodeCreator.Finalize(); MadNode->FindPinChecked(TEXT("A"))->MakeLinkTo(DeltaTimePin); MadNode->FindPinChecked(TEXT("B"))->DefaultValue = TEXT("0.2"); MadNode->FindPinChecked(TEXT("C"))->MakeLinkTo(RelTimeAttrNode->FindPinChecked(TEXT("RelativeTime"))); OutputNode->FindPinChecked(TEXT("RelativeTime"))->MakeLinkTo(MadNode->FindPinChecked(TEXT("Result"))); } // Set pointer in script to source NewScript->Source = Source; FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::Get().LoadModuleChecked<FNiagaraEditorModule>(TEXT("NiagaraEditor")); NiagaraEditorModule.CompileScript(NewScript); } } return NewScript; }