RenderDoc Python 脚本实战:使用 PipeState 管道状态抽象查询任意事件的渲染状态
开发工具调试器图形学GPU【免费下载链接】renderdocRenderDoc is a stand-alone graphics debugging tool.项目地址https://gitcode.com/gh_mirrors/re/renderdoc点击查看免费下载导读本篇文章围绕 RenderDoc Python API 官方示例 Pipeline Statepipe_state.py展开讲解如何通过PipeState这一 API 无关API-agnostic的管道状态抽象在任意事件上查询输出绑定、深度目标、图形管线对象、各阶段着色器以及各类资源绑定。读完本文你将掌握在 RenderDoc 的 Python 脚本窗口中编写自研检查脚本的核心套路把当前事件绑了什么资源这一类问题变成几行可复用的代码。一、什么是PipeStateAPI 无关的管道状态抽象图形 APID3D11、D3D12、OpenGL、Vulkan之间管道状态的呈现方式差异巨大直接为每种 API 各自编写查询代码会导致大量重复且脆弱的工作。RenderDoc 提供的PipeState正是为解决这一问题而生的统一抽象层。从源码看PipeState 结构体被定义在renderdoc/api/replay/pipestate.h其文档注释明确说明An API-agnostic view of the common aspects of the pipeline state. This allows simple access to e.g. find out the bound resources or vertex buffers, or certain pipeline state which is available on all APIs.该对象在 UI 脚本中通过qrenderdoc.CaptureContext.CurPipelineState()获取即官方示例中的pyrenderdoc.CurPipelineState()若使用底层 replay API则对应ReplayController.GetPipelineState()。内部实现上PipeState同时持有四种 API 的具体状态指针D3D11Pipe::State、D3D12Pipe::State、GLPipe::State、VKPipe::State并通过m_PipelineType记录当前打开捕获所属的 API见 pipestate.h。因此它总是能自动适配当前捕获的 API并附带IsCaptureD3D11()、IsCaptureD3D12()、IsCaptureGL()、IsCaptureVK()等判断方法。对于通用查询场景你完全不需要关心底层是哪个 API。需要说明的适用前提PipeState只覆盖各 API 共有的通用状态。如果需要非常 API 特定、精确的细节例如仅存在于某一 API 上的状态则应进一步访问该 API 专属的管线状态结构下文第五节详述。二、脚本骨架从打开捕获到取当前状态官方示例脚本的完整源码位于 pipe_state.py。所有 Python 示例共用的前导代码preamble与捕获打开逻辑详见 examples/index.rst如下# these imports are not strictly necessary, but are convenient import renderdoc import qrenderdoc # this is here to give autocomplete when editing the example # in VS Code where it doesnt know about this global from typing import TYPE_CHECKING if TYPE_CHECKING: pyrenderdoc qrenderdoc.CaptureContext() if not pyrenderdoc.IsCaptureLoaded(): filename pyrenderdoc.Extensions().OpenFileName(Choose a capture, , *.rdc) pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True) pipe pyrenderdoc.CurPipelineState() get_name lambda id: pyrenderdoc.GetResourceName(id)要点说明pyrenderdoc是 RenderDoc 在脚本运行环境中预置的全局变量类型为qrenderdoc.CaptureContextfrom typing import TYPE_CHECKING块仅为外部 IDE 提供自动补全提示脚本先判断是否已有捕获打开没有则弹出文件选择框并调用LoadCapture加载.rdc文件pipe pyrenderdoc.CurPipelineState()取得当前事件当前选中的 action下的管道状态快照。关于当前事件的定位与含义可参考 curevent.rstget_name是一个 lambda 辅助函数把ResourceId映射为可读的资源名称GetResourceName后续所有打印都用它来降低代码冗余度。三、查询输出绑定GetOutputTargets与GetDepthTarget第一个实战场景是打印当前绘制的输出目标渲染目标与深度目标print(-------------------------) print( Outputs ) print(-------------------------) # list all the output targets outs pipe.GetOutputTargets() for i, out in enumerate(outs): id out.resource # ignore any targets that are unbound if id ! renderdoc.ResourceId(): print(fOut {i}: {get_name(id)}) id pipe.GetDepthTarget().resource print(fDepth: {get_name(id)})对应的方法声明在 pipestate.hGetOutputTargets()返回List[Descriptor]即当前绑定到颜色输出的资源列表GetDepthTarget()返回单个Descriptor即深度模板输出资源。示例中通过out.resource取出描述符对应的ResourceId并用renderdoc.ResourceId()的空值判断来跳过未绑定unbound的目标——因为不同 API 下输出目标的数量与绑定方式不同未绑定的槽位可能以空 ID 形式出现。输出条目数量也会因 API 特定细节而异这一点脚本里做了防御性处理。四、查询管线对象与着色器GetGraphicsPipelineObject与GetShader接着可以查询当前绑定的图形管线对象以及顶点/像素着色器print() print(-------------------------) print( Pipeline/Shaders ) print(-------------------------) id pipe.GetGraphicsPipelineObject() print(fPipeline: {get_name(id)}) id pipe.GetShader(renderdoc.ShaderStage.Vertex) print(fVS: {get_name(id)}) id pipe.GetShader(renderdoc.ShaderStage.Pixel) print(fPS: {get_name(id)})源码层面的对应关系pipestate.hGetGraphicsPipelineObject()返回图形管线状态对象PSO的ResourceId另有对应的GetComputePipelineObject()用于计算管线GetShader(ShaderStage stage)返回绑定在指定着色器阶段的着色器对象 IDShaderStage是枚举Vertex、Pixel、Compute、Geometry等。OpenGL 的特别说明原文档明确提示虽然 OpenGL 也有 pipeline管线、program 与 shader 的概念但 OpenGL 的管线不算真正的 PSO因此不会出现在GetGraphicsPipelineObject()的返回中只有 RenderDoc 中 OpenGL 专属的管线状态才会展示这些绑定且大多是不透明的。相比之下着色器无论是否使用 PSO 都始终可以查询。另外这里也是进一步获取着色器反射shader reflection的好时机通过pipe.GetShaderReflection(ShaderStage stage)声明见 pipestate.h可以拿到当前阶段着色器的反射信息再深入到声明的绑定与着色器细节例如结合 shader_refl.py 中的用法。五、着色器绑定辅助GetConstantBlocks与四类绑定分类访问着色器绑定shader binding是各 API 差异最大的领域因此 RenderDoc 的抽象也最复杂并提供了一组最高层的辅助查询方法。5.1 四类绑定分类根据 shader_refl.rst 的说明RenderDoc 将所有资源绑定划分为四大类类别对应反射类型说明常量块 Constant blocksConstantBlock以普通值格式化的只读绑定典型如 constant/uniform buffer采样器 SamplersShaderSampler独立的采样器绑定不含部分 API 的纹理采样器组合对象只读资源 Read-only resourcesShaderResource显式只读绑定的纹理、类型转换 buffer、格式化/结构化 buffer 等读写资源 Read-write resourcesShaderResource可被着色器读写的纹理或 bufferwrite-only 也归入此类围绕这四类分类PipeState在 pipestate.h 中提供了按阶段查询的辅助方法GetConstantBlocks(ShaderStage stage, bool onlyUsed false)GetReadOnlyResources(ShaderStage stage, bool onlyUsed false)GetSamplers(ShaderStage stage, bool onlyUsed false)GetReadWriteResources(ShaderStage stage, bool onlyUsed false)5.2 查询顶点着色器常量块示例查询顶点着色器绑定的常量块并打印绑定到的 bufferprint() print(-------------------------) print( Constant Blocks (VS) ) print(-------------------------) cbs pipe.GetConstantBlocks(renderdoc.ShaderStage.Vertex) for cb in cbs: print( f{str(cb.access.stage)} CB[{cb.access.index}]: {get_name(cb.descriptor.resource)} )这里cb的类型是UsedDescriptor由三部分组成结构定义见 common_pipestate.haccessDescriptorAccess记录哪个着色器反射对象访问了哪个描述符——含阶段stage、类型type、反射索引index、数组元素arrayElement、以及描述符存储位置等descriptorDescriptor被访问描述符的内容普通非采样器描述符samplerSamplerDescriptor采样器描述符的内容普通描述符时为空。示例打印的cb.access.index对应着色器反射中该常量块在ConstantBlocks数组里的索引配合反射信息参见 shader_refl.rst可以用 阶段 索引 反查这个 buffer 绑定在着色器里是如何被使用的。5.3Descriptor中的更多信息原文档指出Descriptor中除resource外还包含更细的描述信息byteOffset常量块绑定起始处的相对字节偏移firstMip纹理访问时可访问的 mip 范围起点format格式转换format-cast后的格式swizzle分量重排component swizzle设置。这些字段使脚本不仅能回答绑了什么还能回答从哪个字节开始、可访问哪些 mip、以什么格式/分量访问。六、直接描述符信息GetAllUsedDescriptors与双轴遍历多数情况下第五节的高层辅助方法已足够定位被访问的资源但若想获得未经过滤的完整信息可以查询所有描述符print() print(-------------------------) print( Descriptors by Stage ) print(-------------------------) # ask for all descriptors but only those that are used descs pipe.GetAllUsedDescriptors(True)对应声明在 pipestate.hGetAllUsedDescriptors(bool onlyUsed false)返回当前事件访问过的全部描述符List[UsedDescriptor]。参数onlyUsedTrue表示只返回确实被使用的描述符。原文档对此有重要说明在某些 API 上还可以查询已绑定但被证明未使用的描述符例如着色器根本没用到的绑定。一般而言这种区分只存在于非 bindless 风格的 API 上——其可能绑定的集合是相对较小且固定的一批槽位。在DescriptorAccess结构common_pipestate.h中对应staticallyUnused标志其语义是该描述符在所有路径上都被证明未使用若为False也不保证GPU 执行期间真的访问过它。接下来示例从两个轴遍历这批描述符先按着色器阶段分组打印该阶段使用的所有类型资源再按描述符类型分组打印所有阶段中该类型的描述符# first well iterate over all possible shader stages, and get all # the descriptors for that stage for stage in renderdoc.ShaderStage: # silently skip stages with no used bindings stage_descs [d for d in descs if d.access.stage stage] if stage_descs []: continue # now iterate over the descriptors and print its type and the resources # in the descriptor print(f** {str(stage)} descriptors:) for d in stage_descs: desc_str f{str(d.access.type)} - if ( d.sampler.object ! renderdoc.ResourceId() and d.descriptor.resource ! renderdoc.ResourceId() ): desc_str ( f{get_name(d.descriptor.resource)} {get_name(d.sampler.object)} ) elif d.sampler.object ! renderdoc.ResourceId(): desc_str f{get_name(d.sampler.object)} else: desc_str f{get_name(d.descriptor.resource)} print(desc_str) # we also print the descriptor store this is stored in print( fin {get_name(d.access.descriptorStore)} at offset {d.access.byteOffset} ) print() print(-------------------------) print( Descriptors by Type ) print(-------------------------) # Iterate in a similar way, but this time grouping by descriptor type for desctype in renderdoc.DescriptorType: type_descs [d for d in descs if d.access.type desctype] if type_descs []: continue print(f** {str(desctype)} descriptors:) for d in type_descs: desc_str f{str(d.access.stage)} - if ( d.sampler.object ! renderdoc.ResourceId() and d.descriptor.resource ! renderdoc.ResourceId() ): desc_str ( f{get_name(d.descriptor.resource)} {get_name(d.sampler.object)} ) elif d.sampler.object ! renderdoc.ResourceId(): desc_str f{get_name(d.sampler.object)} else: desc_str f{get_name(d.descriptor.resource)} print(desc_str) print( fin {get_name(d.access.descriptorStore)} at offset {d.access.byteOffset} )两个循环的关键逻辑第一个循环遍历renderdoc.ShaderStage全部阶段用列表推导筛出属于该阶段的描述符空列表直接跳过第二个循环遍历renderdoc.DescriptorType全部类型按类型分组打印资源时区分三种情况采样器与资源都有效输出resource sampler对应组合的纹理采样器访问、仅有采样器、仅有资源这样能正确覆盖各 API 的组合/分离绑定模式最后打印d.access.descriptorStore描述符存储对象的名称与d.access.byteOffset该描述符在存储中的字节偏移供更深入的分析使用。理解 descriptorStore / byteOffset 需要先理解描述符抽象RenderDoc 以现代 API 结构为蓝本设计了一套描述符抽象——各类资源描述符按各自大小写入称为描述符存储descriptor store的内存对象再由着色器从声明的绑定访问。这在 Vulkan 描述符集descriptor sets、D3D12 描述符堆descriptor heaps上几乎一一对应而对 D3D11、OpenGL 这类固定槽位 APIRenderDoc 会虚拟化一个固定大小的描述符存储来模拟。完整机制详见 descriptors_bindings.rst。七、完整代码将上述片段整合后即为官方示例的完整脚本pipe_state.py。该示例以 Pipeline State 为名内置在Python 脚本窗口的 Examples 部分见 examples/index.rst可直接在 UI 中运行也可在本地以独立脚本方式打开捕获后运行。八、示例输出解读运行脚本后典型的输出如下------------------------- Outputs ------------------------- Out 0: Swapchain Image 127 Depth: 2D Depth Attachment 148 ------------------------- Pipeline/Shaders ------------------------- Pipeline: Graphics Pipeline 112 VS: Shader Module 109 PS: Shader Module 110 ------------------------- Constant Blocks (VS) ------------------------- ShaderStage.Vertex CB[0]: Buffer 100 ------------------------- Descriptors by Stage ------------------------- ** ShaderStage.Vertex descriptors: DescriptorType.ConstantBuffer - Buffer 100 in Descriptor Set 118 at offset 0 ** ShaderStage.Pixel descriptors: DescriptorType.ImageSampler - 2D Image 95 Sampler 98 in Descriptor Set 118 at offset 1 ------------------------- Descriptors by Type ------------------------- ** DescriptorType.ConstantBuffer descriptors: ShaderStage.Vertex - Buffer 100 in Descriptor Set 118 at offset 0 ** DescriptorType.ImageSampler descriptors: ShaderStage.Pixel - 2D Image 95 Sampler 98 in Descriptor Set 118 at offset 1逐段解读Outputs当前绘制把Swapchain Image 127作为颜色输出 0把2D Depth Attachment 148作为深度目标Pipeline/Shaders绑定了图形管线对象Graphics Pipeline 112顶点着色器为Shader Module 109像素着色器为Shader Module 110Constant Blocks (VS)顶点阶段索引 0 的常量块绑定了Buffer 100Descriptors by Stage按阶段看顶点阶段只有一个DescriptorType.ConstantBufferBuffer 100位于Descriptor Set 118偏移 0像素阶段有一个DescriptorType.ImageSampler由2D Image 95 Sampler 98组合而成位于同一描述符集偏移 1Descriptors by Type按类型交叉验证两类描述符与按阶段遍历结果完全一致——常量缓冲区只被顶点阶段使用图像采样器组合只被像素阶段使用。注意输出目标列表的具体数量、深度目标是否存在等都会取决于脚本运行时所在事件的 API 与绑定状态输出可能为空或不同。九、API 特定管线何时需要下沉到具体 API示例脚本本身未展示这部分但原文档明确指出也可以针对每个 API 查询其专属的管线状态结构前提是当前打开的捕获确实使用该 API。何时需要这样做做的事情非常 API 特定需要精确细节需要检查的数据不属于各 API 共享的通用状态只存在于某些 API 上。从源码看PipeState内部正是通过D3D11Pipe::State、D3D12Pipe::State、GLPipe::State、VKPipe::State四套结构承载数据pipestate.h这些结构分别定义在同目录下的d3d11_pipestate.h、d3d12_pipestate.h、gl_pipestate.h、vk_pipestate.h中是查看 API 专属细节的入口。先用IsCaptureD3D11()/IsCaptureD3D12()/IsCaptureGL()/IsCaptureVK()判断捕获 API再安全地访问对应结构即可。十、小结与延伸阅读围绕PipeState本文覆盖了三条递进的使用层次通用资源查询GetOutputTargets/GetDepthTarget/GetGraphicsPipelineObject/GetShader回答绑定了哪些输出与着色器高层绑定辅助GetConstantBlocks/GetReadOnlyResources/GetSamplers/GetReadWriteResources按四类分类便捷查询各阶段的绑定底层描述符遍历GetAllUsedDescriptors返回UsedDescriptor列表内含DescriptorAccess可精确到描述符存储与字节偏移配合着色器反射做更深入的分析。继续深入可阅读以下仓库文档shader_refl.rst着色器反射四类绑定分类的完整定义descriptors_bindings.rst描述符抽象descriptor store、字节偏移的底层机制shader_refl.py结合反射信息分析绑定用途的姊妹示例resource_usage.py从资源使用角度反向查询的示例curevent.rst当前事件current event的定义与脚本定位方式。赞分享开发工具调试器图形学GPU【免费下载链接】renderdocRenderDoc is a stand-alone graphics debugging tool.项目地址https://gitcode.com/gh_mirrors/re/renderdoc点击查看免费下载相关推荐Relay 渲染指南用 Suspense 为查询渲染加载状态Loading StatesRelay 渲染指南用 Suspense 为查询渲染加载状态Loading States Relayreact relay中的 usePreloade前端开发工具Slash数据分析深度探索如何跟踪链接访问与用户行为Slash数据分析深度探索如何跟踪链接访问与用户行为 Slash是一款开源自托管的书签和链接分享平台帮助用户轻松保存和分享链接。通过Slash的数据分析功能Relay 加载状态实战用 React Suspense 构建查询渲染期间的 Loading UIRelay 加载状态实战用 React Suspense 构建查询渲染期间的 Loading UI 导读 在 Relay 应用中 usePreloadedQ前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考