OpenGL
1. OpenGL渲染管线
管线的英文名叫pipeline,其实翻译成流水线更贴切形象一点,渲染管线就是图形图像从数据一步一步形成最终输出的画面所要经历的各种操作过程。数据经过一个操作后,被处理成下一个步骤需要的数据,最终一步一步整合成拼凑最终画面的元素。通常来讲,以下两个大步骤是必要的:
- 顶点渲染:用于渲染出形状
- 像素渲染:在形状中填充色彩
所以可以简单地认为,渲染管线就是:
顶点渲染和像素渲染是图形渲染流水线中最重要的两个步骤,这两步发生在GPU硬件上,本质上就是GPU硬件在执行GPU硬件指令的操作。
类似于C语言的作用,为了对不同的GPU硬件提供统一的顶点渲染和像素渲染编程方式,OpenGL的维护组织Khronos Group制定了GLSL语言规范。GLSL全称为OpenGL Shading Language,针对顶点渲染步骤使用GLSL编写的源程序叫做Vertex Shader(顶点着色器),针对像素渲染步骤使用GLSL编写的源程序叫做Pixel Shader(像素着色器)。
2. Shader
通过一个简单的示例来直观的感受下如何在OpenGL程序中使用Shader:
/// // Initialize the shader and program object // int Init ( void) { char vShaderStr[] = "#version 300 es \n" "layout(location = 0) in vec4 vPosition; \n" "void main() \n" "{ \n" " gl_Position = vPosition; \n" "} \n"; char fShaderStr[] = "#version 300 es \n" "precision mediump float; \n" "out vec4 fragColor; \n" "void main() \n" "{ \n" " fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); \n" "} \n"; GLuint vertexShader; GLuint fragmentShader; GLuint programObject; // Load the vertex/fragment shaders vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr ); fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr ); // Create the program object programObject = glCreateProgram ( ); glAttachShader ( programObject, vertexShader ); glAttachShader ( programObject, fragmentShader ); // Link the program glLinkProgram ( programObject ); // Use the program object glUseProgram ( programObject ); }
假设以上的代码截取自demo.c源文件,对这个示例代码的说明如下:
- vShaderStr字符串表示的就是顶点着色器源码,fShaderStr字符串表示的就是像素着色器源码
- GLSL语言类似于C语言,每个着色器都必须包含main函数
- 通过自定义的函数LoadShader 加载顶点着色器和像素着色器,在本示例中,着色器是直接以字符串源码形式在demo.c中提供,也可以将着色器写在单独的文件中,然后在demo.c中通过打开文件->读取文件中内容->然后加载着色器来实现
- 着色器加载好之后需要进行编译,使用OpenGL接口glLinkProgram 来实现这一目的
可以看出,GLSL程序的编译类似于C语言程序的编译,只不过C语言程序的编译是通过可执行编译器程序实现,而GLSL程序的编译通过调用一系列的OpenGL接口来实现。
3. OpenGL驱动
OpenGL规范其实包含两个部分:
- OpenGL API规范,使用OpenGL API编写的程序叫做OpenGL程序,比如上面的示例demo.c,是在CPU上运行的
- GLSL规范,符合GLSL规范的程序叫做Shader程序,分为顶点Shader和像素Shader,是在GPU上运行的
OpenGL的驱动主要就有两大功能:
- 实现OpenGL API
- 实现GLSL编译器,用于将GLSL程序编译成具体的GPU硬件指令
如下图所示:
GLSL编译器一般在OpenGL驱动中以动态库形式提供,比如对于vivante公司实现的OpenGL驱动中的libGLSLC.so就是GLSL编译器。可以看出,GLSL程序的编译是在OpenGL程序运行时编译,从某种角度来说,GLSL程序的编译属于跨平台编译,因为编译发生在CPU平台,运行发生在GPU平台。
4. GLSL编译器
类似于LLVM编译器,GLSL编译器编译流程也大致分为三步,如下:
- GLSL程序被翻译成某种IR
- 对IR进行优化
- 将优化后的IR翻译成具体的GPU硬件指令
不同的GLSL编译器会使用不同的IR,我们这里介绍Mesa中的GLSL编译器的基本组成。
由于历史的原因,Mesa的GLSL针对不同的GPU使用不同的IR来优化,这里以AMD的GPU为例来说明,如下图所示:
- GLSL程序首先翻译成GLSL IR
- GLSL IR又翻译成TGSI IR
- TGSI IR翻译成LLVM IR
- LLVM IR最终翻译成AMD GPU的指令,AMD GPU指令使用的是GCN架构
SPIR-V是Vulkan和OpenCL中使用的IR,在后面会介绍。
评论