vesperW · 3月5日

一文读懂高通 GPU 驱动渲染流程

1. gpu command 分析

1.1 gpu command 概述

SM8650 平台上,GLES 发送给 KMD(GPU 驱动)的 GPU 命令有两种类型:同步命令和绘制命令。

绘制命令,一般都是一个个的 drawcall 组成的,是真正 GPU 程序指令,KMD 会给 GPU 硬件进行处理。

同步命令,在 KMD 就能处理,无需 GPU 硬件参与。该指令主要是往 KMD 的任务队列塞同步点,用来阻塞绘制指令的处理。比如 dequeueBuffer 带的 release fence,会被打包成一个同步命令,发送到 KMD,KMD 将其放到任务队列,UMD 后续提给 KMD 的命令,就会排在该同步命令后面,就会被阻塞无法被处理,直到该同步命令的同步点被 signal,同步命令从任务队列清除,排在该同步命令后面的命令才能继续被处理。

GLES 给 KMD 发送命令时,会创建一个 struct kgsl_gpu_command 实例,然后把命令打包好,保存到该实例里,然后通过 IOCTL_KGSL_GPU_COMMAND 发送给 KMD 处理。

KMD 把 GPU 命令抽象成 struct kgsl_drawobj 数据结构的子类。比如绘制命令抽象成 struct kgsl_drawobj_cmd,同步命令抽象成 struct kgsl_drawobj_sync,它们都继承于 struct kgsl_drawobj。

故 IOCTL_KGSL_GPU_COMMAND 的实现函数 kgsl_ioctl_gpu_command 函数接收 GLES 发送的消息后,会把 struct kgsl_gpu_command 实例内保存的 GPU 命令转成 struct kgsl_drawobj 实例(这里的转成是指创建 struct kgsl_drawobj 实例,解析把 struct kgsl_gpu_command 内容并填充到 struct kgsl_drawobj 实例,而不是简单的类型强转)。绘制命令转成 struct kgsl_drawobj_cmd 实例, 同步命令转成 struct kgsl_drawobj_sync 类型的实例,然后再把这些 drawobj 实例,保存到 context 的 drawqueue 里面。如图:

image.png

KMD 初始化时,会为每个 GPU 设备创建一个 struct adreno_device 实例,每个 struct adreno_device 实例会有一个 struct adreno_hwsched 实例,每个 struct adreno_hwsched 实例会有 16 条 struct adreno_dispatch_job 类型的链表,每个 struct adreno_dispatch_job 会关联一个 context,如图:

Image

kgsl_ioctl_gpu_command 函数将 struct kgsl_gpu_command 实例保存的 GPU 命令转成 struct kgsl_drawobj 实例,并保存到 context 的 drawqueue 里面;同时还会创建一个 struct adreno_dispatch_job 实例,然后根据 context 的 priority,将创建的 struct adreno_dispatch_job 实例加入到 struct adreno_hwsched 实例对应链表的尾部(如 priority 为 0,则加入第 0 条链表尾部)。

KMD 初始化时,会创建一个名为 kgsl_hwsched 内核线程。该线程专门负责处理 struct adreno_dispatch_job 实例关联的 context 的 drawqueue 里面的 drawobj。

kgsl_ioctl_gpu_command 函数末尾,或者 GPU HW 处理完任务后,或者同步命令的同步点 signal 后,都会唤醒 kgsl_hwsched 线程,kgsl_hwsched 线程依次遍历 struct adreno_hwsched 实例的 16 条链接,取出 struct adreno_dispatch_job 实例关联的 context 的 drawqueue 里面的 drawobj 进行处理。如果是同步命令 drawobj,就直接从 drawqueue 里面移除。如果是绘制命令 drawobj 就发送 GPU HW 处理。

KMD 初始化时,还会创建一个名为 kgsl-events 的内核线程。该线程专门负责 GPU 同步点 signal,如 acquire fence signal,EGLSync/GLSync 的 signal。

每个 context 都间接拥有一个 struct kgsl_event 类型的链表。该链表是用来保存 GPU 同步点的,如 acquire fence,EGLSync/GLSync 等。struct kgsl_event 实例有两个很关键成员变量:timestamp 和 func。timestamp 用来判断一个该 event 是否 signal,func 是事件处理函数。

Image

GPU HW 硬件有个 retire timestamp 寄存器,专门用来记录 GPU HW 完成任务时的时间,GPU HW 每完成一次计算任务就会更新这个 retire timestamp 寄存器。

每次 GPU HW 处理完任务后,KMD 就会遍历比较 context 的 events 链表的 event 的 timestamp 和 retire timestamp 寄存器的时间戳,如果 timestamp 小于 hw 的 retire timestamp 时,KMD 就会在 kgsl_events 线程执行 event 的 func 函数,func 函数就会 signal 同步点。

1.2 同步命令处理分析

1.2.1 同步命令类型

KMD 根据同步点的实现差异,将同步命令分为三种类型:

(1)fence syncpoint command:主要是 fence 类型的同步点,如 release fence。对应的数据结构 struct kgsl_cmd_syncpoint_fence

(2)timestamp syncpoint command:未知(没能通过 gles api 分析出什么情况下会下发该种同步点命令,应该是在不支持 android fence 机制的系统使用的)。对应的数据结构 struct kgsl_cmd_syncpoint_timestamp

(3)timeline syncpoint command:未知(没能通过 gles api 分析出什么情况下会下发该种同步点命令)。对应的数据结构 struct kgsl_cmd_syncpoint_timeline

但是这三种同步点最终都会换转成 struct kgls_drawobj_sync_event 实例,然后保存到 struc kgsl_drawobj_sync 实例的 synclist 变量内。看下图:

Image

1.2.2 fence syncpoint command 处理分析

根据第一章节的概述,我们可以把 GPU 命令处理一般分为二个步骤:

(1)预处理,该步骤主要解析用户空间传进来的 GPU 命令,然后转化成对应的 drawobj 实例并保存到 context 的 drawqueque 里面,同时创建一个 struct adreno_dispatch_job 实例加入到 struct adreno_hwsched 实例的链表内。

(2)ready 后,kgsl_hwsched 内核线程取出 drawobj 处理。

故对于 fence syncpoint command 处理分析。我们也分成这两个步骤:

(1)首先分析同步命令如何转成 struc kgsl_drawobj_sync 实例。上面小节的 UML 图已经很直观的展示这一个转化过程。这里我们再补一张这一流程的时序图:

Image

(2)当 fence signal 后,触发 drawobj_sync_expire 执行,drawobj_sync_expire 函数会再次创建一个 struct adreno_dispatch_job 实例,并根据 context 的 priority,将创建的 struct adreno_dispatch_job 实例加入到 struct adreno_hwsched 实例对应链表的尾部(个人认为这个步骤是多余的,因为上面已经做了这个动作),然后唤醒 kgsl_hwsched 线程进行处理。时序如下:

Image

kgsl_hwsched 线程取出 drawobj 处理,对于 fence sync drawobj 而言,kgsl_hwsched 线程不做任何特殊处理,仅仅移出 drawqueue,避免阻塞后面的 drawobj:

Image

下图是一次处理过程中的 ftrace 打印:

Image

1.2.3 timestamp syncpoint command 处理分析

timestamp syncpoint 的处理流程和 fence syncpoint 处理流程类似,同样分为两个步骤。区别一个使用的是 kgsl events 框架,一个使用的 dma fence 框架。

(1)首先解析用户空间传进来的同步命令,转成 struct kgsl_drawobj_sync 实例,然后把实例保存到 context 的 drawqueque 里面,同时也会创建一个 struc kgsl_event 实例,并保存到 context 的 events 链表里。时序图如下:

Image

(2)当 event 的 timestamp 小于 hw 的 retire timestamp 时,唤醒 ksgl_events 线程执行 event 的事件处理函数

_kgsl_event_worker。_kgsl_event_worker 执行 drawobj_sync_func,drawobj_sync_func 执行 drawobj_sync_expire:

Image

drawobj_sync_expire 函数唤醒 kgsl_hwsched 线程进行处理。流程和 fence syncpoint command 处理流程一样,这里就不赘述了。

1.3 绘制命令处理分析

和上面一样,绘制命令处理,我们同样分为两个步骤进行分析。

(1)解析用户空间传进来的绘制命令,然后转化成 struct kgsl_drawobj_cmd 实例并保存到 context 的 drawqueque 里面,同时创建一个 struct adreno_dispatch_job 实例加入到 struct adreno_hwsched 实例的链表内。

同样,在讲解 struct kgsl_drawobj_cmd 生成过程前,先梳理出其相关的数据结构关系:

Image

struct kgsl_drawobj_cmd 里面保存了两种类型的数据:

(i)一个是指令数据,保存在 cmlist 变量里。由 struct kgsl_gpu_command 的 cmdlist 解析获得。

(ii)一个是对象数据,是输入给指令处理的,保存在 memlist 变量里和 profiling_buf_entry 变量里。由 struct kgsl_gpu_command 的 objlist 解析获得。SM8650 基本上都是 profiling_buf_entry。

生成 struct kgsl_drawobj_cmd 实例的时序图如下:

Image

(2)struct kgsl_drawobj_cmd 实例处理的关键从 gen7_hwsched_submit_drawobj 函数开始。gen7_hwsched_submit_drawobj 函数先把 struct kgsl_drawobj_cmd 转成 struct hfi_submit_cmd 实例和 struct hfi_submit_cmd 实例,struct hfi_submit_cmd 实例和 struct hfi_submit_cmd 实例都是在一块内存里面。然后在调用 gen7_gmu_context_queue_write 函数将该内存的数据写到显存里面,GPU FW 就会取出送给 GPU HW 处理。

Image

代码时序和详细如下图:

Image

Image

Image

Image

2. gpu fence 分析

gpu fence,即 gpu 创建的 fence,由 gpu 进行 signal。queuebuffer 代入的 fence 就是 gpu fence,EGLSync/GLsync 等也是一个 gpu fence。

gpu fence 在 KMD 的数据结构为 struct kgsl_sync_fence,每个 struct kgsl_sync_fence 实例会间接关联一个 struct kgsl_event 实例。

每个 context 都有个 struct kgsl_sync_timeline 实例,struct kgsl_sync_timeline 实例有一个 struct kgsl_sync_fence 链表。context 创建的 gpu fence 都保存在这个链表内。

gpu fence 都是通过 IOCTL_KGSL_TIMESTAMP_EVENT 命令通知 KMD 创建的,该命令的实现函数为 kgsl_ioctl_timestamp_event。kgsl_ioctl_timestamp_event 创建 gpu fence 时,会生成一个 fence fd,然后把这个 fd 保存到参数 priv 变量里带到用户空间。如图:

Image

创建 gpu fence 的时序图:

Image

gpu fence 借助 kgsl events 模块 signal 的,根据上面的分析,我们知道每个 gpu fence 会关联一个 struct kgsl_event 实例,该实例 timestamp 和 gpu fence 是同一个。该实例 signal 就是 gpu fence signal。struct kgsl_event 实例 signal 流程就不分析了,上面分析过。

struct kgsl_event 实例 signal,kgsl-events 线程被唤醒,执行_kgsl_event_worker 函数

执行 kgsl_sync_fence_event_cb 函数,kgsl_sync_fence_event_cb 执行 kgsl_sync_timeline_signal 函数,kgsl_sync_timeline_signal 函数在执行 dma fence 的回调函数,完成 signal 操作。时序图如下:

Image

我们可以通过 ftrace 的 kgsl_fire_event 进行判断:

Image

3. 一次完整渲染流程分析

在 kgsl_ioctl 函数加 trace,一次 eglSwapBuffers,会发起三次 ioctl:

Image

Image

第一次 ioctl,fence syncpoint command

第二次 ioctl,绘制命令

第三次 ioctl,创建 gpu fence

我们可以通过 ftrace 进行分析整个渲染流程:

Image

syncpoint_fence:表示解析 fence syncpoint command 完成

第一个 adreno_cmdbatch_queued:表示 fence syncpoint command 进入待处理队列

第二个 adreno_cmdbatch_queued:表示绘制命令进入待处理队列

kgsl_register_event: 表示在创建 gpu fence

syncpoint_fence_expire:表示 release fence signal

adreno_cmdbatch_submitted:表示指令提交到了 GPU HW

kgsl_fire_event:表示 gpu fence signal

4. 总结

本文是基于 SM8650, Android 系统,GLES API 进行分析的,并不能适用所有高通平台,系统,图形接口,有一定的局限性。如有疏漏,在所难免,还请读者朋友海涵。

以上所有的代码,都来自开源网站:

https://git.codelinaro.org/cl...

END

作者:Andy
来源:OPPO内核工匠

推荐阅读

欢迎大家点赞留言,更多 Arm 技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
2916
内容数
346
分享一些在嵌入式应用开发方面的浅见,广交朋友
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息