码迷,mamicode.com
首页 > 其他好文 > 详细

从GDI到Direct2D:基本准备

时间:2015-04-06 08:57:08      阅读:403      评论:0      收藏:0      [点我收藏+]

标签:direct2d   mfc   gdi   游戏   gdi+   

最近在做个游戏,因为不能用游戏引擎,所以一开始就选了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类里面的那堆东西,可能你在之后的程序中不会再看他第二眼了。

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

从GDI到Direct2D:基本准备

标签:direct2d   mfc   gdi   游戏   gdi+   

原文地址:http://blog.csdn.net/u011808175/article/details/44891571

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!