[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)仍是會有一些不一樣的限制. 固然,最大限制仍是在於本身是否足夠了解圖形學的相關知識,對此我深感力不從心,須要學習更多相關基礎.

相關文章
相關標籤/搜索