前言
對於DirectX程序開發者來說,學會使用Visual Studio Graphics Debugger(圖形調試器)可以幫助你全面了解渲染管線綁定的資源和運行狀態,從而確認問題所在。現在就以我所掌握的圖形調試經驗來進行展開描述。
下面的教程基於Visual Studio 2017/2019 Community進行.因為最近換了VS2019,並且添加了調試對象具名化的功能,里面的圖片來不及做完整更換,但還是能看的。
這一篇需要消耗比較多的流量,沒連接WIFI或者網線的慎入。
同時推薦大家了解一下我的DirectX 11教程,講述了如何脫離DirectX SDK及Effects11,使用HLSL編譯器/D3DCompiler和Windows SDK來開發DirectX 11應用程序:
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
准備工作
首先確定是否安裝了DirectX圖形調試器,需要在Visual Studio Installer中確定是否已經勾選了該項內容。
安裝好並進入項目,在調試之前需要將項目配置成Debug模式
然后觀察着色器的編譯選項,如果使用的是HLSL編譯器,則要重點關注Debug模式下所有着色器是否都禁用了優化,並啟用了調試信息。
首先對其中的一個着色器右鍵-屬性
然后在Debug配置下,選擇HLSL編譯器-所有選項,禁用優化並啟用調試信息
如果使用的是D3DCompiler,在代碼層(運行時)編譯着色器,則需要在Debug模式下給D3DComplieFromFile
函數添加D3DCOMPILE_DEBUG
和D3DCOMPILE_SKIP_OPTIMIZATION
的Flag以開啟着色器調試並關閉優化,否則在調試着色器的時候只能看到匯編代碼:
HRESULT CreateShaderFromFile(const WCHAR * csoFileNameInOut, const WCHAR * hlslFileName,
LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob ** ppBlobOut)
{
HRESULT hr = S_OK;
// 尋找是否有已經編譯好的頂點着色器
if (csoFileNameInOut && D3DReadFileToBlob(csoFileNameInOut, ppBlobOut) == S_OK)
{
return hr;
}
else
{
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
// 設置 D3DCOMPILE_DEBUG 標志用於獲取着色器調試信息。該標志可以提升調試體驗,
// 但仍然允許着色器進行優化操作
dwShaderFlags |= D3DCOMPILE_DEBUG;
// 在Debug環境下禁用優化以避免出現一些不合理的情況
dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
ID3DBlob* errorBlob = nullptr;
hr = D3DCompileFromFile(hlslFileName, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryPoint, shaderModel,
dwShaderFlags, 0, ppBlobOut, &errorBlob);
if (FAILED(hr))
{
if (errorBlob != nullptr)
{
OutputDebugStringA(reinterpret_cast<const char*>(errorBlob->GetBufferPointer()));
}
SAFE_RELEASE(errorBlob);
return hr;
}
// 若指定了輸出文件名,則將着色器二進制信息輸出
if (csoFileNameInOut)
{
return D3DWriteBlobToFile(*ppBlobOut, csoFileNameInOut, FALSE);
}
}
return hr;
}
截取一幀畫面
圖形調試器的調試通常是針對某一幀的畫面進行的。完成了上面的配置后,第一步我們需要打開圖形調試器去截取一幀認為有問題的畫面來進行調試。
運行圖形調試之前請先確保沒有能夠導致觸發斷點異常的問題,如果有的話請先通過普通的調試器解決問題。畢竟圖形調試器是要解決圖形顯示異常,普通調試無法查出來的問題,而要對GPU進行調試。除此之外,還需要撤掉之前在圖形繪制階段的所有斷點。
有兩種方式打開圖形調試器,第一種是快捷鍵Alt+F5啟動,如果沒有反應,則可以通過第二種方式啟動並確認快捷鍵。
第二種是VS界面選擇調試-圖形-啟動圖形調試。
在進入程序后,按下Print Screen(PrtSc)鍵截取一幀有問題的畫面,然后就可以看到紅色方框區域就是你剛截下的一幀畫面
實際上生成的是一個圖形日志文檔(.vsglog),我們需要通過他來進行圖形調試。
你可以在一次調試截取多幀畫面,但基本上目前我們只需要截取一幀畫面就可以退出程序了。關閉程序后,我們可以點擊藍色部分的字:幀XXXX 或者雙擊畫面來打開Visual Studio圖形分析器。
圖形調試器預覽
下面是圖形調試器的主界面
事件列表
事件列表展示了DirectX的一些接口類對象的重要調用。當前查看的是GPU工作,可以觀察到D3D設備上下文關於繪制和內部綁定的GPU數據更新的所有操作。若更改為時間線,則可以觀察更多有關D3D設備上下文的詳細調用操作,可以看到各個階段都有哪些資源被綁定,哪些狀態被改變,以及調用了繪制。
其中帶筆刷的調用說明這是一個繪制調用,可以點擊它觀察直到這個方法被調用后的繪制狀態。
為圖形調試器的對象添加自定義名稱
看到上面的幾張圖片,雖然我們可以推測出來對象: 2
就是m_pd3dImmediateContext
,但是也僅限少數的幾個固定對象名我們能直接推測出是什么對象。等對象一多,我們就難以判別管線所綁定的對象是否正確。因此我們可以在C++代碼來為對象指定名稱。
在d3dUtil.h
中提供了兩個系列的函數,一個用於D3D設備創建出來的對象,一個用於DXGI對象。通過SetPrivateData
方法,並使用WKPDID_D3DDebugObjectName
的GUID
使得我們可以為其設置圖形調試器下的名稱:
// ------------------------------
// D3D11SetDebugObjectName函數
// ------------------------------
// 為D3D設備創建出來的對象在圖形調試器中設置對象名
// [In]resource D3D11設備創建出的對象
// [In]name 對象名
template<UINT TNameLength>
inline void D3D11SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ const char(&name)[TNameLength])
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
resource->SetPrivateData(WKPDID_D3DDebugObjectName, TNameLength - 1, name);
#else
UNREFERENCED_PARAMETER(resource);
UNREFERENCED_PARAMETER(name);
#endif
}
// ------------------------------
// D3D11SetDebugObjectName函數
// ------------------------------
// 為D3D設備創建出來的對象在圖形調試器中設置對象名
// [In]resource D3D11設備創建出的對象
// [In]name 對象名
// [In]length 字符串長度
inline void D3D11SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ LPCSTR name, _In_ UINT length)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
resource->SetPrivateData(WKPDID_D3DDebugObjectName, length, name);
#else
UNREFERENCED_PARAMETER(resource);
UNREFERENCED_PARAMETER(name);
UNREFERENCED_PARAMETER(length);
#endif
}
// ------------------------------
// D3D11SetDebugObjectName函數
// ------------------------------
// 為D3D設備創建出來的對象在圖形調試器中設置對象名
// [In]resource D3D11設備創建出的對象
// [In]name 對象名
inline void D3D11SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ const std::string& name)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
resource->SetPrivateData(WKPDID_D3DDebugObjectName, (UINT)name.length(), name.c_str());
#else
UNREFERENCED_PARAMETER(resource);
UNREFERENCED_PARAMETER(name);
#endif
}
// ------------------------------
// D3D11SetDebugObjectName函數
// ------------------------------
// 為D3D設備創建出來的對象在圖形調試器中清空對象名
// [In]resource D3D11設備創建出的對象
inline void D3D11SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ std::nullptr_t)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
resource->SetPrivateData(WKPDID_D3DDebugObjectName, 0, nullptr);
#else
UNREFERENCED_PARAMETER(resource);
#endif
}
// ------------------------------
// DXGISetDebugObjectName函數
// ------------------------------
// 為DXGI對象在圖形調試器中設置對象名
// [In]object DXGI對象
// [In]name 對象名
template<UINT TNameLength>
inline void DXGISetDebugObjectName(_In_ IDXGIObject* object, _In_ const char(&name)[TNameLength])
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
object->SetPrivateData(WKPDID_D3DDebugObjectName, TNameLength - 1, name);
#else
UNREFERENCED_PARAMETER(object);
UNREFERENCED_PARAMETER(name);
#endif
}
// ------------------------------
// DXGISetDebugObjectName函數
// ------------------------------
// 為DXGI對象在圖形調試器中設置對象名
// [In]object DXGI對象
// [In]name 對象名
// [In]length 字符串長度
inline void DXGISetDebugObjectName(_In_ IDXGIObject* object, _In_ LPCSTR name, _In_ UINT length)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
object->SetPrivateData(WKPDID_D3DDebugObjectName, length, name);
#else
UNREFERENCED_PARAMETER(object);
UNREFERENCED_PARAMETER(name);
UNREFERENCED_PARAMETER(length);
#endif
}
// ------------------------------
// DXGISetDebugObjectName函數
// ------------------------------
// 為DXGI對象在圖形調試器中設置對象名
// [In]object DXGI對象
// [In]name 對象名
inline void DXGISetDebugObjectName(_In_ IDXGIObject* object, _In_ const std::string& name)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
object->SetPrivateData(WKPDID_D3DDebugObjectName, (UINT)name.length(), name.c_str());
#else
UNREFERENCED_PARAMETER(object);
UNREFERENCED_PARAMETER(name);
#endif
}
// ------------------------------
// DXGISetDebugObjectName函數
// ------------------------------
// 為DXGI對象在圖形調試器中清空對象名
// [In]object DXGI對象
inline void DXGISetDebugObjectName(_In_ IDXGIObject* object, _In_ std::nullptr_t)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
object->SetPrivateData(WKPDID_D3DDebugObjectName, 0, nullptr);
#else
UNREFERENCED_PARAMETER(object);
#endif
}
此外,GameObject
類、Model
類、TextureRender
類、SkyRender
類和DynamicSkyRender
類都添加了SetDebugObjectName
方法來為對象設置調試自定義名稱。
現在打開圖形調試器查看,類似效果如下:
對象具名化后可以十分方便地確認自己有沒有正確綁定所需資源。
如果你不希望使用調試器對象具名化,可以在d3dUtil.h
的開頭找到這樣的宏:
// 默認開啟圖形調試器具名化
// 如果不需要該項功能,可通過全局文本替換將其值設置為0
#ifndef GRAPHICS_DEBUGGER_OBJECT_NAME
#define GRAPHICS_DEBUGGER_OBJECT_NAME (1)
#endif
將其修改后只會剩下默認的DDSTextureLoader
和WICTextureLoader
的對象具名化。
注意:在你的Release版本應用程序應該避免出現對調試對象名稱的設置。你可以將相關代碼移出項目。
查看傳入的緩沖區數據
我們可以在圖形調試器查看頂點緩沖區,索引緩沖區和常量緩沖區。
在上面的事件列表中,我們可以看到很多藍色字體的對象,這些都可以點進去觀察。這里我們以某個繪制事件綁定的頂點緩沖區為例
我們可以觀察到緩沖區的字節數、使用情況、綁定標簽、CPU訪問權限等。其中觀察到的數據取決於我們設置的格式。
圖形調試器支持觀察的基本類型如下:
大類 | 基本類型 |
---|---|
有符號字節類型 | byte(sbyte) 2byte 4byte 8byte |
無符號字節類型 | ubyte u2byte u4byte u8byte |
十六進制字節類型 | xbyte x2byte x4byte x8byte |
有符號整型 | short int int64(long) |
無符號整型 | ushort uint uint64(ulong) |
十六進制整型 | xshort xint xint64(xlong) |
半精度浮點型 | half half2 half3 half4 |
單精度浮點型 | float float2 float3 float4 |
雙精度浮點型 | double |
除此之外,格式欄允許我們輸入以支持不同基本類型的組合。比如說現在傳入的頂點包含位置、法向量和紋理坐標,那我們可以在格式欄輸入float3 float3 float2
來將輸入的數據重新解釋成我們傳入的頂點信息:
同樣,對於索引緩沖區,我們可以在格式欄輸入short short short
或int int int
來觀察三個索引組裝一個圖元的索引數組:
而對於常量緩沖區來說,一個着色器階段可能會綁定多個常量緩沖區,傳入的數據取決於你調用的ID3D11DeviceContext::*SSetConstantBuffers
方法綁定的常量緩沖區以及最近一次ID3D11DeviceContext::UpdateSubresource
方法更新的數據,而使用的緩沖區取決於你在着色器寫的代碼。比如有下面這個常量緩沖區塊:
// 物體表面材質
struct Material
{
float4 Ambient;
float4 Diffuse;
float4 Specular; // w = SpecPower
float4 Reflect;
};
cbuffer CBChangesOnResize : register(b3)
{
matrix g_Proj;
}
我們使用float4
格式就可以觀察信息。其中每個矩陣占了4行:
查看着色器資源視圖中的紋理資源
因為着色器資源視圖中可以綁定一張紋理,也可以綁定一個紋理數組。這里我以另一個程序的圖形調試作為實例,演示如何觀察綁定到渲染管線上的紋理資源。
點擊PS着色器資源的藍字部分(Grass.dds),可以查看着色器資源的狀態
現在我們要查看着色器資源綁定的內容,點擊資源對應的藍字(DDSTextureLoader)就可以查看綁定的紋理資源。
這里我們可以觀察到加載的紋理格式。在經過DDSTextureLoader
或WICTextureLoader
加載的紋理會自動生成MipMap鏈,現在加載的是一張512x512的紋理,它有10張子資源,選擇Mip切片可以查看其余子資源紋理。隨着Mip切片等級增大,寬度和高度逐漸是原來上一級的1/2.
而在通道直方圖中,默認觀察的是紋理RGB通道顏色的組合,你可以取消勾選來關閉某一通道的顏色,或者修改范圍來選擇顏色的可視范圍。若選擇Alpha通道,則只會單獨觀察該通道的顏色。下面是原來用的籬笆盒Alpha通道的情況(白色為Alpha值1, 黑色為Alpha值0):
接下來是紋理數組的觀察,其實和之前的操作差不多,但有時候我們在繪制過程可能找不到之前綁定上的紋理,我們可以通過下面的對象表來尋找。對象表已經包含了由D3D設備創建出來的絕大多數資源或對象。
這里用的是公告板的例子,比如我現在要尋找紋理資源,在搜索欄輸入Texture
來根據類型進行查找:
紋理數組加載了4張紋理,它的字節大小也應該是最大的,雙擊它就可以看到樹的紋理了:
我們通過更改數組切片來觀察別的樹的紋理:
當然,如果給對象具名化,在這里面找對象會更加容易一些:
查看資源歷史記錄
細心的話可以發現有些資源是有個時間標志的,點擊它可以查看該資源的歷史變更情況,即有哪些方法對該資源進行了變更。
比如說我點擊了PS着色器資源:Grass.dds
右邊的時間標志(VS2015不支持)
就可以在右邊看到資源的讀取和寫入情況:
然后點擊查看就可以看到該資源當時的具體情況了。
跟蹤渲染管線各個階段的狀態
選擇一個繪制事件,然后在下面的狀態欄就可以看到跟上一繪制事件相比,有哪些階段發生了變化。變化的部分會有紅色高亮顯示。在該狀態可以查看當前繪制已經綁定的所有資源、着色器和狀態,相比對象表查找起來會更清晰一些。
管道階段
同樣是要先選擇一個繪制事件,然后在下面的狀態欄選擇管道階段,就可以看到當前運行的各個着色階段,以及是否存在從某個階段開始就沒有輸入/輸出或者沒有執行的問題。
對象查看
對於3D模型,你可以點擊輸入裝配器進入預覽網格界面來觀察加載出來的網格。
要對場景進行操作,必須要選擇上行的其中一個工具才能對場景操作。
若要對物體進行操作,則必須要選擇左邊列的其中一個工具來對其操作。
此外,你可以觀察物體的法向量或面向量
你也可以通過上圖右邊的屬性欄修改物體的基本屬性。至於其余功能你可以自行探索。
頂點位置
對於可編程的頂點着色器階段來說,我們可以看到視圖:輸入/輸出欄有 輸入/輸出的每個頂點的值和對應語義。其中SV_POSITION
的值是未經過透視除法的,我們可以將(x, y, z, w)
的每個分量除以w
,變成(x/w, y/w, z/w, 1)
來觀察它是否位於NDC坐標系(齊次裁剪坐標系)內,若不在則該頂點不會傳遞給下一階段。每個頂點都可以單獨進行着色器調試。
注意:在像素着色器中,SV_POSITION
的x
分量和y
分量都已經經過視口變換成為最終的屏幕坐標,且帶有小數點0.5
,這是因為要取到像素的中心位置,即對於800x600的視口區域,實際上的屏幕坐標取值范圍為[0.5, 800.5]x[0.5, 600.5]
,z
分量取值范圍為[0, 1]
。這一點讀者可以修改像素着色器使得SV_POSITION
與像素顏色結果有關聯,然后進入調試以驗證。
綁定的資源
將視圖:輸入/輸出切換成綁定的資源,同樣也能看到在該着色器階段綁定了哪些資源可供使用。
切換到像素着色器有可能是看不到任何的輸入和輸出的,但可以通過另一種方式,指定像素來觀察該像素經歷的像素着色器階段。這里在下面會講到。
最后是輸出合並器,切換到綁定的資源,可以看到輸出合並階段綁定的深度/模板緩沖區和后備緩沖區的狀態。
查看深度/模板緩沖區資源
緊接着剛才所講的內容,點擊左邊的深度/模板緩沖區,我們就可以看到一張以紅色為背景,黑色代表深度值的紋理。黑色越深,深度值越小。
因為這張圖沒有模板值的變更,我再選擇一張帶有模板和深度值的輸出來演示。
實際上在這里,包含有模板值的區域應當是綠色,但是連同深度緩沖區的紅色混在一起就變成了黃色,我們可以關閉深度部分來觀察只包含模板值的綠色部分。
另一種方式就是更改查看方式。如DXGI_FORMAT_D24_UNORM_S8_UINT
同時包含了模板值和深度值,那DXGI_FORMAT_R24_UNORM_X8_TYPELESS
就只包含了深度值,DXGI_FORMAT_X24_TYPELESS_G8_UINT
則只包含了模板值。
查看該幀圖片下某一像素的繪制歷史
點擊加載的報告XX-XX.vsglog,然后選擇要觀察的某一個像素,就可以看到該像素從開始到結束都經歷了哪些繪制步驟,在某一個繪制事件還可以看到它屬於頂點/幾何着色器的哪一個圖元內,以及像素着色器、輸出合並器的經歷。
着色器調試
接下來就開始進入到重點部分了,使用圖形調試器的核心目的還是要觀察着色器運行的時候遇到了哪些問題。當然有時候甚至會遇到該有的着色器卻被跳過不執行的情況,這時候就先要去前面排查該綁定的資源、狀態、着色器、輸入是否都OK了,然后才是對上一個正常運行的着色器進行調試。
回到管線階段或者在像素的繪制歷史,指定某一個着色器階段,選擇一個元素,點擊一個類似播放的按鈕就可以開始進入着色器調試。
然后就會在着色器代碼實際可執行的第一行暫停停住。你可以設置斷點,也可以單步調試,像之前在VS調試那樣來調試。此時首先你需要優先關注局部變量中各個會被用到的常量、輸入值是否都是正常的,如果出現常量緩沖區中的值全0或者亂值的情況,說明常量緩沖區可能沒有被更新。若常量緩沖區的值在從C++端傳入到這里出現問題,你還需要去觀察常量緩沖區的打包是否出現了問題。
關於HLSL的打包規則,可以查看這里:
深入理解HLSL常量緩沖區打包規則
若出現局部變量有未使用的說明,有可能在這個調試器的確根本不會用到這個值,又或者你忘記將該常量緩沖區綁定到該着色器階段了。
而局部變量出現在作用域內的說明,則可能是該變量還沒被聲明出來或者沒被賦值,需要繼續執行才能看到。
着色器反匯編
一般來說我們看着色器的反匯編不主要是為了看匯編指令,而是它還附帶了一些額外的信息,如該着色器使用了哪些常量緩沖區、結構體,輸入/輸出簽名如何,這些常量緩沖區經過打包后各個元素所處的字節偏移量如何。
有的同學在還沒開始進行GPU調試的時候點擊了管道階段的藍字,然后看到編譯器輸出那欄字,以為反匯編沒有開啟。實際上是你的打開方式不對。
進入着色器調試后,對着色器代碼右鍵,選擇 轉到反匯編,就可以看到反匯編指令,又或者是點擊上方的反匯編窗口切換:
然后一路往上滾,滾到開頭就可以看到上述所說的內容:
以編程方式捕獲圖形信息
在某些特殊情況下,你可以需要用到編程捕獲的方法:
- 圖形應用不使用交換鏈,即只是渲染到一張單獨的紋理
- 使用DirectCompute(計算着色器)來執行計算
- 通過流輸出階段將數據輸出到緩沖區
- 在手動測試的時候難以預測和捕獲問題幀,但可以通過編程的方式預測出現問題幀的情況
Windows 8.1及以上的編程捕獲
DirectX 11.2 API需要Windows 8.1及更高版本系統的支持。接下來你需要完成下面的任務:
- 准備好所需頭文件
- 獲取
IDXGraphicsAnalysis
接口 - 開啟圖形調試,捕獲圖形信息
注意:以前的編程捕獲的實現依賴於Visual Studio遠程工具提供的捕獲功能,但從Windows 8.1起可以直接通過Direct3D 11.2來支持捕獲功能。因此,你不需要在Windows 8.1上安裝用於編程捕獲的遠程工具。
首先你需要包含下面的這些頭文件:
#include <DXGItype.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <DXProgrammableCapture.h>
需要注意的是,這些頭文件無法與頭文件vsgcapture.h
兼容,因為它不能與DirectX 11.2兼容。如果在d3d11_2.h
后面包含該頭文件,編譯器將會發出警告;而如果在d3d11_2.h
前面包含該頭文件,應用程序將不會啟動。
此外,如果你的電腦安裝了DirectX SDK(June 2010),並且你的項目包含路徑包括了%(DXSDK_DIR)Include\
,請將它移到包含路徑的最末端或者去掉。
然后你需要添加下面代碼以獲取DXGI調試接口IDXGraphicsAnalysis
:
IDXGraphicsAnalysis* pGraphicsAnalysis;
HRESULT getAnalysis = DXGIGetDebugInterface1(0, __uuidof(pGraphicsAnalysis), reinterpret_cast<void**>(&pGraphicsAnalysis));
if (FAILED(getAnalysis))
{
// 終止你的應用程序
}
如果你沒有以圖形調試形式啟動程序,DXGIGetDebugInterface1
將返回E_NOINTERFACE
現在假定你已經獲取了一個能用的IDXGraphicsAnalysis
接口,你可以使用BeginCapture
和EndCapture
方法捕獲圖形信息:
pGraphicsAnalysis->BeginCapture();
// ...這部分管線命令都將被捕獲到
pGraphicsAnalysis->EndCapture();
現在,你應該可以看到計算着色器的調試了:
早期版本的編程捕獲
如果你的系統不支持DirectX 11.2及以上版本的API,則可以使用該舊版圖形捕獲方法。這種方法適用於任意DirectX 11.X版本API中使用。
首先你需要包含頭文件vsgcapture.h
,然后創建VsgDbg
對象。關於VsgDbg
類,目前你只需要了解這些方法:
方法名 | 描述 |
---|---|
構造函數 | 形參指定為true 時,將默認產生臨時的vsglog文件 |
BeginCapture | 從該語句執行起捕獲所有的GPU事件 |
EndCapture | 結束以BeginCature開始的捕獲事件 |
CaptureCurrentFrame | 捕獲從當前語句到這一幀結束的所有GPU事件 |
通常情況下我們可以構造函數的形參指定為true
,然后可以開始捕獲圖形信息:
pVSGraphicsDebugger->BeginCapture();
// ...這部分管線命令都將被捕獲到
pVSGraphicsDebugger->EndCapture();
要想了解更多的信息,可以查閱MSDN文檔(編程捕獲)
常見案例
案例1 :繪制的天空盒異常
這是一個比較經典的案例。在繪制天空盒的時候會出現天空盒異常,甚至有閃屏現象。當然這個案例可以上升為:繪制的幾何體出現異常。
在圖形調試截取異常幀后,進入該幀進行分析。選擇繪制天空盒的那一次繪制調用,可以看到輸入裝配器出現了問題
通常說頂點緩沖區是不會出問題的,現在來觀察索引緩沖區,使用格式uint uint uint
來觀察,發現索引明顯有異常:
這時候要考慮是不是索引緩沖區使用了16位的索引,我們將格式換成ushort ushort ushort
看看:
這時候就可以確定索引緩沖區用的是16位索引了,我們可以推測,導致繪制出問題的原因是緩沖區用的是16位索引,但繪制的時候卻是按32位索引來解析的。因此我們可以定位代碼到Geometry.h
和SkyRender.cpp
來排查並解決這個問題。
總結
調試技巧需要經常使用才能夠熟練掌握,相比普通調試來說,圖形調試會更加復雜。在初學DX的階段容易在資源管理上出問題,因此重點是要先確認在繪制之前,綁定到渲染管線的各種資源是否正常,然后才是對着色器代碼進行調試。所以前期准備工作的出錯一般占很大的一部分,而着色器代碼引發的錯誤可能只是占較小的一部分。等到了渲染管線的資源綁定管理體系逐漸穩定以后,使用圖形調試的重心才會逐漸轉移到以着色器代碼的調試為主。有時候圖形調試器解決不了的問題,還需要仔細觀察普通調試下的輸出窗口是否有渲染管線繪制事件執行時輸出的報錯信息。
當然里面還有很多強大的功能沒有挖掘出來,或者現在還不是比較常用而沒列出來。有興趣的讀者可以查看微軟的官方中文文檔了解一下:
這篇博客在后續還會有所變動,因為后續個人的學習會引發新的調試需求而變動。
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。