0x1 总体流程
GPU硬件简单说来可以包括这几块,首先是内存访问相关模块,如MMU,Cache等。其次是各种Fixed Function模块,如Rasterizer,Clipper等。最后是可编程单元模块Shader Core,Shader Core的加入使GPU具有了和CPU一样具体处理各种复杂问题的能力,赋予了用户很大的发挥空间来编写各个OpenGL shader,OpenCL kernel,这些shader和kernel最后都会生成对应的专有GPU指令并运行在Shader Core上。在Intel Gen GPU中,对应的Shader Core模块也称为EU(Execution Unit)。
从GPU driver到GPU硬件完成绘制的流程可以简单说明如下。
a. GPU user space driver在CPU侧根据应用设置的各种状态生成各种command list。
b. GPU user space driver在CPU侧通过compiler把shader源代码生成对应的gpu shader core指令. 这个shader core指令也是保存在memory中,driver会把其地址保存在某一个command结构体中。
c. GPU user space driver把前面生成的内容发送到GPU kernel space driver中。GPU kernel space driver启动gpu硬件读取command list的内容。
d. GPU中的command executor开始消费command,根据command的内容驱动gpu硬件中各个不同的模块协同工作。当需要shader core执行的时候,从相应的memory buffer中读取GPU指令,shader core根据GPU指令完成相应的shader操作。当需要读取Vertex和Texture内容的时候,从相应的command找到对应的buffer地址,然后读取相应的内容。
上面提到的流程说明可以简单地用下图所示。
本文后面用一个简单的三角形绘制测试程序说明一下GPU driver是如何工作的。这里的GPU driver主要是指开源的User space driver实现mesa。
这个测试程序的流程如下图所示。
0x2 Shader编译
下面说明一下mesa driver中Intel Gen shader编译过程。
Vertex Shader的编译过程如下。
对应的GLSL source code如下所示。
这个shader只是把外部设置的attribute信息Position设置到gl_Position中。
|
|
编译的第一阶段是完成词法分析,语法分析,生成对应的抽象语法树AST。
然后根据AST生成Mesa内部的中间表示NIR。然后在NIR上执行各种编译器优化。每执行一次优化称为一个Pass。
经过多个Pass优化以后,最后生成的NIR代码如下所示。
|
|
下面开始执行编译器后端,生成具体的GPU shader core指令。这里shader core也就是前面提到的EU,所以我们也就是要生成EU code。
这里面也用到了传统的编译器后端技术,如图着色寄存器分配等。
最后生成的EU code如下所示。
Fragment Shader的编译过程如下。
具体的流程和Vertex Shader的编译过程类似。详细的过程不介绍了,下面只是列出各个编译阶段的结果。
GLSL source code
NIR code
|
|
EU code
|
|
0x3 Command生成
Command Stream Unit是Gen GPU内部用来管理3D pipeline或者Media单元的模块,通过配置不同的Command Stream命令,我们可以精细地控制3D pipeline的运行。
Command Stream Unit还提供了URB分配和管理的功能。URB可以理解成是用来在各个Pipeline阶段(如VS, Rasterizer, Clipper, PS等)之间传递参数的buffer。
对Command Stream的编程,简单地理解就是把Graphics API的状态配置转换成Command Stream的命令。不同API函数执行的时候需要配置不同的Command Stream命令,我们可以通过command stream dump机制把生成command保存下来,然后通过可视化的工具分析出现时问题。mesa提供了可视化的viewer工具来分析生成的每个command。
对于Broadcom GPU来说,和Command Stream类似的概念叫做Control List,执行Control List的硬件模块叫做Control List Executor。Broadcom GPU驱动也需要配置Control List来驱动GPU的执行。
下面来说明一下一个简单的OpenGL ES应用执行的时候需要配置哪些Command Stream命令。
eglCreateContext调用以后,会执行driver初始化动作。需要配置下面这些Command。
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_PIPELINE_SELECT
state GEN9_L3CNTLREG
GEN9_MI_LOAD_REGISTER_IMM
GEN9_PIPE_CONTROL
GEN9_STATE_BASE_ADDRESS
GEN9_PIPE_CONTROL
state GEN9_CS_DEBUG_MODE2
GEN9_MI_LOAD_REGISTER_IMM
state GEN9_CACHE_MODE_1
GEN9_MI_LOAD_REGISTER_IMM
GEN9_3DSTATE_DRAWING_RECTANGLE
GEN9_3DSTATE_SAMPLE_PATTERN
GEN9_3DSTATE_AA_LINE_PARAMETERS
GEN9_3DSTATE_WM_CHROMAKEY
GEN9_3DSTATE_WM_HZ_OP
GEN9_3DSTATE_POLY_STIPPLE_OFFSET
GEN9_3DSTATE_PUSH_CONSTANT_ALLOC_VS
GEN9_3DSTATE_PUSH_CONSTANT_ALLOC_VS
GEN9_3DSTATE_PUSH_CONSTANT_ALLOC_VS
GEN9_3DSTATE_PUSH_CONSTANT_ALLOC_VS
GEN9_3DSTATE_PUSH_CONSTANT_ALLOC_VS
GEN9_3DSTATE_CC_STATE_POINTERS
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_PIPELINE_SELECT
state GEN9_L3CNTLREG
GEN9_MI_LOAD_REGISTER_IMM
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_STATE_BASE_ADDRESS
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
eglMakeCurrent调用以后,需要给frame buffer分配Gem buffer。
glShaderSource/glCompileShader创建shader对象,把glsl source code设置到gpu driver中。
glLinkProgram调用以后,执行glsl的编译动作,把glsl source code编译成glsl AST,然后转换成NIR。
glClear调用以后,mesa driver会通过Blit engine来执行clear操作,这个时候需要给Blit engine生成EU code。所以这个时候会生成glsl fragment shader code,再转换成NIR,再生成EU code。这个时候需要配置下面这些Command。
state GEN9_GT_MODE
GEN9_PIPE_CONTROL
GEN9_MI_LOAD_REGISTER_IMM
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_STATE_BASE_ADDRESS
GEN9_PIPE_CONTROL
这边再说明一下,给Blit engine生成的fragment shader的NIR和EU code如下所示
然后通过下面代码加载Vertex数据。
glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
glEnableVertexAttribArray ( 0 );
调用下面的函数执行Draw操作。
glDrawArrays ( GL_TRIANGLES, 0, 3 );
这个时候才会把对应shader的NIR代码转换成EU code。注意前面执行glCompileShader的时候只是生成了NIR,没有完成EU code的生成,也就是说编译器的后端到这个时候才开始工作。
先生成fragment shader的EU code,然后把EU code配置到下面的command中。
其中GEN9_3DSTATE_PS有一个分量会指明PS对应的EU Code保存地址。
GEN9_3DSTATE_PS
GEN9_3DSTATE_PS_EXTRA
然后生成vertex shader的EU code,然后把EU code配置到下面的command中。
其中GEN9_3DSTATE_VS有一个分量会指明VS对应的EU Code保存地址。
GEN9_3DSTATE_STREAMOUT
GEN9_3DSTATE_SO_DECL_LIST
GEN9_3DSTATE_VS
然后继续配置如下的command。其中GEN9_3DPRIMITIVE用来说明了需要参加绘制的Vertex数目。
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
state GEN9_BLEND_STATE_ENTRY
GEN9_3DSTATE_PS_BLEND
state GEN9_BLEND_STATE
GEN9_3DSTATE_SF
GEN9_3DSTATE_RASTER
GEN9_3DSTATE_CLIP
GEN9_3DSTATE_WM
GEN9_3DSTATE_LINE_STIPPLE
GEN9_3DSTATE_VERTEX_ELEMENTS
state GEN9_VERTEX_ELEMENT_STATE
GEN9_3DSTATE_VF_INSTANCING
state GEN9_VERTEX_ELEMENT_STATE
GEN9_3DSTATE_VF_INSTANCING
state GEN9_VERTEX_BUFFER_STATE
GEN9_PIPE_CONTROL
state GEN9_CS_CHICKEN1
GEN9_MI_LOAD_REGISTER_IMM
state GEN9_CC_VIEWPORT
GEN9_3DSTATE_VIEWPORT_STATE_POINTERS_CC
state GEN9_SF_CLIP_VIEWPORT
GEN9_3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP
GEN9_3DSTATE_URB_VS
GEN9_3DSTATE_URB_VS
GEN9_3DSTATE_URB_VS
GEN9_3DSTATE_URB_VS
state GEN9_BLEND_STATE
GEN9_3DSTATE_BLEND_STATE_POINTERS
state GEN9_COLOR_CALC_STATE
GEN9_3DSTATE_CC_STATE_POINTERS
GEN9_3DSTATE_CONSTANT_VS
GEN9_3DSTATE_CONSTANT_VS
GEN9_3DSTATE_BINDING_TABLE_POINTERS_VS
GEN9_3DSTATE_BINDING_TABLE_POINTERS_VS
GEN9_3DSTATE_BINDING_TABLE_POINTERS_VS
GEN9_3DSTATE_BINDING_TABLE_POINTERS_VS
GEN9_3DSTATE_BINDING_TABLE_POINTERS_VS
GEN9_3DSTATE_SAMPLER_STATE_POINTERS_VS
GEN9_3DSTATE_SAMPLER_STATE_POINTERS_VS
GEN9_3DSTATE_MULTISAMPLE
GEN9_3DSTATE_SAMPLE_MASK
GEN9_3DSTATE_HS
GEN9_3DSTATE_TE
GEN9_3DSTATE_DS
GEN9_3DSTATE_GS
GEN9_3DSTATE_PS
GEN9_3DSTATE_PS_EXTRA
GEN9_3DSTATE_STREAMOUT
GEN9_3DSTATE_CLIP
GEN9_3DSTATE_SF
GEN9_3DSTATE_WM
GEN9_3DSTATE_SBE
GEN9_3DSTATE_SBE_SWIZ
GEN9_3DSTATE_PS_BLEND
GEN9_3DSTATE_WM_DEPTH_STENCIL
GEN9_3DSTATE_SCISSOR_STATE_POINTERS
GEN9_3DSTATE_CLEAR_PARAMS
GEN9_3DSTATE_POLY_STIPPLE_PATTERN
GEN9_3DSTATE_VF_TOPOLOGY
GENX(3DSTATE_VERTEX_BUFFERS)
GEN9_3DSTATE_VF_SGVS
GEN9_3DSTATE_VF
GEN9_3DSTATE_VF_STATISTICS
GEN9_3DPRIMITIVE
最后执行eglSwapBuffers,把前面生成的command都送到GPU kernel driver中,然后启动GPU硬件完成绘制。
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
GEN9_PIPE_CONTROL
0x4 Command配置的实现
这里以Vulkan driver中的Command配置实现为例说明Mesa中Gen gpu的command是如何配置的。对OpenGL driver来说,这部分实现也是很类似的。
通过xml配置文件来保存Command Streamer的结构体信息,然后在编译的时候通过python代码把xml文件转换成对应的配置头文件。这个过程如下图所示。
其中可以看到Gen的各种Driver实现都依赖于这个配置头文件。另外提到一点就是这些配置的函数都是在不同的Driver中实现的,有不少冗余代码,可以作为一个代码优化方向。
xml的内容是参考下面两个文档(以Gen11为例)来生成的。
Intel® Iris® Plus Graphics and UHD Graphics Open Source
Programmer’s Reference Manual
For the 2019 10th Generation Intel CoreTM Processors based on the
“Ice Lake” Platform
Volume 2a - Command Reference: Instructions (Command Opcodes)
Intel® Iris® Plus Graphics and UHD Graphics Open
Source
Programmer’s Reference Manual
For the 2019 10th Generation Intel CoreTM Processors
based on the “Ice Lake” Platform
Volume 8: Command Stream Programming
下面截取了部分xml内容如下所示。其中包括了PIPE_CONTROL的配置。
|
|
把xml内容转换后头文件内容如下。
Command配置的宏定义如下。
该宏定义包括了所有Command的配置。
上面的宏定义是通过实现各个不同Command的pack函数(也是通过前面的genXml自动生成的)来实现不同Command的配置的。
例如下面的函数实现了PIPE_CONTROL Command的结构体信息配置。
|
|
在Vulkan驱动中通过类似的配置的调用代码来设置具体的Command信息。
最后调用的函数是前面实现的GEN9_PIPE_CONTROL_pack()。