Files
2025-05-18 13:04:45 +08:00

1643 lines
53 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MaterialStats.h"
#include "MaterialStatsGrid.h"
#include "SMaterialEditorStatsWidget.h"
#include "Styling/AppStyle.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/Text/SMultiLineEditableText.h"
#include "Text/HLSLSyntaxHighlighterMarshaller.h"
#include "HAL/PlatformApplicationMisc.h"
#include "MaterialEditorActions.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInsights.h"
#include "IMaterialEditor.h"
#include "MaterialEditorSettings.h"
#include "ShaderCompiler.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Modules/ModuleManager.h"
#include "MessageLogModule.h"
#include "Interfaces/IShaderFormat.h"
#define LOCTEXT_NAMESPACE "MaterialStats"
const FName FMaterialStats::StatsTabId(TEXT("MaterialStats_Grid"));
const FName FMaterialStats::OldStatsTabId(TEXT("OldMaterialStats_Grid"));
const FName FMaterialStats::HLSLCodeTabId(TEXT("MaterialStats_HLSLCode"));
/***********************************************************************************************************************/
/*begin FShaderPlatformSettings functions*/
FShaderPlatformSettings::FShaderPlatformSettings(
const EPlatformCategoryType _PlatformType,
const EShaderPlatform _ShaderPlatformID,
const FName _Name,
const bool _bAllowPresenceInGrid,
const bool _bAllowCodeView,
const FString& _Description,
const bool bAlwaysOn
)
: PlatformType(_PlatformType)
, PlatformShaderID(_ShaderPlatformID)
, PlatformName(_Name)
, PlatformDescription(_Description)
, bAlwaysOn(bAlwaysOn)
, bAllowCodeView(_bAllowCodeView)
, bAllowPresenceInGrid(_bAllowPresenceInGrid)
{
PlatformNameID = *FMaterialStatsUtils::ShaderPlatformTypeName(PlatformShaderID);
}
void FShaderPlatformSettings::ClearResources()
{
TArray<TRefCountPtr<FMaterial>> MaterialsToDeleteOnRenderThread;
// free material resources
for (int32 i = 0; i < EMaterialQualityLevel::Num; ++i)
{
for (int32 InstanceIndex = 0; InstanceIndex < PlatformData[i].Instances.Num(); ++InstanceIndex)
{
auto& Instance = PlatformData[i].Instances[InstanceIndex];
FMaterialResourceStats* Resource = Instance.MaterialResourcesStats;
if (Resource != nullptr)
{
if (Resource->PrepareDestroy_GameThread())
{
MaterialsToDeleteOnRenderThread.Add(Resource);
}
else
{
delete Resource;
}
}
Instance.ArrShaderEntries.Empty();
}
PlatformData[i].Instances.Empty();
}
FMaterial::DeleteMaterialsOnRenderThread(MaterialsToDeleteOnRenderThread);
}
FText FShaderPlatformSettings::GetSelectedShaderViewComboText(EMaterialQualityLevel::Type QualityLevel, const int32 InstanceIndex) const
{
const auto& Instance = PlatformData[QualityLevel].Instances[InstanceIndex];
if (Instance.ArrShaderEntries.Num() == 0)
{
return FText::FromString(TEXT("-Compiling-Shaders-"));
}
return FText::FromString(Instance.ComboBoxSelectedEntry.Text);
}
void FShaderPlatformSettings::OnShaderViewComboSelectionChanged(TSharedPtr<FMaterialShaderEntry> Item, EMaterialQualityLevel::Type QualityType, const int32 InstanceIndex)
{
if (Item.IsValid())
{
auto& Instance = PlatformData[QualityType].Instances[InstanceIndex];
Instance.ComboBoxSelectedEntry = *Item.Get();
Instance.bUpdateShaderCode = true;
}
}
FText FShaderPlatformSettings::GetShaderCode(const EMaterialQualityLevel::Type QualityType, const int32 InstanceIndex)
{
if (PlatformData[QualityType].Instances.IsEmpty())
{
return FText::GetEmpty();
}
check(InstanceIndex >= 0 && InstanceIndex < PlatformData[QualityType].Instances.Num());
auto& Instance = PlatformData[QualityType].Instances[InstanceIndex];
// if there were no change to the material return the cached shader code
if (!Instance.bUpdateShaderCode)
{
return Instance.ShaderCode;
}
Instance.ShaderCode = LOCTEXT("ShaderCodeMsg", "Shader code compiling or not available!");
FMaterialResource *Resource = Instance.MaterialResourcesStats;
const FMaterialShaderMap* MaterialShaderMap = Resource->GetGameThreadShaderMap();
const bool bCompilationFinished = Resource->IsCompilationFinished() && (MaterialShaderMap != nullptr);
// check if shader compilation is done and extract shader code
if (bCompilationFinished)
{
TMap<FShaderId, TShaderRef<FShader>> ShaderMap;
MaterialShaderMap->GetShaderList(ShaderMap);
const FShaderId& ShaderId = Instance.ComboBoxSelectedEntry.ShaderId;
const auto Entry = ShaderMap.Find(ShaderId);
if (Entry != nullptr)
{
const TShaderRef<FShader>& Shader = *Entry;
const FMemoryImageString* ShaderSource = MaterialShaderMap->GetShaderSource(Shader.GetVertexFactoryType(), Shader.GetType(), ShaderId.PermutationId);
if (ShaderSource != nullptr)
{
Instance.bUpdateShaderCode = false;
Instance.ShaderCode = FText::FromString(*ShaderSource);
}
}
}
return Instance.ShaderCode;
}
/** returns all shaders' stats concatenated together*/
FString FShaderPlatformSettings::GetShadersStats() const
{
static FString ExtraLine("---------------------------------------------\n");
FString ShadersStats;
for (const FPlatformData& LevelData : PlatformData)
{
for (int32 ShaderType = 0; ShaderType < (int32)ERepresentativeShader::Num; ++ShaderType)
{
for (const FInstanceData& InstanceData : LevelData.Instances)
{
if (InstanceData.ShaderStatsInfo.ShaderInstructionCount.IsEmpty())
{
continue;
}
if (auto* Count = InstanceData.ShaderStatsInfo.ShaderInstructionCount.Find((ERepresentativeShader)ShaderType))
{
ShadersStats += FString("\n") + ExtraLine + FMaterialStatsUtils::RepresentativeShaderTypeToString((ERepresentativeShader)ShaderType);
ShadersStats += Count->StrDescriptionLong;
}
}
}
}
return ShadersStats;
}
void FShaderPlatformSettings::AllocateMaterialResources()
{
ClearResources();
const ERHIFeatureLevel::Type TargetFeatureLevel = GetMaxSupportedFeatureLevel(PlatformShaderID);
for (int32 QualityLevelIndex = 0; QualityLevelIndex < EMaterialQualityLevel::Num; QualityLevelIndex++)
{
auto& Data = PlatformData[QualityLevelIndex].Instances.AddDefaulted_GetRef();
Data.MaterialResourcesStats = new FMaterialResourceStats();
Data.MaterialResourcesStats->SetMaterial(Material, MaterialInstance, TargetFeatureLevel, (EMaterialQualityLevel::Type)QualityLevelIndex);
Data.ShaderStatsInfo.Reset();
Data.bNeedShaderRecompilation = true;
for (int32 i = 0; i < DerivedMaterialInstances.Num(); ++i)
{
auto& Data2 = PlatformData[QualityLevelIndex].Instances.AddDefaulted_GetRef();
Data2.MaterialResourcesStats = new FMaterialResourceStats();
Data2.MaterialResourcesStats->SetMaterial(DerivedMaterialInstances[i]->GetBaseMaterial(), DerivedMaterialInstances[i], TargetFeatureLevel, (EMaterialQualityLevel::Type)QualityLevelIndex);
Data2.ShaderStatsInfo.Reset();
Data2.bNeedShaderRecompilation = true;
}
// ensure we start compiling shaders soon due to bNeedShaderRecompilation being set above
PlatformData[QualityLevelIndex].LastTimeCompilationRequested = 0.0;
}
}
void FShaderPlatformSettings::SetMaterial(UMaterial *InBaseMaterial, UMaterialInstance *InBaseMaterialInstance, const TArray<TObjectPtr<UMaterialInstance>>& InDerivedMaterialInstances)
{
bool bReallocate = false;
if (InBaseMaterial != nullptr && InBaseMaterialInstance == nullptr && Material != InBaseMaterial)
{
Material = InBaseMaterial;
MaterialInstance = nullptr;
bReallocate = true;
}
if (InBaseMaterial == nullptr &&
InBaseMaterialInstance != nullptr &&
(MaterialInstance != InBaseMaterialInstance || Material != InBaseMaterialInstance->GetMaterial())) // If the Material Instance changed or its parent changed
{
Material = InBaseMaterialInstance->GetMaterial();
MaterialInstance = InBaseMaterialInstance;
bReallocate = true;
}
if (DerivedMaterialInstances != InDerivedMaterialInstances)
{
// TODO avoid copy
DerivedMaterialInstances = InDerivedMaterialInstances;
bReallocate = true;
}
if (bReallocate)
{
AllocateMaterialResources();
}
}
bool FShaderPlatformSettings::CheckShaders(bool bIgnoreCooldown)
{
if (Material == nullptr)
{
return false;
}
bool bRefreshStatUI = false;
// don't refresh stats too often
const double kMinimumTimeBetweenCompilationsSeconds = 1.5;
const double CurrentTime = FPlatformTime::Seconds();
// check and triggers shader recompilation if needed
for (int32 QualityLevelIndex = 0; QualityLevelIndex < EMaterialQualityLevel::Num; QualityLevelIndex++)
{
auto &Data = PlatformData[QualityLevelIndex];
const bool bNeedsShaders = (bPresentInGrid && Data.bExtractStats) || Data.bExtractCode;
const bool bCooledDown = bIgnoreCooldown || (CurrentTime - Data.LastTimeCompilationRequested) > kMinimumTimeBetweenCompilationsSeconds;
bool bTriggeredCompilation = false;
for (int32 InstanceIndex = 0; InstanceIndex < Data.Instances.Num(); ++InstanceIndex)
{
auto& Instance = Data.Instances[InstanceIndex];
if (Instance.bNeedShaderRecompilation && bNeedsShaders)
{
bRefreshStatUI = true;
// reset even if we don't immediately recompile to improve UI feedback time
Instance.ShaderStatsInfo.Reset();
if (bCooledDown)
{
Instance.MaterialResourcesStats->CancelCompilation();
Material->UpdateCachedExpressionData();
if (MaterialInstance != nullptr)
{
MaterialInstance->UpdateCachedData();
}
TMap<FName, TArray<FMaterialStatsUtils::FRepresentativeShaderInfo>> ShaderTypeNamesAndDescriptions;
FMaterialStatsUtils::GetRepresentativeShaderTypesAndDescriptions(ShaderTypeNamesAndDescriptions, Instance.MaterialResourcesStats);
TArray<const FVertexFactoryType*> VFTypes;
TArray<const FShaderPipelineType*> PipelineTypes;
TArray<const FShaderType*> ShaderTypes;
for (auto& DescriptionPair : ShaderTypeNamesAndDescriptions)
{
const FVertexFactoryType* VFType = FindVertexFactoryType(DescriptionPair.Key);
check(VFType);
auto& DescriptionArray = DescriptionPair.Value;
for (const FMaterialStatsUtils::FRepresentativeShaderInfo& ShaderInfo : DescriptionArray)
{
const FShaderType* ShaderType = FindShaderTypeByName(ShaderInfo.ShaderName);
// in compile only setting we only care if derived MI's are compiling at all
// so we only take one shader combination not to slow down material stats window too much
// it's likely sufficient to treat first shader as most complex, otherwise we need to refactor FindShaderTypeByName
if (Instance.bOnlyCompileMostComplexShader && VFTypes.Num() >= 1)
{
break;
}
if (ShaderType && VFType)
{
VFTypes.Add(VFType);
ShaderTypes.Add(ShaderType);
PipelineTypes.Add(nullptr);
}
}
}
// Prepare the resource for compilation, but don't compile the completed shader map.
const bool bSuccess = Instance.MaterialResourcesStats->CacheShaders(PlatformShaderID, EMaterialShaderPrecompileMode::None);
if (bSuccess)
{
// Compile just the types we want.
Instance.MaterialResourcesStats->CacheGivenTypes(PlatformShaderID, VFTypes, PipelineTypes, ShaderTypes);
}
Instance.bCompilingShaders = true;
Instance.bUpdateShaderCode = true;
Instance.bNeedShaderRecompilation = false;
bTriggeredCompilation = true;
}
}
}
if (bTriggeredCompilation)
{
Data.LastTimeCompilationRequested = CurrentTime;
}
}
return bRefreshStatUI;
}
bool FShaderPlatformSettings::Update()
{
bool bRetValue = CheckShaders(false);
// if a shader compilation has been requested check if completed and extract shader names needed by code viewer combo-box
for (int32 QualityLevelIndex = 0; QualityLevelIndex < EMaterialQualityLevel::Num; ++QualityLevelIndex)
{
auto &Data = PlatformData[QualityLevelIndex];
for (int32 InstanceIndex = 0; InstanceIndex < Data.Instances.Num(); ++InstanceIndex)
{
auto& Instance = Data.Instances[InstanceIndex];
if (Instance.bCompilingShaders)
{
FMaterialResource* Resource = Instance.MaterialResourcesStats;
// if compilation is complete extract the list of compiled shader names
const bool bCompilationFinished = Resource->IsCompilationFinished();
if (bCompilationFinished)
{
Instance.bCompilingShaders = false;
Instance.bUpdateShaderCode = true;
const FMaterialShaderMap* MaterialShaderMap = Resource->GetGameThreadShaderMap();
if (MaterialShaderMap != nullptr)
{
TMap<FShaderId, TShaderRef<FShader>> ShaderMap;
MaterialShaderMap->GetShaderList(ShaderMap);
Instance.ArrShaderEntries.Empty();
for (const auto& Entry : ShaderMap)
{
FShaderType* EntryShader = Entry.Value.GetType();
FVertexFactoryType* VertexFactory = Entry.Value.GetVertexFactoryType();
FString ShaderName = FString::Printf(TEXT("%s/%s"), VertexFactory ? VertexFactory->GetName() : TEXT("NullVF"), EntryShader->GetName());
Instance.ArrShaderEntries.Add(MakeShareable(new FMaterialShaderEntry{ Entry.Key, ShaderName }));
}
if (Instance.ArrShaderEntries.Num() > 0)
{
Instance.ArrShaderEntries.Sort([](const TSharedPtr<FMaterialShaderEntry>& First, const TSharedPtr<FMaterialShaderEntry>& Second)
{
return First->Text < Second->Text;
});
Instance.ComboBoxSelectedEntry = *Instance.ArrShaderEntries[0];
}
}
FMaterialStatsUtils::ExtractMatertialStatsInfo(PlatformShaderID, Instance.ShaderStatsInfo, Resource);
Instance.bNeedToWarnAboutCompilationErrors = Resource->GetCompileErrors().Num() > 0;
bRetValue = true;
}
}
}
}
return bRetValue;
}
bool FShaderPlatformSettings::CachePendingShaders()
{
const bool bNeedsGridRefresh = CheckShaders(true);
FString MaterialName;
TArray<FMaterial*> MaterialsToCompile;
for (int32 QualityLevelIndex = 0; QualityLevelIndex < EMaterialQualityLevel::Num; QualityLevelIndex++)
{
for (int32 InstanceIndex = 0; InstanceIndex < PlatformData[QualityLevelIndex].Instances.Num(); ++InstanceIndex)
{
auto& Instance = PlatformData[QualityLevelIndex].Instances[InstanceIndex];
if (Instance.bCompilingShaders)
{
if (MaterialName.IsEmpty())
{
MaterialName = Instance.MaterialResourcesStats->GetFriendlyName();
}
MaterialsToCompile.Add(Instance.MaterialResourcesStats);
}
}
}
FMaterial::FinishCompilation(*MaterialName, MaterialsToCompile);
return bNeedsGridRefresh;
}
/*end FShaderPlatformSettings functions*/
/***********************************************************************************************************************/
/***********************************************************************************************************************/
/*begin FMaterialStats functions*/
FMaterialStats::~FMaterialStats()
{
SaveSettings();
}
void FMaterialStats::Initialize(IMaterialEditor* InMaterialEditor, const bool bShowMaterialInstancesMenu, const bool bAllowIgnoringCompilationErrors)
{
MaterialEditor = InMaterialEditor;
StatsGrid = MakeShareable(new FMaterialStatsGrid(AsShared()));
BuildShaderPlatformDB(bAllowIgnoringCompilationErrors);
LoadSettings(bAllowIgnoringCompilationErrors);
StatsGrid->BuildGrid();
GridStatsWidget = SNew(SMaterialEditorStatsWidget)
.MaterialStatsWPtr(SharedThis(this))
.ShowMaterialInstancesMenu(bShowMaterialInstancesMenu)
.AllowIgnoringCompilationErrors(bAllowIgnoringCompilationErrors);
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
FMessageLogInitializationOptions LogOptions;
// Show Pages so that user is never allowed to clear log messages
LogOptions.bShowPages = false;
LogOptions.bShowFilters = false; //TODO - Provide custom filters? E.g. "Critical Errors" vs "Errors" needed for materials?
LogOptions.bAllowClear = false;
LogOptions.MaxPageCount = 1;
OldStatsListing = MessageLogModule.CreateLogListing("MaterialEditorStats", LogOptions);
OldStatsWidget = MessageLogModule.CreateLogListingWidget(OldStatsListing.ToSharedRef());
auto ToolkitCommands = MaterialEditor->GetToolkitCommands();
const FMaterialEditorCommands& Commands = FMaterialEditorCommands::Get();
ToolkitCommands->MapAction(
Commands.TogglePlatformStats,
FExecuteAction::CreateSP(this, &FMaterialStats::ToggleStats),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FMaterialStats::IsShowingStats));
ToolkitCommands->MapAction(
Commands.ToggleMaterialStats,
FExecuteAction::CreateSP(this, &FMaterialStats::ToggleOldStats),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FMaterialStats::IsShowingOldStats));
}
void FMaterialStats::LoadSettings(const bool bAllowIgnoringCompilationErrors)
{
Options = NewObject<UMaterialStatsOptions>();
for (const auto& PlatformEntry : ShaderPlatformStatsDB)
{
const EShaderPlatform PlatformID = PlatformEntry.Key;
const bool bPresentInGrid = !!Options->bPlatformUsed[PlatformID];
auto Platform = PlatformEntry.Value;
Platform->SetPresentInGrid(bPresentInGrid);
}
for (int32 i = 0; i < EMaterialQualityLevel::Num; ++i)
{
const bool bUsed = !!Options->bMaterialQualityUsed[i];
bArrStatsQualitySelector[(EMaterialQualityLevel::Type)i] = bUsed || bArrStatsQualitySelectorAlwaysOn[(EMaterialQualityLevel::Type)i];
for (const auto& PlatformEntry : ShaderPlatformStatsDB)
{
TSharedPtr<FShaderPlatformSettings> SomePlatform = PlatformEntry.Value;
if (SomePlatform.IsValid())
{
SomePlatform->SetExtractStatsFlag((EMaterialQualityLevel::Type)i, bUsed);
}
}
}
MaterialStatsDerivedMIOption = Options->MaterialStatsDerivedMIOption;
// force compilation of derived instances if we're not allowed to ignore compilation errors
if (!bAllowIgnoringCompilationErrors && (MaterialStatsDerivedMIOption == EMaterialStatsDerivedMIOption::Ignore || MaterialStatsDerivedMIOption == EMaterialStatsDerivedMIOption::InvalidOrMax))
{
MaterialStatsDerivedMIOption = EMaterialStatsDerivedMIOption::CompileOnly;
}
}
void FMaterialStats::SaveSettings()
{
for (const auto& PlatformEntry : ShaderPlatformStatsDB)
{
const EShaderPlatform PlatformID = PlatformEntry.Key;
const auto Platform = PlatformEntry.Value;
const bool bPresentInGrid = Platform->IsPresentInGrid();
Options->bPlatformUsed[PlatformID] = bPresentInGrid ? 1 : 0;
}
for (int32 i = 0; i < EMaterialQualityLevel::Num; ++i)
{
const bool bQualityPresent = GetStatsQualityFlag((EMaterialQualityLevel::Type)i);
Options->bMaterialQualityUsed[i] = bQualityPresent ? 1 : 0;
}
Options->MaterialStatsDerivedMIOption = MaterialStatsDerivedMIOption;
Options->SaveConfig();
}
void FMaterialStats::SetShowStats(const bool bValue)
{
bShowStats = bValue;
// open/close stats tab
DisplayStatsGrid(bShowStats);
GetGridStatsWidget()->RequestRefresh();
}
void FMaterialStats::SetShowOldStats(const bool bValue)
{
bShowOldStats = bValue;
// open/close stats tab
DisplayOldStats(bShowOldStats);
}
void FMaterialStats::ToggleStats()
{
// Toggle the showing of material stats each time the user presses the show stats button
SetShowStats(!bShowStats);
}
void FMaterialStats::ToggleOldStats()
{
// Toggle the showing of material stats each time the user presses the show stats button
SetShowOldStats(!bShowOldStats);
}
void FMaterialStats::DisplayOldStats(const bool bShow)
{
if (bShow)
{
MaterialEditor->GetTabManager()->TryInvokeTab(OldStatsTabId);
}
else if (!bShowOldStats && OldStatsTab.IsValid())
{
OldStatsTab.Pin()->RequestCloseTab();
}
}
void FMaterialStats::DisplayStatsGrid(const bool bShow)
{
if (bShow)
{
MaterialEditor->GetTabManager()->TryInvokeTab(StatsTabId);
}
else if (!bShowStats && StatsTab.IsValid())
{
StatsTab.Pin()->RequestCloseTab();
}
}
void FMaterialStats::RefreshStatsGrid()
{
GetGridStatsWidget()->RequestRefresh();
}
void FMaterialStats::BuildShaderPlatformDB(const bool bAllowIgnoringCompilationErrors)
{
// set High quality level as always on if we're not allowed to ignore compilation errors in UI
// this will not allow to disable this particular quality level
if (!bAllowIgnoringCompilationErrors)
{
bArrStatsQualitySelectorAlwaysOn[EMaterialQualityLevel::High] = true;
bArrStatsQualitySelector[EMaterialQualityLevel::High] = true;
}
#if PLATFORM_WINDOWS
// DirectX
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_PCD3D_SM5, TEXT("DirectX SM5"), true, TEXT("Desktop, DirectX, Shader Model 5"));
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_PCD3D_SM6, TEXT("DirectX SM6"), true, TEXT("Desktop, DirectX, Shader Model 6"), !bAllowIgnoringCompilationErrors);
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_PCD3D_ES3_1, TEXT("DirectX Mobile"), true, TEXT("Desktop, DirectX, Mobile"), !bAllowIgnoringCompilationErrors);
#endif
// Vulkan
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_VULKAN_SM5, TEXT("Vulkan SM5"), true, TEXT("Desktop, Vulkan, Shader Model 5"));
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_VULKAN_SM6, TEXT("Vulkan SM6"), true, TEXT("Desktop, Vulkan, Shader Model 6"));
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_VULKAN_PCES3_1, TEXT("Vulkan Mobile"), true, TEXT("Desktop, Vulkan, Mobile"));
// Android
AddShaderPlatform(EPlatformCategoryType::Android, SP_OPENGL_ES3_1_ANDROID, TEXT("Android GLES Mobile"), true, TEXT("Android, OpenGLES Mobile"));
AddShaderPlatform(EPlatformCategoryType::Android, SP_VULKAN_ES3_1_ANDROID, TEXT("Android Vulkan Mobile"), true, TEXT("Android, Vulkan Mobile"));
AddShaderPlatform(EPlatformCategoryType::Android, SP_VULKAN_SM5_ANDROID, TEXT("Android Vulkan SM5"), true, TEXT("Android, Vulkan SM5"));
#if PLATFORM_MAC
// macOS
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_METAL_SM5, TEXT("Metal SM5"), true, TEXT("macOS, Metal, Shader Model 5"));
AddShaderPlatform(EPlatformCategoryType::Desktop, SP_METAL_SM6, TEXT("Metal SM6"), true, TEXT("macOS, Metal, Shader Model 6"), !bAllowIgnoringCompilationErrors);
#endif
// iOS
AddShaderPlatform(EPlatformCategoryType::IOS, SP_METAL_ES3_1_IOS, TEXT("IOS Metal ES3_1"), true, TEXT("iOS, Metal, Mobile"));
AddShaderPlatform(EPlatformCategoryType::IOS, SP_METAL_SM5_IOS, TEXT("IOS Metal SM5"), true, TEXT("iOS, Metal, Shader Model 5"));
AddShaderPlatform(EPlatformCategoryType::IOS, SP_METAL_SIM, TEXT("Metal Simulator"), true, TEXT("iOS, Metal, Mobile"));
ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
// Add platform extensions
for (int32 StaticPlatform = SP_StaticPlatform_First; StaticPlatform <= SP_StaticPlatform_Last; ++StaticPlatform)
{
EShaderPlatform ShaderPlatform = (EShaderPlatform)StaticPlatform;
if (FDataDrivenShaderPlatformInfo::IsValid(ShaderPlatform))
{
bool bIsConsole = FDataDrivenShaderPlatformInfo::GetIsConsole(ShaderPlatform);
const FName ShaderFormat = LegacyShaderPlatformToShaderFormat(ShaderPlatform);
if (TPM.FindShaderFormat(ShaderFormat) != nullptr)
{
const FName PlatformName = ShaderPlatformToPlatformName(ShaderPlatform);
AddShaderPlatform(bIsConsole ? EPlatformCategoryType::Console : EPlatformCategoryType::Desktop, ShaderPlatform, PlatformName, true, PlatformName.ToString());
}
}
}
}
TSharedPtr<FShaderPlatformSettings> FMaterialStats::AddShaderPlatform(const EPlatformCategoryType PlatformType, const EShaderPlatform PlatformID, const FName PlatformName, const bool bAllowCodeView, const FString& Description, const bool bAlwaysOn)
{
if (!FDataDrivenShaderPlatformInfo::IsValid(PlatformID))
{
return TSharedPtr<FShaderPlatformSettings>();
}
ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
const IShaderFormat* ShaderFormat = TPM.FindShaderFormat(LegacyShaderPlatformToShaderFormat(PlatformID));
const bool bAllowPresenceInGrid = ShaderFormat ? ShaderFormat->CanCompileBinaryShaders() : false;
TSharedPtr<FShaderPlatformSettings> PlatformPtr = MakeShareable(new FShaderPlatformSettings(PlatformType, PlatformID, PlatformName, bAllowPresenceInGrid, bAllowCodeView, Description, bAlwaysOn));
ShaderPlatformStatsDB.Add(PlatformID, PlatformPtr);
auto& ArrayPlatforms = PlatformTypeDB.FindOrAdd(PlatformType);
ArrayPlatforms.Add(PlatformPtr);
for (int32 i = 0; i < EMaterialQualityLevel::Num; ++i)
{
PlatformPtr->SetExtractStatsFlag((EMaterialQualityLevel::Type)i, bAlwaysOn && bArrStatsQualitySelectorAlwaysOn[i]);
}
return PlatformPtr;
}
void FMaterialStats::SignalMaterialChanged()
{
ExtractHLSLCode();
for (auto Entry : ShaderPlatformStatsDB)
{
for (int32 i = 0; i < EMaterialQualityLevel::Num; ++i)
Entry.Value->SetNeedShaderCompilation((EMaterialQualityLevel::Type)i, true, MaterialStatsDerivedMIOption == EMaterialStatsDerivedMIOption::CompileOnly);
}
}
bool FMaterialStats::SwitchShaderPlatformUseStats(const EShaderPlatform PlatformID)
{
bool bRetValue = false;
auto* Item = ShaderPlatformStatsDB.Find(PlatformID);
if (Item != nullptr)
{
bRetValue = (*Item)->FlipPresentInGrid();
GetStatsGrid()->OnAddOrRemovePlatform(*Item);
SaveSettings();
}
return bRetValue;
}
void FMaterialStats::SetStatusQualityFlag(const EMaterialQualityLevel::Type QualityLevel, const bool bValue)
{
check(QualityLevel < EMaterialQualityLevel::Num);
bArrStatsQualitySelector[QualityLevel] = bValue || bArrStatsQualitySelectorAlwaysOn[QualityLevel];
for (const auto& PlatformEntry : ShaderPlatformStatsDB)
{
TSharedPtr<FShaderPlatformSettings> SomePlatform = PlatformEntry.Value;
if (SomePlatform.IsValid())
{
SomePlatform->SetExtractStatsFlag(QualityLevel, bValue);
}
}
SaveSettings();
}
bool FMaterialStats::SwitchStatsQualityFlag(EMaterialQualityLevel::Type Quality)
{
check(Quality < EMaterialQualityLevel::Num);
const bool bValue = !bArrStatsQualitySelector[Quality];
SetStatusQualityFlag(Quality, bValue);
return bValue;
}
void FMaterialStats::SetMaterialStatsDerivedMIOption(const EMaterialStatsDerivedMIOption value)
{
MaterialStatsDerivedMIOption = value;
if (MaterialEditor != nullptr)
{
MaterialEditor->RefreshStatsMaterials();
}
SaveSettings();
}
void FMaterialStats::SetShaderPlatformUseCodeView(const EShaderPlatform PlatformID, const EMaterialQualityLevel::Type Quality, const bool bValue)
{
auto* Item = ShaderPlatformStatsDB.Find(PlatformID);
if (Item != nullptr)
{
(*Item)->SetCodeViewNeeded(Quality, bValue);
}
}
FName FMaterialStats::GetPlatformName(const EShaderPlatform InEnumValue) const
{
FName PlatformName = NAME_None;
auto* Entry = ShaderPlatformStatsDB.Find(InEnumValue);
if (Entry != nullptr && Entry->IsValid())
{
PlatformName = (*Entry)->GetPlatformName();
}
return PlatformName;
}
EShaderPlatform FMaterialStats::GetShaderPlatformID(const FName InName) const
{
for (auto Entry : ShaderPlatformStatsDB)
{
if (Entry.Value.Get()->GetPlatformName() == InName)
{
return Entry.Key;
}
}
return SP_NumPlatforms;
}
TSharedPtr<FShaderPlatformSettings> FMaterialStats::GetPlatformSettings(const EShaderPlatform PlatformID)
{
auto* Entry = ShaderPlatformStatsDB.Find(PlatformID);
if (Entry == nullptr)
{
return TSharedPtr<FShaderPlatformSettings>(nullptr);
}
return *Entry;
}
TSharedPtr<FShaderPlatformSettings> FMaterialStats::GetPlatformSettings(const FName PlatformName)
{
const EShaderPlatform PlatformID = GetShaderPlatformID(PlatformName);
return GetPlatformSettings(PlatformID);
}
FText FMaterialStats::GetShaderCode(const EShaderPlatform PlatformID, const EMaterialQualityLevel::Type QualityType, const int32 InstanceIndex)
{
auto* Entry = ShaderPlatformStatsDB.Find(PlatformID);
if (Entry == nullptr)
{
return FText::FromString(TEXT("Shader code compiling or not available!"));
}
return (*Entry)->GetShaderCode(QualityType, InstanceIndex);
}
FString FMaterialStats::GetShadersStats() const
{
static FString ExtraLine("=============================================\n");
FString ShadersStats;
int Index = 0;
for (const auto& MapEntry : ShaderPlatformStatsDB)
{
TSharedPtr<FShaderPlatformSettings> PlatformPtr = MapEntry.Value;
FString PlatformShadersStats = PlatformPtr->GetShadersStats();
if (PlatformShadersStats.Len())
{
ShadersStats += (Index ? FString("\n\n") : FString("")) + ExtraLine + ExtraLine + PlatformPtr->GetPlatformName().GetPlainNameString();
ShadersStats += PlatformShadersStats;
++Index;
}
}
return ShadersStats;
}
void FMaterialStats::Update()
{
const bool bNeedsUpdate = IsShowingStats() || IsCodeViewWindowActive();
// don't update materials if we don't need to verify derived MI's
// this is to preserve old behaviour of speeding up material editor by hiding platform stats
if (!bNeedsUpdate && !GetProvideDerivedMIFlag())
{
return;
}
// otherwise always update material compilation info so we can check if shaders are compiling at material editor save
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto PlatformStats = Entry.Value;
bNeedsGridRefresh |= PlatformStats->Update();
}
if (!bNeedsUpdate)
{
return;
}
if (bNeedsGridRefresh)
{
// in compile only mode the amount of columns might change if some of MI's fail to compile
if (GetMaterialStatsDerivedMIOption() == EMaterialStatsDerivedMIOption::CompileOnly)
{
auto GridPtr = GetGridStatsWidget();
if (GridPtr.IsValid())
{
GridPtr->OnColumnNumChanged();
}
}
else
{
GetStatsGrid()->OnShaderChanged();
}
// update any dependent tabs
RefreshDependentTabs.Broadcast();
bNeedsGridRefresh = false;
}
ComputeGridWarnings();
}
void FMaterialStats::CacheAndCompilePendingShaders()
{
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto PlatformStats = Entry.Value;
bNeedsGridRefresh |= PlatformStats->CachePendingShaders();
}
}
void FMaterialStats::SetMaterial(UMaterial *InMaterial, const TArray<TObjectPtr<UMaterialInstance>>& InDerivedMaterialInstances)
{
if (MaterialInterface != InMaterial || DerivedMaterialInstances != InDerivedMaterialInstances)
{
MaterialInterface = InMaterial;
DerivedMaterialInstances = InDerivedMaterialInstances;
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto& Platform = Entry.Value;
if (Platform.IsValid())
{
Platform->SetMaterial(InMaterial, nullptr, DerivedMaterialInstances);
}
}
auto GridPtr = GetGridStatsWidget();
if (GridPtr.IsValid())
{
GridPtr->OnColumnNumChanged();
}
}
}
void FMaterialStats::SetMaterial(UMaterialInstance *InMaterialInstance)
{
MaterialInterface = InMaterialInstance;
DerivedMaterialInstances.Empty();
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto& Platform = Entry.Value;
if (Platform.IsValid())
{
Platform->SetMaterial(nullptr, InMaterialInstance, DerivedMaterialInstances);
}
}
auto GridPtr = GetGridStatsWidget();
if (GridPtr.IsValid())
{
GridPtr->OnColumnNumChanged();
}
}
bool FMaterialStats::AnyNewCompilationErrors(const int32 StartingFromInstanceIndex)
{
bool bNewCompilationErrors = false;
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto& Platform = Entry.Value;
if (Platform.IsValid())
{
for (int32 QualityLevel = 0; QualityLevel < EMaterialQualityLevel::Num; ++QualityLevel)
{
for (int32 InstanceIndex = StartingFromInstanceIndex; InstanceIndex < Platform->GetPlatformData(static_cast<EMaterialQualityLevel::Type>(QualityLevel)).Instances.Num(); ++InstanceIndex)
{
auto &Data = Platform->GetInstanceData(static_cast<EMaterialQualityLevel::Type>(QualityLevel), InstanceIndex);
bNewCompilationErrors |= Data.bNeedToWarnAboutCompilationErrors;
Data.bNeedToWarnAboutCompilationErrors = false;
}
}
}
}
return bNewCompilationErrors;
}
// Generates a string of line numbers by counting the number of new-line characters in input source code.
static FText GenerateLineNoTextForCodeView(const FString& InSourceCode)
{
FString LineNumberStr;
int32 LineNo = 1;
for (TCHAR Chr : InSourceCode)
{
if (Chr == TEXT('\n'))
{
LineNumberStr += FString::Printf(TEXT("% 6d\n"), LineNo);
++LineNo;
}
}
return FText::FromString(MoveTemp(LineNumberStr));
}
TSharedRef<SWidget> FMaterialStats::BuildShaderCodeWidget(FText TextLabel, TFunction<FText(void)>&& InShaderCodeCallback, TFunction<FReply(void)>&& InCopyCodeCallback, TAttribute<EVisibility> InVisibilityAttribute)
{
TSharedRef<SVerticalBox> Box = SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SVerticalBox)
// Copy Button
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(SButton)
.Text(LOCTEXT("CopyHLSLButton", "Copy"))
.ToolTipText(LOCTEXT("CopyHLSLButtonToolTip", "Copies all HLSL code to the clipboard."))
.ContentPadding(3)
.OnClicked_Lambda(InCopyCodeCallback)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(STextBlock)
.Text(TextLabel)
]
]
// Separator
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SSeparator)
]
]
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SScrollBox)
+ SScrollBox::Slot().Padding(5)
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Horizontal)
+ SSplitter::Slot()
.Resizable(false)
.SizeRule(SSplitter::SizeToContent)
[
SNew(STextBlock)
.Text_Lambda([InShaderCodeCallback]()
{
return GenerateLineNoTextForCodeView(InShaderCodeCallback().ToString());
})
.TextStyle(FAppStyle::Get(), "MessageLog")
.ColorAndOpacity(FLinearColor::Gray)
]
+ SSplitter::Slot()
[
SNew(SMultiLineEditableText)
.Text_Lambda([InShaderCodeCallback]() { return InShaderCodeCallback(); })
.TextStyle(FAppStyle::Get(), "MessageLog")
.IsReadOnly(true)
.Marshaller(SyntaxHighlighter)
]
]
];
Box->SetVisibility(InVisibilityAttribute);
return Box;
}
TSharedRef<class SDockTab> FMaterialStats::SpawnTab_HLSLCode(const class FSpawnTabArgs& Args)
{
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("HLSLCodeTitle", "HLSL Code"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Vertical)
+SSplitter::Slot()
[
SNew(SSplitter)
.Orientation(EOrientation::Orient_Horizontal)
+SSplitter::Slot()
[
BuildShaderCodeWidget(LOCTEXT("CurrentTranslatorLabel", "New Translator HLSL Code"),
[Code = &HLSLCode]() { return FText::FromString(*Code); },
[Code = &HLSLCode]() {
FPlatformApplicationMisc::ClipboardCopy(**Code);
return FReply::Handled();
},
MakeAttributeLambda([Code = &HLSLCode]() { return (*Code).Len() == 0 ? EVisibility::Collapsed : EVisibility::Visible; })
)
]
+SSplitter::Slot()
[
BuildShaderCodeWidget(LOCTEXT("PreviousTranslatorLabel", "HLSL Code"),
[Code = &PreviousTranslatorHLSLCode]() { return FText::FromString(*Code); },
[Code = &PreviousTranslatorHLSLCode]() {
FPlatformApplicationMisc::ClipboardCopy(**Code);
return FReply::Handled();
},
MakeAttributeLambda([Code = &PreviousTranslatorHLSLCode]() { return (*Code).Len() == 0 ? EVisibility::Collapsed : EVisibility::Visible; })
)
]
]
+SSplitter::Slot()
[
BuildShaderCodeWidget(LOCTEXT("MaterialIRLabel", "Intermediate Representation"),
[Code = &MaterialIR]() { return FText::FromString(*Code); },
[Code = &MaterialIR]() {
FPlatformApplicationMisc::ClipboardCopy(**Code);
return FReply::Handled();
},
MakeAttributeLambda([Code = &MaterialIR]() { return (*Code).Len() == 0 ? EVisibility::Collapsed : EVisibility::Visible; })
)
]
]
];
HLSLTab = SpawnedTab;
ExtractHLSLCode();
return SpawnedTab;
}
TSharedRef<SDockTab> FMaterialStats::SpawnTab_ShaderCode(const FSpawnTabArgs& Args, const EShaderPlatform PlatformID, const EMaterialQualityLevel::Type QualityLevel, const int32 InstanceIndex)
{
SetShaderPlatformUseCodeView(PlatformID, QualityLevel, true);
const FString PlatformName = GetPlatformName(PlatformID).ToString();
const FString FullPlatformName = PlatformName + TEXT(" -- ") + FMaterialStatsUtils::MaterialQualityToString(QualityLevel);
TSharedPtr<FShaderPlatformSettings> PlatformPtr = GetPlatformSettings(PlatformID);
check(PlatformPtr.IsValid());
const TArray<TSharedPtr<FMaterialShaderEntry>> *ShaderEntriesPtr = PlatformPtr->GetShaderEntries(QualityLevel, InstanceIndex);
if (ShaderEntriesPtr == nullptr)
{
// SComboBox needs a valid pointer otherwise follow up SetItemsSource will fail.
static TArray<TSharedPtr<FMaterialShaderEntry>> EmptyShaderEntriesArray = {};
ShaderEntriesPtr = &EmptyShaderEntriesArray;
}
ensure(ShaderEntriesPtr != nullptr);
TSharedRef<SComboBox<TSharedPtr<FMaterialShaderEntry>>> ShaderBox = SNew(SComboBox<TSharedPtr<FMaterialShaderEntry>>)
.OptionsSource(ShaderEntriesPtr)
.OnGenerateWidget_Lambda([](TSharedPtr<FMaterialShaderEntry> Value) { return SNew(STextBlock).Text(FText::FromString(Value->Text)); })
.OnSelectionChanged_Lambda([PlatformPtr, QualityLevel, InstanceIndex](TSharedPtr<FMaterialShaderEntry> Item, ESelectInfo::Type SelectInfo) { PlatformPtr->OnShaderViewComboSelectionChanged(Item, QualityLevel, InstanceIndex); })
[
SNew(STextBlock)
.Text_Lambda([PlatformPtr, QualityLevel, InstanceIndex]() { return PlatformPtr->GetSelectedShaderViewComboText(QualityLevel, InstanceIndex); })
];
// Refresh ShaderBox when shaders are updated
FDelegateHandle ShaderBoxUpdater = RefreshDependentTabs.AddLambda([PlatformPtrWeakPtr = PlatformPtr.ToWeakPtr(), ShaderBoxWeakPtr = ShaderBox.ToWeakPtr(), QualityLevel, InstanceIndex]()
{
TSharedPtr<FShaderPlatformSettings> PlatformPtr = PlatformPtrWeakPtr.Pin();
TSharedPtr<SComboBox<TSharedPtr<FMaterialShaderEntry>>> ShaderBox = ShaderBoxWeakPtr.Pin();
if (PlatformPtr.IsValid() && ShaderBox.IsValid())
{
ShaderBox->SetItemsSource(PlatformPtr->GetShaderEntries(QualityLevel, InstanceIndex));
}
});
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.OnTabClosed_Lambda([MaterialStatsWeakPtr = this->AsWeak(), ShaderBoxUpdater](TSharedRef<SDockTab> Tab)
{
TSharedPtr<FMaterialStats> MaterialStats = MaterialStatsWeakPtr.Pin();
if (MaterialStats.IsValid() && ShaderBoxUpdater.IsValid())
{
MaterialStats->RefreshDependentTabs.Remove(ShaderBoxUpdater);
}
})
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SVerticalBox)
// Copy Button
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 2.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(FText::FromString(FullPlatformName))
]
]
// Separator
+ SVerticalBox::Slot()
.FillHeight(1)
[
SNew(SSeparator)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
ShaderBox
]
+ SVerticalBox::Slot()
.FillHeight(1)
[
PlatformPtr->GetShaderViewerScrollBox(QualityLevel).ToSharedRef()
]
];
PlatformPtr->SetCodeViewerTab(QualityLevel, SpawnedTab);
SpawnedTab->SetLabel(FText::FromString(PlatformName));
return SpawnedTab;
}
FName FMaterialStats::MakeTabName(const EPlatformCategoryType PlatformType, const EShaderPlatform ShaderPlatformType, const EMaterialQualityLevel::Type QualityLevel, const int32 InstanceIndex)
{
const FString TabName = FMaterialStatsUtils::GetPlatformTypeName(PlatformType) + FMaterialStatsUtils::ShaderPlatformTypeName(ShaderPlatformType) + FMaterialStatsUtils::MaterialQualityToString(QualityLevel) + FString::FromInt(InstanceIndex);
return FName(*TabName);
}
void FMaterialStats::BuildViewShaderCodeMenus()
{
auto TabManager = MaterialEditor->GetTabManager();
auto ParentCategoryRef = MaterialEditor->GetWorkspaceMenuCategory();
TSharedPtr<FWorkspaceItem> PlatformGroupMenuItem = ParentCategoryRef->AddGroup(LOCTEXT("ViewShaderCodePlatformsGroupMenu", "Shader Code"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "MaterialEditor.Tabs.HLSLCode"));
// Create the syntax highlighter
FHLSLSyntaxHighlighterMarshaller::FSyntaxTextStyle CodeStyle(
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Normal"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Operator"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Keyword"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.String"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Number"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Comment"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.PreProcessorKeyword"),
FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SyntaxHighlight.SourceCode.Error"));
SyntaxHighlighter = FHLSLSyntaxHighlighterMarshaller::Create(CodeStyle);
// add hlsl code viewer tab
TabManager->RegisterTabSpawner( HLSLCodeTabId, FOnSpawnTab::CreateSP(this, &FMaterialStats::SpawnTab_HLSLCode))
.SetDisplayName( LOCTEXT("HLSLCodeTab", "HLSL Code") )
.SetGroup( PlatformGroupMenuItem.ToSharedRef() )
.SetIcon( FSlateIcon(FAppStyle::GetAppStyleSetName(), "MaterialEditor.Tabs.HLSLCode") );
const int32 InstanceIndex = 0; // only work on base material
for (auto MapEntry : PlatformTypeDB)
{
const EPlatformCategoryType PlatformType = MapEntry.Key;
const FString PlatformName = FMaterialStatsUtils::GetPlatformTypeName(PlatformType);
TSharedPtr<FWorkspaceItem> PlatformMenuItem = PlatformGroupMenuItem->AddGroup(FText::FromString(PlatformName), FSlateIcon(FAppStyle::GetAppStyleSetName(), "MaterialEditor.Tabs.HLSLCode"));
const auto& ArrShaderPlatforms = MapEntry.Value;
for (int32 i = 0; i < ArrShaderPlatforms.Num(); ++i)
{
const auto& PlatformPtr = ArrShaderPlatforms[i];
if (!PlatformPtr.IsValid())
{
continue;
}
const EShaderPlatform PlatformID = PlatformPtr->GetPlatformShaderType();
if (PlatformID == SP_NumPlatforms || !PlatformPtr->IsCodeViewAllowed())
{
continue;
}
const FString ShaderPlatformName = PlatformPtr->GetPlatformName().ToString();
TSharedPtr<FWorkspaceItem> ShaderPlatformMenuItem = PlatformMenuItem->AddGroup(FText::FromString(ShaderPlatformName), FSlateIcon(FAppStyle::GetAppStyleSetName(), "MaterialEditor.Tabs.HLSLCode"));
for (int32 q = 0; q < EMaterialQualityLevel::Num; ++q)
{
const EMaterialQualityLevel::Type QualityLevel = (EMaterialQualityLevel::Type)q;
const FString MaterialQualityName = FMaterialStatsUtils::MaterialQualityToString(QualityLevel);
const FName TabName = MakeTabName(PlatformType, PlatformID, QualityLevel, InstanceIndex);
TabManager->RegisterTabSpawner(TabName, FOnSpawnTab::CreateSP(this, &FMaterialStats::SpawnTab_ShaderCode, PlatformID, QualityLevel, InstanceIndex))
.SetGroup(ShaderPlatformMenuItem.ToSharedRef())
.SetDisplayName(FText::FromString(MaterialQualityName));
TSharedRef<SScrollBox> CodeScrollBox = SNew(SScrollBox)
+ SScrollBox::Slot().Padding(5)
[
BuildShaderCodeWidget(LOCTEXT("ShaderCodeLabel", "Shader Code"),
[MaterialStats = TWeakPtr<FMaterialStats>(SharedThis(this)), PlatformID, QualityLevel]()
{
auto StatsPtr = MaterialStats.Pin();
if (StatsPtr.IsValid())
{
return StatsPtr->GetShaderCode(PlatformID, QualityLevel, InstanceIndex);
}
return FText::FromString(TEXT("Error reading shader code!"));
},
[MaterialStats = TWeakPtr<FMaterialStats>(SharedThis(this)), PlatformID, QualityLevel]() {
auto StatsPtr = MaterialStats.Pin();
if (StatsPtr.IsValid())
{
FText ShaderCode = StatsPtr->GetShaderCode(PlatformID, QualityLevel, InstanceIndex);
FPlatformApplicationMisc::ClipboardCopy(*ShaderCode.ToString());
return FReply::Handled();
}
return FReply::Unhandled();
},
MakeAttributeLambda([]() { return EVisibility::Visible; }))
];
PlatformPtr->GetPlatformData(QualityLevel).CodeScrollBox = CodeScrollBox;
}
}
}
}
bool FMaterialStats::IsCodeViewWindowActive() const
{
for (const auto& MapEntry : ShaderPlatformStatsDB)
{
TSharedPtr<FShaderPlatformSettings> PlatformPtr = MapEntry.Value;
for (int q = 0; q < EMaterialQualityLevel::Num; ++q)
{
if (PlatformPtr->GetCodeViewerTab((EMaterialQualityLevel::Type)q).IsValid())
{
return true;
}
}
}
return false;
}
TSharedRef<SDockTab> FMaterialStats::SpawnTab_Stats(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == StatsTabId);
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("Platform Stats", "Platform Stats"))
.OnTabClosed_Lambda([&bShowStats = bShowStats](TSharedRef<SDockTab>) { bShowStats = false; })
[
SNew(SBox)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialStats")))
[
GetGridStatsWidget().ToSharedRef()
]
];
StatsTab = SpawnedTab;
// like so because the material editor will automatically restore this tab if it was still opened when the editor shuts down
bShowStats = true;
return SpawnedTab;
}
TSharedRef<SDockTab> FMaterialStats::SpawnTab_OldStats(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == OldStatsTabId);
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("Stats", "Stats"))
.OnTabClosed_Lambda([&bShowStats = bShowOldStats](TSharedRef<SDockTab>) { bShowStats = false; })
[
SNew(SBox)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MaterialStats")))
[
OldStatsWidget.ToSharedRef()
]
];
OldStatsTab = SpawnedTab;
// like so because the material editor will automatically restore this tab if it was still opened when the editor shuts down
bShowOldStats = true;
return SpawnedTab;
}
void FMaterialStats::BuildStatsTab()
{
const auto ParentCategoryRef = MaterialEditor->GetWorkspaceMenuCategory();
auto TabManager = MaterialEditor->GetTabManager();
TabManager->RegisterTabSpawner(StatsTabId, FOnSpawnTab::CreateSP(this, &FMaterialStats::SpawnTab_Stats))
.SetDisplayName(LOCTEXT("StatsTab", "Platform Stats"))
.SetGroup(ParentCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::Get().GetStyleSetName(),"MaterialEditor.TogglePlatformStats.Tab"));
}
void FMaterialStats::BuildOldStatsTab()
{
const auto ParentCategoryRef = MaterialEditor->GetWorkspaceMenuCategory();
auto TabManager = MaterialEditor->GetTabManager();
TabManager->RegisterTabSpawner(OldStatsTabId, FOnSpawnTab::CreateSP(this, &FMaterialStats::SpawnTab_OldStats))
.SetDisplayName(LOCTEXT("OldStatsTab", "Stats"))
.SetGroup(ParentCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MaterialEditor.ToggleMaterialStats.Tab"));
}
void FMaterialStats::RegisterTabs()
{
BuildStatsTab();
BuildOldStatsTab();
BuildViewShaderCodeMenus();
}
void FMaterialStats::UnregisterTabs()
{
auto TabManager = MaterialEditor->GetTabManager();
const int32 InstanceIndex = 0;
for (auto MapEntry : PlatformTypeDB)
{
const EPlatformCategoryType PlatformType = MapEntry.Key;
TArray<TSharedPtr<FShaderPlatformSettings>>& ArrShaderPlatforms = MapEntry.Value;
for (int32 i = 0; i < ArrShaderPlatforms.Num(); ++i)
{
TSharedPtr<FShaderPlatformSettings> PlatformPtr = ArrShaderPlatforms[i];
if (!PlatformPtr.IsValid())
{
continue;
}
const EShaderPlatform ShaderPlatformID = PlatformPtr->GetPlatformShaderType();
for (int32 q = 0; q < EMaterialQualityLevel::Num; ++q)
{
const FName TabName = MakeTabName(PlatformType, ShaderPlatformID, (EMaterialQualityLevel::Type)q, InstanceIndex);
TabManager->UnregisterTabSpawner(TabName);
}
}
}
TabManager->UnregisterTabSpawner(StatsTabId);
TabManager->UnregisterTabSpawner(OldStatsTabId);
TabManager->UnregisterTabSpawner(HLSLCodeTabId);
}
void FMaterialStats::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(Options);
}
void FMaterialStats::ComputeGridWarnings()
{
auto GridPtr = GetGridStatsWidget();
if (!GridPtr.IsValid())
{
return;
}
// no need to update warnings that often
const double kMinimumTimeBetweenWarningsRefreshSeconds = 0.1;
const double CurrentTime = FPlatformTime::Seconds();
const bool bCooledDown = (CurrentTime - LastGridMessagesUpdate) > kMinimumTimeBetweenWarningsRefreshSeconds;
if (!bCooledDown)
{
return;
}
LastGridMessagesUpdate = CurrentTime;
TArray<FString> Messages;
TArray<bool> IsErrorMessage;
bool bAnyQualityPresent = false;
for (int32 q = 0; q < EMaterialQualityLevel::Num; ++q)
{
auto Quality = static_cast<EMaterialQualityLevel::Type>(q);
bAnyQualityPresent |= GetStatsQualityFlag(Quality);
}
if (!bAnyQualityPresent)
{
Messages.Emplace("No material quality selected. Please use the 'Settings' button and choose the desired quality level to be analyzed.");
IsErrorMessage.Add(false);
}
for (const auto& Entry : ShaderPlatformStatsDB)
{
auto& Platform = Entry.Value;
if (Platform.IsValid())
{
for (int32 QualityLevel = 0; QualityLevel < EMaterialQualityLevel::Num; ++QualityLevel)
{
for (int32 InstanceIndex = 0; InstanceIndex < Platform->GetPlatformData(static_cast<EMaterialQualityLevel::Type>(QualityLevel)).Instances.Num(); ++InstanceIndex)
{
auto& Data = Platform->GetInstanceData(static_cast<EMaterialQualityLevel::Type>(QualityLevel), InstanceIndex);
auto& Errors = Data.MaterialResourcesStats->GetCompileErrors();
for (int32 ErrorIndex = 0; ErrorIndex < Errors.Num(); ++ErrorIndex)
{
const auto& PlatformName = Platform->GetPlatformName().ToString();
const auto& AssetName = GetMaterialName(InstanceIndex);
const FString QualityName = FMaterialStatsUtils::MaterialQualityToShortString((EMaterialQualityLevel::Type)QualityLevel);
FString Message = FString::Printf(TEXT("[%s/%s:%s] %s"), *AssetName, *PlatformName, *QualityName, *Errors[ErrorIndex]);
Messages.Add(Message);
IsErrorMessage.Add(true);
}
}
}
}
}
bool bAnyPlatformPresent = false;
bool bPlatformsNeedOfflineCompiler = false;
const auto& PlatformList = GetPlatformsDB();
for (auto Pair : PlatformList)
{
auto& PlatformPtr = Pair.Value;
if (PlatformPtr->IsPresentInGrid())
{
bAnyPlatformPresent = true;
auto ShaderPlatformType = PlatformPtr->GetPlatformShaderType();
bool bNeedsOfflineCompiler = FMaterialStatsUtils::PlatformNeedsOfflineCompiler(ShaderPlatformType);
if (bNeedsOfflineCompiler)
{
bool bCompilerAvailable = FMaterialStatsUtils::IsPlatformOfflineCompilerAvailable(ShaderPlatformType);
if (!bCompilerAvailable)
{
auto WarningString = FString::Printf(TEXT("Platform %s needs an offline shader compiler to extract instruction count. Please check 'Editor Preferences' -> 'Content Editors' -> 'Material Editor' for additional settings."), *PlatformPtr->GetPlatformName().ToString());
Messages.Add(MoveTemp(WarningString));
IsErrorMessage.Add(false);
}
}
}
}
if (!bAnyPlatformPresent)
{
Messages.Emplace("No platform selected. Please use the 'Settings' button and choose desired platform to be analyzed.");
IsErrorMessage.Add(false);
}
if (Messages != LastGridMessages)
{
LastGridMessages = Messages;
GridPtr->ClearMessages();
check(Messages.Num() == IsErrorMessage.Num());
for (int32 i = 0; i < Messages.Num(); ++i)
{
GridPtr->AddMessage(Messages[i], IsErrorMessage[i]);
}
}
}
void FMaterialStats::ExtractHLSLCode()
{
HLSLCode = TEXT("");
PreviousTranslatorHLSLCode = TEXT("");
MaterialIR = TEXT("");
if (!HLSLTab.IsValid())
{
return;
}
FString FullSource;
if (MaterialInterface != nullptr && MaterialInterface->GetMaterialResource(GMaxRHIFeatureLevel)->GetMaterialExpressionSource(FullSource))
{
if (const FMaterialInsights* Insights = MaterialInterface->MaterialInsight.Get())
{
MaterialIR = Insights->IRString;
TMap<FString, FString> SortedLegacy_ShaderString = Insights->Legacy_ShaderStringParameters;
SortedLegacy_ShaderString.KeySort([](const FString& A, const FString& B) { return A < B; });
TMap<FString, FString> SortedNew_ShaderString = Insights->New_ShaderStringParameters;
SortedNew_ShaderString.KeySort([](const FString& A, const FString& B) { return A < B; });
for (const TPair<FString, FString>& Pair : SortedLegacy_ShaderString)
{
const FString& Key = Pair.Key;
const FString& Value = Pair.Value;
PreviousTranslatorHLSLCode += FString::Printf(TEXT("// %s\n"), *Key);
PreviousTranslatorHLSLCode += FString::Printf(TEXT("%s\n"), *Value);
}
for (const TPair<FString, FString>& Pair : SortedNew_ShaderString)
{
const FString& Key = Pair.Key;
const FString& Value = Pair.Value;
HLSLCode += FString::Printf(TEXT("// %s\n"), *Key);
HLSLCode += FString::Printf(TEXT("%s\n"), *Value);
}
if (MaterialInterface->IsUsingNewHLSLGenerator())
{
HLSLCode += TEXT("// FULL SOURCE BELOW\n");
HLSLCode += FullSource;
}
else
{
PreviousTranslatorHLSLCode += TEXT("// FULL SOURCE BELOW\n");
PreviousTranslatorHLSLCode += FullSource;
}
}
}
}
#undef LOCTEXT_NAMESPACE
/*end FMaterialStats functions*/
/***********************************************************************************************************************/