赞
踩
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。
从基本意义上来说,着色器只是一种把输入转化为输出的程序。
着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
格式:
#version version_number
in type in_variable_name;
out type out_variable_name;
int main(){
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
GLSL的数据类型有: int 、float、double、uint、bool
向量:
vecnN — 包含N个float分量的默认向量
dvecnN —包含N个double分量的默认向量
ivecnN —包含N个int分量的默认向量
uvecnN —包含N个unsigned int分量的默认向量
bvecnN —包含N个bool分量的默认向量
大多数用vecN 就足够了。
一个向量可以通过vec.x 这里的x 就是第一个分量, 可以分别通过.x .y .z .w 来获取第1、2、3、4个分量
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;
然而,你不允许在一个vec2向量中去获取.z元素。
我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
每个着色器都有自己的代码,用于组成一个整体,为了方便数据交流和传递,GLSL定义了in、out关键字来实现。
在顶点着色器的输入上有特殊处理,因为这一层是cpu上配置的,通常使用layout(location = 0)标识符来表示
全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。
第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
另一个例外是片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。
如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
片段着色器:
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
void main()
{
FragColor = vertexColor;
}
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。
首先,uniform是全局的(Global)。
它与in out的区别就是可以不许要通过顶点着色器作为媒介传递数据,直接修改uniform的变量值就可以传递到片段着色器里
片段着色器:
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
vertexShader = glCreateShader(GL_VERTEX_SHADER); // 创建一个shader glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // 存入顶点着色器代码 glCompileShader(vertexShader); // 编译着色器 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); // success ==0 表示编译失败 shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); // 将着色器程序指定到program 上 glLinkProgram(shaderProgram); // 创建一个运行在程序的顶点处理器 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); // success ==0 表示链接失败 glUseProgram(shaderProgram); // 将program作用当前的渲染状态 int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor"); // 获取着色器代码中的ourColor变量 glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); // 修改全局的uniform变量 (函数后缀4f : 需要四个float 的值, 类似的还有 i int、 ui unsigned int、 fv、需要一个float的数组或者向量)
在mesa 中, glxxx 函数对应与 _mesa_xxx ,如 glCreateShader 对应 _mesa_CreateShader
glCreateShader → _mesa_CreateShader → create_shader_err → create_shader
static GLuint
create_shader(struct gl_context *ctx, GLenum type)
{
struct gl_shader *sh;
GLuint name;
_mesa_HashLockMutex(ctx->Shared->ShaderObjects);
name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1);
sh = _mesa_new_shader(name, _mesa_shader_enum_to_shader_stage(type));
sh->Type = type;
_mesa_HashInsertLocked(ctx->Shared->ShaderObjects, name, sh);
_mesa_HashUnlockMutex(ctx->Shared->ShaderObjects);
return name;
}
1) ctx 是当前的gl 上下文、type 是传入的参数(GL_VERTEX_SHADER、 GL_FRAGMENT_SHADER等类型)
2)从_mesa_HashLockMutex 可以看出这整个函数就是访问hash表的一个操作。
3)_mesa_new_shader 就是在当前上下文中创建一个新的shader sh, 并指定类型 type。
4)通过_mesa_HashInsertLocked插入到 hash 表中,然后返回name 值,这是一个unsigned int 类型的,可以理解为内核中创建文件返回fd 值, 做了应用隔离。
void glShaderSource(GLuint shader,GLsizei count,const GLchar * const *string,const GLint *length);
glShaderSource 4个参数,
第一个 shader 就是通过glCreateShader 创建返回的id值,
第二个count,指定了字符串和数组中的元素数,
第三个string 就是着色器代码
第四个length 代码长度,一般不输入或者给null,mesa 中会自己strlen算的
glShaderSource → _mesa_ShaderSource → shader_source
static ALWAYS_INLINE void shader_source(struct gl_context *ctx, GLuint shaderObj, GLsizei count, const GLchar *const *string, const GLint *length, bool no_error) { ... ... sh = _mesa_lookup_shader(ctx, shaderObj); ... ... offsets = malloc(count * sizeof(GLint)); ... ... for (i = 0; i < count; i++) { //一般count 是1 if (length == NULL || length[i] < 0) offsets[i] = strlen(string[i]); // length 是null 所以这里使用strlen算 else offsets[i] = length[i]; /* accumulate string lengths */ } /* Total length of source string is sum off all strings plus two. * One extra byte for terminating zero, another extra byte to silence * valgrind warnings in the parser/grammer code. */ totalLength = offsets[count - 1] + 2; source = malloc(totalLength * sizeof(GLcharARB)); for (i = 0; i < count; i++) { GLint start = (i > 0) ? offsets[i - 1] : 0; memcpy(source + start, string[i], (offsets[i] - start) * sizeof(GLcharARB)); } source[totalLength - 1] = '\0'; source[totalLength - 2] = '\0'; ... ... set_shader_source(sh, source); ... ... }
1) ctx 当前上下文, shaderobj 是传入的shader id 号
2)_mesa_lookup_shader 通过shaderobj 在当前上下文中找到之前创建好的shader
3)offsets 是来计算shader 代码的偏移,如过count为1 则代表的是 shader 代码的长度,
总长度需要+2,一个额外字节用于终止零,另一个额外字节用于消除解析器/语法代码中的valgrind警告。
4)memcpy 将shader 代码string 拷贝到 source,然后通过 set_shader_source 放到sh->Source ,专门存放code 的一个成员
glCompileShader → _mesa_CompileShader 通过 shaderobj 在当前上下文中找到shader → _mesa_compile_shader 一些判断处理 → _mesa_glsl_compile_shader
_mesa_glsl_compile_shader :
void _mesa_glsl_compile_shader(struct gl_context *ctx, struct gl_shader *shader, bool dump_ast, bool dump_hir, bool force_recompile) { // dump_ast = 0 , dump_hir = 0, force_recompile = 0 const char *source = force_recompile && shader->FallbackSource ? shader->FallbackSource : shader->Source; // source = shader->Source if (!force_recompile) { if (ctx->Cache) { char buf[41]; disk_cache_compute_key(ctx->Cache, source, strlen(source), shader->sha1); if (disk_cache_has_key(ctx->Cache, shader->sha1)) { /* We've seen this shader before and know it compiles */ if (ctx->_Shader->Flags & GLSL_CACHE_INFO) { _mesa_sha1_format(buf, shader->sha1); fprintf(stderr, "deferring compile of shader: %s\n", buf); } shader->CompileStatus = COMPILE_SKIPPED; free((void *)shader->FallbackSource); shader->FallbackSource = NULL; return; } } } else { /* We should only ever end up here if a re-compile has been forced by a * shader cache miss. In which case we can skip the compile if its * already be done by a previous fallback or the initial compile call. */ if (shader->CompileStatus == COMPILE_SUCCESS) return; } ... ... }
1)从_meas_compile_shader 调下来时,后面三个参数都是false,这里 source = shader->Source,
找到之前通过glShaderSource存放的shader 代码
2)ctx->Cache 是磁盘缓存(struct disk_cache), 默认的路径是 ~/.cache/mesa_shader_cache,这写都是sha1 值,存放格式
sha1 前两位作为目录,剩下的做文件名, 所以想找到完整的值就是 目录名+ 文件名
默认的路径可以通过 环境变量来修改 export MESA_SHADER_CACHE_DIR=xxx
也可以通过export MESA_SHADER_CACHE_DISABLE=1 禁用 磁盘缓存
3、disk_cache_compute_key 计算代码的sha1 存放到 shader->sha1 , 并将shader 代码存放到 ctx->Cache->driver_keys_blob
4、shader->CompileStatus = COMPLIE_SKIPPED, 这里暂时不编译,我理解这里是异步的,
真正编译着色器是在si_draw_vbo 中,这里会通过si_update_shaders 更新着色器程序,如果有最新被改过或者新增就会触发编译
最终调用si_llvm_compile 编译,将代码编译成IR(LLVM IR, 是一种中间语言表示)。
glGetShaderiv → _mesa_GetShaderiv → get_shaderiv 返回shader->CompileStatus 状态
static void get_shaderiv(struct gl_context *ctx, GLuint name, GLenum pname, GLint *params) { struct gl_shader *shader = _mesa_lookup_shader_err(ctx, name, "glGetShaderiv"); if (!shader) { return; } switch (pname) { case GL_SHADER_TYPE: *params = shader->Type; break; case GL_DELETE_STATUS: *params = shader->DeletePending; break; case GL_COMPLETION_STATUS_ARB: /* _mesa_glsl_compile_shader is not offloaded to other threads. */ *params = GL_TRUE; return; case GL_COMPILE_STATUS: *params = shader->CompileStatus ? GL_TRUE : GL_FALSE; break; case GL_INFO_LOG_LENGTH: *params = (shader->InfoLog && shader->InfoLog[0] != '\0') ? strlen(shader->InfoLog) + 1 : 0; break; case GL_SHADER_SOURCE_LENGTH: *params = shader->Source ? strlen((char *) shader->Source) + 1 : 0; break; case GL_SPIR_V_BINARY_ARB: *params = (shader->spirv_data != NULL); break; default: _mesa_error(ctx, GL_INVALID_ENUM, "glGetShaderiv(pname)"); return; } }
1)get_shaderiv获取 GLSL shader 状态
glCreateProgram → _mesa_CreateProgram → create_shader_program
create_shader_program:
static GLuint create_shader_program(struct gl_context *ctx) { GLuint name; struct gl_shader_program *shProg; _mesa_HashLockMutex(ctx->Shared->ShaderObjects); name = _mesa_HashFindFreeKeyBlock(ctx->Shared->ShaderObjects, 1); shProg = _mesa_new_shader_program(name); _mesa_HashInsertLocked(ctx->Shared->ShaderObjects, name, shProg); assert(shProg->RefCount == 1); _mesa_HashUnlockMutex(ctx->Shared->ShaderObjects); return name; }
glAttachShader → _mesa_AttachShader → get_shaderiv 返回shader->CompileStatus 状态
static void
attach_shader_err(struct gl_context *ctx, GLuint program, GLuint shader,
const char *caller)
{
... ...
shProg = _mesa_lookup_shader_program_err(ctx, program, caller);
... ...
sh = _mesa_lookup_shader_err(ctx, shader, caller);
... ...
n = shProg->NumShaders;
for (i = 0; i < n; i++) {
.... ...
attach_shader(ctx, shProg, sh);
}
}
1) 通过program 和shader 的id 分别找到 shProg 和 sh 这两个都是存在当前上下文ctx->Shared->ShaderObjects 中的
2) attach_shader 中 将sh 放入到shProg->Shaders[n] 中
glLinkProgram →
_mesa_LinkProgram 通过shprog id 找到shprog
|→ link_program_error
|→ link_program
|→ _mesa_glsl_link_shader
|→ link_shaders
|→ link_intrastage_shaders 有点多,还待分析
glUseProgram 实现:
glUseProgram → _mesa_UseProgram
|→ use_program
static ALWAYS_INLINE void
use_program(GLuint program, bool no_error)
{
if (shProg) {
/* Attach shader state to the binding point */
_mesa_reference_pipeline_object(ctx, &ctx->_Shader, &ctx->Shader);
/* Update the program */
_mesa_use_shader_program(ctx, shProg);
}
1)更新ctx->_Shader , 并在_mesa_use_shader_program里激活着色器程序, 就是将shProg 放到
ctx->Shader.ActiveProgram
Shader —GLSL shader object state
_Shader —Current active shader pipeline
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。