本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制

在 Windows Vista 就引入了一个服务,桌面窗口管理器Desktop Window Manager,虽然从借助 C++ 进行 Windows 开发博客可以看到 DWM 不是一个好的方法,但是比之前好。

在 win8 的时候,微软提出了 DirectComposition ,这是一个新的方法。

在软件的渲染一直都是两个阵营,一个是使用直接渲染模式。直接渲染的例子是使用 Direct2D 和 Direct3D ,而直接通过 Dx api 的方式当然需要使用 C++ 和底层的 API ,这开发效率比较差。

在 1511 发布,微软告诉大家可以使用底层的 DirectComposition 接口,这样大家就可以通过 DirectComposition 做出好看的效果

在原来的 UWP 应用,大家很容易使用 xaml 来写一个界面,但是如果没有 xaml 那么如何创建一个界面。

我不会告诉大家去 new 一个控件,因为这样和使用之前的方法差不多。我会告诉大家如何从一个 Visual 开始画。

在 UWP 可以通过下面几个方式显示界面

  • 通过 xaml 或者后台新建控件显示。这是最推荐的方法,本文下面的方法是不推荐的,但是可以让大家知道原理。使用 xaml 显示的元素一般都是继承 UIElement ,创建出来的元素可以带交互。

  • 如果需要高性能的画图,通过 win2d 是一个很好的方法。大家也知道创建的win2d只是显示,不会有交互,如果需要交互需要自己写。虽然写一个交互很简单,但是如果没有使用框架,重复代码很多。

  • 使用 DirectX APIs 来画 3d 的图片,但是现在需要一些 C++ 代码。

在 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 项目,注意选择比较高的目标。

win10 uwp 渲染原理 DirectComposition 渲染_C#

如何写显示

现在创建项目,删除所有的 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());
        }
    }
}

上面代码有一些注释,通过这个方式就可以创建一个显示矩形

win10 uwp 渲染原理 DirectComposition 渲染_C#_02

实际上从上面代码很容易就知道,只需要一个类继承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 交互数据。

win10 uwp 渲染原理 DirectComposition 渲染_C#_03

从上面代码实际只是画了普通的矩形,如果要写文字,画线,那么怎么办。这时就需要使用 CompositionSurfaceBrush ,这是最复杂的。通过这个类可以使用 d2d 来画,在 UWP 简单使用的方法是 win2d 所以下面告诉大家如何使用 win2d 来画。

但是 UWP 底层是直接使用d2d没有经过 win2d 的封装。在 WPF 使用 d2d 是比较难的,因为很难集合两个在一个界面。但是 UWP 通过这个类就可以把底层渲染放在指定层级。这就是为什么说 UWP 可以做出比较高性能,因为 WPF 是很难修改他的渲染,即使使用D3DImage也是把渲染位图作为图片显示,需要先在显卡渲染然后把位图复制到内存,让WPF画出图片。但是 UWP 可以直接画出,不需要使用 WPF 这样的方法。我看来 UWP 在这里是很大提升,这就是我看到很多大神说不在 WPF 添加 win2d ,从底层技术实现是不相同。

CompositionSurfaceBrush

首先需要安装 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));
            }
        }

什么时候可以调用这个函数?实际上在刚才的函数最后调用就可以了。

现在的界面就是两个矩形

win10 uwp 渲染原理 DirectComposition 渲染_C#_04

所有的代码

    internal class View : IFrameworkView, IFrameworkViewSource
    {
        public void SetWindow(CoreWindow window)
        {
            _compositor = new Compositor();

            _compositionTarget = _compositor.CreateTargetForCurrentView();

            // 创建 win2d 用于渲染
            GetCanvasAndGraphicsDevices();

            _drawingSurface = _graphicsDevice.CreateDrawingSurface(
                new Size(600, 600),
                DirectXPixelFormat.B8G8R8A8UIntNormalized,
                DirectXAlphaMode.Premultiplied);

            var brush = _compositor.CreateSurfaceBrush(
                _drawingSurface);

            var drawingVisual = _compositor.CreateSpriteVisual();
            drawingVisual.Size = new Vector2(600, 600);

            drawingVisual.Brush = brush;


            var containerVisual = _compositor.CreateContainerVisual();
            _compositionTarget.Root = containerVisual;

            containerVisual.Children.InsertAtTop(drawingVisual);

            Redraw();
        }

        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 CompositionGraphicsDevice _graphicsDevice;
        private CompositionDrawingSurface _drawingSurface;

        private static void Main()
        {
            CoreApplication.Run(new View());
        }

        private void GetCanvasAndGraphicsDevices()
        {
            var canvasDevice = CanvasDevice.GetSharedDevice();

            _graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
                _compositor, canvasDevice);

            //_graphicsDevice.RenderingDeviceReplaced += OnRenderingDeviceReplaced;
        }

        private void OnRenderingDeviceReplaced(
            CompositionGraphicsDevice sender, RenderingDeviceReplacedEventArgs args)
        {
            Redraw();
        }

        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));
            }
        }
    }

修改函数和普通使用 win2d 没有不同

        using (var drawingSession = CanvasComposition.CreateDrawingSession(
                _drawingSurface))
            {
                drawingSession.Clear(Colors.White);
                drawingSession.DrawText("lindexi", new Vector2(100, 100), Color.FromArgb(0xFF, 100, 100, 100));
            }

win10 uwp 渲染原理 DirectComposition 渲染_C#_05

还有如何使用动画和特效,我这里就不说了。

代码参考 图形和动画 - Windows 组合支持 10 倍缩放

 

win10 uwp 渲染原理 DirectComposition 渲染_dotnet_06
欢迎转载、使用、重新发布,但务必保留文章署名lindexi(包含链接:https://blog.51cto.com/u_11283245 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。