在 C# 的 WinForm 應用中,界面的繪制使用的是 GDI+。不過在一些特別的應用中,可能需要用硬件加速來提高繪制的效率。下面就來介紹兩種在 WinForm 應用中嵌入 Direct2D 的方法。
這里所謂的“嵌入”,指的是只有窗口的某一部分應用 Direct2D 繪制(用一些控件承載),而不是整個窗口都使用 Direct2D 繪制。這是一種混合方案,需要用硬件加速的部分由自己來繪制,其它部分仍然可以使用現有的 WinForm 技術。
至於 Direct2D 的類庫,我仍然使用 SharpDX 類庫,使用 SharpDX.Windows.RenderControl 控件承載 Direct2D 渲染。
一、使用 HwndRenderTarget
HwndRenderTarget(ID2D1HwndRenderTarget interface),在 SharpDX 中對應的類是 WindowRenderTarget,是將窗口句柄(hwnd)作為渲染目標的類,利用它可以非常容易的在窗口中嵌入 Direct2D 渲染。
它的用法非常簡單,只要先創建一個 Direct2D 工廠(SharpDX.Direct2D1.Factory),接下來直接創建 WindowRenderTarget 實例,然后就可以使用了。其核心代碼如下所示:
// 創建 Direct2D 單線程工廠。 Factory factory = new Factory(FactoryType.SingleThreaded); // 渲染參數。 RenderTargetProperties renderProps = new RenderTargetProperties { PixelFormat = D2PixelFormat, Usage = RenderTargetUsage.None, Type = RenderTargetType.Default }; // 渲染目標屬性。 HwndRenderTargetProperties hwndProps = new HwndRenderTargetProperties() { // 承載控件的句柄。 Hwnd = hwndRenderControl.Handle, // 控件的尺寸。 PixelSize = new Size2(hwndRenderControl.ClientSize.Width, hwndRenderControl.ClientSize.Height), PresentOptions = PresentOptions.None }; // 渲染目標。 hwndRenderTarget = new WindowRenderTarget(factory, renderProps, hwndProps) { AntialiasMode = AntialiasMode.PerPrimitive };
一般的用法,就是在控件的 Paint 事件中,調用 hwndRenderTarget 的相關方法進行繪制。需要特別注意的是,如果控件的大小發生了改變,必須調用 WindowRenderTarget.Resize 方法,重新調整渲染目標的尺寸才可以,否則會導致繪制結果被拉伸,引起失真。
這個方法的優點就是非常簡單易用,而且基本上只要操作系統是 Windows 7 及更高版本,都可以正常繪制(在第 8 行設置 Type = RenderTargetType.Default,會根據情況自動選擇硬件渲染或軟件渲染),適用范圍很廣。
其缺點首先是不能在 Windows 8 的 Store app 中使用,其次是與 Direct2D 的一些高級功能不能很好的結合。Direct2D 的很多高級功能,如渲染特效,都是需要與 DeviceContext 結合使用的,而 HwndRenderTarget 不能直接使用 DeviceContext 的渲染結果。
二、使用 DeviceContext
DeviceContext 則是一個比較“萬能”的類,它可以將結果繪制到任意的 Image 上,在離線渲染與多線程渲染中是非常有用的。
使用 DeviceContext 來繪制 Direct2D 的做法則要復雜很多,由於 DeviceContext 並不能直接將結果渲染到窗口句柄上,因此需要在 DeviceContext 和 hwnd 之間建立起聯系,這里使用的是 DXGI 的 SwapChain。
大致的步驟,是先利用 DeviceContext,將結果繪制到一塊緩沖區中(BackBuffer 后台緩沖區),然后由 SwapChain 將后台緩沖區的內容,呈現到 hwnd 上,完成一次繪制。
創建時,需要創建 Direct3D Device、DXGI Device 和 Diect2D Device,這樣才能創建 DeviceContext。接着再創建 SwapChain 和相應的 Surface 作為緩沖區,才能正常使用。相應的代碼如下所示,由於會有很多重名類,因此我用 using 語句定義了很多類型別名,代碼看起來會亂一些:
// 創建 Dierect3D 設備。 D3D11Device d3DDevice = new D3D11Device(DriverType.Hardware, DeviceCreationFlags.BgraSupport); DXGIDevice dxgiDevice = d3DDevice.QueryInterface<D3D11Device1>().QueryInterface<DXGIDevice>(); // 創建 Direct2D 設備和工廠。 D2D1Device d2DDevice = new D2D1Device(dxgiDevice); this.deviceContext = new DeviceContext(d2DDevice, DeviceContextOptions.None); // 創建 DXGI SwapChain。 SwapChainDescription swapChainDesc = new SwapChainDescription() { BufferCount = 1, Usage = Usage.RenderTargetOutput, OutputHandle = dcRenderControl.Handle, IsWindowed = true, // 這里寬度和高度都是 0,表示自動獲取。 ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format), SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.Discard }; this.swapChain = new SwapChain(dxgiDevice.GetParent<Adapter>().GetParent<DXGIFactory>(), d3DDevice, swapChainDesc); // 創建 BackBuffer。 this.backBuffer = Surface.FromSwapChain(this.swapChain, 0); // 從 BackBuffer 創建 DeviceContext 可用的目標。 this.targetBitmap = new Bitmap1(this.deviceContext, backBuffer); this.deviceContext.Target = targetBitmap;
繪制同樣是在控件的 Paint 事件中繪制的,但要記得在最后調用 swapChain.Present 方法,令 SwapChain 將結果呈現在 hwnd 中。
在改變控件大小時,也要復雜很多。因為在調用 SwapChain 的 ResizeBuffers 方法之前,需要先將與 SwapChain 相關的資源全部釋放掉,才能調整緩沖區的尺寸,最后還要重建相關的資源。
使用 DiviceContext 的優點很多,包括:
- 可以用於 Windows Store apps。
- DeviceContext 可以繪制到其它目標,只要簡單的改變 Target 屬性即可。
- 可以創建 Direct2D 特效。
- 可以使用多個 DeviceContext 來進行多線程繪制,可以參見這里。
在下面的 Demo 中,我也簡單的演示了其它 DeviceContext 的繪制結果,可以直接被繪制到界面中。
該方法的缺點,就是創建略微復雜,而且需要電腦在一定程度上支持 Direct3D 才可以(具體要支持到什么程度,還不清楚~)。
關於 Direct2D 特效的應用,可以參見《C# 使用 Direct2D 實現斜角效果》。Direct2D 在 WinForm 中的實際應用,可以參見我的拼圖游戲。
所有源代碼和用到的類庫都可以在這里下載。