DX9入門筆記1-D3D初始化


對3D編程期待已久,卻一直葉公好龍淺嘗輒止。近期在公司實習卻無具體的工作安排,琢磨着學習個新的手藝,就又想起了3D Programming。這次從大名鼎鼎的龍書(Introduction to 3D Game Progamming with Directx 9.0)開始學起,堅持...
作為入門的第一篇筆記,本文對D3D初始化過程做了總結。DirectX3D的初始化需要做兩部分工作:創建一個窗口、創建IDirect3DDevice9對象,窗口用於展示D3D渲染出來的場景;IDirect3DDevice9則是一個C++對象,表示用來displaying3D圖形的物理設備。本文也主要包含兩個部分:

  1. D3D的初始化
  2. 龍書中使用簡單框架的代碼分析

1.D3D的初始化

龍書中將一個D3D的初始化過程分為4個步驟:

  1. 獲取IDirect3D9接口的指針,該接口通常用於取得物理設備信息和創建IDirect3DDevice9對象。
  2. 檢測設備的處理能力(D3DCAPS9),檢測顯示適配器(display adapter)是否支持頂點的硬件處理。
  3. 初始化結構體D3DPRESENT_PARAMETERS,該結構指定了一些參數用來創建IDirect3DDevice9。
  4. 創建IDeirect3DDevice9對象。

1.1取得IDirect3D9指針

D3D中有專門的函數來獲取IDirect3D9指針

	IDirect3D9* d3d9 = NULL;
	d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

D3D_SDK_VERSION是創建IDirect3D9唯一需要的參數,該參數能夠保證程序是基於當前版本的DirectX生成的。

1.2 檢測硬件的處理能力

在創建IDirect3DDevice9時需要指定其頂點處理類型(硬件或者軟件),硬件頂點處理速度較快,但是不是所有的顯示適配器都具有該能力。可以調用函數GetDeviceCaps,該函數的原型如下

HRESULT IDirect3D9::GetDeviceCaps(
    UINT Adapter,
    D3DDEVTYPE DeviceType,
    D3DCAPS9 *pCaps
);
  1. Adpater 指定要檢測的顯示適配器
  2. DeviceType 硬件類型,D3DDEVTYPE_HAL(硬件設備)、D3DDEVTYPE_REF(軟件設備)。
  3. pCaps 返回D3DCAPS9結構,包含了硬件的所支持的各種特性。
//  檢查硬件的頂點處理能力
D3DCAPS9 caps;
d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);

int vp = 0 ; //頂點處理類型
if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
	vp  = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
	vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

1.3 初始化 D3DPRESENT_PARAMETERS

D3DPRESENT_PARAMETERS用於指定IDirect3Device9的一些特性,其聲明如下:

typedef struct _D3DPRESENT_PARAMETERS_
{
    UINT                BackBufferWidth;
    UINT                BackBufferHeight;
    D3DFORMAT           BackBufferFormat;
    UINT                BackBufferCount;

    D3DMULTISAMPLE_TYPE MultiSampleType;
    DWORD               MultiSampleQuality;

    D3DSWAPEFFECT       SwapEffect;
    HWND                hDeviceWindow;
    BOOL                Windowed;
    BOOL                EnableAutoDepthStencil;
    D3DFORMAT           AutoDepthStencilFormat;
    DWORD               Flags;

    /* FullScreen_RefreshRateInHz must be zero for Windowed mode */
    UINT                FullScreen_RefreshRateInHz;
    UINT                PresentationInterval;
} D3DPRESENT_PARAMETERS;
  • BackBufferWidth,BackBufferHeight bakck buffer的寬和高
  • BackBufferFormat back buffer的像素格式,例如:D3DFMT_A8R8G8B8
  • BackBufferCount back buffer的數量,通常為1
  • MultiSampleType,MultiSampleQuality 抗鋸齒的類型和質量
  • SwapEffect 指定buffer在交換鏈(flipping chain)是如何被交換的,改值是D3DSWAPEFFECT類型的枚舉,其中D3DSWAPEFFECT_DISCARD是效率 最高的。
  • hDeviceWindow 用於繪制的窗口句柄
  • Windowed 是窗口模式還是全屏,true為全屏。
  • EnableAutoDepthStencil 設置為true,D3D將自動的創建深度/模板buffer
  • AutoDepthStencilFormat 深度/模板buffer的格式
  • Flags 附加的一些特性,可以指定為0或者D3DPRESENTFLAG中某個值。比較推薦下面兩個值
    • D3DPRESENTFLAG_LOCKABLE_BACKBUFFER back buffer能給被鎖定,這會降低程序的性能。
    • D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL 在present下一個buffer時,當前的深度/模板buffer會被“丟棄(discard)",這有利於提升程序的性能。”discard“的意思是深度/模板buffer所占用的存儲空間會被丟棄或者不可用。
  • FullScreen_RefreshRateInHz 刷新頻率,使用D3DPRESENT_RATE_DEFAULT指定為默認的刷新頻率。
  • PresentationInterval D3DPRESENT成員,兩個常用的標志
    • D3DPRESENT_INTERVAL_IMMEDIATE 立即呈現
    • D3DPRESENT_INTERVAL_DEFAULT D3D選中呈現的速度,通常等於刷新率。
      D3DPRESENT_PARAMETERS的填充如下
	D3DPRESENT_PARAMETERS d3dpp;

	d3dpp.BackBufferWidth            = width;
	d3dpp.BackBufferHeight           = height;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 1;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = windowed;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

1.4 創建IDirect3Device9

填充D3DPRESENT_PARAMETERS后,IDirect3Device9就很簡單了

d3d9->CreateDevice(D3DADAPTER_DEFAULT,deviceType,hwnd,vp,&d3dpp,device);

2. 簡單框架 d3dUtility.h/cpp

在龍書的第二章提供了一個簡單框架,用於D3D的初始化工作。
d3dUtility.h如下:

namespace d3d
{
	bool InitD3D(HINSTANCE hInstance, //應用程序實例
		int width,int height, //Back buffer的size
		bool windowed, //是否全屏
		D3DDEVTYPE deviceType, //設備類型 HAL或者REF
		IDirect3DDevice9** device); // 創建的設備 [out]

	int EnterMsgLoop(bool (*ptr_display)(float timeDelta));

	LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);

	template<class T> void Release(T t)
	{
		if(t)
		{
			t->Release();
			t = NULL;
		}
	}

	template<class T> void Delete(T t)
	{
		if(t)
		{
			delete t;
			t = NULL;
		}
	}
}

函數Init3D創建程序的主窗口,並進行D3D的初始化工作,初始化完成返回IDirect3Device9的指針。
EnterMsgLoop 進入窗口的消息循環,它的參數是一個顯示函數,該函數是用於圖形的繪制。
Release 用於釋放一個COM接口
Delete 用於刪除一個對象
WndProc 是窗口的消息處理函數。

2.1 亂碼處理

VS中的項目編碼默認的是Unicode,這樣在進行一些字符串處理(例如設置窗口標題)非常容易出現亂碼。這就需要加入頭文件tchar.h

#include <tchar.h>

在使用字符串的時候使用宏_T轉換就可以避免亂碼,例如:

wc.lpszClassName = _T("Direct3D9App");

2.2 PeekMessage 和 GetMessage的區別

在該框架中,進入窗口循環的代碼如下:

int d3d::EnterMsgLoop(bool (*ptr_display)(float timeDelta))
{
	MSG msg;
	::ZeroMemory(&msg,sizeof(MSG));

	static float lastTime = (float)timeGetTime();

	while(msg.message != WM_QUIT)
	{
		if(::PeekMessage(&msg,0,0,0,PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
		{
			float currTime = static_cast<float>(timeGetTime());
			float timeDelta = (currTime - lastTime) * 0.001f;

			ptr_display(timeDelta);

			lastTime = currTime;
		}
	}
	return msg.wParam;
}

在上述代碼中取得消息使用的是PeekMessage而不是常用的GetMessagePeekMessageGetMessage都是從消息隊列中抓取消息,如果消息隊列為空,程序的主線程會被操作系統掛起。過一段時間,當操作系統再次執行該線程時,如果消息隊列仍然為空,這時這兩個函數的行為則就不一樣了:

  • GetMessage 會直接返回,阻塞當前線程,操作系統去執行其他的線程。
  • PeekMessage 則不同,它會取回控制權,使得當前線程再執行一段時間。上面的代碼中,消息隊列如果為空就執行display函數繪制圖形。

3 總結

本文是龍書第二章的學習筆記,總結了D3D初始化的4個步驟,並對龍書中提供的代碼框架作為簡單分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM