体系结构和组件

注意

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

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

软件组件

DirectComposition 由以下main软件组件组成。

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

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

下图显示了 main 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 方法必须按特定顺序调用,如果不进行任何同步,应用程序可能会经历不可预知的行为,具体取决于线程交错的方式。 例如,如果两个线程同时将同一对象的同一属性更改为不同的值,则应用程序无法预测这两个值中的哪一个将是该属性的最终值。 同样,如果两个线程在同一设备上调用 Commit ,则两个线程都不会获得真正的事务性行为,因为调用一个线程上的 Commit 将提交两个线程发出的所有命令的批处理,而不仅仅是调用 Commit 的命令。

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

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

合成引擎

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

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

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

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

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

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

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

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

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

合成帧计划

如果组合引擎由于组合树未更改而无法执行任何工作,则组合线程在等待新批处理时处于睡眠状态。 提交新批后,合成线程会唤醒,但会立即返回到睡眠状态,直到下一个垂直空白。 此行为可确保应用程序和合成引擎的帧开始和结束时间是可预测的。

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

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

DirectComposition 概念