又又年更了
最近无意中打开Instancing 的一个DrawCall查看
又又年更了
最近无意中打开Instancing 的一个DrawCall查看
发现里面有4个Buffer, 一直没有仔细看过这些东西的内容就好奇点开看看
Globals 里包含了 全局参数部分
比如光照位置,颜色,相机信息,阴影信息,时间等
UnityPerMaterial 里是自定义材质上的一些参数
UnityDrawCallInfo 是Instancing的一些Setup信息
然后最大的一个Buffer叫 UnityInstancng_PerDraw0
对于一个最简单的Shader来说,里面包含了
UnityObject2World * InstanceCount
UnityWorld2Object * InstanceCount
于是一个想法突然蹦出,对于最简单的Shader来说,没有用到 UnityWorld2Object, 那么是不是可以
将图中的12888 优化至 6444, 既优化一半
于是在优化之前先写一个Demo 用来验证这个优化的可行性和必要性
思路如下
1.使用MaterialPropertyBlock 来存储所有的M矩阵
2.移除Shader中所有的Unity相关矩阵的引用,这样在Shader编译时会触发相关Buffer编入的移除
3.测试当传仅传入 Object2World Array 和 传入 Object2World Array + World2Object Array 的性能开销
C#准备部分
Shader准备部分
测试场景 一个DrawCall 1023个Instance
左侧一个按钮用来控制 Buffer优化方案的切换
方案运行后在RenderDoc的 BufferSlot下,成功让UnityInstancing_PerDraw0 这个Buffer消失,同时替代的TestProp出现在列表上
点开TestProp 发现仅有上传的M矩阵
Demo准备完毕,开始打包真机测试性能
测试模型
使用SDP进行Snapshot分析
未优化时
优化后
差异部分 size 16384(无优化) size 8192(优化后)
AvgByte 减少 5%
ReadTotal 减少 3%
GPU频率 减少 5%
频率分析
切换方案后,成功得到一个三角形曲线,说明方案有效
将方案合入项目后,发现 GPU虽然得到了优化,但是CPU测的内存上升的非常快
且触发了Unity经典错误
Property (TestMat) exceeds previous array size (821 vs 2). Cap to previous size. Restart Unity to recreate the arrays.
对于这个经典错误可以简单通过Clear MaterialPropertyBlock 或者初始化创建一个1023的Array解决,不过需要耗费很多内存和GC
如果不每帧设置MaterialPropertyBlock 的MatrixArray,可以解决GC问题, 但是内存依然会升高
通过查看源码发现
InstancedRendering的时候,如果有传入MaterialPropertyBlock 就会触发 sourceData.CopyFrom(MaterialPropertyBlock)
所以只要使用MaterialPropertyBlock 就必然导致内存变大
并且发现Unity在源码中写死了Object2World的参数名
那么有没有办法不改变Object2World参数名的情况下解决MaterialPropertyBlock 的内存占用,又解决不额外传入World2Object矩阵的问题呢
实验了几次后发现只能放弃使用MaterialPropertyBlock
好在PerDraw Buffer的定义不在源码中,而是在外部的Package里
直接去修改
Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl
找到PerDraw Buffer的定义
将UNITY_DEFINE_INSTANCED_PROP(float4x4, unity_WorldToObjectArray) 移除,并修改相关引用部分
最后的结果就是不使用MaterialPropertyBlock, 使用一个自定义宏去切换UnityInstancing.hlsl里PerDraw Buffer的Layout,在自己的Layout里移除不用的部分
虽然蚊子腿小,但是蚊子腿也是肉啊
Done!