前言

由于最近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,以及有什么问题也可以在这里汇报。

RenderDoc图形调试器详细使用教程(基于DirectX11)的更多相关文章

  1. Visual Studio图形调试器详细使用教程(基于DirectX11)

    前言 对于DirectX程序开发者来说,学会使用Visual Studio Graphics Debugger(图形调试器)可以帮助你全面了解渲染管线绑定的资源和运行状态,从而确认问题所在.现在就以我 ...

  2. [笔记]Python的调试器pudb简易教程

    Linux下运行python脚本,pudb是一个不错的调试器. 语法高亮,断点,调用栈,命令行,都有了,如下图. [安装] pip install pudb [使用] pudb xxx.py [快捷键 ...

  3. [原创]iFPGA-Cable FT2232H Xilinx / Altera / Lattice 三合一JTAG & UART调试器-详细使用说明

    iFPGA-Cable调试器使用说明 全文分为6部分: 第0部分:实物.连线及其驱动安装说明 第1部分:Xilinx JTAG 第2部分:UART 第3部分:Altera JTAG 第4部分:Latt ...

  4. 使用GDB命令行调试器调试C/C++程序

    原文:http://xmodulo.com/gdb-command-line-debugger.html作者: Adrien Brochard 没有调试器的情况下编写程序时最糟糕的状况是什么?编译时跪 ...

  5. 17. Debuggers (调试器 5个)

    反编译是安全研究的重要组成部分. 它将帮助您解剖Microsoft补丁,以发现他们无法告诉您的默认修复的错误,或更仔细地检查服务器二进制文件以确定为什么您的漏洞利用不起作用. 许多调试器都可用,但ID ...

  6. 使用GDB命令行调试器调试C/C++程序【转】

    转自:https://linux.cn/article-4302-1.html 编译自:http://xmodulo.com/gdb-command-line-debugger.html作者: Adr ...

  7. 仿迅雷播放器教程 -- 基于VLC的MFC播放器 (6)

        代码下载:http://download.csdn.net/detail/qq316293804/6409417   昨天的教程里写着预计MFC播放器会隔得久一点,但是今晚仔细看了下VLC的常 ...

  8. 最全Pycharm教程(10)——Pycharm调试器总篇

    最全Pycharm教程(1)--定制外观 最全Pycharm教程(2)--代码风格 最全Pycharm教程(3)--代码的调试.执行 最全Pycharm教程(4)--有关Python解释器的相关配置 ...

  9. SSM框架——详细整合教程

    SSM框架——详细整合教程(Spring+SpringMVC+MyBatis) 1.基本概念   1.1.Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Jav ...

随机推荐

  1. linux历史命令审计

    一.添加历史命令记录 1.首先在/etc/profile中添加 export HISTORY_FILE=/var/log/Command/Command.log export PROMPT_COMMA ...

  2. 清理 Docker 占用的磁盘空间

    Docker 很占用空间,每当我们运行容器.拉取镜像.部署应用.构建自己的镜像时,我们的磁盘空间会被大量占用. 如果你也被这个问题所困扰,咱们就一起看一下 Docker 是如何使用磁盘空间的,以及如何 ...

  3. 渗透测试之本地文件包含(LFI)

    一.本地文件包含 本地文件包含漏洞指的是包含本地的php文件,而通过PHP文件包含漏洞入侵网站,可以浏览同服务器所有文件,并获得webshell. 看见?page=标志性注入点,提示我们输入?=pag ...

  4. PostgreSQL VACUUM 之深入浅出 (四)

    VACUUM 参数优化 上面已经介绍过了以下设置表级 AUTOVACUUM 相关参数和 autovacuum_max_workers: ALTER TABLE pgbench_accounts SET ...

  5. C# 成员访问修饰符protected internal等

    1.C#4个修饰符的权限修饰符 级别 适用成员 解释public 公开 类及类成员的修饰符 对访问成员没有级别限制private   私有 类成员的修饰符 只能在类的内部访问protected 受保护 ...

  6. 基于COCO数据集验证的目标检测算法天梯排行榜

    基于COCO数据集验证的目标检测算法天梯排行榜 AP50 Rank Model box AP AP50 Paper Code Result Year Tags 1 SwinV2-G (HTC++) 6 ...

  7. Android系统编程入门系列之硬件交互——无线通信WLAN

    Android系统的移动设备大多支持无线WLAN技术.利用该技术,不仅能实现互联网通信,还能实现无线定位,热点共享等远程通信功能.针对使用WLAN的不同功能,可能需要分别申请不同的权限声明,同时调用不 ...

  8. Python:获取某一月的天数

    import calendarcalendar.monthlen(2021,6)30calendar.monthrange(2021,6)(1, 30) calendar.monthrange( ye ...

  9. package.xml使用说明

    1. package.xml使用说明 a. pacakge.xml 包含了package的名称. 版本号. 内容描述. 维护人员. 软件许可. 编译构建工具. 编译依赖. 运行依赖等信息. 2. pa ...

  10. 虚拟机服务启动失败报错npm ERR! code ELIFECYCLE

    可能是由于node_modules模块中缺失或者某些东西冲突引起的,我们可以使用如下的方法解决这个: rm -rf node_modules 删除,不询问 rm package-lock.json 删 ...