在 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 = ,
Usage = Usage.RenderTargetOutput,
OutputHandle = dcRenderControl.Handle,
IsWindowed = true,
// 這裡寬度和高度都是 0,表示自動獲取。
ModeDescription = new ModeDescription(, , new Rational(, ), Format),
SampleDescription = new SampleDescription(, ),
SwapEffect = SwapEffect.Discard
};
this.swapChain = new SwapChain(dxgiDevice.GetParent<Adapter>().GetParent<DXGIFactory>(),
d3DDevice, swapChainDesc);
// 建立 BackBuffer。
this.backBuffer = Surface.FromSwapChain(this.swapChain, );
// 從 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 中的實際應用,可以參見我的拼圖遊戲

所有原始碼和用到的類庫都可以在這裡下載。

原文連結:在 WinForm 中使用 Direct2D