DirectX/9.0/Direct3D/初始化
Direct3D 中最重要的部分之一是初始化。有兩種方法可以實現它:正確的方法(讀:繁瑣、困難、煩人,但值得學習),本章的絕大部分內容都圍繞它展開,以及簡單的方法,它將用作介紹,因為某些部分無論你如何操作都必須使用。在本節結束時,我們將將其應用到我們的基準測試應用程式中。
開始初始化 Direct3D 時,您必須做的第一件事是建立一個 Direct3D 物件。此物件將在本書的其餘部分中至關重要。幸運的是,這並不意味著它很難。實際上,您唯一需要做的就是呼叫一個函式
IDirect3D9* Direct3DCreate9( UINT SDKVersion );
此函式返回它建立的 IDirect3D9 介面的地址,即我們的 Direct3D 物件。您需要使用引數告訴它 SDK 的 DirectX 版本。通常您只傳遞 D3D_SDK_VERSION,除非您出於某種原因想要舊的介面。
IDirect3D9* d3dobject = Direct3DCreate9( D3D_SDK_VERSION );
//Please note that DirectX uses the same naming conventions as Win32,
//so you can also initialize the variable as a LPDIRECT3D9 instead of a IDirect3D9*.
//Also, it's a good idea to make sure the device was created.
//If it wasn't, d3dobject will be null, and DirectX9 or greater probably isn't installed
就這樣。不幸的是,其餘過程並不那麼簡單。您需要的下一件事是一組呈現引數,這些引數儲存在 D3DPRESENT_PARAMETERS 結構中。如果您檢視內部,您會注意到有很多變數。您不需要填充所有變數,甚至不需要填充大多數變數。只需確保它們都被清零即可。您唯一關注的兩個變數是 Windowed 和 SwapEffect。對於 Windowed,您可以簡單地將其設定為 true。SwapEffect 取決於您想要什麼。對於本教程,最快的 D3DSWAPEFFECT_DISCARD 將可以正常工作。到目前為止,您的程式碼應如下所示
D3DPRESENT_PARAMETERS d3dpresent;
memset( &d3dpresent, 0, sizeof( D3DPRESENT_PARAMETERS ) );
d3dpresent.Windowed = TRUE;
d3dpresent.SwapEffect = D3DSWAPEFFECT_DISCARD;
現在我們可以真正建立裝置了。這可以透過呼叫 d3dobject 引用的函式來完成
IDirect3D9::CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS * pPresentationParameters, IDirect3DDevice9 ** ppReturnedDeviceInterface );
這將返回 0(成功)或錯誤。有很多引數,但大多數引數的值總是有效的。第一個是介面卡編號。現在,我們可以只使用 D3DADAPTER_DEFAULT,它總是有效的。DeviceType 也有一個總是有效的引數:D3DDEVTYPE_REF。這非常慢,除非絕對必要,否則不應該使用;但是現在,它就足夠了(它也不能在沒有安裝 SDK 庫的情況下使用,因此不要在其他計算機上嘗試)。我們的下一個引數是我們視窗的控制代碼,這不在本書中介紹(請參閱 Windows 程式設計)。BehaviorFlags 將與 D3DCREATE_SOFTWARE_VERTEXPROCESSING 一起使用,這是另一個緩慢的選擇。pPresentationParameters(為什麼微軟使用如此長的名稱?)獲取我們呈現引數結構的地址,而 ppReturnedDeviceInterface 獲取我們要填充的指標的指標。因此,我們的呼叫如下所示
d3dobject->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, window, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpresent, &d3ddevice );
就是這樣初始化裝置的方法!當然,您幾乎肯定想要一個更快、功能更強大的裝置,您確實應該檢查錯誤,這樣即使您的程式崩潰,使用者也不會丟失記憶體,但這將在下一節中介紹。
所有(良好)使用 DirectX 的商業遊戲都會以正確的方式初始化裝置。幾乎所有(如果不是全部)都允許您選擇其引擎執行的圖形卡或介面卡。因此,我們將從這裡開始。確定可用的圖形卡非常有用。首先,它為使用者提供了一定程度的自由,這始終很重要。對於商業遊戲,它還允許他們建立包含適合該卡的設定的列表,從而使使用者更容易操作。我們當然會將此功能實現到我們的基準測試應用程式中,儘管這僅僅是為了第一個原因。您必須做的第一件事(當然是在初始化 IDirect3D9 介面之後)是確定可用的介面卡數量。這是一個簡單的函式
UINT IDirect3D9::GetAdapterCount( );
它返回可用的介面卡數量。我們需要做的下一件事是獲取 D3DADAPTER_IDENTIFIER9 結構陣列。建立陣列後,我們可以使用迴圈和呼叫來填充它
HRESULT IDirect3D9::GetAdapterIdentifier( UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9 * pIdentifier );
如果引數有效,則它返回 D3D_OK,如果出現錯誤,則返回 D3DERR_INVALIDCALL。傳遞給介面卡的值可以在 [0,GetAdapterCount()) 內。Flags 應為 0(除非我們希望 Windows 下載新的驅動程式證書,我們沒有),而 pIdentifier 應該是當前索引的地址。現在,我們的程式碼如下所示
UINT adaptercount = d3dobject->GetAdapterCount( );
D3DADAPTER_IDENTIFIER9* adapters = ( D3DADAPTER_IDENTIFIER9* )malloc( sizeof( D3DADAPTER_IDENTIFIER9 ) * adaptercount );
for( int i = 0; i < adaptercount; i++ )
{
d3dobject->GetAdapterIdentifier( i, 0, &( adapters[i] ) );
}
然後,我們可以根據使用者選擇的影片卡(使用訊息處理程式)或程式根據 D3DADAPTER_IDENTIFIER9 結構中的詳細資訊認為最佳的卡來選擇應用程式將使用的介面卡。
選擇介面卡後,我們可以使用 D3DCAPS9 結構找出它能做什麼。不幸的是,這是一個有點漫長的過程。您需要呼叫
HRESULT IDirect3D9::GetDeviceCaps( INT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9 * pCaps );
引數有點棘手。Adapter 和 pCaps 很簡單,它們只是我們選擇的介面卡編號和我們要填充的 D3DCAPS 結構的指標。DeviceType 是問題。它必須使用另一個函式確定。我們可以使用以下方法檢查硬體裝置
HRESULT IDirect3D9::CheckDeviceType( UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat,
BOOL Windowed );
呼叫此函式後,我們可以根據它是否返回 D3D_OK 來檢查裝置型別是否有效。但是,引數需要另一個資訊。對於 Adapter,我們提供介面卡編號。DeviceType 是我們要檢查的型別,應該是 D3DDEVTYPE_HAL。由於我們是視窗化的,因此應該是 TRUE。DisplayFormat 和 BackBufferFormat 是我們需要確定的,它們通常(對於全屏模式,它們必須)應該相同。它們可以透過巢狀的 if 語句在試錯法中解決,從我們喜歡的格式開始,並遍歷大多數格式。以下是有效的顯示和後備緩衝區格式列表: http://msdn.microsoft.com/en-us/library/bb172558(VS.85).aspx 如果它從未返回 D3D_OK,則您只能使用 D3DDEVTYPE_REF 或 D3DDEVTYPE_SW。我們將跳過具有 alpha 欄位的格式,因為它們不受全屏模式的支援,我們將找到 HAL 或 REF 的最佳格式,因為我們以後會需要它。以下是我們的程式碼的外觀
//adapternum is to have been defined earlier in the program and contains the choice of adapter
//fullscreen is also to have been previously defined, and contains either TRUE or FALSE
D3DCAPS9 caps;
D3DFORMAT format;
D3DDEVTYPE devicetype; //We'll need this and format later
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, window ) != D3D_OK )
//Best format, almost always works
{
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_HAL, D3DFMT_X1R5G5B5, D3DFMT_X1R5G5B5, window ) != D3D_OK ) {
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_HAL, D3DFMT_R5G6B5, D3DFMT_R5G6B5, window ) != D3D_OK ) {
devicetype = D3DDEVTYPE_REF;
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_REF, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, window ) != D3D_OK ) {
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_REF, D3DFMT_X1R5G5B5, D3DFMT_X1R5G5B5, window ) != D3D_OK ) {
if( d3dobject->CheckDeviceType( adapternum, D3DDEVTYPE_REF, D3DFMT_R5G6B5, D3DFMT_R5G6B5, window ) != D3D_OK ) {
//Error - No valid format or device detected!
//You need to decide what to do - if it is windowed, you can try using D3DFMT_UNKNOWN
} else {
format = D3DFMT_R5G6B5;
} } else {
format = D3DFMT_X1R5G5B5;
} } else {
format = D3DFMT_X8R8G8B8;
} } else {
devicetype = D3DDEVTYPE_HAL;
format = D3DFMT_R5G6B5;
} } else {
devicetype = D3DDEVTYPE_HAL;
format = D3DFMT_X1R5G5B5;
} } else {
devicetype = D3DDEVTYPE_HAL;
format = D3DFMT_X8R8G8B8;
}
d3dobject->GetDeviceCaps( ( INT )adapternum, devicetype, &caps );
一些瞭解如何執行此操作的人可能會指出,您只需使用 IDirect3D9*->GetAdapterDisplayMode 即可確定視窗化應用程式的有效格式。我沒有在這裡執行此操作,因為它在全屏模式下不起作用,而上面的程式碼總是有效的。無論如何,您現在擁有該介面卡的裝置功能。我們該怎麼用它呢?我們可以做的第一件事是找出硬體是否支援變換或光照。我們可以使用 caps 中的 DevCaps 變數來做到這一點。您只需使用一些按位運算即可檢視它是否具有 D3DDEVCAPS_HWTRANSFORMANDLIGHT。如果有,我們將在 CreateDevice 中使用 D3DCREATE_HARDWARE_VERTEXPROCESSING 而不是 D3DCREATE_SOFTWARE_VERTEXPROCESSING。我們還能夠提取有關我們可以透過它傳遞的其他資訊,例如 D3DCREATE_PUREDEVICE(對除錯不利,但對速度很有利),它需要硬體。
DWORD vtx_proc;
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) {
vtx_proc = D3DCREATE_HARDWARE_VERTEXPROCESSING;
if( caps.DevCaps & D3DDEVCAPS_PUREDEVICE ) {
vtx_proc |= D3DCREATE_PUREDEVICE;
}
} else {
vtx_proc = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
我們還可以確定可以使用的呈現間隔。它將有效間隔儲存為其 PresentationIntervals 欄位中的位掩碼。此頁面上的任何值都可以使用: http://msdn.microsoft.com/en-us/library/bb172585(VS.85).aspx 。如果我們想檢視裝置是否支援某種形式,我們只需執行以下操作
DWORD presinterval;
if( caps.PresentationIntervals & D3DPRESENT_INTERVAL_FOUR )
{
presinterval = D3DPRESENT_INTERVAL_FOUR;
}
CAPS 結構中還有更多有關裝置的資訊,但目前用處不大(讀:我不知道該如何使用它)。
獲取有效的深度模板與獲取有效格式非常相似。它需要呼叫
IDirect3D9::CheckDeviceFormat( UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage,
D3DRESOURCETYPE RType, D3DFORMAT CheckFormat );
並在成功時返回 D3D_OK。非顯而易見的引數是 Usage、RType 和 CheckFormat。Usage 指示我們要檢查的格式型別;深度模板;並且應該是 D3DUSAGE_DEPTHSTENCIL。RType 指示我們使用的資源形式,應該是 D3DRTYPE_SURFACE。CheckFormat 是我們要檢查的深度模板格式。確定此格式需要另一種試錯法;並且使用格式列表中名稱中帶有“D”的格式: http://msdn.microsoft.com/en-us/library/bb172558(VS.85).aspx 。以下是一個示例
D3DFORMAT depthstencil;
if( d3dobject->CheckDeviceFormat( adapter, devicetype, format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32 ) != D3D_OK )
{
...
} else {
depthstencil = D3DFMT_32;
}
多重取樣是另一個試錯過程,幸運的是也是最後一個。我們將確定要使用的兩個值:多重取樣級別和多重取樣的最大質量。這兩個值都透過以下函式確定
HRESULT IDirect3D9::CheckDeviceMultiSampleType( UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat,
BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels );
如果多重取樣型別有效,CheckDeviceMultiSampleType 返回 D3D_OK。它還會在 pQualityLevels 中提供最大質量級別。我們的 D3DFORMAT 引數應該是我們的後緩衝區格式,而 pQualityLevels 應該是 DWORD 的地址。D3DMULTISAMPLE_TYPE 可以是此處列出的任何值:http://msdn.microsoft.com/en-us/library/bb172574(VS.85).aspx 始終如一,以下是我們使用的示例
DWORD quality;
D3DMULTISAMPLE_TYPE multisample;
if( d3dobject->CheckDeviceMultiSampleType( adapter, devicetype, format, !fullscreen, D3DMULTISAMPLE_16_SAMPLES,
&quality ) != D3D_OK )
{
...
} else {
multisample = D3DMULTISAMPLE_16_SAMPLES;
}
我們的下一個目標是確定可用的最佳顯示模式。與其他任務相比,這相當容易。有兩種適用的方法可以做到這一點。第一個是簡單地使用使用者當前正在使用的模式。你可以透過對以下內容進行一次呼叫來實現這一點
HRESULT IDirect3D9::GetAdapterDisplayMode( UINT Adapter, D3DDISPLAYMODE * pMode );
pMode 是我們關心的返回值。它是一個結構,它提供了使用者正在使用的解析度、重新整理率和格式。這種方法快捷簡便,但適應性不如某些情況下所需的那樣強。下一種方法更復雜,但適應性更強。你需要兩個函式
UINT IDirect3D9::GetAdapterModeCount( UINT Adapter, D3DFORMAT Format );
和
HRESULT EnumAdapterModes( UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode );
GetAdapterModeCount 很簡單。它獲取介面卡和後緩衝區格式,並提供顯示模式的數量。EnumAdapterModes 稍微複雜一些,但仍然很簡單。它也獲取介面卡和後緩衝區格式,但它還需要一個數字和一個儲存模式的位置。數字必須小於 GetAdapterModeCount 返回的值,而 pMode 只需要是某些可用的記憶體。你通常在迴圈中使用這兩個函式來查詢你想要的值。例如
D3DDISPLAYMODE dispmode, bestmode;
d3dobject->EnumAdapterModes( adapter, format, 0, &bestmode );
for( UINT i = 1; i < d3dobject->GetAdapterModeCount( adapter, format ); i++ )
{
d3dobject->EnumAdapterModes( adapter, format, i, &dispmode );
if( dispmode.Width > bestmode.Width )
{
bestmode.Width = dispmode.Width;
bestmode.Height = dispmode.Height;
bestmode.RefreshRate = dispmode.RefreshRate;
continue;
}
if( dispmode.Height > bestmode.Height )
{
bestmode.Height = dispmode.Height;
bestmode.RefreshRate = dispmode.RefreshRate;
continue;
}
if( dispmode.RefreshRate > bestmode.RefreshRate )
{
bestmode.RefreshRate = dispmode.RefreshRate;
continue;
}
}
此程式碼獲取所選格式的絕對最佳顯示模式,並將它放入 bestmode 中。
現在我們擁有了所有這些資料,我們可以按照應有的方式初始化我們的裝置 - 快速且靈活!我們的第一個目標是填充 D3DPRESENT_PARAMETERS 結構。這是大部分資料存放的地方。以下是該結構的全部內容
struct D3DPRESENT_PARAMETERS {
UINT BackBufferWidth, BackBufferHeight;
D3DFORMAT BackBufferFormat;
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType;
DWORD MultiSampleQuality;
D3DSWAPEFFECT SwapEffect;
HWND hDeviceWindow;
BOOL Windowed;
BOOL EnableAutoDepthStencil;/
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags;
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
}
以下是我們填充它的方法(以一種大部分線性的順序)
d3dpresent.BackBufferWidth = bestmode.Width;
d3dpresent.BackBufferHeight = bestmode.Height;
d3dpresent.BackBufferFormat = format;
d3dpresent.MultiSampleType = multisample;
d3dpresent.MultiSampleQuality = quality;
d3dpresent.hDeviceWindow = window;
d3dpresent.Windowed = !fullscreen;
d3dpresent.AutoDepthStencilFormat = depthstencil;
d3dpresent.FullScreen_RefreshRateInHz = bestmode.RefreshRate;
d3dpresent.PresentationInterval = presinterval;
如果你注意到,我們沒有填充所有引數。這是因為我們需要知道我們的應用程式將要做什麼 - 我們對剩下的引數有(在很大程度上)自由的範圍。我們第一個未填充的引數是 BackBufferCount。我們可以用 0 到 D3DPRESENT_BACK_BUFFERS_MAX 之間的任何值來填充它。通常我們希望它為 1。接下來是 SwapEffect。可以在此處找到有效的交換效果列表:http://msdn.microsoft.com/en-us/library/bb172612(VS.85).aspx。通常情況下,它是 D3DSWAPEFFECT_DISCARD。如果我們將 MultiSampleType 設定為除了 D3DMULTISAMPLE_NONE 之外的任何值,則它**必須**是 D3DSWAPEFFECT_DISCARD。現在我們需要設定 EnableAutoDepthStencil。它可以是 TRUE 或 FALSE。如果我們希望 Direct3D 管理深度模板(我們希望這樣做),它應該是 TRUE。最後,我們需要為 Flags 獲取一個值。它可以是 0 或此處列出的任何值:http://msdn.microsoft.com/en-us/library/bb172586(VS.85).aspx。我們將使用 0。以下是我們為其餘引數編寫的程式碼
d3dpresent.BackBufferCount = 1;
d3dpresent.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpresent.EnableAutoDepthStencil = TRUE;
d3dpresent.Flags = 0;
現在我們可以呼叫 CreateDevice 函式。如果你還記得,它看起來像這樣
HRESULT IDirect3D9::CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS * pPresentationParameters, IDirect3DDevice9 ** ppReturnedDeviceInterface );
唯一需要解釋的引數是 BehaviorFlags。BehaviorFlags 作為位掩碼指定了許多內容。它可以包含的完整常量列表如下:http://msdn.microsoft.com/en-us/library/bb172527(VS.85).aspx。我們只將它用於我們想要的頂點處理型別。因此,我們只需插入我們的 vtx_proc 變數。這就是我們最終的裝置初始化函式!
d3dobject->CreateDevice( adapter, devicetype, window, vtx_proc, &d3dpresent, &d3ddevice );
雖然不直接參與初始化,但討論釋放你在初始化部分分配的記憶體是很有必要的。使用 Direct3D 物件建立的所有裝置以及物件本身都必須使用其 Release 函式釋放。在這裡,我們只分配了裝置和物件,因此我們需要執行以下操作
d3ddevice->Release( );
d3dobject->Release( );