渲染引擎的资源加载优化

2022-06-30

渲染引擎的资源加载优化

针对一个渲染引擎的性能,最主要的当然是实时渲染的帧率。另一方面,对于轻型的引擎来说,用户不会对场景加载
保有一个较长的加载时间的心理预期,最好是,点击按键,瞬间整个场景出现,毕竟轻量应用场景不是重型游戏。针对
这种需求,我们需要对资源加载做一个全面的性能排查与优化。

Steps

根据《Unix编程艺术》中的描述,性能优化最好是不要优化,如果要优化那么花费一点时间把 bottle neck 找到是很有
必要的。所以在我的优化工作中,我首先对整个系统做了较为完整的 profile. 我大致将整个资源加载部分逻辑分成了
自顶向下的三层:

  1. meshrender 层:整个场景存在若干个 meshrender,该层级统计各个 meshrender 的加载时间
  2. resource 层:每个meshrender 依赖若干资源:mesh, material, skin/animation 等等
  3. detailed resource 层:对每个 resource 的详尽的分部统计,不同的 resource 步骤也不同

MeshRender

对我们的整个引擎的加载部分,分层级自上而下统计,对每个 meshrender 统计得到的情况如下表,其中分别有几个模块:
Mesh Material Skin 各占一部分,同时图片的加载已经使用了异步线程加载方式,故不统计在总时间内(加载图的子
线程在主加载线程完成任务之前就已经完成结束)。从下表数据看,Mesh 与 Material 均占较大部分的比例,所以在下面
detailed 层,专门针对这两个部分继续剖析。

- Img Load(Async) Mesh Material Skin Others Total
Current 31.07 40.85 101.70 8.27 3.04 153.86

Detailed

Detailed 分析分别对几个不同的模块进行了分析。其中 Mesh 的加载虽然耗时较长,但是实际时间绝大部分用在将 mesh
数据从文件中读取到内存,并没有发现其他可能浪费时间的耗时操作,逻辑上也比较简单,故结论是无优化空间。

另外 Material 部分,其逻辑就比单纯加载一块 mesh 的buffer 要复杂许多了。其中包括了特定对象 MaterialAsset 的
创建与初始化,shader 的加载以及解析,启动异步读图线程等等。具体的耗时分布如下表所示。

- Get AbsPath Material Asset Shader Async load imgs Others Total
Current 1.38 1.77 8.20 1.71 1.10 14.15

其中 shader 部分是耗时大头,此处 shader 并不仅仅包括 shader 文件的加载,还有其中一些 uniform 变量的解析,
探究其逻辑发现两点

  • shader 文件读取存在一次多余的 copy
  • 其中有一个超过20个 case 的 switch 语句

针对这两者情况我都做了相应的优化

  • 使用数据指针代替原有的 std::vector<> 在传递时几乎没有损耗
  • 使用查表的方式替代 switch-case 语句,提高性能同时也提升代码质量,减少圈复杂度,同时更便于维护和更新

优化以后的数据如下表

- Get AbsPath Material Asset Shader Async load imgs Others Total
Current 1.38 1.77 8.20 1.71 1.10 14.15
Optimize 1.52 1.85 7.44 1.28 0.52 12.61

在 Skin 的加载部分同样找到了部分多余的值 copy 操作,优化为引用后数据略有提升。

总结

最后我们来看一下总体的性能提升情况

- Img Load(Async) Mesh Material Skin Others Total
Current 31.07 40.85 101.70 8.27 3.04 153.86
Optimize 33.62 36.40 80.81 6.88 3.28 127.37
Performance of resource loading

              Mesh                    Material               Skin Others
before opt  xxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxx|x|
after opt   xxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxx|xx|x|