本文是我对理解Mutter运行机制以及Linux的DRM子系统的一次尝试。之前的尝试似乎由于种种原因没有进行下去,而我最近深受GNOME下各种HIDPI问题的困扰,所以决定坚持下去,达到可以为GNOME开发特性的水准。在以前的尝试中,总结出比较重要的几点列举如下,做为之后的指导方向:

  • 对于GPU的功能一定要准确的划分清楚。对于Mutter使用了GPU哪部分的功能也要有清晰的认识。Mutter本质上是利用了Linux内核抽象出来的DRM接口,控制Display Controller将图输出到屏幕上。而输出的内容则由各个窗口的内容合成出来,故而被称作Compositor,合成的过程则利用了GPU的图形pipeline,但也仅仅是非常简单的利用,并不是什么复杂的3D程序。因此分析Mutter代码时,一定要重点关注窗口合成的逻辑,而非具体GPU的实现。
  • Mutter事实上实现了两个后端,一个被称为native,即DRM+wayland那一套,另一个为x11。X11是没有分析的必要的,后续也会淘汰,未来是Wayland的天下。

由于本文中夹杂着以前遗留下来的文档,故而逻辑不是很清晰,阅读时需要顺着自己的逻辑。以前的多次尝试都是直接从Mutter入手开始阅读,但在最后都由于卡在了自己没有涉猎的知识点上,导致流产,因此本次直接从Mutter的底层依赖库入手进行学习。因此,这次的顺序应该为:

  • Clutter。Clutter是gtk3时代的产物,本质上是因为gtk3缺乏GPU加速能力(即无法利用GPU渲染)而被gnome开发者创造出来的一套替代方案,是与gtk3平行的toolkit。因此clutter被gnome开发者拿来写compositor,本质上是因为其具备基本的3D渲染能力。
  • EGL。EGL是通用的OpenGL系渲染API的Context创建接口,Mutter利用其创建基本的渲染Context。
  • libinput。与wayland同期开发出来的用户输入处理库。

问题

希望本文档结束时,我可以回答以下问题:

  • MetaWindow是如何绘制的,如何重新绘制
  • clutter的长宽坐标是如何传入的
  • 整个mutter的显示架构是什么样的
  • HIDPI是如何实现的

感兴趣的特性

  • void GPU实现。本质上允许无mutter运行在无GPU模式下,这使得mutter可以动态卸载唯一的GPU,让内核卸载其驱动,将设备用以GPU穿透。实现单GPU不重启进行GPU穿透。
  • 根据EDID自动决定默认缩放比例。
  • Atomic Modesetting

Clutter

看了一部分Clutter的文档,感觉对于Mutter来说,并不需要理解许多多余的概念。首先是ClutterActor,即一个2D元素,可以在3D空间中被变换,即移动,拉伸,旋转。而ClutterActor可以组成树型结构,即构成绘制树,child在parent之上进行绘制。ClutterStage是总的ClutterActor也是一个ClutterCroup,可以放多个ClutterActor。

Mutter

从入口core/mutter.c看起。首先明确一点,mutter是一个支持插件的设计,gnome-shell实际上是mutter的一个插件。gnome3的桌面环境并没有mutter进程,只有gnome-shell进程。core/mutter.c实际上是一个独立的可执行程序,这里我直接从这里入手,可以先避开gnome-shell的代码。

可以看到这个文件很短,除掉命令行参数处理就只剩下几行:

1
2
3
4
5
6
  if (plugin)
    meta_plugin_manager_load (plugin);

  meta_init ();
  meta_register_with_session ();
  return meta_run ();

其中plugin默认为libdefault,这里先不理会。所以入手点为meta_init函数,这个函数几乎是所有组件初始化的入口,目前只需要知道它初始化backend和事件循环。

来看meta_run,可以看到mutter是一个事件驱动的软件,其核心操作即为初始化一个事件循环,然后进行事件循环直至退出。

1
2
3
4
  if (!meta_display_open ())
    meta_exit (META_EXIT_ERROR);

  g_main_loop_run (meta_main_loop);

至此,我们进入了最主要的入口meta_display_open函数。该函数目的是初始化一个MetaDisplay,该对象是mutter最核心的对象,表示整个mutter管理的显示,注意mutter支持多屏幕(Screen)。作为一个GObject,MetaDisplay的_init函数里是空的,也就是说我们只需要关注meta_display_open

MetaDisplay

MetaDisplay原先是对XDisplay的抽象,从Wayland支持后被赋予了更加抽象的意义。MetaDisplay表示整个Mutter管理的显示会话。我们从其创建函数meta_display_opne开始分析。

meta_display_open

函数首先获取MetaBackend,该对象是单例对象,无论是哪里设置的,我们只考虑Native后端,而不考虑X11。函数首先通过g_object_new创建一个MetaDisplay然后对其进行初始化。MetaDisplay中记录的大量的配置,用以决定其行为,而默认的配置我们就不进行进一步分析了。

TODO

MetaCompositor

这是一个抽象类,我们只关心其MetaComositorWayland子类。从名字上看来这个类是Mutter对其Compositor功能的抽象,虽然Wayland下并没有独立的Compositor组件。其中MetaCompositor管理的窗口实质上都是MetaWindowActor,这里的Actor实际上就是ClutterActor一个很直接的概念上的迁移。MetaWindowActor的结构如下:

1
2
3
4
5
6
7
8
9
 MetaWindowActor
  ↳ MetaSurfaceActor (surface)
     ↳ MetaShapedTexture
     ↳ MetaSurfaceActor (subsurface)
        ↳ MetaShapedTexture
        ↳ MetaSurfaceActor (sub-subsurface)
           ↳ MetaShapedTexture
     ↳ MetaSurfaceActor (subsurface)
        ↳ MetaShapedTextur

注意Wayland的Sub Surface特性,这里的MetaSurfaceActor抽象了这一特性。这里的MetaShapedTexture实质上是Surface的内容,Mutter中将Surface的内容与其形状区分了开来。所以Compositor的任务实质上就是管理这样的一个树形结构,并将其合成,并渲染成一张位图。

meta_compositor_add_window

该函数如其名字所述,就是将一个MetaWindow加入到Compositor的管理中来。函数首先根据传入的MetaWindow的类型将其分类为X11或者Wayland,后续根据其类型创建MetaWindowActorX11或者MetaWindowActorWayland

1
2
3
4
  window_actor = g_object_new (window_actor_type,
                               "meta-window", window,
                               "show-on-set-parent", FALSE,
                               NULL);

作为一个stack-based的窗口管理器,Mutter中存在layer的概念,这很好理解,想想我们常见的置顶窗口功能,这就是将窗口设置到top layer的行为。Mutter中的layer定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * MetaStackLayer:
 * @META_LAYER_DESKTOP: Desktop layer
 * @META_LAYER_BOTTOM: Bottom layer
 * @META_LAYER_NORMAL: Normal layer
 * @META_LAYER_TOP: Top layer
 * @META_LAYER_DOCK: Dock layer
 * @META_LAYER_OVERRIDE_REDIRECT: Override-redirect layer
 * @META_LAYER_LAST: Marks the end of the #MetaStackLayer enumeration
 *
 * Layers a window can be in.
 * These MUST be in the order of stacking.
 */

但是Compositor只会将META_LAYER_OVERRIDE_REDIRECT进行特殊对待,为其分配一个单独的Group

1
2
3
4
  if (window->layer == META_LAYER_OVERRIDE_REDIRECT)
    window_group = priv->top_window_group;
  else
    window_group = priv->window_group;
1
2
3
4
  // struct _MetaCompositorPrivate
  ClutterActor *window_group;
  ClutterActor *top_window_group;
  ClutterActor *feedback_group;

注意这里的GroupClutter中的ClutterGroup概念,本质上就是可以放ClutterActor的容器,最常见的就是ClutterStage。随后所有创建的MetaWindowActor会被放到Compositor内维护的windows列表中,最后调用sync_actor_stackingsync_actor_stacking函数本质上是维护真个Compositor的stack窗口管理的性质,后续在分析meta_compositor_sync_stack函数时仔细分析。

meta_compositor_sync_stack

现在来看sync_actor_stacking函数。本质上,windows是一个链表,保存着所有的MetaWindowsActor,也就是所有的窗口。我们知道Stack-based的窗口管理器有着深度这一概念,也就是窗口在Stack中的位置,而windows链表本质上就是反映了这个位置信息,windows的第一个元素就是Stack中最底下的窗口。而事实上合成整个窗口要利用Clutter实现,因此每个窗口对应一个ClutterActor,同时背景也要一同绘制,因此MetaBackGroundActor也算一个ClutterActor。这些ClutterActor都放置在window_group这个ClutterGroup中。sync_actor_stacking函数的功能实质上就是同步windows链表中窗口的深浅顺序与window_groupClutterActor的深浅顺序。函数首先检测windowswindow_group的顺序是否同步,一旦不同步,则调用clutter_actor_set_child_below_sibling一个一个地重建整个window_groupClutterActor的顺序。

meta_compositor_{manage,unmange}

manage函数的目的是让对应的Display被Compositor接管,内部调用了meta_compositor_do_manage函数。函数首先连接了ClutterStage上的presented信号:

1
2
3
4
  priv->stage_presented_id =
    g_signal_connect (stage, "presented",
                      G_CALLBACK (on_presented),
                      compositor);

随后创建三个MetaWindowGroup,并将其加入到ClutterStage中:

1
2
3
4
5
6
  priv->window_group = meta_window_group_new (display);
  priv->top_window_group = meta_window_group_new (display);
  priv->feedback_group = meta_window_group_new (display);
  clutter_actor_add_child (stage, priv->window_group);
  clutter_actor_add_child (stage, priv->top_window_group);
  clutter_actor_add_child (stage, priv->feedback_group);

最后调用子类实现的manage虚函数。MetaCompositoative没有实现这个虚函数。

meta_compositor_queue_frame_draw

函数的直接参数是一个MetaWindow,但是前面提到了一个MetaWindow是直接与其对应的MetaWindowActor绑定的。因此函数获取对应的MetaWindowActor后直接调用meta_window_actor_queue_frame_draw。对应于MetaWindowActorWayland那就是什么都不做,也就是说这个函数在Wayland窗口上是空的。

MetaStage

MetaStage实质上是ClutterStage的子类。本质上代表了多组需要显示在屏幕上的Actor。meta_state_new负责创建一个MetaStage,创建时,监听MetaMonitorManager上的power-save-mode-changed信号。除此之外,实现了ClutterState类提供的多个虚函数:

1
2
3
4
5
6
  actor_class->paint = meta_stage_paint;

  stage_class->activate = meta_stage_activate;
  stage_class->deactivate = meta_stage_deactivate;
  stage_class->before_paint = meta_stage_before_paint;
  stage_class->paint_view = meta_stage_paint_view

meta_stage_paint

MetaStage作为ClutterActor的子类,其paint虚函数是被重写过的,即meta_stage_paint函数。这个函数实质上是给MetaStage的绘制操作后面添加了信号处理与鼠标绘制。可以看到每当paint回调函数调用完毕之后,就会触发MetaStage上的ACTORS_PAINTED信号,然后检查paint_contexts中的标志位,如果发现没有NO_CURSOR则会重新绘制所有相关联的MetaOverlay

1
2
3
  if (!(clutter_paint_context_get_paint_flags (paint_context) &
        CLUTTER_PAINT_FLAG_NO_CURSORS))
    g_list_foreach (stage->overlays, (GFunc) meta_overlay_paint, paint_context);

注意这里默认所有MetaOverlay都表示鼠标了。

MetaOverlay

MetaOverlay从概念上讲应该是一个Overlay区域,这个名词一般是指一个plane上的叠加层。MetaOverlay与一个MetaStage相关联,表示这个MetaStage显示区域上的一个叠加层,其定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct _MetaOverlay
{
  MetaStage *stage;

  gboolean is_visible;

  CoglPipeline *pipeline;
  CoglTexture *texture;

  graphene_rect_t current_rect;
  graphene_rect_t previous_rect;
  gboolean previous_is_valid;
};

各个字段的意思都比较直观,用户可以通过meta_overlay_set设置填充这个Overlay使用的材质,并设置其显示位置。meta_overlay_paint函数用于绘制OverlayMetaStage上来。

光标Overlay绘制

熟悉DRM架构的人一定知道DRM提供了三种plane,其中一种就是Cursor Plane,用于支持硬件光标。这个功能本质上是实现了一个额外的图层绘制光标。Mutter中将其抽象为一个Overlay,可以看到几个用于操作这个Overlay的接口:

1
2
3
4
5
6
7
8
MetaOverlay      *meta_stage_create_cursor_overlay   (MetaStage   *stage);
void              meta_stage_remove_cursor_overlay   (MetaStage   *stage,
						      MetaOverlay *overlay);

void              meta_stage_update_cursor_overlay   (MetaStage       *stage,
                                                      MetaOverlay     *overlay,
                                                      CoglTexture     *texture,
                                                      graphene_rect_t *rect)

MetaWindowActor

MetaWindowActorClutterActor的子类,其基本目的是将一个MetaWindowClutterActor绑定起来,如前面所述。这里只分析其对应Wayland的子类MetaWindowActorWayland

MetaSurfaceActor

MetaSurfaceActor也是ClutterActor的子类,其目的是表示一个surface,类似于Waylandsubsurface的概念,是一个嵌套的树形结构,允许在Surface上添加子Surface。这是由于Surface可能有着不同的像素格式,比如父Surface是RGB格式的,而子SurfaceYUV格式的,这个问题在硬件解码时比较常见。有了Sub Surface特性,就不需要应用程序自己对像素格式进行转换,也少了一层拷贝,只需要将硬件解码出来的位图作为一个Sub Surface的内容即可将其显示在窗口上。

该类的Private数据定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct _MetaSurfaceActorPrivate
{
  MetaShapedTexture *texture;

  cairo_region_t *input_region;

  /* MetaCullable regions, see that documentation for more details */
  cairo_region_t *unobscured_region;

  /* Freeze/thaw accounting */
  cairo_region_t *pending_damage;
  guint frozen : 1;
} MetaSurfaceActorPrivate;

meta_surface_actor_update_area

TODO

MetaShapedTexture

MetaShapedTexture实现了ClutterContent接口,其目的是为一个ClutterActor绘制内容。ClutterContent接口提供的回调函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct ClutterContentIface {
  gboolean      (* get_preferred_size)  (ClutterContent   *content,
                                         gfloat           *width,
                                         gfloat           *height);
  void          (* paint_content)       (ClutterContent   *content,
                                         ClutterActor     *actor,
                                         ClutterPaintNode *node);

  void          (* attached)            (ClutterContent   *content,
                                         ClutterActor     *actor);
  void          (* detached)            (ClutterContent   *content,
                                         ClutterActor     *actor);

  void          (* invalidate)          (ClutterContent   *content);
};

具体到MetaShapedTexture,则是将一个CoglTexture表示的材质绘制到ClutterActor上。

meta_shaped_texture_update_area

MetaMonitor

这个类型很明显是对显示器的抽象。事实上,Mutter中的MetaMonitor分为两个子类:MetaMonitorTiledMetaMonitorNormal,这里只分析MetaMonitorNormal。Mutter中定义了一系列描述显示器相关信息与设置的类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct _MetaMonitorModeSpec
{
  int width;
  int height;
  float refresh_rate;
  MetaCrtcModeFlag flags;
} MetaMonitorModeSpec;

typedef struct _MetaMonitorCrtcMode
{
  MetaOutput *output;
  MetaCrtcMode *crtc_mode;
} MetaMonitorCrtcMode;

MetaMonitor从设计上就需要其子类实现一系列虚函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void
meta_monitor_normal_class_init (MetaMonitorNormalClass *klass)
{
  MetaMonitorClass *monitor_class = META_MONITOR_CLASS (klass);

  monitor_class->get_main_output = meta_monitor_normal_get_main_output;
  monitor_class->derive_layout = meta_monitor_normal_derive_layout;
  monitor_class->calculate_crtc_pos = meta_monitor_normal_calculate_crtc_pos;
  monitor_class->get_suggested_position = meta_monitor_normal_get_suggested_position;
}

从代码中来看,MetaMonitor记录了整个显示器的状态。包括:

  • 这个显示器属于哪一个GPU
  • 这个显示的包含的output,这里的MetaOutput是抽象DRM输出口的概念,这样可以实现复制屏
  • 显示器的mode,优先的mode,当前的mode
  • 显示器参数(复制于第一个output)
  • 窗口系统ID

MetaMonitorManager

这个类本质上就是你在gnome-control-center里配置显示器选项时的直接接口。事实上,gnome-control-center是通过dbus接口与系统组件进行通信的,为了让系统中的进程可以配置显示器属性,MetaMonitorManager中实现了org.gnome.Mutter.DisplayConfig接口。

由于存在外部接口,对这个类型的分析就从dbus接口开始。在创建对象之时,会创建dbus接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  manager->dbus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
                                          "org.gnome.Mutter.DisplayConfig",
                                          G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
                                          (meta_get_replace_current_wm () ?
                                           G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
                                          on_bus_acquired,
                                          on_name_acquired,
                                          on_name_lost,
                                          g_object_ref (manager),
                                          g_object_unref);

接口相关代码是由gdbus-codegen生成的,我们只需要知道其基本模型是gdbus-codegen帮助生成一个GObject,然后我们监听其上特定的信号,即可知道其他人调用了特定的method。作为典型案例,分析handle-apply-monitors-config信号的处理,即ApplyMonitorsConfig方法的处理函数。

ApplyMonitorsConfig

函数首先检查serial的值是否与当前MetaMonitorManager保存的serial是否一致,如果不一致,则认为调用方设置的参数不合法。这个操作是因为显示器的属性是动态改变的,如果配置发生变化,则MetaMonitorManager会自增自身保存的serial,与此同时通过GetResources获取显示器属性时,也会拿到一个serial。通过serial的比对,可以确定调用方手中保存的属性是否合法。忽略掉中间复杂的参数处理,函数最后生成一个MetaMonitorsConfig对象,并调用meta_monitor_manager_apply_monitors_config,最后调用到子类实现的apply_monitors_config虚函数。由于这里分析的是Native后端,则对应子类为MetaMonitorManagerKms

MetaMonitorManagerKms

LayoutMode

1
2
3
4
5
6
/* Equivalent to the 'layout-mode' enum in org.gnome.Mutter.DisplayConfig */
typedef enum _MetaLogicalMonitorLayoutMode
{
  META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL = 1,
  META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL = 2
} MetaLogicalMonitorLayoutMode;

只要系统中开启的fractional scaling的特性,默认使用Logical模式,否则为Physical模式。在Physical模式下,一个MetaMonitor的维度使用显示器分辨率,而Logical模式下使用显示器分辨率除以缩放系数作为维度。详见org.gnome.Mutter.DisplayConfig.xml文件中相关的描述。

calculate_supported_scales

该函数计算一个显示器分辨率支持的缩放系数。这个函数实现的基本思路是枚举缩放系数,然后进行修正。从代码中可以看到枚举步长,最大值与最小值:

1
2
3
4
5
6
#define SCALE_FACTORS_PER_INTEGER 4
#define SCALE_FACTORS_STEPS (1.0 / (float) SCALE_FACTORS_PER_INTEGER)
#define MINIMUM_SCALE_FACTOR 1.0f
#define MAXIMUM_SCALE_FACTOR 4.0f
#define MINIMUM_LOGICAL_AREA (800 * 480)
#define MAXIMUM_REFRESH_RATE_DIFF 0.001

即函数以0.25以步长,从1.0开始,依次1.0, 1.25, 1.5, 1.75 …. 直到4.0。经过枚举之后,枚举出来的缩放系数并不一定能够工作正常,需要进一步修正。首先明确终极目标,或者说什么情况下缩放系数才合法:存在分辨率w x h使得:$ a \times w = mode.w \and a \times h = mode.h $,其中w与h为整数。所以函数使用了一个算法,首先计算floor(mode.w / a),这是一个整数,然后以这个整数为中心依次向两边推开,然后验证mode.h / a是否也是一个整数。经过这样的枚举,最终得到对一个分辨率可以使用的缩放系数。

calculate_monitor_mode_scale

本质上调用MetaMonitor上的calculate_mode_scale计算一个MetaMonitorMode应该使用的缩放系数。实现方式比较naive,没有照顾到HiDPI屏幕,后面我可能在这里开刀,增加计算HiDPI屏幕缩放系数的相关代码。函数首先检查有没有设置全局缩放系数,如果设置了,就使用它。否则根据从MetaMonitor中读出显示器的分辨率和物理尺寸(毫米),然后计算出屏幕DPI,如果超过一定限度,则将缩放系数设置成2.0。

如果我要实现HiDPI屏幕的支持,计算出对应于HiDPI屏幕正确的缩放系数,那么我感觉需要进行如下步骤:

  • 首先判断是否支持fractional scaling,不支持的话直接跳过
  • 随后计算屏幕DPI
  • 枚举所有支持的缩放系数,然后检查经过缩放后的DPI是否会落在一个合理范围内
  • 第一个使DPI落在合理范围内的缩放系数即使我们想要的缩放系数

MetaCullable

最近碰到了MetaCullabler相关的问题,详见#1500MetaCullable是一个相对于Clutter的优化,本质上是在将ClutterActor提交给opengl渲染器进行渲染前,提前将一些不合适的ClutterActor去掉,让其不参与渲染。这样做的意义源自目前PC上主要使用的都是独立显卡,一般为独立显存,进行渲染时,需要先将材质上传到显存中,而显存拷贝时的带宽是有瓶颈的。如果将所有的ClutterActor提交渲染,则会占用大量的显存带宽,而没有任何实际意义,因为ClutterActor形成的树在Mutter应用场景下大部分都是堆叠的2D平面图片,没有什么特效(3D变换)。也就是说,可以通过一个简单的算法让ClutterActor决定是否将自己Cull掉,即从最终进行渲染的ClutterActor中去掉。

MetaCullable是一个GInterface,很明显MetaWindowActor需要实现该Interface。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct _MetaCullableInterface
{
  GTypeInterface g_iface;

  void (* cull_out)      (MetaCullable   *cullable,
                          cairo_region_t *unobscured_region,
                          cairo_region_t *clip_region);
  gboolean (* is_untransformed) (MetaCullable *cullable);
  void (* reset_culling) (MetaCullable  *cullable);
};

在Mutter中,很多实现该接口的类型,其实现的cull_out函数都是直接调用meta_cullable_cull_out_children实现的。

最初的调用入口

在代码中搜索meta_cullable_cull_out,可以找到该函数唯一的外部调用,即在meta_window_group_paint中。我们知道MetaWindowGroup是一个ClutterActor的子类,表示当前Mutter虚拟显示区域,它的children就是各个MetaWindowActor。可以看到meta_cullable_cull_out最初的两个参数由如下方式生成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  visible_rect.x = visible_rect.y = 0;
  visible_rect.width = clutter_actor_get_width (CLUTTER_ACTOR (stage));
  visible_rect.height = clutter_actor_get_height (CLUTTER_ACTOR (stage));

  unobscured_region = cairo_region_create_rectangle (&visible_rect);

  /* Get the clipped redraw bounds so that we can avoid painting shadows on
   * windows that don't need to be painted in this frame. In the case of a
   * multihead setup with mismatched monitor sizes, we could intersect this
   * with an accurate union of the monitors to avoid painting shadows that are
   * visible only in the holes. */
  clip_region = cairo_region_copy (redraw_clip);

  cairo_region_translate (clip_region, -paint_x_origin, -paint_y_origin);

其中obscured_region应该是MetaWindowGroup的整个大小,而clip_region一般情况下就是redraw_clip

meta_background_actor_cull_out

该函数为MetaBackgroundActor实现的cull_out回调函数。函数简单检查是否有content,如果有则将cull_out让其代为执行:

1
2
3
4
5
6
  if (!self->content)
    return;

  meta_background_content_cull_out (self->content,
                                    unobscured_region,
                                    clip_region);

meta_background_content_cull_out仅仅是将这个参数保存到MetaBackgroundContent中,并不进行额外的操作。

meta_surface_actor_cull_out

TODO

meta_cullable_cull_out_children

该函数的完成的工作非常简单,仅仅是遍历当前ClutterActor所有的子ClutterActor,然后在他们上面继续调用meta_cullable_cull_out函数。当然这只是粗略的描述,有一些细节还是要看的。在遍历过程中,函数首先检查该子ClutterActor(子类)是否实现了MetaCullable接口,如果没有则跳过该子ClutterActor

1
2
      if (!META_IS_CULLABLE (child))
        continue;

允许culling的前提是:

  • 传入的两个参数unobscured_regionclip_region都不为空
  • ClutterActor的没有特效(effective),没有进行过变换

一旦进行需要进行culling,则将两个参数unobscured_regionclip_region的坐标转换为子ClutterActor的,然后调用meta_cullable_cull_out

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
      if (needs_culling)
        {
          clutter_actor_get_position (child, &x, &y);

          /* Temporarily move to the coordinate system of the actor */
          cairo_region_translate (unobscured_region, - x, - y);
          cairo_region_translate (clip_region, - x, - y);

          meta_cullable_cull_out (META_CULLABLE (child), unobscured_region, clip_region);

          cairo_region_translate (unobscured_region, x, y);
          cairo_region_translate (clip_region, x, y);
        }
      else
        {
          meta_cullable_cull_out (META_CULLABLE (child), NULL, NULL);
        }

反之也会调用meta_cullable_cull_out,但是传入的两个参数为空。