本文來告訴大家一個新的技術DirectComposition,在 win7 之後(實際上是 vista),微軟正在考慮一個新的渲染機制 ...
本文來告訴大家一個新的技術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 項目,註意選擇比較高的目標。
如何寫顯示
現在創建項目,刪除所有的 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 倍縮放
參考:
【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 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名林德熙(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。