Open suhao opened 2 years ago
微软在 Windows 10中 面向通用 Windows 应用 (Universal Windows Apps, UWA) 新引入了一套用于用户界面合成的 API:Composition API。Composition API 将使开发者使用更少量的代码在通用 Windows 应用实现更为炫丽的界面效果。本系列文章会对该套 API 的由来、用途以及使用方法进行介绍。
本文为个人博客备份文章,原文地址:http://validvoid.net/windows-composition-api-guide-introduction/
简单来说,Composition API 就是一套在桌面窗口管理器(Desktop Window Manager,下文简称 DWM)之上新引入的,面向通用 Windows 应用的用户界面合成 API。DWM 是一个利用硬件加速特性进行图形用户界面合成以及渲染的组件,最早在 Windows Vista 中引入。DWM 诞生的目的旨在提供更高性能的界面渲染以及更丰富的界面效果。需要指出的是,可能很多用户对 DWM 的印象就停留在 Windows Aero 视觉风格中的磨砂玻璃特效,但事实上 DWM 的能力远不止如此。
自 DWM 引入以来,用户在屏幕上实际看到的用户界面实际上是由 DWM 对来自不同应用程序的缓冲区数据进行合成生产的图像。DWM 基于 DirectX 利用显卡显存和 GPU 提供相比老式 GDI 渲染更好的性能来完成界面图形的和成和渲染。对于采用老式 GDI 方法进行呈现的应用,DWM 也会对其绘图方法进行映射。在 DWM 的渲染过程中,桌面实际上是一个全屏大小的 Direct3D 表面,桌面中的窗口则是由两个三角形构成的网格经变换成 2D 矩形。UI 框架作为纹理映射到这些矩形上。窗口过渡等各种效果则是通过 shader 处理、变换等过程实现的。
在 DWM 处理中,每个应用程序都有各自的缓冲区,应用将自身的界面图形数据写入自己的缓冲区,DWM 读取每个应用各自的缓冲区数据进行合成。DWM 使用非托管的媒体整合层(Media Integration Layer, MIL)将窗口描述为合成树 (composition tree) 中的合成节点 (composition nodes) 进行渲染。合成树表示了桌面以及其容纳的窗口,MIL 依照合成树按照从后往前的顺序对整个用户界面的内容进行渲染。有了 MIL 这种依靠合成树的保留模式1图形机制,应用就从刷新重绘这些工作中解放了出来。而自 Windows Vista 开始提供的诸如模糊、Flip 3D、实时缩略图等功能,都是依靠即时缓冲区、shader 等特性实现的。简言之,DWM 是由 DirectX 驱动的高性能新一代界面引擎。
在 Windows 8 中,微软引入了 DirectComposition API。DirectComposition 组件使开发者能够进行高性能的位图合成,并附加变换、特效以及动画等各种效果,以此打造出更为复杂、生动、流畅的用户界面。DirectComposition 利用图形硬件的加速特性可以进行与 UI 线程无关的渲染处理,支持 2D 仿射变换、3D 透视变换等多种变换,以及剪切、不透明等基本特效。正如其名,DirectComposition 的设计受 DirectX 启发,但提供更为友好的方式供开发者使用,也让开发者更清晰地认识到 DWM 这一强大的合成引擎所能实现的效果。
不过,DirectComposition 是面向组建对象模型 (Component Object Model, COM) 构建的一套 API ,并不适合通常使用托管语言进行应用开发的开发者。实际上在 DirectComposition 的 MSDN 文档里明确写道:
The DirectComposition API is intended for experienced and highly-capable graphics developers who know C/C++, have a solid understanding of the Component Object Model (COM), and are familiar with Windows programming concepts.
即,DirectComposition API 适用于熟悉 C/C++、COM的图形处理开发者。
现在,随着 Windows 10 的到来,开发者有了新的选择: Composition API。Composition API 基于 Windows 运行时 (Windows Runtime, WinRT) 构建,在整合 Direct2D 和 Direct3D 的立即模式2图形能力的同时,提供了保留模式的视觉树机制,实现了效果丰富、支持动画的高性能界面合成渲染。Composition API 面向 通用 Windows 应用,通过 Windows.UI.Composition 命名空间提供。这意味着,开发者可以使用任何一种通用 Windows 平台 (Universal Windows Platform, UWP) 语言(C#, JavaScript, C++/CX 或 Modern C++ 等)在 UWA 应用中使用,并在所有 UWP 平台设备(PC、平板、手机)上(几乎)无差别运行。
Windows.UI.Composition 是声明式、保留模式的 API。它不仅可以与如今广泛使用的 XAML 结合使用,也可以脱离 XAML 独立使用。这意味着开发者如今可以以更为灵活的方式,使用自己最熟悉的语言,整合 XAML、DirectX 的各种特性打造现代 UI。下图展示了通用 Windows 应用使用 XAML、Composition API 以及 DirectX 进行渲染的层次结构:
借助 Composition API,开发者可以在基于 XAML 的应用中随时从框架层深入至视觉层应用更为复杂的图形效果。
Composition API 除了提供一套视觉树机制外,还提供了以下特性,这些特性将在后文逐一做详细介绍:
如图所示,CompositionObject 是 Composition API 的核心基类。它实现 ICompositionObject 接口,表示视觉树中的一个节点。
由 CompositionObject 类派生出了以下主要类型:
Windows.UI.Composition 包含一个实现 ICompositor 接口的合成器类 Compositor,该类扮演创建合成对象的工厂角色,负责创建视觉树种几乎所有操作对象,MSDN 文档对其描述为“管理应用与系统合成器之间的会话。”
保留模式:图形 API 可以分为保留模式和立即模式两类。保留模式 API 是声明式的,应用从形状、线条等图形基元构建场景,将场景抽象成一个图或树的模型保存在内存中。当要绘制一帧画面时,图形处理将场景变换为一组绘图命令。在帧间,图形处理库回将场景保留在内存中。要渲染更新的内容,应用会发送更新场景的命令——例如添加或删除一个形状。之后图形处理库负责重绘场景。保留模式更为直观,更容易操作,因为诸如初始化、状态管理、资源清理等很多底层工作都由 API 负责实行了。但保留模式 API 缺乏灵活性,因为它是用自己的场景模型,很多特性可能并未公开。另外,因为其提供了通用的场景模型,保留模式 API 可能会有更多的内存占用。 ↩
立即模式:图形 API 可以分为保留模式和立即模式两类。立即模式 API 是过程式的。每次有新帧进行绘制,应用都会直接发送绘图命令。图形处理库并不会储存场景模型,而是由应用自行管理场景。相比保留模式,立即模式 API 因为能够进行针对性优化,因而在性能上可以实现更好的表现。
某些术语如 "Sprite",有部分资料翻译为精灵,个人以为不如保留原文不翻译,翻译后可能会造成误解,故本系列涉及到的概念、术语视上下文酌情翻译或不译。一般指某一类型、接口时直接使用类名、接口名原文,否则酌情翻译为泛指的中文,比如 "Visual" 特指类型时使用 Visual,一般叙述翻译为视觉元素。
本博客系备份博客,原博客地址为: http://validvoid.net/
在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制,在 Windows Vista 就引入了一个服务,桌面窗口管理器Desktop Window Manager,虽然从借助 C++ 进行 Windows 开发博客可以看到 DWM 不是一个好的方法,但是比之前好。
在 win8 的时候,微软提出了 DirectComposition ,这是一个新的方法。
在软件的渲染一直都是两个阵营,一个是使用直接渲染模式。直接渲染的例子是使用 Direct2D 和 Direct3D ,而直接通过 Dx api 的方式当然需要使用 C++ 和底层的 API ,这开发效率比较差。在 1511 发布,微软告诉大家可以使用底层的 DirectComposition 接口,这样大家就可以通过 DirectComposition 做出好看的效果。
在 UWP 可以通过下面几个方式显示界面
在 UWP 的显示,推荐使用 xaml 来写界面,原因是 xaml 是一个界面无关的代码,也就是无论是 C# 和 C++ 都可以使用。如果使用 C# 来写界面,那么代码就和 C# 合在一起,不能很好在 C++ 运行。而且使用xaml 写简单比使用C#更简单,在 vs 实时编译器可以看到界面效果。
也许大家会关系 fds 是如何做出来的,对于微软的设计,所有的 xaml 或者 win2d 的显示都是位图。这里的位图不是大家想的 bitmapImage 而是显示的一个说法,微软对所有的位图输出到 DirectComposition 。微软的 DirectComposition 在官方是这样说 “DirectComposition 组件使开发者能够进行高性能的位图合成,并附加变换、特效以及动画等各种效果,以此打造出更为复杂、生动、流畅的用户界面。DirectComposition 利用图形硬件的加速特性可以进行与 UI 线程无关的渲染处理,支持 2D 仿射变换、3D 透视变换等多种变换,以及剪切、不透明等基本特效”。
翻译参见 Windows Composition API 指南 - 认识 Composition API 感谢大神。
那么是不是可以通过Composition显示元素,自己来写 UWP 框架。
在开始告诉大家写 UWP 框架之前,先给大家一个简单的例子,如何应用 DirectComposition 。
之前写的一个简单的动画是一个好看效果,请看 win10 uwp 进度条 WaveProgressControl
下面来通过删除所有 xaml 文件,从头自己写。
首先创建一个 UWP 项目,注意选择比较高的目标,删除所有的 app 和 mainpage 类。重新创建一个类。
只要支持显示,那么就可以完成一半了,因为 UWP 的元素显示都是通过布局找到元素显示的位置。当然这里不会提到 Translate 等。然后元素通过调用DrawContex告诉显卡需要显然的图形。然后在加上用户的输入,就构成了框架。
虽然一个框架比上面说的复杂很多,但是在写 Avalonial 的时候,大神告诉我,实际上一个界面框架主要的就是显示和交互。本文不会告诉大家如何写交互,只是告诉大家如何显示。
删除了所有的自动生成的代码,现在创建一个类 View ,用来显示。
下面代码的意思请看 【Win 10 应用开发】UI Composition 札记(一):视图框架的实现 - 东邪独孤 - 博客园
using System.Numerics;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Core;
namespace HmeucHsvv
{
internal class View : IFrameworkView, IFrameworkViewSource
{
public void SetWindow(CoreWindow window)
{
_compositor = new Compositor();
_compositionTarget = _compositor.CreateTargetForCurrentView();
// 创建一个容器,用来向他的 Children 添加 Visual 显示复杂的元素
var container = _compositor.CreateContainerVisual();
_compositionTarget.Root = container;
// 创建 SpriteVisual ,这个类不仅是一个容器,同时本身也是可以画出来
var visual = _compositor.CreateSpriteVisual();
// 告诉这个元素的大小和左上角,所以这个就是一个矩形,而且设置颜色
visual.Size = new Vector2(100, 100);
visual.Offset = new Vector3(10, 10, 0);
visual.Brush = _compositor.CreateColorBrush(Colors.Red);
// 添加元素,添加进去的元素就会被显示
container.Children.InsertAtTop(visual);
}
public void Run()
{
//启动窗口需要激活
var window = CoreWindow.GetForCurrentThread();
window.Activate();
//调度方式使用 Dispatcher 通过这个就可以获得消息
window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
}
public void Initialize(CoreApplicationView applicationView)
{
}
public void Load(string entryPoint)
{
}
public void Uninitialize()
{
}
public IFrameworkView CreateView()
{
return this;
}
private CompositionTarget _compositionTarget;
private Compositor _compositor;
private static void Main()
{
CoreApplication.Run(new View());
}
}
}
上面代码有一些注释,通过这个方式就可以创建一个显示矩形
实际上从上面代码很容易就知道,只需要一个类继承IFrameworkView, IFrameworkViewSource,然后使用CreateView返回他自己,这时这个类就可以显示。
但是还需要使用主函数告诉软件启动的类是哪个,在运行启动窗口,如果注释掉window.Activate那么就会看到只有一个欢迎的图片不会显示矩形。
那么是什么时候窗口支持渲染的?
核心代码是CreateTargetForCurrentView这个函数只能调用一次,如果你尝试调用他两次,那么就会出现异常。因为调用这个函数就会告诉 DirectComposition 创建元素。
_compositor = new Compositor();
_compositionTarget = _compositor.CreateTargetForCurrentView();
显示的矩形是通过创建 SpriteVisual 来显示。那么下面再写一个 SpriteVisual ,让两个加起来。
public void SetWindow(CoreWindow window)
{
_compositor = new Compositor();
_compositionTarget = _compositor.CreateTargetForCurrentView();
// 创建一个容器,用来向他的 Children 添加 Visual 显示复杂的元素
var container = _compositor.CreateContainerVisual();
_compositionTarget.Root = container;
// 创建 SpriteVisual ,这个类不仅是一个容器,同时本身也是可以画出来
var visual = _compositor.CreateSpriteVisual();
// 告诉这个元素的大小和左上角,所以这个就是一个矩形,而且设置颜色
visual.Size = new Vector2(100, 100);
visual.Offset = new Vector3(10, 10, 0);
visual.Brush = _compositor.CreateColorBrush(Colors.Red);
// 添加元素,添加进去的元素就会被显示
container.Children.InsertAtTop(visual);
var visual1 = _compositor.CreateSpriteVisual();
// 创建一个重叠元素
visual1.Size = new Vector2(100, 100);
visual1.Offset = new Vector3(20, 20, 0);
visual1.Brush = _compositor.CreateColorBrush(
Color.FromArgb(128 /*透明*/, 0, 255, 0));
container.Children.InsertAtTop(visual1);
}
使用这个方法就可以创建多个矩形,而且通过指定位置就和大小就可以决定他在哪显示。
上面用到了三个东西第一个是 Visual ,这是一个基础的类。有 ContainerVisual 继承 Visual ,实际上他只是可以存在子元素。最后一个是 SpriteVisual ,这个类和 ContainerVisual 一样,但是他可以使用笔刷。
那么 SpriteVisual 设置的笔刷是什么,他可以设置三个不同的笔刷。第一个就是刚才给大家看的 CompositionColorBrush ,这是一个纯色笔刷。 第二个是比较复杂的,可以使用特效的 CompositionEffectBrush 笔刷,最后一个是 CompositionSurfaceBrush 可以和 dx 交互数据。
从上面代码实际只是画了普通的矩形,如果要写文字,画线,那么怎么办。这时就需要使用 CompositionSurfaceBrush ,这是最复杂的。通过这个类可以使用 d2d 来画,在 UWP 简单使用的方法是 win2d 所以下面告诉大家如何使用 win2d 来画。
但是 UWP 底层是直接使用d2d没有经过 win2d 的封装。从我的博客WPF 使用 SharpDX 在 D3DImage 显示可以知道,在 WPF 使用 d2d 是比较难的,因为很难集合两个在一个界面。但是 UWP 通过这个类就可以把底层渲染放在指定层级。这就是为什么说 UWP 可以做出比较高性能,因为 WPF 是很难修改他的渲染,即使使用D3DImage也是把渲染位图作为图片显示,需要先在显卡渲染然后把位图复制到内存,让WPF画出图片。但是 UWP 可以直接画出,不需要使用 WPF 这样的方法。我看来 UWP 在这里是很大提升,这就是我看到很多大神说不在 WPF 添加 win2d ,从底层技术实现是不相同。
首先需要安装 win2d ,然后在 SetWindow 使用 CompositionSurfaceBrush 。还是和上面代码一样,但是需要先创建一个函数,用来创建 win2d ,请看下面
private void GetCanvasAndGraphicsDevices()
{
var canvasDevice = CanvasDevice.GetSharedDevice();
_graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
_compositor, canvasDevice);
_graphicsDevice.RenderingDeviceReplaced += OnRenderingDeviceReplaced;
}
通过这个方法就可以拿到 graphicsDevice ,这个就是用来做 CompositionSurfaceBrush 。
如果需要创建 CompositionSurfaceBrush 那么就需要一个 CompositionDrawingSurface ,而 CompositionDrawingSurface 可以通过 graphicsDevice 创建,代码很简单
_compositor = new Compositor();
_compositionTarget = _compositor.CreateTargetForCurrentView();
// 创建 win2d 用于渲染
GetCanvasAndGraphicsDevices();
_drawingSurface = _graphicsDevice.CreateDrawingSurface(
new Size(600, 600),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
var brush = _compositor.CreateSurfaceBrush(
_drawingSurface);
那么创建的 CompositionSurfaceBrush 如何显示?刚才讲到SpriteVisual可以显示笔刷,那么就创建这个类来显示。
var drawingVisual = _compositor.CreateSpriteVisual();
drawingVisual.Size = new Vector2(600, 600);
drawingVisual.Brush = brush;
然后把他加入视觉,和上面的代码一样,只是把 Brush 的创建写了其他的代码
var containerVisual = _compositor.CreateContainerVisual();
_compositionTarget.Root = containerVisual;
containerVisual.Children.InsertAtTop(drawingVisual);
下面就是让 win2d 画出矩形。
private void Redraw()
{
using (var drawingSession = CanvasComposition.CreateDrawingSession(
_drawingSurface))
{
drawingSession.FillRectangle(
new Rect(10, 10, 200, 200),
Colors.Red);
drawingSession.FillRectangle(
new Rect(300, 300, 200, 200),
Color.FromArgb(255,126,50,50));
}
}
DirectComposition API 适用于了解 C/C++ 的经验丰富的且支持高度支持的图形开发人员,对组件对象模型 (COM) 有扎实的理解,并熟悉Windows编程概念。DirectComposition 是在 Windows 8 中引入的。 它包含在 32 位、64 位和 ARM 平台中。
将视觉层与 Win32 结合使用
可以在 Win32 应用中使用 Windows 运行时 Composition API(也称为视觉层)来创建面向 Windows 用户的现代特色体验。
GitHub 上提供了本教程的完整代码:Win32 HelloComposition 示例。
需要精确控制其 UI 合成的通用 Windows 应用程序可以访问 Windows.UI.Composition 命名空间,以便对其 UI 的合成和呈现方式进行精细控制。 但是,此 Composition API 并不局限于 UWP 应用。 Win32 桌面应用程序可以利用 UWP 和 Windows 中的现代合成系统。
如何从 Win32 桌面应用程序使用 Composition API
为了在 Win32 应用中使用 Windows 运行时 (WinRT) API,我们将使用 C++/WinRT。 需要配置 Visual Studio 项目才能添加 C++/WinRT 支持。
若要使用视觉层承载创建的内容,请创建一个类 (CompositionHost) 用于管理互操作并创建合成元素。 将在此类中完成用于承载 Composition API 的大部分配置,包括:
我们将此类设为单一实例,以免出现线程问题。 例如,只能为每个线程创建一个调度程序队列,因此,在同一线程中实例化 CompositionHost 的另一个实例会导致出错。