DirectDraw編程方法與技巧
添加時間:2006-10-18 出處:www.3DGameDev.org 作者:songtao
1 概 述
DirectX是Microsoft為軟件開發人員提供的一套精心設計的接口,用于開發高性能、實時的應用程序。它以COM(component object modal)為基本結構[1],位于硬件和軟件之間,像gdi(graphics device interface)一樣提供了硬件無關的API(application programming interface)接口;它和GDI有一重要不同點:DirectX是一套底層的API接口,它提供了直接訪問硬件的能力,使得DirectX應用程序能充分發揮硬件的威力。
DirectDraw是DirectX中提供直接操縱顯存、執行硬件映射、硬件覆蓋及切換顯示頁等功能的組件。它不但兼容已有的Windows應用程序和驅動程序,而且還兼容許多顯示卡:從簡單的SVGA到支持圖像剪裁、拉伸和非RGB格式圖像的高檔顯示卡。DirectDrawHAL(hardware-abstraction layer)抽象了顯示卡的硬件功能,以設備無關的方式提供了一些以前是設備相關的功能,如多顯示頁技術、訪問并控制顯示卡映射寄存器、支持3DZ-Buffer、支持Z-order硬件覆蓋、訪問圖像拉伸硬件,以及同時訪問標準顯存和控制顯存。正因為DirectDraw有這些優點,現在許多基于Windows95/98/NT的游戲程序都使用了DirectDraw;而它的設備無關性使開發者擺脫了繁重的顯示卡接口工作,集中精力實現程序的主要功能。DirectX中的DDraw.dll實現了DirectDraw所需要的函數、對象及其接口。下面先介紹DirectDraw中主要的函數、對象和接口,然后再說明DirectDraw的使用方法。
2 DirectDraw中的函數、對象和接口簡介
DirectDraw包括一個代表顯示卡的主對象DirectDraw,由函數DirectDrawCreate創建,實現接口IDirectDraw和IDirectDraw2。如果機器上裝有多個顯示卡,可以為每個顯示卡創建一個DirectDraw對象。也可以創建多個DirectDraw對象,它們各自獨立并代表同一物理對象。IDirectDraw是為了和以前版本DirectX兼容而保留的接口,基于DirectX3以上版本的程序應該使用IDirectDraw2。IDirectDraw2和IDirectDraw接口所包含的方法定義基本相同,但具體實現不同。DirectDraw對象主要用于創建其它3個對象:DirectDrawSurface、DirectDrawPalette、DirectDrawClipper。
DirectDrawSurface對象由函數IDirectDraw2::CreateSurface創建,代表顯存中一塊線性區域,實現接口IDirectDrawSurface和IDirectDrawSurface2。IDirectDrawSurface是為了和以前版本DirectX兼容而保留的接口,基于DirectX3以上版本的程序應該使用IDirectDrawSurface2。二者的接口定義也基本相同。可以為一個DirectDraw對象創建多個IDirectDrawSurface對象,代表物理屏幕或邏輯屏幕,通過IDirectDrawSurface2::Flip、IDirectDrawSurface2::BltFast等方法切換顯示頁或映射部分屏幕內容。
DirectDrawPalette對象由函數IDirectDraw2::CreatePalette創建,實現接口IDirectDrawPalette。它代表顯示卡的物理調色板,可以是16色或256色。每個DirectDrawPalette必須附著(attach)在一個DirectDrawSurface上,不同的DirectDrawSurface對象可以有不同的DirectDrawPalette。
DirectDrawClipper對象由函數IDirectDraw2::CreateClipper或DirectDrawCreateClipp
er創建,實現接口IDirectDrawClipper。DirectDraw用它來處理屏幕的剪貼。它常用于在Window模式(與全屏模式相對應)下運行的DirectDraw程序,在使用前也必須被附著在一個DirectDrawSurface上。
3 DirectDraw程序一般工作過程
和一般的Windows程序[2]一樣,DirectDraw程序要先創建一個主窗口,然后進行DirectDraw的初始化:創建所需要的對象,設置程序的工作模式,建立必要的數據結構。在初始化工作完成后,就可以在主窗口的消息循環中根據用戶的輸入調用相應對象的方法。
3.1 初始化DirectDraw
3.1.1 創建DirectDraw對象
首先調用DirectDrawCreate創建代表某個顯示卡的DirectDraw對象:
ddres=DirectDrawCreate(NULL,&lpDD,NULL);
DirectDrawCreate的第一項參數是代表顯示卡驅動程序的GUID(globally unique identifier),若為NULL則表示采用系統默認的驅動程序。第二項參數是IDirectDraw接口類型指針的地址,用于接收指向由DirectDrawCreate所創建對象的指針。這個指針不需應用程序預先分配內存。DirectDrawCreate成功時會調用AddRef將對象的引用計數加1。如果要知道系統中所有驅動程序的GUID,可以調用DirectDrawEnumerate,它接收兩個參數:回調函數的地址和傳給回調函數的自定義數據的地址,其工作方式和Win32 API Enum Windows類似。
3.1.2 設置DirectDraw的工作模式
創建DirectDraw對象后應該馬上設置DirectDraw對象的工作模式:
ddres=lpDD→SetCooperativeLevel(hWnd,DDSCL_EXCLUSIVE|DDSCLFUL_LSCREEN);
SetCooperativeLevel的第一個參數是和DirectDraw對象關聯的窗口句柄,一般是程序主窗口的句柄;第二個參數指明了DirectDraw對象的工作模式。DirectDraw對象有兩種工作模式:普通模式(Windowedmode,參數DDSCL_NORMAL)和獨占模式(Full_Screen mode,參數DDSCL_EXCLUSIVE)。普通模式下DirectDraw和普通Windows程序的區別不大,主要是DirectDraw程序可以隨心所欲地讀取整個屏幕的內容或在屏幕的任意位置輸出,而其它的Windows程序毫無察覺。獨占模式就是游戲“紅色警報”和“赤壁”所采用的方式,并必須和全屏模式(參數DDSCL_FULLSCREEN)聯用,此時程序的主窗口被擴展為整個屏幕。其它應用程序都成為后臺程序,使用Alt+Tab鍵可以在程序間切換。
3.1.3 得到IDirectDraw2類型的接口
接下來利用COM的重要方法QueryInterface,通過IDirectDraw接口得到一個IDirectDraw2類型的接口。QueryInterface成功時會將對象的引用計數加1,而我們也不再需要IDirectDraw接口,因此這里調用IDirectDraw::Release將對象的引用計數減1,也即釋放先前得到的IDirectDraw接口。
3.1.4 根據需要切換屏幕顯示模式
如果DirectDraw的工作模式設定為全屏獨占模式,則可以根據需要切換屏幕顯示模式:
ddres=lpDD2→SetDisplayMode(800,600,16);
SetDisplayMode的前兩個參數是屏幕的橫、縱分辨率,最后一個是每個像素點的顏色位數。上例將屏幕設為800×600,16位色。
3.1.5 創建DirectDrawSurface對象并得到IDirectDrawSurface2接口
創建DirectDraw對象后,下一步調用IDirectDraw2::CreateSurface創建代表物理屏幕或邏輯屏幕的DirectDrawSurface對象。IDirectDraw2::CreateSurface的第一個參數是DDSURFACEDESC結構的地址,第二個參數是一個IDirectDrawSurface接口類型的指針地址,第三個參數必須是NULL。數據結構DDSURFACEDESC包含了創建DirectDrawSurface所需信息。在得到一個IDirectDrawSurface類型接口后,仍使用QueryInterface得到一個IDirectDrawSurface2類型的接口,并釋放先前得到的IDirectDrawSurface接口:
//Get the IDirectDrawSurface2interface.
LPDIRECTDRAWSURFACE2lpDDSPrimary=NULL;
ddres=lpDDSPrimaryTemp>QueryInterface(IID_IDirectDrawSurface2,(LPVOID)&lpDDSPrimary;
lpDDSPrimaryTemp→Release();
上面得到的lpDDSPrimary指向代表物理屏幕的對象。還需調用IDirectDrawSurface2::GetAttachedSurface得到指向創建時附帶的代表相關邏輯屏幕的對象指針。至此,DirectDraw初始化工作完成。
3.2 使用DirectDraw
3.2.1 使用GDI函數向物理屏幕或邏輯屏幕輸出
調用IDirectDrawSurface2::GetDC可得代表某個屏幕的設備描述表的句柄,使用GDI函數輸出,最后調用IDirectDrawSurface2::ReleaseDC釋放句柄。為防止在對屏幕作圖期間其它應用程序爭奪顯存,IDirectDrawSurface2::GetDC調用IDirectDrawSurface2::Lock得到Winl6 Lock。這意味著其它程序在該程序釋放Win16Lock前都不能訪問GDI和USER資源。IDirectDrawSurface2::ReleaseDC調用IDirectDrawSurface2::Unlock釋放Win16 Lock。于是,在調用IDirectDrawSurface2::GetDC和IDirectDrawSurface2::ReleaseDC期間,Windows將被掛起。因此,應用程序應盡量縮短這一對函數調用之間的間隔時間,而且調試程序也無法跟蹤這段時間內執行的操作。
3.2.2 交替切換物理屏幕和邏輯屏幕或執行屏幕內容的映射
準備好內存中的邏輯屏幕后,可以調用IDirectDrawSurface2::Flip方法切換物理屏幕和邏輯屏幕,也可調用IDirectDrawSurface2::BltFast、IDirectDrawSurface2::Blt等方法執行部分屏幕內容的映射。一般情況下程序采用異步方式,在顯示卡硬件執行切換動作的同時準備下一頁屏幕,使CPU和顯示卡硬件并行,提高整體執行速度。
使用Flip切換物理屏幕和邏輯屏幕后,原指向物理屏幕的指針仍然指向物理屏幕,原指向邏輯屏幕的指針仍然指向邏輯屏幕,即指針所指內容也被交換了,便于程序操縱各個屏幕而不至于混淆。
3.2.3 釋放DirectDraw對象
程序結束之前要釋放所創建的DirectDraw對象。這只要在相應接口上調用Release方法即可。
4 使用DirectDraw的技巧和注意事項
4.1 檢查方法的返回值
正確執行DirectDraw方法時都返回DDOK。且其值是零。返回其它值表明發生了某種錯誤。一般地,程序應檢查這些返回值以決定是否出錯。
4.2 檢查Flip和Blt的狀態
如果在Flip或Blt操作的返回值是DDERR_WASSTILLDRAWING情況,為提高效率,DirectDraw提供了IDirectDrawSurface2::GetFlipStatus和IDirectDrawSurface2::GetBltStatus方法。它們能立即返回當前的Flip和Blt狀態,于是應用程序可以在上一操作完成之前執行某些其他的任務。
4.3 在位圖映射中使用ColorKey
ColorKey是一種或幾種顏色的集合,用于在位圖映射操作中區分前景色和背景色。ColorKey包括兩種:Source color key和Destination color key。前者是指源位圖中代表透明色的顏色,在執行映射操作時將不被映射到目標位圖上;后者是指目標位圖中將被源位圖中相應位置顏色取代的顏色,如果目標位圖指定了Destination color key,則只有這些指定的顏色被替換。可以在創建DirectDrawSurface對象時指定Color Key;也可使用方法IDirectDrawSurface2::GetColorKey和IDirectDrawSurface2::SetColorKey以獲取和設置已有的DirectDrawSurface對象的ColorKey。
4.4 GDI重定向
由于GDI在Windows系統啟動時先于DirectDraw被裝入,而DirectDraw工作時又繞過了GDI,因此GDI不知道DirectDraw對物理屏幕所進行的操作。即使DirectDraw調用了Flip方法將先前的物理屏幕切換為邏輯屏幕,如不采取措施,GDI將仍然向切換后的邏輯屏幕上輸出。如果DirectDraw程序擁有菜單、滾動條等由GDI負責繪制的元素,那么在Flip完成后這些元素就會成為不可見,而在對應的邏輯屏幕被切換成物理屏幕時又會顯示出來。為避免屏幕閃爍,DirectDraw提供了IDirectDraw2::GetGDISurface,用于確定當前被GDI認為是物理屏幕的DirectDrawSurface對象;以及IDirectDraw2::FlipToGDISurface,用于將GDI的輸出重新定向到當前的物理屏幕上。如果需要,可以在每次Flip操作后調用它,以保證屏幕正常。
4.5 在顯存中存放位圖
由于從顯存到顯存的映射比從系統內存到顯存的映射快,所以經常將需要映射的位圖存放在顯存中以提高速度。大多數顯示卡在存放了物理屏幕和相關邏輯屏幕之外還有足夠的內存可以用來存放位圖。可以調用IDirectDraw2::GetCaps檢查顯存;在創建DirectDrawSurface對象時可以通過結構DDSURFACEDESC中的DDSCAPS域指定該對象存在于顯存或系統內存中。如果指定在顯存中創建對象,而顯存又沒有地方容納該對象,IDirectDraw2::CreateSurface會返回錯誤信息DDERR_OUTOFVIDEOMEMORY。如果沒有指定創建的位置,DirectDraw總是盡量利用空閑的顯存。
4.6 檢查硬件的性能
雖然DirectDraw通過HAL和HEL屏蔽了硬件的具體特性,但應用程序也需根據硬件的不同性能來改變自身的執行方式。利用IDirectDraw2::GetCaps方法可以得到有關硬件性能的詳細信息。
4.7 保持主窗口的消息循環暢通
在調用SetCooperativeLevel設置DirectDraw的工作模式時,應用程序為DirectDraw指定了主窗口。由于DirectDraw直接操縱硬件可能導致死機,因此DirectDraw在后臺監視主窗口的消息循環,當消息循環長時間沒有反應時,DirectDraw就釋放所有的資源,結束應用程序的執行。所以DirectDraw程s序應該注意避免長時間封鎖消息循環。