在 C# 的 WinForm 应用中,界面的绘制使用的是 GDI+。不过在一些特别的应用中,可能需要用硬件加速来提高绘制的效率。下面就来介绍两种在 WinForm 应用中嵌入 Direct2D 的方法。
这里所谓的“嵌入”,指的是只有窗口的某一部分应用 Direct2D 绘制(用一些控件承载),而不是整个窗口都使用 Direct2D 绘制。这是一种混合方案,需要用硬件加速的部分由自己来绘制,其它部分仍然可以使用现有的 WinForm 技术。
至于 Direct2D 的类库,我仍然使用 ,使用 控件承载 Direct2D 渲染。
一、使用 HwndRenderTarget
HwndRenderTarget(),在 SharpDX 中对应的类是 ,是将窗口句柄(hwnd)作为渲染目标的类,利用它可以非常容易的在窗口中嵌入 Direct2D 渲染。
它的用法非常简单,只要先创建一个 Direct2D 工厂(),接下来直接创建 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 的相关方法进行绘制。需要特别注意的是,如果控件的大小发生了改变,必须调用 方法,重新调整渲染目标的尺寸才可以,否则会导致绘制结果被拉伸,引起失真。
这个方法的优点就是非常简单易用,而且基本上只要操作系统是 Windows 7 及更高版本,都可以正常绘制(在第 8 行设置 Type = RenderTargetType.Default,会根据情况自动选择硬件渲染或软件渲染),适用范围很广。
其缺点首先是不能在 Windows 8 的 Store app 中使用,其次是与 Direct2D 的一些高级功能不能很好的结合。Direct2D 的很多高级功能,如渲染特效,都是需要与 结合使用的,而 HwndRenderTarget 不能直接使用 DeviceContext 的渲染结果。
二、使用 DeviceContext
DeviceContext 则是一个比较“万能”的类,它可以将结果绘制到任意的 上,在离线渲染与多线程渲染中是非常有用的。
使用 DeviceContext 来绘制 Direct2D 的做法则要复杂很多,由于 DeviceContext 并不能直接将结果渲染到窗口句柄上,因此需要在 DeviceContext 和 hwnd 之间建立起联系,这里使用的是 DXGI 的 。
大致的步骤,是先利用 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().QueryInterface ();// 创建 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 ().GetParent (), 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 特效的应用,可以参见《》。Direct2D 在 WinForm 中的实际应用,可以参见我的。
所有源代码和用到的类库都可以在下载。