DreamGUI
工具

调试与测量

不开调试器看 DreamGUI 画了什么、花了多少:DreamUI.Capture、DreamUI.Stats 与 Insights 作用域、stat DreamGUI、DreamGUI.Memory、两个校验开关、诊断命令、可供 A/B 的控制台变量和日志分类。

两个问题——"它画了什么"和"它花了多少"——都能不用调试器回答。

它画了什么:DreamUI.Capture

DreamUI.Capture [Directory]

把当前世界的 UI 写成 PNG:视口一张,每个渲染到渲染目标的根画布各一张。不给目录时写进 Saved/DreamUI/Captures 下一个新建的、带时间戳的目录。画布直接画在屏幕上时没有自己的图——它是视口那张图的一部分, 截视口就行。

同样的事是一个 Blueprint 库,UDreamUICaptureLibrary:

函数作用
CaptureAll控制台命令做的事:Viewport.png 加每个渲染到目标的根画布的 <widget 名>.png,返回写出的文件
SaveViewportToPng世界所在的视口——场景加上面的每个面板,玩家看到的样子
SaveCanvasToPng一个渲染到目标的画布画了什么
SaveRenderTargetToPng一张渲染目标的像素,保留 alpha
GetCaptureDirectory<Project>/Saved/DreamUI/Captures,相对路径从这里算

每次调用都要把图从 GPU 读回来,所以会等渲染线程把手上的活做完:这是用来看的工具,不是每帧做的事。没有 GPU (-nullrhi)时没东西可读,调用在日志里说明并返回 false。在游戏里,视口是直接画进窗口的、帧与帧之间没有纹理, 所以 SaveViewportToPng 返回 false 并提示改用引擎自己的截图请求。

它花了多少:DreamUI.Stats 与 Insights

DreamUI.Stats

打印自上一次 DreamUI.Stats 以来的帧逐阶段的花费,然后重新开始计数——所以在想测的那段前后各敲一次。

阶段是什么
ManagerTickUI 管理器的 tick
CanvasUpdate画布更新
Batching合批
DrawCallSubmit提交绘制调用
RenderRecord渲染线程录制

每个阶段给出每帧毫秒数和运行次数。后面是计数器,每帧平均和总数:BatchesRecorded、VerticesRecorded、 SectionUploads、UploadedBytes、DataTextureUpdates、GeometryCopies、SectionReuses、WidgetsUpdated、 SectionPatches、DrawCallRebuilds、InPlaceRefreshes、渲染层的 RenderLayerMoves / Promotions / Demotions, 以及文字的 TextPaints、TextMoveRepaints、SmallTextPlacements、SharpenSweepTexts、SharpenRepaints、 CoverageItemsDrawn、CoverageGlyphLookups、CoverageRastersSync、CoverageJobs、CoverageFlushes、 FontAtlasUploadBytes。

在 Unreal Insights 里,每个阶段也是一个具名作用域,名字以 DreamUI_ 开头(DreamUI_LayoutPass、 DreamUI_UpdateRootCanvases、DreamUI_TextLayout、DreamUI_RecordScreenSpace、DreamUI_WorldSpaceRaycast……), 在 CPU 通道上。每个画布、每个分段、每个 widget 都要跑一次的细粒度作用域在一个单独的通道 DreamUIDetail 上,默认关闭—— 几千个 widget 的墙上它们会跑几千次,每次读两遍时钟,开着的一帧就说明不了没开的一帧。需要时:

-trace=default,DreamUIDetail
Trace.Enable DreamUIDetail

前者在命令行上,后者在控制台里。

stat DreamGUI 是插件的 stat 组,显示画布的合批、绘制调用更新、几何体更新、顶点变换、布局、属性绑定轮询、 根画布更新等计时,以及动画播放器的两个计数(自己计时的、由 Sequencer 更新的)。

它占了多少:DreamGUI.Memory

DreamGUI.Memory
DreamGUI.Memory Json
DreamGUI.Memory File=<path>

每个字体的字形图集(切片数、GPU 字节数和 CPU 副本、格子、字形、字体文件字节)、精灵图集页,以及每个有 UI 管理器的世界里 画布网格的分段和绘制参数行。Json 改打印 JSON,File= 把 JSON 写进文件(相对于 Saved)。打包测试里的探针就是用它 记录内存报告的。

两个校验开关

为一个"画错了"或"没画出来"的报告准备的:

变量打开后
r.DreamUI.VerifyPartialPrepare 1画布每次从上一次增量准备(只重做请求了的、新来的、移动了的 widget 画的东西),都和一次全量准备对比;有差异就是一个点名画布的 ensure
r.DreamUI.VerifyKeptPointers 1DreamGUI 不每帧查找而是保留的对象——widget 的画布、画布的渲染层、UI 管理器的画布、动画属性绑定的对象——在每次使用时也查找一遍;不一致就是一个说明是哪个的 ensure

两者默认都关,开着有代价(全量准备、查找本来省下的开销)。测试宿主在 Config/DefaultEngine.ini 里把它们打开, 所以整个测试套件都在它们下面跑:

[ConsoleVariables]
r.DreamUI.VerifyPartialPrepare=1
r.DreamUI.VerifyKeptPointers=1

基准测试(Tools/Bench)在测量的会话里会把它们关掉。文字的增量布局有自己的对应开关: DreamGUI.Text.VerifyIncremental 1 让每次基于保留状态的布局都再从头布一次并比较,第一处差异写进日志,用新的那份。

诊断命令

命令作用
dreamgui.ListPendingWidgets列出已经创建但还没加到任何地方的 widget(ConstructWidget 之后、AddChild 之前的"停放"状态),以及它们等了多久。把它们放在一个具名数组里是为了不被回收,能列出来是为了不让那里变成东西悄悄堆积的地方
DreamGUI.Diag.FindTreeBridges列出当前世界的关卡持有的对象(actor、它的组件或子对象)指向某棵 DreamGUI widget 树的每一个引用,以及那是谁的树。播放会话复制关卡时会顺着这种引用进入树
DreamGUI.Text.ShapeCacheFlush清空文字整形缓存,计数器保留
DreamUI.ExportSymbols重写 DUI/.dui-symbols.json(编辑器),见 VS Code 扩展

追踪类变量,打开后往日志里写:dreamgui.LayoutTrace 1(每次面板排列和它提交的每个矩形)、 dreamgui.ScrollBoxTrace 1(每次改变状态的滚动框拖动增量和物理 tick)、dreamgui.DumpMaterialDraws 1 (每次屏幕空间材质绘制尝试走了哪个分支,直到设回 0)。

可以拿来做 A/B 的开关

这些变量存在是为了测量:一次会话里先测 A,切换,再测 B。默认值就是出厂行为。

日志分类

分类谁写
DreamGUI核心以及输入、控件、扩展、示例模块
LogDreamGUIRenderer渲染器
DreamTweenTween
DreamGUIEditor设计器和资产工具(文本编译、写回、桥)
DreamGUIK2NodesBlueprint 节点
LogDreamGUIReferenceDocs参考文档 commandlet

用引擎自己的方式调详细程度,比如命令行上 -LogCmds="DreamGUI Verbose",或控制台里 Log DreamGUI Verbose。

本页目录