作者:i_dovelemon
日期:2014 / 8 / 31
来源: CSDN博客
文章: GPU硬件架构
引言
在3D图形学中,可编程渲染管道的出现,无疑是一种创举。在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader。
Vertex Shader
在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行的。所以,掌握了Vertex ALU的构成方式,我们就能够很轻松的使用汇编语言来操作它。下面是Vertex ALU的结构图:
从上面我们可以看到除了中心的ALU之外,还有很多附属的寄存器,这些寄存器将会用来作为ALU的输入和输出。在上面可以看到有几种不同的寄存器。下面依次来讲解他们的作用,以及如何使用他们。
在最上面的V0到V15这16个寄存器,将会对应于在DirectX中定义的顶点格式描述符或者FVF描述符。在GPU中,每一个寄存器都是128bit的,也就是说,刚好能够容纳4个32位的float型数据,即一个向量。所以,这16个寄存器将会作为Vertex ALU输入寄存器。如何输入,需要我们定义顶点格式,以及在Shader中申明哪一个寄存器接受哪一个向量。怎么理解这句话了?实际上,在我们定义顶点格式的时候,如果大家对DirectX熟悉的话,就会知道,我们会说一个顶点中哪个向量是什么用途的,用D3DUSAGE_NORAL0,D3DUSAGE_POSITION0这样的格式来表示它的用途。所以,同样的,在Shader中,我们可以通过del_normal0,
del_position0来申明哪一个寄存器来接受哪一个数据。这样说,应该理解了吧!
除了上面的V0到V15这几个寄存器之外,还存在一些用于接受常量的寄存器。这些寄存器就是上图中右上角的以C开头的寄存器。这些寄存器称为常量寄存器,从这个名字我们就可以知道,在常量寄存器中存储的应该就是常量数据,也就是说Vertex ALU只能够对常量寄存器进行读,没有办法进行写操作。而这些常量寄存器,用于接受我们从应用程序中输入的额外的,会在Vertex Shader中使用的常量数据,比如World
Transform Matrix, 将模型坐标变换到齐次裁剪空间的Matrix,或者光源的Position, Color, 某个物体的Material等等。总之,你可能用到的所有常量的数据,都可以通过应用程序来进行设置。当设置好了数据之后,我们就可以简单的直接使用这些数据来完成我们想要完成的工作。
上面介绍的两种寄存器,都是输入对Vertex ALU的输入寄存器。那么Vertex ALU计算的结果该放在什么地方了?
读者可以从上图中看到,在右下方有一排的寄存器。这些寄存器就是ALU的输出寄存器,也就是说ALU的计算结果应该输入到这些寄存器中去。在上面的Color0和Color1分别称为0D0,和0D1寄存器,其中OD0将会保存Vertex Shader中计算出来的顶点的Diffuse Color,而0D1将会保存顶点的Specular Color。但是,并不总是需要这样做。说到底他们都只是寄存器,存放的只是数据而已,如何解释还要靠你自己。也就是说,你可以在这些寄存器中保留其他的值,但最好遵守Shader版本的一些规定。对模型顶点进行的World
* View * Projection 变换的结果保存在一个名为oPos的寄存器中。注意这个寄存器,Shader规定了只能够存放变换后的顶点的坐标,而不能用作其他用途。实际上,我们只要按照上面的解释来进行数据的填充,想要在Vertex Shader和Pixel Shader之间沟通,有如下的oT0到oT7这么多个寄存器可供使用。在oT0中,一般会存放顶点对应的纹理坐标,这样就可以将纹理坐标传递到Pixel Shader中去。除了这个之外,剩下的所有的寄存器,我们就可以按照我们想要的来使用他们。如果读者有过HLSL的编写经验,那么就应该知道,在Pixel
Shader中可以使用:TEXCOORD0, :TEXCOORD1来申明这些东西,所以这就在硬件上对应了oT1到oT7这些寄存器。
有了输入和输出的寄存器,那么我们还需要在ALU进行计算的过程中,用于临时保存变量的寄存器。这些寄存器在上图中左下角以r开头的这些都是称为临时寄存器,用于接受在Vertex Shader中进行计算的临时数据。
好了以上,就是对Vertex ALU的基本简单介绍。
Pixel Shader
在Vertex Shader中有ALU来处理,那么在Pixel Shader中,同样也有ALU来进行处理。下面是Pixel ALU的硬件架构图:
实际上,它的架构方法和Vertex ALU十分的相似。以c开头的寄存器还是常量寄存器,使用方法同Vertex ALU一样。以r开头的寄存器同样还是临时寄存器,但是有一点不同,r0寄存将要保存在最后计算出来的像素的颜色值,也就是说这是Pixel ALU唯一的一个输出寄存器。 v0和v1,分别对应了在Vertex ALU中的oD0和oD1,你在Vertex Shader中传入了什么数据,那么在Pixel
Shader中就用同样的法则来解释他们。
除了上面的这些寄存器之外,就剩下了左边的几个以t开头的寄存器了。你还记得Device->SetTexture这个函数吗?这个函数的申明如下:
HRESULT SetTexture( [in] DWORD Sampler, [in] IDirect3DBaseTexture9 *pTexture);
在DirectX SDK的文档中,这样解释第一个参数:
表示的是采样器的编号,不同的纹理绑定在不同的采样器上面,在可编程渲染管道中,我们通过采样器编号来引用纹理。
也就是说,不同的纹理可以绑定在不同的采样器上,然后在Pixel Shader中进行使用。这就是为什么我们能够实现Multi-texture的原因。
但是,上面的t开头的寄存器,又不仅仅能够做这样使用。还记得在上面的Vertex ALU中,我们曾经说过,在Vertex Shader和Pixel Shader之间进行沟通的方法就是通过oT0-oT7这些寄存器。也就是说,oT0和oT7分别对应了Pixel ALU中的t0到t7(oops!上图并没有t7啊,这个图只是概念上的图,具体有多少个寄存器,需要查看D3DCAPS属性中的介绍)。那么,如果我们在Vertex
Shader想要传递一些数据到Pixel Shader中,我们就可以将数据保存在0T1或者其他的寄存器中,然后在Pixel Shader中通过特定的指令来访问这些数据,获取这些数据,从而完成我们需要的工作。
好了,以上就是今天的关于GPU硬件架构的内容。软件工程中的摩尔定律说过,每18个月,计算机硬件性能就会翻新。这说明了硬件的架构方法总是在变化,可能这里的架构并不符合你现在使用的GPU的架构,但是基本的原理和设定还是相同的。
希望通过这篇文章,大家能够对Shader如何进行工作有了一定的理解,希望对大家有一定的帮助!
在此还要说一下,上面只是讲解了GPU的结构,那么如何通过汇编语言来操作GPU和上面的寄存器,也就是有哪些指令?请到MSDN的官网上阅读有关ASM
Shader Reference的资料。