You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Preview windows and the r.PostProcessing.UserSceneTextureDebug mode were updated to handle material instances. If both a base material and an instance of it are being edited, the instance will be preferred in other preview windows over the base material, rather than including both and having their effects potentially overlap. Sorting of previewed materials was improved by ordering them in the reverse order they are encountered, so dependencies earlier in a chain are added first. Fixed cases where preview windows weren't refreshed on certain types of changes or debug mode toggling. To handle generic materials that can work at multiple resolutions, a "ScaleRelativeToInput" field was added, which generates the output resolution relative to a given User Scene Texture input. Positive divisors downsample, while negative divisors upsample (so 2 would divide resolution by 2, while -2 would multiply by 2). To handle the use of a generic material where you want the input or output to be SceneColor (say a first pass that reads SceneColor, or a last pass that writes SceneColor), you now have the option to specify "SceneColor" as a User Scene Texture input or output name. This special name doesn't create a transient User Scene Texture, but just hooks up SceneColor itself. The final feature added is a flag which suppresses PreExposure adjustments (multiply by View.OneOverPreExposure on input, multiply by View.PreExposure on output), useful if creating a material that doesn't care about the absolute value of linear colors (such as a blur where the inputs and outputs have the same scale), or where the output is not a linear color (such as a mask, modulation, or some sort of non-color data like positions, normals, offsets, IDs, etc). Especially simplifies writing custom HLSL by avoiding the need for confusing View.OneOverPreExposure.xxx boilerplate, in addition to providing a minor perf gain. #jira UE-215194 #rb eric.renaudhoude, Jason.Nadro, Ruslan.Idrisov [CL 34375539 by jason hoerner in ue5-main branch]
959 lines
37 KiB
C++
959 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/MaterialExpressionExecBegin.h"
|
|
#include "Materials/MaterialExpressionExecEnd.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 FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource)
|
|
{
|
|
TSharedPtr<class IMaterialEditor> MaterialEditor = GetIMaterialEditorForObject(Graph);
|
|
if (MaterialEditor.IsValid())
|
|
{
|
|
return MaterialEditor->CreateNewMaterialExpression(NewExpressionClass, 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)
|
|
{
|
|
// TODO: Not sure if this is necessary/usable anymore
|
|
// Get all menu extenders for this context menu from the material editor module
|
|
/*IMaterialEditorModule& MaterialEditor = FModuleManager::GetModuleChecked<IMaterialEditorModule>( TEXT("MaterialEditor") );
|
|
TArray<IMaterialEditorModule::FMaterialMenuExtender> MenuExtenderDelegates = MaterialEditor.GetAllMaterialCanvasMenuExtenders();
|
|
|
|
TArray<TSharedPtr<FExtender>> Extenders;
|
|
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
|
|
{
|
|
if (MenuExtenderDelegates[i].IsBound())
|
|
{
|
|
Extenders.Add(MenuExtenderDelegates[i].Execute(MaterialEditorPtr.Pin()->GetToolkitCommands()));
|
|
}
|
|
}
|
|
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);*/
|
|
|
|
bool bUseUnsortedMenus = false;
|
|
MaterialExpressionClasses* ExpressionClasses = MaterialExpressionClasses::Get();
|
|
|
|
if (bUseUnsortedMenus)
|
|
{
|
|
AddMaterialExpressionCategory(ActionMenuBuilder, FText::GetEmpty(), &ExpressionClasses->AllExpressionClasses, bMaterialFunction);
|
|
}
|
|
else
|
|
{
|
|
// Add Favourite expressions as a category
|
|
const FText FavouritesCategory = LOCTEXT("FavoritesMenu", "Favorites");
|
|
AddMaterialExpressionCategory(ActionMenuBuilder, FavouritesCategory, &ExpressionClasses->FavoriteExpressionClasses, bMaterialFunction);
|
|
|
|
// 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, bMaterialFunction);
|
|
}
|
|
|
|
if (ExpressionClasses->UnassignedExpressionClasses.Num() > 0)
|
|
{
|
|
AddMaterialExpressionCategory(ActionMenuBuilder, FText::GetEmpty(), &ExpressionClasses->UnassignedExpressionClasses, bMaterialFunction);
|
|
}
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
if (Material->IsUsingControlFlow())
|
|
{
|
|
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Material->GetExpressionExecBegin(), INDEX_NONE), MaterialInstance, VisibleExpressions, FunctionStack);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 );
|
|
if (MaterialExpression->IsA(UMaterialExpressionExecBegin::StaticClass()))
|
|
{
|
|
Material->GetExpressionCollection().ExpressionExecBegin = static_cast<UMaterialExpressionExecBegin*>(MaterialExpression);
|
|
}
|
|
else if (MaterialExpression->IsA(UMaterialExpressionExecEnd::StaticClass()))
|
|
{
|
|
Material->GetExpressionCollection().ExpressionExecEnd = static_cast<UMaterialExpressionExecEnd*>(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)
|
|
{
|
|
for (auto& Layer : LayersExpression->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 : LayersExpression->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);
|
|
}
|
|
|
|
TArray<FExpressionExecOutputEntry> ExpressionExecOutputs;
|
|
MaterialExpressionKey.Expression->GetExecOutputs(ExpressionExecOutputs);
|
|
for (const FExpressionExecOutputEntry& Entry : ExpressionExecOutputs)
|
|
{
|
|
GetVisibleMaterialParametersFromExpression(FMaterialExpressionKey(Entry.Output->GetExpression(), INDEX_NONE), 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)
|
|
{
|
|
// Get type of dragged pin
|
|
uint32 FromPinType = 0;
|
|
if (ActionMenuBuilder.FromPin)
|
|
{
|
|
FromPinType = UMaterialGraphSchema::GetMaterialValueType(ActionMenuBuilder.FromPin);
|
|
}
|
|
|
|
for (int32 Index = 0; Index < MaterialExpressions->Num(); ++Index)
|
|
{
|
|
const FMaterialExpression& MaterialExpression = (*MaterialExpressions)[Index];
|
|
if (IsAllowedExpressionType(MaterialExpression.MaterialClass, bMaterialFunction) && 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;
|
|
}
|
|
|
|
TSharedPtr<FMaterialGraphSchemaAction_NewNode> NewNodeAction(new FMaterialGraphSchemaAction_NewNode(
|
|
CategoryName,
|
|
CreationName,
|
|
ToolTip, 0, CastChecked<UMaterialExpression>(MaterialExpression.MaterialClass->GetDefaultObject())->GetKeywords()));
|
|
NewNodeAction->MaterialExpressionClass = MaterialExpression.MaterialClass;
|
|
ActionMenuBuilder.AddAction(NewNodeAction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
uint32 InputType = DefaultExpression->GetInputType(It.Index);
|
|
if (CanConnectMaterialValueTypes(InputType, TestType))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 NumOutputs = DefaultExpression->GetOutputs().Num();
|
|
for (int32 Index = 0; Index < NumOutputs; ++Index)
|
|
{
|
|
uint32 OutputType = DefaultExpression->GetOutputType(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
|