当前位置:   article > 正文

mesa 着色器流程分析_mesa shader cache

mesa shader cache

1、着色器

着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。
  • 1

从基本意义上来说,着色器只是一种把输入转化为输出的程序。
着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
着色器是使用一种叫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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

数据类型

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;
  • 1
  • 2
  • 3
  • 4

你可以使用上面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);
  • 1
  • 2
  • 3

输入与输出

每个着色器都有自己的代码,用于组成一个整体,为了方便数据交流和传递,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); // 把输出变量设置为暗红色
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

片段着色器:

#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)

void main()
{
    FragColor = vertexColor;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

uniform

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。
  • 1

首先,uniform是全局的(Global)。
它与in out的区别就是可以不许要通过顶点着色器作为媒介传递数据,直接修改uniform的变量值就可以传递到片段着色器里
片段着色器:

#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量

void main()
{
    FragColor = ourColor;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用流程

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的数组或者向量)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

mesa 中实现

在mesa 中, glxxx 函数对应与 _mesa_xxx ,如 glCreateShader 对应 _mesa_CreateShader

glCreateShader 实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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 值, 做了应用隔离。

glShaderSource实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

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 实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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 实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

1)get_shaderiv获取 GLSL shader 状态

glCreateProgram 实现:

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;
}      

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 和 create_shader 类似,都是在ctx->Shared->ShaderObjects 表中申请,然后通过_mesa_new_shader_program 创建
  2. 返回一个类似shader 的id

glAttachShader 实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

1) 通过program 和shader 的id 分别找到 shProg 和 sh 这两个都是存在当前上下文ctx->Shared->ShaderObjects 中的
2) attach_shader 中 将sh 放入到shProg->Shaders[n] 中

glLinkProgram 实现:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1)更新ctx->_Shader , 并在_mesa_use_shader_program里激活着色器程序, 就是将shProg 放到
ctx->Shader.ActiveProgram

Shader —GLSL shader object state
_Shader —Current active shader pipeline

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/998781
推荐阅读
相关标签
  

闽ICP备14008679号