从GDI到Direct2D:基本准备

最近在做个游戏,因为不能用游戏引擎,所以一开始就选了MFC+GDI的组合,毕竟CImage类是相当好用的,结果发现游戏竟然在还没有加什么功能的时候就只能跑到30帧出头,我觉得有点悬,将来如果加上更多的功能的话,一旦卡到30帧以下就没法忍了。所以我去学了一下Direct2D,这个传说当中的GDI替代品。

网上现在Direct2D的资料不是很多,其中我感觉MSDN的几个样例比较适合新手入门,上面的例子都是Win32的,可能迁移到MFC有点障碍。这篇博客主要针对已经会用GDI做各种操作的人,使他们能够快速地迁移到Direct2D上。

我打算记录三件事在这里:一是基本准备,就是在正式干活之前你要做的事情(这一部分应该说是跟GDI最不一样的),第二是贴图,第三是渲染文字;这篇博客主要说说第一部分。

这几篇博客代码量会很大,但是这些代码都是可以复用的,也就是说,我们只需要将它扒下来,然后直接用就好了…………虽然我也研究了一整天;难点主要在于每部分之间的关系,这也使这篇笔记将着重讲的,实现当中的细节请去查阅MSDN。

首先,按照微软的示例所说的,我们应该创建一个.h文件,里面包括了我们常用的头文件:

#pragma once
// Windows Header Files:
#include <windows.h>

// C RunTime Header Files:
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>

#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")

template<class Interface>
inline void SafeRelease(Interface **ppInterfaceToRelease)
{
    if (*ppInterfaceToRelease != NULL)
    {
        (*ppInterfaceToRelease)->Release();

        (*ppInterfaceToRelease) = NULL;
    }
}

#ifndef Assert
#if defined( DEBUG ) || defined( _DEBUG )
#define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
#else
#define Assert(b)
#endif //DEBUG || _DEBUG
#endif

#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif

这里面比较重要的可能就是那个SafeRelease宏,我相信各位都知道它是干什么的……其他的我们可以暂时不关心。

然后就要说到资源,如果使用GDI的话,我们不需要什么前期准备,直接拿到DC往上面画就行了,但是D2D不一样,在干活之前,我们要先做些准备。这里是我自己封装的一个类,里面包含了常用的几种D2D需要的资源,在MFC中这些可以直接添加到View中,也可以让View继承这个类,享有这个类的成员和函数。

class CDirect2DMFCBase
{

private:
    HWND m_hwnd;
    ID2D1Factory* m_pDirect2dFactory;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    IWICImagingFactory* m_pWICImagingFactory;
    IDWriteFactory* m_pWriteFactory;

我们来分条看看这些都是干什么的:

ID2D1Factory* m_pDirect2dFactory;这是D2D的老大,是设备无关资源,只需要在程序一开始创建一次,之后就不用理了。

ID2D1HwndRenderTarget* m_pRenderTarget; RenderTarget的地位相当于GDI里面的DC,一会会频繁用到,它是设备相关资源,不着急初始化它;

IWICImagingFactory* m_pWICImagingFactory;这个东西也是设备无关资源,一开始初始化就好,如果你想贴图,那么就需要使用它;

IDWriteFactory* m_pWriteFactory;这是管渲染字体的设备无关资源,同样一开始初始化就好。

接下来我们可以写一个初始化的函数(为了代码简洁,我去掉了一些错误检查):

HRESULT CDirect2DMFCBase::InitializeD2D()
{
    HRESULT hr;
    // Initialize device-independent resources, such
    // as the Direct2D factory.
    CoInitialize(NULL);//初始化COM接口
    hr = CreateDeviceIndependentResources();
    return hr;
}

在一开始我们就是要创建所有的设备无关资源,CreateDeviceIndependentResources()实现如下:

HRESULT CDirect2DMFCBase::CreateDeviceIndependentResources()
{
    HRESULT hr = S_OK;

    // Create a Direct2D factory.
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);

    hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IWICImagingFactory,
        reinterpret_cast<void **>(&m_pWICImagingFactory)
        );

    hr = DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&m_pWriteFactory)
        );
    return hr;
}

这样就把所有的设备无关资源都创建完了。

然后,我们需要提供一个创建设备有关资源的方法,在需要的时候调用:

HRESULT CDirect2DMFCBase::CreateDeviceResources()
{
    HRESULT hr = S_OK;

    if (!m_pRenderTarget)
    {
        RECT rc;
        GetClientRect(m_hwnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);

        // Create a Direct2D render target.
        hr = m_pDirect2dFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hwnd, size),
            &m_pRenderTarget
            );

    }

    return hr;
}

大体上,就是创建了一个RenderTarget。需要注意的是,所有的示例都把这个函数写在了重绘函数中,目的是在设备丢失时重新获取设备,所以一开始才有那句if (!m_pRenderTarget)

接下来我们看看关键部分,也就是渲染的写法:

HRESULT CDirect2DMFCBase::OnRender()
{
    HRESULT hr = S_OK;

    hr = CreateDeviceResources();//这就是上面说的,这句话经常出现在绘图函数的开头,目的是为了防止设备丢失
    if (SUCCEEDED(hr))
    {
        m_pRenderTarget->BeginDraw();

        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

        D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();

        ///Draw Something

        hr = m_pRenderTarget->EndDraw();
    }

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }

    return hr;
}

那个BeginDraw和EndDraw是不是看起来有些眼熟?

到时候我们基本上是要重写这个函数的,在//Draw Something那里填进自己的绘图逻辑。

这个类的完整代码如下所示(目前还只是第一版,不知道有什么问题,用的话请谨慎)

#pragma once
#include "stdafx.h"
#include "D2D1Header.h"

class CDirect2DMFCBase
{

private:
    HWND m_hwnd;
    ID2D1Factory* m_pDirect2dFactory;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    IWICImagingFactory* m_pWICImagingFactory;
    IDWriteFactory* m_pWriteFactory;

public:
    CDirect2DMFCBase(void);
    ~CDirect2DMFCBase(void);

    // call methods for instantiating drawing resources
    HRESULT InitializeD2D();

    ID2D1HwndRenderTarget* GetRenderTarget();
    IWICImagingFactory* GetWICImagingFactory();
    IDWriteFactory* GetWriteFactory(){return m_pWriteFactory;}
    void SetHwnd(HWND v){m_hwnd = v;}

protected:
    // Initialize device-independent resources.
    HRESULT CreateDeviceIndependentResources();

    // Initialize device-dependent resources.
    virtual HRESULT CreateDeviceResources(); //由于通常会在这里加载一些外部资源,所以建议你重写这个函数;

    // Release device-dependent resource.
    void DiscardDeviceResources();

    // Draw content.
    virtual HRESULT OnRender();//建议重写这个函数来自己定义怎么重画

    // Resize the render target.
    void OnResize(UINT width,UINT height);
};

注意那两个virtual函数,你很有可能要重写那两个函数,一个用于加载资源,另一个就是实际的绘图函数。

#include "stdafx.h"
#include "Direct2DMFCBase.h"

CDirect2DMFCBase::CDirect2DMFCBase(void)
{
    m_hwnd= NULL;
    m_pDirect2dFactory = NULL;
    m_pRenderTarget = NULL;
    m_pWICImagingFactory = NULL;
}

CDirect2DMFCBase::~CDirect2DMFCBase(void)
{
    SafeRelease(&m_pDirect2dFactory);
    SafeRelease(&m_pRenderTarget);
}

HRESULT CDirect2DMFCBase::InitializeD2D()
{
    HRESULT hr;
    CoInitialize(NULL);
    // Initialize device-independent resources, such
    // as the Direct2D factory.
    hr = CreateDeviceIndependentResources();

    if (SUCCEEDED(hr))
    {

        // Because the CreateWindow function takes its size in pixels,
        // obtain the system DPI and use it to scale the window size.
        FLOAT dpiX, dpiY;

        // The factory returns the current system DPI. This is also the value it will use
        // to create its own windows.
        m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY);

    }

    return hr;
}

HRESULT CDirect2DMFCBase::CreateDeviceIndependentResources()
{
    HRESULT hr = S_OK;

    // Create a Direct2D factory.
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);

    hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IWICImagingFactory,
        reinterpret_cast<void **>(&m_pWICImagingFactory)
        );

    hr = DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&m_pWriteFactory)
        );
    return hr;
}

HRESULT CDirect2DMFCBase::CreateDeviceResources()
{
    HRESULT hr = S_OK;

    if (!m_pRenderTarget)
    {
        RECT rc;
        GetClientRect(m_hwnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);

        // Create a Direct2D render target.
        hr = m_pDirect2dFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hwnd, size),
            &m_pRenderTarget
            );

    }

    return hr;
}

void CDirect2DMFCBase::DiscardDeviceResources()
{
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&m_pWICImagingFactory);
}

HRESULT CDirect2DMFCBase::OnRender()
{
    HRESULT hr = S_OK;

    hr = CreateDeviceResources();
    if (SUCCEEDED(hr))
    {
        m_pRenderTarget->BeginDraw();

        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

        m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

        D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();

        hr = m_pRenderTarget->EndDraw();
    }

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }

    return hr;
}

void CDirect2DMFCBase::OnResize(UINT width, UINT height)
{
    if (m_pRenderTarget)
    {
        // Note: This method can fail, but it‘s okay to ignore the
        // error here, because the error will be returned again
        // the next time EndDraw is called.
        m_pRenderTarget->Resize(D2D1::SizeU(width, height));
    }
}

IWICImagingFactory* CDirect2DMFCBase::GetWICImagingFactory()
{
    return m_pWICImagingFactory;
}

ID2D1HwndRenderTarget* CDirect2DMFCBase::GetRenderTarget()
{
    return m_pRenderTarget;
}

好,大部分教程可能到这儿就完了,我在看的时候就特想说,你说的我都懂,可是怎么用呢?

我们以MFC为例:首先,这个类在地位上应该是跟View差不多的,你既可以把这些成员变量和函数塞进View里去,也可以让View继承它,当然View已经继承了CWnd,不过C++是支持多继承的,虽然我是第一次用……这里我让View继承了CDirect2DMFCBase。

首先在View中找地方调用一发初始化函数,比如在OnCreate里:

int CBomberManOnlineView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    Init();
    InitializeD2D();
    CreateDeviceResources();

    SetTimer(TIMER_RENDER, 1000/MAX_FPS, NULL);
    last_time = timeGetTime();
}

最后两行是我游戏中设置定时器用的。

然后重写CreateDeviceResources();:

HRESULT CBomberManOnlineView::CreateDeviceResources()
{
    HWND hwnd = AfxGetMainWnd()->m_hWnd;
    SetHwnd(hwnd);//告诉CDirect2DMFCBase窗口句柄

    HRESULT hr = CDirect2DMFCBase::CreateDeviceResources();

    //我们可以在这里加载图片等等

    return hr;
}

之后的OnRender也别忘了重写,框架和上面的基本一样,贴过来之后中间加上自己的绘图逻辑即可。

还有一件比较重要的事就是关于加载资源,它为什么要写在CreateDeviceResources()之后呢?我们在GDI中很少有人这么干。这在之后会解释。新手也会困扰,这样的话岂不是丢失一次设备就要重新加载一次资源?确实是这样的,只不过丢失设备的情况很罕见……

当你做完了上面所有的事情之后,你就搭好了一个D2D程序的框架(的一半),虽然你现在还是什么都画不出来,但是前面那个Base类是可以在所有D2D程序中使用的(大概),而且虽然代码量很大,不过你根本不需要知道太多的细节,尤其是Base类里面的那堆东西,可能你在之后的程序中不会再看他第二眼了。

基本准备工作到这里就结束了,下面是一个激动人心的环节,就是贴图了。

时间: 2024-10-10 21:41:28

从GDI到Direct2D:基本准备的相关文章

Direct2D教程(一)Direct2D已经来了,谁是GDI的终结者?

什么是Direct2D 一言以蔽之,就是Windows 7平台上的一个2D图形API,可以提供高性能,高质量的2D渲染.大多数人对Direct2D可能都比较陌生,以至于我之前在论坛上提到这个词的时候,有人竟然说你是不是写错了?可能大家比较熟悉的是Direct3D,因为D3D已经是当前游戏界的主流应用.在过去,3D世界是OpenGL与DirectX二分天下,但是由于OpenGL发展十分滞后,而且没有一个像微软这样强大的后台支柱,所以逐渐被DirectX所超越.回过头来说2D,过去Windows上的

基于DirectUI 的 SCW- App 主体部分的思路总结

基于DirectUI 的SCW- App 主体部分的思路总结 基于 C++ 的 SC DirectUI 界面库的想法与实现到今天也有近半个月了.一些新的想法与思路在学习和实践中得到了提高.推翻重来,重复再干,虽然是件苦事,但,不得不为之.这样才能有所提高. 上一篇:  基于DirectUI的SC设计规划的个人构想与目标 设计 SCW时,曾总结了一下程序的组成部分: 全局: 管理程序中唯一性的数据成员,对象成员等. 桌面: 与系统桌面相关的一些参数或功能.比如桌面的屏幕大小,鼠标形状等. 窗口:

基于DirectUI 的SCW- App 主体部分的思路总结

基于 C++ 的 SC DirectUI 界面库的想法与实现到今天也有近半个月了.一些新的想法与思路在学习和实践中得到了提高.推翻重来,重复再干,虽然是件苦事,但,不得不为之.这样才能有所提高. 上一篇:  基于DirectUI的SC设计规划的个人构想与目标 设计 SCW时,曾总结了一下程序的组成部分: 全局: 管理程序中唯一性的数据成员,对象成员等. 桌面: 与系统桌面相关的一些参数或功能.比如桌面的屏幕大小,鼠标形状等. 窗口: 负责注册窗.创建.显示.销毁窗口以及其他与窗口相关的功能. 事

[转] 基于DirectUI的SC设计规划的个人构想与目标

原文:http://my.oschina.net/isixth/blog/385092 SC设计的目标: SC是一个简单的基于DirectUI的界面库.设计SC,主要是基于个人爱好与学习的目的.在本人学习C++的这几个月来,将一点点收获与理解.想通过设计SC来进行提升与巩固.是一个重复造轮子的过程,也是一个个人学习提高的过程. 在学习C++的同时,也感到用C++做开发,界面设计,是一个基础且必须要做的事.优秀.成熟且系统性的有QT等,开源的更是不少,但学习与了解别人的代码,看是一个基础,自己写,

基于DirectUI的SC设计规划的个人构想与目标

SC设计的目标: SC是一个简单的基于DirectUI的界面库.设计SC,主要是基于个人爱好与学习的目的.在本人学习C++的这几个月来,将一点点收获与理解.想通过设计SC来进行提升与巩固.是一个重复造轮子的过程,也是一个个人学习提高的过程. 在学习C++的同时,也感到用C++做开发,界面设计,是一个基础且必须要做的事.优秀.成熟且系统性的有QT等,开源的更是不少,但学习与了解别人的代码,看是一个基础,自己写,能更深刻地掌握基础.所以想通过自己的学习和积累,逐步地,累积性地开发设计一个基于Dire

在 WinForm 中使用 Direct2D

在 C# 的 WinForm 应用中,界面的绘制使用的是 GDI+.不过在一些特别的应用中,可能需要用硬件加速来提高绘制的效率.下面就来介绍两种在 WinForm 应用中嵌入 Direct2D 的方法. 这里所谓的“嵌入”,指的是只有窗口的某一部分应用 Direct2D 绘制(用一些控件承载),而不是整个窗口都使用 Direct2D 绘制.这是一种混合方案,需要用硬件加速的部分由自己来绘制,其它部分仍然可以使用现有的 WinForm 技术. 至于 Direct2D 的类库,我仍然使用 Sharp

将System.Drawing.Bitmap转换为Direct2D.D2DBitmap

最近在尝试Direct2D编程,挺好玩的. 但是有时候还是会用到GDI+来生成图片,但D2D绘图需要用到自己的D2DBitmap类. 因此需要转换,查阅了下网上的资料,写了这么一个方法: 1 using System; 2 using System.Windows.Forms; 3 using System.Linq; 4 using System.Text; 5 using System.Diagnostics; 6 using DX = SharpDX; 7 using D2D = Shar

D2D引擎与GDI\GDI+绘制效果对比

本例主要是对比D2D和GDI在绘制文字.线条的区别,以及D2D与GDI+在绘制图片时的区别. D2D是基于COM组件开发的,使用前的CoInitialize(NULL)是必须的:另外,GDI+的初始化GdiplusStartup()也别忘了. 废话少说,完整代码如下: // D2DDemo.cpp : 定义应用程序的入口点. // #include "stdafx.h" #include "D2DDemo.h" #include <D2D1.h> #in

GDI、DirectX、WPF、Winform等绘图相关关系梳理

绘图引擎方面,Windows下常用的为GDI.GDI+.DirectX.OpenGL等引擎,而Winform.MFC.WPF.Qt等则是用来实现对这些引擎调用的工具,属于更上层的范围,这几个绘图引擎中,前三者都是微软自己的东西,因此如果用C#来写的话用这几个引擎相对就会方便些. WPF采用的是DirectX作为底层绘图引擎,Winform和MFC则都采用GDI+,因此,Winform和MFC基本的绘图效率应该是接近的. DirectX是可以实现硬件加速的,如果PC上有GPU的话,那么WPF的图形