Windows XP 的窗口拖影现象的成因与解决

本文转载自知乎用户我也是加把劲骑士的文章 Windows XP 的窗口拖影现象的成因与解决

1.jpg

1. 名称的声明与解释

首先统一一下口径,我们管下图这种现象叫做“拖影”(trail),也有其他说法,比如说“幽灵窗口”(Ghost Window)。

2.png
窗口拖影 / 幽灵窗口

2. Windows 技术因素

在 Windows 上,​窗体程序内如何绘图是由程序自己负责的​,所以当某个程序完全陷入阻塞时、不响应系统消息时,它是不会进行绘图的,这时候窗体是未响应状态的。

Windows 应用程序是自己负责绘制的,当一个窗体“不干净”了,也就是说窗体改变了大小,或者部分被其它程序窗体遮盖,或者从最小化状态恢复时,程序都会收到需要绘制的信息。Windows 把这种“不干净”状态称为“无效的(Invalidated)”状态。

例如,假设用户移动另一个窗口,使其遮挡下层窗口的一部分。当遮挡部分再次变为可见时,该部分将添加到更新区域,并且下层窗口会收到另一条 WM_PAINT 消息。窗体响应消息对需要更新的区域进行重绘。

3.png
遮盖窗口移除后的重绘区域

Windows 的绘图的策略是​脏矩形渲染​,即每个刷新周期内只会清空并重新绘制发生变化的那部分区域。(残留着以前的数据的、不能正确地表达该区域当前图形的矩形区域,被称作脏矩形)

什么是脏矩形?简单的说,就是游戏每次画面的刷新只更新需要更新的那一块区域。Windows 本身就是最好的例子。

4.png
CCTV-9 纪录频道形象宣传 2014 年 A 版纪录典藏;
该短片比较形象地描绘了脏的概念,即留在画布上的轨迹就是脏数据

3. 在 XP 上的具体成因

对于拖影现象,一种说法是,如果窗口完全阻塞,那么卡死的窗口区域就无法重新绘制,Windows XP 不强制对该区域进行重绘(更早期的 Windows,即 2000(基于 NT)或更早的 98、95(基于 DOS)等,是会进行重绘的,策略是会为其强制绘制上背景色),也就是不修改该区域对应的图像缓冲区的内容,让该区域维持卡死时的原状;但如果上方刚好有其它可响应窗口处在这个区域内,那么在该窗口挪开后,它原先位置的窗口图形残留是不会重新绘制上的,因为下方的程序不能执行相应的绘图,​也就是脏数据无法清除​,而该窗口挪动到的新区域由该窗口本身负责绘图,因此新区域的该窗口是正常绘制的。所以如果下方有一个未响应窗口,上方有一个可响应窗口,拖动上方窗口就会出现拖影现象。

虽然要两个窗口上下层配合的条件听上去有点苛刻,但是往往当一个窗口卡死时,它的上方就会弹出一个可响应的弹窗,来告知你程序出问题以及你是否要进行某些操作(比如发送错误报告等),所以这种拖影现象在 Windows XP 的年代十分常见,也很容易被发现。当然,如果你的手速足够快的话,仅仅是直接快速拖动某一个能动的窗口可能也会出现类似现象,因为下层窗口或者桌面还没来得及重绘,或者说重绘效率低,这种现象在下层窗口的控件很多时尤为严重,整体看上去就会卡顿、割裂和闪烁,或者也有人管它叫做“果冻状”(jelly)。

5.png
Windows 特色拖影

另外也有一种说法,它说 Windows XP 实际上是接管了绘图的,并且在每个刷新周期内都对未响应窗口区域进行了重绘,但是重绘的方式是不停地对未响应窗口区域截图,再将截图重绘到该区域。如果有其他窗口经过这片区域,他们在这片区域的重新绘图就会污染这片区域,而 Windows 不知道程序原本应该怎么执行绘图,只能用截图绘制覆盖,而截图上是​带有被污染的脏数据的​。

所以无论 Windows 有没有进行重绘,​根源问题是一样的,那就是脏数据没有被清除​。

这时可能也有人会疑惑,如果说 Windows XP 是不断截图并重绘的话,为什么不能把窗口的“图层”单独截图出来然后像正常应用绘图一样地绘制并混合上去呢?或者说即便本来是不重绘的,那它为什么不能这样做呢?

其实问题就在于做不到。早期的 Windows(NT 5 及以前)窗口混合都是实时混合的,每个窗口都是直接绘制到屏幕或主显示设备的;因为当时的显卡性能不够强,显存很小,所以采取的策略是应用程序在窗口客户区画图,绘制图形交给桌面管理器,桌面管理器立刻根据这个窗口所处的位置,决定哪些区域该显示,哪些不该显示,需要显示的立马显示到显示终端上,而不会保留图像备份,也就是说​只保留了“混合后的图层”​;这就类似于很多人在同一张画布上一起绘图,在上层画的就会覆盖掉下层的画,想恢复原状就必须重新画回去。

这导致了只有应用程序知道应该怎么绘图,而 Windows 则完全不知道。因此当未响应的窗口被遮挡时,Windows 就截取不到被遮挡的部分,因为这部分图像数据当前是不存在的。而如果此时将窗体部分拖拽到屏幕范围外侧,则连这已有的脏数据都会丢失,此时就只能为窗体缺失的部分强制绘上背景色了。

6.png
trail

由于​没有原始数据​,就只能使用脏数据凑合着继续进行绘制。

总的来说,这种做法还是一定程度上有先进性的,起码相比于之前直接绘制背景色,这种策略下还能保留窗口的一部分原貌,只不过受限于 Windows 的其他策略,产生了其他的问题。

4. Vista 及以上的解决

后来 Windows 在 NT 6 时代(Vista 及以后)时引入了 DWM(Desktop Window Manager,桌面窗口管理器),​按微软的说法,它“从根本上改变了应用程序在屏幕上显示像素的方式”。

当窗口未响应(Vista 判断标准是 5 秒以上没有接受消息)、并且窗口区域接受到了用户输入时,Windows 会接管窗口的绘图(没有用户输入时会维持原状)。DWM 会隐藏掉卡死程序真正的窗体,然后另外新建一个窗口,新窗口的标题为“<原标题>(未响应)”、之后将卡死窗口的“图层”输出到新的窗口内;该窗口并不是原程序的窗口,它是由 DWM 管理的窗口,因此它是可响应的。它被称为“幽灵窗体”(窗口类为“Ghost”),是专门负责与用户交互的(专门负责恶心用户的(雾)),如果用户点击该窗口,它就会变白并提醒用户程序未响应;如果用户叉掉该窗口,Windows 会在确认后关闭该窗口并杀死实际的原窗口进程。

这样一来​用户可见的、用户进行交互的窗体实际上都是可响应的窗体​,这样就不会存在无法重绘的脏数据,如此​不仅解决了拖影问题,还顺带解决了交互问题​。

7.jpg
新版本卡死的窗口

而 Vista 及以上的系统之所以能做到单独“图层”剥离输出,其实也和 DWM 有关。在引入 DWM 后,每个程序都不会直接绘制到显示器上,而是输出到一个“离屏表面”(off-screen surface)上,也就是说每个程序的图形都会分别输出到显存里的一片彼此隔离的内存空间里(相当于一个假想的显示器上),之后再由 DWM 统一进行图形混合和输出到屏幕。也就是说,​每个窗体程序的绘图数据都被完整地保存了下来​(只有顶级窗口,不包括 MDI 子窗口),这样 Windows 至少知道了程序卡死时完整的绘图是长什么样子的,自然也就可以单独对每个程序进行绘制(尽管只是一张卡死时的截图)。

很显然,这样虽然更耗费显存,但是保存了更多的实时数据,为更加方便的图形显示打下了基础。而且,这样做省去了复杂的窗口间通信和重绘,拖动窗口时只需要重新混合和输出即可,不用“牵一发而动全身”,使得整体的显示效果看上去更加流畅顺滑。如果说原先是一张画布,那现在则像是一张剪贴报。由此看来,它确实可以说是从根本上改变了显示的方式。不过不得不提的是,这种策略在 Vista 刚刚推出的年代,确实面临过主流的硬件不足以支持这种强大功能的窘境;当然这也有变相推动硬件厂商发展的积极意义。

8.png
窗口混合

应该说这是早期微软负责的体现,因为原先程序彻底卡死时,程序和 Windows 都不能对绘图和交互负责;而改进之后,虽然程序仍然是卡死的,但 Windows 起码尽最大可能保证了基础的绘图和交互,增强了用户体验。

不过也要说明,​未响应的窗口未必是完全陷入阻塞​,Windows 判断窗口未响应的逻辑是看它能不能对 Windows 的消息进行响应;所以很多时候会出现,程序不响应用户的输入,Windows 认为它未响应,窗口变白,但是程序实际上仍然在正常运行,只不过它将时间片完全用在处理当前的任务上,而用户的输入之类的消息则陷入阻塞,程序不对这些消息进行回应,​这时候程序也是有可能继续正常地执行绘图的​,这种时候等待程序响应即可。

不过如果程序确实是包括绘图在内的任务也不能执行,彻底卡死的话,那就只能是显示截图了,也就是显存里剩下什么就显示什么;这个取决于程序是怎么设计的,或者说程序具体是不是“完全阻塞”的(根据评论区提及的现象进行补充)

总的来说,DWM 是时代进步的一种体现,伴随着 NT 5 的 XP 到 NT 6 的 Vista 的大版本升级和硬件的快速的迭代发展,Windows 的图形驱动模型也从早先的 XPDM 更换到了 WDDM,使用更加先进的 3D 技术,Vista 之后底层所有的渲染都是基于 Direct X 3D,这些系统的桌面实际上是一个全屏大小的 Direct3D 表面,桌面中的窗口则是由两个三角形构成的网格经变换成 2D 矩形。

XPDM 模型与 WDDM 模型
9.png 10.png

不过,微软为了向后兼容一些老程序,同时为了适配低性能硬件,在 Vista 和 Windows 7 里留了一个开关,可以关闭 DWM(禁用桌面元素),等同于直接禁用 DWM 服务或者在 Windows 7 中使用 Basic 主题,这样所有的窗口混合都会退回传统的统一缓冲区实时混合,在这种情况下仍然可能出现拖影现象;在 Vista 和 Windows 7 中也可以使用 Classic 主题来回退到更老的界面;Classic 和 Basic 都使用传统的混合方式,区别在于 Basic 可以使用一些新功能,比如 Alt+Tab 快捷键的切换栏中可以显示“桌面”,而 Classic 中则不能,仍然完全使用老一代界面。

而在 Windows 8(或 Windows Server 2012)及以上则不存在这种情况,因为它为了兼容 Morden 风格的应用和开始菜单,是强制 DWM 渲染的,DWM 强制启用不能正常关闭,这等于同时也取消了对 Classic 样式的兼容。如今微软还在维护的不强制使用 DWM 的只有未安装图形界面的 Windows Server(即 Server Core 版本)。

11.png
禁用桌面元素(其实翻译为“禁用桌面混合”更准确)

此外,鉴于 MDI 的子窗口不会被分配单独的显存,即使到现在使用 MDI 布局的应用仍然有可能出现拖影现象(比如 Windows 自带的 mmc,即控制台)。

12.png
mmc 控制台的 MDI 布局

5. 所产生的新问题

不过 DWM 的引入也带来了不少问题,至今也未能完全解决(Windows 至今仍然基于 NT 6,包括最新的 Windows 11,架构上没有根本性改变);

例如,Windows 8 以上的 DWM 桌面混合是单线程的,如果某些程序绘图时间太长,其他程序就要等待它绘制完成才能绘制,因此极端情况下可能出现一个程序绘制卡顿而导致整个桌面绘图都卡死的情况。(Windows 10 到目前为止累计更新横跨 7 年,可能中途对此问题会有改善)

当然还有比较常见的、不知从何时起出现的,在 Windows 10 和 11 上饱受诟病的 DWM 内存泄漏问题。(似乎和显卡驱动的质量以及 Windows 本身的一些策略(比如快速启动)同时有关)