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的调用栈。先把关键调用栈组织如下(省略了不相关的调用)。
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的流程实现:
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是在那里被实现的呢,我们继续往下看:
//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里面见过:
//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。
//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层。
这里并没有给出winsys层,因为不同的OS有不同实现,这里关注Android。就是上图中的与libdrm交互的那一层,这层会使用ioctl将gpu-spec层组织的数据交给kms,再由kms把数据下发到gpu硬件。
总结
本文从glClear和glFlush的函数的调用栈分析了mesa实现OpenGL的一个典型流程。对于任何一个gl api可以套用该流程,熟悉mesa代码的调用脉络。