RenderDoc圖形調試器詳細使用教程(基於DirectX11)

前言

由於最近Visual Studio的圖形調試器老是抽風,不得不尋找一個替代品了。

對於圖形程序開發者來說,學會使用RenderDoc圖形調試器可以幫助你全面瞭解渲染管線綁定的資源和運行狀態,從而確認問題所在。

RenderDoc官網

DirectX11 With Windows SDK完整目錄

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

運行程序

爲了調試我們的程序,需要通過RenderDoc來執行程序。

選擇File - Launch Application後,在Program - Executable Path中選擇要打開的程序。

注意:在你自己編寫的項目需要將exe放到項目(.vcxproj)所在的位置,或者讓VS在生成程序的時候輸出到項目位置!

如果待調試的程序需要加載Assimp的動態庫,我們還需要添加環境變量:

然後就可以點擊Launch運行程序了。

截取一幀畫面

在進入程序後,按下Print Screen(PrtSc)鍵截取一幀有問題的畫面,然後就可以看到程序窗口說已經捕獲了一幀:

捕獲完成後退出程序即可,捕獲的一幀文件類型爲*.rdc

你可以在一次調試截取多幀畫面,但基本上目前我們只需要截取一幀畫面就可以退出程序了。

事件瀏覽器(Event Broser)

下面是圖形調試器的主界面:

事件瀏覽器展示了DirectX中關於ID3D11DeviceContext的重要調用,呈現了這一幀繪製涉及到的ClearDrawDispatchPresentResolve等命令。選擇具體某個事件,可以在下面的API Inspector看到在這個事件之前大概15個DeviceContext的調用事件。

事件瀏覽器會將繪製到同一系列渲染目標和深度緩衝區的事件摺疊成一個Pass,我們可以展開觀察裏面的具體繪製過程。

在選中某次繪製後,我們可以觀察的有:

  • Texture Viewer:完成當前繪製後渲染目標的結果、深度緩衝區的結果、像素着色器調試
  • Pipeline State:觀察當前渲染管線有哪些階段是被激活的,以及不同的階段狀態是怎樣的
  • Mesh Viewer:觀察當前正在渲染的模型從頂點輸入是什麼情況,經過頂點着色輸出後又是什麼情況,並且能夠觀察正在渲染的模型
  • Resource Inspector:觀察當前繪製後有哪些資源,狀態如何

接下來會按教程的順序來講可能需要查看的內容

Pipeline State

在管線狀態中我們可以清楚地看到當前有哪些執行的階段,選擇IA(輸入裝配階段)可以看到輸入佈局頂點緩衝區輸入圖元類型

如果找不到窗口可以去菜單欄Window找到Pipeline State。

Mesh Viewer

點擊上圖中的Mesh View內的立方體可以跳轉到模型線框觀察頁面,同時可以觀察輸入的頂點數據:

通過Controls可以切換攝像機模式爲第一人稱,然後使用WSAD移動

如果屏幕上沒有渲染出想要的東西,首先應當檢查的是輸出的頂點SV_POSITION是否位於NDC空間內,具體爲:

\[-1\leq\frac{x}{w}\leq 1\\ -1\leq\frac{y}{w}\leq 1\\ 0\leq\frac{z}{w}\leq 1 \]

要調試某個頂點,只需要在VS Input中選擇一個頂點右鍵 - Debug this vertex即可進入着色器調試。但調試環節我們留到後面再講。

Texture Viewer

在Texture Viewer中我們可以觀察綁定到管線上的圖片(Input),以及渲染管線輸出到的渲染目標、深度緩衝區(Output)。在選擇某個Output圖後,我們右鍵選中一個像素,右下角的Pixel Context就會顯示具體的位置:

選擇History可以查看在此之前有哪些繪製事件影響到當前像素,選擇Debug則可以調試當前像素。

觀察深度/模板緩衝區

選中深度/模板緩衝區,一般情況下越遠的物體顯得越白,越近顯得越黑,且深度圖的顏色分佈大多在白色上。

而如果使用了反向Z,越遠的物體顯得越黑,越近顯得越白,且分佈大多在黑色上,這時候看深度圖就是純黑一片,根本不知道什麼情況:

由於此時深度值大部分在靠近0的位置上,我們需要縮小顯示範圍來提高較遠物體的亮度:

爲了觀察模板測試的結果,我們先選中Stencil,如果模板的輸出值爲1,可能需要將Range右邊的條拖到最左邊纔看得到(白色區域模板值爲1,黑色區域模板值爲0):

在Overlay中,我們可以觀察當前繪製中影響到的像素區域、深度測試(綠Pass紅Fail)、模板測試、背面剔除等結果。下圖演示了模型的線框在圖中的位置:

Resource Inspector

在這裏可以觀察與當前繪製相關的所有資源:

選中某個資源後,可以看到和它相關的資源、資源在哪些事件中被用到、資源初始化相關的調用。

觀察常量緩衝區

在管道狀態的着色器階段中,我們可以看到綁定的常量緩衝區:

其中Slot的名稱來自着色器聲明cbuffer時的名稱,Buffer的名稱則需要在C++代碼中設置,具體參考下一節。

選擇某一個常量緩衝區,點擊Go處的箭頭,我們就可以看到裏面的具體內容:

注意:在當前教程中我們會傳入經過DirectXMath轉置後的矩陣,但是在這裏觀察值的時候,依然是以行矩陣的方式顯示纔是正常的!即平移分量位於第四行。

若常量緩衝區的值在從C++端傳入到這裏出現問題,你還需要去觀察常量緩衝區的打包是否出現了問題。

關於HLSL的打包規則,可以查看這裏:
深入理解HLSL常量緩衝區打包規則

爲圖形調試器的對象添加自定義名稱

看前面的圖片,Buffer在沒有指定名稱的時候默認是以Buffer 142的形式顯示的。等對象一多,我們就難以判別管線所綁定的對象是否正確。因此在某些需要的情況下,我們可以在C++代碼來爲對象指定名稱。

d3dUtil.h中提供了兩個系列的函數,一個用於D3D設備創建出來的對象,一個用於DXGI對象。通過SetPrivateData方法,並使用WKPDID_D3DDebugObjectNameGUID使得我們可以爲其設置圖形調試器下的名稱(string_view版本要求C++17,或者可以參照舊d3dUtil.h中的實現):

// ------------------------------
// D3D11SetDebugObjectName函數
// ------------------------------
// 爲D3D設備創建出來的對象在圖形調試器中設置對象名
// [In]resource				D3D11設備創建出的對象
// [In]name					對象名
inline void D3D11SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ std::string_view name)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
	resource->SetPrivateData(WKPDID_D3DDebugObjectName, (UINT)name.length(), name.data());
#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					對象名
inline void DXGISetDebugObjectName(_In_ IDXGIObject* object, _In_ std::string_view 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
}

在已經設置過名字的情況下,想要更名需要先調用nullptr_t重載版本,再調用正常版本。

設置好後,在圖形調試的時候一看名字就能知道綁定的情況了。

如果你不希望使用調試器對象具名化,可以在d3dUtil.h的開頭找到這樣的宏:

// 默認開啓圖形調試器具名化
// 如果不需要該項功能,可通過全局文本替換將其值設置爲0
#ifndef GRAPHICS_DEBUGGER_OBJECT_NAME
#define GRAPHICS_DEBUGGER_OBJECT_NAME (1)
#endif

將其修改後只會剩下默認的DDSTextureLoaderWICTextureLoader的對象具名化。

注意:在你的Release版本應用程序應該避免出現對調試對象名稱的設置。你可以將相關代碼移出項目。

查看着色器資源視圖中的紋理資源

以下圖像素着色器階段的爲例:

我們可以很清楚地看到資源的綁定情況,紅色表示當前Slot沒有資源綁定上去,如果對沒有綁定紋理的對象進行採樣,會在程序調試運行時的調試輸出窗口看到DX Error。當然本示例紅的也並不影響,因爲會在着色器檢查Dimension是否爲0從而避開採樣。

綠色的資源姑且認爲是一個有UNKNOWN含義的DXGI格式,在通過SRV具體化。點擊Go的箭頭我們可以觀察傳入的着色器資源。

查看管線狀態、採樣器

基本上光柵化狀態、深度/模板狀態和混合狀態都是所見即所得

採樣器則在像素着色器階段選中採樣器可以查看

雖然這些狀態你也可以在C++看

着色器調試

接下來就開始進入到重點部分了,使用圖形調試器的核心目的還是要觀察着色器運行的時候遇到了哪些問題。當然有時候甚至會遇到該有的着色器卻被跳過不執行的情況,這時候就先要去前面排查該綁定的資源、狀態、着色器、輸入是否都OK了,然後纔是對上一個正常運行的着色器進行調試。

對於頂點着色器,在Mesh Viewer中選擇要調試的頂點右鍵 - Debug this vertex即可

對於像素着色器,在Texture Viewer中的Output選擇RT後,右鍵選取某一像素,在Pixel Context處點Debug即可

而調試計算着色器,需要在Pipeline State選擇CS,按下圖選擇Debug,然後填寫要調試的線程組編號和組內線程編號(或者全局線程ID):

然後就進入到了着色器調試界面:

因爲鼠標操作麻煩,我們需要記住幾個快捷鍵:F10單步跳過,F11單步進入,ctrl+F11單步跳出

左側Constants & Resources可以查看頂點輸入、使用的常量、資源等,右側Watch可以添加變量觀察

鼠標懸停在代碼的變量可以觀察變量值

右鍵代碼Go to disassembly可以轉匯編查看

左側file list可以查看用到的hlsl文件,以及編譯shader時候的預定義宏

此時首先你需要優先關注局部變量中各個會被用到的常量、輸入值是否都是正常的,如果出現常量緩衝區中的值全0或者亂值的情況,說明常量緩衝區可能沒有被更新。

修改着色器再運行

這是VS的圖形調試器所沒有的功能,在修改了某次繪製用到的着色器代碼並編譯後,就可以影響到當前及之後的所有繪製。

下面是一個例子,這裏嘗試修改某個繪製的像素着色器代碼:

然後嘗試修改下面g_VisualizePerSampleShaingtrue,使得當前繪製的像素顏色強制爲紅色:

完成後選擇Apply changes,返回Texture Viewer觀察渲染目標的輸出變化:

可以看到,那些執行PS的像素都被染成了紅色,觀看後續的幀也可以發現的確產生了影響:

如果要退回變化,則回到像素着色器的Edit處,選擇Remove changes即可。

以編程方式捕獲圖形信息

因爲目前暫時還沒有使用的需要,具體信息查看下面文檔:

https://renderdoc.org/docs/in_application_api.html

如果某些DrawCall、Dispatch不是每幀都會產生的話,編程捕獲的方式還是有必要的。

總結

調試技巧需要經常使用才能夠熟練掌握,相比普通調試來說,圖形調試會更加複雜。目前RenderDoc的調試體驗比VS的圖形調試器會好一些,並且最近VS的圖形調試器有些問題,調試不了shader。在初學DX的階段容易在資源管理上出問題,因此重點是要先確認在繪製之前,綁定到渲染管線的各種資源是否正常,然後纔是對着色器代碼進行調試。所以前期準備工作的出錯一般佔很大的一部分,而着色器代碼引發的錯誤可能只是佔較小的一部分。等到了渲染管線的資源綁定管理體系逐漸穩定以後,使用圖形調試的重心纔會逐漸轉移到以着色器代碼的調試爲主。有時候圖形調試器解決不了的問題,還需要仔細觀察普通調試下的輸出窗口是否有渲染管線繪製事件執行時輸出的報錯信息。

當然裏面還有很多強大的功能沒有挖掘出來,或者現在還不是比較常用而沒列出來。有興趣的讀者可以查看renderdoc的文檔:

Introduction — RenderDoc documentation

這篇博客在後續還會有所變動,因爲後續個人的學習會引發新的調試需求而變動。

DirectX11 With Windows SDK完整目錄

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章