win10 uwp 渲染原理 DirectComposition 渲染

本文来告诉你们一个新的技术DirectComposition,在 win7 以后(其实是 vista),微软正在考虑一个新的渲染机制git

 

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

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

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

在 1511 发布,微软告诉你们可使用底层的 DirectComposition 接口,这样你们就能够经过 DirectComposition 作出好看的效果框架

在原来的 UWP 应用,你们很容易使用 xaml 来写一个界面,可是若是没有 xaml 那么如何建立一个界面。ide

我不会告诉你们去 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 项目,注意选择比较高的目标。

如何写显示

如今建立项目,删除全部的 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 ,从底层技术实现是不相同。

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

何时能够调用这个函数?实际上在刚才的函数最后调用就能够了。

如今的界面就是两个矩形

全部的代码

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 写文字就请看win10 uwp win2d

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

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

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

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

参考:

图形和动画 - Windows 组合支持 10 倍缩放

【Win 10 应用开发】UI Composition 札记(一):视图框架的实现 - 东邪独孤 - 博客园

借助 C++ 进行 Windows 开发 - 使用 Windows 组合引擎实现高性能窗口分层

借助 C++ 进行 Windows 开发 - 使用 Windows 组合引擎

Windows, UI and Composition (the Visual Layer) – Mike Taulty

Windows with C++ - DirectComposition: A Retained-Mode API to Rule Them All

我搭建了本身的博客 https://lindexi.gitee.io/ 欢迎你们访问,里面有不少新的博客。只有在我看到博客写成熟以后才会放在csdn或博客园,可是一旦发布了就再也不更新

若是在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎你们加入

本做品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、从新发布,但务必保留文章署名林德熙,不得用于商业目的,基于本文修改后的做品务必以相同的许可发布。若有任何疑问,请与我联系。