Files
UnrealEngine/Engine/Source/Editor/MaterialEditor/Private/DiffTool/MaterialDiffPanel.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

587 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DiffTool/MaterialDiffPanel.h"
#include "EdGraphUtilities.h"
#include "Framework/Commands/GenericCommands.h"
#include "GraphDiffControl.h"
#include "HAL/PlatformApplicationMisc.h"
#include "IDetailsView.h"
#include "MaterialDomain.h"
#include "MaterialEditorActions.h"
#include "MaterialShared.h"
#include "MaterialEditor/PreviewMaterial.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionBreakMaterialAttributes.h"
#include "Materials/MaterialExpressionFunctionOutput.h"
#include "Materials/MaterialExpressionStaticBool.h"
#include "Materials/MaterialExpressionStaticBoolParameter.h"
#include "SMaterialEditorViewport.h"
#include "SMyBlueprint.h"
#include "Widgets/Layout/SBox.h"
#define LOCTEXT_NAMESPACE "MaterialDiffPanel"
DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditorDiff, Log, All);
void FMaterialDiffPanel::GeneratePanel(UEdGraph* NewGraph, UEdGraph* OldGraph)
{
const TSharedPtr<TArray<FDiffSingleResult>> Diff = MakeShared<TArray<FDiffSingleResult>>();
FGraphDiffControl::DiffGraphs(OldGraph, NewGraph, *Diff);
GeneratePanel(NewGraph, Diff, TAttribute<int32>{});
}
void FMaterialDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtr<TArray<FDiffSingleResult>> DiffResults, TAttribute<int32> FocusedDiffResult)
{
if (GraphEditor.IsValid() && GraphEditor.Pin()->GetCurrentGraph() == Graph)
{
return;
}
// clang-format off
TSharedPtr<SWidget> Widget = SNew(SBorder)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("MaterialGraphDiffPanelNoGraphTip", "Graph does not exist in this revision"))
];
// clang-format on
if (Graph)
{
SGraphEditor::FGraphEditorEvents InEvents;
{
const auto SelectionChangedHandler = [this](const FGraphPanelSelectionSet& SelectionSet) {
if (MaterialNodeDetailsView)
{
if (SelectionSet.Array().Num() == 1)
{
if (UMaterialGraphNode_Base* MaterialNode = Cast<UMaterialGraphNode_Base>(SelectionSet.Array()[0]))
{
MaterialNodeDetailsView->SetObject(MaterialNode->GetMaterialNodeOwner());
MaterialNodeDetailsView->HighlightProperty(PropertyToFocus);
MaterialNodeDetailsView->SetVisibility(EVisibility::Visible);
MaterialNodeDetailsView->SetIsPropertyEditingEnabledDelegate(
FIsPropertyEditingEnabled::CreateLambda([]() { return false; }));
}
else if (UMaterialGraphNode_Comment* CommentNode = Cast<UMaterialGraphNode_Comment>(SelectionSet.Array()[0]))
{
MaterialNodeDetailsView->SetObject(CommentNode);
MaterialNodeDetailsView->SetVisibility(EVisibility::Visible);
}
else
{
MaterialNodeDetailsView->SetVisibility(EVisibility::Hidden);
}
}
else
{
MaterialNodeDetailsView->SetVisibility(EVisibility::Hidden);
}
}
};
const auto ContextMenuHandler = [this](UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) {
if (MenuBuilder)
{
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Copy);
MenuBuilder->AddMenuEntry(FMaterialEditorCommands::Get().SelectDownstreamNodes);
MenuBuilder->AddMenuEntry(FMaterialEditorCommands::Get().SelectUpstreamNodes);
if (const UMaterialGraphNode* MaterialGraphNode = Cast<UMaterialGraphNode>(InGraphNode))
{
if (UMaterialExpression* MaterialExpression = MaterialGraphNode->MaterialExpression)
{
// Don't show preview option for bools
if (!MaterialExpression->IsA(UMaterialExpressionStaticBool::StaticClass()) && !MaterialExpression->IsA(UMaterialExpressionStaticBoolParameter::StaticClass()))
{
// Add a preview node option if only one node is selected
if (MaterialGraphNode->MaterialExpression == PreviewExpression)
{
// If we are already previewing the selected node, the menu option should tell the user that this will stop previewing
MenuBuilder->AddMenuEntry(FMaterialEditorCommands::Get().StopPreviewNode);
}
else
{
// The menu option should tell the user this node will be previewed.
MenuBuilder->AddMenuEntry(FMaterialEditorCommands::Get().StartPreviewNode);
}
}
}
}
return FActionMenuContent(MenuBuilder->MakeWidget());
}
return FActionMenuContent();
};
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateLambda(SelectionChangedHandler);
InEvents.OnCreateNodeOrPinMenu = SGraphEditor::FOnCreateNodeOrPinMenu::CreateLambda(ContextMenuHandler);
}
if (!GraphEditorCommands.IsValid())
{
GraphEditorCommands = MakeShared<FUICommandList>();
GraphEditorCommands->MapAction(FGenericCommands::Get().Copy,
FExecuteAction::CreateRaw(this, &FMaterialDiffPanel::CopySelectedNodes),
FCanExecuteAction::CreateRaw(this, &FMaterialDiffPanel::CanCopyNodes));
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().SelectDownstreamNodes,
FExecuteAction::CreateRaw(this, &FMaterialDiffPanel::SelectDownstreamNodes));
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().SelectUpstreamNodes,
FExecuteAction::CreateRaw(this, &FMaterialDiffPanel::SelectUpstreamNodes));
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().StartPreviewNode,
FExecuteAction::CreateRaw(this, &FMaterialDiffPanel::OnPreviewNode));
GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().StopPreviewNode,
FExecuteAction::CreateRaw(this, &FMaterialDiffPanel::OnPreviewNode));
}
TSharedRef<SGraphEditor> Editor = SNew(SGraphEditor)
.AdditionalCommands(GraphEditorCommands)
.GraphToEdit(Graph)
.GraphToDiff(nullptr)
.DiffResults(DiffResults)
.FocusedDiffResult(FocusedDiffResult)
.IsEditable(false)
.GraphEvents(InEvents);
GraphEditor = Editor;
Widget = Editor;
}
GraphEditorBox->SetContent(Widget.ToSharedRef());
}
FGraphPanelSelectionSet FMaterialDiffPanel::GetSelectedNodes() const
{
FGraphPanelSelectionSet CurrentSelection{};
TSharedPtr<SGraphEditor> FocusedGraphEd = GraphEditor.Pin();
if (FocusedGraphEd.IsValid())
{
CurrentSelection = FocusedGraphEd->GetSelectedNodes();
}
return CurrentSelection;
}
void FMaterialDiffPanel::CopySelectedNodes()
{
// Export the selected nodes and place the text on the clipboard
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
FString ExportedText;
FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
bool FMaterialDiffPanel::CanCopyNodes() const
{
// If any of the nodes can be duplicated then we should allow copying
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode())
{
return true;
}
}
return false;
}
void FMaterialDiffPanel::SelectDownstreamNodes() const
{
TArray<UMaterialGraphNode*> NodesToCheck;
TArray<UMaterialGraphNode*> CheckedNodes;
TArray<UMaterialGraphNode*> NodesToSelect;
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
if (GraphNode)
{
NodesToCheck.Add(GraphNode);
}
}
int32 WatchDogWhile = 0;
static constexpr int32 LimitIteration = 100;
while (WatchDogWhile++ < LimitIteration && NodesToCheck.Num() > 0)
{
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
for (UEdGraphPin* Pin : CurrentNode->Pins)
{
if (Pin->Direction == EGPD_Output)
{
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Pin->LinkedTo[LinkIndex]->GetOwningNode());
if (LinkedNode)
{
int32 FoundIndex = -1;
CheckedNodes.Find(LinkedNode, FoundIndex);
if (FoundIndex < 0)
{
NodesToSelect.Add(LinkedNode);
NodesToCheck.Add(LinkedNode);
}
}
}
}
}
// This graph node has now been examined
CheckedNodes.Add(CurrentNode);
NodesToCheck.Remove(CurrentNode);
}
if (WatchDogWhile >= LimitIteration)
{
UE_LOG(LogMaterialEditorDiff, Warning, TEXT("FMaterialDiffPanel::SelectDownstreamNodes - Infinite loop encounter"));
return;
}
if (TSharedPtr<SGraphEditor> FocusedGraphEd = GraphEditor.Pin())
{
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
{
FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true);
}
}
}
void FMaterialDiffPanel::SelectUpstreamNodes() const
{
TArray<UMaterialGraphNode*> NodesToCheck;
TArray<UMaterialGraphNode*> CheckedNodes;
TArray<UMaterialGraphNode*> NodesToSelect;
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
if (GraphNode)
{
NodesToCheck.Add(GraphNode);
}
}
int32 WatchDogWhile = 0;
static constexpr int32 LimitIteration = 100;
while (WatchDogWhile++ < LimitIteration && NodesToCheck.Num() > 0)
{
UMaterialGraphNode* CurrentNode = NodesToCheck.Last();
for (UEdGraphPin* Pin : CurrentNode->Pins)
{
if (Pin->Direction == EGPD_Input)
{
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
UMaterialGraphNode* LinkedNode = Cast<UMaterialGraphNode>(Pin->LinkedTo[LinkIndex]->GetOwningNode());
if (LinkedNode)
{
int32 FoundIndex = -1;
CheckedNodes.Find(LinkedNode, FoundIndex);
if (FoundIndex < 0)
{
NodesToSelect.Add(LinkedNode);
NodesToCheck.Add(LinkedNode);
}
}
}
}
}
// This graph node has now been examined
CheckedNodes.Add(CurrentNode);
NodesToCheck.Remove(CurrentNode);
}
if (WatchDogWhile >= LimitIteration)
{
UE_LOG(LogMaterialEditorDiff, Warning, TEXT("FMaterialDiffPanel::SelectUpstreamNodes - Infinite loop encounter"));
return;
}
if (TSharedPtr<SGraphEditor> FocusedGraphEd = GraphEditor.Pin())
{
for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index)
{
FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true);
}
}
}
void FMaterialDiffPanel::OnPreviewNode()
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
if (SelectedNodes.Num() == 1)
{
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(*NodeIt);
if (GraphNode)
{
if (TSharedPtr<SGraphEditor> FocusedGraphEd = GraphEditor.Pin())
{
FocusedGraphEd->NotifyGraphChanged();
}
SetPreviewExpression(GraphNode->MaterialExpression);
}
}
}
}
void FMaterialDiffPanel::FocusDiff(UEdGraphPin& Pin)
{
if (GraphEditor.IsValid())
{
GraphEditor.Pin()->JumpToPin(&Pin);
}
}
void FMaterialDiffPanel::FocusDiff(UEdGraphNode& Node)
{
if (GraphEditor.IsValid())
{
GraphEditor.Pin()->JumpToNode(&Node, false);
}
}
TSharedRef<SWidget> FMaterialDiffPanel::GetMaterialNodeDetailsViewWidget() const
{
// If this panel is displaying a null object, return an empty boarder instead of a IDetailsView
return MaterialNodeDetailsView ? MaterialNodeDetailsView.ToSharedRef() : TSharedRef<SWidget>(SNew(SBorder));
}
void FMaterialDiffPanel::SetViewportToDisplay()
{
if (!MaterialGraph)
{
return;
}
if (MaterialGraph->Material && MaterialGraph->Material->IsUIMaterial())
{
SAssignNew(Preview2DViewport, SMaterialEditorUIPreviewViewport, MaterialGraph->Material);
}
SAssignNew(Preview3DViewport, SMaterialEditor3DPreviewViewport)
.PreviewMaterial(MaterialGraph->Material);
// See FMaterialEditor::InitMaterialEditor
if (MaterialGraph->MaterialFunction && MaterialGraph->Material)
{
bool bSetPreviewExpression = false;
UMaterialExpressionFunctionOutput* FirstOutput = nullptr;
for (int32 ExpressionIndex = MaterialGraph->Material->GetExpressions().Num() - 1; ExpressionIndex >= 0; ExpressionIndex--)
{
UMaterialExpression* Expression = MaterialGraph->Material->GetExpressions()[ExpressionIndex];
// Setup the expression to be used with the preview material instead of the function
Expression->Function = nullptr;
Expression->Material = MaterialGraph->Material;
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(Expression);
if (FunctionOutput)
{
FirstOutput = FunctionOutput;
if (FunctionOutput->bLastPreviewed)
{
bSetPreviewExpression = true;
// Preview the last output previewed
SetPreviewExpression(FunctionOutput);
}
}
}
if (!bSetPreviewExpression && FirstOutput)
{
SetPreviewExpression(FirstOutput);
}
}
}
TSharedRef<SWidget> FMaterialDiffPanel::GetViewportToDisplay() const
{
if (Preview2DViewport)
{
return Preview2DViewport.ToSharedRef();
}
return Preview3DViewport ? Preview3DViewport.ToSharedRef() : TSharedRef<SWidget>(SNew(SBorder));
}
void FMaterialDiffPanel::SetViewportVisibility(bool bShowViewport)
{
if (!MaterialGraph)
{
return;
}
if (Preview2DViewport)
{
Preview2DViewport->SetVisibility(bShowViewport ? EVisibility::Visible : EVisibility::Collapsed);
}
if (Preview3DViewport)
{
Preview3DViewport->SetVisibility(bShowViewport ? EVisibility::Visible : EVisibility::Collapsed);
}
}
void FMaterialDiffPanel::SetPreviewExpression(UMaterialExpression* NewPreviewExpression)
{
if (!MaterialGraph)
{
return;
}
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(NewPreviewExpression);
const UMaterial* Material = MaterialGraph->Material;
if (!NewPreviewExpression || PreviewExpression == NewPreviewExpression)
{
if (FunctionOutput)
{
FunctionOutput->bLastPreviewed = false;
}
// If we are already previewing the selected expression toggle previewing off
PreviewExpression = nullptr;
if(ExpressionPreviewMaterial)
{
ExpressionPreviewMaterial->GetExpressionCollection().Empty();
}
SetPreviewMaterial(MaterialGraph->Material);
// Recompile the preview material to get changes that might have been made during previewing
UpdatePreviewMaterial();
}
else
{
if (ExpressionPreviewMaterial == nullptr)
{
// Create the expression preview material if it hasnt already been created
ExpressionPreviewMaterial = NewObject<UPreviewMaterial>(GetTransientPackage(), NAME_None, RF_Public);
ExpressionPreviewMaterial->bIsPreviewMaterial = true;
ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator();
// MODAVI_TODO
//ExpressionPreviewMaterial->bEnableExecWire = Material->IsUsingControlFlow();
if (Material->IsUIMaterial())
{
ExpressionPreviewMaterial->MaterialDomain = MD_UI;
}
else if (Material->IsPostProcessMaterial())
{
ExpressionPreviewMaterial->MaterialDomain = MD_PostProcess;
}
}
if (FunctionOutput)
{
FunctionOutput->bLastPreviewed = true;
}
else
{
// Hooking up the output of the break expression doesn't make much sense, preview the expression feeding it instead.
UMaterialExpressionBreakMaterialAttributes* BreakExpr = Cast<UMaterialExpressionBreakMaterialAttributes>(NewPreviewExpression);
if (BreakExpr && BreakExpr->GetInput(0) && BreakExpr->GetInput(0)->Expression)
{
NewPreviewExpression = BreakExpr->GetInput(0)->Expression;
}
}
// The expression preview material's expressions array must stay up to date before recompiling
// So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated
ExpressionPreviewMaterial->AssignExpressionCollection(Material->GetExpressionCollection());
// The preview window should now show the expression preview material
SetPreviewMaterial(ExpressionPreviewMaterial);
// Set the preview expression
PreviewExpression = NewPreviewExpression;
// Recompile the preview material
UpdatePreviewMaterial();
}
}
void FMaterialDiffPanel::SetPreviewMaterial(UMaterialInterface* InMaterialInterface) const
{
if (Preview2DViewport)
{
Preview2DViewport->SetPreviewMaterial(InMaterialInterface);
}
if (Preview3DViewport)
{
Preview3DViewport->SetPreviewMaterial(InMaterialInterface);
}
}
void FMaterialDiffPanel::UpdatePreviewMaterial() const
{
if (!MaterialGraph)
{
return;
}
if (PreviewExpression && ExpressionPreviewMaterial)
{
ExpressionPreviewMaterial->UpdateCachedExpressionData();
PreviewExpression->ConnectToPreviewMaterial(ExpressionPreviewMaterial, 0);
}
const UMaterial* Material = MaterialGraph->Material;
if (PreviewExpression)
{
check(ExpressionPreviewMaterial);
// The preview material's expressions array must stay up to date before recompiling
// So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated
ExpressionPreviewMaterial->AssignExpressionCollection(Material->GetExpressionCollection());
ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator();
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread);
UpdateContext.AddMaterial(ExpressionPreviewMaterial);
// If we are previewing an expression, update the expression preview material
ExpressionPreviewMaterial->PreEditChange(nullptr);
ExpressionPreviewMaterial->PostEditChange();
}
// Reregister all components that use the preview material, since UMaterial::PEC does not reregister components using a bIsPreviewMaterial=true material
if (Preview3DViewport)
{
Preview3DViewport->RefreshViewport();
}
}
void FMaterialDiffPanel::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(ExpressionPreviewMaterial);
Collector.AddReferencedObject(PreviewExpression);
Collector.AddReferencedObject(MaterialGraph);
}
FString FMaterialDiffPanel::GetReferencerName() const
{
return TEXT("FMaterialDiffPanel");
}
#undef LOCTEXT_NAMESPACE