体系结构和组件

注意

对于 Windows 10 上的应用,我们建议使用 Windows.UI.Composition API 而不是 DirectComposition。 有关详细信息,请参阅 使用视觉层现代化桌面应用。

本主题介绍构成 Microsoft DirectComposition 的组件。 它由以下部分组成。

软件组件

DirectComposition 由以下主要软件组件组成。

  • 实现基于组件对象模型(COM)的公共 API 的用户模式应用程序库(dcomp.dll)。
  • 在桌面窗口管理器(DWM)进程(dwm.exe)中托管的用户模式组合引擎(dwmcore.dll),并执行实际的桌面合成。
  • 内核模式对象数据库(win32k.sys的一部分)用于将命令从应用程序封送到合成引擎。

合成引擎的单个实例处理所有应用程序的 DirectComposition 组合树和 DWM 组合树,这表示整个桌面。 内核模式对象数据库和用户模式组合引擎每个会话实例化一次,因此具有多个用户的终端服务器计算机具有这两个组件的多个实例。

下图显示了主要的 DirectComposition 组件及其彼此之间的关系。

directcomposition 顶级体系结构

应用程序库

DirectComposition 应用程序库是一个基于 COM 的公共 API,其中包含从 dcomp.dll 导出的单个平面入口点,并返回指向设备对象的接口指针。 设备对象又具有创建所有其他对象的方法,每个对象都由接口指针表示。 所有 DirectComposition 接口都继承自 IUnknown接口并完全实现。 接受 DirectComposition 接口的所有方法都会检查接口是在 dcomp.dll 内实现还是由另一个组件实现。 由于 DirectComposition 不可扩展,因此如果接口未在 dcomp.dll中实现,则将接口作为参数的方法返回E_INVALIDARG。 API 不需要特殊权限;它可以由在最低访问级别运行的进程调用。 但是,由于 API 不在会话 0 中运行,因此它不适用于服务。 在这些方面,DirectComposition API 与其他 Microsoft DirectX API 类似,尤其是 Direct2D、Microsoft Direct3D 和 Microsoft DirectWrite。

由于合成引擎专用于异步执行,因此 DirectComposition API 中的对象属性是只写的。 所有属性都具有 setter 方法,但不包含 getter 方法。 读取属性不仅资源密集型,而且可能不准确,因为合成引擎返回的任何值都可能立即变得无效。 例如,如果独立动画绑定到正在读取的属性,则可能会发生这种情况。

API 是线程安全的。 应用程序可以随时从任何线程调用任何方法。 但是,由于必须在特定序列中调用许多 API 方法,因此应用程序无需进行任何同步,都可能会遇到不可预知的行为,具体取决于线程交错的方式。 例如,如果两个线程同时将同一对象的相同属性更改为不同的值,则应用程序无法预测这两个值中的哪一个将是该属性的最终值。 同样,如果两个线程在同一设备上调用 提交,那么两个线程都不会获得真正的事务行为,因为对一个线程上 提交 的调用将提交这两个线程发出的所有命令的批处理,而不仅仅是调用 提交的命令。

系统维护每个设备对象的所有内部状态。 如果应用程序创建两个或更多 DirectComposition 设备对象,则应用程序可以维护独立批处理和两者之间的其他状态。

所有 DirectComposition 对象都具有设备对象相关性;由特定设备对象创建的对象只能与该设备对象一起使用,并且只能与同一设备对象创建的其他对象相关联。 换句话说,每个设备对象都是一个单独的不相交的功能岛。 一个例外是视觉类,它允许生成视觉对象,其中视觉对象可以属于与其父对象不同的设备对象。 这样,应用程序和控件就可以管理单个组合树,而无需共享单个 DirectComposition 设备对象。

合成引擎

DirectComposition 合成引擎在专用进程中运行,独立于任何应用程序进程。 单个组合过程 dwm.exe支持会话中的每个应用程序。 每个应用程序都可以为其拥有的每个窗口创建两个可视化树。 所有树实际上是作为大型可视化树的子树实现的,该树还包含 DWM 的构成结构。 DWM 为会话中的每个桌面构造一个大型可视化树。 以下是此体系结构的主要优势:

  • 合成引擎可以访问所有应用程序位图和可视化树,这可实现跨进程窗口互作性和组合。
  • 合成引擎在独立于任何应用程序进程的受信任系统进程中运行,使具有低访问权限的应用程序能够安全地撰写受保护的内容。
  • 合成引擎可以检测特定窗口被完全遮挡的时间,并避免浪费为窗口撰写的 CPU 和图形处理单元(GPU)资源。
  • 合成引擎可以直接撰写到屏幕后退缓冲区,避免需要额外的副本,这是每个进程合成引擎所必需的。
  • 所有应用程序共享单个 Direct3D 设备进行组合,这样可以节省大量内存

可视化树是一个保留的结构。 DirectComposition API 公开用于以原子方式处理的更改批处理中编辑结构的方法。 DirectComposition API 中的根对象是设备对象,它充当所有其他 DirectComposition 对象的工厂,并包含名为 Commit的方法。 合成引擎不会反映应用程序对可视化树所做的任何更改,直到应用程序调用 提交,此时自上次 提交 以来的所有更改都作为单个事务进行处理。

调用 提交 的要求与“帧”的概念类似,不同之处在于,由于合成引擎以异步方式运行,因此它可以在调用 提交之间呈现多个不同的帧。 在 DirectComposition 中, 是组合引擎的单个迭代,应用程序在对 提交 的两次调用之间花费的间隔称为 批处理

DirectComposition 对 DirectComposition API 的所有应用程序调用进行批处理。 在 win32k.sys 会话驱动程序中实现的内核对象数据库存储与 API 调用关联的所有状态信息。

合成引擎为显示中的每个垂直空白生成一帧。 框架从垂直空白处开始,面向后续的垂直空白。 帧启动时,合成引擎会选取所有挂起的批处理,并在该帧中包含其命令。 当应用程序调用 提交时,批处理将置于挂起队列中,并在帧开头以原子方式刷新挂起的队列。 因此,有一个时间点标记帧的开头。 在帧中包括在此点之前提交的任何批处理,而提交后的任何批处理必须等到处理下一帧。 完整合成循环如下所示:

  1. 估计下一个垂直空白的时间。
  2. 检索所有挂起的批处理。
  3. 处理检索的批处理。
  4. 使用步骤 1 中估计的时间更新所有动画。
  5. 确定需要重新组合的屏幕区域。
  6. 重新撰写脏区域。
  7. 通过翻转每个屏幕的后缓冲区和前缓冲区来呈现帧。
  8. 如果步骤 6 和 7 中未编写并显示任何内容,请等待提交批处理。
  9. 等待下一个垂直空白。

如果有多个监视器附加到单个视频适配器,则合成引擎使用主监视器的垂直空白来驱动合成循环并设置动画采样时间。 每个监视器由单独的全屏翻转链表示;合成引擎使用单个 Direct3D 设备,以轮循机制方式为每个监视器重复步骤 6 和 7。 如果还有多个视频适配器,则合成引擎在步骤 6 和 7 中为每个视频适配器使用单独的 Direct3D 设备。

合成帧计划始终从垂直空白处开始,如下图所示。

合成帧计划

如果合成引擎没有工作要做,因为合成树没有更改,则合成线程在等待新批时休眠。 提交新批后,合成线程会唤醒,但会立即返回到睡眠状态,直到下一个垂直空白。 此行为可确保应用程序和合成引擎的可预测帧开始和结束时间。

合成引擎发布帧呈现时间和当前帧速率。 通过发布此信息,应用程序可以估算其自己的批的呈现时间,从而可以同步动画。 具体而言,应用程序可以使用合成引擎中的帧统计信息组合,以及其 UI 线程生成批处理所花费的时间的历史模型,以确定其自己的动画的采样时间。

例如,在上图中显示的应用程序批处理的开头,应用程序可以查询合成引擎以确定下一帧的确切呈现时间。 然后,应用程序可以使用当前时间以及之前生成的批信息,以确定应用程序是否可以在下一个垂直空白之前完成当前批处理。 因此,应用程序使用帧呈现时间作为其自身动画的采样时间。 如果应用程序确定不可能在当前垂直空白中完成其工作,则应用程序可以使用后续帧时间作为采样时间,使用合成引擎返回的帧速率信息来计算该时间。

DirectComposition 概念