零. 前言
俗话说,工欲善其事必先利其器,在苹果开发中,XCode给我们提供了众多的debug工具,既然开发工程代码有方便的断点调试机制,那么想必Metal开发也有非常好用的调试手段,今天就来介绍一下如何利用XCode自带的工具调试Metal开发。
这次我们选取的是Metal Sample Code里面的代码,是一个非常非常酷炫的3D带阴影带荧光带光照的特效:
一. 捕获某一帧
首先将代码跑到手机,点击这个小相机按钮,即可捕获到这一帧的信息
下图是这一帧动画的捕获结果,可以看到里面有很多部分,比如G缓冲、光照、阴影等等渲染效果。
我们可以通过setLabel:
方法对某些需要重视的缓冲、编码器等进行设置标签,并在Debug导航栏展示。
commandBuffer.label = @"GBuffer & Lighting Commands";
可以对编码器通过pushDebugGroup:
和popDebugGroup
进行文件夹分级:
/// Draw to the three textures which compose the GBuffer
- (void)drawGBuffer:(nonnull id <MTLRenderCommandEncoder>)renderEncoder
{
[renderEncoder pushDebugGroup:@"Draw G-Buffer"];
[renderEncoder setCullMode:MTLCullModeBack];
[renderEncoder setRenderPipelineState:_GBufferPipelineState];
[renderEncoder setDepthStencilState:_GBufferDepthStencilState];
[renderEncoder setStencilReferenceValue:128];
[renderEncoder setVertexBuffer:_frameDataBuffers[_frameDataBufferIndex] offset:0 atIndex:AAPLBufferIndexFrameData];
[renderEncoder setFragmentBuffer:_frameDataBuffers[_frameDataBufferIndex] offset:0 atIndex:AAPLBufferIndexFrameData];
[renderEncoder setFragmentTexture:_shadowMap atIndex:AAPLTextureIndexShadow];
[self drawMeshes:renderEncoder];
[renderEncoder popDebugGroup];
}
二. 顶点着色器的Debug
我们点击小虫子按钮就可以出现debug界面啦
选中Vertex可以查看顶点情况,可以看到这个图像由若干个三角形组成,现在我们选中其中一个三角形,点击Debug按钮
这时候就会定位到.metal文件的顶点着色器代码,我们可以看到这个三角形对应的坐标情况,每一步都能看到对应的矩阵信息输出,非常非常方便。
如果渲染时发现图像消失掉了,不妨可以用顶点着色器的Debug,看看对应的三角形到底跑哪去了。
三. 片段着色器的Debug
现在我们用像素查看器定位到闪现荧光的地方,看看荧光对应的片段着色器,我们选中Fragment选项,点击Debug按钮
这时候就可以看到对应的片段着色器的代码了,这个着色器起名为Fairy,营造了一种小精灵的气氛哈哈。我们点击右侧的矩阵还可以看到对应的图片效果,把鼠标放上去还可以看到实时的RGBA,真的非常人性化了。
当然,如果你这时候定位到bug了,对Metal文件一番修改后,想实时查看效果,那么可以点击,我改了一下代码,就实时地把小精灵的气氛毁坏掉了=v=
四. 性能表现的Debug
1. Summary
点击Summary栏,可以看到这个程序有6个命令缓冲区、6个渲染命令编码器、33个描绘指令,渲染一帧大概需要7.03ms,接近60w个顶点,纹理内存102.8MB,缓冲内存3.1MB
而下半部分则是Metal对性能上的一些优化建议,如当我点开BandWidth栏的时候,系统告知这是未被使用的资源。
点击后,系统建议编码完成后如果不需要用这一帧,换成MTLStoreActionDontCare,这样可以省掉8.03MB的大小。
2. Counters
计数器选项可以看到每个分组的一些渲染情况,还可以看到哪些阶段比较耗性能,从而进行排查。
3. Memory
内存选项可以看到内存的情况,纹理和缓冲用了多少内存在这里都一目了然。
4. Pineline State
点击这个按钮,切换到Pineline State
选中一个看上去耗时最长的fragment看看情况
可以看到,该G缓冲着色器总耗时1.39ms,且每一步的一些性能占比情况都展示了出来。
点击饼状图还能看到详情:
根据苹果文档Optimizing Performance with the Shader Profiler,这些性能指标分别代表:
ALU:Arithmetic Logic Unit,逻辑计算单元,负责逻辑运算
Memory:内存,采样、读取和存储操作
Synchronization:同步,等待内存、被阻塞、原子操作
根据苹果文档Reducing Shader Bottlenecks,我们可以通过以下操作提高性能:
如果对精度不那么敏感,可以适当降低精度
使用Metal的快速运算标记flag,只有真正需要用到的时候才使用精确运算flag,这个在Metal Shading Language Guide的1.5章有提到
能用half就用half,不行才用float,避免half和float的相互转换
能采样纹理就不要手动写函数计算,比如相对于写一个噪声点的生成函数,更好的选择是生成一个噪声纹理进行采样。
5. Instrument分析
业界对Instrument的Metal System Trace的分析比较少,找了几个WWDC视频才大致了解。
打开Instrument,Option勾选如下
A13是苹果的Metal Device,而下面的则是各个CPU的占用情况,如果有过高占用的话会很容易看到,从而进行排查。
展开A13又看到几个熟悉的老面孔,顶点和片段着色器,还有一些性能指标
筛选一些数值放大到一次渲染过程看看,可以看到顶点和片段着色器的一些函数执行的流水线,如果需要的话可以定点到具体方法排查。下方还有一些时间信息。
五. 总结
这篇文章主要介绍了如何利用XCode对Metal渲染进行调试,还有介绍了一些性能情况的查看,我们可以通过不同阶段的性能表现来定位哪些函数比较消耗性能,从而优化他们~
参考文章
WWDC2018 Metal Shader Debugging and Profiling
Metal框架详细解析(十八) —— 工具、分析和调试之Metal GPU Capture(二)