跳轉到內容

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( );
華夏公益教科書