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 國際許可協議進行許可。歡迎轉載、使用、從新發布,但務必保留文章署名林德熙,不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫。

相關文章
相關標籤/搜索