deepin-wm的动态背景模糊实现
这篇文档的目的是对深度窗口管理器动态背景模糊实现技术做一个大致的描述。
简介
kwin从很早开始就支持类似windows窗口的菜单栏毛玻璃的效果。其实现机制在kwin良好的插件架构下相对容易,然而要在基于mutter的窗口管理器上实现同样的效果却不容易。这主要在于mutter依赖的技术之一clutter的设计思路跟kwin完全不同。两者的共同点都是基于OpenGL实现(当然,kwin有多个后端,可以不依赖OpenGL,但是其他后端不能开启很多依赖硬件支持的特效)。
kwin对OpenGL没有做过度封装,因此可以直接使用OpenGL的特性。而clutter依赖cogl这个OpenGL封装库,为了提供最大的兼容性,cogl暴露的功能相对有限,尤其是对于需要OpenGL扩展的情况。
kwin的特效是全局的,在每一帧绘制中,所有激活的特效按顺序对当前的draw framebuffer内容进行处理,因此kwin里大量的上下文信息对于所有特效来说是共享的,并且容易访问。而clutter的设计是以Actor为单元来进行处理的的。每个窗口,每个可视元素对应一个Actor,因而有针对Actor的Effects(等价于窗口特效),而且Effect只能操作Actor的内容,并没有一个机制来对全局场景进行处理。
为了实现标题栏或窗口背景等的动态毛玻璃效果,必须对framebuffer进行两次渲染。首先是渲染所有不带背景模糊的对象(Actor),然后是渲染有背景模糊的对象的模糊部分,从framebuffer中截取背景部分的内容作为材质(texture)进行模糊运算,然后再把结果贴回framebuffer被截取的区域作为对象的背景,再与对象进行alpha混合(没有透明度的窗口自然不能做背景模糊)。
分层实现
因此为了能支持动态背景模糊,具体下来要做几件事情:
首先修改cogl,让其支持glCopyTexSubImage 2D这是最主要的基础功能。可以从当前的绘图buffer中取出已经绘制的内容来作为材质,以便进行模糊处理,再进行后续的混成。
其次,修改clutter。让其保证那些特殊的带有模糊背景材质的Actor在每帧都能被绘制。
最后就可以在mutter里根据上面的基础设施实现MetaBlurActor。
定义_NET_WM_DEEPIN_BLUR_REGION_MASK属性,所有有此属性的透明窗口都能自动实现动态背景模糊。
下面具体描述一下。
修改cogl很直观,没什么好说的。至于为什么要修改clutter要做一个说明。clutter的绘制模型大致是这样的:
首先,clutter里所有的actor跟X11一样是一个单根的树形结构。顶层的actor叫stage,每个actor都可以有任意数量的子actor。在每一帧绘制的开始,stage搜集所有需要重绘的actors(一般通过queue_redraw(_with_clip)来通知)。stage根据这些actor查找所有路径上的父节点的actor(即子actor重绘会引起父actor的重绘),加入待重绘列表。然后根据clip等信息确定列表里真实需要绘制的actors,并调用clutter_actor_paint自顶向下绘制这些actor。这里面有很多优化细节,但是对于这里的讨论没有太大影响。
总的来说,在每一帧,只有自己请求了重绘的那些actor才会发生重绘。
我设计并实现了MetaBlurActor,当我们需要对一个代表窗口的actor做动态背景模糊时,将此MetaBlurActor作为需要动态模糊背景的actor的子actor,这样保证每次actor需要redraw时,clutter也会同时重绘MetaBlurActor。在actor发生移动和resize时也能按需更新。MetaBlurActor本身实现了使用glCopyTexSubImage2D取得这个actor背后区域的内容,生成材质,进行模糊处理的功能。
现在的问题是作为动态背景,只要actor后面的任何内容有变化MetaBlurActor都应该发生重绘,而不仅仅是actor本身请求重绘时才重绘。但是MetaBlurActor本身并没有明显的方式确定是否需要重绘,这是由clutter的绘制机制决定的。一种可能的实现方式是MetaBlurActor自己监视每个在其后面的actor的damage(X11有window的damage机制,但是actor并没有),但是当有大量MetaBlurActor时,这种方式估计开销很大,而且clutter也没有机制方便跟踪actor之间的层次变化(类似X11的window stacking change事件)。
因此目前修改了clutter加入了一种能快速实现的方式,保证所有的MetaBlurActor在每帧都能得到绘制。当然目前这个方式显然在性能方面还不完善,后面还会进行优化。比如通过全局追踪每帧damage的region,来判断和裁剪MetaBlurActor需要更新的材质区域,减少不必要的模糊运算。目前使用的模糊算法近似高斯模糊,对模糊的速度和效果有一个比较好的折中。
MetaBlurActor的实现
上面已经简单说明了MetaBlurActor的基本功能:通过glCopyTexSubImage2D取得这个actor背后区域的内容,生成材质,对材质进行离线模糊处理,然后重新绘制出来。因此当MetaBlurActor发生移动或改变大小时,都要实时的追踪背后的变化。模糊算法是近似高斯模糊,但是由于高斯模糊的速度问题,要快速的进行大半径的高斯模糊,需要使用一些技巧。
目前通过downscale材质来实现(这也造成了一些问题)。MetaBlurActor不仅要实现动态模糊,而且还需要支持异形窗口的背景模糊。窗口根据需要定义了_NET_WM_DEEPIN_BLUR_REGION_MASK,这是一个alpha位图,可以定义任何形状,deepin-wm根据这个属性生成一个shape mask来处理模糊的区域,以实现异形模糊窗口。比如:深度录屏的模糊效果,OSD提示,深度通知,dock等等。
好奇的同学可以参考这里的实现:
https://github.com/linuxdeepin/deepin-mutter/blob/release/3.20/src/compositor/meta-blur-actor.c
【相关链接】