// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/DumpMaterialInfo.h" #include "Modules/ModuleManager.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/AssetData.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Materials/Material.h" #include "Materials/MaterialInstance.h" #include "MaterialStatsCommon.h" #include "MaterialShared.h" #include "MaterialDomain.h" #include "ShaderCompiler.h" #include "Algo/Accumulate.h" #include "MaterialShaderType.h" #include "RendererUtils.h" #include "Internationalization/Regex.h" #include "String/ParseTokens.h" #include "ProfilingDebugging/DiagnosticTable.h" DEFINE_LOG_CATEGORY_STATIC(LogDumpMaterialInfo, Log, All); namespace MaterialInfo { class FOutput; // Material data from which properties are read to be dumped to csv struct FPropertyDumpInput { UMaterial* Material; FMaterialResource* MaterialResource; FMaterialRelevance MaterialRelevance; TArray* ShaderTypes; }; // Set of material properties that can be dumped struct FPropertySet { using FDumpFunction = TFunction; // Names of all the properties in this set, used as column headers and for filtering TArray PropertyNames; // Read each property value in this set from Input, and write it to Output, in order specified by PropertyNames FDumpFunction DumpFunction; FPropertySet() { } FPropertySet(TArray InNames, FDumpFunction InDumpFunction) : PropertyNames(InNames) , DumpFunction(InDumpFunction) { } FPropertySet(FString InName, FDumpFunction InDumpFunction) : PropertyNames( { InName } ) , DumpFunction(InDumpFunction) { } template static FPropertySet FromAccessor(const FString& InName); template static FPropertySet FromEnumAccessor(const FString& InName); }; struct FPropertyValue { // Column name const FString& Name; // Value as string view, valid until FOutput::Reset is called. FStringView Value; }; struct FSlice { int Start; int End; }; // Class to efficiently write out property values to a stringbuffer and verify the output. class FOutput { int NumExpectedValues = 0; // Storage for output strings TStringBuilder<2048> Buffer; TArray BufferSlices; // Populated after all values have been written. TArray Values; public: void DumpPropertySet(const FPropertySet& PropertySet, const FPropertyDumpInput& Input) { NumExpectedValues = PropertySet.PropertyNames.Num(); PropertySet.DumpFunction(Input, *this); check(NumExpectedValues == 0); for (int Index = 0; Index < BufferSlices.Num(); Index++) { const FSlice& Slice = BufferSlices[Index]; Values.Push( { PropertySet.PropertyNames[Index], Buffer.ToView().SubStr(Slice.Start, Slice.End - Slice.Start) }); } BufferSlices.Reset(); } const TArray& GetValues() { return Values; } void Reset() { NumExpectedValues = 0; Buffer.Reset(); BufferSlices.Reset(); Values.Reset(); } template void WriteFormat(const FmtType& Fmt, Types... Args) { check(NumExpectedValues-- > 0); int Start = Buffer.Len(); Buffer.Appendf(Fmt, Forward(Args)...); int End = Buffer.Len(); BufferSlices.Push( { Start, End } ); } void Write(int32 Value) { WriteFormat(TEXT("%d"), Value); } void Write(uint32 Value) { WriteFormat(TEXT("%u"), Value); } void Write(bool Value) { WriteFormat(TEXT("%d"), Value); } void Write(float Value) { WriteFormat(TEXT("%f"), Value); } void Write(const FString& Value) { WriteFormat(TEXT("%s"), *Value); } }; template FPropertySet FPropertySet::FromAccessor(const FString& InName) { return FPropertySet( InName, [](const FPropertyDumpInput& Input, FOutput& Output) { Output.Write((Input.MaterialResource->*MemberAccessor)()); }); } template FPropertySet FPropertySet::FromEnumAccessor(const FString& InName) { return FPropertySet( InName, [](const FPropertyDumpInput& Input, FOutput& Output) { Output.Write(UEnum::GetValueOrBitfieldAsString((Input.MaterialResource->*MemberAccessor)())); }); } } UDumpMaterialInfoCommandlet::UDumpMaterialInfoCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } TArray GetMaterialInfoProperties() { #if WITH_EDITOR using namespace MaterialInfo; TArray PropertySets = { FPropertySet::FromAccessor(TEXT("GetSamplerUsage")), FPropertySet { { TEXT("NumUsedUVScalars"), TEXT("NumUsedCustomInterpolatorScalars") }, [](const FPropertyDumpInput& Input, FOutput& Output) { uint32 NumUsedUVScalars = 0; uint32 NumUsedCustomInterpolatorScalars = 0; Input.MaterialResource->GetUserInterpolatorUsage(NumUsedUVScalars, NumUsedCustomInterpolatorScalars); Output.Write(NumUsedUVScalars); Output.Write(NumUsedCustomInterpolatorScalars); } }, FPropertySet::FromAccessor(TEXT("GetEstimatedNumVirtualTextureLookups")), FPropertySet { { TEXT("LWCUsagesVS"), TEXT("LWCUsagesPS"), TEXT("LWCUsagesCS") }, [](const FPropertyDumpInput& Input, FOutput& Output) { FMaterialResource::FLWCUsagesArray LWCUsagesVS {}; FMaterialResource::FLWCUsagesArray LWCUsagesPS {}; FMaterialResource::FLWCUsagesArray LWCUsagesCS {}; Input.MaterialResource->GetEstimatedLWCFuncUsages(LWCUsagesVS, LWCUsagesPS, LWCUsagesCS); Output.Write(Algo::Accumulate(LWCUsagesVS, 0)); Output.Write(Algo::Accumulate(LWCUsagesPS, 0)); Output.Write(Algo::Accumulate(LWCUsagesCS, 0)); } }, FPropertySet::FromAccessor(TEXT("GetNumVirtualTextureStacks")), //FPropertySet::FromAccessor(TEXT("MaterialUsageDescription")), //FPropertySet::FromAccessor(TEXT("ShaderMapId")), //FPropertySet::FromAccessor(TEXT("StaticParameterSet")), FPropertySet::FromEnumAccessor(TEXT("GetMaterialDomain")), FPropertySet::FromAccessor(TEXT("IsTranslucencyWritingFrontLayerTransparency")), FPropertySet::FromAccessor(TEXT("IsTranslucencyWritingFrontLayerTransparency")), FPropertySet::FromAccessor(TEXT("IsTangentSpaceNormal")), FPropertySet::FromAccessor(TEXT("ShouldGenerateSphericalParticleNormals")), FPropertySet::FromAccessor(TEXT("ShouldDisableDepthTest")), FPropertySet::FromAccessor(TEXT("ShouldWriteOnlyAlpha")), FPropertySet::FromAccessor(TEXT("ShouldEnableResponsiveAA")), FPropertySet::FromAccessor(TEXT("ShouldDoSSR")), FPropertySet::FromAccessor(TEXT("ShouldDoContactShadows")), FPropertySet::FromAccessor(TEXT("HasPixelAnimation")), FPropertySet::FromAccessor(TEXT("IsLightFunction")), FPropertySet::FromAccessor(TEXT("IsUsedWithEditorCompositing")), FPropertySet::FromAccessor(TEXT("IsDeferredDecal")), FPropertySet::FromAccessor(TEXT("IsVolumetricPrimitive")), FPropertySet::FromAccessor(TEXT("IsWireframe")), FPropertySet::FromAccessor(TEXT("IsVariableRateShadingAllowed")), FPropertySet::FromEnumAccessor(TEXT("GetShadingRate")), FPropertySet::FromAccessor(TEXT("IsUIMaterial")), FPropertySet::FromAccessor(TEXT("IsPostProcessMaterial")), FPropertySet::FromAccessor(TEXT("IsSpecialEngineMaterial")), FPropertySet::FromAccessor(TEXT("IsUsedWithSkeletalMesh")), FPropertySet::FromAccessor(TEXT("IsUsedWithLandscape")), FPropertySet::FromAccessor(TEXT("IsUsedWithParticleSystem")), FPropertySet::FromAccessor(TEXT("IsUsedWithParticleSprites")), FPropertySet::FromAccessor(TEXT("IsUsedWithBeamTrails")), FPropertySet::FromAccessor(TEXT("IsUsedWithMeshParticles")), FPropertySet::FromAccessor(TEXT("IsUsedWithNiagaraSprites")), FPropertySet::FromAccessor(TEXT("IsUsedWithNiagaraRibbons")), FPropertySet::FromAccessor(TEXT("IsUsedWithNiagaraMeshParticles")), FPropertySet::FromAccessor(TEXT("IsUsedWithStaticLighting")), FPropertySet::FromAccessor(TEXT("IsUsedWithMorphTargets")), FPropertySet::FromAccessor(TEXT("IsUsedWithSplineMeshes")), FPropertySet::FromAccessor(TEXT("IsUsedWithInstancedStaticMeshes")), FPropertySet::FromAccessor(TEXT("IsUsedWithGeometryCollections")), FPropertySet::FromAccessor(TEXT("IsUsedWithAPEXCloth")), FPropertySet::FromAccessor(TEXT("IsUsedWithGeometryCache")), FPropertySet::FromAccessor(TEXT("IsUsedWithWater")), FPropertySet::FromAccessor(TEXT("IsUsedWithHairStrands")), FPropertySet::FromAccessor(TEXT("IsUsedWithLidarPointCloud")), FPropertySet::FromAccessor(TEXT("IsUsedWithVirtualHeightfieldMesh")), FPropertySet::FromAccessor(TEXT("IsUsedWithNeuralNetworks")), FPropertySet::FromAccessor(TEXT("IsUsedWithNanite")), FPropertySet::FromAccessor(TEXT("IsUsedWithVolumetricCloud")), FPropertySet::FromAccessor(TEXT("IsUsedWithHeterogeneousVolumes")), FPropertySet::FromAccessor(TEXT("IsFullyRough")), FPropertySet::FromAccessor(TEXT("GetForceCompatibleWithLightFunctionAtlas")), FPropertySet::FromAccessor(TEXT("UseNormalCurvatureToRoughness")), FPropertySet::FromEnumAccessor(TEXT("GetMaterialFloatPrecisionMode")), FPropertySet::FromAccessor(TEXT("IsUsingAlphaToCoverage")), FPropertySet::FromAccessor(TEXT("IsUsingPreintegratedGFForSimpleIBL")), FPropertySet::FromAccessor(TEXT("IsUsingHQForwardReflections")), FPropertySet::FromAccessor(TEXT("GetForwardBlendsSkyLightCubemaps")), FPropertySet::FromAccessor(TEXT("IsUsingPlanarForwardReflections")), FPropertySet::FromAccessor(TEXT("IsNonmetal")), FPropertySet::FromAccessor(TEXT("UseLmDirectionality")), FPropertySet::FromEnumAccessor(TEXT("GetBlendMode")), FPropertySet::FromEnumAccessor(TEXT("GetRefractionMode")), FPropertySet::FromAccessor(TEXT("GetRootNodeOverridesDefaultRefraction")), FPropertySet::FromAccessor(TEXT("GetMaterialDecalResponse")), FPropertySet::FromAccessor(TEXT("HasBaseColorConnected")), FPropertySet::FromAccessor(TEXT("HasNormalConnected")), FPropertySet::FromAccessor(TEXT("HasRoughnessConnected")), FPropertySet::FromAccessor(TEXT("HasSpecularConnected")), FPropertySet::FromAccessor(TEXT("HasMetallicConnected")), FPropertySet::FromAccessor(TEXT("HasEmissiveColorConnected")), FPropertySet::FromAccessor(TEXT("HasAnisotropyConnected")), FPropertySet::FromAccessor(TEXT("HasAmbientOcclusionConnected")), FPropertySet::FromAccessor(TEXT("HasDisplacementConnected")), FPropertySet::FromAccessor(TEXT("IsSubstrateMaterial")), //FPropertySet::FromAccessor(TEXT("HasMaterialPropertyConnected")), //FPropertySet::FromAccessor(TEXT("GetShadingModels")), FPropertySet::FromAccessor(TEXT("IsShadingModelFromMaterialExpression")), FPropertySet::FromEnumAccessor(TEXT("GetTranslucencyLightingMode")), FPropertySet::FromAccessor(TEXT("GetOpacityMaskClipValue")), FPropertySet::FromAccessor(TEXT("GetCastDynamicShadowAsMasked")), FPropertySet::FromAccessor(TEXT("IsDistorted")), FPropertySet::FromEnumAccessor(TEXT("GetRefractionCoverageMode")), FPropertySet::FromEnumAccessor(TEXT("GetPixelDepthOffsetMode")), FPropertySet::FromAccessor(TEXT("GetTranslucencyDirectionalLightingIntensity")), FPropertySet::FromAccessor(TEXT("GetTranslucentShadowDensityScale")), FPropertySet::FromAccessor(TEXT("GetTranslucentSelfShadowDensityScale")), FPropertySet::FromAccessor(TEXT("GetTranslucentSelfShadowSecondDensityScale")), FPropertySet::FromAccessor(TEXT("GetTranslucentSelfShadowSecondOpacity")), FPropertySet::FromAccessor(TEXT("GetTranslucentBackscatteringExponent")), FPropertySet::FromAccessor(TEXT("IsTranslucencyAfterDOFEnabled")), FPropertySet::FromAccessor(TEXT("IsTranslucencyAfterMotionBlurEnabled")), //FPropertySet::FromAccessor(TEXT("IsDualBlendingEnabled")), FPropertySet::FromAccessor(TEXT("IsMobileSeparateTranslucencyEnabled")), //FPropertySet::FromAccessor(TEXT("GetDisplacementScaling")), FPropertySet::FromAccessor(TEXT("IsDisplacementFadeEnabled")), //FPropertySet::FromAccessor(TEXT("GetDisplacementFadeRange")), //FPropertySet::FromAccessor(TEXT("GetTranslucentMultipleScatteringExtinction")), FPropertySet::FromAccessor(TEXT("GetTranslucentShadowStartOffset")), FPropertySet::FromAccessor(TEXT("IsMasked")), FPropertySet::FromAccessor(TEXT("IsDitherMasked")), FPropertySet::FromAccessor(TEXT("AllowNegativeEmissiveColor")), //FPropertySet::FromAccessor(TEXT("GetFriendlyName")), FPropertySet::FromAccessor(TEXT("GetAssetName")), FPropertySet::FromAccessor(TEXT("RequiresSynchronousCompilation")), FPropertySet::FromAccessor(TEXT("IsDefaultMaterial")), FPropertySet::FromAccessor(TEXT("GetNumCustomizedUVs")), FPropertySet::FromAccessor(TEXT("GetBlendableLocation")), FPropertySet::FromAccessor(TEXT("GetBlendableOutputAlpha")), FPropertySet::FromAccessor(TEXT("GetDisablePreExposureScale")), FPropertySet::FromAccessor(TEXT("IsStencilTestEnabled")), FPropertySet::FromAccessor(TEXT("GetStencilRefValue")), FPropertySet::FromAccessor(TEXT("GetStencilCompare")), FPropertySet::FromAccessor(TEXT("GetRefractionDepthBiasValue")), FPropertySet::FromAccessor(TEXT("ShouldApplyFogging")), FPropertySet::FromAccessor(TEXT("ShouldApplyCloudFogging")), FPropertySet::FromAccessor(TEXT("ShouldAlwaysEvaluateWorldPositionOffset")), FPropertySet::FromAccessor(TEXT("IsSky")), FPropertySet::FromAccessor(TEXT("ComputeFogPerPixel")), FPropertySet::FromAccessor(TEXT("HasPerInstanceCustomData")), FPropertySet::FromAccessor(TEXT("HasPerInstanceRandom")), FPropertySet::FromAccessor(TEXT("HasVertexInterpolator")), FPropertySet::FromAccessor(TEXT("HasRuntimeVirtualTextureOutput")), FPropertySet::FromAccessor(TEXT("CastsRayTracedShadows")), FPropertySet::FromAccessor(TEXT("IsTessellationEnabled")), FPropertySet::FromAccessor(TEXT("HasRenderTracePhysicalMaterialOutputs")), FPropertySet::FromAccessor(TEXT("GetPreshaderGap")), FPropertySet::FromAccessor(TEXT("GetNeuralProfileId")), FPropertySet { { TEXT("ShadingModelMask"), TEXT("SubstrateUintPerPixel"), TEXT("SubstrateClosureCountMask"), TEXT("bUsesComplexSpecialRenderPath"), TEXT("bOpaque"), TEXT("bMasked"), TEXT("bDistortion"), TEXT("bHairStrands"), TEXT("bTwoSided"), TEXT("bSeparateTranslucency"), TEXT("bTranslucencyModulate"), TEXT("bPostMotionBlurTranslucency"), TEXT("bNormalTranslucency"), TEXT("bUsesSceneColorCopy"), TEXT("bOutputsTranslucentVelocity"), TEXT("bUsesGlobalDistanceField"), TEXT("bUsesWorldPositionOffset"), TEXT("bUsesDisplacement"), TEXT("bUsesPixelDepthOffset"), TEXT("bDecal"), TEXT("bTranslucentSurfaceLighting"), TEXT("bUsesSceneDepth"), TEXT("bUsesSkyMaterial"), TEXT("bUsesSingleLayerWaterMaterial"), TEXT("bHasVolumeMaterialDomain"), TEXT("CustomDepthStencilUsageMask"), TEXT("bUsesDistanceCullFade"), TEXT("bDisableDepthTest"), TEXT("bUsesAnisotropy"), TEXT("bIsLightFunctionAtlasCompatible"), }, [](const FPropertyDumpInput& Input, FOutput& Output) { Output.Write(Input.MaterialRelevance.ShadingModelMask); Output.Write(Input.MaterialRelevance.SubstrateUintPerPixel); Output.Write(Input.MaterialRelevance.SubstrateClosureCountMask); Output.Write(Input.MaterialRelevance.bUsesComplexSpecialRenderPath); Output.Write(Input.MaterialRelevance.bOpaque); Output.Write(Input.MaterialRelevance.bMasked); Output.Write(Input.MaterialRelevance.bDistortion); Output.Write(Input.MaterialRelevance.bHairStrands); Output.Write(Input.MaterialRelevance.bTwoSided); Output.Write(Input.MaterialRelevance.bSeparateTranslucency); Output.Write(Input.MaterialRelevance.bTranslucencyModulate); Output.Write(Input.MaterialRelevance.bPostMotionBlurTranslucency); Output.Write(Input.MaterialRelevance.bNormalTranslucency); Output.Write(Input.MaterialRelevance.bUsesSceneColorCopy); Output.Write(Input.MaterialRelevance.bOutputsTranslucentVelocity); Output.Write(Input.MaterialRelevance.bUsesGlobalDistanceField); Output.Write(Input.MaterialRelevance.bUsesWorldPositionOffset); Output.Write(Input.MaterialRelevance.bUsesDisplacement); Output.Write(Input.MaterialRelevance.bUsesPixelDepthOffset); Output.Write(Input.MaterialRelevance.bDecal); Output.Write(Input.MaterialRelevance.bTranslucentSurfaceLighting); Output.Write(Input.MaterialRelevance.bUsesSceneDepth); Output.Write(Input.MaterialRelevance.bUsesSkyMaterial); Output.Write(Input.MaterialRelevance.bUsesSingleLayerWaterMaterial); Output.Write(Input.MaterialRelevance.bHasVolumeMaterialDomain); Output.Write(Input.MaterialRelevance.CustomDepthStencilUsageMask); Output.Write(Input.MaterialRelevance.bUsesDistanceCullFade); Output.Write(Input.MaterialRelevance.bDisableDepthTest); Output.Write(Input.MaterialRelevance.bUsesAnisotropy); Output.Write(Input.MaterialRelevance.bIsLightFunctionAtlasCompatible); } }, }; return PropertySets; #else return {}; #endif } static void DumpMaterials( FDiagnosticTableWriterCSV& CsvWriter, TArrayView MaterialInterfaceAssets, TArray& MaterialInfoProperties, TSet& Columns, ITargetPlatform* Platform, EShaderPlatform ShaderPlatform, ERHIFeatureLevel::Type FeatureLevel, EMaterialQualityLevel::Type MaterialQualityLevel, bool bMatchAllMaterials, const FRegexPattern& RequestedMaterialPattern ); int32 UDumpMaterialInfoCommandlet::Main(const FString& Params) { TArray Tokens; TArray Switches; TMap ParamVals; UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals); // Display help if (Switches.Contains("help")) { UE_LOG(LogDumpMaterialInfo, Log, TEXT("DumpMaterialInfo")); UE_LOG(LogDumpMaterialInfo, Log, TEXT("This commandlet will dump information about materials.")); UE_LOG(LogDumpMaterialInfo, Log, TEXT("A typical way to invoke it is: -run=DumpMaterialInfo -targetplatform=Windows -unattended -sm6 -allowcommandletrendering -nomaterialshaderddc -csv=C:/output.csv")); UE_LOG(LogDumpMaterialInfo, Log, TEXT("")); UE_LOG(LogDumpMaterialInfo, Log, TEXT("Options:")); UE_LOG(LogDumpMaterialInfo, Log, TEXT(" -help Print this message")); UE_LOG(LogDumpMaterialInfo, Log, TEXT(" -help=columns Print the list of available columns")); UE_LOG(LogDumpMaterialInfo, Log, TEXT(" -csv=filename Writes the output to a CSV file")); UE_LOG(LogDumpMaterialInfo, Log, TEXT(" -material=name Only dump materials matching this material name or regular expression")); UE_LOG(LogDumpMaterialInfo, Log, TEXT(" -columns=a,b Comma-seperated list of the columns that should be included in the output")); return 0; } FString* Help = ParamVals.Find(TEXT("help")); if (Help) { if ((*Help) == TEXT("columns")) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("Available columns:"), **Help); for (const MaterialInfo::FPropertySet& PropertySet : GetMaterialInfoProperties()) { for (const FString& PropertyName : PropertySet.PropertyNames) { UE_LOG(LogDumpMaterialInfo, Display, TEXT(" %s"), *PropertyName); } } } else { UE_LOG(LogDumpMaterialInfo, Error, TEXT("Unknown help option %s"), **Help); return 1; } return 0; } // Parse params FString* CsvPath = ParamVals.Find(TEXT("csv")); if (!CsvPath) { UE_LOG(LogDumpMaterialInfo, Error, TEXT("No output CSV file path was specified")); return 1; } FString* RequestedMaterialPatternString = ParamVals.Find(TEXT("material")); bool bMatchAllMaterials = !RequestedMaterialPatternString; FRegexPattern RequestedMaterialPattern(RequestedMaterialPatternString ? *RequestedMaterialPatternString : FString()); FString* ColumnsString = ParamVals.Find(TEXT("columns")); TSet Columns {}; if (ColumnsString) { UE::String::ParseTokens(*ColumnsString, TEXT(","), [&Columns](const auto& SubString) { Columns.Add(FString(SubString)); }, UE::String::EParseTokensOptions::SkipEmpty | UE::String::EParseTokensOptions::Trim); } ERHIFeatureLevel::Type FeatureLevel = ERHIFeatureLevel::SM6; EMaterialQualityLevel::Type MaterialQualityLevel = EMaterialQualityLevel::High; EShaderPlatform ShaderPlatform = SP_PCD3D_SM6; // Get available material properties and filter them TArray MaterialInfoProperties = GetMaterialInfoProperties(); if (!Columns.IsEmpty()) { // Retain property sets that have at least 1 requested property MaterialInfoProperties = MaterialInfoProperties.FilterByPredicate([&Columns](const MaterialInfo::FPropertySet& PropertySet) { return PropertySet.PropertyNames.ContainsByPredicate([&Columns](const FString& PropertyName) { return Columns.Contains(PropertyName); }); }); } UE_LOG(LogDumpMaterialInfo, Log, TEXT("Searching for materials within the project...")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.SearchAllAssets(true); TArray MaterialInterfaceAssets; { TArray MaterialAssets; AssetRegistry.GetAssetsByClass(UMaterial::StaticClass()->GetClassPathName(), MaterialAssets, true); UE_LOG(LogDumpMaterialInfo, Log, TEXT("Found %d materials"), MaterialAssets.Num()); MaterialInterfaceAssets = MaterialAssets; } { TArray MaterialInstanceAssets; AssetRegistry.GetAssetsByClass(UMaterialInstance::StaticClass()->GetClassPathName(), MaterialInstanceAssets, true); UE_LOG(LogDumpMaterialInfo, Log, TEXT("Found %d material instances"), MaterialInstanceAssets.Num()); MaterialInterfaceAssets.Append(MaterialInstanceAssets); } ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); const TArray& Platforms = TPM->GetActiveTargetPlatforms(); FArchive* CsvFileWriter = IFileManager::Get().CreateFileWriter(**CsvPath); if (!CsvFileWriter) { UE_LOG(LogDumpMaterialInfo, Error, TEXT("Failed to open output file %s"), **CsvPath); return 1; } FDiagnosticTableWriterCSV CsvWriter(CsvFileWriter); // CSV header { for (const MaterialInfo::FPropertySet& Property : MaterialInfoProperties) { for (const FString& PropertyName : Property.PropertyNames) { if (Columns.IsEmpty() || Columns.Contains(PropertyName)) { CsvWriter.AddColumn(TEXT("%s"), *PropertyName); } } } CsvWriter.CycleRow(); CsvFileWriter->Flush(); } for (ITargetPlatform* Platform : Platforms) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("Compiling shaders for %s..."), *Platform->PlatformName()); const int MaxBatchSize = 1000; int NumBatches = FMath::DivideAndRoundUp(MaterialInterfaceAssets.Num(), MaxBatchSize); for (int BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("Dumping batch %d of %d"), BatchIndex, NumBatches); DumpMaterials( CsvWriter, TArrayView(MaterialInterfaceAssets).Mid(BatchIndex * MaxBatchSize, MaxBatchSize), MaterialInfoProperties, Columns, Platform, ShaderPlatform, FeatureLevel, MaterialQualityLevel, bMatchAllMaterials, RequestedMaterialPattern); } CsvFileWriter->Flush(); } // Platforms return 0; } static void DumpMaterials( FDiagnosticTableWriterCSV& CsvWriter, TArrayView MaterialInterfaceAssets, TArray& MaterialInfoProperties, TSet& Columns, ITargetPlatform* Platform, EShaderPlatform ShaderPlatform, ERHIFeatureLevel::Type FeatureLevel, EMaterialQualityLevel::Type MaterialQualityLevel, bool bMatchAllMaterials, const FRegexPattern& RequestedMaterialPattern ) { TSet MaterialsToCompile; for (const FAssetData& AssetData : MaterialInterfaceAssets) { bool bInclude = (bMatchAllMaterials || FRegexMatcher(RequestedMaterialPattern, AssetData.GetFullName()).FindNext()); if (!bInclude) { continue; } if (UMaterialInterface* MaterialInterface = Cast(AssetData.GetAsset())) { UMaterial* Material = MaterialInterface->GetMaterial(); if (Material) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("BeginCache for %s"), *MaterialInterface->GetFullName()); MaterialInterface->BeginCacheForCookedPlatformData(Platform); // need to call this once for all objects before any calls to ProcessAsyncResults as otherwise we'll potentially upload // incremental/incomplete shadermaps to DDC (as this function actually triggers compilation, some compiles for a particular // material may finish before we've even started others - if we call ProcessAsyncResults in that case the associated shader // maps will think they are "finished" due to having no outstanding dependencies). if (!MaterialInterface->IsCachedCookedPlatformDataLoaded(Platform)) { MaterialsToCompile.Add(MaterialInterface); } } } } TSet MaterialsToAnalyse = MaterialsToCompile; UE_LOG(LogDumpMaterialInfo, Log, TEXT("Found %d materials to compile."), MaterialsToCompile.Num()); static constexpr bool bLimitExecutationTime = false; int32 PreviousOutstandingJobs = 0; constexpr int32 MaxOutstandingJobs = 20000; // Having a max is a way to try to reduce memory usage.. otherwise outstanding jobs can reach 100k+ and use up 300gb committed memory // Submit all the jobs. { TRACE_CPUPROFILER_EVENT_SCOPE(SubmitJobs); UE_LOG(LogDumpMaterialInfo, Display, TEXT("Submit Jobs")); while (MaterialsToCompile.Num()) { for (auto It = MaterialsToCompile.CreateIterator(); It; ++It) { UMaterialInterface* MaterialInterface = *It; if (MaterialInterface->IsCachedCookedPlatformDataLoaded(Platform)) { It.RemoveCurrent(); UE_LOG(LogDumpMaterialInfo, Display, TEXT("Finished cache for %s."), *MaterialInterface->GetFullName()); UE_LOG(LogDumpMaterialInfo, Display, TEXT("Materials remaining: %d"), MaterialsToCompile.Num()); } GShaderCompilingManager->ProcessAsyncResults(bLimitExecutationTime, false /* bBlockOnGlobalShaderCompilation */); while (true) { const int32 CurrentOutstandingJobs = GShaderCompilingManager->GetNumOutstandingJobs(); if (CurrentOutstandingJobs != PreviousOutstandingJobs) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("Outstanding Jobs: %d"), CurrentOutstandingJobs); PreviousOutstandingJobs = CurrentOutstandingJobs; } // Flush rendering commands to release any RHI resources (shaders and shader maps). // Delete any FPendingCleanupObjects (shader maps). FlushRenderingCommands(); if (CurrentOutstandingJobs < MaxOutstandingJobs) { break; } FPlatformProcess::Sleep(1); } } } } // Process the shader maps and save to the DDC. { TRACE_CPUPROFILER_EVENT_SCOPE(ProcessShaderCompileResults); UE_LOG(LogDumpMaterialInfo, Log, TEXT("ProcessAsyncResults")); while (GShaderCompilingManager->IsCompiling()) { GShaderCompilingManager->ProcessAsyncResults(bLimitExecutationTime, false /* bBlockOnGlobalShaderCompilation */); while (true) { const int32 CurrentOutstandingJobs = GShaderCompilingManager->GetNumOutstandingJobs(); if (CurrentOutstandingJobs != PreviousOutstandingJobs) { UE_LOG(LogDumpMaterialInfo, Display, TEXT("Outstanding Jobs: %d"), CurrentOutstandingJobs); PreviousOutstandingJobs = CurrentOutstandingJobs; } // Flush rendering commands to release any RHI resources (shaders and shader maps). // Delete any FPendingCleanupObjects (shader maps). FlushRenderingCommands(); if (CurrentOutstandingJobs < MaxOutstandingJobs) { break; } FPlatformProcess::Sleep(1); } } } // Look up compilation result for the materials TArray VFTypes; TArray PipelineTypes; TArray ShaderTypes; for (UMaterialInterface* MaterialInterface : MaterialsToAnalyse) { VFTypes.Empty(); PipelineTypes.Empty(); ShaderTypes.Empty(); UMaterial* Material = MaterialInterface->GetMaterial(); if (Material) { TArray ResourcesToCache; FMaterialResource* CurrentResource = FindOrCreateMaterialResource(ResourcesToCache, Material, nullptr, FeatureLevel, MaterialQualityLevel); check(CurrentResource); TMap> ShaderTypeNamesAndDescriptions; FMaterialStatsUtils::GetRepresentativeShaderTypesAndDescriptions(ShaderTypeNamesAndDescriptions, CurrentResource); 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); 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 = CurrentResource->CacheShaders(ShaderPlatform, EMaterialShaderPrecompileMode::None); if (bSuccess) { // Compile just the types we want. CurrentResource->CacheGivenTypes(ShaderPlatform, VFTypes, PipelineTypes, ShaderTypes); } if (!CurrentResource->IsGameThreadShaderMapComplete()) { UE_LOG(LogDumpMaterialInfo, Warning, TEXT("Missing shader map data")); } FMaterialRelevance MaterialRelevance = CurrentResource->GetMaterialInterface()->GetRelevance(FeatureLevel); // CSV line { MaterialInfo::FOutput Output; for (const MaterialInfo::FPropertySet& Property : MaterialInfoProperties) { MaterialInfo::FPropertyDumpInput Input; Input.Material = Material; Input.MaterialResource = CurrentResource; Input.MaterialRelevance = MaterialRelevance; Input.ShaderTypes = &ShaderTypes; Output.DumpPropertySet(Property, Input); for (const MaterialInfo::FPropertyValue& Value : Output.GetValues()) { if (Columns.IsEmpty() || Columns.Contains(Value.Name)) { CsvWriter.AddColumn(TEXT("%s"), *FString(Value.Value)); } } Output.Reset(); } CsvWriter.CycleRow(); } FMaterial::DeferredDeleteArray(ResourcesToCache); } } // Perform cleanup and clear cached data for cooking. { TRACE_CPUPROFILER_EVENT_SCOPE(ClearCachedCookedPlatformData); UE_LOG(LogDumpMaterialInfo, Display, TEXT("Clear Cached Cooked Platform Data")); for (const FAssetData& AssetData : MaterialInterfaceAssets) { if (UMaterialInterface* MaterialInterface = Cast(AssetData.GetAsset())) { MaterialInterface->ClearAllCachedCookedPlatformData(); } } } }