RenderDoc截帧DXBC编译 一文详解(上)
Thepoly原创 14441 0
实名

通过了实名认证的内容创造者

发布于 2023-4-27 14:05:16

您需要 登录 才可以下载或查看,没有账号?注册

x

81206981b603f57a91c07389ac43df78.jpg
Hello . 大家好
今天给大家带来的是 一文详解RenderDoc截帧DXBC编译我是守城的麦田
a8e7f8990925571c6f19c9a600dbce26.png
1DXBC简介
DXBC指令是D3D着色器语言使用的指令,HLSL高级着色语言经过编译器编译之后,会生成相应的DXBC指令。DXBC指令可以理解为GPU需要真正执行的指令。 OpenGL或者说是其它GPU厂商,他们提供的指令其实跟DXBC大同小异,略微有差异的也只是某些特殊的指令不是硬件支持而已。虽然编译器大部分情况下可以帮助我们优化代码,但是由于编译器特别智能,在某些情况下并不能保证代码是最优的方式。了解DXBC指令可以帮助我们在编写Shader时,能够写出更加可靠、性能优异的代码。另外了解DXBC指令,在某些情况下可以帮助我们更好的逆向其它游戏的一些材质效果。2开始准备
DXBC指令其实非常简单,学习起来也非常容易,它的套路可以说是非常的单一,大多数指令可以归纳为这样的形式:
下面是一个片段着色器的编译结果



我们这次测试的最基本的Shader如下



为了方便复制:

                Pass        {            HLSLPROGRAM            #pragma vertex vert            #pragma fragment frag            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            struct Attributes            {                float4 positionOS : POSITION;            };
            struct Varyings            {                float4 positionHCS : SV_POSITION;            };
            CBUFFER_START(UnityPerMaterial)            half4 _BaseColor;            CBUFFER_END
            Varyings vert(Attributes IN)            {                Varyings OUT;                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);                return OUT;            }
            half4 frag() : SV_Target            {                return _BaseColor;            }            ENDHLSL
3片元阶段分析   

挂载上RenderDoc后,找到对应的渲染Pass



当前Shader只包含顶点和片元着色器,我们先从PS阶段读起,PS阶段代码如下所示:



ps_4_0      dcl_constantbuffer cb0[1], immediateIndexed      dcl_output o0.xyzw   0: mov o0.xyzw, cb0[0].xyzw   1: retDXBC中外部传入的变量:
以下涉及到的DXBC官方解释链接如下:

https://docs.microsoft.com/zh-cn/windows/win32/direct3dhlsl/dx-graphics-hlsl-sm4-registers-ps-4-0?redirectedfrom=MSDN

ps_4_0本部分包含像素着色器版本 4_0 实现的输入和输出寄存器的参考信息,即当前的HLSL寄存器版本为4.0dcl_constantbuffer cb0[1]声明着色器常量缓冲区这句话表明声明寄存器 cb0 的常量缓冲区,其中包含 1 个元素。可以使用文本索引访问这些元素。immediateIndexed使用文本值为缓冲区编制索引。输出 return

dcl_output声明一个着色器输出寄存器。

o0.xyzwo0,o1,o2这种类型的寄存器,为输出寄存器,整个DXBC内要去猜测这个寄存器的含义。

mov指令使用句式:mov dst, srcdst 是目标寄存器。src 是一个源寄存器。即在源寄存器和目标寄存器内移动数据。0: mov o0.xyzw, cb0[0].xyzw这里将常量缓冲区内的数据移动到目标缓冲区内ret指令对于主函数,此指令将停止着色器执行。顶点阶段4顶点阶段分析vs_4_0      dcl_constantbuffer cb0[77], immediateIndexed      dcl_constantbuffer cb1[4], immediateIndexed      dcl_input v0.xyz      dcl_output_siv o0.xyzw, position      dcl_temps 2   0: mul r0.xyzw, v0.yyyy, cb1[1].xyzw   1: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw   2: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw   3: add r0.xyzw, r0.xyzw, cb1[3].xyzw   4: mul r1.xyzw, r0.yyyy, cb0[74].xyzw   5: mad r1.xyzw, cb0[73].xyzw, r0.xxxx, r1.xyzw   6: mad r1.xyzw, cb0[75].xyzw, r0.zzzz, r1.xyzw   7: mad o0.xyzw, cb0[76].xyzw, r0.wwww, r1.xyzw   8: ret
dcl_input指令声明着色器输入寄存器
语法:dcl_input vN[.mask][, 内插Mode]N 是标识寄存器号的整数。[.mask] 是一个可选组件掩码 (.xyzw) ,指定要使用的注册组件。此处的dcl_input v0.xyz即对应Shader内的float4 positionOS : POSITION;
dcl_output_siv指令声明包含 系统值 参数的输出寄存器。语法:dcl_output_siv oN[.masks], systemValue


所以这里的dcl_output_siv o0.xyzw, position即代表Varyings下的positionHCS接下来就是一系列的矩阵转换,从模型空间转换到齐次裁剪空间,分别乘MVP矩阵,Unity内写法为OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
拆解一下这句,如下




上面注释的一句等价于下面三句。其中OptimizeProjectionMatrix(glstate_matrix_projection)是由UniversalRenderPipelineCore.cs文件下的ShaderPropertyId类传入的,用于根据平台特殊处理的矩阵。public static class ShaderPropertyId    {    ...        public static readonly int viewMatrix = Shader.PropertyToID("unity_MatrixV");        public static readonly int projectionMatrix = Shader.PropertyToID("glstate_matrix_projection");    ...    }
dcl_temps指令声明临时寄存器。语法:dcl_temps N N为临时寄存器个数每个寄存器都有一个 32 位四分量值的空间。临时和 可索引临时 寄存器的总数必须小于或等于 4096。此处的dcl_temps 2即申明了两个临时寄存器r0-r1。下面需要用到。
mul 指令语法 mul dst、src0、src1dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。即dest.x = src0.x * src1.x;mul r0.xyzw, v0.yyyy, cb1[1].xyzw这条指令的意思为:r0.xyzw = * v0.yyyy * cb1[1].xyzw
mad乘以并添加源。语法:mad dst, src0, src1, src2dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。src2 是源寄存器。举例:dest.x = src0.x * src1.x + src2.x;mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw这条指令的意思为:r0.xyzw = cb1[0].xyzw * v0.xxxx + r0.xyzw
add指令两个向量相加。语法:add dst, src0, src1dst 是目标寄存器。
src0 是源寄存器。src1 是源寄存器。举例:dest.x = src0.x + src1.x;add r0.xyzw, r0.xyzw, cb1[3].xyzw这条指令的意思为:r0.xyzw = r0.xyzw + cb1[3].xyzw

分析

      0: mul r0.xyzw, v0.yyyy, cb1[1].xyzw   1: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw   2: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw   3: add r0.xyzw, r0.xyzw, cb1[3].xyzw

为了更加具象点,把上面这段DXBC翻译一下:

      0: r0.xyzw = v0.yyyy * cb1[1].xyzw;   1: r0.xyzw = cb1[0].xyzw * v0.xxxx + r0.xyzw   2: r0.xyzw = cb1[2].xyzw * v0.zzzz + r0.xyzw   3: r0.xyzw = r0.xyzw + cb1[3].xyzw

这里在原Shader内只是做了一系列的矩阵转换,但是DXBC会默认将HLSL替换为Vector4类型,包括向量,由推导可知,这里的cb1[4]是M矩阵,怎么来的呢?

由dcl_constantbuffer cb1可知,**cb1[4]**是由外部传入的参数类型我在Shader内只计算M矩阵,汇编后如下图所示:



由此,可推断出:cb1[4]正是unity_ObjectToWorld,而cb1[1]——cb1[3]分别对应M矩阵的每一列。unity_ObjectToWorld是一个4X44X44X4的矩阵,positionOS则是一个4X14X14X1的矩阵(Unity内使用列向量,矩阵左乘)那么一切都了然了,回想起矩阵乘法,两个矩阵A和B相乘,需要满足A的列数等于B的行数。a矩阵的行元素乘以每一列然后相加作为新矩阵的行元素

继续看下面:

        4: mul r1.xyzw, r0.yyyy, cb0[74].xyzw    5: mad r1.xyzw, cb0[73].xyzw, r0.xxxx, r1.xyzw    6: mad r1.xyzw, cb0[75].xyzw, r0.zzzz, r1.xyzw    7: mad o0.xyzw, cb0[76].xyzw, r0.wwww, r1.xyzw依此类推,范围cb [73]——[76]则代表VP矩阵 unity_MatrixVP范围cb [61]——[64]则代表V矩阵 unity_MatrixV范围cb [57]——[60]则代表P矩阵 UNITY_MATRIX_P最后输出ret结束
到此,本次截帧分析的基础Shader已经结束。下篇分析,如果存在贴图等类型的输入分析。

- End -



   

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表