- 开发工具
- 调试器
- 图形学
- GPU
【免费下载链接】renderdoc
RenderDoc is a stand-alone graphics debugging tool.
本篇技术指南围绕 RenderDoc 的捕获文件(.rdc)访问机制展开,系统讲解renderdocPython 模块中CaptureAccess与CaptureFile两套接口的定位与用法、打开文件与启动回放的完整流程、文件格式与转换能力,以及通过 Section 读写和 ASCII Section 手工拼接两种方式为捕获附加自定义数据的具体方法。读完本文,你将掌握在不依赖 RenderDoc UI 的情况下,用脚本独立完成捕获文件的打开、元数据查询、格式转换、自定义数据注入与回放启动的完整实战方案。
.rdc 捕获文件是什么
RenderDoc 在应用运行期间触发捕获后,会将一帧(或多帧)的完整 GPU 执行数据保存为.rdc文件。这个文件包含了回放该捕获帧所需的全部数据,同时也包含元数据与附加信息:
- 捕获使用的图形 API(即源码文档中称为 driver 的部分)及其版本信息;
- 捕获产生平台的机器标识(machine ident);
- 帧捕获正文(frame capture)数据;
- 可选的无损缩略图(extended thumbnail);
- 可选的调用栈(callstack)及其模块信息;
- 用户或工具附加的自定义 Section。
从源码层面看,.rdc文件是一个结构简单但组织清晰的容器:RDCFile实现位于 rdcfile.cpp,文件以'R','D','O','C'四字符魔数(MAGIC_HEADER,见 rdcfile.cpp#L142)开头,随后是文件头(magic、版本号、版本字符串)、可选的二进制缩略图、捕获元数据(CaptureMetaData,包含machineIdent与driverID)、时间基准(CaptureTimeBase,包含timeBase与timeFreq),最后是一系列紧挨排列的 Section(详见 rdcfile.cpp#L80-L138 中完整格式注释)。
正是由于容器结构简单,.rdc文件可以由脚本管理:既能通过官方 Python API 附加自定义信息,也能用最朴素的方式——把文本格式的 Section 直接拼接到文件末尾(即下文"ASCII Sections"一节)。
这套文件访问能力也正是 RenderDoc 在脱离 UI 情况下启动回放与分析的基础:当你不使用 RenderDoc 图形界面,而是通过 python_module 中描述的 Replay API 完全独立地驱动回放时,首先接触的就是这里讲的捕获文件接口。
访问捕获文件的两大接口:CaptureAccess 与 CaptureFile
捕获文件由两个主要接口管理(见 renderdoc_replay.h#L1243-L1247 的接口声明):
| 接口 | 定位 | 适用场景 |
|---|---|---|
renderdoc.CaptureAccess | 功能受限的子集 | 可通过网络连接使用,不要求本地磁盘上有该文件 |
renderdoc.CaptureFile | 功能更完整 | 必须在本地可访问的文件上初始化,继承自CaptureAccess并追加本地文件能力 |
具体来说,CaptureAccess支持 Section 的枚举、查找与读写、可用 GPU 列表查询等基础能力(GetSectionCount、FindSectionByName、FindSectionByType、GetSectionProperties、GetSectionContents、WriteSection、GetAvailableGPUs);而CaptureFile(对应 C++ 侧ICaptureFile,见 renderdoc_replay.h#L1583-L1795)在此基础上增加了:
OpenFile/OpenBuffer:从磁盘文件或内存缓冲区初始化;Convert:格式转换与导出;GetCaptureFileFormats:查询支持的格式;LocalReplaySupport:本地回放支持程度;RecordedMachineIdent/TimestampBase/TimestampFrequency:机器与时间信息;GetThumbnail:读取缩略图;GetStructuredData/SetStructuredData:结构化数据读取与注入;OpenCapture:本地启动回放。
注:本文以本地
CaptureFile为主线展开,并在需要时标注哪些功能可以通过远程CaptureAccess使用。RenderDoc 的网络回放功能详见 remote_replay。
从零创建一个 CaptureFile
创建CaptureFile的入口是renderdoc.OpenCaptureFile()(对应 C 接口RENDERDOC_OpenCaptureFile,见 renderdoc_replay.h#L1959)。这个句柄归 Python 所有,使用完毕后必须调用CaptureFile.Shutdown()显式销毁:
import renderdoc as rd # 创建句柄(由 python 持有所有权) cap = rd.OpenCaptureFile() # ... 打开文件、读取数据、回放 ... # 使用完毕后显式关闭句柄 cap.Shutdown()Shutdown()会关闭文件句柄并释放其持有的资源。如果跳过这一步,句柄会一直持有对文件(或内存缓冲)的引用,甚至可能因未释放文件锁而导致后续操作失败。
在 UI 脚本中获取当前捕获
如果你运行在 RenderDoc UI 内的 Python 脚本中,可以直接访问 UI 当前加载的捕获:
qrenderdoc.ReplayManager.GetCaptureAccess():获取当前捕获的CaptureAccess(无论本地还是远程打开都可用);qrenderdoc.ReplayManager.GetCaptureFile():获取当前捕获的CaptureFile。注意:如果捕获是在远程打开的,此方法会返回None,因为本地并没有该文件。
import qrenderdoc as qrd replay = qrd.ReplayManager.Get() # 任何情况下都可用 access = replay.GetCaptureAccess() # 仅当捕获文件在本地打开时返回 CaptureFile,远程打开时为 None cap = replay.GetCaptureFile()此外,这两个接口必须仅在 replay thread 上访问(参见 threading 中关于pythreading的说明)。Replay API 的线程模型要求所有回放相关调用在专门的回放线程中执行,UI 脚本应通过qrenderdoc.ReplayManager.BlockInvoke等机制将访问调度到该线程。
打开捕获文件:OpenFile 与进度回调
创建CaptureFile后,调用OpenFile(filename, filetype, progress)打开文件:
import renderdoc as rd cap = rd.OpenCaptureFile() def progress_cb(p): print("Opening... %d%%" % int(p * 100)) result = cap.OpenFile("frame123.rdc", "", progress_cb) if result != rd.ResultCode.Succeeded: print("Failed to open: %s" % result) else: print("Opened successfully")OpenFile的关键语义(对应 renderdoc_replay.h#L1591-L1610):
filename:要打开的.rdc文件路径;filetype:输入格式。绝大多数情况下传""即可——rdc是始终保证受支持的格式,当filetype为空或无法识别时都会按rdc处理;progress:可选进度回调,打开过程中(尤其发生格式导入时)会被间歇调用。回调签名须匹配rd.ProgressCallback(接收一个 0~1 的float)。
另外还有OpenBuffer(buffer, filetype, progress)变体,允许从内存缓冲区初始化句柄,适用于"不想解析整个文件"或"数据已在内存中"的场景(见 renderdoc_replay.h#L1612-L1628)。
打开文件后的磁盘锁
OpenFile成功后,RenderDoc 会对磁盘上的文件持有独占锁,这使得用户无法在文件打开期间复制它。如果需要复制,应使用CopyFileTo(newpath):它会把磁盘文件复制到新位置,新文件加锁、旧文件解锁(以便必要时删除),而原捕获句柄不受影响(见 renderdoc_replay.h#L1630-L1642)。
打开捕获进行回放:OpenCapture 与生命周期约束
拿到CaptureFile后,可以用OpenCapture(opts, progress)启动回放,成功时返回ReplayController(对应 renderdoc_replay.h#L1737-L1755):
import renderdoc as rd cap = rd.OpenCaptureFile() if cap.OpenFile("frame123.rdc", "", None) != rd.ResultCode.Succeeded: raise RuntimeError("open failed") # 使用默认回放选项启动回放,返回 (ResultDetails, ReplayController) status, controller = cap.OpenCapture(rd.ReplayOptions(), None) if status == rd.ResultCode.Succeeded: print("Replay ready, driver: %s" % controller.GetDriverName()) # ---- 在这里进行帧回放、状态查询、着色器调试等分析 ---- # 先关闭控制器,再关闭捕获文件 controller.Shutdown() else: print("Replay failed: %s" % status) cap.Shutdown()生命周期上有一个必须遵守的顺序:CaptureFile在ReplayController使用期间必须保持打开状态,因此你应该先完成分析并关闭控制器,再关闭捕获文件。ReplayOptions用于控制回放方式,例如强制在特定 GPU 上回放(结合CaptureAccess.GetAvailableGPUs()获取的 GPU 列表)等。
注意:OpenCapture仅支持以原生rdc格式打开的句柄,其他格式打开的文件会直接失败(见 renderdoc_replay.h#L1737-L1738)。远程场景下则由IRemoteServer.OpenCapture(proxyid, filename, opts, progress)完成"远程打开、本地代理渲染",且必须用CloseCapture而不是Shutdown来正确清理本地代理(见 renderdoc_replay.h#L1542-L1574)。
捕获文件格式:rdc 与其他格式的转换
绝大多数情况下OpenFile打开的都是标准.rdc文件,此时filetype应传"rdc"或空字符串。RenderDoc 也支持其他格式,但导入支持非常有限——导入要求格式包含回放所需的全部信息;相对而言,导出(Convert)更有实用价值,因为导出可以只包含数据的受限子集。
import renderdoc as rd cap = rd.OpenCaptureFile() cap.OpenFile("frame123.rdc", "", None) # 查询所有支持的格式及其能力 for fmt in cap.GetCaptureFileFormats(): print("ext=%s name=%s desc=%s requiresBuffers=%s open=%s convert=%s" % ( fmt.extension, fmt.name, fmt.description, fmt.requiresBuffers, fmt.openSupported, fmt.convertSupported)) # 将当前文件导出为指定格式 res = cap.Convert("frame123_out.xxx", "xxx", None, None) cap.Shutdown()GetCaptureFileFormats()返回的CaptureFileFormat(定义见 control_types.h#L1227)字段含义如下:
| 字段 | 含义 |
|---|---|
extension | 格式扩展名,如rdc |
name | 格式名称 |
description | 格式描述 |
requiresBuffers | 转换该格式是否需要缓冲区数据(与结构化数据导出相关) |
openSupported | 是否支持从该格式导入(打开) |
convertSupported | 是否支持导出到该格式 |
Convert(filename, filetype, file, progress)还接受一个可选的SDFile参数:当目标格式不需要缓冲区(requiresBuffers == False)且你已经有一个携带结构化数据的ReplayController时,可以传入已有的SDFile避免重新加载文件;传None则内部自行获取结构化数据。
捕获数据:轻量打开与元数据查询
打开文件是一个非常轻量的操作:它只解码容器、加载捕获元数据,不会发起任何图形 API 调用、不会开始回放,也不会把大量数据载入内存。因此适合在脚本中快速对一批捕获做"体检"。
打开后可以查询的元数据包括:
import renderdoc as rd cap = rd.OpenCaptureFile() cap.OpenFile("frame123.rdc", "", None) # 使用的图形 API(driver),如 "D3D11"、"OpenGL"、"Vulkan" print("Driver:", cap.DriverName()) # 本地是否支持回放(返回 rd.ReplaySupport) print("Local replay support:", cap.LocalReplaySupport()) # 记录该捕获的机器标识:平台(x86 / Android 等)与位数(32/64 位) print("Machine ident:", cap.RecordedMachineIdent()) # 缩略图(支持 JPG/PNG/TGA/BMP,maxsize 限制最大宽高) thumb = cap.GetThumbnail(rd.FileType.PNG, 512) print("Thumbnail %dx%d, %d bytes" % (thumb.width, thumb.height, len(thumb.data))) # 时间基准:所有时间戳相对的基础值与频率 print("Timestamp base:", cap.TimestampBase()) print("Timestamp freq:", cap.TimestampFrequency()) cap.Shutdown()DriverName():返回创建该捕获的驱动名称字符串(见 renderdoc_replay.h#L1347-L1352);LocalReplaySupport():查询本地对该捕获的回放支持程度(见 renderdoc_replay.h#L1674-L1682)。如果文件是以非rdc格式打开的,该查询恒返回"不支持回放";RecordedMachineIdent():机器标识字符串,包含平台与位数信息(如 x86 或 Android、32 位或 64 位)。在显示"该捕获不兼容"之类的用户提示时非常有用(见 renderdoc_replay.h#L1684-L1689);GetThumbnail(type, maxsize):返回嵌入缩略图,type仅支持FileType.JPG / PNG / TGA / BMP,maxsize为最大宽高,超出则缩放(见 renderdoc_replay.h#L1779-L1790);TimestampBase()/TimestampFrequency():时间戳基准值与换算频率,时间戳除以频率即可换算为微秒(见 renderdoc_replay.h#L1691-L1705)。
请求结构化数据:SDFile
除了元数据,还可以请求捕获的结构化数据(SDFile)。这与回放是完全不同的两个操作:请求结构化数据时,RenderDoc 会解码捕获内的序列化数据,但不会发起任何图形 API 调用。详细机制见 structured_data。
import renderdoc as rd cap = rd.OpenCaptureFile() cap.OpenFile("frame123.rdc", "", None) sd = cap.GetStructuredData() # 返回 rd.SDFile print("Structured data chunk count:", len(sd.chunks)) cap.Shutdown()关键特性:
- 可跨平台:只要该构建的 RenderDoc 支持目标 API,就能加载结构化数据。例如 Linux 上因完全没有 D3D 支持而无法加载 D3D 捕获的结构化数据,但 Windows 机器可以加载 Android Vulkan 捕获的结构化数据,即使它无法回放该捕获;
- 重量级操作:请求结构化数据需要读取并解码整个捕获,比单纯打开文件重得多;
- 返回的
SDFile包含缓冲区(buffers),因此与任何requiresBuffers为真(CaptureFileFormat.requiresBuffers)的导出格式兼容,可直接用于Convert; SDFile的生命周期与捕获句柄绑定,句柄销毁后不可再使用(见 renderdoc_replay.h#L1757-L1765)。
反向地,SetStructuredData(sd)允许用生成的SDFile填充捕获(数据会被内部复制),配合SetMetadata(...)可以纯内存地构造一个捕获文件,再通过Convert保存到磁盘(见 renderdoc_replay.h#L1707-L1777)。
捕获 Sections:容器中的信息单元
rdc容器文件由一个小文件头加任意数量的 Section组成。默认情况下至少包含帧捕获本身对应的 Section,通常还包含一个扩展无损缩略图;如果捕获了调用栈,还会有一个平台相关的 Section 记录已加载模块信息,供后续调用栈解析使用。
SectionType:官方预定义 Section
已知的官方 Section 由SectionType枚举定义(见 replay_enums.h#L120-L137),每个枚举值同时对应一个字符串路径(如帧捕获为renderdoc/internal/framecapture):
| 枚举值 | 说明 |
|---|---|
Unknown | 未知/自定义类型,通常为 0 |
FrameCapture | 帧捕获正文,路径renderdoc/internal/framecapture |
ResolveDatabase | 调用栈解析数据库 |
Bookmarks | 书签 |
Notes | UI 笔记,路径renderdoc/ui/notes |
ResourceRenames | 资源重命名记录 |
AMDRGPProfile | AMD RGP 性能分析数据 |
ExtendedThumbnail | 扩展(无损)缩略图 |
EmbeddedLogfile | 内嵌日志文件 |
EditedShaders | 编辑过的着色器 |
D3D12Core/D3D12SDKLayers | D3D12 相关模块信息 |
EmbeddedExternalFiles | 内嵌的外部依赖文件(如着色器调试文件) |
Section 的枚举与访问既可以通过CaptureAccess也可以通过CaptureFile完成;两个接口还都支持新增或覆写Section。
读取与写入自定义 Section
自定义数据通过以下 API 读写:
import renderdoc as rd cap = rd.OpenCaptureFile() cap.OpenFile("frame123.rdc", "", None) # 枚举所有 section for i in range(cap.GetSectionCount()): props = cap.GetSectionProperties(i) print("Section %d: name=%s type=%s version=%d flags=%s" % ( i, props.name, props.type, props.version, props.flags)) data = cap.GetSectionContents(i) print(" contents: %d bytes" % len(data)) # 按名字查找(返回索引,找不到为 -1) idx = cap.FindSectionByName("mytool/customdata") # 按类型查找(返回索引,找不到为 -1) idx2 = cap.FindSectionByType(rd.SectionType.Notes) # 写入/覆写一个自定义 section props = rd.SectionProperties() props.name = "mytool/customdata" props.type = rd.SectionType.Unknown props.version = 1 res = cap.WriteSection(props, b"my custom payload") if res != rd.ResultCode.Succeeded: print("WriteSection failed: %s" % res) cap.Shutdown()接口细节(见 renderdoc_replay.h#L1257-L1309):
GetSectionCount():Section 总数;FindSectionByName(name)/FindSectionByType(type):按名字或类型定位索引,找不到返回-1。索引不应被缓存,因为写入 Section 可能重排索引顺序;GetSectionProperties(index):返回SectionProperties,描述该 Section;GetSectionContents(index):返回该 Section 的原始字节内容(bytes);WriteSection(props, contents):写入新 Section。若已存在相同类型或名字的 Section,会被覆写(同一捕获中不允许两个 Section 共享相同类型或名字,见 rdcfile.cpp#L110-L111 的格式约束)。
SectionProperties结构(见 data_types.h#L199-L238)包含:
| 字段 | 含义 |
|---|---|
name | Section 名字(字符串) |
type | SectionType,未知/自定义为Unknown |
flags | SectionFlags,描述存储方式(如是否压缩) |
version | Section 版本号,含义由类型自行定义 |
uncompressedSize | 解压后的数据字节数 |
compressedSize | 磁盘上压缩后的字节数 |
命名规范(重要):自定义 Section 的名字应避免使用
renderdoc/前缀。Section 名可以是任意字符串,请使用自己的命名空间(如mytool/...、company/project/...),以免与官方内部 Section 冲突。
ASCII Sections:用纯文本手工追加数据
为了给"原始脚本"提供更便捷的访问途径,RenderDoc 支持把文本文件直接拼接到.rdc文件末尾来添加 Section——这样即便不调用 Python API,也能以非常有限的方式向捕获注入数据。
⚠️高级特性警告:这是一个advanced功能,操作时必须非常小心。任何格式错误都可能使捕获文件完全无法打开!除非确实需要这么做,否则请优先考虑更安全的方案(即使用
WriteSectionAPI)。
ASCII Section 的格式
一个 ASCII Section 的头部格式如下(示例中带注释,但真实格式必须不含多余空白与注释):
A # 字面字符 'A',表示这是一个 ASCII Section 119 # 内容长度(字节数,十进制数字) 4 # SectionType 的数值(自定义 section 通常为 0) 1 # 本 section 的版本号,用于向后兼容 renderdoc/ui/notes # section 的名字 # 名字之后必须有一个换行符对照 rdcfile.cpp#L92-L113 的容器格式定义,每个 ASCII Section 的头部逐行含义为:
- 一个字面字符
A(ASCII 0x41),标识该 Section 为 ASCII 格式,便于脚本/手工直接拼接; - 一个十进制数字字符串,表示紧跟其后的 section 数据长度(字节数);
- 一个十进制数字字符串,表示该 Section 的
SectionType数值——自定义 Section 通常是0(Unknown); - 一个十进制数字字符串,表示 section 版本号;
- Section 名字(UTF-8 字符串),后面必须紧跟一个换行符。
头部可以用文本编辑器或任何脚本手工构造。如上文所说,第二行的长度(字节数)应该由脚本自动计算生成,而不是手工维护。
头部之后就是 Section 内容,内容可以是任意数据。官方 UI 的 notes Section 是 JSON 格式的,其中"comments"键对应要显示的字符串:
{ "comments": "These are some notes! there isn't really much to put here, except to demonstrate an ASCII section." }拼接步骤与验证
把上面的头部去掉注释和空白,再拼上 JSON 内容,整体追加到一个已有.rdc文件的末尾:
# 1. 构造 ASCII section 文本文件(头部 + 内容,无注释无多余空白) # 2. 追加到 .rdc 末尾 cat ascii_section.txt >> frame123.rdc之后用 RenderDoc UI 打开该捕获,即可在 UI 的笔记视图中看到 JSON 中"comments"字段提供的文字内容。这种做法的典型用途就是为捕获批量添加可被 UI 展示的备注信息。
从实现上看,解析器对 ASCII Section 的校验相当严格(见 rdcfile.cpp#L454-L509):
- 换行序列必须是
\n(允许\r\n,单独\r视为非法,返回FileCorrupted); - 长度、类型、版本逐行按十进制解析,非法字符会污染解析结果;
- 解析失败会报
ResultCode::FileCorrupted,文件将被判定为损坏而无法打开——这正是文档反复警告格式错误后果严重的原因。
二进制 Section 与 ASCII Section 的结构差异
为便于理解容器设计,这里对比两类 Section 的磁盘布局(完整定义见 rdcfile.cpp#L88-L133):
- ASCII Section:
'A'标志 + 换行分隔的四行十进制字段(长度/类型/版本/名字)+ 换行 + 原始数据; - 二进制 Section:
'\0'标志 + 3 字节保留位 +uint32类型 +uint64压缩长度 +uint64解压长度 +uint64版本 +uint32flags +uint32名字长度 + UTF-8 名字 + 数据。
二进制 Section 支持压缩(SectionFlags中记录),数据长度在磁盘上和解压后可能不同;ASCII Section 则不支持压缩、布局固定为纯文本,这也是它能被手工拼接的原因。
完整示例:脚本化"打开 → 查询 → 加注 → 回放"流水线
结合以上内容,一个典型的脚本化捕获处理流水线如下:
import renderdoc as rd def handle_capture(path): cap = rd.OpenCaptureFile() try: # 1) 轻量打开,仅解析容器与元数据 if cap.OpenFile(path, "", None) != rd.ResultCode.Succeeded: return None # 2) 元数据查询 print("Driver:", cap.DriverName()) print("Replay support locally:", cap.LocalReplaySupport()) print("Recorded on:", cap.RecordedMachineIdent()) # 3) 写入自定义追踪信息(覆写同名 section) props = rd.SectionProperties() props.name = "mytool/processed" props.type = rd.SectionType.Unknown props.version = 1 cap.WriteSection(props, b"processed by my pipeline") # 4) 启动回放进行分析(成功后 controller 独立于 cap 存在) status, controller = cap.OpenCapture(rd.ReplayOptions(), None) if status != rd.ResultCode.Succeeded: print("Replay failed:", status) return None # 在这里使用 controller 分析帧...(略) controller.Shutdown() # 先关控制器 return controller finally: cap.Shutdown() # 后关捕获文件句柄 handle_capture("frame123.rdc")小结
.rdc文件是 RenderDoc 捕获的容器格式,包含帧捕获正文、缩略图、调用栈信息及任意自定义 Section,其底层布局定义于 rdcfile.cpp;CaptureAccess提供跨网络的受限能力子集,CaptureFile则在本地文件上提供完整能力(打开、转换、结构化数据、回放),两者都必须只在 replay thread 上访问;OpenFile是轻量操作,只读元数据不发图形 API 调用;OpenCapture才真正启动回放,且需先关ReplayController再关CaptureFile;- 自定义数据可以通过
WriteSection/GetSectionContents以编程方式读写,也可通过将纯文本 ASCII Section 追加到文件末尾实现"零 API"注入,但后者必须严格遵循格式,否则会损坏捕获文件; - 更深入的主题可继续阅读 python_module(独立驱动 Replay API)、remote_replay(网络回放)、structured_data(结构化数据)与 threading(线程模型)。
- 开发工具
- 调试器
- 图形学
- GPU
【免费下载链接】renderdoc
RenderDoc is a stand-alone graphics debugging tool.
相关推荐
RenderDoc 捕获注释(Capture Comments)完全指南:为 .rdc 捕获文件添加持久化文本备注
RenderDoc 捕获注释(Capture Comments)完全指南:为 .rdc 捕获文件添加持久化文本备注 本指南聚焦 RenderDoc 图形调试工具
开发工具调试器图形学GPU如何将普通小爱音箱升级为AI智能助手:MiGPT完整指南
如何将普通小爱音箱升级为AI智能助手:MiGPT完整指南 还在为小爱音箱的"人工智障"表现而烦恼吗?你是否渴望让家里的智能音箱真正理解你的需求,成为能够深度对话
人工智能AI 应用语音智能家居交互助手ContextMenu未来路线图:iOS 17+适配与新功能展望
ContextMenu未来路线图:iOS 17+适配与新功能展望 ContextMenu是一款受Things 3启发的iOS上下文菜单UI组件,当前版本为0.5
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考