說明
學習ARKit前,需要先學習SceneKit,參考SceneKit系列文章目錄
UI介面/3D模型除錯
介面與模型除錯仍然是使用View Debuger,進入除錯狀態後可看到圖層狀態
點選3D圖層,會進入3D模型編輯器,顯示的是實時狀態的模型
右側可以調整材質等資訊,點選右下角的按鈕,可以檢視Shader和Action,選中可以檢視我們使用的Shader,再點選問號可檢視引數輸入:
效能除錯
效能除錯分為實時檢視(用Gauge),與錄製分析(用Instruments)
FPS Gauge
FPS Gauge不僅是實時的,還能顯示負載的分類
Instruments->SceneKit
一般先使用SceneKit
工具分析,可以精確到每幀,但需要先錄製一段;分析問題找到原因後再處理,或使用Metal System Trace
再分析Metal程式
例如,蘋果官方的例子中,先用SceneKit工具分析出問題出在Metal程式碼上,再用
Metal System Trace
分析出問題出在Shader編譯太慢上.對應的解決方法是:提前編譯Shader.
Metal2高階除錯
本部分內容來自於WWDC2017中的Metal 2 Optimization and Debugging
示例程式的程式碼蘋果貌似沒有全部提供,反正我在蘋果文件庫中只找到了一個示例MetalDeferredLighting.
Metal Frame Debugger(Metal幀偵錯程式)
其實就是對原來的GPU Frame Debugger的增強,使用方法還是和原來一樣,在執行中點選下方工具欄中的照相機圖示捕捉Frame,現在長按也可以彈出選單顯示更多內容了.
原有功能:
- 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來演示新增功能的使用. 首先,執行一下,這是一個有問題的程式,注意雪花附近出現了異常
接著開始除錯,先看紋理本身有沒有異常,在對應位置打斷點,就可以直接預覽GPU上的紋理,非常方便:
紋理沒有問題.接著捕捉一幀,看看到底怎麼回事.在相機圖示上長按,選擇Rendering
,進入了Metal Frame Debuger中:
要找到繪製雪花顆粒(particle
)的地方,可以在左下角使用高階搜尋,新增條件Forward Render
和Particle
,搜尋結果中只有一個API滿足要求,點選這個搜尋結果,顯示詳情:
看看頂點資料是不是有問題,雙擊Vertex Attributes
檢視頂點的輸入輸出資料
資料看起來沒有肉眼可見的異常,估計應該是正常的.再找別的原因. 先回到左側導航欄搜尋結果中,展開當前API呼叫涉及的所有資源,選中Attachments
,開啟右下角的畫素檢查器:
兩張圖:左邊是渲染目標的色彩圖,右側是渲染目標的深度圖,移動圓形的畫素檢查器Pixel Inspector
來檢查畫素級的問題.
經過查詢,我們發現右側的深度圖上,雪花邊緣附近的深度值不一致,這就是bug所在,正常情況下particle
不應該寫入深度值到深度緩衝器depth buffer
中.
修復這個bug即可,此處略過...
GPU Shader Profiler
這是整合在Metal Frame Debugger
中的Shader分析利器,可以分析出編譯後的Shader哪裡有效能問題.例如:
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
介面中:
中間的上方顯示出編譯器給出的優化建議,我們先處理和棧相關的第2個和第4個,雙擊進入shader中:
部分程式碼如下,發現其中的可變陣列會造成影響:
//問題程式碼
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,各項數值已減小:
GPU Counter Profiling(GPU計數分析)
這個工具也是整合在Metal Frame Debugger
中的,但並不針對於Shader.功能有圖形化列表展示和瓶頸分析:
還是分析一個Demo,執行捕捉,進入幀偵錯程式,選中GPU
展示GPU Counter Profiling
介面:
先看左側圖形化列表,雙指放大,選中啟動時的最高峰:
我們發現問題出在Fragment Shader Time
和Pixels Stored
上面,先展開第一個Fragment Shader Time
進行分析:
發現FS Stall Time
很高,說明等待的時間非常長,這一般是由於從記憶體中讀取圖片或資料造成的.向下滾動檢視紋理快取的情況:
看到Texture Cache Miss Rate
很高,也就說明紋理命中率只有不到40%,所以需要不斷從記憶體中讀取紋理,造成效能問題.這也解釋了為什麼前面Pixels Stored
也很高.
具體哪裡出現了問題,還需要看右側的瓶頸分析資料表:
提示紋理可能沒有mipmaps,點選右側箭頭:
原來是載入了一個256M的高度地圖,造成了快取被大量佔用,快取命中率低,不斷從記憶體讀取圖片,GPU不斷等待.
原因找到!!
最後
Metal相關的除錯技巧也適用於蘋果的機器學習框架(基於Metal)中.學習相關技巧,受益很多.
由於水平所限,本文第二部分的高階除錯基本是照搬蘋果WWDC2017的演講,具體在自己的專案中使用時,因為不同平臺(macOS/iOS/tvOS),不同技術(SceneKit/SpriteKit,Metal/OpenGLES)還是會有一些不同的限制. 當然,最大限制還是在於自己是否足夠了解圖形學的相關知識,對此我深感力不從心,需要學習更多相關基礎.