https://unity.com/cn/resources/optimize-mobile-game-performance-unity-2022lts Game Programming Patterns
UGUI的大量元素会导致大量的Draw Call,每个UI元素需要单独进行渲染处理,过多的Draw Call会占用CPU资源,影响帧率
优化目标:
- 减少Draw Call数量
- 减少Canvas更新频率
- 压缩资源
- 针对平台优化
- 减少半透明的使用
默认情况下,Canvas会对挂载在其下面的所有UI元素进行一次性绘制(批量处理,合并Draw Call) 当某个UI元素(例如按钮、文本、图片)发生变化,Canvas会重新计算这些元素的布局和绘制信息。 为了确保正确的结果,Canvas通常需要重新计算并重新绘制 整个Canvas 下的内容,而不是只更新改动的部分。
Canvas
利用批处理来降低Draw Call频率,将 UI 层级下的所有内容集中在一次绘制操作中完成
如果你有一个非常巨大的Canvas,拥有上千的元素,更新一个元素会强制整个Canvas做更新,这会产生CPU性能尖刺。
为什么Canvas被设计成更新一个元素就要更新整个Canvas?具体实现是怎样的?
uGUI的源码大量使用对象池 因为Graphic对象需要频繁获取Canvas列表或Component列表
Canvas Rebuild做了什么 实现ICanvasElement接口的类需要实现自己的Rebuild方法 对于视觉UI元素 Runtime.Core.Graphic
- 如果顶点有更新,那么就为CanvasRenderer重新生成网格
- 如果材质和纹理有更新,那么就为CanvasRenderer更新纹理和材质 其他非视觉元素,大概就是更新以下各自维护的数据结构
什么情况下会触发Canvas Rebuild - Runtime.Core.CanvasUpdateRegistry
- LayoutRebuildQueue
- GraphicRebuildQueue 对外提供接口
- RegisterCanvasElementForLayoutRebuild
- RegisterCanvasElementForGraphicRebuild
Graphic Rebuild Runtime.Core.Graphic
- SetVerticesDirty
- SetMaterialDirty
Layout Rebuild Runtime.Core.Graphic
- SetLayoutDirty 由Runtime.Core.Layout.LayoutRebuilder负责实现
Graphic类型由具体的子类在业务逻辑中负责调用各类SetDirty方法
在哪里实现了,改变Canvas的一个子元素就要使整个Canvas的所有子元素都Rebuild?
Layout相关变化会触发Layout Rebuild,如果子元素的布局影响了父元素的布局(ContentSizeFitter或LayoutGroup,动态添加或移除子元素),则会导致整个Canvas的布局重新计算
视觉相关变化(修改Image的Sprite,修改Text内容等),只触发Graphic Rebuild,通常只影响该元素自身,如果子元素的改动影响了父元素的渲染(Mask, CanvasGroup),则父元素也需要进行Rebuild
如果某个子元素的变化导致了顶点数据或渲染顺序的变化,Unity 可能需要重新生成整个 Canvas
的批次数据。
Runtime.Core.GraphicRegistry
RegisterGraphicForCanvas(Canvas c, Graphic graphic)
合批规则的源代码在哪? Canvas, CanvasRenderer是在引擎内部代码,C++实现吗?怎么去追踪 找到了Unity C# Binding,但没有实现 https://github.com/Unity-Technologies/UnityCsReference/tree/master
也就是说,合批规则,需要从现有的资料触发,通过观察Frame Debugger,然后实验出来。没办法直接从最新版本的引擎代码里解读出来,因为Canvas的实现代码没有暴露出来。
https://blog.csdn.net/sinat_25415095/article/details/112388638