一文读懂高通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里面。如图:    

c202cf0b94860d2358e59e5c47cc844c.jpeg

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

8a3156e9f175dd0797d87fe9234e14f2.jpeg

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是事件处理函数。

08f6896bd9abbd341767651a53ea58fd.jpeg

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变量内。看下图:    

877f5d592433055633d17b2f323ece1c.jpeg

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图已经很直观的展示这一个转化过程。这里我们再补一张这一流程的时序图:    

cdd86bb5c30b723a4a2fc48f6e3de372.png

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

e5b1a96ca09fc45aa069b0b3237f3215.png

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

f1dfa7d9a89cbbc83d5490d43b12b53a.png    

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

bcf56684d247c8e1ed5be73b861fd82e.png

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链表里。时序图如下:

29ce43cb362f1f5ca39a4aa75e7e75ee.png

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

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

ce2bba9612ff93055ecdaf21a2cffa35.png

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生成过程前,先梳理出其相关的数据结构关系:

e9380cfd65518566bd71a80d4cd178ca.jpeg

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实例的时序图如下:

5eddf3f4e3a8b312cc3d0979f67acefc.png

(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处理。    

60a7a3139f6524372427207ede9a5422.jpeg

代码时序和详细如下图:    

9b01441e9189b70381f72b8a636e2d55.png    

0f39957a84632eef6cfb8025a836cabf.png

3fbd0c50c3ab62b174806ab6a382d23a.png    

89abd924cb242881c9f5e524c853a11f.png

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变量里带到用户空间。如图:    

4b2c075a1eb946978af713d9b6f53c47.jpeg

创建gpu fence的时序图:

36e506b552aad4a8fd4210dde15de0f7.png

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操作。时序图如下:    

7238aa324f43fe556fd69175fa32e7d9.png

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

d6f521649863cc034df9537a67e78b07.png    

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

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

c98937ed5443a8b777aee63233c547b3.png

59bf2a3c6adaaeb5fbbfa1d854cdc8d0.png

第一次ioctl,fence syncpoint command

第二次ioctl,绘制命令

第三次ioctl,创建gpu fence

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

e89505db8b99de0a308443127ce73f4e.png

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/clo/la/platform/vendor/qcom/opensource/graphics-kernel/-/tree/gfx-kernel.lnx.14.0.r6-rel?ref_type=heads

df7ceb876a1d0ee061666309492b4102.jpeg

Linux内存管理中锁使用分析及典型优化案例总结

Binder驱动中的流程详解

2024年Arm最新处理器架构分析——X925和A725

cd3907258efc384917e0348e990563e2.gif

长按关注内核工匠微信

Linux内核黑科技| 技术文章| 精选教程  

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OPPO内核工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值