AMD Radeon™ GPU Detective
AMD Radeon™ GPU Detective (RGD) 是一款用于 GPU 崩溃事后分析的工具。RGD 可以捕获 DirectX® 12 应用的 AMD GPU 崩溃转储。
Radeon GPU Detective (RGD) 是一款用于 GPU 崩溃事后分析的工具。该工具允许开发者捕获和分析 AMD GPU 崩溃转储,并生成有助于缩小崩溃根本原因搜索范围的信息。此类信息包括页面错误详细信息、资源详细信息以及反映崩溃前 GPU 工作进度的执行标记。
在本教程中,我们将学习如何使用针对 Windows 上的 GPU 崩溃的 RGD v1.0。
RGD v1.0 专为捕获 Windows 上的 GPU 崩溃而设计。如果 GPU 故障(例如内存页面错误或着色器中的无限循环)导致 GPU 驱动程序在预定时间内(Windows 上默认是 2 秒)不响应操作系统,操作系统将检测到该故障并尝试重启或移除设备。此机制也称为“TDR”(Timeout Detection and Recovery),是此工具范围内我们所考虑的 GPU 崩溃。
从功能上看,当 GPU 崩溃发生时,屏幕可能会闪烁或变黑几秒钟,“AMD Bug Report Tool”窗口将会出现。
在崩溃的应用程序代码中,D3D12 或 DXGI 函数(如 IDXGISwapChain::Present())将返回错误代码,例如 DXGI_ERROR_DEVICE_RESET、DXGI_ERROR_DEVICE_REMOVED、DXGI_ERROR_DEVICE_HUNG 或 DXGI_ERROR_DRIVER_INTERNAL_ERROR,并且 D3D12 Device 对象将变得不可用。
请注意,RGD 不会检测纯 CPU 崩溃(例如,CPU 空指针解引用或整数除以零)。您需要使用 CPU 调试器来处理这些情况。请使用 Microsoft® Visual Studio 等 CPU 调试机制来调查此类案例。
不正确使用 D3D12 的渲染代码也可能仅在 CPU 上失败,而不会触及图形驱动程序或 GPU。因此,此类崩溃不会被 RGD 捕获。它们通常会返回 DXGI_ERROR_INVALID_CALL 错误代码,并且通常由 D3D12 Debug Layer 检测到。
开始之前,让我们总结一下要求,以确保该工具适合您的用例
请注意,该工具会将驱动程序置于“Crash Analysis”(崩溃分析)模式,并采用更严格的页面错误处理策略。这意味着,在没有使用该工具的情况下,某些在特定 AMD GPU 上可能不会导致游戏崩溃的页面错误,在使用 RGD 时将被观察到。这有助于您检测此类崩溃并修复它们,以提高游戏在各种 AMD GPU 上的稳定性。
最后,为了充分利用 RGD,强烈建议使用 AMD GPU Services (AGS) 库围绕渲染通道添加字符串标记,这与 RGP 类似,并使用 ID3D12Object::SetName() 来命名 Direct3D 12 内存对象(堆、缓冲区、纹理),因为这些字符串会出现在崩溃分析摘要文件中,并在发生页面错误时有助于识别相关资源。
现在我们终于准备好开始了。
我们将首先下载包含 RGD 的最新 Radeon Developer Tool Suite。您可以从我们的 GPUOpen Tools 页面下载该软件包。
使用 RGD 非常简单。我们将通过启动 Radeon Developer Panel (RDP) 来开始,这与其他 RDTS 工具(如 Radeon GPU Profiler (RGP)、Radeon Memory Visualizer (RMV) 和 Radeon Raytracing Analyzer (RRA))类似。
启动 Radeon Developer Panel (RDP):“RadeonDeveloperPanel.exe”。
RDP 应用程序的窗口会出现。
如果未自动配置,我们需要通过按“Connect”(连接)按钮建立本地连接。
窗口左上角会出现一个绿色的圆圈,表示连接已激活。这是工具直接连接到 AMD 图形驱动程序的连接。
{w=969px}
在“SYSTEM”(系统)->“My applications”(我的应用程序)选项卡下,选择 Global workflow = “Crash Analysis”(全局工作流 = “崩溃分析”)。这是我们为 RGD 添加的新工作流。它会将驱动程序置于崩溃分析模式,并在发生 GPU 崩溃时捕获 AMD GPU 崩溃转储(.rgd 文件)。
{w=655px}
启动游戏。游戏可以作为独立可执行文件启动,也可以从 Steam、Epic Launcher 等启动器启动,或者以任何其他方式启动。
如果 RDP 检测到游戏正在运行并使用 Direct3D 12,它将自动切换到一个专用于该可执行文件的新选项卡,并显示“Status: Active”(状态:活动)。
{w=674px}
玩游戏并重现崩溃。
对于 RGD v1.0,崩溃分析的性能开销通常很低,因此在大多数情况下,游戏在崩溃重现步骤中应该可以完美运行。您也不必担心会话的长度。尽管 AMD GPU 崩溃转储文件(如果创建)的大小会随着时间的推移而增长,但通常只有几十兆字节。
当检测到 TDR 并终止游戏时,将创建一个新的 AMD GPU 崩溃转储文件(.rgd 文件)。它会出现在 RDP 的“Recently collected dumps”(最近收集的转储)列表中,仍然在专用于特定游戏可执行文件的选项卡上。
双击崩溃转储文件,或者右键单击并从上下文菜单中选择“Open text summary”(打开文本摘要),将在系统文本编辑器中打开一个文本文件,其中包含崩溃报告。
{w=737px}
就是这样!让我们双击 .rgd 文件以打开崩溃分析摘要文本文件,并学习如何解释结果。
崩溃分析文本文件包含以下部分
让我们深入探讨一下相关部分。
前两个部分(CRASH ANALYSIS FILE 和 SYSTEM INFO)是自explanatory 的。它们提供的信息有助于识别发生崩溃的配置,并提供崩溃分析会话的基本详细信息。如果您选择与我们 AMD 分享 AMD GPU 崩溃转储文件,这些信息也将对我们很有用。
“EXECUTION MARKER TREE”(执行标记树)部分是理解 GPU 在崩溃发生前那一刻所执行工作的关键。它包含命令列表、绘制调用和渲染通道的层次结构,这些都在崩溃发生时正在进行中。请注意,有两种类型的标记:默认标记,它们已嵌入驱动程序中,围绕关键渲染操作;以及用户标记,您可以,作为应用程序开发者,在应用程序代码中添加。默认情况下,如果应用程序代码中未添加用户标记,您将看到一个平面的渲染操作列表,例如绘制调用(显示为“Draw”)和计算调度(显示为“Dispatch”)。为了充分利用该工具,**强烈建议在应用程序源代码中添加用户标记**,并在渲染通道和其他渲染帧的逻辑部分周围使用有意义的字符串名称。这样,崩溃分析摘要文件将以树状图的形式包含崩溃期间发生的 GPU 工作情况的层次可视化。例如
Command Buffer ID: 0x107c=========================[>] "Frame 1040 CL0" ├─[X] "Depth + Normal + Motion Vector PrePass" ├─[X] "Shadow Cascade Pass" ├─[X] "TLAS Build" ├─[X] "Classify tiles" ├─[X] "Trace shadows" ├─[X] "Denoise shadows" ├─[X] "GltfPbrPass::DrawBatchList" ├─[X] "Skydome Proc" ├─[X] "GltfPbrPass::DrawBatchList" ├─[>] "DownSamplePS" │ ├─[X] Draw │ ├─[X] Draw │ ├─[X] Draw │ ├─[>] Draw │ └─[>] Draw └─[>] "Bloom" ├─[>] "BlurPS" │ ├─[>] Draw │ └─[>] Draw ├─[>] Draw ├─[>] "BlurPS" │ ├─[>] Draw │ └─[>] Draw ├─[ ] Draw ├─[ ] "BlurPS" ├─[ ] Draw ├─[ ] "BlurPS" ├─[ ] Draw ├─[ ] "BlurPS" └─[ ] Draw有几种方法可以添加用户标记,使其出现在 RGD 输出文件中
PIXBeginEvent、PIXEndEvent 调用)由 Microsoft DirectX 运行时使用,未触及我们的驱动程序,因此它们不受支持。但是,我们提供了替换头文件,可以自动插入这些标记,以方便集成。您可以在 Radeon Developer Tool Suite 包的“samples/AmdDxExt/AmdPix3.h”下找到它。有关如何使用此机制的更多详细信息,请参阅 RGP 文档中的此部分。D3D12.EmitRgpFrameMarkers 设置为 1。执行标记的状态由以下符号表示
[X] 已完成[>] 进行中[ ] 未开始状态(未开始、进行中、已完成)是根据 GPU 从命令列表中获取并在 GPU 流水线各个阶段执行的命令确定的,这不仅仅包括着色器的执行。这意味着,即使在它们之间存在屏障,进一步的通道和绘制调用也可能在它们开始执行着色器之前就显示为“进行中”。
了解这一点后,解释多个显示为“进行中”的通道和绘制调用的方法是:
“MARKERS IN PROGRESS”(进行中的标记)部分以更简洁的形式总结了与执行标记树相同的信息。它只显示**在崩溃期间进行中的标记和绘制调用的列表**。此部分中的每个项目都看起来像一个路径,其中 / 表示层次结构,如下所示
Command Buffer ID: 0x107c=========================Frame 1040 CL0/DownSamplePS/Draw [2 repeating occurrences]Frame 1040 CL0/Bloom/BlurPS/Draw [2 repeating occurrences]Frame 1040 CL0/Bloom/DrawFrame 1040 CL0/Bloom/BlurPS/Draw [2 repeating occurrences]如前所述,“PAGE FAULT SUMMARY”(页面错误摘要)部分仅在崩溃被确定为由内存页面错误引起时出现。当 GPU 尝试访问不正确或非法的内存地址(即没有有效缓冲区、纹理、描述符、命令列表或其他 D3D12 资源的地址)时,就会发生页面错误。发生这种情况时,我们可以在此部分中看到有问题的虚拟地址(VA),以及在崩溃的游戏进程内存中曾出现过并且位于有问题的 VA 中的所有资源的详细信息。例如
Resource id: 0x5a49f0600000a7f Type: Image Name: Postprocessing render target 4 Virtual address: 0x236c00000 [size: 16810352 (16.03 MB), parent address + offset: 0x236c00000 + 0x0, preferred heap: Local] Commit type: COMMITTED Attributes: Create flags: PREFER_SWIZZLE_EQUATIONS | FIXED_TILE_SWIZZLE (24576) Usage flags: SHADER_READ | SHADER_WRITE | RESOLVE_DESTINATION | COLOR_TARGET (27) Image type: 2D Dimensions <x, y, z>: 1920 x 1080 x 1 Swizzle Pattern: XYZW Image Format: X16Y16Z16W16_FLOAT Mip levels: 1 Slices: 1 Sample count: 1 Fragment count: 1 Tiling type: Optimal Resource timeline: 00:00:09.4618368 : Create 00:00:09.4622336 : Bind into 0x236c00000 00:00:09.4622336 : Make Resident into 0x236c00000 00:00:09.4634816 : Destroy识别资源的简便方法是使用其名称。如果资源在应用程序代码中使用 D3D12 Release() 调用使用 ID3D12Resource::SetName() 命名,则该名称将出现在 RGD 文本输出文件(上例中的“Postprocessing render target 4”)中该资源的“Name”(名称)字段中。如果资源未命名,您仍然可以根据其参数(如纹理尺寸、像素格式、Mip 级别数等)来识别资源。
如上例所示,每个资源都有一个与之关联的事件“Timeline”(时间线),如 Create(创建)、Destroy(销毁)、Bind(绑定)、Evict(驱逐)、Make Resident(常驻)。其中一些来自显式的 DX12 调用(例如,资源的创建),一些由驱动程序自动生成(资源绑定到内存分配,并在创建后自动使其常驻)。时间戳的格式为“hh:mm:ss.clks”,表示从崩溃进程开始计时。
现在我们已经涵盖了崩溃分析文本输出文件的不同部分,让我们退一步从整体上看。TDR 可能有各种原因。解释 RGD 生成结果的一般方法是:
| 检测到页面错误? | VA 是否有关联的资源? | 含义 |
|---|---|---|
| 是 | 是 | 可能尝试访问已释放的资源 |
| 是 | 否(表示从未有资源驻留在此 VA 中) | 可能发生越界索引或使用垃圾数据作为索引/指针 |
| 否 | 否 | 挂起,例如着色器中的无限循环(使用标记缩小范围) |
让我们详细说明
如果检测到页面错误并找到关联资源,这很可能意味着 bug 是关于在资源释放或从内存中驱逐后访问该资源。不正确的(过时的或错误索引的)描述符可能是原因。然后,检查每个资源的“Timeline”将是一个好主意。
当资源时间线以 Destroy(销毁)事件结束时,表示 GPU 在使用 D3D12 Release() 调用释放该资源后,仍然访问了该资源。
当资源时间线以 Evict(驱逐)事件结束时,表示 GPU 在使用 D3D12 Evict() 调用驱逐该资源后,仍然访问了该资源。
当资源时间线不包含 MakeResident(常驻)事件时,表示该资源被创建为非驻留。
如果检测到页面错误但未找到关联资源,这很可能意味着 GPU(例如着色器)尝试访问错误的地址内存,这可能表明地址计算或索引存在 bug。
当未检测到页面错误时,这很可能意味着崩溃与内存访问无关,而是其他类型的其他问题,例如由于超时(执行时间过长)或无限循环导致的着色器挂起。
在使用工具之前,请先启用 D32D12 Debug Layer 来测试游戏。D3D12 Debug Layer 可以捕获某些不会到达 GPU 驱动程序或 GPU 本身,并且工具无法检测到的错误。这样做可以为您节省大量时间。
插入更细粒度的标记:如果 RGD 显示的执行标记无法提供关于崩溃区域足够精确的信息,您可以在渲染代码中围绕某些渲染通道,甚至单个绘制调用来插入更细粒度的标记,并在它们的字符串中包含额外信息,例如材质、着色器或特定对象的名称。使用 RGD 显示的进行中标记作为指南,来确定可能需要更多标记的位置。
尝试启用崩溃分析的 DRED:如果您的代码实现了自定义功能来报告 GPU 崩溃(使用 WriteBufferImmediate() 或 Device Removed Extended Data (DRED) API),RGD 也可以与之一起使用。在 RDP 中启用“Crash Analysis”(崩溃分析)可以使此类自定义面包屑标记更准确。为此,请按照捕获 GPU 崩溃转储的相同步骤进行操作。这将确保在您的应用程序运行时,驱动程序中会启用崩溃分析模式。