本篇为最坑的部分,关于shader的导出

 
官方文档相关演示内容参见
关于Shader导出的方式,官方文档里给出了一种写法
targets = controller.GetDisassemblyTargets(True) target = targets[0] #... # For some APIs, it might be relevant to set the PSO id or entry point name pipe = state.GetGraphicsPipelineObject() # Get the pixel shader's reflection object ps = state.GetShaderReflection(rd.ShaderStage.Pixel) #... controller.DisassembleShader(pipe, ps.reflection, target)
这个方案确实是可以正确导出shader的,不过是完全原版,也就是在Renderdoc内对某一个阶段的shader点view后打开的Disassembly界面中Disassembly Type选择默认的那个版本
notion image
这个“默认”来源于上面这段code中的target[0]
如果你希望得到其他版本。理论上按照可以看到的顺序选择不同target序号进行disassmble即可。
但实际上如果你输出所有targets的内容,会发现,数量对不上,即部分Disassmbly Type不会出现在GetDisassemblyTargets中。这是因为有部分是通过第三方插件完成的。
通过第三方插件完成的主要是Spirv相关的几种选项。它们的设置可以在Renderdoc中查看Settings → Shader Viewer
 
比如,上述截图中GLSL(SPIRV-Cross)版本是通过调用SPIRV-Cross.exe这个外部工具实现的,其路径可以在Shader Viewer设置中找到
notion image
 

确认方案

如果你希望获得GLSL(SPIRV-Cross)的结果,就需要在Python中调用SPIRV-Cross。官方文档中表明qrenderdoc库提供了一个类叫ShaderProcessingTool
查看该类的参数可以发现其内容和上述截图中的内容几乎匹配,其中也存在ShaderProcessingTool.CompileShader()和ShaderProcessingTool.DisassembleShader()两个函数。
但是文档中可以看到,其传入的参数必须有一个QWidget
notion image
notion image
这个QWidget实际上是一个窗口= =
如果希望通过纯code方式进行操作,就不能从这个入口进行调用
既然本身是调用的第三方应用程序,为什么一定要显示界面呢?
 
SPIRV-Cross本身是一个独立开源库,github上有使用说明,我们只需要把它需要的数据喂给他执行,就能得到最终结果了。
查看其Github库的Readme可以看到它本身支持通过Command Line Interface进行调用,也就是可以通过cmd调用它的功能
README.md
KhronosGroup
notion image
 
然后稳妥起见我重新打开了Renderdoc的源码工程,想确认一下Renderdoc是如何调用的,最好是能和Renderdoc采用同样的方式调用,这样才会得到相同的结果。
而上文中有提到qrenderdoc.shaderprocessingtool这个类。其文档中有写到需要传递args给目标工具,且有一个DefaultArguments()函数可以获取默认的args
notion image
Renderdoc源码工程中我找到了对应内容,其函数写在PersistantConfig.cpp这个类中:
rdcstr ShaderProcessingTool::DefaultArguments() const { if(tool == KnownShaderTool::SPIRV_Cross) return "--vulkan-semantics --entry {entry_point} --stage {glsl_stage4} --version 460"; else if(tool == KnownShaderTool::SPIRV_Cross_OpenGL) return "--entry {entry_point} --stage {glsl_stage4}"; else if(tool == KnownShaderTool::spirv_dis || tool == KnownShaderTool::spirv_dis_OpenGL) return "--no-color"; else if(tool == KnownShaderTool::glslangValidatorGLSL) return "-g -V -S {glsl_stage4} --target-env {spirv_ver}"; else if(tool == KnownShaderTool::glslangValidatorGLSL_OpenGL) return "-g -G -S {glsl_stage4} --target-env {spirv_ver}"; else if(tool == KnownShaderTool::glslangValidatorHLSL) return "-D -g -V -S {glsl_stage4} -e {entry_point} --target-env {spirv_ver}"; else if(tool == KnownShaderTool::spirv_as || tool == KnownShaderTool::spirv_as_OpenGL) return ""; else if(tool == KnownShaderTool::dxcSPIRV) return "-T {hlsl_stage2}_6_0 -E {entry_point} -spirv -fspv-target-env={vulkan_ver}"; else if(tool == KnownShaderTool::dxcDXIL) return "-T {hlsl_stage2}_6_0 -E {entry_point}"; else if(tool == KnownShaderTool::fxc) return "/T {hlsl_stage2}_5_0 /E {entry_point}"; else if(tool == KnownShaderTool::slangSPIRV) return "-lang slang -target spirv -emit-spirv-directly -g2 -entry {entry_point} -stage " "{full_stage}"; else if(tool == KnownShaderTool::slangDXIL) return "-lang slang -target dxil -g2 -entry {entry_point} -stage {full_stage}"; return args; }
有没有觉得return内容眼熟?其实就是SPIRV-Cross的Readme中CLI那部分args
所以可以确认这个调用途径是一样的
 

确认调用内容

然后是如何在Python中调用SPIRV-Cross.exe,我直接问的豆包(。
给出的回复是用subprocess这个库可以从Python中调用CLI运行其他程序,大概长这样(实际执行是失败的):
import subprocess reflection_command = [spirv_cross_executable, "--reflect", input_file] try: reflection_result = subprocess.run(reflection_command, stdout = subprocess.PIPE, stderr = subprocess.PIPE, text=True) if reflection_result.returncode == 0: print("Reflection executed successfully") print("Output: ", reflection_result.stdout) else: print("Error: ", reflection_result.stderr) except FileNotFoundError: print("spirv - cross executable not found")
这里reflection_command实际上就是在cmd中输入的指令
spirv_cross_executable就是执行程序的路径(如果用过cmd的同学应该有些印象,指令的第一个内容就是要执行的程序)
这个路径,Renderdoc已经写这儿了 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
notion image
问题出在哪儿呢,出在我们不知道arg实际应该怎么传。
如果你这时候只运行程序不传参数,即写成reflection_command = [spirv_cross_executable] 然后直接调用subprocess.run运行,
实际上是可以在Console界面看到SPIRV-Cross自己提供的args说明,非常全(我只截最上面一点)
notion image
最上面可以看到Log了一句Didn’t specify input file,意思是你调用一定得有一个input的shader file,然后才能转译
而KhronosGroup在这里也没有讲清楚在Args之前需要传入什么内容

确认 Args

经过一些测试之后我发现实际上上述红色提示中[SPIR-V File](- is stdin)指的就是input的shader file,且必须指明。它不像指定output需要-o或者--output 标记,它就跟在可执行程序路径后。
所以command实际上是这样的:
{executable_path} {input_shader_file_path} {args}
而args部分几经测试后,针对RenderDoc的api中DefaultArguments()返回内容的搜索,可以确认到,我需要使用如下这套args,并且将entry_point与glsl_stage4进行替换以正确设置
if(tool == KnownShaderTool::SPIRV_Cross) return "--vulkan-semantics --entry {entry_point} --stage {glsl_stage4} --version 460";
查看源码工程可以确认,这里的entry_point等于文档中ShaderReflection.entryPoint转字符串,通常结果是“main”
notion image
  • glsl_stage4在源码中意义就很明显了
notion image
这样所有args内容就都是可以确定下来了

最后一个坑

args确定完,只要传一个正确的input shader file就完成了。ShaderProcessingTool.CompileShader()函数中也需要传递一个str格式的source参数作为source code,那顺理成章的认为传这个进去就行了
notion image
RenderDoc给的官方案例中也提供了输出原Shader File的办法,就是controller.DisassembleShader,最后可以得到shader 文本。顺着这个思路把这个函数调用的结果赋值给字符串,然后把字符串保存成文件就可以做成shader file了。也是有效的,最终这个file也可以查看到,和Renderdoc内打开的默认版本是一样的
notion image
然而实际上执行结果是一行LogError
notion image
😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅
回头又看了一眼SPIRV-Cross的Readme发现他们样例中传入的是一个.spv文件
问了一下豆包才知道.spv是一个二进制文件,不是一个可阅读的版本,那么尬住了,我上哪儿找这个版本的文件
然后回去看了一眼Renderdoc源码发现了盲点
ShaderProcessingTool.DisassembleShader()是这么写的,它传入了一个ShaderReflection:
ShaderToolOutput ShaderProcessingTool::DisassembleShader(QWidget *window, const ShaderReflection *shaderDetails, rdcstr arguments) const { //... QFile binHandle(input_file); if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) { binHandle.write( QByteArray((const char *)shaderDetails->rawBytes.data(), shaderDetails->rawBytes.count())); binHandle.close(); } //... }
而Shader ProcessingTool.CompileShader()是这么写的,它传入的source是一个str:
ShaderToolOutput ShaderProcessingTool::CompileShader(QWidget *window, rdcstr source, rdcstr entryPoint, ShaderStage stage, rdcstr spirvVer, rdcstr arguments) const { //... QFile binHandle(input_file); if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) { binHandle.write(QByteArray((const char *)source.c_str(), source.count())); binHandle.close(); } //... }
那么很显然,这个可以存储为.spv版本的二进制shader源码,就是ShaderReflection.rawBytes😅😅😅😅😅
notion image
到这儿就结束了。相当于需要把ShaderReflection.rawBytes存成.spv文件然后通过command传给SPIRV-Cross.exe进行转译,这时能得到正确的输出结果。
notion image
这回导出的终于对了,整套流程硬控了我半个月😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅
 
Renderdoc自动化导出重组踩坑(二):缺失的样例描述Renderdoc自动化导出重组踩坑(四):您可以直接转HLSL的.jpg