Mesa GL Dispatch分发分析与理解

Mesa GL Dispatch分发分析与理解

引言

这篇博客的核心是从OpenGL应用程序的典型api入手,分析gl api 调用到用户态驱动后端的过程,进而总结出一个典型的调用栈。理解了这个典型调用栈,对后续任何一个API的调用过程分析,都是a piece of cake。

在正式分析相关分发流程之前,我们必须明确的一点就是现在的Mesa采用的是最新的gallium架构,下图的左侧调用栈是在gallium 架构之前采用的模式,右侧是gallium架构下的模式。可以看出gallium架构有比较明显的三个分层(夹心饼干):

  • state_tracker层:负责收集OpenGL状态;

  • GPU-specific层:厂商的用户态驱动核心部分;

  • OS WinSys层:操作系统对接层。

下面章节出现的build-android-aarch64目录是通过meson编译的out目录!

二. glClear和glFlush的调用栈分析

接下来以glClear的函数实现,来分析从应用程序到GPU-specific的调用栈。先把关键调用栈组织如下(省略了不相关的调用)。

c 复制代码
glClear(GL_COLOR_BUFFER_BIT);
    _mesa_Clear(GL_COLOR_BUFFER_BIT);
        st_Clear(ctx, GL_COLOR_BUFFER_BIT);
            st->pipe->Clear(...);
                gpu_specific_Clear(...);

我们详细分析下glFlush的流程实现:

c 复制代码
GL_API void GL_APIENTRY glFlush (void)//gl.h
//build-android-aarch64/src/mapi/es2api/glapi_mapi_tmp.h
GLAPI void APIENTRY glFlush(void)
{
   const struct _glapi_table *_tbl = entry_current_get();//核心点,找到table
   mapi_func _func = ((const mapi_func *) _tbl)[217];
   ((void (APIENTRY *)(void)) _func)();
}    
    
// src/mapi/entry.c   
static inline const struct _glapi_table *
entry_current_get(void)
{
   return GET_DISPATCH();
}    
    

//build-android-aarch64/src/mapi/glapi/glapi.h 
_GLAPI_EXPORT extern __THREAD_INITIAL_EXEC struct _glapi_table * _glapi_tls_Dispatch;
_GLAPI_EXPORT extern __THREAD_INITIAL_EXEC void * _glapi_tls_Context;    
# define GET_DISPATCH() _glapi_tls_Dispatch
# define GET_CURRENT_CONTEXT(C)  struct gl_context *C = (struct gl_context *) _glapi_tls_Context
    
    

这个_glapi_tls_Dispatch是在那里被实现的呢,我们继续往下看:

c 复制代码
//build-android-aarch64/src/mapi/u_current.c
__THREAD_INITIAL_EXEC struct _glapi_table *_glapi_tls_Dispatch
   = (struct _glapi_table *) table_noop_array;
   
  

这个table_noop_array是不是似曾相见,有点眼熟,是的在前面eglCreateContext里面见过:

c 复制代码
//build-android-aarch64/src/mapi/shared-glapi/glapi_mapi_tmp.h
static void APIENTRY noopFlush(void)
{
      noop_warn("glFlush");
}
const mapi_func table_noop_array[] = {
    ...
    (mapi_func) noopFlush,
    ...
}

通过我们前面的st_create_context_priv分析可知,获取到table表以后,然后通过id会找到对应的函数,即指向了_mesa_Flush。

c 复制代码
//build-android-aarch64/src/mesa/main/context.c
/**
 * Execute glFlush().
 */
void GLAPIENTRY
_mesa_Flush(void)
{
   GET_CURRENT_CONTEXT(ctx);//转换成gl_context
   ASSERT_OUTSIDE_BEGIN_END(ctx);
   _mesa_flush(ctx);
}
_mesa_flush(...)
    struct st_context *st = st_context(ctx);
    st_glFlush(st, ...)//src/mesa/state_tracker/st_cb_flush.c
        st->pipe->flush(st->pipe, fence, flags);//st->pipe指向pipe_context,最终会调用到gpu_specific代码,这里通过前面的eglCreateContext分析可以知道会调用到xxx_gpu_flush
           xxx_gpu_flush(...)//src/gallium/drivers/xxx_gpuxxx_gpu_context.c   
    

三.对于GL Dispatch分发的理解

3.1 gl开头的函数

gl_开头的函数是gl的api入口函数,其是通过libglapi.so作为入口切入的。

3.2 _mesa_开头的函数

gl_打头的函数是OpenGL API,首先这些函数都是函数指针,这些函数挂载的函数就是mesa里以_mesa_打头的对应函数。这里有个调试技巧,把函数名gl_前缀换成_mesa_前缀即可。例如glGenBuffers的mesa入口函数是_mesa_GenBuffers。 _mesa_打头函数在前文的libmesa.a静态库中,而libmesa.a又被libgallium_dri.so包含,因此这些代码都是后端驱动的一部分,可以随意修改。

3.3 st_开头的函数

_mesa_打头的函数会调用st_开头的函数。这些st函数就是上图中的state tracker层。

3.4 pipe层字样的函数

pipe层,是接近于硬件的模块,GPU-specific层是从pipe层继承的。可以把pipe层看成接口层,各家驱动实现该接口。

3.5 GPU-specific层的函数

把pipe层的OpenGL状态转换为厂商支持的状态字段。该层一般会封装和处理硬件相关的状态,即硬件命令字。这些封装数据一般要由kmd交给硬件配置相关寄存器。

四、gl api 典型调用栈

通过以上分析,这里给出一个典型的gl函数的调用栈。即:mesa层、st层、pipe层、gpu-spec层。


mesa典型调用栈

这里并没有给出winsys层,因为不同的OS有不同实现,这里关注Android。就是上图中的与libdrm交互的那一层,这层会使用ioctl将gpu-spec层组织的数据交给kms,再由kms把数据下发到gpu硬件。

总结

本文从glClear和glFlush的函数的调用栈分析了mesa实现OpenGL的一个典型流程。对于任何一个gl api可以套用该流程,熟悉mesa代码的调用脉络。