集成 RenderDoc¶
实现像 Unity 一样,直接从自己程序里调起 RenderDoc 截帧的功能。参考文档:In-application API — RenderDoc documentation。
引入头文件¶
在 RenderDoc 的安装目录里,有 renderdoc_app.h
,复制进项目即可。然后,写一个类简单封装一下。
class RenderDoc final
{
public:
static bool IsLoaded();
static void Load();
static void CaptureSingleFrame();
static uint32_t GetNumCaptures();
static std::tuple<int32_t, int32_t, int32_t> GetVersion();
static std::string GetLibraryPath();
};
cpp 文件里
static RENDERDOC_API_1_5_0* g_Api = nullptr;
加载¶
在创建图形设备前,枚举常用的 RenderDoc 安装位置,动态加载安装目录里的 renderdoc.dll
。
std::string RenderDoc::GetLibraryPath() const
{
// 常用安装位置,可以多枚举几个
return "C:\\Program Files\\RenderDoc\\renderdoc.dll";
}
如果 LoadLibrary
前,renderdoc.dll
已经被加载,说明用户是用 RenderDoc 启动 App 的,就不需要再手动 load 了。
bool RenderDoc::IsLoaded()
{
return g_Api != nullptr;
}
void RenderDoc::Load()
{
if (IsLoaded())
{
return;
}
// 如果使用 RenderDoc 启动 App 的话,不重复加载 dll
HMODULE hModule = GetModuleHandleA("renderdoc.dll");
if (!hModule)
{
hModule = LoadLibraryA(GetLibraryPath().c_str());
}
if (!hModule)
{
DEBUG_LOG_ERROR("Failed to load RenderDoc library");
return;
}
auto RENDERDOC_GetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(hModule, "RENDERDOC_GetAPI"));
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_5_0, reinterpret_cast<void**>(&g_Api));
if (ret != 1)
{
g_Api = nullptr;
DEBUG_LOG_ERROR("Failed to get RenderDoc API. Return Code: %d", ret);
return;
}
g_Api->MaskOverlayBits(eRENDERDOC_Overlay_None, eRENDERDOC_Overlay_None); // 不显示 overlay
g_Api->SetCaptureKeys(nullptr, 0);
}
在最后,调用 MaskOverlayBits
把 RenderDoc 左上角黑色的 Overlay 信息隐藏掉;调用 SetCaptureKeys
把默认的快捷键取消掉。
D3D12 调试层¶
在做 D3D12 开发时,我们通常会开启 D3D12 的调试层,但 RenderDoc 默认情况下禁用了 API Validation 和 Debug Output,使得 D3D12 的调试层失去作用。
在加载 RenderDoc 后,调用下面的方法可以解决。1
g_Api->SetCaptureOptionU32(eRENDERDOC_Option_APIValidation, 1);
g_Api->SetCaptureOptionU32(eRENDERDOC_Option_DebugOutputMute, 0);
另外,RenderDoc 会使 ID3D12InfoQueue1
失去作用,因为它只提供了一个 dummy 的实现。2
// give every impression of working but do nothing.
// Just allow the user to call functions so that they don't
// have to check for E_NOINTERFACE when they expect an infoqueue to be there
struct DummyID3D12InfoQueue : public ID3D12InfoQueue1
{
// ...
}
RenderDoc 会使 D3D12 的调试层变得不完整,它本身又有一些额外的开销,所以不建议每次启动应用时都加载 RenderDoc。
可以像 Unity 一样,提供一个加载按钮,但是加载 RenderDoc 后需要重新创建图形设备,整个过程是比较麻烦的。也可以提供一个命令行参数 -load-renderdoc
,仅在有该参数的情况下加载 RenderDoc,修改 VisualStudio 调试器的启动参数就行。
截帧¶
void RenderDoc::CaptureSingleFrame()
{
if (!IsLoaded())
{
return;
}
g_Api->TriggerCapture();
if (g_Api->IsTargetControlConnected())
{
g_Api->ShowReplayUI();
}
else
{
g_Api->LaunchReplayUI(1, nullptr);
}
}
调用这个方法后,会立即截一帧,然后打开 RenderDoc 窗口,就像 Unity 一样。
附加信息¶
获取截帧和版号信息。
uint32_t RenderDoc::GetNumCaptures()
{
if (!IsLoaded())
{
return 0;
}
return g_Api->GetNumCaptures();
}
std::tuple<int32_t, int32_t, int32_t> RenderDoc::GetVersion()
{
if (!IsLoaded())
{
return std::make_tuple(0, 0, 0);
}
int verMajor = 0;
int verMinor = 0;
int verPatch = 0;
g_Api->GetAPIVersion(&verMajor, &verMinor, &verPatch);
return std::make_tuple(static_cast<int32_t>(verMajor), static_cast<int32_t>(verMinor), static_cast<int32_t>(verPatch));
}
快捷键和 UI¶
这部分用 ImGui 实现,使用快捷键 Alt+C
就能截帧。
if (ImGui::BeginMainMenuBar())
{
if (ImGui::Shortcut(ImGuiMod_Alt | ImGuiKey_C, ImGuiInputFlags_RouteAlways))
{
RenderDoc::CaptureSingleFrame();
}
if (ImGui::BeginMenu("RenderDoc"))
{
if (ImGui::MenuItem("Capture", "Alt+C", nullptr, RenderDoc::IsLoaded()))
{
RenderDoc::CaptureSingleFrame();
}
ImGui::SeparatorText("Information");
if (ImGui::BeginMenu("Library"))
{
ImGui::TextUnformatted(RenderDoc::GetLibraryPath().c_str());
ImGui::EndMenu();
}
if (ImGui::BeginMenu("API Version"))
{
auto [major, minor, patch] = RenderDoc::GetVersion();
ImGui::Text("%d.%d.%d", major, minor, patch);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Num Captures"))
{
ImGui::Text("%d", RenderDoc::GetNumCaptures());
ImGui::EndMenu();
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}