Mesa代码解读 - OpenGL API dispatch layer

抛开Nvidia闭源驱动,mesa能够说是linux系统标配的OpenGL框架实现。称之为框架实现,是由于mesa并非一个独立应用,它编译的结果是一堆.so动态库。对应用程序而言,mesa提供了实现全部OpenGL和GLX API的libGL.so动态库。对显卡驱动而言,mesa提供了一套将应用层API(glXXX())与驱动实现动态结合的机制,具体可了解(DRI/DRM, Gallium3D)。mesa的灵活之处在于,无论底层用哪一个厂家的驱动,上层libGL.so实现实际上是同样的,即代码彻底复用。显卡驱动只须要实现特定的接口。html

OpenGL API Dispatch在mesa官网上有讲解:https://www.mesa3d.org/dispatch.htmllinux

对应的代码以下,集中在 src/mapi 目录:api

mesa-19.0.5/src
              - mapi
                   - mapi_glapi.c: _glapi_set_dispatch() will be called by mesa state tracker.
                   - u_current.c: u_current_set_table() Set the global or per-thread dispatch table pointer.
                   - es1api/glapi_mapi_tmp.h: The implementations of all OpenGL ESv1 functions.
                   - es2api/glapi_mapi_tmp.h: The implementations of all OpenGL ESv2 functions.
                   - glapi: OpenGL API dispatch layer, it is responsible for dispatching all the gl*() functions.
                         - glapi_mapi_tmp.h:  The implementations of all OpenGL functions.
                         - glapi.h:Define GET_DISPATCH().
                         - glapitable.h: Define struct _glapi_table.
                         - glapitemp.h: This file is a template which generates the OpenGL API entry point functions.
                         - glapi_dispatch.c: This file generates all the gl* function entrypoints through #include "glapi/glapitemp.h".
                         - glprocs.h: This file is only included by glapi.c and is used for the GetProcAddress() function.
                         - glapi_getproc.c: Code for implementing glXGetProcAddress()

全部OpenGL声明和定义在 glapi_mapi_tmp.h ,格式都很统一。
声明:多线程

GLAPI void APIENTRY GLAPI_PREFIX(NewList)(GLuint list, GLenum mode);

实现:框架

GLAPI void APIENTRY GLAPI_PREFIX(NewList)(GLuint list, GLenum mode)
{
    /* 获取当前上下文使用的struct _glapi_table指针 */
   const struct _glapi_table *_tbl = entry_current_get();
   /* 从struct _glapi_table指针索引到目标函数指针 */
   mapi_func _func = ((const mapi_func *) _tbl)[0];
   /* 调用 */
   ((void (APIENTRY *)(GLuint list, GLenum mode)) _func)(list, mode);
}

结构体struct _glapi_table的定义在 src/mapi/glapi/glapitable.h :
这个结构体包含了mesa支持的全部GL函数。GLAPIENTRYP宏是指针标识符*,即_glapi_table每个数据成员是一个函数指针。函数

struct _glapi_table
{
   void (GLAPIENTRYP NewList)(GLuint list, GLenum mode); /* 0 */
   void (GLAPIENTRYP EndList)(void); /* 1 */
   void (GLAPIENTRYP CallList)(GLuint list); /* 2 */
   void (GLAPIENTRYP CallLists)(GLsizei n, GLenum type, const GLvoid * lists); /* 3 */
   void (GLAPIENTRYP DeleteLists)(GLuint list, GLsizei range); /* 4 */
   GLuint (GLAPIENTRYP GenLists)(GLsizei range); /* 5 */
   ...
}

glapi.h定义了宏用于获取当前的GL Dispatch指针和当前上下文:oop

#define GET_DISPATCH() \
     (likely(_glapi_Dispatch) ? _glapi_Dispatch : _glapi_get_dispatch())
#define GET_CURRENT_CONTEXT(C)  struct gl_context *C = (struct gl_context *) \
     (likely(_glapi_Context) ? _glapi_Context : _glapi_get_context())

mapi_glapi.c提供了函数对当前GL Dispatch指针赋值。mesa在建立或切换上下文时会调用_glapi_set_dispatch()修改该指针:优化

/* It will be called in src/mesa/main code */
void _glapi_set_dispatch(struct _glapi_table *dispatch)
{
   u_current_set_table((const struct _glapi_table *) dispatch);
}

#define u_current_table_glapi_Dispatch 
#define u_current_context _glapi_Context
/**
 * Set the global or per-thread dispatch table pointer.
 * If the dispatch parameter is NULL we'll plug in the no-op dispatch
 * table (__glapi_noop_table).
 */
void u_current_set_table(const struct _glapi_table *tbl)
{
   u_current_init();

   stub_init_once();

   if (!tbl)
      tbl = (const struct _glapi_table *) table_noop_array;

#if defined(GLX_USE_TLS)
   u_current_table = (struct _glapi_table *) tbl;
#else
   tss_set(u_current_table_tsd, (void *) tbl);
   u_current_table = (ThreadSafe) ? NULL : (void *) tbl;
#endif
}

在mesa建立GL上下文时,会调用_mesa_initialize_exec_table()初始化struct _glapi_table *exec,从而将struct _glapi_table结构体的gl函数指针与mesa函数链接起来。ui

/**
 * Initialize a context's exec table with pointers to Mesa's supported GL functions.
 *  *
 * This function depends on ctx->Version.
 *
 * \param ctx  GL context to which \c exec belongs.
 */
void _mesa_initialize_exec_table(struct gl_context *ctx)
{
   struct _glapi_table *exec;

   exec = ctx->Exec;
   assert(exec != NULL);

   assert(ctx->Version > 0);

   _mesa_initialize_exec_dispatch(ctx, exec);

   if (!_mesa_is_no_error_enabled(ctx) && (_mesa_is_desktop_gl(ctx) || (ctx->API == API_OPENGLES2 && ctx->Version >= 30))) {
      SET_BeginTransformFeedback(exec, _mesa_BeginTransformFeedback);
      SET_BindBufferRange(exec, _mesa_BindBufferRange);
      SET_BindFragDataLocation(exec, _mesa_BindFragDataLocation);
      SET_BindFragDataLocationIndexed(exec, _mesa_BindFragDataLocationIndexed);
      SET_BindSampler(exec, _mesa_BindSampler);
      ...
   }
   ...
}

src/mapi目录下还有部分代码是处理多上下文和多线程环境下GL Dispatch指针的读写问题,以及针对特定平台的优化。spa

/* GL API Call Trace */
glDrawArrays(...);  // src/mapi/glapi/glapi_mapi_tmp.h
    ((struct _glapi_table *disp)[310])(...);  // Mesa GL API Dispatch Layer
        _mesa_DrawArrays(...); // src/mesa/main/draw.c. More info: _mesa_initialize_exec_dispatch()
            (struct gl_context *)ctx->Driver.Draw(...); // src/mesa/main/draw.c. Binding Mesa API and State Tracker API: st_init_draw_functions()
                st_draw_vbo(...); //src/mesa/state_tracker/st_draw.c
                    (struct pipe_context *)pipe->draw_vbo(...); //Call into the GPU driver
                        virgl_draw_vbo(...); // e.g. VirtioGPU. Binging through virgl_context_create()/virgl_create_screen()/virgl_drm_screen_create()