分离 Shader 调试信息并在 RenderDoc 里用
分离 Shader 调试信息并在 RenderDoc 里用
在 Windows 上用 fxc 编译 shader,默认会把调试信息嵌进 shader 二进制里,包体会因此变大。可以用 fxc /Qstrip_debug 剥掉调试信息,但这样一来 RenderDoc 里就没法调试了。这篇讲怎么两全:把调试信息单独导出成 pdb,剥离后的二进制照常发布,同时让 RenderDoc 仍能加载到调试信息。
原理
- 在 RenderDoc 里设置 shader 调试信息的搜索路径;
- 给剥离后的 shader 二进制写一段特殊的 private data,告诉 RenderDoc 对应的 pdb 在哪。
两者缺一不可——RenderDoc 靠 private data 里的路径去搜索路径下找 pdb。
设置 RenderDoc 的 Shader Debug 路径
先建一个放 pdb 的文件夹,假设是 D:\ShaderPdb。然后在 RenderDoc:
Tools → Settings → Core → Shader debug search paths
把这个文件夹加进去。RenderDoc 侧就绪。
给 shader 写入 private data
关键是往 shader 里塞一段信息,指向 pdb 的位置。分两种编译场景。
运行时通过 API 编译
正常流程是 D3DCompile 编译出 shader,然后剥离调试信息:
// 剥离调试信息
D3DStripShader(shaderBuffer->GetBufferPointer(),
shaderBuffer->GetBufferSize(),
D3DCOMPILER_STRIP_DEBUG_INFO,
&strippedBlob);
shaderBuffer->Release();
// 从这里开始改用 strippedBlob
用 d3ddevice 创建 shader:
d3ddevice->CreatePixelShader(strippedBlob->GetBufferPointer(),
strippedBlob->GetBufferSize(),
NULL, &psShader);
然后设置 private data(把 pdb 路径写进去):
psShader->SetPrivateData(RENDERDOC_ShaderDebugMagicValue,
(UINT)pathName.length(),
pathName.c_str());
之后从游戏里抓帧,RenderDoc 就能看到 shader 调试信息了。
构建期编译(没有 d3ddevice)
如果是在构建期(build time)编译 shader,此时没有 d3ddevice 来创建 shader 对象,就得直接给 blob 绑 private data:
// 工具期
D3DSetBlobPart(strippedBlob->GetBufferPointer(),
strippedBlob->GetBufferSize(),
D3D_BLOB_PRIVATE_DATA, 0,
&path, pathSize,
&annotatedBlob);
// 从这里开始改用 annotatedBlob 而不是 strippedBlob
通过 fxc.exe 命令行编译
如果直接跑 fxc,用 /setprivate 实现同样的功能。/setprivate 需要一个文件作参数,文件内容是 GUID + PdbPath。整个流程分两步:
// 第一步:带 /Zi 编译,导出 pdb
pSI->Arguments =
"/Zi" // 必须有 /Zi 才能开调试模式
" /Fo D:\\ShaderPdb\\123.pdb" // pdb 路径
" /T vs_5_0"
" \"" + sourceFilePath + "\"";
Run(pSI);
// 第二步:加载 pdb,剥离调试信息,写入 private data
pSI->Arguments =
"/dumpbin" // 加载文件而不是编译
" /Qstrip_debug" // 剥离调试信息
" /Fo \"" + targetPath + "\"" // 最终二进制
" /setprivate temp.priv" // 刚创建的 priv 文件
" D:\\ShaderPdb\\123.pdb";
Run(pSI);
temp.priv 就是前面说的 GUID + PdbPath 文件。这样发布的是剥离后的小体积二进制,而调试时 RenderDoc 顺着 private data 找到 pdb,照样能看源码级调试信息。