跳至内容

FidelityFX 面包屑 1.0.1

面包屑库有助于进行事后 GPU 崩溃分析。

问题

由于 **Direct3D 12** 和 **Vulkan** 等现代图形 API 的显式性质,很容易因 API 使用不当而导致 GPU 崩溃。当发生此类崩溃时,操作系统会使用一种称为 **超时检测恢复 (Timeout Detection Recovery)** (简称 TDR) 的机制来重新启动 GPU 并使其恢复联机。然而,在此过程中,所有 GPU 进程都会终止,应用程序会收到 **设备丢失 (Device Lost)** 错误,这意味着它们必须重新初始化所有图形功能。这通常意味着向用户显示错误消息,保存所有可能的状态并重新启动整个应用程序。要调试此类崩溃,必须了解复杂渲染过程的哪个部分失败了,以及从哪里开始查找以修复问题。事后分析唯一可用的数据是稀疏的,并且会导致对引擎整个图形子系统的调查,这需要很长时间。

解决方案

为了帮助开发者调试 GPU 崩溃,已经开发了一种技术,该技术基于在实际 GPU 工作周围放置面包屑,以确定在崩溃时正在执行哪些命令。这些面包屑由写入特殊缓冲区组成,这些写入只能在所需的命令运行之前和完成后不久发生。通过检查此缓冲区,可以重建 GPU 上实际执行的工作负载图,以及在崩溃期间已完成、正在进行或尚未开始的命令。有了这些信息,开发者就可以更好地了解渲染过程中的问题,从而开始查找崩溃的根源,从而大大缩短解决时间。

示例输出

以下是 GPU 崩溃后,带有面包屑树的库的示例输出。帧 251 和 252 的命令都尚未开始,而 GPU 在帧 250 中已完成直到 ClearRenderTarget 操作。有一个正在进行的命令,这是导致崩溃的主要原因:帧 250 中的 DrawIndexed: "Draw simple triangle"

[BREADCRUMBS]
<Frame 250>
- [>] Queue type <0>, submission no. 0, command list 1: "VK test command list"
├─[X] RESOURCE_BARRIER: "Backbuffer barrier to RT"
├─[>] Main Rendering
│ ├─[X] CLEAR_RENDER_TARGET: "Reset current backbuffer contents"
│ └─[>] DRAW_INDEXED: "Draw simple triangle"
└─[ ] RESOURCE_BARRIER: "Backbuffer barrier to PRESENT"
<Frame 251>
- [ ] Queue type <0>, submission no. 0, command list 1: "VK test command list"
├─[ ] RESOURCE_BARRIER: "Backbuffer barrier to RT"
├─[ ] Main Rendering
│ ├─[ ] CLEAR_RENDER_TARGET: "Reset current backbuffer contents"
│ └─[ ] DRAW_INDEXED: "Draw simple triangle"
└─[ ] RESOURCE_BARRIER: "Backbuffer barrier to PRESENT"
<Frame 252>
- [ ] Queue type <0>, submission no. 0, command list 1: "VK test command list"
├─[ ] RESOURCE_BARRIER: "Backbuffer barrier to RT"
├─[ ] Main Rendering
│ ├─[ ] CLEAR_RENDER_TARGET: "Reset current backbuffer contents"
│ └─[ ] DRAW_INDEXED: "Draw simple triangle"
└─[ ] RESOURCE_BARRIER: "Backbuffer barrier to PRESENT"

功能

该库实现了先前描述的面包屑技术,允许在单个绘图调用或一组绘图调用周围放置“开始-结束”标记。此外,您可以为每个标记赋予一个有意义的名称和标签,这些名称和标签稍后会出现在输出中。

有关直接 API 参考,请参阅 host/ffx_breadcrumbs.h 头文件,其中描述了所有选项和参数。此库默认支持 Direct3D 12 和 Vulkan 后端,但您可以通过在 FfxInterface 中提供带有 fpBreadcrumbs 前缀的回调来开发自己的后端。GPU 崩溃后,您可以简单地调用 ffxBreadcrumbsPrintStatus() 来生成包含标记树和崩溃设备信息的文本缓冲区,以便稍后保存和分析。

请注意,面包屑标记的准确性在很大程度上取决于崩溃的类型和 GPU 上调度的工作的特性。在特定的工作负载下,可能会发生缓存刷新比崩溃命令早或晚于崩溃命令,或者 GPU 会继续执行命令列表中的后续工作,从而延迟崩溃报告,这可能会导致正在进行中的命令的指示器出现偏移。

传递给库的所有字符串名称都可以选择性地标记为“外部拥有”,这意味着它们的内存不会被复制,而是使用原始指针。这允许在存在静态字符串和由其他技术保留底层内存的字符串的情况下节省内存。当执行类似的连续绘图调用,每个名称都遵循“Draw triangle 1”、“Draw triangle 2”等模式时,您可以省略名称字符串中的递增数字,因为库会自动将序数添加到相同的标签,例如“DRAW_INDEXED 1”、“DRAW_INDEXED 2”等。通过此功能,您可以依赖静态字符串名称,从而避免不必要的复制。

用法

要使用 FidelityFX Breadcrumbs,您必须创建一个 FfxBreadcrumbsContext 实例,这将启用设置“开始-结束”标记。使用它们最佳的位置是在您的通用标记工具中,例如,将它们与经典的 **PIX** 标记一起使用。

代码示例

有关如何使用此库功能的快速参考,请参阅下面的示例代码。

/* Preceding initialization of graphic's API and GPU device... */
FfxBreadcrumbsContextDescription breadDesc = {}; // Fill out initial parameters as desired
// Initialize given API backend
const size_t backendSize = ffxGetScratchMemorySizeDX12(1);
uint8_t* backendBuffer = new uint8_t[backendSize];
// Pass in required parameters for given graphic's API connection
ffxGetInterfaceDX12(&breadDesc.backendInterface, ffxGetDeviceDX12(d3d12Device), backendBuffer, backendSize, 1);
FfxBreadcrumbsContext breadCtx;
ffxBreadcrumbsContextCreate(&breadCtx, &breadDesc); // Create main Breadcrumbs context
// After creating pipeline state you can optionally register it inside the context to provide shader details for markers
FfxBreadcrumbsPipelineStateDescription pipelineDesc = {};
pipelineDesc.Pipeline = ffxGetPipelineDX12(d3d12Pipeline1);
pipelineDesc.VertexShader = { "PhongVS", true }; // Static strings don't require copy
/* Fill out other desired parameters */
ffxBreadcrumbsRegisterPipeline(&breadCtx, &pipelineDesc);
pipelineDesc.Pipeline = ffxGetPipelineDX12(d3d12Pipeline2);
pipelineDesc.VertexShader = { shaderName + "VS", false }; // Dynamic strings require copy
/* Fill out other desired parameters */
ffxBreadcrumbsRegisterPipeline(&breadCtx, &pipelineDesc);
// Render loop
while (true)
{
// Start new frame
ffxBreadcrumbsStartFrame(&breadCtx);
// Register command list before using breadcrumbs with it (can be done before or after opening command list)
FfxBreadcrumbsCommandListDescription listDesc = {};
// You can call ffxGetCommandList*() multiple times, no need to cache results
listDesc.commandList = BffxGetCommandListDX12(d3d12CommandList);
listDesc.pipeline = ffxGetPipelineDX12(d3d12Pipeline1); // Can be nullptr and set later on
/* Fill out other desired parameters */
ffxBreadcrumbsRegisterCommandList(&breadCtx, &listDesc);
// Group multiple calls into single marker...
ffxBreadcrumbsBeginMarker(&breadCtx, ffxGetCommandListDX12(d3d12CommandList), /* ... */);
/* Multiple draw calls */
// ...or put them per single draw call
ffxBreadcrumbsBeginMarker(/* ... */);
d3d12CommandList->DrawInstanced(/* ... */);
ffxBreadcrumbsEndMarker(/* ... */);
// You can change what pipeline will be used for next markers...
ffxBreadcrumbsSetPipeline(&breadCtx, ffxGetCommandListDX12(d3d12CommandList),
ffxGetPipelineDX12(d3d12Pipeline2));
ffxBreadcrumbsBeginMarker(/* ... */);
d3d12CommandList->DrawIndexedInstanced(/* ... */);
ffxBreadcrumbsEndMarker(/* ... */);
// ...or disable pipeline at all, it's tracked per single command list so starting new frame will reset this information
ffxBreadcrumbsSetPipeline(&breadCtx, ffxGetCommandListDX12(d3d12CommandList), nullptr);
// End top grouping call
ffxBreadcrumbsEndMarker(&breadCtx, ffxGetCommandListDX12(d3d12CommandList));
// This calls are without pipeline shader info
ffxBreadcrumbsBeginMarker(/* ... */);
d3d12CommandList->ClearRenderTargetView(/* ... */);
d3d12CommandList->DrawIndexedInstanced(/* ... */);
ffxBreadcrumbsEndMarker(/* ... */);
/* Execute command list and finish the frame */
// Retrieve breadcrumbs info when receiving device lost error
// (check for calls that return DXGI_ERROR_DEVICE_REMOVED for D3D12 or VK_ERROR_DEVICE_LOST for Vulkan)
HRESULT hr = d3d12Queue->Present(/* ... */);
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
// Generate text buffer
FfxBreadcrumbsMarkersStatus markerStatus = {};
ffxBreadcrumbsPrintStatus(&breadCtx, &markerStatus);
/* Save pBuffer to a file directly or append it to another crash log (UTF-8 formatted) */
// Free crash log buffer and perform restart or shutdown as desired
FFX_FREE(markerStatus.pBuffer);
break;
}
}
/* Wait for gpu to finish all the work before destroying breadcrumbs context */
// Destroy context and backend buffer during shutdown
ffxBreadcrumbsContextDestroy(&breadCtx);
delete[] backendBuffer;

要求

  • 至少需要 C++11 兼容的编译器。
  • 对于 DirectX 12 后端,需要支持 ID3D12Device3 的 SDK 和运行时。
  • 对于 Vulkan 后端,系统已安装的任何版本的 SDK。
  • 对于测试样本,需要 Windows 环境。
© . This site is unofficial and not affiliated with AMD.