像素迷踪,当Unity的Frame Debugger力不从心时
http://www.manew.com/thread-92382-1-1.html
从版本5开始,Unity包含了一个全新的可视化帧调试工具,Frame Debugger。该工具能帮你解决很多图形方面的问题,Z-fighting,GPU状态不正常,渲染队列错误、混合操作错误,过多的draw call,效率低下等等。相比游戏视图中的状态列表,它提供了更加详尽的信息,通过与渲染事件/步骤的交互和检查,你也能学习到大量GPU管线的相关知识。真地,每个开发人员都应该了解这个工具。
本期内容中,我会简要的叙述Frame Debugger的使用方法,并在之后指出其不足之处和其他可以弥补这些问题的工具。我不打算面面俱到,只是对这些内容做一个总体概览。
1. 使用Frame debugger
使用Window-> Frame Debugger菜单打开其主窗口,我建议同时打开frame debugger 和game view (游戏视图)。为此你可能需要对窗口布局做些调整,本人用来调试图形显示时的界面如下图:
Recommended layout (except for the Asset Store tab)
建议的窗口布局(除了资源商店标签页)
使游戏进入播放状态,来到让你头痛脑热、心情不爽的位置,在frame debugger窗口中,点击enable就能得到最近渲染帧的细节信息。
其中包含了两个主要部分:事件,以及在其右侧的详细信息。事件中基本上都是CPU发送给GPU的命令,相当的简单,毕竟在此无法看到真正被调用的API函数,只是将整个过程按照时间顺序打包显示出来。其中大部分是清除和绘制(不透明/透明)几何体的命令,但是你也可以注意到更多的信息,如:GUI渲染,阴影处理,图形效果等以及相应的回调次数。
在选择一个事件后,你可以立即在details面板中得到关于该事件的详细信息。包括:着色器范围和其标志,渲染输出事件和着色器属性信息。同时,游戏视图会显示在该事件发生前已被渲染的对象(包括当前事件哦)。下面是一个例子
<ignore_js_op>
Event example
Event example
2. Frame Debugger干嘛用?
Frame debugger为我们提供了一些有价值的信息。绘制事件的调用顺序(也即是:渲染队列)对渲染的正确性十分重要,如果游戏中的元素未能正确的显示粗来,在此你就可能发现它未被正确的放入渲染队列中,从而导致过早或延迟渲染;特别是在进行自定义着色器开发或Z write(深度缓冲写入)参数调整和测试时。通过这个工具为我解决了不少问题:查找出由于忘掉设置标志位和渲染次序而导致天空盒子覆盖了俺着色器输出的问题。
其次,相对的,你也可以在此查看draw call的调用次数,并通过顶点/索引数量来间接衡量场景的渲染代价。当然,着色器pass数也有帮助意义,但其复杂度在此没有显示,这就要求你得有些基本常识了,所有这些信息都能帮助你提高场景的性能,比如:你会发现出于某些原因一些网格绘制未能进行静态/动态批处理。在我的例子中,你会发现通过使用材质图集来将两个图片精灵的2次draw call操作合并为1个。
第三,通过与Frame Debugger交互,你可以快速学习GPU的体系架构知识并了解Unity渲染处理的流程。可以用键盘在不同的事件中跳转一步步查看场景的渲染过程。在之前的例子中,可以看到场景渲染开始于对三个缓存的清理(颜色、z、stencil),接着是不透明几何体(从前面到背面),天空盒子和透明几何体(从背面到前面)的渲染。
最后,你可以访问着色器属性以获得关于材质和着色器的更多信息,也可以得到被对象使用的数据的引用,如:材质。大多数人都没用过Unity中的advanced views,你可以在inspector中通过点击右上部的paragraph图标,并选择“Debug”来访问他们。
<ignore_js_op>
Shader properties
着色器属性
2. Caveats
在为一个客户工作期间,我也碰到了由于Frame Debugger过于简洁的界面而导致的问题。我在Unite 2016 Europe中与Unity的一些开发做了交流,他们也清楚这种情况,但针对未来是否打算对其进行扩展时他们并没表态。从我的经验看,主要的问题有:
- 无法获得事件之后API调用的底层信息。就这个问题来说,Unity将事件作为一个黑盒处理了,因此,在特定的条件下你将很难对这个问题作出改进。
- 无法获得每个事件中GPU管线的总体状态,只能得到关于几何体和材质的信息,而缺少在不同阶段,像顶点/几何体/片段着色器和栅格化器中的数据。
- 无法真正的调试着色器。要么采用修改输出颜色来进行代码的可视化调试(比打印还遭罪)或者依赖像GLSL-Debugge类的外部软件。
- 难以建立结果像素与写出这些像素的相应事件的关联。你得一步步检查究竟是哪个事件在像素中写入了错误的信息,在有大量的draw call调用时,这过程会相当耗时。
- 缺少能够衡量每个draw call效率的精确指示器,顶点/索引的数量并不是表征渲染效率的最好手段。尽管更多的顶点需要更大的带宽,但是在几何体上执行一个复杂的顶点着色器或代价高昂的状态转换性能可能会更差。
如果至此你还愿意继续看下去,可能你也在寻找针对以上问题的解决方案,使用一些补充性的其他工具怎么样呢?
4. RenderDoc
在进行了一些软件的测试后,我铁定可以为大家推荐几款。你工具箱里的首选必须是RenderDoc,Crytek开发的一款免费工具,用于解决底层调用信息的问题,该工具使用了网络攻击者的经典做法:它建立一个虚拟的图形驱动作为中间人进行(MITM)捕获应用中的DX/GL/Vulkan调用并提供详细的调试信息。好消息是:该工具已经支持在Unity中原生集成,你只需在windows机器上安装RenderDoc并重启unity的编辑器即可。
本节俺的目标是为大家概述该工具的用法,让你充满兴趣的开始,使用时更加顺畅,而且少花电费。
我们开始吧,如果安装顺利,在游戏中你应该可以右键单击并在菜单中选择Load RenderDoc:
<ignore_js_op>
几秒钟后,进入播放模式,如果你对刚渲染的那帧感兴趣,点击最大化播放按钮左侧光头佬一样的新图标:
<ignore_js_op>
接着切换到新打开的RenderDoc窗口,双击自动捕捉的日志开始分析它:
<ignore_js_op>
RenderDoc captured log
Rendoc捕捉日志
哇哦,这就是信息的大海啊,我会淹死的。冷静下来,一切都会顺利。现在你会在几千个窗口中得到所有帧的数据,表担心,下面我会继续讲解真正有趣的内容。
4.1. Event browser + API calls.
4.1 事件浏览器+API调用
该工具的优点就是能够获得每个事件的底层信息,事件浏览器就像Unity中Frame Debugger的一个扩展,其中还包括耗时(按毫秒计)和其他提示信息,能为衡量效率提供大量帮助。RenderDoc具有上下文敏感特征,也就是说,不论何时你选择一个事件,大部分其他窗口和区域将会显示仅与该事件相关的信息,其下的API 调用窗口则列出了为处理该事件而调用的函数。
<ignore_js_op>
RenderDoc: Event Browser + APIcalls
能得到这些耗时数据真的很棒!虽然它们可能不是绝对精确,但是具有相对准确性,也就是说,通过比较耗时你就能推断出哪些操作的draw call需要优化。
4.2. Pipeline state.
管线状态
我经常怀疑渲染管线的工作模式,精巧的几何体是如何进入它的内部并转换为50寸屏幕上像素的?好吧,不幸的是,在这个工具中你也无法得到一个用户友好的动画以展示其工作过程。
但确实有些很好的免费工具可以做到。不过,不管怎样,RenderDoc能让你访问事件中的大量管线状态信息:input assembler,顶点/体/域/几何体/片段/计算着色器,栅格化器和输出合并器。相信俺的判断,RenderDoc不虚其名。
<ignore_js_op>
RenderDoc: Pipelinestate for a pixel shader
<ignore_js_op>
RenderDoc: Output Merger example
<ignore_js_op>
RenderDoc: Rasterizer state
4.3. Mesh output
网格输出
RenderDoc的另一项特征是可以通过顶点着色器的:输入输出位置,法线,纹理坐标等信息获取事件中渲染的网格数据。实际上,你可以用它抓取并保存3D程序或游戏中正在渲染的网格,但在这边文章中我就不结合案例讲解了。
4.4. Texture viewer
纹理观察器
这是我的菜,将事件中的输入和输出纹理进行可视化展示。包括渲染到纹理或其他中间缓冲数据,在你使用中间缓冲进行图像效果处理时会非常有趣,此外,这也也能能节省纹理空间。
<ignore_js_op>
RenderDoc: Texture viewer
4.5. Pixel debugging
像素级调试
在帧缓冲中,错误渲染的坏像素覆盖正确像素的情况并不少见,大多数开发者都碰到过这种噩梦,坏像素在场景中留下的奇怪感受让我们意识到有问题发生了,但却不知原因何在,就是赶脚不对!如何确认我们的猜想并找出问题所在…
是的,以下是该问题的解法:选择一个像素,点击histroy(得到一个在帧缓冲中向那个像素写入信息的事件序列)或者debug(调试它的像素着色器)。调试这些问题需要真正的技术大牛,你需要汇编知识,并对像素着色器的反汇编后的版本有一定了解。
<ignore_js_op>
RenderDoc: Pixel selection
RenderDoc:像素选择
<ignore_js_op>
RenderDoc: Pixel’s history
RenderDoc: 像素历史
<ignore_js_op>
RenderDoc: shader debugger
5. Example
示例
有些同学会怀疑在实际工作中搞出性能分析器意义何在。我这就告诉你:非常必要。在不确定某项特性的性能影响时,使用性能分析器做下测试,不论是frame debugger,或者必要的话,动用RenderDoc等类似工具都会对你的工作大有裨益。
我们来看个示例。不是所有人都知道在脚本中访问材质和访问渲染器的shaderMaterial是有差别的,但只有少数人确切知道这会对性能产生影响,虽然大多数情形下是一样的。不过既然我们手边有工具,何不一探究竟?
我们的测试场景只有一个精灵,反复使用相同的transform实例化10000次后,它占据了几乎全部的视野,因此可以想见,其中有许多无需绘制的部分,代码如下:
void Start () {
for(int i = 0; i < NumberSprites; ++i)
{
varsprite = Instantiate(Sprite);
sprite.transform.parent= transform;
varsharedMaterial = sprite.sharedMaterial;
// Uncomment me for creating amaterial copy per sprite.
//varmat = sprite.material;
}
}
访问sharedMaterial时很流畅,该材质仅有一个,并在所有精灵实例中共享;而访问material时,就需要为每个精灵创建一个单独的材质,这样就无法对Draw call进行批处理,使用renderDoc查看Render.TransparentGeometry函数的分析结果如下:
<ignore_js_op>
我觉得RenerDoc给出的时间开销可信度不高。材质的读取好像根本不准确,我也不知道这个示例中真实的硬件循环调用次数,我也查看了Unity的性能测试器。但无论哪种情况下,共享材质对比实例化新的材质,第二个实现方式都因为无法将draw call合并处理导致了更高的驱动开销和带宽开销。
真正代价高昂的是drawcall中的状态转换,而不是drawcall本身的数量。在使用Material.SetColor为每个精灵设置了不同的随机颜色后,我注意到又掉了2个FPS。这个问题可以通过最近发布的Vulkan API予以解决。
6. Conclusions
结论
RenderDoc既不是一个完善的工具更无法替代Unity 的frame debugger,它只是一个完善性工具,你可以方便的使用它获取需要的底层信息,能用到什么程度也是很有趣的,但你走的越远,问题就越复杂,此时你必须对GPU的硬件体系有充分的了解。
我得承认,使用renderDoc是个耗时的活,因此要记住:先做性能测试,然后再进行针对性的优化。95%的情况下你都可以用Unity自带的framedebugger完成调试,但为了那可能使你生活立即变得不幸福的5%你也得做好万全准备。
请将这些工具和性能测试器结合在一起使用,实际中总会有些琐碎的情况,不知道瓶颈源自何处,特别是在一个缺少调试手段的平台进行开发时。
一如既往,感谢您反馈并提出问题!
原文链接:
http://www.gamasutra.com/blogs/R ... nough_RenderDoc.php