ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RenderDoc Python API 实战:着色器反射(Shader Reflection)解析与调试信息提取

RenderDoc Python API 实战:着色器反射(Shader Reflection)解析与调试信息提取 RenderDoc Python API 实战着色器反射Shader Reflection解析与调试信息提取【免费下载链接】renderdocRenderDoc is a stand-alone graphics debugging tool.项目地址: https://gitcode.com/gh_mirrors/re/renderdocRenderDoc 通过ShaderReflection向脚本开放着色器反射数据展示着色器期望绑定的各类接口及其格式与内容。本文以官方 Python 脚本示例 shader_refl.py 为骨架完整演示如何从当前管线状态获取顶点/像素着色器的反射信息解析输入输出签名、常量块、只读资源与采样器绑定读取调试信息并生成反汇编帮助你在 qrenderdoc 的 Python 脚本窗口中写出 API 无关、可复用的着色器分析脚本。获取着色器反射对象着色器反射数据有两种获取入口从当前管线状态获取调用pyrenderdoc.CurPipelineState().GetShaderReflection(ShaderStage.Vertex)对应接口定义见 pipestate.h返回当前事件下指定阶段已绑定的着色器反射按 ID 与入口点直接查询若已知某个着色器的ResourceId和入口点名称可通过renderdoc.ReplayController.GetShader直接查询接口定义见 renderdoc_replay.h。vs pyrenderdoc.CurPipelineState().GetShaderReflection(renderdoc.ShaderStage.Vertex) ps pyrenderdoc.CurPipelineState().GetShaderReflection(renderdoc.ShaderStage.Pixel) if vs is None or ps is None: raise ValueError(Expected a draw with a VS and PS to be selected)当前事件必须是一个同时使用了顶点着色器与像素着色器的 draw否则反射为None。脚本开头还通过IsCaptureLoaded()判断是否已加载捕获文件未加载时弹出文件选择框if not pyrenderdoc.IsCaptureLoaded(): filename pyrenderdoc.Extensions().OpenFileName(Choose a capture, , *.rdc) pyrenderdoc.LoadCapture(filename, renderdoc.ReplayOptions(), filename, False, True)从源码看ShaderReflection结构shader_types.h还包含resourceId、entryPoint、stage、encoding、rawBytes、dispatchThreadsDimension、outputTopology、inputSignature、outputSignature、constantBlocks、samplers、readOnlyResources、readWriteResources、interfaces、pointerTypes以及 task/mesh 着色器的taskPayload、光线追踪的rayPayload与rayAttributes等字段本文示例只用到其中一部分。输入与输出签名Input Output Signatures顶点着色器的inputSignature与outputSignature是SigParameter列表定义见 shader_types.h。输入签名中包含特殊内建元素例如ShaderBuiltin.VertexIndex、ShaderBuiltin.InstanceIndex也包含声明的固定功能顶点输入输出签名则通常同时包含 position 等特殊值以及需要插值并传递给像素着色器的用户自定义值。警告RenderDoc 用ShaderBuiltin标识此类特殊输入但不规定其解释方式。在不同 API 下含义可能不同——例如要么始终从 0 开始索引要么会偏移 draw 参数如firstVertex或vertexOffset。不同 API 对签名值的命名方式不同反射中使用两个可选名字优先使用变量名SigParameter.varName最可能与实际含义相关若没有反射出的变量名如 D3D 系列 API则回退使用语义名与语义索引组合的semanticIdxName。遍历签名时还可以查询元素类型varType与向量分量数compCount。固定功能顶点输入的数据来源配置可通过PipeState.GetVertexInputs查询并用SigParameter.regIndex索引对应条目而顶点输出签名则可用于按签名布局解码网格输出数据参见 mesh_output.py。for vin in vs.inputSignature: name vin.varName if name : name vin.semanticIdxName print( fVertex input {name} is {str(vin.varType)} x {vin.compCount} fat register {vin.regIndex} ) for vout in vs.outputSignature: name vout.varName if name : name vout.semanticIdxName print( fVertex input {name} is {str(vout.varType)} x {vout.compCount} fat register {vout.regIndex} )SigParameter中还包含semanticName、semanticIndex、systemValue、regChannelMask、channelUsedMask、perPrimitiveRate、stream等字段用于处理语义匹配、寄存器通道打包、逐图元速率输出以及多输出流等场景。常量块绑定Constant Block BindingsRenderDoc 将着色器的各类绑定归纳为四类常量块ConstantBlock、采样器ShaderSampler、只读资源ShaderResource与读写资源ShaderResource。这一分类可能与各 API 自身的绑定概念略有出入详见 in_depth/shader_refl.rst。对于常量块可以查询名称ConstantBlock.name可能为空、字节大小ConstantBlock.byteSize以及绑定点。绑定点是 API 特定的fixedBindSetOrSpace与fixedBindNumberRenderDoc 内部不使用它来定位资源但可用于用户展示或按 API 具体方式解读详见 descriptors_bindings.rst。ConstantBlock.variables是ShaderConstant列表递归描述结构体、数组以及标量/向量/矩阵等基础值。ShaderConstantshader_types.h包含name、相对父结构的byteOffset、位域偏移bitFieldOffset/bitFieldSize、默认值defaultValue与类型信息type类型ShaderConstantType则提供baseType、rows、columns、elements、arrayByteStride、matrixByteStride、flags与递归的members。if len(vs.constantBlocks) 0: cb vs.constantBlocks[0] print( f First is named {cb.name} fat {cb.fixedBindSetOrSpace}:{cb.fixedBindNumber} ) if cb.compileConstants: print( (compile-time constants)) elif not cb.bufferBacked: print( (runtime non-buffer temp data)) else: print(f (from a buffer, expected {cb.byteSize} bytes)) print(f containing {len(cb.variables)} variables) if len(cb.variables) 0: var cb.variables[0] print(f the first is named {var.name} at offset {var.byteOffset}) print( ftype {str(var.type.baseType)} fdimension {var.type.rows}x{var.type.columns} )提示若需要解码并定位常量块中变量的实际内容推荐使用ReplayController.GetCBufferVariableContents接口见 renderdoc_replay.h它会将变量解释到给定结构中并内联给出所有值。该函数还能处理常量块并非来自缓冲区、内容无法直接获取的情况。ConstantBlock有三个布尔标志描述其数据来源shader_types.h标志含义bufferBacked内容是否存储于内存缓冲区中否则由其他 API 特定方式如直接函数调用或编译期特化常量设置inlineDataBytes是否由内联数据字节提供而非特定缓冲区compileConstants是否为罗列编译期特化常量的虚拟缓冲区各 API 的映射情况如下D3D11只有常量缓冲区一种类型按阶段绑定到管线bufferBacked恒为TrueD3D12常量缓冲区可通过 root constants、root descriptors 或 root table 绑定bufferBacked均为True着色器本身不区分常量缓冲区来自真实缓冲区还是 root constants因此反射不提供该信息。经ResourceDescriptorHeap访问的常量缓冲区不会出现在反射中截至本文写作时 DXC 不为其输出反射数据OpenGL既可能是 uniform buffer也可能是包含所有“裸” uniform 的特殊虚拟块。虚拟块的bufferBacked为False其存储不透明、并非可寻址字节故inlineDataBytes也为FalseVulkan普通 uniform buffer 的bufferBacked为Truepush constants 区域为bufferBackedFalse、inlineDataBytesTrue特化常量为bufferBackedFalse、compileConstantsTrue且inlineDataBytesTrue。纹理与采样器绑定Texture Sampler Bindings与常量块类似还可以查询着色器拥有哪些只读资源含只读纹理与采样器绑定。最有用的数据通常存于描述符本身而非着色器反射中但绑定信息里包含 API 通常要求与描述符匹配的期望资源类型ShaderResource.textureType与格式ShaderResource.variableType见 shader_types.h。某些 API 下资源类型可能是“图像采样器”的组合体此时不会出现采样器绑定纹理本身会通过ShaderResource.hasSampler标记内嵌/组合采样器。print(fPS has {len(ps.readOnlyResources)} R/O resources) if len(ps.readOnlyResources) 0: res ps.readOnlyResources[0] print( f First is named {res.name} fat {res.fixedBindSetOrSpace}:{res.fixedBindNumber} ) print(f declared as {str(res.textureType)} of {res.variableType.baseType}) if res.hasSampler: print(f has attached sampler) print(fPS has {len(ps.samplers)} samplers) if len(ps.samplers) 0: samp ps.samplers[0] print( f First is named {samp.name} fat {samp.fixedBindSetOrSpace}:{samp.fixedBindNumber} )ShaderResource为只读与读写资源共用通过isReadOnly区分descriptorType用于区分同一绑定内的不同描述符类型通常与各 API 绑定类型一一对应。ShaderSamplershader_types.h仅有name、fixedBindNumber、fixedBindSetOrSpace与bindArraySize各 API 差异不大例外是 OpenGL 着色器中没有真正独立采样器的概念其samplers数组恒为空。D3D12 经SamplerDescriptorHeap访问的采样器同样不会出现在反射中。关于fixedBindSetOrSpace/fixedBindNumber的 API 特定含义来自 in_depth/shader_refl.rstD3D11fixedBindNumber为寄存器号fixedBindSetOrSpace被忽略D3D12fixedBindSetOrSpace为spacefixedBindNumber为寄存器号之后在 root signature 中重映射OpenGL两者均不使用因为绑定可能依据运行时的 uniform 值动态决定VulkanfixedBindSetOrSpace为setfixedBindNumber为描述符集内的 binding 号。只读/读写资源在各 API 下的具体映射详见 in_depth/shader_refl.rstD3D 上只读资源对应 SRV、读写资源对应 UAVStructuredBuffer映射为DescriptorType.BufferBuffer映射为DescriptorType.TypedBufferOpenGL 上只读资源为纹理含列为TypedBuffer的缓冲纹理读写资源为 SSBO、load/store 图像与 atomic counter buffers后者表现为带单个unsigned int成员、变量名为atomic_uint的读写缓冲区Vulkan 上只读资源为 sampled images、combined image/samplers、input attachmentsisInputAttachmentTrue、texel buffers 与加速结构读写资源为 storage images 与 storage buffers。调试信息Debug InformationShaderReflection.debugInfo保存着可选的ShaderDebugInfo结构shader_types.h仅当着色器编译时带有调试信息时才可用。若调试信息被剥离或编译器从未生成大量字段将不可用因此不要假设其必然存在。print(fPS was compiled by {renderdoc.ToolExecutable(ps.debugInfo.compiler)}) print(f{str(ps.debugInfo.encoding)} was compiled to {str(ps.encoding)}) if ps.debugInfo.debuggable: print(PS is debuggable!) else: print(fPS cant be debugged: {ps.debugInfo.debugStatus})ShaderDebugInfo主要字段encoding源码的ShaderEncodingShaderReflection.encoding则是最终着色器二进制的编码如 DXBC/DXIL/SPIR-Vcompiler已知着色器工具枚举KnownShaderTool如glslangValidator未知编译器则返回 UnknowncompileFlags编译标志键值对cmdline为编译器命令行参数SPIR-V 着色器的spirver记录重编译目标版本如spirv1.3debuggable该着色器是否可调试即使 API 整体支持调试个别着色器可能因使用不受支持的特性而不可调试debugStatus不可调试时的原因说明sourceDebugInformation是否已获得足够信息支持源码级调试debugInfoLoadingLog调试信息加载日志如搜索 shader PDB 的过程可用于诊断问题files着色器源文件列表ShaderSourceFile的filename与contents。若预处理输出文件中通过#line指令引用多个文件列表会同时包含预处理文件与按指令虚拟拆分出的文件从而在缺少部分源文件时也能引用原始文件中的原始行entrySourceName/entryLocation入口点在源码中的名称与位置入口点可能被 API 重命名此时该字段记录源码侧原名。各 API 都能在编译时提供额外调试信息但可能需要显式开启或默认被剥离部分 API 还能将调试信息分离到离线文件此时传给图形 API 的字节仅含定位标识。相关配置见 how_shader_debug_info.rst。若调试信息不完整RenderDoc 会尽量用可用数据填充其余字段缺失部分留空或信息较少。着色器反汇编Shader Disassembly反汇编不直接包含在着色器反射中因为存在多种反汇编格式且生成与存储反汇编的代价不足以默认开启。获取反汇编需调用ReplayController.DisassembleShaderrenderdoc_replay.h其参数为着色器反射对象返回请求格式的反汇编字符串若反汇编失败则返回错误信息。若反汇编格式传入空字符串RenderDoc 使用默认反汇编——依 API 不同为 DXBC、DXIL 或 SPIR-V即被认为是最易读的着色器直接表示其他可用格式可通过ReplayController.GetDisassemblyTargets枚举返回字符串列表每个字符串是一个反汇编目标名。返回值首项恒为默认目标着色器的原生反汇编其后可能是额外反汇编视图或硬件特定 ISA 格式。targets pyrenderdoc.GetDisassemblyTargets(withPipelineTrue) disasm pyrenderdoc.DisassembleShader(pipeline_id, reflection, targets[0])警告GetDisassemblyTargets的withPipeline参数若设为True会包含必须提供管线对象的反汇编格式。即便如此其他反汇编格式在管线对象可用但被省略时也可能无法产生 100% 准确的结果。因此依赖管线的 API 场景下最好传入当前 draw 的管线 IDPipeState.GetGraphicsPipelineObject()再反汇编。示例输出与运行方式以下是示例脚本在典型 Vulkan 捕获上的输出shader_refl.rst 原文Vertex input gl_VertexIndex is VarType.SInt x 1 at register 0 Vertex input gl_Position is VarType.Float x 4 at register 0 Vertex input texcoord is VarType.Float x 4 at register 0 Vertex input frag_pos is VarType.Float x 3 at register 1 VS has 1 constant blocks declared First is named ubuf at 0:0 (from a buffer, expected 1216 bytes) containing 3 variables the first is named MVP at offset 0 type VarType.Float dimension 4x4 PS has 1 R/O resources First is named tex at 0:1 declared as TextureType.Texture2D of 0 has attached sampler PS has 0 samplers PS was compiled by glslangValidator ShaderEncoding.GLSL was compiled to ShaderEncoding.SPIRV PS is debuggable!该示例以Shader Reflection名称注册在 qrenderdoc 的 Python 脚本窗口参见 python_scripting.rst中脚本源码可在 shader_refl.py 查看。运行前请先加载捕获文件并选中一个同时使用 VS 与 PS 的 draw 事件。若希望在编辑器中获得自动补全可按脚本顶部的做法将pyrenderdoc标注为qrenderdoc.CaptureContext()类型typing.TYPE_CHECKING守卫。【免费下载链接】renderdocRenderDoc is a stand-alone graphics debugging tool.项目地址: https://gitcode.com/gh_mirrors/re/renderdoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进