如何在Unreal编辑器插件中访问材质指令计数?
问题背景
我正在开发Unreal编辑器插件,需要获取材质的指令计数。在引擎代码中找到官方编辑器的实现路径:\Engine\Source\Editor\MaterialEditor\Public\MaterialStatsCommon.h里的FMaterialStatsUtils类,提供了静态方法ExtractMatertialStatsInfo用来提取材质统计信息:
/** class used for various stats utilities */ class FMaterialStatsUtils { public: MATERIALEDITOR_API static void ExtractMatertialStatsInfo(struct FShaderStatsInfo& OutInfo, const FMaterialResource* Target); };
但问题在于:FMaterialStatsUtils是公开API,而存储统计结果的FShaderStatsInfo结构体却定义在同模块的私有文件夹MaterialStats.h中。插件引入MaterialEditor模块后,只能通过前向声明FShaderStatsInfo调用方法,但无法访问其内部成员(编译器提示"类型不完整"),也不能直接包含私有头文件MaterialStats.h。
我的卡点代码如下:
#include "MaterialStatsCommon.h" struct FShaderStatsInfo; void MyEditorBPLibraryName::GetMaterialStatsInfo(UMaterial* Material, int32 BasePassShaderInstructionCount, int32 VertexShaderInstructionCount, int32 TextureSampleCount) { if (Material == nullptr) { return; } FShaderStatsInfo* OutInfo = nullptr; FMaterialStatsUtils::ExtractMatertialStatsInfo(*OutInfo, Material->GetMaterialResource(ERHIFeatureLevel::SM5, EMaterialQualityLevel::Num)); //OutInfo-> 需访问ShaderInstructionCount,但无法操作 return; }
需要用到的FShaderStatsInfo结构体定义(来自私有头文件MaterialStats.h):
#include "MaterialStatsCommon.h" #include "UObject/GCObject.h" /** structure used to store various statistics extracted from compiled shaders */ struct FShaderStatsInfo { struct FContent { FString StrDescription; FString StrDescriptionLong; }; TMap<ERepresentativeShader, FContent> ShaderInstructionCount; FContent SamplersCount; FContent InterpolatorsCount; FContent TextureSampleCount; FContent VirtualTextureLookupCount; FString StrShaderErrors; void Reset() { ShaderInstructionCount.Empty(); SamplersCount.StrDescription = TEXT("Compiling..."); SamplersCount.StrDescriptionLong = TEXT("Compiling..."); InterpolatorsCount.StrDescription = TEXT("Compiling..."); InterpolatorsCount.StrDescriptionLong = TEXT("Compiling..."); TextureSampleCount.StrDescription = TEXT("Compiling..."); TextureSampleCount.StrDescriptionLong = TEXT("Compiling..."); VirtualTextureLookupCount.StrDescription = TEXT("Compiling..."); VirtualTextureLookupCount.StrDescriptionLong = TEXT("Compiling..."); StrShaderErrors.Empty(); } void Empty() { ShaderInstructionCount.Empty(); SamplersCount.StrDescription.Empty(); SamplersCount.StrDescriptionLong.Empty(); InterpolatorsCount.StrDescription.Empty(); InterpolatorsCount.StrDescriptionLong.Empty(); TextureSampleCount.StrDescription.Empty(); TextureSampleCount.StrDescriptionLong.Empty(); VirtualTextureLookupCount.StrDescription.Empty(); VirtualTextureLookupCount.StrDescriptionLong.Empty(); StrShaderErrors.Empty(); } bool HasErrors() { return !StrShaderErrors.IsEmpty(); } };
解决办法
方法1:通过Build配置访问私有头文件
这种方法允许你直接使用官方的FMaterialStatsUtils逻辑,需要修改插件的.Build.cs文件添加私有头文件路径:
using System.IO; using UnrealBuildTool; public class YourEditorPlugin : ModuleRules { public YourEditorPlugin(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", "UnrealEd", "MaterialEditor" // 必须依赖MaterialEditor模块 } ); // 添加MaterialEditor私有头文件的路径 string EngineDir = Target.RelativeEnginePath; PrivateIncludePaths.Add(Path.Combine(EngineDir, "Source/Editor/MaterialEditor/Private")); } }
修改后,就可以在代码中直接包含MaterialStats.h并访问结构体成员了:
#include "MaterialStatsCommon.h" #include "MaterialStats.h" // 现在可以正常包含 void MyEditorBPLibraryName::GetMaterialStatsInfo(UMaterial* Material, int32& BasePassShaderInstructionCount, int32& VertexShaderInstructionCount, int32& TextureSampleCount) { // 初始化输出参数 BasePassShaderInstructionCount = 0; VertexShaderInstructionCount = 0; TextureSampleCount = 0; if (!Material) { return; } const FMaterialResource* MaterialResource = Material->GetMaterialResource(ERHIFeatureLevel::SM5, EMaterialQualityLevel::Num); if (!MaterialResource) { return; } FShaderStatsInfo OutInfo; OutInfo.Reset(); FMaterialStatsUtils::ExtractMatertialStatsInfo(OutInfo, MaterialResource); // 提取BasePass像素Shader指令数 if (const FContent* BasePassContent = OutInfo.ShaderInstructionCount.Find(ERepresentativeShader::BasePassPixel)) { FString NumStr = BasePassContent->StrDescription.Split(" ").Get<0>(); BasePassShaderInstructionCount = FCString::Atoi(*NumStr); } // 提取BasePass顶点Shader指令数 if (const FContent* VSContent = OutInfo.ShaderInstructionCount.Find(ERepresentativeShader::BasePassVertex)) { FString NumStr = VSContent->StrDescription.Split(" ").Get<0>(); VertexShaderInstructionCount = FCString::Atoi(*NumStr); } // 提取纹理采样数 FString SampleNumStr = OutInfo.TextureSampleCount.StrDescription.Split(" ").Get<0>(); TextureSampleCount = FCString::Atoi(*SampleNumStr); }
注意:这种方法依赖引擎私有API,不同Unreal版本可能会修改FShaderStatsInfo的结构或FMaterialStatsUtils的实现,需要做好版本兼容性测试。
方法2:自行实现材质统计逻辑(无私有API依赖)
如果不想依赖私有API,可以直接从FMaterialResource中获取Shader并统计指令数,逻辑参考官方实现但完全自主:
#include "Materials/Material.h" #include "Materials/MaterialResource.h" #include "ShaderCore.h" #include "BasePassRendering.h" // 包含BasePassShader的定义 void MyEditorBPLibraryName::GetMaterialStatsInfo(UMaterial* Material, int32& BasePassShaderInstructionCount, int32& VertexShaderInstructionCount, int32& TextureSampleCount) { // 初始化输出参数 BasePassShaderInstructionCount = 0; VertexShaderInstructionCount = 0; TextureSampleCount = 0; if (!Material) { return; } const FMaterialResource* MaterialResource = Material->GetMaterialResource(ERHIFeatureLevel::SM5, EMaterialQualityLevel::Num); if (!MaterialResource) { return; } // 获取BasePass像素Shader并统计指令数 const FShader* BasePassPS = MaterialResource->GetShader<FBasePassPixelShader>(); if (BasePassPS) { BasePassShaderInstructionCount = BasePassPS->GetNumInstructions(); } // 获取BasePass顶点Shader并统计指令数 const FShader* BasePassVS = MaterialResource->GetShader<FBasePassVertexShader>(); if (BasePassVS) { VertexShaderInstructionCount = BasePassVS->GetNumInstructions(); } // 纹理采样数统计:参考官方FMaterialStatsUtils的实现,遍历Shader的资源绑定或使用Shader编译器输出 // 这里可以通过解析Shader的反射信息或统计采样器数量来实现 // 示例逻辑(需要根据引擎版本调整): // TextureSampleCount = MaterialResource->GetNumTextureSamples(); }
这种方法不依赖私有API,稳定性更高,但需要自行实现纹理采样数的统计逻辑,可参考引擎中FMaterialStatsUtils::ExtractMatertialStatsInfo的源码实现。
内容的提问来源于stack exchange,提问作者VictoriaL

