Mutter实现分析:Atomic Modesetting
文章目录
首先注意一个名字上的区分,一开始看代码的人可能会对命名有疑惑。在Jonas的设计中,整个KMS相关代码分为两个上下文:main和impl。main上下文就是mutter运行的上下文,即GMainLoop所在线程,而impl上下文虽然目前也运行在GMainLoop所在线程,但是从设计上就是KMS操作运行的上下文,后期可能迁移到独立线程上。因此,所有命名为Impl的类都是注册在Impl
上下文的。
MetaKms
该对象为简单容器,是Jonas为了实现transactional modesetting对KMS进行的抽象。transactional KMS最终的目的有两个:
- 使mutter可以利用Atomic Modesetting API,更加充分高效的利用硬件特性,消除modesetting的中间状态
- 使KMS API的调用主体可以为独立线程,本质上是允许KMS API的异步调用,即调用时
首先明确其抽象的目标,即transactional modesetting
,transactional
这个词与数据库中的意义一致。即对于KMS设备进行的modesetting是原子性的,没有中间状态,要么成功要么失败,失败时不会进行任何状态更新。因此MetaKms
使用MetaKmsUpdate
抽象一个transaction
。这套抽象目前使用普通的KMS API实现,但是只要接口移植完毕,那么即可直接调用Atomic KMS实现对应的操作。因此MetaKms
作为容器也有选择后端实现的功能,但是当前的实现都为普通KMS。
创建MetaKms
对象时,需要传入一个MetaBackend
,meta_kms_new
函数会自行创建一个MetaKmsImpl
对象:
|
|
随后会从后端中取出MetaUdev
对象,然后注册两个信号的处理函数,可以看出这是为了处理GPU热插拔事件:
|
|
MetaKmsUpdate
这个结构体是transactional KMS API设计的核心。其核心思想是:
- 对显示控制器的操作(即modesetting)transaction化
- 每个
MetaKmsUpdate
即代表一个transaction,操作时将所有操作缓存到MetaKmsUpdate
中 - 只有commit操作时,原先cache的操作才会真正应用到显示控制器中
因此,MetaKms
会保存一个当前的MetaKmsUpdate
,所有需要进行的KMS操作都需要缓存到其中,然后在合适的时机进行commit。
|
|
在明白上述原理后,这个对象的表示也就比较容易理解了。首先is_sealed
表示这个对象是否已经被封装好,即是否能够继续向其中添加操作。随后紧跟的是多个List,分别可以保存特定的一类操作。
MetaKms
提供了多个接口,用于创建、获取,并commit一个MetaKmsUpdate
:
|
|
因此,MetaKmsImpl
的实现的功能就显而易见了,即负责真正执行一个MetaKmsUpdate
。
MetaKmsImplDevice
为了实现Atomic Modesetting
,首先需要抽象一个KMS
支持的操作。MetaKmsImplDevice
抽象出了这个接口:
|
|
并由MetaKmsImplDeviceSimple
和MetaKmsImplDeviceAtomic
实现。显然handle_page_flip_callback
和discard_pending_page_flips
与process
被直接当作虚函数导出。另一个非常重要的操作是dispatch
,实现如下:
|
|
可以看出,首先使用setup_drm_event_context
回调函数设置好drmEventContext
,本质上就是page flip
的处理函数,在随后的drmHandleEvent
中,如果出现PageFlip
事件,则会调用MetaKmsImplDeviceXXX
中设置好的Page flip
处理函数进行处理。
除此之外比较有意思的是这个类保存了所有的DRM设备属性,也就是connector
、CRTC
等KMS接口导出的对象:
|
|
可以在该类型的初始化函数中得到对应的初始化方式,且该类型的初始化函数中还隐藏了比较重要的信息:一个GSource
,如下:
|
|
本质上是在GMainContext中注册了一个GSource
,以DRM的文件描述符的可读状态为事件源,以传入的kms_event_dispatch_in_impl
函数作为dispatch
回调函数。
|
|
而在该函数中调用了meta_kms_impl_device_dispatch
函数,除此之外别无它地。上述即为Page Flip
的处理机制,从这里看,MetaKmsImplDevice
一旦创建,即会注册Page Flip
的处理函数,并进行处理。
MetaKmsImplDeviceSimple
~~这个是目前mutter唯一实现的MetaKmsImpl
,~~实质上是用普通KMS(即非atomic)操作实现transacational KMS的接口。可以预见,后续mutter项目会使用Atomic KMS接口实现一个MetaKmsImplAtomic
,实现事实意义上的atomic KMS支持。
这里分析MetaKmsImplSimple
几个比较复杂的操作。首先明确入口为meta_kms_impl_simple_process_update
函数,其内部连续处理缓存在MetaKmsUpdate
中的KMS操作。简单的操作只是简单的wrap一个drmMode*
函数,可以略过,直接分析最复杂的PageFlip处理。先看其缓存在MetaKmsUpdate
中的形式:
|
|
先理解drmModePageFlip
函数,该函数与drmModeSetCrtc
函数非常相似,只不过其只有在VBlank事件来了时才生效,也就是告诉DRM当VBlank事件到来时,更新framebuffer。我们可以将函数的flags参数传入DRM_MODE_PAGE_FLIP_EVENT
,此时每当PageFlip发生时,都会产生一个PageFlip事件。事件通过drm文件描述符变为可读告知用户态,且我们可以使用drmHandleEvent
处理事件。注意drmModePageFlip
的最后一个参数为user_data
,一个用户态指针,每当PageFlip事件生成时,对应的指针就会跟着传入drmHandleEvent
函数,所以我们可以通过该指针辨别PageFlip。
接下来看process_page_flip
函数,该函数是PageFlip处理的核心。首先可以知道MetaKmsPageFlip
中提供了一个让调用方自己写PageFlip处理函数的机制,即对应于custom_page_flip_func
函数指针和其对应的数据。当他们存在时,就通过它们进行PageFlip的处理,反之则使用标准的处理流程,即调用drmModePageFlip
函数:
|
|
page_flip_data
究竟记录了什么,目前不需要知道,到时候在分析mutter frame调度器的时候进行分析,目前只看到:
|
|
注意drmModePageFlip
函数在一个VBlank期间只能调用一次,在已经调用过一次的情况下再继续调用的话会返回-EBUSY
。可以看到process_page_flip
函数在该情况下实现了一个缓存机制,将返回-EBUSY
的调用重新调度到下一次VBlank。这里只需要看到它是将多余的PageFlip计算出一个时间间隔,并缓存到了一张表内,其余细节在分析frame调度时再分析。
MetaKmsImplDeviceAtomic
半年之后jonas终于把Atomic Modesetting
的支持做完了,不过熟悉GNOME的人都应该懂这个特性起码review半年以上,也就是mutter 40都不一定可以合入。前面提到Atomic Modesetting
的支持就差最后一个Buffer
,以前我对于DRM的理解没有那么深刻,结果误解了这个Buffer
的含义。这个Buffer
实质上是指Buffer
更新,即设置CRTC的scanout。因此,需要定义一个PlaneAssignment
用于抽象这个操作。
Atomic Modesetting
的实质是使用Atomic API
替换掉原有的legacy接口,需要重新实现一个MetaKmsImplDevice
,也就是MetaKmsImplDeviceAtomic
。该函数的核心操作就是process_update
,在进行这个操作时,首先自己是否已经初始化,如果没有初始化,则进行以下操作:
- MetaKmsDevice里管理的所有connector的
CRTC_ID
属性设置为0 - MetaKmsDevice里管理的所有plane的
CRTC_ID
与FB_ID
设置为0
随后则进行update操作,如下:
- 依次处理
MetaKmsUpdate
中保存的信息,本质上就是更改对应Object里的属性 - 然后commit
MetaKmsDevice
没意思。
有意思了,原先没有看到PageFlip事件的处理在MetaKmsDevice
里,单纯以为其是drm设备文件描述符的容器。漏掉了最重要的函数meta_kms_device_dispatch_sync
。先分析通用实现,然后再看MetaKmsImplDevice
。
可以看到函数首先调用MetaKmsImpl
的meta_kms_impl_dispatch_idle
函数,后续调用了MetaKmsImplDevice
实现的dispatch
函数,中间穿插了meta_kms_flush_callback
的调用。
MetaCursorRenderer
来看个比较独立也看起来比较简单的东西吧:鼠标光标绘制。首先看通用的抽象即MetaCursorRender
。首先明确光标绘制的两种方式,硬件光标和软件光标。首先说软件光标,这个比较好理解,即直接在屏幕上进行光标的绘制,与绘制窗口无异。硬件光标则不同,目前的显示控制器一般都实现了cursor图层(plane),即屏幕的主图层和cursor图层是相互独立的,只有在scanout的时候才由硬件进行叠加操作。cursor图层一般支持绘制一块比较小的bitmap到屏幕上,而该bimtap可以在屏幕上快速移动。但是硬件实现的cursor图层是有局限性的,特定情况下并不能启用,所以mutter需要管理硬件cursor的启用。
事实上通过简单思考即抽象出Cursor管理需要向外提供的接口:
- 光标(相对屏幕)位置的设置和获取
- 硬件光标的启用与停用管理
- 光标位图设置
- 光标更新(绘制)
mutter使用MetaCursorRender
抽象cursor的绘制器,而使用MetaCursorSprite
描述光标图片(注意,这个可以是动画)。为了管理硬件cursor的启用条件,定义了MetaHwCursorInhibitor
接口,实现了该接口的对象可以提供一个函数用于确定是否可以启用硬件cursor。
可以看到MetaCursorRender
向外提供的接口如下:
- set_curosr/get_cursor,更换和获取`MetaCursorSprite(并绘制)
- set_position/get_position,更该和获取光标位置(并绘制)
- force_update,强制重新绘制光标
- {is,add,remove}_hw_cursor_inhibitor,向其注册删除
MetaHwCursorInhibitor
先来看一下绘制光标的抽象接口:
|
|
这是meta_cursor_renderer_update_cursor
中的片段。其大致逻辑就是,如果子类实现的update_cursor
函数没有处理光标绘制,则交由MetaCursorRenderer
实现的软件绘制器进行光标绘制。
MetaDrmBuffer
Buffer管理应该是transacational KMS最后一个还没有做的部分了。很明显一个MetaDrmBuffer
对应一个DRM Buffer,且其只提供一个虚函数:get_fb_id
。现在看到这个类有三个实现:
- DUMB,这个就是那个几乎所有DRM驱动都支持的DUMB Buffer,不支持GPU加速,不看
- GBM,这个应该是本地GPU通过libgbm分配出来的Buffer
- Import,这个是其他GPU通过prime接口导出的Buffer,这个也暂时不看
MetaDrmBuffer
是一个抽象类,由于上面的DUMB
和Import
类型的Buffer都没有进一步分析的必要,所以这里主要分析MetaDrmBufferGbm
。首先明确这个Buffer对应的是DRM中的Buffer概念,且是使用libgbm分配而出的Buffer,所以这一套API与EGL和DRM高度相关。MetaDrmBufferGbm
提供了两个创建方法:
- new_lock_front。该方法对应EGL中的
lock front buffer
操作,本质是锁定一个gbm_surface中的一个后端Buffer然后将其返回供用户操作。一旦完成操作则需要将该Buffer返还gbm_surface并解除锁定。 - new_take。直接将一个
gbm_bo
包装成一个MetaDrmBufferGbm
。
无论使用哪个方法进行创建,都需要使用meta_gpu_kms_add_fb
将整个buffer注册到DRM中,并得到一个framebuffer id
并保存起来。
MetaRendererNative
原来一直没看懂这个东西存在的必要,现在明白了。MetaRenderer
的功能是维护一个MetaRendererView
列表,同时提供创建CoglRenderer
的功能。为了理解它,首先明确整个Mutter
中ClutterStage
是只有一个的,也就是哪怕有多个显示器,整个Session中也是公用一个ClutterStage
的。在Clutter
将整个桌面渲染出来之后,需要将其进行拆分,并对应到所有的显示器中。对于Native后端来说,上述操作就是多个CRTC的scanout设置到同一个Buffer的不同区域中。真正负责将输出渲染(Render)到逻辑显示器上的就是MetaRandererView
,而MetaRenderer
则负责管理MetaRenderer
上的显示器。
MetaRendererViewNative
MetaRendererView
是ClutterStageViewCogl
的子类,提供了有限的接口,大部分需要其子类实现:
|
|
简单包含了两个属性:CRTC对应的ID以及显示器变换。
TODO: 涉及到COGL,以及ClutterStageView,后面再看
文章作者 crab2313
上次更新 2021-03-27 (ee1692e)