ARKit系列文章目錄html
學習ARKit前,須要先學習SceneKit,參考SceneKit系列文章目錄c++
界面與模型調試仍然是使用View Debuger,進入調試狀態後可看到圖層狀態 數組
點擊3D圖層,會進入3D模型編輯器,顯示的是實時狀態的模型 緩存
右側能夠調整材質等信息,點擊右下角的按鈕,能夠查看Shader和Action,選中能夠查看咱們使用的Shader,再點擊問號可查看參數輸入: 網絡
性能調試分爲實時查看(用Gauge),與錄製分析(用Instruments)app
FPS Gauge不只是實時的,還能顯示負載的分類 框架
通常先使用SceneKit
工具分析,能夠精確到每幀,但須要先錄製一段;分析問題找到緣由後再處理,或使用Metal System Trace
再分析Metal程序 機器學習
例如,蘋果官方的例子中,先用SceneKit工具分析出問題出在Metal代碼上,再用 Metal System Trace
分析出問題出在Shader編譯太慢上.對應的解決方法是:提早編譯Shader. 編輯器
本部份內容來自於WWDC2017中的Metal 2 Optimization and Debuggingide
示例程序的代碼蘋果貌似沒有所有提供,反正我在蘋果文檔庫中只找到了一個示例MetalDeferredLighting.
其實就是對原來的GPU Frame Debugger的加強,使用方法仍是和原來同樣,在運行中點擊下方工具欄中的照相機圖標捕捉Frame,如今長按也能夠彈出菜單顯示更多內容了.
原有功能:
新增了一些功能:
下面讓咱們結合一個Demo來演示新增功能的使用. 首先,運行一下,這是一個有問題的程序,注意雪花附近出現了異常
接着開始調試,先看紋理自己有沒有異常,在對應位置打斷點,就能夠直接預覽GPU上的紋理,很是方便:
紋理沒有問題.接着捕捉一幀,看看到底怎麼回事.在相機圖標上長按,選擇Rendering
,進入了
Metal Frame Debuger中:
要找到繪製雪花顆粒(particle
)的地方,能夠在左下角使用高級搜索,添加條件Forward Render
和Particle
,搜索結果中只有一個API知足要求,點擊這個搜索結果,顯示詳情:
看看頂點數據是否是有問題,雙擊Vertex Attributes
查看頂點的輸入輸出數據
數據看起來沒有肉眼可見的異常,估計應該是正常的.再找別的緣由. 先回到左側導航欄搜索結果中,展開當前API調用涉及的全部資源,選中Attachments
,打開右下角的像素檢查器:
兩張圖:左邊是渲染目標的色彩圖,右側是渲染目標的深度圖,移動圓形的像素檢查器Pixel Inspector
來檢查像素級的問題.
通過查找,咱們發現右側的深度圖上,雪花邊緣附近的深度值不一致,這就是bug所在,正常狀況下particle
不該該寫入深度值到深度緩衝器depth buffer
中.
修復這個bug便可,此處略過...
這是集成在Metal Frame Debugger
中的Shader分析利器,能夠分析出編譯後的Shader哪裏有性能問題.例如:
這個工具也是集成在Metal Frame Debugger
中的Shader分析利器.功能以下:
Per-shader metrics:每一個着色器節奏 編譯器產生的統計數據:
Compiler remarks :編譯器評價 能避免如下狀況出現:
仍是經過一個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,各項數值已減少:
這個工具也是集成在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)仍是會有一些不一樣的限制. 固然,最大限制仍是在於本身是否足夠了解圖形學的相關知識,對此我深感力不從心,須要學習更多相關基礎.