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

1346 lines
45 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SMaterialAnalyzer.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ContentBrowserDataLegacyBridge.h"
#include "PropertyCustomizationHelpers.h"
#include "CollectionManagerModule.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Views/TableViewMetadata.h"
#include "Framework/Application/SlateApplication.h"
#include "ICollectionContainer.h"
#include "ICollectionManager.h"
#include "ICollectionSource.h"
#include "AssetManagerEditorModule.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstance.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "SAnalyzedMaterialNodeWidgetItem.h"
#include "Widgets/Images/SThrobber.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableText.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Views/STreeView.h"
#include "Filters/SFilterSearchBox.h"
#include "Styling/StyleColors.h"
#include "DesktopPlatformModule.h"
#include "ProfilingDebugging/DiagnosticTable.h"
#define LOCTEXT_NAMESPACE "MaterialAnalyzer"
static TMap<FName, FName> BasePropertyOverrideNames;
SMaterialAnalyzer::SMaterialAnalyzer()
: BuildBaseMaterialTreeTask(nullptr)
, AnalyzeTreeTask(nullptr)
, AnalyzeForIdenticalPermutationsTask(nullptr)
, bWaitingForAssetRegistryLoad(false)
{
BasePropertyOverrideNames.Empty();
BasePropertyOverrideNames.Add(TEXT("bOverride_OpacityMaskClipValue"), TEXT("OpacityMaskClipValueOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_BlendMode"), TEXT("BlendModeOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_ShadingModel"), TEXT("ShadingModelOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_DitheredLODTransition"), TEXT("DitheredLODTransitionOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_CastDynamicShadowAsMasked"), TEXT("CastDynamicShadowAsMaskedOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_TwoSided"), TEXT("TwoSidedOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_bIsThinSurface"), TEXT("bIsThinSurfaceOverride"));
BasePropertyOverrideNames.Add(TEXT("bOverride_OutputTranslucentVelocity"), TEXT("bOutputTranslucentVelocity"));
BasePropertyOverrideNames.Add(TEXT("bOverride_bHasPixelAnimation"), TEXT("bHasPixelAnimation"));
BasePropertyOverrideNames.Add(TEXT("bOverride_bEnableTessellation"), TEXT("bEnableTessellation"));
BasePropertyOverrideNames.Add(TEXT("bOverride_DisplacementScaling"), TEXT("DisplacementScaling"));
BasePropertyOverrideNames.Add(TEXT("bOverride_bEnableDisplacementFade"), TEXT("bEnableDisplacementFade"));
BasePropertyOverrideNames.Add(TEXT("bOverride_DisplacementFadeRange"), TEXT("DisplacementFadeRange"));
BasePropertyOverrideNames.Add(TEXT("bOverride_MaxWorldPositionOffsetDisplacement"), TEXT("MaxWorldPositionOffsetDisplacement"));
BasePropertyOverrideNames.Add(TEXT("bOverride_CompatibleWithLumenCardSharing"), TEXT("bCompatibleWithLumenCardSharing"));
}
SMaterialAnalyzer::~SMaterialAnalyzer()
{
}
const FAssetData* FindParentAssetData(const FAssetData* InAssetData, const TArray<FAssetData>& ArrayToSearch)
{
check(InAssetData != nullptr);
static const FName NAME_Parent = TEXT("Parent");
FString ParentPathString = InAssetData->GetTagValueRef<FString>(NAME_Parent);
int32 FirstCut = INDEX_NONE;
ParentPathString.FindChar(L'\'', FirstCut);
FSoftObjectPath ParentPath;
if(FirstCut != INDEX_NONE)
{
ParentPath = ParentPathString.Mid(FirstCut + 1, ParentPathString.Len() - FirstCut - 2);
}
else
{
ParentPath = ParentPathString;
}
if(ParentPath.IsValid())
{
return ArrayToSearch.FindByPredicate(
[&](FAssetData& Entry)
{
return Entry.GetSoftObjectPath() == ParentPath;
}
);
}
return nullptr;
}
void SMaterialAnalyzer::Construct(const FArguments& InArgs, const TSharedRef<SDockTab>& ConstructUnderMajorTab, const TSharedPtr<SWindow>& ConstructUnderWindow)
{
TArray<const UClass*> AllowedClasses;
AllowedClasses.Add(UMaterialInterface::StaticClass());
TSharedRef<SWidget> AssetPickerWidget = SNew(SObjectPropertyEntryBox)
.ObjectPath(this, &SMaterialAnalyzer::GetCurrentAssetPath)
.AllowedClass(UMaterialInterface::StaticClass())
.OnObjectChanged(this, &SMaterialAnalyzer::OnAssetSelected)
.AllowClear(false)
.DisplayUseSelected(true)
.DisplayBrowse(true)
.NewAssetFactories(TArray<UFactory*>())
.IsEnabled(this, &SMaterialAnalyzer::IsMaterialSelectionAllowed);
this->ChildSlot
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
.BorderBackgroundColor(FLinearColor::Gray) // Darken the outer border
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(5.0f, 5.0f, 5.0f, 5.0f))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("MaterialToAnalyzeLabel", "Material To Analyze: "))
]
+ SHorizontalBox::Slot()
.FillWidth(0.4f)
[
AssetPickerWidget
]
+ SHorizontalBox::Slot()
.FillWidth(0.4f)
[
SNew(SFilterSearchBox)
.HintText(LOCTEXT("MaterialParametersToFilterHint", "Parameters to Filter..."))
.ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search (pressing enter selects the results)"))
.OnTextCommitted(this, &SMaterialAnalyzer::OnParameterFilterChanged)
]
+SHorizontalBox::Slot()
.FillWidth(0.2f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "ButtonStyle")
.OnClicked(this, &SMaterialAnalyzer::OnExportAnalyzedMaterialToCSV)
.ContentPadding(FMargin(2.0f))
.Content()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Save"))
.ColorAndOpacity(FSlateColor(EStyleColor::Black))
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("ExportToCSV", "Export to CSV"))
.ColorAndOpacity(FSlateColor(EStyleColor::Black))
]
]
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Vertical)
+ SSplitter::Slot()
[
SNew(SBorder)
.Padding(0)
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
[
SAssignNew(MaterialTree, SAnalyzedMaterialTree)
.TreeItemsSource(&MaterialTreeRoot)
.OnGenerateRow(this, &SMaterialAnalyzer::HandleReflectorTreeGenerateRow)
.OnGetChildren(this, &SMaterialAnalyzer::HandleReflectorTreeGetChildren)
.OnSetExpansionRecursive(this, &SMaterialAnalyzer::HandleReflectorTreeRecursiveExpansion)
.HeaderRow
(
SNew(SHeaderRow)
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_MaterialName)
.DefaultLabel(LOCTEXT("MaterialNameLabel", "Material Name"))
.FillWidth(0.80f)
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_NumberOfChildren)
.DefaultLabel(LOCTEXT("NumberOfMaterialChildrenLabel", "Number of Children (Direct/Total)"))
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_BasePropertyOverrides)
.DefaultLabel(LOCTEXT("BasePropertyOverridesLabel", "Base Property Overrides"))
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_MaterialLayerParameters)
.DefaultLabel(LOCTEXT("MaterialLayerParametersLabel", "Material Layer Parameters"))
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_StaticSwitchParameters)
.DefaultLabel(LOCTEXT("StaticSwitchParametersLabel", "Static Switch Parameters"))
+ SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_StaticComponentMaskParameters)
.DefaultLabel(LOCTEXT("StaticComponenetMaskParametersLabel", "Static Component Mask Parameters"))
)
]
]
+ SSplitter::Slot()
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
.BorderBackgroundColor(FLinearColor::Gray) // Darken the outer border
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("SuggestionsLabel", "Suggestions"))
]
+ SVerticalBox::Slot()
[
SAssignNew(SuggestionsBox, SScrollBox)
]
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(EVerticalAlignment::VAlign_Bottom)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SAssignNew(StatusBox, STextBlock)
.Text(LOCTEXT("DoneLabel", "Done"))
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.Padding(0,0,4,0)
[
SAssignNew(StatusThrobber, SThrobber)
.Animate(SThrobber::EAnimation::None)
]
]
]
];
// Load the asset registry module to listen for updates
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
if(AssetRegistryModule.Get().IsLoadingAssets())
{
StartAsyncWork(LOCTEXT("WaitingForAssetRegistry", "Waiting for Asset Registry to finish loading"));
bWaitingForAssetRegistryLoad = true;
}
else
{
SetupAssetRegistryCallbacks();
BuildBasicMaterialTree();
}
}
void SMaterialAnalyzer::SetupAssetRegistryCallbacks()
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().OnAssetAdded().AddSP(this, &SMaterialAnalyzer::OnAssetAdded);
}
void SMaterialAnalyzer::OnAssetAdded(const FAssetData& InAssetData)
{
if(InAssetData.IsInstanceOf(UMaterialInterface::StaticClass()))
{
RecentlyAddedAssetData.Add(InAssetData);
}
}
void SMaterialAnalyzer::OnAssetSelected(const FAssetData& AssetData)
{
if (AnalyzeTreeTask == nullptr)
{
CurrentlySelectedAsset = AssetData;
UpdateViewForSelectedAsset();
}
}
void SMaterialAnalyzer::OnParameterFilterChanged(const FText& Filter, const ETextCommit::Type InTextAction)
{
if (AnalyzeTreeTask == nullptr)
{
ParameterFilter = Filter;
bHasParameterFilterChanged = true;
// Parameter filter is applied in two steps:
// 1. Re-build analyzed material tree in order to highlight the filtered parameters (FBuildBasicMaterialTreeAsyncTask)
// 2. Apply filter to UI elements by re-analyzing material tree (FAnalyzeMaterialTreeAsyncTask)
if (CurrentlySelectedAsset.IsValid())
{
RecentlyAddedAssetData.Add(CurrentlySelectedAsset);
}
}
}
void WriteAnalyzedMaterialNodeToCSVStringInternal(const FAnalyzedMaterialNodeRef& Node, FDiagnosticTableWriterCSV& CSVTable)
{
auto AddCSVCell = [&CSVTable](int32 NumElements, const TFunction<FString(int32 ElementIndex)>& ElementNameCallback) -> void
{
FString Cell;
for (int32 ElementIndex = 0; ElementIndex < NumElements; ++ElementIndex)
{
Cell += ElementNameCallback(ElementIndex);
if (ElementIndex + 1 < NumElements)
{
Cell += TEXT("\n");
}
}
CSVTable.AddColumn(TEXT("%s"), *Cell);
};
FString Output;
CSVTable.AddColumn(TEXT("%s"), *Node->AssetData.AssetName.ToString());
AddCSVCell(
Node->BasePropertyOverrides.Num(),
[Node](int32 ElementIndex)
{
return FString::Printf(TEXT("%s ( %f )"), *Node->BasePropertyOverrides[ElementIndex]->ParameterName.ToString(), Node->BasePropertyOverrides[ElementIndex]->ParameterValue);
}
);
AddCSVCell(
Node->MaterialLayerParameters.Num(),
[Node](int32 ElementIndex)
{
return FString::Printf(TEXT("%s ( %s )"), *Node->MaterialLayerParameters[ElementIndex]->ParameterName.ToString(), *Node->MaterialLayerParameters[ElementIndex]->ParameterValue);
}
);
AddCSVCell(
Node->StaticSwitchParameters.Num(),
[Node](int32 ElementIndex)
{
return FString::Printf(TEXT("%s ( %s )"), *Node->StaticSwitchParameters[ElementIndex]->ParameterName.ToString(), Node->StaticSwitchParameters[ElementIndex]->ParameterValue ? TEXT("True") : TEXT("False"));
}
);
AddCSVCell(
Node->StaticComponentMaskParameters.Num(),
[Node](int32 ElementIndex)
{
const FStaticComponentMaskParameterNode& Parameter = *Node->StaticComponentMaskParameters[ElementIndex];
return FString::Printf(
TEXT("%s ( %s%s%s%s )"),
*Parameter.ParameterName.ToString(),
Parameter.R ? TEXT("R") : TEXT("_"),
Parameter.G ? TEXT("G") : TEXT("_"),
Parameter.B ? TEXT("B") : TEXT("_"),
Parameter.A ? TEXT("A") : TEXT("_")
);
}
);
CSVTable.CycleRow();
for (const FAnalyzedMaterialNodeRef& ChildNode : Node->GetChildNodes())
{
WriteAnalyzedMaterialNodeToCSVStringInternal(ChildNode, CSVTable);
}
}
static void WriteAnalyzedMaterialNodeToCSVString(const FAnalyzedMaterialNodeRef& Node, FDiagnosticTableWriterCSV& CSVTable)
{
CSVTable.AddColumn(TEXT("MATERIAL"));
CSVTable.AddColumn(TEXT("BASE PROPERTY OVERRIDES"));
CSVTable.AddColumn(TEXT("LAYER PARAMETERS"));
CSVTable.AddColumn(TEXT("STATIC SWITCHES"));
CSVTable.AddColumn(TEXT("STATIC COMPONENT MASKS"));
CSVTable.CycleRow();
WriteAnalyzedMaterialNodeToCSVStringInternal(Node, CSVTable);
}
static bool SaveFileDialog(const FString& Title, const FString& FileTypes, FString& OutFilename, FString& InOutLastFileanme)
{
OutFilename.Empty();
TArray<FString> OutFilenames;
if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get())
{
const bool bFileChosen = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
Title,
FPaths::GetPath(InOutLastFileanme),
FPaths::GetPathLeaf(InOutLastFileanme),
FileTypes,
EFileDialogFlags::None,
OutFilenames
);
if (bFileChosen && OutFilenames.Num() > 0)
{
// User successfully chose a file; remember the path for the next time the dialog opens.
OutFilename = OutFilenames[0];
InOutLastFileanme = OutFilenames[0];
return true;
}
}
return false;
}
FReply SMaterialAnalyzer::OnExportAnalyzedMaterialToCSV()
{
if (!MaterialTreeRoot.IsEmpty())
{
FString ExportFilename;
static FString LastUsedFilename = FPaths::Combine(FPaths::GetProjectFilePath(), TEXT("Saved"), TEXT("Logs"), TEXT("MaterialProperties.csv"));
if (SaveFileDialog(NSLOCTEXT("UnrealEd", "Export", "Export").ToString(), TEXT("Comma Separated Value (CSV) Files|*.csv"), ExportFilename, LastUsedFilename))
{
if (TUniquePtr<FArchive> CSVTableFile = TUniquePtr<FArchive>{ IFileManager::Get().CreateFileWriter(*ExportFilename) })
{
FDiagnosticTableWriterCSV CSVTable{ CSVTableFile.Get() };
WriteAnalyzedMaterialNodeToCSVString(MaterialTreeRoot[0], CSVTable);
}
else
{
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Export operation failed!"), *NSLOCTEXT("UnrealEd", "Error", "!").ToString());
}
}
}
return FReply::Handled();
}
void SMaterialAnalyzer::UpdateViewForSelectedAsset()
{
if (CurrentlySelectedAsset.IsValid())
{
const FAssetData* ParentAssetData = &CurrentlySelectedAsset;
const FAssetData* NextParentAssetData = FindParentAssetData(&CurrentlySelectedAsset, AssetDataArray);
// get the topmost parent
while (NextParentAssetData != nullptr)
{
ParentAssetData = NextParentAssetData;
NextParentAssetData = FindParentAssetData(ParentAssetData, AssetDataArray);
}
// empty the previous tree root
MaterialTreeRoot.Empty(1);
// Add the new tree root
FAnalyzedMaterialNodeRef* NewRoot = AllMaterialTreeRoots.FindByPredicate([&](FAnalyzedMaterialNodeRef& Entry)
{
return Entry->ObjectPath == ParentAssetData->GetSoftObjectPath();
});
check(NewRoot != nullptr);
MaterialTreeRoot.Add(*NewRoot);
MaterialTree->RequestTreeRefresh();
SuggestionsBox->ClearChildren();
AnalyzeTreeTask = new FAsyncTask<FAnalyzeMaterialTreeAsyncTask>(*NewRoot, AssetDataArray, ParameterFilter);
StartAsyncWork(FText::Format(LOCTEXT("AnalyzingMaterial", "Analyzing {0}"), FText::FromString(AnalyzeTreeTask->GetTask().CurrentMaterialNode->Path)));
AnalyzeTreeTask->StartBackgroundTask();
}
}
void SMaterialAnalyzer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (bWaitingForAssetRegistryLoad)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
if(!AssetRegistryModule.Get().IsLoadingAssets())
{
SetupAssetRegistryCallbacks();
BuildBasicMaterialTree();
bWaitingForAssetRegistryLoad = false;
}
}
else
{
if (BuildBaseMaterialTreeTask != nullptr && BuildBaseMaterialTreeTask->IsDone())
{
delete BuildBaseMaterialTreeTask;
BuildBaseMaterialTreeTask = nullptr;
AsyncWorkFinished(FText::Format(FTextFormat(LOCTEXT("DoneWithMaterialInterfaces", "Done with {0} MaterialInterfaces")), GetTotalNumberOfMaterialNodes()));
// If the parameter filter has changed, we also have to run analysis async task again,
// which applies the actual parameter filter to UI elements in the tree view pane.
if (bHasParameterFilterChanged)
{
UpdateViewForSelectedAsset();
bHasParameterFilterChanged = false;
}
else
{
MaterialTree->RequestTreeRefresh();
}
}
if (BuildBaseMaterialTreeTask == nullptr && RecentlyAddedAssetData.Num() > 0)
{
// Need to make this append to the previously generated list instead of erase all of the old info
// Current problem is that if we only have a portion of the asset registry it will create duplicate
// nodes since it won't find all parents in the tree. Need to modify the async task to not create
// nodes that don't have parents until we can find their parent.
AssetDataArray.Append(RecentlyAddedAssetData);
RecentlyAddedAssetData.Empty();
AllMaterialTreeRoots.Empty(AllMaterialTreeRoots.Num());
BuildBaseMaterialTreeTask = new FAsyncTask<FBuildBasicMaterialTreeAsyncTask>(AllMaterialTreeRoots, AssetDataArray);
BuildBaseMaterialTreeTask->StartBackgroundTask();
StartAsyncWork(LOCTEXT("BuildingBasicTree", "Building Basic MaterialTree"));
}
if (AnalyzeTreeTask != nullptr && AnalyzeTreeTask->IsDone())
{
if (AnalyzeTreeTask->GetTask().LoadNextMaterial())
{
StartAsyncWork(FText::Format(LOCTEXT("AnalyzingMaterial", "Analyzing {0}"), FText::FromString(AnalyzeTreeTask->GetTask().CurrentMaterialNode->Path)));
AnalyzeTreeTask->StartBackgroundTask();
}
else
{
MaterialTree->RequestListRefresh();
// Kick off a check for identical permutations
// @todo make this a series of tests that users can choose to run
AnalyzeForIdenticalPermutationsTask = new FAsyncTask<FAnalyzeForIdenticalPermutationsAsyncTask>(AnalyzeTreeTask->GetTask().MaterialTreeRoot);
AnalyzeForIdenticalPermutationsTask->StartBackgroundTask();
delete AnalyzeTreeTask;
AnalyzeTreeTask = nullptr;
StartAsyncWork(LOCTEXT("AnalyzingTreeForIdenticalPermutations", "Analyzing material tree for identical permutations"));
}
}
if (AnalyzeForIdenticalPermutationsTask != nullptr && AnalyzeForIdenticalPermutationsTask->IsDone())
{
MaterialTree->RequestListRefresh();
AsyncWorkFinished(LOCTEXT("Done", "Done!"));
TMultiMap<int32, FPermutationSuggestionData> Suggestions = AnalyzeForIdenticalPermutationsTask->GetTask().GetSuggestions();
Suggestions.KeySort([](int32 A, int32 B) {
return A > B; // sort to show most improvement possibility first
});
int32 BackgroundColorCounter = 0;
SuggestionDataArray.Empty();
for (auto It = Suggestions.CreateConstIterator(); It; ++It)
{
TSharedPtr<FPermutationSuggestionView> SuggestionHeader = MakeShareable(new FPermutationSuggestionView());
SuggestionHeader->Header = It.Value().Header;
for (FString Material : It.Value().Materials)
{
TSharedPtr<FPermutationSuggestionView> SuggestionChild = MakeShareable(new FPermutationSuggestionView());
SuggestionChild->Header = FText::FromString(Material);
SuggestionHeader->Children.Add(SuggestionChild);
}
SuggestionDataArray.Add(SuggestionHeader);
}
SuggestionsBox->AddSlot()
[
SAssignNew(SuggestionsTree, STreeView<TSharedPtr<FPermutationSuggestionView>>)
.TreeItemsSource(&SuggestionDataArray)
.OnGenerateRow(this, &SMaterialAnalyzer::OnGenerateSuggestionRow)
.OnGetChildren(this, &SMaterialAnalyzer::OnGetSuggestionChildren)
];
delete AnalyzeForIdenticalPermutationsTask;
AnalyzeForIdenticalPermutationsTask = nullptr;
}
}
}
TSharedRef< ITableRow > SMaterialAnalyzer::OnGenerateSuggestionRow(TSharedPtr<FPermutationSuggestionView> Item, const TSharedRef< STableViewBase >& OwnerTable)
{
if (Item->Children.Num() > 0)
{
return SNew(STableRow<TSharedPtr<FPermutationSuggestionView>>, OwnerTable)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Bottom)
[
SNew(SEditableText)
.IsReadOnly(true)
.Text(Item->Header)
]
+ SVerticalBox::Slot()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Visibility(this, &SMaterialAnalyzer::ShouldShowAdvancedRecommendations, Item)
.Text(LOCTEXT("PermutationRecommendation", "It is recommended that you reparent them in a way so only dynamic parameters differ."))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNullWidget::NullWidget
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleSharpButton")
.Visibility(this, &SMaterialAnalyzer::ShouldShowAdvancedRecommendations, Item)
.OnClicked(this, &SMaterialAnalyzer::CreateLocalSuggestionCollectionClicked, Item)
.ContentPadding(FMargin(2.0f))
.Content()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Plus"))
.ColorAndOpacity(FSlateColor(EStyleColor::Black))
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("CreateLocalCollection", "Create Local Collection"))
.ColorAndOpacity(FSlateColor(EStyleColor::Black))
]
]
]
]
];
}
return SNew(STableRow<TSharedPtr<FPermutationSuggestionView>>, OwnerTable)
[
SNew(SEditableText)
.IsReadOnly(true)
.Text(Item->Header)
];
}
EVisibility SMaterialAnalyzer::ShouldShowAdvancedRecommendations(TSharedPtr<FPermutationSuggestionView> Item) const
{
return SuggestionsTree->IsItemExpanded(Item) ? EVisibility::Visible : EVisibility::Collapsed;
}
void SMaterialAnalyzer::OnGetSuggestionChildren(TSharedPtr<FPermutationSuggestionView> InParent, TArray< TSharedPtr<FPermutationSuggestionView> >& OutChildren)
{
OutChildren = InParent->Children;
}
FReply SMaterialAnalyzer::CreateLocalSuggestionCollectionClicked(TSharedPtr<FPermutationSuggestionView> InSuggestion)
{
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
FCollectionManagerModule::GetModule().Get().GetVisibleCollectionContainers(CollectionContainers);
if (CollectionContainers.Num() == 1)
{
CreateLocalSuggestionCollection(*CollectionContainers[0], *InSuggestion);
}
else
{
// Create a menu to select the collection container.
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("MaterialAnalyzerCollectionContainersMenuHeading", "Collection Containers"));
for (const TSharedPtr<ICollectionContainer>& CollectionContainer : CollectionContainers)
{
MenuBuilder.AddMenuEntry(
CollectionContainer->GetCollectionSource()->GetTitle(),
TAttribute<FText>(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this, CollectionContainer, InSuggestion]()
{
CreateLocalSuggestionCollection(*CollectionContainer, *InSuggestion);
}),
FCanExecuteAction::CreateLambda([CollectionContainer]()
{
return !CollectionContainer->IsReadOnly(ECollectionShareType::CST_Local);
})));
}
MenuBuilder.EndSection();
FWidgetPath WidgetPath;
if (FSlateApplication::Get().GeneratePathToWidgetUnchecked(AsShared(), WidgetPath))
{
FSlateApplication::Get().PushMenu(
AsShared(),
WidgetPath,
MenuBuilder.MakeWidget(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TopMenu)
);
}
}
return FReply::Handled();
}
void SMaterialAnalyzer::CreateLocalSuggestionCollection(ICollectionContainer& InCollectionContainer, const FPermutationSuggestionView& InSuggestion)
{
TArray<FString> AllSelectedPackageNames;
ECollectionShareType::Type ShareType = ECollectionShareType::CST_Local;
for (TSharedPtr<FPermutationSuggestionView> Child : InSuggestion.Children)
{
AllSelectedPackageNames.Add(Child->Header.ToString());
}
if (AllSelectedPackageNames.Num() > 0)
{
FString FirstAssetString = CurrentlySelectedAsset.AssetName.ToString() + TEXT("_") + FString::FromInt(InSuggestion.Children.Num());
FName FirstAssetName = FName(*FirstAssetString);
InCollectionContainer.CreateUniqueCollectionName(FirstAssetName, ShareType, FirstAssetName);
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FName> PackageNamesToAddToCollection;
TArray<FName> PackageNameSet;
for (FString PackageToAdd : AllSelectedPackageNames)
{
PackageNameSet.Add(FName(*FPaths::GetBaseFilename(*PackageToAdd, false)));
}
const FText MaterialAnalyzerText = LOCTEXT("MaterialAnalyzerPrefix", "MaterialAnalyzer");
FName ParentName = FName(*MaterialAnalyzerText.ToString());
if (!InCollectionContainer.CollectionExists(ParentName, ShareType))
{
InCollectionContainer.CreateCollection(ParentName, ShareType, ECollectionStorageMode::Static);
}
const bool bCollectionSucceeded = IAssetManagerEditorModule::Get().WriteCollection(InCollectionContainer, FirstAssetName, ShareType, PackageNameSet, true);
if (bCollectionSucceeded)
{
InCollectionContainer.ReparentCollection(FirstAssetName, ShareType, ParentName, ShareType);
}
}
}
void SMaterialAnalyzer::StartAsyncWork(const FText& WorkText)
{
bIsAsyncWorkInProgress = true;
if(StatusBox.IsValid())
{
StatusBox->SetText(WorkText);
}
if(StatusThrobber.IsValid())
{
StatusThrobber->SetAnimate(SThrobber::Horizontal);
StatusThrobber->SetVisibility(EVisibility::SelfHitTestInvisible);
}
}
void SMaterialAnalyzer::AsyncWorkFinished(const FText& CompleteText)
{
if (StatusBox.IsValid())
{
StatusBox->SetText(CompleteText);
}
if (StatusThrobber.IsValid())
{
StatusThrobber->SetAnimate(SThrobber::None);
StatusThrobber->SetVisibility(EVisibility::Collapsed);
}
bIsAsyncWorkInProgress = false;
}
int32 SMaterialAnalyzer::GetTotalNumberOfMaterialNodes()
{
int32 NumMaterialNodes = AllMaterialTreeRoots.Num();
for(int i = 0; i < AllMaterialTreeRoots.Num(); ++i)
{
NumMaterialNodes += AllMaterialTreeRoots[i]->TotalNumberOfChildren();
}
return NumMaterialNodes;
}
FString SMaterialAnalyzer::GetCurrentAssetPath() const
{
return CurrentlySelectedAsset.IsValid() ? CurrentlySelectedAsset.GetObjectPathString() : FString("");
}
void SMaterialAnalyzer::BuildBasicMaterialTree()
{
static const FName AssetRegistryName(TEXT("AssetRegistry"));
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryName);
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
AssetRegistry.GetAssetsByClass(UMaterial::StaticClass()->GetClassPathName(), AssetDataArray, true);
AssetRegistry.GetAssetsByClass(UMaterialInstance::StaticClass()->GetClassPathName(), AssetDataArray, true);
if (BuildBaseMaterialTreeTask == nullptr && AssetDataArray.Num() > 0)
{
AllMaterialTreeRoots.Empty(AllMaterialTreeRoots.Num());
BuildBaseMaterialTreeTask = new FAsyncTask<FBuildBasicMaterialTreeAsyncTask>(AllMaterialTreeRoots, AssetDataArray);
BuildBaseMaterialTreeTask->StartBackgroundTask();
StartAsyncWork(LOCTEXT("BuildingBasicTree", "Building Basic MaterialTree"));
if (StatusThrobber.IsValid())
{
StatusThrobber->SetAnimate(SThrobber::EAnimation::Horizontal);
}
}
}
TSharedRef<ITableRow> SMaterialAnalyzer::HandleReflectorTreeGenerateRow(FAnalyzedMaterialNodeRef InMaterialNode, const TSharedRef<STableViewBase>& OwnerTable)
{
TSharedPtr<SAnalyzedMaterialNodeWidgetItem> NewWidget = SNew(SAnalyzedMaterialNodeWidgetItem, OwnerTable)
.MaterialInfoToVisualize(InMaterialNode);
// if we're the base level we're going to expand right away
if(!InMaterialNode->Parent.IsValid())
{
MaterialTree->SetItemExpansion(InMaterialNode, true);
}
return NewWidget.ToSharedRef();
}
void SMaterialAnalyzer::HandleReflectorTreeGetChildren(FAnalyzedMaterialNodeRef InMaterialNode, TArray<FAnalyzedMaterialNodeRef>& OutChildren)
{
if (ParameterFilter.IsEmpty())
{
OutChildren = InMaterialNode->GetChildNodes();
}
else
{
OutChildren.Empty(InMaterialNode->GetChildNodes().Num());
for (const FAnalyzedMaterialNodeRef& ChildNode : InMaterialNode->GetChildNodes())
{
if (ChildNode->HasAnyFilteredParameters(ParameterFilter.ToString()))
{
OutChildren.Add(ChildNode);
}
}
}
}
void SMaterialAnalyzer::HandleReflectorTreeRecursiveExpansion(FAnalyzedMaterialNodeRef InTreeNode, bool bIsItemExpanded)
{
TArray< FAnalyzedMaterialNodeRef > Children = InTreeNode->GetChildNodes();
if (Children.Num())
{
MaterialTree->SetItemExpansion(InTreeNode, bIsItemExpanded);
const bool bShouldSaveState = true;
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
FAnalyzedMaterialNodeRef Child = Children[ChildIndex];
MaterialTree->SetItemExpansion(Child, bIsItemExpanded);
}
}
}
FAnalyzedMaterialNodePtr FBuildBasicMaterialTreeAsyncTask::FindOrMakeBranchNode(FAnalyzedMaterialNodePtr ParentNode, const FAssetData* ChildData)
{
check(ChildData != nullptr);
FAnalyzedMaterialNodeRef* OutNode = nullptr;
FSoftObjectPath ChildPath = ChildData->GetSoftObjectPath();
TArray<FAnalyzedMaterialNodeRef>& NodesToSearch = ParentNode.IsValid() ? ParentNode->GetChildNodes() : MaterialTreeRoot;
OutNode = NodesToSearch.FindByPredicate([&](FAnalyzedMaterialNodeRef& Entry) { return Entry->ObjectPath == ChildPath; });
if (OutNode == nullptr)
{
FAnalyzedMaterialNode ChildNode;
ChildNode.Path = ChildData->AssetName.ToString();
ChildNode.ObjectPath = ChildData->GetSoftObjectPath();
ChildNode.Parent = ParentNode;
ChildNode.AssetData = *ChildData;
NodesToSearch.Add(FAnalyzedMaterialNodeRef(new FAnalyzedMaterialNode(ChildNode)));
OutNode = &NodesToSearch[NodesToSearch.Num() - 1];
}
return FAnalyzedMaterialNodePtr (*OutNode);
}
void FBuildBasicMaterialTreeAsyncTask::DoWork()
{
for(int i = 0; i < AssetDataToAnalyze.Num(); ++i)
{
const FAssetData& AssetData = AssetDataToAnalyze[i];
TArray<const FAssetData*> FullBranch;
const FAssetData* CurrentBranchNode = &AssetData;
while(CurrentBranchNode)
{
FullBranch.Add(CurrentBranchNode);
CurrentBranchNode = FindParentAssetData(CurrentBranchNode, AssetDataToAnalyze);
}
FAnalyzedMaterialNodePtr ParentNode = nullptr;
for(int Depth = FullBranch.Num() - 1; Depth >= 0; --Depth)
{
ParentNode = FindOrMakeBranchNode(ParentNode, FullBranch[Depth]);
}
}
}
bool FAnalyzeMaterialTreeAsyncTask::LoadNextMaterial()
{
if (CurrentMaterialQueueIndex < MaterialQueue.Num())
{
CurrentMaterialNode = MaterialQueue[CurrentMaterialQueueIndex];
check(CurrentMaterialNode->ObjectPath.IsValid());
CurrentMaterialInterface = FindObject<UMaterialInterface>(NULL, *CurrentMaterialNode->ObjectPath.ToString());
if (CurrentMaterialInterface == nullptr)
{
CurrentMaterialInterface = LoadObject<UMaterialInterface>(NULL, *CurrentMaterialNode->ObjectPath.ToString());
check(CurrentMaterialInterface);
}
return true;
}
return false;
}
void FAnalyzeMaterialTreeAsyncTask::DoWork()
{
MaterialQueue.Append(CurrentMaterialNode->GetChildNodes());
TArray<FMaterialParameterInfo> MaterialLayersParameterInfo;
check(CurrentMaterialInterface != nullptr);
TArray<FMaterialParameterInfo> ParameterInfo;
TArray<FGuid> Guids;
UMaterial* CurrentMaterial = Cast<UMaterial>(CurrentMaterialInterface);
UMaterialInstance* CurrentMaterialInstance = Cast<UMaterialInstance>(CurrentMaterialInterface);
bool bCanBeOverriden = CurrentMaterial != nullptr;
if(CurrentMaterial != nullptr)
{
CurrentMaterial->GetAllStaticSwitchParameterInfo(StaticSwitchParameterInfo, StaticSwitchGuids);
CurrentMaterial->GetAllStaticComponentMaskParameterInfo(StaticMaskParameterInfo, StaticMaskGuids);
}
CurrentMaterialNode->BasePropertyOverrides.Empty(BasePropertyOverrideNames.Num());
const FString ParameterFilterString = this->ParameterFilter.ToString();
auto IsIncludedInParameterFilter = [&ParameterFilterString](const FName& Name) -> bool
{
return ParameterFilterString.IsEmpty() || Name.ToString().Contains(ParameterFilterString);
};
for (const TPair<FName, FName>& BasePropertyOverrideName : BasePropertyOverrideNames)
{
float TempValue = 0.0f;
bool bIsOverridden = false;
if (!IsIncludedInParameterFilter(BasePropertyOverrideName.Value))
{
continue;
}
if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_OpacityMaskClipValue")))
{
TempValue = CurrentMaterialInterface->GetOpacityMaskClipValue();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_BlendMode")))
{
TempValue = (float)CurrentMaterialInterface->GetBlendMode();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_BlendMode;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_ShadingModel")))
{
if (CurrentMaterialInterface->IsShadingModelFromMaterialExpression())
{
TempValue = (float)MSM_FromMaterialExpression;
}
else
{
ensure(CurrentMaterialInterface->GetShadingModels().CountShadingModels() == 1);
TempValue = (float)CurrentMaterialInterface->GetShadingModels().GetFirstShadingModel();
}
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_ShadingModel;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DitheredLODTransition")))
{
TempValue = (float)CurrentMaterialInterface->IsDitheredLODTransition();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_CastDynamicShadowAsMasked")))
{
TempValue = CurrentMaterialInterface->GetCastShadowAsMasked();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_CastDynamicShadowAsMasked;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_TwoSided")))
{
TempValue = CurrentMaterialInterface->IsTwoSided();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_TwoSided;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bIsThinSurface")))
{
TempValue = CurrentMaterialInterface->IsThinSurface();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bIsThinSurface;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_OutputTranslucentVelocity")))
{
TempValue = CurrentMaterialInterface->IsTranslucencyWritingVelocity();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_OutputTranslucentVelocity;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bHasPixelAnimation")))
{
TempValue = CurrentMaterialInterface->HasPixelAnimation();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bHasPixelAnimation;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bEnableTessellation")))
{
TempValue = CurrentMaterialInterface->IsTessellationEnabled();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bEnableTessellation;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DisplacementScaling")))
{
TempValue = CurrentMaterialInterface->GetDisplacementScaling().Magnitude;
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DisplacementScaling;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bEnableDisplacementFade")))
{
TempValue = CurrentMaterialInterface->IsDisplacementFadeEnabled();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bEnableDisplacementFade;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DisplacementFadeRange")))
{
TempValue = CurrentMaterialInterface->GetDisplacementFadeRange().EndSizePixels;
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DisplacementFadeRange;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_MaxWorldPositionOffsetDisplacement")))
{
TempValue = CurrentMaterialInterface->GetMaxWorldPositionOffsetDisplacement();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_MaxWorldPositionOffsetDisplacement;
}
}
else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_CompatibleWithLumenCardSharing")))
{
TempValue = (float)CurrentMaterialInterface->IsCompatibleWithLumenCardSharing();
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_CompatibleWithLumenCardSharing;
}
}
// Check the parent for this variable
FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent;
if (!bIsOverridden && Parent.IsValid())
{
// We shouldn't be able to get in here for the base Material
FBasePropertyOverrideNodeRef ParentParameter = Parent->FindBasePropertyOverride(BasePropertyOverrideName.Value);
CurrentMaterialNode->BasePropertyOverrides.Add(FBasePropertyOverrideNodeRef(
new FBasePropertyOverrideNode(ParentParameter->ParameterName,
ParentParameter->ParameterID,
ParentParameter->ParameterValue,
false,
this->ParameterFilter)));
}
else
{
CurrentMaterialNode->BasePropertyOverrides.Add(FBasePropertyOverrideNodeRef(
new FBasePropertyOverrideNode(BasePropertyOverrideName.Value, BasePropertyOverrideName.Key, TempValue, bIsOverridden, this->ParameterFilter)));
}
}
FMaterialLayersFunctions MaterialLayers;
if (CurrentMaterialInterface->GetMaterialLayers(MaterialLayers))
{
bool bIsOverridden = false;
if (CurrentMaterialInstance)
{
FMaterialLayersFunctions ParentMaterialLayers;
const bool bParentHasLayers = CurrentMaterialInstance->Parent->GetMaterialLayers(ParentMaterialLayers);
bIsOverridden = !bParentHasLayers || MaterialLayers != ParentMaterialLayers;
}
CurrentMaterialNode->MaterialLayerParameters.Add(FStaticMaterialLayerParameterNodeRef(
new FStaticMaterialLayerParameterNode(FName(),
MaterialLayers.GetStaticPermutationString(),
bIsOverridden,
this->ParameterFilter)));
}
CurrentMaterialNode->StaticSwitchParameters.Empty(StaticSwitchParameterInfo.Num());
for (int ParameterIndex = 0; ParameterIndex < StaticSwitchParameterInfo.Num(); ++ParameterIndex)
{
FMaterialParameterMetadata Meta;
bool bIsOverridden = false;
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->GetParameterOverrideValue(EMaterialParameterType::StaticSwitch, StaticSwitchParameterInfo[ParameterIndex], Meta);
}
else if(CurrentMaterial)
{
bIsOverridden = CurrentMaterial->GetParameterValue(EMaterialParameterType::StaticSwitch, StaticSwitchParameterInfo[ParameterIndex], Meta);
}
if (!IsIncludedInParameterFilter(StaticSwitchParameterInfo[ParameterIndex].Name))
{
continue;
}
if (!bIsOverridden)
{
// Check the parent for this variable
FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent;
// We shouldn't be able to get in here for the base Material
check(Parent.IsValid());
FStaticSwitchParameterNodeRef ParentParameter = Parent->FindStaticSwitchParameter(StaticSwitchParameterInfo[ParameterIndex].Name);
CurrentMaterialNode->StaticSwitchParameters.Add(FStaticSwitchParameterNodeRef(
new FStaticSwitchParameterNode(ParentParameter->ParameterName,
ParentParameter->ParameterValue,
false,
this->ParameterFilter)));
}
else
{
CurrentMaterialNode->StaticSwitchParameters.Add(FStaticSwitchParameterNodeRef(
new FStaticSwitchParameterNode(StaticSwitchParameterInfo[ParameterIndex].Name, Meta.Value.AsStaticSwitch(), true, this->ParameterFilter)));
}
}
CurrentMaterialNode->StaticComponentMaskParameters.Empty(StaticMaskParameterInfo.Num());
for (int ParameterIndex = 0; ParameterIndex < StaticMaskParameterInfo.Num(); ++ParameterIndex)
{
if (!IsIncludedInParameterFilter(StaticMaskParameterInfo[ParameterIndex].Name))
{
continue;
}
FMaterialParameterMetadata Meta;
bool bIsOverridden = false;
if (CurrentMaterialInstance)
{
bIsOverridden = CurrentMaterialInstance->GetParameterOverrideValue(EMaterialParameterType::StaticComponentMask, StaticMaskParameterInfo[ParameterIndex], Meta);
}
else if (CurrentMaterial)
{
bIsOverridden = CurrentMaterial->GetParameterValue(EMaterialParameterType::StaticComponentMask, StaticMaskParameterInfo[ParameterIndex], Meta);
}
if(!bIsOverridden)
{
// Check the parent for this variable
FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent;
// We shouldn't be able to get in here for the base Material
check(Parent.IsValid());
FStaticComponentMaskParameterNodeRef ParentParameter = Parent->FindStaticComponentMaskParameter(StaticMaskParameterInfo[ParameterIndex].Name);
CurrentMaterialNode->StaticComponentMaskParameters.Add(FStaticComponentMaskParameterNodeRef(
new FStaticComponentMaskParameterNode(ParentParameter->ParameterName,
ParentParameter->R,
ParentParameter->G,
ParentParameter->B,
ParentParameter->A,
false,
this->ParameterFilter)));
}
else
{
CurrentMaterialNode->StaticComponentMaskParameters.Add(FStaticComponentMaskParameterNodeRef(
new FStaticComponentMaskParameterNode(StaticMaskParameterInfo[ParameterIndex].Name,
Meta.Value.Bool[0],
Meta.Value.Bool[1],
Meta.Value.Bool[2],
Meta.Value.Bool[3],
true,
this->ParameterFilter)));
}
}
CurrentMaterialQueueIndex++;
}
bool FAnalyzeForIdenticalPermutationsAsyncTask::CreateMaterialPermutationHashForNode(const FAnalyzedMaterialNodeRef& MaterialNode, uint32& OutHash)
{
TArray<char> ByteArray;
bool bAnyOverrides = false;
for (int ParameterIndex = 0; ParameterIndex < MaterialNode->BasePropertyOverrides.Num(); ++ParameterIndex)
{
FString FloatToHash = FString::SanitizeFloat(MaterialNode->BasePropertyOverrides[ParameterIndex]->ParameterValue);
ByteArray.Append(TCHAR_TO_ANSI(*FloatToHash), FloatToHash.Len());
bAnyOverrides = bAnyOverrides || MaterialNode->BasePropertyOverrides[ParameterIndex]->bOverride;
}
for (int ParameterIndex = 0; ParameterIndex < MaterialNode->MaterialLayerParameters.Num(); ++ParameterIndex)
{
ByteArray.Append(TCHAR_TO_ANSI(*MaterialNode->MaterialLayerParameters[ParameterIndex]->ParameterValue), MaterialNode->MaterialLayerParameters[ParameterIndex]->ParameterValue.Len());
bAnyOverrides = bAnyOverrides || MaterialNode->MaterialLayerParameters[ParameterIndex]->bOverride;
}
for(int ParameterIndex = 0; ParameterIndex < MaterialNode->StaticSwitchParameters.Num(); ++ParameterIndex)
{
ByteArray.Add(MaterialNode->StaticSwitchParameters[ParameterIndex]->ParameterValue ? 1 : 0);
bAnyOverrides = bAnyOverrides || MaterialNode->StaticSwitchParameters[ParameterIndex]->bOverride;
}
for(int ParameterIndex = 0; ParameterIndex < MaterialNode->StaticComponentMaskParameters.Num(); ++ParameterIndex)
{
FStaticComponentMaskParameterNodeRef NodeRef = MaterialNode->StaticComponentMaskParameters[ParameterIndex];
ByteArray.Add(NodeRef->R ? 1 : 0);
ByteArray.Add(NodeRef->G ? 1 : 0);
ByteArray.Add(NodeRef->B ? 1 : 0);
ByteArray.Add(NodeRef->A ? 1 : 0);
bAnyOverrides = bAnyOverrides || NodeRef->bOverride;
}
OutHash = CityHash32(ByteArray.GetData(), ByteArray.Num());
return bAnyOverrides;
}
void FAnalyzeForIdenticalPermutationsAsyncTask::DoWork()
{
for(int i = 0; i < MaterialQueue.Num(); ++i)
{
FAnalyzedMaterialNodeRef CurrentMaterialNode = MaterialQueue[i];
MaterialQueue.Append(CurrentMaterialNode->GetChildNodes());
uint32 MaterialPermutationHash = 0;
if(CreateMaterialPermutationHashForNode(CurrentMaterialNode, MaterialPermutationHash))
{
MaterialPermutationHashToMaterialObjectPath.FindOrAdd(MaterialPermutationHash).Add(CurrentMaterialNode->ObjectPath);
}
}
GatherSuggestions();
}
void FAnalyzeForIdenticalPermutationsAsyncTask::GatherSuggestions()
{
Suggestions.Empty();
for (TPair<uint32, TArray<FSoftObjectPath>>& IdenticalPermutations : MaterialPermutationHashToMaterialObjectPath)
{
if (IdenticalPermutations.Value.Num() > 1)
{
TArray<FString> AllNames;
AssetCount = IdenticalPermutations.Value.Num();
for (int i = 0; i < IdenticalPermutations.Value.Num(); ++i)
{
FString PermutationString = IdenticalPermutations.Value[i].ToString();
AllNames.Add(PermutationString);
}
FPermutationSuggestionData NewData = FPermutationSuggestionData(FText::Format(LOCTEXT("IdenticalStaticPermutationSuggestions", "The following {0} materials all have identical static parameter permutations."),
FText::AsNumber(AssetCount)),
AllNames);
Suggestions.Add
(
AssetCount,
NewData
);
}
}
}
#undef LOCTEXT_NAMESPACE