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實現其實是一樣的,即代碼完全複用。顯卡驅動只需要實現特定的接口。

OpenGL API Dispatch在mesa官網上有講解:https://www.mesa3d.org/dispatch.html

對應的代碼如下,集中在 src/mapi 目錄:

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指針和當前上下文:

#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函數連接起來。

/**
 * 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指針的讀寫問題,以及針對特定平臺的優化。

/* 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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章