[ARKit]6-3D与AR/VR应用Debug与优化浅谈

说明

ARKit系列文章目录html

学习ARKit前,须要先学习SceneKit,参考SceneKit系列文章目录c++

UI界面/3D模型调试

界面与模型调试仍然是使用View Debuger,进入调试状态后可看到图层状态 数组

WX20180302-092750@2x.png

点击3D图层,会进入3D模型编辑器,显示的是实时状态的模型 缓存

WX20180302-092322@2x.png

右侧能够调整材质等信息,点击右下角的按钮,能够查看Shader和Action,选中能够查看咱们使用的Shader,再点击问号可查看参数输入: 网络

WX20180302-092432@2x.png
WX20180302-092607@2x.png

性能调试

性能调试分为实时查看(用Gauge),与录制分析(用Instruments)app

FPS Gauge

FPS Gauge不只是实时的,还能显示负载的分类 框架

WX20180302-092059@2x.png
WX20180302-094045@2x.png

Instruments->SceneKit

通常先使用SceneKit工具分析,能够精确到每帧,但须要先录制一段;分析问题找到缘由后再处理,或使用Metal System Trace再分析Metal程序 机器学习

WX20180302-091837@2x.png
WX20180302-090733@2x.png
WX20180302-090847@2x.png

例如,苹果官方的例子中,先用SceneKit工具分析出问题出在Metal代码上,再用 Metal System Trace分析出问题出在Shader编译太慢上.对应的解决方法是:提早编译Shader. 编辑器

WX20180302-095213@2x.png
WX20180302-095230@2x.png
WX20180302-095243@2x.png

Metal2高级调试

本部份内容来自于WWDC2017中的Metal 2 Optimization and Debuggingide

示例程序的代码苹果貌似没有所有提供,反正我在苹果文档库中只找到了一个示例MetalDeferredLighting.

Metal Frame Debugger(Metal帧调试器)

其实就是对原来的GPU Frame Debugger的加强,使用方法仍是和原来同样,在运行中点击下方工具栏中的照相机图标捕捉Frame,如今长按也能够弹出菜单显示更多内容了.

WX20180303-105617@2x.png

原有功能:

  • Fully featured frame debugger :全功能的帧调试器
  • Navigate through your workload :浏览工做负载
  • Inspect state and resource :检查状态和资源
  • Debug graphics and compute :调试图形和计算
  • Integrated into Xcode :整合进Xcode

新增了一些功能:

  • Improved Capture Performance :改进性能,提高10倍速度
  • Full Metal 2 Support :全面支持Metal 2
    • Raster order groups :光栅顺序组
    • Sampler arrays :采样器数组
    • Viewport arrays :视口数组
    • New pixel formats :新像素格式
    • New vertex array formats :新顶点数组格式
    • Argument buffers :参数缓存器
  • VR Support :支持VR(macOS上,如SteamVR)
  • Improved Capture Workflow :改进捕捉流程
  • Metal Quick Looks :Metal预览(也能够用在神经网络CNN中查看相关数据)
  • Advanced Filtering :高级搜索过滤
  • Pixel Inspection :像素检查
  • Inspect Vertex Attribute Outputs :顶点属性输出检查

下面让咱们结合一个Demo来演示新增功能的使用. 首先,运行一下,这是一个有问题的程序,注意雪花附近出现了异常

WX20180303-114733@2x.png

接着开始调试,先看纹理自己有没有异常,在对应位置打断点,就能够直接预览GPU上的纹理,很是方便:

WX20180303-114827@2x.png
纹理没有问题.接着捕捉一帧,看看到底怎么回事.在相机图标上长按,选择 Rendering,进入了 Metal Frame Debuger中:
WX20180303-114931@2x.png
WX20180303-115050@2x.png

要找到绘制雪花颗粒(particle)的地方,能够在左下角使用高级搜索,添加条件Forward RenderParticle,搜索结果中只有一个API知足要求,点击这个搜索结果,显示详情:

WX20180303-115204@2x.png
WX20180303-115338@2x.png

看看顶点数据是否是有问题,双击Vertex Attributes查看顶点的输入输出数据

WX20180303-115403@2x.png
WX20180303-115454@2x.png

数据看起来没有肉眼可见的异常,估计应该是正常的.再找别的缘由. 先回到左侧导航栏搜索结果中,展开当前API调用涉及的全部资源,选中Attachments,打开右下角的像素检查器:

WX20180303-115812@2x.png

两张图:左边是渲染目标的色彩图,右侧是渲染目标的深度图,移动圆形的像素检查器Pixel Inspector来检查像素级的问题.

WX20180303-115900@2x.png
WX20180303-115938@2x.png

通过查找,咱们发现右侧的深度图上,雪花边缘附近的深度值不一致,这就是bug所在,正常状况下particle不该该写入深度值到深度缓冲器depth buffer中.
修复这个bug便可,此处略过...

GPU Shader Profiler

这是集成在Metal Frame Debugger中的Shader分析利器,能够分析出编译后的Shader哪里有性能问题.例如:

WX20180303-124248@2x.png

Metal Pipeline Statistics(Metal管线统计)

这个工具也是集成在Metal Frame Debugger中的Shader分析利器.功能以下:

  • Per-shader metrics:每一个着色器节奏 编译器产生的统计数据:

    • Instruction count :指令数
    • Instruction mix :指令组合
    • Register usage :寄存器使用
    • Occupancy :占用率
  • Compiler remarks :编译器评价 能避免如下状况出现:

    • Slow math usage:低效的数学运算
    • Register spills:寄存器泄露
    • Stack usage:栈的使用(GPU用栈储存或读取会形成负担,如shader使用了可变数组)
    • Other optimization opportunities:其余可优化状况

仍是经过一个Demo来演示
打开项目运行,点击相机图标,进入帧调试器,切换显示方式,找到Pipeline Statistics界面中:

WX20180303-124539@2x.png
WX20180303-125026@2x.png

中间的上方显示出编译器给出的优化建议,咱们先处理和栈相关的第2个和第4个,双击进入shader中:

WX20180303-131003@2x.png

部分代码以下,发现其中的可变数组会形成影响:

//问题代码
float3 v = in.v_view * (scene_z / in.v_view.z);

// Now, we have everything we need to calculate our view-space lighting vectors.
FragOutput output;
output.light = float4(0);
output.albedo = gBuffers.albedo;
output.normal = gBuffers.normal;
output.depth = gBuffers.depth;

float4 lightingFinal[FAIRY_GROUP_SIZE];
for(int i = 0; i < FAIRY_GROUP_SIZE; i++) {
    lightingFinal[i] = float4(0);
    float3 l = (lightData->view_light_position.xyz - v);
    float n_ls = dot(n, n);
    float v_ls = dot(v, v);
    float l_ls = dot(l, l);
    float3 h = (l * rsqrt(l_ls / v_ls) - v);
    float h_ls = dot(h, h);
    float nl = dot(n, l) * rsqrt(n_ls * l_ls);
    float nh = dot(n, h) * rsqrt(n_ls * h_ls);
    float d_atten = sqrt(l_ls);
    float atten = fmax(1.0 - d_atten / lightData->light_color_radius.w, 0.0);
    float diffuse = fmax(nl, 0.0) * atten;
    
    float4 light = gBuffers.light;
    light.rgb += lightData->light_color_radius.xyz * diffuse;
    light.a += pow(fmax(nh, 0.0), 32.0) * step(0.0, nl) * atten * 1.0001;
    lightingFinal[i] = light / FAIRY_GROUP_SIZE;
}

for(int i = 0; i < FAIRY_GROUP_SIZE; i++) {
    output.light += lightingFinal[i];
}

return output;
复制代码

修改代码,移除数组相关代码,直接计算光线最终值:

//改后代码,移除lightingFinal数组相关代码
float3 v = in.v_view * (scene_z / in.v_view.z);

// Now, we have everything we need to calculate our view-space lighting vectors.
FragOutput output;
output.light = float4(0);
output.albedo = gBuffers.albedo;
output.normal = gBuffers.normal;
output.depth = gBuffers.depth;

for(int i = 0; i < FAIRY_GROUP_SIZE; i++) {
    float3 l = (lightData->view_light_position.xyz - v);
    float n_ls = dot(n, n);
    float v_ls = dot(v, v);
    float l_ls = dot(l, l);
    float3 h = (l * rsqrt(l_ls / v_ls) - v);
    float h_ls = dot(h, h);
    float nl = dot(n, l) * rsqrt(n_ls * l_ls);
    float nh = dot(n, h) * rsqrt(n_ls * h_ls);
    float d_atten = sqrt(l_ls);
    float atten = fmax(1.0 - d_atten / lightData->light_color_radius.w, 0.0);
    float diffuse = fmax(nl, 0.0) * atten;
    
    float4 light = gBuffers.light;
    light.rgb += lightData->light_color_radius.xyz * diffuse;
    light.a += pow(fmax(nh, 0.0), 32.0) * step(0.0, nl) * atten * 1.0001;
    output.light += light / FAIRY_GROUP_SIZE;
}

return output;
复制代码

点击按钮,从新加载Shader,各项数值已减少:

WX20180303-132648@2x.png
WX20180303-133515@2x.png

GPU Counter Profiling(GPU计数分析)

这个工具也是集成在Metal Frame Debugger中的,但并不针对于Shader.功能有图形化列表展现瓶颈分析:

仍是分析一个Demo,运行捕捉,进入帧调试器,选中GPU展现GPU Counter Profiling界面:

WX20180303-135226@2x.png

先看左侧图形化列表,双指放大,选中启动时的最高峰:

WX20180303-135351@2x.png
WX20180303-140032@2x.png

咱们发现问题出在Fragment Shader TimePixels Stored上面,先展开第一个Fragment Shader Time进行分析:

WX20180303-140147@2x.png

发现FS Stall Time很高,说明等待的时间很是长,这通常是因为从内存中读取图片或数据形成的.向下滚动查看纹理缓存的状况:

WX20180303-140258@2x.png

看到Texture Cache Miss Rate很高,也就说明纹理命中率只有不到40%,因此须要不断从内存中读取纹理,形成性能问题.这也解释了为何前面Pixels Stored也很高.

具体哪里出现了问题,还须要看右侧的瓶颈分析数据表:

WX20180303-141425@2x.png

提示纹理可能没有mipmaps,点击右侧箭头:

WX20180303-141620@2x.png

原来是加载了一个256M的高度地图,形成了缓存被大量占用,缓存命中率低,不断从内存读取图片,GPU不断等待.

缘由找到!!

最后

Metal相关的调试技巧也适用于苹果的机器学习框架(基于Metal)中.学习相关技巧,受益不少.

因为水平所限,本文第二部分的高级调试基本是照搬苹果WWDC2017的演讲,具体在本身的项目中使用时,由于不一样平台(macOS/iOS/tvOS),不一样技术(SceneKit/SpriteKit,Metal/OpenGLES)仍是会有一些不一样的限制. 固然,最大限制仍是在于本身是否足够了解图形学的相关知识,对此我深感力不从心,须要学习更多相关基础.