1.名词解释

User Widget:对应一个用户界面。

Widget Tree:每一个 User Widget 都是存储成树状结构。

Panel Widget:不会渲染出来,用于对 Child Widget 进行布局,如 Canva Panel, Grid Panel, Horizontal Box 等。

Common Widget:用于渲染,会生成到最后的 Draw Elements 中,如 Button, Image, Text 等。

2.渲染流程

在游戏线程 (Game Thread),Slate Tick 每一帧会遍历两次 Widget Tree。

Prepass:从下到上遍历树,计算每一个Widget的理想尺寸 (Desired Size)。

OnPaint:从上到下遍历树,计算渲染所需的 Draw Elements 。这个过程中,会根据 Common Widget 的类型和参数生成相应的 Vertex Buffer,将 Common Widget 的 Render Transform 计算到 Vertex Buffer 中,并根据 Layer ID 和 Material 等信息进行批次合并。最后一个 User Widget 会生成1个或多个 Draw Element,并将 Draw Elements 传递给渲染线程进行渲染,其中每个 Draw Element 对应一个 Draw Call。

在渲染线程 (Render Thread),Slate 渲染分为两步:

Widget Render:执行 UI 的 RTT,如果使用了 Retainer Box,这里会将 Draw Elements 渲染到 Retainer Box 的 Render Target。

Slate Render:将 Draw Elements 渲染到 Back Buffer,如果使用了 Retainer Box,会将 Retainer Box 对应的 Texture Resource 渲染到 Back Buffer。

3.优化:游戏线程

1.invalidationBox

使用 Invalidation Box 封装 User Widget,从而缓存 Slate Tick 数据,不需要每帧都进行计算。在 Invalidation Box 下的所有 Prepass 和 OnPaint 计算结果都会被缓存下来。如果某个 Child Widget 的渲染信息发生变化,就会通知 Invalidation Box 重新计算一次 Prepass 和 OnPaint 更新缓存信息。

当某个 Widget 的渲染信息变化时,会通知所在的 Invalidation Box 重新缓存 Vertex Buffer。在一个复杂的 User Widget 中,Invalidation Box 频繁缓存整个 Widget Tree 会带来很高的性能开销,有两种方式可以解决这个问题。

解决方法: i) 拆分InvalidationBox

ii)将 Widget 设定成 Is Volatile,这样上层的 Invalidation Box 在缓存时就会排除这个 Widget,该 Widget 每帧都会 Tick 并计算 Prepass 和 OnPaint,但整体 Widget Tree 的缓存不会受到影响。

如果在影响 Draw Element 的属性上使用了 Widget Binding,会导致引擎每帧都要 Tick 查询是否属性发生变化,从而判断是否需要更新 Draw Element,因此应该避免使用 Widget Binding。

Debug: 可以通过 Slate.InvalidationDebugging 查看是否正确地设置了 Invalidation Box 和 Volatile。Slate.AlwaysInvalidate 命令可以强制 Invalidation Box 每帧更新缓存,可以用于测试是否会造成突然的卡顿。如果一个 User Widget 过于复杂,可以拆分成多个 Invalidation Box,将 Widget 按照更新频率的高低放入不同的 Invalidtion Box。

        2. 合理设置(Widget Visibility)

Widget 可见性有 5 种:

        Visible: 可见、可点击

        HitTestInvisible: 可见、当前 Widget 不可点击、所有 Child Widget 不可点击

        SelfHitTestInvisible: 可见、当前 Widget 不可点击、不影响 Child Widget

        Hidden: 不可见、占用布局空间

        Collapsed: 不可见、不占用布局空间

很多 Widget 默认属性是 Visible,需要手动设置成 HitTestInvisible 和 SelfHitTestInvisible。如果大量 Widget 设置成 Visible,那么引擎在点击响应时的效率就会大大下降,这也会增加游戏线程的开销。

Collapsed 不占用布局空间(Layout Space),因此在隐藏后不会进行 Prepass 的计算,性能优于 Hidden。

可以使用 Widget Reflector 帮助检查是否有错误设置的 Visibility 属性

        3.Widget Binding

在分析 Volatile 时提到过 Widget Binding 会导致 Volatile 从而降低 UI 性能。另外 Widget Binding 是每帧 Tick 执行,性能比较低。不建议在项目中使用这个功能,建议通过 C++(或蓝图)调用函数的方式传值。

RemoveFromViewport/AddToViewport 会销毁以及重新构建 User Widget,使用 Collapsed/SelfHitTestInvisible 可以得到更好的性能。

另外,在移动平台上建议将蓝图 Tick 中复杂的运算逻辑移动到 C++ 中。

4.优化:渲染线程优化

1.合并批次

CanvasPanel在4.15之前不支持合并。

4.15 增加了对 Canvas Panel 合并批次的支持,开启方式位于 Project Settings 中:"Engine->Slate Settings->Constraint Canvas->Explicit Canvas Child ZOrder"。

接着可以通过设定 Canvas Panel 的 Child Widget 的 ZOrder 属性,ZOrder 相同(渲染参数也相同)的会合并批次,比起 Grid Panel 和 Horizontal Box,Canvas Panel 没有额外的布局计算,OnPaint 效率会稍微高一些(游戏线程)。

2.合并贴图

在 UE4 中的 Sprite 很方便地支持合并贴图的编辑和使用

       3. Retainer Box

通过合并批次和合并贴图的方式,UI 的 Draw Call 数量可能减少到比较低,但仍然会有很高的像素填充率。

在很多情况下,UI 不需要每帧都渲染,因此可以通过 Retainer Box 缓存渲染结果,每隔几帧更新一次。Retainer Box 的原理就是将 UI 渲染缓存在 Render Target上,再将  Render Target 渲染到屏幕。

i)目前 Retainer Box 需要指定每隔几帧强制更新一次,但某些情况下 User Widget 不需要按照固定频率更新,只会在用户操作(且操作不频繁)时才更新。这种情况下就可以通过扩展 Retainer Box 来支持事件驱动的方式。

实现思路是继承 URetainerBox 和 SRetainerWidget,并在 PaintRetainedContent(在 4.16 之前的版本函数名是 OnTickRetainers)中判断是否有事件触发更新,如果需要更新则调用父类的 PaintRetainedContent,否则 return。

5.UI Bug

释放贴图内存的一个前提是不要在编辑中设置贴图(下图中的 Image 项),而是通过程序进行手动的贴图加载、贴图设置、以及贴图销毁。不在编辑器中设置贴图,可以避免在 CDO(Class Default Object)中引用这个贴图对象。CDO 的引用会使得 SharedPtr 的引用计数至少为1,并且退出应用前不会销毁。

​​​​​​​        

如果在 Editor 中设置了 Image 属性,同时又希望销毁这个贴图,Epic Games 的王弥提供了一个思路,可以在 Cook 阶段解除 UImage 和 UTexture 的引用关系,从而这个 User Widget 的 CDO 不会引用到 UTexture。

Ue4 UI优化文档整理理解相关推荐

  1. [-UI设计-] UI设计文档

    今天不想说UI设计原理和原则,只想说文档内容.一个solo族,可能根本不会理睬这种文档,因为他的idea完全在脑中,自己来实现就OK了.而在一个团队协作过程中,产品设计部门就有很高的必要把这个文档写好 ...

  2. 将Html文档整理为规范XML文档

    有多种方式可以在.NET 平台进行HTML文件解析.数据提取,其中最简单.稳妥的办法是先使用工具将Html文档整理成XML文档,再通过XML Dom模型或XPath灵活地进行数据处理.SGML便是一个 ...

  3. [官方] mysql 性能优化文档(中英文自译)

    大家好,我是烤鸭: 根据官方文档翻译并精简部分内容.建议有时间的朋友下载原版查看,全文106页pdf,快的话1-2天就能看完.自己翻译的有些地方可能不完整,欢迎指正. 官方pdf下载,需登录: htt ...

  4. django+nginx+uwsgi项目部署文档整理

    django+nginx+uwsgi项目部署文档整理 参考文章:https://blog.csdn.net/qq_42314550/article/details/81805328 一.python安 ...

  5. NodeJS-001-Nodejs学习文档整理(转-出自http://www.cnblogs.com/xucheng)

    Nodejs学习文档整理 http://www.cnblogs.com/xucheng/p/3988835.html 1.nodejs是什么: nodejs是一个是javascript能在后台运行的平 ...

  6. iOS端的UI设计文档

    iOS端的UI设计文档 APP和网站,风格色调始终注意保持一致(平台一致性) 在App不断更新的过程中定义设计准则.风格.规范 设计规范: 1.分类合理(为了能让用户快速查找,合理的分类必不可少) 2 ...

  7. 2503平台GPS MT3333秒定参考文档整理 - MTK物联网在线解答 - 技术论坛

        2503平台GPS MT3333秒定参考文档整理 [DESCRIPTION] 以下是目前整理的给客户参考的2503秒定测试及GPS介绍的文档,其中均可在DCC上下载. [SOLUTION] 2 ...

  8. 韩顺平php可爱屋源码_韩顺平_php从入门到精通_视频教程_第20讲_仿sohu主页面布局_可爱屋首页面_学习笔记_源代码图解_PPT文档整理...

    韩顺平_php从入门到精通_视频教程_第20讲_仿sohu首页面布局_可爱屋首页面_学习笔记_源代码图解_PPT文档整理 对sohu页面的分析 注释很重要 经验:写一点,测试一点,这是一个很好的方法. ...

  9. Android 学习文档整理收集

    利用闲暇时间整理了一份 Android 学习文档整理收集,希望能够对大家有所帮助,也欢迎各位帮忙补充. Android Android基础入门教程 CSDN主题Android专栏 极客头条Androi ...

最新文章

  1. 22个案例详解 Pandas 数据分析/预处理时的实用技巧,超简单
  2. day18 17.c3p0连接池使用
  3. spring aop实践_使用Spring AOP实现活动记录模式
  4. 出现在海马#30524;前的c++
  5. Java Servlet的配置文件web.xml配置内容和具体含义
  6. AS3多线程快速入门(一):Hello World[
  7. 嵌入式编程(二):ARM单片机如何将函数 定义到指定程序地址
  8. 解决本地工具无法连接服务器上的mysql的问题
  9. vue学习笔记-vue双向数据绑定
  10. 网站如何设置一个小图标
  11. 985计算机硕士考公,985大学生不愿意考公务员?并不是瞧不起,真实原因有点扎心...
  12. [百家号]大英帝国的人口和面积比现在的英国大多少?
  13. HJ70 矩阵乘法计算量估算 ——
  14. LZ77压缩算法原理剖析
  15. 腾讯游戏扫码登录源码
  16. uniapp接入微信客服聊天流程(企业微信)
  17. 八道简单入门编程题详解+拓展(水花仙,二进制序列……)
  18. 现代通信原理1:绪论
  19. 伺服驱动器和电机控制
  20. 重型鼓音源混音教程|没有鼓手没关系,教你如何用Guitar Pro 5的midi鼓变成真鼓声!(鼠标党必备)| MZD studios

热门文章

  1. java公路车组装教程_骑行入门:怎样组装一辆自行车——零部件的准备
  2. 1980-2018年全国30米土地利用类型样例数据
  3. 直播带货系统,带货直播系统中发布商品的逻辑处理流程
  4. 世界历史50大著名统治者
  5. 在NW.js里面使用node-printer
  6. HDMI接口类型种类区分图(高清图)
  7. 看大数据平台如何打造餐饮业务一体化?
  8. 小乌龟html5小游戏,晨会互动小游戏之《抓乌龟》
  9. FPGA错误集锦(二):Output pins are stuck at VCC or GND
  10. 华为设备用户接入与认证配置命令