Files
jeremy moore 6b872a5ea3 Change Virtual Texture conversion dialog UI.
Remove "apply filter" button, and apply filter whenever we change the filter value in the dropdown.
Select initial filter value as min or max size according to whether we are converting to VT or from VT.
Default initial filter value is fully inclusive so that we don't get strange UX of selecting to convert a set of textures but none of them pass the initial filter.
#test In editor select to convert one or more VT textures to regular (or the other direction) and validate that all selected textures appear in inital filtered view.

[CL 33424132 by jeremy moore in ue5-main branch]
2024-05-03 08:35:10 -04:00

649 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SConvertToVirtualTexture.h"
#include "ContentBrowserMenuContexts.h"
#include "Engine/Texture2D.h"
#include "Styling/AppStyle.h"
#include "Materials/Material.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialFunctionInstance.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "Particles/SubUVAnimation.h"
#include "Editor.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBorder.h"
#include "Misc/FeedbackContext.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "IAssetTools.h"
#include "AssetRegistry/AssetRegistryHelpers.h"
#define LOCTEXT_NAMESPACE "SConvertToVirtualTexture"
void SConvertToVirtualTexture::Construct(const FArguments& InArgs, bool bInBackwards)
{
UserResponse = FConvertToVTDlg::Cancel;
ParentWindow = InArgs._ParentWindow.Get();
static FName ErrorIcon = "MessageLog.Error";
for (int i = 0; i < 16; i++)
{
TextureSizes.Add(MakeShareable(new int32(1 << i)));
}
const int32 InitiallySelectedIndex = bInBackwards ? 15 : 0;
ThresholdValue = *TextureSizes[InitiallySelectedIndex];
this->ChildSlot[
SNew(SVerticalBox)
// Textbox at the top giving an introductory message
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(STextBlock)
.AutoWrapText(true)
.Visibility(this, &SConvertToVirtualTexture::GetIntroMessageVisibility)
.Text(this, &SConvertToVirtualTexture::GetIntroMessage)
]
// Error message at the top giving a common error message
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SHorizontalBox)
.Visibility(this, &SConvertToVirtualTexture::GetErrorMessageVisibility)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::GetBrush(ErrorIcon))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(STextBlock)
.AutoWrapText(true)
.Text(this, &SConvertToVirtualTexture::GetErrorMessage)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SSeparator)
]
// The actual list of assets
+ SVerticalBox::Slot()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SBorder)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SAssignNew(AssetListContainer, SVerticalBox)
]
]
]
// The bottom row of widgets: texture size selector
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Left)
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("ConvertToVT_Size", "Texture size threshold: "))
.AutoWrapText(true)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SComboBox<TSharedPtr<int32>>)
.OptionsSource(&TextureSizes)
.OnSelectionChanged(this, &SConvertToVirtualTexture::OnThresholdChanged)
.OnGenerateWidget(this, &SConvertToVirtualTexture::OnGenerateThresholdWidget)
.InitiallySelectedItem(TextureSizes[InitiallySelectedIndex])
[
SNew(STextBlock)
.Text(this, &SConvertToVirtualTexture::GetThresholdText)
]
]
]
// Separator
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SSeparator)
]
// Dialog ok/cancel buttons
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SUniformGridPanel)
.SlotPadding(FAppStyle::GetMargin("StandardDialog.SlotPadding"))
.MinDesiredSlotWidth(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotWidth"))
.MinDesiredSlotHeight(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotHeight"))
+ SUniformGridPanel::Slot(0, 0)
[
SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked(this, &SConvertToVirtualTexture::OnButtonClick, FConvertToVTDlg::Confirm)
.IsEnabled(this, &SConvertToVirtualTexture::GetOkButtonEnabled)
.Text(LOCTEXT("ConvertToVT_OK", "OK"))
]
+ SUniformGridPanel::Slot(1, 0)
[
SNew(SButton)
.HAlign(HAlign_Center)
.ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding"))
.OnClicked(this, &SConvertToVirtualTexture::OnButtonClick, FConvertToVTDlg::Cancel)
.Text(LOCTEXT("ConvertToVT_Cancel", "Cancel"))
]
]
];
// will be done by SetUserTextures :
//UpdateList();
}
void SConvertToVirtualTexture::SetBackwards(bool bSetBackwards)
{
Worker.SetConversionDirection(bSetBackwards);
bBackwards = bSetBackwards;
}
void SConvertToVirtualTexture::SetUserTextures(const TArray<UTexture2D *> &Textures)
{
Worker.UserTextures = ObjectPtrWrap(Textures);
UpdateList();
}
FConvertToVTDlg::EResult SConvertToVirtualTexture::GetUserResponse() const
{
return UserResponse;
}
TSharedRef<SWidget> SConvertToVirtualTexture::CreateAssetLine(int index, const FAssetData &Asset, const FConversionStatus &Status)
{
const bool bEngineAsset = Asset.PackagePath.ToString().StartsWith(TEXT("/Engine/"));
FName SeverityIcon = NAME_None;
FText DetailedInfoText;
if (Status.UserSelected)
{
if (Status.InvalidMaterialUsage)
{
SeverityIcon = "MessageLog.Error";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_InvalidUsage", "The texture could not be converted to VT due to its usage in materials (may be connected to a property that doesn't support VT).");
}
else if (Status.NonPowerOf2)
{
SeverityIcon = "MessageLog.Error";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_NonPowerOf2", "The texture could not be converted to VT because its size is not a power of 2.");
}
else if (bEngineAsset)
{
SeverityIcon = "MessageLog.Note";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_Engine", "The texture is an engine asset, a converted copy will be created in the current project.");
}
else if (Status.UnderSized)
{
SeverityIcon = "MessageLog.Note";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_Undersize", "The texture was under the threshold size but should still be converted to vt because it's using a shared parameter with other VT textures.");
}
}
else
{
if (Status.InvalidMaterialUsage)
{
SeverityIcon = "MessageLog.Warning";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_InvalidUsageNotSelected", "The texture could not be converted to VT due to its usage in materials (may be connected to a property that doesn't support VT).");
}
else if (Status.NonPowerOf2)
{
SeverityIcon = "MessageLog.Warning";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_NonPowerOf2NotSelected", "The texture could not be converted to VT because its size is not a power of 2.");
}
else if (bEngineAsset)
{
SeverityIcon = "MessageLog.Note";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_EngineNotSelected", "The engine asset texture was not selected but a converted copy will be created in the current project, because it's using a shared parameter with other VT textures.");
}
else if (Status.UnderSized)
{
SeverityIcon = "MessageLog.Note";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_UndersizeNotSelected", "The texture was not selected and furthermore is under the threshold size but should still be converted to vt because it's using a shared parameter with other VT textures.");
}
else
{
SeverityIcon = "MessageLog.Note";
DetailedInfoText = LOCTEXT("ConvertToVT_ToolTip_NotSelected", "The texture was not selected but should still be converted to vt because it's using a shared parameter with other VT textures.");
}
}
auto Result =
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
// Class icon
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SBox)
.Padding(2.0f)
[
SNew(SImage)
.Image(FSlateIcon(FAppStyle::GetAppStyleSetName(),
(Asset.GetClass() == UTexture2D::StaticClass()) ?
"ClassIcon.Texture2D" :
((Asset.GetClass() == UMaterialFunction::StaticClass()) ? "ClassIcon.MaterialFunction" : "ClassIcon.Material")).GetIcon())
]
]
// Error/warning icon
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBox)
.Padding(2.0f)
[
(SeverityIcon == NAME_None) ? SNullWidget::NullWidget :
static_cast<TSharedRef<SWidget>>(SNew(SImage).Image(FAppStyle::GetBrush(SeverityIcon)))
]
]
// Fold out button with asset name
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
(DetailedInfoText.IsEmpty())
? static_cast<TSharedRef<SWidget>>(SNew(STextBlock)
.Text(FText::FromString(Asset.GetObjectPathString())))
: SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ClickMethod(EButtonClickMethod::MouseDown)
.OnClicked(this, &SConvertToVirtualTexture::OnExpanderClicked, index)
.ContentPadding(0.f)
.ForegroundColor(FSlateColor::UseForeground())
.IsFocusable(false)
//.Text(FText::FromName(Asset.ObjectPath))
[
// Fold out icon
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SImage)
.Image(this, &SConvertToVirtualTexture::GetExpanderImage, index)
.ColorAndOpacity(FSlateColor::UseForeground())
]
// Fold out text
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(Asset.GetObjectPathString()))
]
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(DetailedInfoText)
.Visibility(this, &SConvertToVirtualTexture::GetDetailVisibility, index)
]
+SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
//SNew(STextBlock)
//.Text(GetAuditTrailText(Asset))
//.Visibility(this, &SConvertToVirtualTexture::GetDetailVisibility, index)
GetAuditTrailText(Asset, index)
];
return Result;
}
TSharedRef<SWidget> SConvertToVirtualTexture::GetAuditTrailText(const FAssetData &Asset, int32 index)
{
//FString Result;
UObject *MaybeOk = Asset.GetAsset();
auto Trail = Worker.AuditTrail.Find(MaybeOk);
TSharedRef<SVerticalBox> Box = SNew(SVerticalBox)
.Visibility(this, &SConvertToVirtualTexture::GetDetailVisibility, index);
Box->AddSlot()
.AutoHeight()
.Padding(8.0f, 4.0f, 8.0f, 4.0f)
[
SNew(SSeparator)
];
Box->AddSlot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(LOCTEXT("ConvertToVT_AuditTrail","This texture was included because of the following dependencies:"))
];
while (Trail)
{
Box->AddSlot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(FText::FromString(Trail->PathDescription))
];
if (Trail->Destination)
{
Box->AddSlot()
.AutoHeight()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(FText::FromString(Trail->Destination->GetPathName()))
];
// May return null at which point we'll break out of the loop
auto NewTrail = Worker.AuditTrail.Find(Trail->Destination);
if (NewTrail == nullptr || NewTrail == Trail)
{
break;
}
Trail = NewTrail;
}
else
{
break;
}
//TODO: We just display the first step only as there isn't much more usefull info
//in the trail anyway for now and it could actually confuse people seeing half a trail.
break;
}
return Box;
}
void SConvertToVirtualTexture::UpdateList()
{
Worker.FilterList(ThresholdValue);
AssetList.Empty();
AssetStatus.Empty();
auto CheckUnderSized = [] (const UTexture2D * Texture, int SizeThreshold, bool bConvertBackward)
{
// code dupe from FVirtualTextureConversionWorker::FilterList
bool DoInclude;
// don't use Texture->GetSizeX() as it can be Default texture
FIntPoint Size = Texture->Source.GetLogicalSize();
uint64 TexturePixelCount = (uint64) Size.X * Size.Y;
DoInclude = ( TexturePixelCount >= (uint64)SizeThreshold * SizeThreshold );
// for Backwards this should be the other way around
// we want to filter textures *Smaller* than SizeThreshold
if ( bConvertBackward ) DoInclude = ! DoInclude;
return ! DoInclude;
};
for (UTexture2D *Texture : Worker.Textures)
{
AssetList.Add(Texture);
FConversionStatus* Status = new(AssetStatus) FConversionStatus();
Status->UserSelected = Worker.UserTextures.Contains(Texture);
Status->UnderSized = CheckUnderSized(Texture,ThresholdValue,bBackwards);
Status->NonPowerOf2 = !Texture->Source.AreAllBlocksPowerOfTwo() && Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::None;
}
for (UTexture2D* Texture : Worker.MaterialRejectedTextures)
{
AssetList.Add(Texture);
FConversionStatus* Status = new(AssetStatus) FConversionStatus();
Status->UserSelected = Worker.UserTextures.Contains(Texture);
Status->UnderSized = CheckUnderSized(Texture,ThresholdValue,bBackwards);
Status->NonPowerOf2 = !Texture->Source.AreAllBlocksPowerOfTwo() && Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::None;
Status->InvalidMaterialUsage = true;
}
for (UMaterial *RootMat : Worker.Materials)
{
// Don't want to show anything from the transient package, this may include preview materials from any currently active material editor
// We patch these up so material editor remains valid, but not useful to display this to user
if (RootMat->GetOutermost() != GetTransientPackage())
{
AssetList.Add(RootMat);
AssetStatus.Add(FConversionStatus());
}
}
for (UMaterialFunctionInterface* Func : Worker.Functions)
{
if (Func->GetOutermost() != GetTransientPackage())
{
AssetList.Add(Func);
AssetStatus.Add(FConversionStatus());
}
}
check(AssetList.Num() == AssetStatus.Num());
AssetListContainer->ClearChildren();
for (int Id = 0; Id < AssetList.Num(); Id++)
{
AssetListContainer->AddSlot()
.AutoHeight()
[
CreateAssetLine(Id, AssetList[Id], AssetStatus[Id])
];
}
ErrorMessage = FText();
}
FReply SConvertToVirtualTexture::OnButtonClick(FConvertToVTDlg::EResult ButtonID)
{
ParentWindow->RequestDestroyWindow();
UserResponse = ButtonID;
if (ButtonID == FConvertToVTDlg::EResult::Confirm)
{
Worker.DoConvert();
}
return FReply::Handled();
}
void SConvertToVirtualTexture::OnThresholdChanged(TSharedPtr<int32> InSelectedItem, ESelectInfo::Type SelectInfo)
{
ThresholdValue = *InSelectedItem;
UpdateList();
}
FText SConvertToVirtualTexture::GetThresholdText() const
{
return FText::FromString(FString::Format(TEXT("{0}"), TArray<FStringFormatArg>({ ThresholdValue })));
}
TSharedRef<SWidget> SConvertToVirtualTexture::OnGenerateThresholdWidget(TSharedPtr<int32> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(FString::Format(TEXT("{0}"), TArray<FStringFormatArg>({ *InItem }))));
}
FReply SConvertToVirtualTexture::OnExpanderClicked(int index)
{
if (ExpandedIndexes.Contains(index))
{
ExpandedIndexes.Remove(index);
}
else
{
ExpandedIndexes.Add(index);
}
return FReply::Handled();
}
bool SConvertToVirtualTexture::GetOkButtonEnabled() const
{
return ErrorMessage.IsEmpty();
}
EVisibility SConvertToVirtualTexture::GetDetailVisibility(int index) const
{
return (ExpandedIndexes.Contains(index)) ? EVisibility::Visible : EVisibility::Collapsed;
}
const FSlateBrush* SConvertToVirtualTexture::GetExpanderImage(int index) const
{
FName ResourceName;
if (GetDetailVisibility(index) == EVisibility::Visible)
{
static FName ExpandedName = "TreeArrow_Expanded";
ResourceName = ExpandedName;
}
else
{
static FName CollapsedName = "TreeArrow_Collapsed";
ResourceName = CollapsedName;
}
return FCoreStyle::Get().GetBrush(ResourceName);
}
EVisibility SConvertToVirtualTexture::GetIntroMessageVisibility() const
{
return (IntroMessage.IsEmpty()) ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SConvertToVirtualTexture::GetErrorMessageVisibility() const
{
return (ErrorMessage.IsEmpty()) ? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SConvertToVirtualTexture::GetThresholdVisibility() const
{
return (bThresholdVisible) ? EVisibility::Collapsed : EVisibility::Visible;
}
FText SConvertToVirtualTexture::GetIntroMessage() const
{
return IntroMessage;
}
FText SConvertToVirtualTexture::GetErrorMessage() const
{
return ErrorMessage;
}
FConvertToVTDlg::FConvertToVTDlg(const TArray<UTexture2D *> &Textures, bool bBackwards)
{
if (FSlateApplication::IsInitialized())
{
DialogWindow = SNew(SWindow)
.Title((bBackwards) ?
LOCTEXT("ConvertToVTDlgTitle_Backwards", "Convert VT to Regular if < Threshold") :
LOCTEXT("ConvertToVTDlgTitle", "Convert To VT Textures >= Threshold"))
.SupportsMinimize(false).SupportsMaximize(false)
.ClientSize(FVector2D(500, 500));
TSharedPtr<SBorder> DialogWrapper =
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SAssignNew(DialogWidget, SConvertToVirtualTexture, bBackwards)
.ParentWindow(DialogWindow)
];
// SetUserTextures will do UpdateList :
DialogWidget->SetBackwards(bBackwards);
DialogWidget->SetUserTextures(Textures);
DialogWindow->SetContent(DialogWrapper.ToSharedRef());
}
}
FConvertToVTDlg::EResult FConvertToVTDlg::ShowModal()
{
//Show Dialog
GEditor->EditorAddModalWindow(DialogWindow.ToSharedRef());
EResult UserResponse = (EResult)DialogWidget->GetUserResponse();
DialogWindow->GetParentWindow()->RemoveDescendantWindow(DialogWindow.ToSharedRef());
return UserResponse;
}
void SConvertToVirtualTexture::ConvertVTTexture(TArray<UTexture2D*> InTextures, bool backwards)
{
TArray<UTexture2D*> UserTextures; // The original selection of the user
for (UTexture2D* Texture : InTextures)
{
if (Texture != nullptr && Texture->VirtualTextureStreaming == backwards)
{
UserTextures.Add(Texture);
}
}
FConvertToVTDlg ConvertDlg(UserTextures, backwards);
ConvertDlg.ShowModal();
}
#undef LOCTEXT_NAMESPACE