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)