Files
UnrealEngine/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorUtilities.cpp
2025-05-18 13:04:45 +08:00

961 lines
37 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MaterialEditorUtilities.h"
#include "UObject/UObjectHash.h"
#include "EdGraph/EdGraph.h"
#include "Materials/Material.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "MaterialGraph/MaterialGraphNode_Composite.h"
#include "IMaterialEditor.h"
#include "Materials/MaterialExpressionFunctionInput.h"
#include "Materials/MaterialExpressionFunctionOutput.h"
#include "Materials/MaterialExpressionParameter.h"
#include "Materials/MaterialExpressionStaticBoolParameter.h"
#include "Materials/MaterialExpressionStaticBool.h"
#include "Materials/MaterialExpressionStaticSwitch.h"
#include "Materials/MaterialExpressionComment.h"
#include "Materials/MaterialExpressionComposite.h"
#include "Materials/MaterialExpressionTextureCollectionParameter.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "Materials/MaterialExpressionTextureSampleParameter.h"
#include "Materials/MaterialExpressionRuntimeVirtualTextureSampleParameter.h"
#include "Materials/MaterialExpressionSparseVolumeTextureSample.h"
#include "Materials/MaterialExpressionFontSampleParameter.h"
#include "Materials/MaterialExpressionMaterialFunctionCall.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Materials/MaterialExpressionStaticSwitchParameter.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "Materials/MaterialExpressionMaterialAttributeLayers.h"
#include "Materials/MaterialExpressionRerouteBase.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialInstanceConstant.h"
#include "DebugViewModeHelpers.h"
#include "Toolkits/ToolkitManager.h"
#include "MaterialEditor.h"
#include "MaterialExpressionClasses.h"
#include "MaterialInstanceEditor.h"
#include "Materials/MaterialInstance.h"
#include "MaterialUtilities.h"
#include "Misc/ScopedSlowTask.h"
#include "Templates/UniquePtr.h"
#include "Materials/MaterialFunctionInstance.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "MaterialEditor/PreviewMaterial.h"
#define LOCTEXT_NAMESPACE "MaterialEditorUtilities"
DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditorUtilities, Log, All);
UMaterialExpression* FMaterialEditorUtilities::CreateNewMaterialExpression(const class UEdGraph* Graph, UClass* NewExpressionClass, const UE::Slate::FDeprecateVector2DParameter& NodePos, bool bAutoSelect, bool bAutoAssignResource)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
return MaterialEditor->CreateNewMaterialExpression(NewExpressionClass, FDeprecateSlateVector2D(NodePos), bAutoSelect, bAutoAssignResource, Graph);
}
return nullptr;
}
UMaterialExpressionComposite* FMaterialEditorUtilities::CreateNewMaterialExpressionComposite(const class UEdGraph* Graph, const FVector2D& NodePos)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
return MaterialEditor->CreateNewMaterialExpressionComposite(NodePos, Graph);
}
return nullptr;
}
UMaterialExpressionComment* FMaterialEditorUtilities::CreateNewMaterialExpressionComment(const class UEdGraph* Graph, const FVector2D& NodePos)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
return MaterialEditor->CreateNewMaterialExpressionComment(NodePos, Graph);
}
return nullptr;
}
void FMaterialEditorUtilities::ForceRefreshExpressionPreviews(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->ForceRefreshExpressionPreviews();
}
}
void FMaterialEditorUtilities::AddToSelection(const class UEdGraph* Graph, UMaterialExpression* Expression)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->AddToSelection(Expression);
}
}
void FMaterialEditorUtilities::DeleteSelectedNodes(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->DeleteSelectedNodes();
}
}
void FMaterialEditorUtilities::DeleteNodes(const class UEdGraph* Graph, const TArray<UEdGraphNode*>& NodesToDelete)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->DeleteNodes(NodesToDelete);
}
}
FText FMaterialEditorUtilities::GetOriginalObjectName(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
return MaterialEditor->GetOriginalObjectName();
}
return FText::GetEmpty();
}
void FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->UpdateMaterialAfterGraphChange();
}
}
void FMaterialEditorUtilities::MarkMaterialDirty(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->MarkMaterialDirty();
}
}
void FMaterialEditorUtilities::UpdateDetailView(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if (MaterialEditor.IsValid())
{
MaterialEditor->UpdateDetailView();
}
}
bool FMaterialEditorUtilities::CanPasteNodes(const class UEdGraph* Graph)
{
bool bCanPaste = false;
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if(MaterialEditor.IsValid())
{
bCanPaste = MaterialEditor->CanPasteNodes();
}
return bCanPaste;
}
void FMaterialEditorUtilities::PasteNodesHere(class UEdGraph* Graph, const FVector2D& Location)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if(MaterialEditor.IsValid())
{
MaterialEditor->PasteNodesHere(Location);
}
}
int32 FMaterialEditorUtilities::GetNumberOfSelectedNodes(const class UEdGraph* Graph)
{
int32 SelectedNodes = 0;
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if(MaterialEditor.IsValid())
{
SelectedNodes = MaterialEditor->GetNumberOfSelectedNodes();
}
return SelectedNodes;
}
void FMaterialEditorUtilities::GetMaterialExpressionActions(FGraphActionMenuBuilder& ActionMenuBuilder, bool bMaterialFunction)
{
UObject* MaterialOrFunction = bMaterialFunction ? UMaterialFunction::StaticClass()->GetDefaultObject() : UMaterial::StaticClass()->GetDefaultObject();
GetMaterialExpressionActions(ActionMenuBuilder, MaterialOrFunction);
}
void FMaterialEditorUtilities::GetMaterialExpressionActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UObject* MaterialOrFunction)
{
bool bUseUnsortedMenus = false;
MaterialExpressionClasses* ExpressionClasses = MaterialExpressionClasses::Get();
if (bUseUnsortedMenus)
{
AddMaterialExpressionCategory(ActionMenuBuilder, FText::GetEmpty(), &ExpressionClasses->AllExpressionClasses, MaterialOrFunction);
}
else
{
// Add Favourite expressions as a category
const FText FavouritesCategory = LOCTEXT("FavoritesMenu", "Favorites");
AddMaterialExpressionCategory(ActionMenuBuilder, FavouritesCategory, &ExpressionClasses->FavoriteExpressionClasses, MaterialOrFunction);
// Add each category to the menu
for (int32 CategoryIndex = 0; CategoryIndex < ExpressionClasses->CategorizedExpressionClasses.Num(); ++CategoryIndex)
{
FCategorizedMaterialExpressionNode* CategoryNode = &(ExpressionClasses->CategorizedExpressionClasses[CategoryIndex]);
AddMaterialExpressionCategory(ActionMenuBuilder, CategoryNode->CategoryName, &CategoryNode->MaterialExpressions, MaterialOrFunction);
}
if (ExpressionClasses->UnassignedExpressionClasses.Num() > 0)
{
AddMaterialExpressionCategory(ActionMenuBuilder, FText::GetEmpty(), &ExpressionClasses->UnassignedExpressionClasses, MaterialOrFunction);
}
}
}
bool FMaterialEditorUtilities::IsMaterialExpressionInFavorites(UMaterialExpression* InExpression)
{
return MaterialExpressionClasses::Get()->IsMaterialExpressionInFavorites(InExpression);
}
FMaterialRenderProxy* FMaterialEditorUtilities::GetExpressionPreview(const class UEdGraph* Graph, UMaterialExpression* InExpression)
{
FMaterialRenderProxy* ExpressionPreview = NULL;
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if(MaterialEditor.IsValid())
{
ExpressionPreview = MaterialEditor->GetExpressionPreview(InExpression);
}
return ExpressionPreview;
}
void FMaterialEditorUtilities::UpdateSearchResults(const class UEdGraph* Graph)
{
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
if(MaterialEditor.IsValid())
{
MaterialEditor->UpdateSearch(false);
}
}
/////////////////////////////////////////////////////
// Static functions moved from SMaterialEditorCanvas
void FMaterialEditorUtilities::GetVisibleMaterialParameters(const UMaterial* Material, UMaterialInstance* MaterialInstance, TArray<FMaterialParameterInfo>& VisibleExpressions)
{
check(Material);
check(MaterialInstance);
VisibleExpressions.Empty();
if (Material->IsUsingNewHLSLGenerator())
{
// When using the new HLSL generator, MI parameter list will already have unused parameters culled
// We can assume that any remaining parameters are visible
TArray<FMaterialParameterInfo> ParameterInfo;
TArray<FGuid> ParamterGuid;
for (int32 TypeIndex = 0; TypeIndex < NumMaterialParameterTypes; ++TypeIndex)
{
MaterialInstance->GetAllParameterInfoOfType((EMaterialParameterType)TypeIndex, ParameterInfo, ParamterGuid);
VisibleExpressions.Append(ParameterInfo);
}
return;
}
TUniquePtr<FGetVisibleMaterialParametersFunctionState> FunctionState = MakeUnique<FGetVisibleMaterialParametersFunctionState>(nullptr);
TArray<FGetVisibleMaterialParametersFunctionState*> FunctionStack;
FunctionStack.Push(FunctionState.Get());
for (uint32 i = 0; i < MP_MAX; ++i)
{
FExpressionInput* ExpressionInput = ((UMaterial*)Material)->GetExpressionInputForProperty((EMaterialProperty)i);
if (ExpressionInput)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(ExpressionInput->Expression, ExpressionInput->OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
}
TArray<UMaterialExpressionCustomOutput*> CustomOutputExpressions;
Material->GetAllCustomOutputExpressions(CustomOutputExpressions);
for (UMaterialExpressionCustomOutput* Expression : CustomOutputExpressions)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Expression, 0), MaterialInstance, VisibleExpressions, FunctionStack);
}
TArray<UMaterialExpressionFunctionOutput*> FunctionOutputExpressions;
Material->GetAllFunctionOutputExpressions(FunctionOutputExpressions);
for (UMaterialExpressionFunctionOutput* Expression : FunctionOutputExpressions)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Expression, 0), MaterialInstance, VisibleExpressions, FunctionStack);
}
}
bool FMaterialEditorUtilities::GetStaticSwitchExpressionValue(UMaterialInstance* MaterialInstance, UMaterialExpression* SwitchValueExpression, bool& bOutValue, FGuid& OutExpressionID, TArray<FGetVisibleMaterialParametersFunctionState*>& FunctionStack)
{
// Trace any re-route nodes between the input pin and the actual expression
UMaterialExpression* TracedExpression = SwitchValueExpression;
if (UMaterialExpressionRerouteBase* Reroute = Cast<UMaterialExpressionRerouteBase>(TracedExpression))
{
TracedExpression = Reroute->TraceInputsToRealInput().Expression;
}
// If switch value is a function input expression then we must recursively find the associated input expressions from the parent function/material to evaluate the value.
UMaterialExpressionFunctionInput* FunctionInputExpression = Cast<UMaterialExpressionFunctionInput>(TracedExpression);
if(FunctionInputExpression && FunctionInputExpression->InputType == FunctionInput_StaticBool)
{
FGetVisibleMaterialParametersFunctionState* TopmostFunctionState = FunctionStack.Pop();
if (TopmostFunctionState->FunctionCall)
{
const TArray<FFunctionExpressionInput>* FunctionInputs = &TopmostFunctionState->FunctionCall->FunctionInputs;
// Get the FFunctionExpressionInput which stores information about the input node from the parent that this is linked to.
const FFunctionExpressionInput* MatchingInput = FindInputById(FunctionInputExpression, *FunctionInputs);
if (MatchingInput && (MatchingInput->Input.Expression || !FunctionInputExpression->bUsePreviewValueAsDefault))
{
GetStaticSwitchExpressionValue(MaterialInstance, MatchingInput->Input.Expression, bOutValue, OutExpressionID, FunctionStack);
}
else
{
GetStaticSwitchExpressionValue(MaterialInstance, FunctionInputExpression->Preview.Expression, bOutValue, OutExpressionID, FunctionStack);
}
}
FunctionStack.Push(TopmostFunctionState);
}
if (TracedExpression)
{
UMaterialExpressionStaticBoolParameter* SwitchParamValue = Cast<UMaterialExpressionStaticBoolParameter>(TracedExpression);
UMaterialExpressionStaticBool* StaticBoolValue = Cast<UMaterialExpressionStaticBool>(TracedExpression);
UMaterialExpressionStaticSwitch* StaticSwitchValue = Cast<UMaterialExpressionStaticSwitch>(TracedExpression);
if (SwitchParamValue)
{
// Use the current stack state's parameter association
FMaterialParameterInfo ParamInfo = FunctionStack.Top()->StackParameterInfo;
ParamInfo.Name = SwitchParamValue->ParameterName;
MaterialInstance->GetStaticSwitchParameterValue(ParamInfo, bOutValue, OutExpressionID);
return true;
}
else if (StaticBoolValue)
{
bOutValue = StaticBoolValue->Value;
return true;
}
else if (StaticSwitchValue)
{
bool bSwitchValue = StaticSwitchValue->DefaultValue;
GetStaticSwitchExpressionValue(MaterialInstance, StaticSwitchValue->Value.Expression, bSwitchValue, OutExpressionID, FunctionStack);
if (bSwitchValue)
{
GetStaticSwitchExpressionValue(MaterialInstance, StaticSwitchValue->A.Expression, bOutValue, OutExpressionID, FunctionStack);
}
else
{
GetStaticSwitchExpressionValue(MaterialInstance, StaticSwitchValue->B.Expression, bOutValue, OutExpressionID, FunctionStack);
}
return true;
}
}
return false;
}
const FFunctionExpressionInput* FMaterialEditorUtilities::FindInputById(const UMaterialExpressionFunctionInput* InputExpression, const TArray<FFunctionExpressionInput>& Inputs)
{
for (int32 InputIndex = 0; InputIndex < Inputs.Num(); InputIndex++)
{
const FFunctionExpressionInput& CurrentInput = Inputs[InputIndex];
if (CurrentInput.ExpressionInputId == InputExpression->Id && CurrentInput.ExpressionInput->GetOuter() == InputExpression->GetOuter())
{
return &CurrentInput;
}
}
return NULL;
}
void FMaterialEditorUtilities::InitExpressions(UMaterial* Material)
{
FString ParmName;
Material->GetExpressionCollection().Empty();
TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Material, ChildObjects, /*bIncludeNestedObjects=*/false);
for ( int32 ChildIdx = 0; ChildIdx < ChildObjects.Num(); ++ChildIdx )
{
UMaterialExpression* MaterialExpression = Cast<UMaterialExpression>(ChildObjects[ChildIdx]);
if( IsValid(MaterialExpression) )
{
// Comment expressions are stored in a separate list.
if ( MaterialExpression->IsA( UMaterialExpressionComment::StaticClass() ) )
{
Material->GetExpressionCollection().AddComment( static_cast<UMaterialExpressionComment*>(MaterialExpression) );
}
else
{
Material->GetExpressionCollection().AddExpression( MaterialExpression );
}
}
}
Material->BuildEditorParameterList();
// Propagate RF_Transactional to all referenced material expressions.
Material->SetFlags( RF_Transactional );
for(UMaterialExpression* MaterialExpression : Material->GetExpressions())
{
if(MaterialExpression)
{
MaterialExpression->SetFlags( RF_Transactional );
}
}
for(UMaterialExpressionComment* Comment : Material->GetEditorComments())
{
Comment->SetFlags( RF_Transactional );
}
}
///////////
// private
void FMaterialEditorUtilities::GetVisibleMaterialParametersFromExpression(
FMaterialExpressionKey MaterialExpressionKey,
UMaterialInstance* MaterialInstance,
TArray<FMaterialParameterInfo>& VisibleExpressions,
TArray<FGetVisibleMaterialParametersFunctionState*>& FunctionStack)
{
if (!MaterialExpressionKey.Expression)
{
return;
}
check(MaterialInstance);
// Bail if we already parsed this expression
if (FunctionStack.Top()->VisitedExpressions.Contains(MaterialExpressionKey))
{
return;
}
FunctionStack.Top()->VisitedExpressions.Add(MaterialExpressionKey);
FunctionStack.Top()->ExpressionStack.Push(MaterialExpressionKey);
const int32 FunctionDepth = FunctionStack.Num();
FMaterialParameterInfo ParameterInfo = FunctionStack.Top()->StackParameterInfo;
UMaterial* BaseMaterial = MaterialInstance->GetBaseMaterial();
bool bCompilingFunctionPreview = BaseMaterial && BaseMaterial->bIsFunctionPreviewMaterial;
// If it's a material parameter it must be visible so add it to the list
UMaterialExpressionParameter* Param = Cast<UMaterialExpressionParameter>( MaterialExpressionKey.Expression );
UMaterialExpressionTextureSampleParameter* TexParam = Cast<UMaterialExpressionTextureSampleParameter>( MaterialExpressionKey.Expression );
UMaterialExpressionTextureCollectionParameter* TextureCollectionParam = Cast<UMaterialExpressionTextureCollectionParameter>( MaterialExpressionKey.Expression );
UMaterialExpressionRuntimeVirtualTextureSampleParameter* RuntimeVirtualTexParam = Cast<UMaterialExpressionRuntimeVirtualTextureSampleParameter>(MaterialExpressionKey.Expression);
UMaterialExpressionSparseVolumeTextureSampleParameter* SparseVolumeTexParam = Cast<UMaterialExpressionSparseVolumeTextureSampleParameter>(MaterialExpressionKey.Expression);
UMaterialExpressionFontSampleParameter* FontParam = Cast<UMaterialExpressionFontSampleParameter>( MaterialExpressionKey.Expression );
if (Param)
{
ParameterInfo.Name = Param->ParameterName;
}
else if (TexParam)
{
ParameterInfo.Name = TexParam->ParameterName;
}
else if (TextureCollectionParam)
{
ParameterInfo.Name = TextureCollectionParam->ParameterName;
}
else if (RuntimeVirtualTexParam)
{
ParameterInfo.Name = RuntimeVirtualTexParam->ParameterName;
}
else if (SparseVolumeTexParam)
{
ParameterInfo.Name = SparseVolumeTexParam->ParameterName;
}
else if (FontParam)
{
ParameterInfo.Name = FontParam->ParameterName;
}
if (Param || TexParam || TextureCollectionParam || FontParam || RuntimeVirtualTexParam || SparseVolumeTexParam)
{
VisibleExpressions.AddUnique(ParameterInfo);
}
// Check if it's a switch expression and branch according to its value
UMaterialExpressionStaticSwitchParameter* StaticSwitchParamExpression = Cast<UMaterialExpressionStaticSwitchParameter>(MaterialExpressionKey.Expression);
UMaterialExpressionStaticSwitch* StaticSwitchExpression = Cast<UMaterialExpressionStaticSwitch>(MaterialExpressionKey.Expression);
UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast<UMaterialExpressionMaterialFunctionCall>(MaterialExpressionKey.Expression);
UMaterialExpressionMaterialAttributeLayers* LayersExpression = Cast<UMaterialExpressionMaterialAttributeLayers>(MaterialExpressionKey.Expression);
UMaterialExpressionFunctionInput* FunctionInputExpression = Cast<UMaterialExpressionFunctionInput>(MaterialExpressionKey.Expression);
if (StaticSwitchParamExpression)
{
bool Value = false;
FGuid ExpressionID;
MaterialInstance->GetStaticSwitchParameterValue(ParameterInfo, Value, ExpressionID);
if (Value)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(StaticSwitchParamExpression->A.Expression, StaticSwitchParamExpression->A.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
else
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(StaticSwitchParamExpression->B.Expression, StaticSwitchParamExpression->B.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
}
else if (StaticSwitchExpression)
{
bool bValue = StaticSwitchExpression->DefaultValue;
FGuid ExpressionID;
if (StaticSwitchExpression->Value.Expression)
{
GetStaticSwitchExpressionValue(MaterialInstance, StaticSwitchExpression->Value.Expression, bValue, ExpressionID, FunctionStack);
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(StaticSwitchExpression->Value.Expression, StaticSwitchExpression->Value.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
if(bValue)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(StaticSwitchExpression->A.Expression, StaticSwitchExpression->A.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
else
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(StaticSwitchExpression->B.Expression, StaticSwitchExpression->B.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
}
else if (FunctionCallExpression)
{
if (FunctionCallExpression->MaterialFunction && FunctionCallExpression->FunctionOutputs.IsValidIndex(MaterialExpressionKey.OutputIndex))
{
for (int32 FunctionCallIndex = 0; FunctionCallIndex < FunctionStack.Num(); FunctionCallIndex++)
{
checkSlow(FunctionStack[FunctionCallIndex]->FunctionCall != FunctionCallExpression);
}
TUniquePtr<FGetVisibleMaterialParametersFunctionState> NewFunctionState = MakeUnique<FGetVisibleMaterialParametersFunctionState>(FunctionCallExpression);
NewFunctionState->StackParameterInfo = ParameterInfo; // Don't change back to Global parameter association when stepping into a function called from a blend/layer function
FunctionStack.Push(NewFunctionState.Get());
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(FunctionCallExpression->FunctionOutputs[MaterialExpressionKey.OutputIndex].ExpressionOutput, 0), MaterialInstance, VisibleExpressions, FunctionStack);
check(FunctionStack.Top()->ExpressionStack.Num() == 0);
FunctionStack.Pop();
}
}
else if (LayersExpression)
{
//ParameterInfo.Name = LayersExpression->ParameterName;
//VisibleExpressions.AddUnique(ParameterInfo);
// TODO: We only need to traverse a solo Layer[0] or the final Blend[N-1] here it will recurse anyway
FMaterialLayersFunctions LayersValue;
if (MaterialInstance->GetMaterialLayers(LayersValue))
{
LayersExpression->OverrideLayerGraph(&LayersValue);
if (LayersExpression->bIsLayerGraphBuilt)
{
TArray<TObjectPtr<UMaterialExpressionMaterialFunctionCall>>& LayerCallers = LayersExpression->LayerCallers;
TArray<TObjectPtr<UMaterialExpressionMaterialFunctionCall>>& BlendCallers = LayersExpression->BlendCallers;
if(Substrate::IsMaterialLayeringSupportEnabled())
{
TSharedPtr< FMaterialLayersFunctionsRuntimeGraphCache > LayerRuntimeTree = LayersValue.RuntimeGraphCache;
LayerCallers = LayerRuntimeTree->LayerCallers;
BlendCallers = LayerRuntimeTree->BlendCallers;
}
for (auto& Layer : LayerCallers)
{
// Possible that Layer->FunctionOutputs will be empty if this is a newly create layer
if (Layer && Layer->MaterialFunction && Layer->FunctionOutputs.IsValidIndex(MaterialExpressionKey.OutputIndex))
{
TUniquePtr<FGetVisibleMaterialParametersFunctionState> NewFunctionState = MakeUnique<FGetVisibleMaterialParametersFunctionState>(Layer);
FunctionStack.Push(NewFunctionState.Get());
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Layer->FunctionOutputs[MaterialExpressionKey.OutputIndex].ExpressionOutput, 0), MaterialInstance, VisibleExpressions, FunctionStack);
check(FunctionStack.Top()->ExpressionStack.Num() == 0);
FunctionStack.Pop();
}
}
for (auto& Blend : BlendCallers)
{
if (Blend && Blend->MaterialFunction && Blend->FunctionOutputs.IsValidIndex(MaterialExpressionKey.OutputIndex))
{
TUniquePtr<FGetVisibleMaterialParametersFunctionState> NewFunctionState = MakeUnique<FGetVisibleMaterialParametersFunctionState>(Blend);
FunctionStack.Push(NewFunctionState.Get());
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Blend->FunctionOutputs[MaterialExpressionKey.OutputIndex].ExpressionOutput, 0), MaterialInstance, VisibleExpressions, FunctionStack);
check(FunctionStack.Top()->ExpressionStack.Num() == 0);
FunctionStack.Pop();
}
}
}
LayersExpression->OverrideLayerGraph(nullptr);
}
}
else if (FunctionInputExpression && FunctionStack.Num() > 1)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(FunctionInputExpression->Preview.Expression, FunctionInputExpression->Preview.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
FGetVisibleMaterialParametersFunctionState* FunctionState = FunctionStack.Pop();
const FFunctionExpressionInput* MatchingInput = FindInputById(FunctionInputExpression, FunctionState->FunctionCall->FunctionInputs);
check(MatchingInput);
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(MatchingInput->Input.Expression, MatchingInput->Input.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
FunctionStack.Push(FunctionState);
}
else
{
// If this is a reroute node of any type, we trace to the first available 'real' input and traverse that single input
if (const UMaterialExpressionRerouteBase* Reroute = Cast<UMaterialExpressionRerouteBase>(MaterialExpressionKey.Expression))
{
FExpressionInput Input = Reroute->TraceInputsToRealInput();
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Input.Expression, Input.OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
else
{
// Retrieve the expression input and then start parsing its children
for (FExpressionInputIterator It{ MaterialExpressionKey.Expression }; It; ++It)
{
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(It->Expression, It->OutputIndex), MaterialInstance, VisibleExpressions, FunctionStack);
}
}
}
FMaterialExpressionKey TopExpressionKey = FunctionStack.Top()->ExpressionStack.Pop();
check(FunctionDepth == FunctionStack.Num());
//ensure that the top of the stack matches what we expect (the same as MaterialExpressionKey)
check(MaterialExpressionKey == TopExpressionKey);
}
TSharedPtr<class IMaterialEditor> FMaterialEditorUtilities::GetIMaterialEditorForObject(const UObject* ObjectToFocusOn)
{
check(ObjectToFocusOn);
// Find the associated Material
UMaterial* Material = Cast<UMaterial>(ObjectToFocusOn->GetOuter());
// May be inspecting a subgraph, in which case, get the material from the composite node
if (!Material)
{
if (UMaterialGraphNode_Composite* Composite = Cast<UMaterialGraphNode_Composite>(ObjectToFocusOn->GetOuter()))
{
Material = Composite->MaterialExpression->Material;
}
}
TSharedPtr<IMaterialEditor> MaterialEditor;
if (Material != NULL)
{
TSharedPtr< IToolkit > FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(Material);
if (FoundAssetEditor.IsValid())
{
MaterialEditor = StaticCastSharedPtr<IMaterialEditor>(FoundAssetEditor);
}
}
return MaterialEditor;
}
void FMaterialEditorUtilities::BringFocusAttentionOnObject(const UObject* ObjectToFocusOn)
{
TSharedPtr<IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(ObjectToFocusOn);
if (MaterialEditor.IsValid())
{
MaterialEditor->FocusWindow();
MaterialEditor->JumpToHyperlink(ObjectToFocusOn);
}
}
void FMaterialEditorUtilities::AddMaterialExpressionCategory(FGraphActionMenuBuilder& ActionMenuBuilder, FText CategoryName, TArray<struct FMaterialExpression>* MaterialExpressions, bool bMaterialFunction)
{
UObject* MaterialOrFunction = bMaterialFunction ? UMaterialFunction::StaticClass()->GetDefaultObject() : UMaterial::StaticClass()->GetDefaultObject();
AddMaterialExpressionCategory(ActionMenuBuilder, CategoryName, MaterialExpressions, MaterialOrFunction);
}
void FMaterialEditorUtilities::AddMaterialExpressionCategory(FGraphActionMenuBuilder& ActionMenuBuilder, FText CategoryName, TArray<struct FMaterialExpression>* MaterialExpressions, const UObject* MaterialOrFunction)
{
// Get type of dragged pin
uint32 FromPinType = 0;
if (ActionMenuBuilder.FromPin)
{
FromPinType = UMaterialGraphSchema::GetMaterialValueType(ActionMenuBuilder.FromPin);
}
bool bMaterialFunction = Cast<UMaterialFunction>(MaterialOrFunction) != nullptr;
for (int32 Index = 0; Index < MaterialExpressions->Num(); ++Index)
{
const FMaterialExpression& MaterialExpression = (*MaterialExpressions)[Index];
UMaterialExpression* DefaultExpression = CastChecked<UMaterialExpression>(MaterialExpression.MaterialClass->GetDefaultObject());
if (DefaultExpression->IsAllowedIn(MaterialOrFunction) && MaterialExpression.MaterialClass != UMaterialExpressionComposite::StaticClass())
{
if (!ActionMenuBuilder.FromPin || HasCompatibleConnection(MaterialExpression.MaterialClass, FromPinType, ActionMenuBuilder.FromPin->Direction, bMaterialFunction))
{
FText CreationName = FText::FromString(MaterialExpression.Name);
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("Name"), CreationName);
FText ToolTip = FText::Format(LOCTEXT("NewMaterialExpressionTooltip", "Adds a {Name} node here"), Arguments);
if (!MaterialExpression.CreationDescription.IsEmpty())
{
ToolTip = MaterialExpression.CreationDescription;
}
if (!MaterialExpression.CreationName.IsEmpty())
{
CreationName = MaterialExpression.CreationName;
}
UMaterialExpression* MaterialExpressionDefaultObject = CastChecked<UMaterialExpression>(MaterialExpression.MaterialClass->GetDefaultObject());
TSharedPtr<FMaterialGraphSchemaAction_NewNode> NewNodeAction(new FMaterialGraphSchemaAction_NewNode(
CategoryName,
CreationName,
ToolTip,
0,
MaterialExpressionDefaultObject->GetKeywords()
));
NewNodeAction->MaterialExpressionClass = MaterialExpression.MaterialClass;
ActionMenuBuilder.AddAction(NewNodeAction);
// Register Any Additional Menu Actions for this Expression
MaterialExpressionDefaultObject->RegisterAdditionalMenuActions(ActionMenuBuilder, CategoryName);
}
}
}
}
bool FMaterialEditorUtilities::HasCompatibleConnection(UClass* ExpressionClass, uint32 TestType, EEdGraphPinDirection TestDirection, bool bMaterialFunction)
{
if (TestType != 0)
{
UMaterialExpression* DefaultExpression = CastChecked<UMaterialExpression>(ExpressionClass->GetDefaultObject());
if (TestDirection == EGPD_Output)
{
for (FExpressionInputIterator It{ DefaultExpression }; It; ++It)
{
EMaterialValueType InputType = DefaultExpression->GetInputValueType(It.Index);
if (CanConnectMaterialValueTypes(InputType, TestType))
{
return true;
}
}
}
else
{
int32 NumOutputs = DefaultExpression->GetOutputs().Num();
for (int32 Index = 0; Index < NumOutputs; ++Index)
{
EMaterialValueType OutputType = DefaultExpression->GetOutputValueType(Index);
if (CanConnectMaterialValueTypes(TestType, OutputType))
{
return true;
}
}
}
if (bMaterialFunction)
{
// Specific test as Default object won't have texture input
if (ExpressionClass == UMaterialExpressionTextureSample::StaticClass() && TestType & MCT_Texture && TestDirection == EGPD_Output)
{
return true;
}
// Always allow creation of new inputs as they can take any type
else if (ExpressionClass == UMaterialExpressionFunctionInput::StaticClass())
{
return true;
}
// Allow creation of outputs for floats and material attributes
else if (ExpressionClass == UMaterialExpressionFunctionOutput::StaticClass() && TestType & (MCT_Float|MCT_MaterialAttributes))
{
return true;
}
}
}
return false;
}
void FMaterialEditorUtilities::BuildTextureStreamingData(UMaterialInterface* UpdatedMaterial)
{
const EMaterialQualityLevel::Type QualityLevel = EMaterialQualityLevel::High;
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
if (UpdatedMaterial)
{
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FScopedSlowTask SlowTask(2.f, (LOCTEXT("MaterialEditorUtilities_UpdatingTextureStreamingData", "Updating Texture Streaming Data")));
SlowTask.MakeDialog(true);
// Clear the build data.
const TArray<FMaterialTextureInfo> EmptyTextureStreamingData;
UpdatedMaterial->SetTextureStreamingData(EmptyTextureStreamingData);
// Skip compilation for cooked materials
UMaterial* RootMaterial = UpdatedMaterial->GetMaterial();
if (!RootMaterial || !RootMaterial->GetPackage()->HasAnyPackageFlags(PKG_Cooked))
{
TSet<UMaterialInterface*> Materials;
Materials.Add(UpdatedMaterial);
if (CompileDebugViewModeShaders(DVSM_OutputMaterialTextureScales, QualityLevel, FeatureLevel, Materials, &SlowTask))
{
FMaterialUtilities::FExportErrorManager ExportErrors(FeatureLevel);
for (UMaterialInterface* MaterialInterface : Materials)
{
FMaterialUtilities::ExportMaterialUVDensities(MaterialInterface, QualityLevel, FeatureLevel, ExportErrors);
}
ExportErrors.OutputToLog();
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
}
}
}
void FMaterialEditorUtilities::OnOpenMaterial(const FAssetData InMaterial)
{
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>(InMaterial.GetAsset());
OpenSelectedParentEditor(MaterialInterface);
}
void FMaterialEditorUtilities::OnOpenFunction(const FAssetData InFunction)
{
UMaterialFunctionInterface* MaterialFunctionInterface = Cast<UMaterialFunctionInterface>(InFunction.GetAsset());
OpenSelectedParentEditor(MaterialFunctionInterface);
}
void FMaterialEditorUtilities::OnShowMaterialInContentBrowser(const FAssetData InMaterial)
{
TArray<UObject*> SyncedObject;
SyncedObject.Add(InMaterial.GetAsset());
GEditor->SyncBrowserToObjects(SyncedObject);
}
void FMaterialEditorUtilities::OnShowFunctionInContentBrowser(const FAssetData InFunction)
{
TArray<UObject*> SyncedObject;
SyncedObject.Add(InFunction.GetAsset());
GEditor->SyncBrowserToObjects(SyncedObject);
}
void FMaterialEditorUtilities::OpenSelectedParentEditor(UMaterialInterface* InMaterialInterface)
{
// See if its a material or material instance constant.
if (ensure(InMaterialInterface))
{
if (InMaterialInterface->IsA(UMaterial::StaticClass()))
{
// Show material editor
UMaterial* Material = Cast<UMaterial>(InMaterialInterface);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Material);
}
else if (InMaterialInterface->IsA(UMaterialInstance::StaticClass()))
{
// Show material instance editor
UMaterialInstance* MaterialInstance = Cast<UMaterialInstance>(InMaterialInterface);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(MaterialInstance);
}
}
}
void FMaterialEditorUtilities::OpenSelectedParentEditor(UMaterialFunctionInterface* InMaterialFunction)
{
// See if its a material or material instance constant.
if (ensure(InMaterialFunction) )
{
if (InMaterialFunction->IsA(UMaterialFunctionInstance::StaticClass()))
{
// Show function instance editor
UMaterialFunctionInstance* FunctionInstance = Cast<UMaterialFunctionInstance>(InMaterialFunction);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(FunctionInstance);
}
else
{
// Show function editor
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(InMaterialFunction);
}
}
}
void FMaterialEditorUtilities::RefreshPostProcessPreviewMaterials(UMaterialInterface* ExcludeMaterialInterface, bool bRedrawOnly)
{
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
TArray<UObject*> EditedAssets = AssetEditorSubsystem->GetAllEditedAssets();
for (UObject* EditedAsset : EditedAssets)
{
UPreviewMaterial* EditedPreviewMaterial = Cast<UPreviewMaterial>(EditedAsset);
if (EditedPreviewMaterial && EditedPreviewMaterial != ExcludeMaterialInterface)
{
UMaterial* EditedMaterial = EditedPreviewMaterial->GetMaterial();
if (EditedMaterial->IsPostProcessMaterial())
{
TArray<IAssetEditorInstance*> Editors = AssetEditorSubsystem->FindEditorsForAsset(EditedAsset);
for (IAssetEditorInstance* Editor : Editors)
{
if (Editor->GetEditorName() == FName("MaterialEditor"))
{
FMaterialEditor* MaterialEditor = (FMaterialEditor*)Editor;
if (bRedrawOnly)
{
MaterialEditor->RefreshPreviewViewport();
}
else
{
// Calling "SetPreviewMaterial" will refresh the other editor
MaterialEditor->SetPreviewMaterial(EditedPreviewMaterial);
}
}
}
}
}
UMaterialInstanceConstant* EditedMaterialInstance = Cast<UMaterialInstanceConstant>(EditedAsset);
if (EditedMaterialInstance && EditedMaterialInstance != ExcludeMaterialInterface)
{
UMaterial* BaseMaterial = EditedMaterialInstance->GetBaseMaterial();
if (BaseMaterial && BaseMaterial->IsPostProcessMaterial())
{
TArray<IAssetEditorInstance*> Editors = AssetEditorSubsystem->FindEditorsForAsset(EditedAsset);
for (IAssetEditorInstance* Editor : Editors)
{
if (Editor->GetEditorName() == FName("MaterialInstanceEditor"))
{
FMaterialInstanceEditor* MaterialEditor = (FMaterialInstanceEditor*)Editor;
if (bRedrawOnly)
{
MaterialEditor->RefreshPreviewViewport();
}
else
{
// Calling "SetPreviewMaterial" will refresh the other editor
MaterialEditor->SetPreviewMaterial(EditedMaterialInstance);
}
}
}
}
}
}
}
#undef LOCTEXT_NAMESPACE