初次接觸:DirectDraw
第六章 初次接觸:DirectDraw本章,你將初次接觸DirectX中最重要的組件:DirectDraw。DirectDraw可能是DirectX中最強大的技術,因為其貫穿著2D圖形繪製同時其幀緩存層也是DirectX3D的基礎。當然,DirectX8.0中DirectDraw已經完全集成到了DirectX3D裏麵。此外,如果你對DirectDraw深有了解,那將擁有創建任何DOS16/32類型的圖形化程序的能力。DirectDraw是理解眾多DirectX自身概念的鑰匙。所以,聽仔細了。
下麵是本章你要解決掉的知識點:
·DirectDraw接口
·創建一個DirectDraw對象
·與windows合作
·深入研究模式
·奇妙的顏色
·構建顯示表麵
DirectDraw的眾接口
DirectDraw由一係列的接口組成。如果你複習一下我們在第五章“DirectX基礎與恐怖的COM”對組件對象模型(COM)的討論,那你便會明白,接口不過是你用來與組件通信的函數或/和方法之集合。看下圖6.1關於DirectDraw眾接口的圖示。記住,我不打算把版本好加在每個接口上,因為到目前為止我們的討論都是抽象了。比如,IDirectDraw確實已經升級到7.0了,所以當我們用到DirectDraw時我們指的是IDirectDraw7,但現在,我隻想展示一下DirectDraw的一些基礎和關係。
圖 6.1 DirectDraw的眾接口
接口特性
如你所見,組成DirectDraw的接口隻有五個:
IUnknown:所有的COM對象都必須從這個基接口派生,DirectDraw當然也不例外。IUnknown除了包含將被其他所有接口重載的Addref(), Release(),和 QueryInterface()函數之外什麼也沒有。
IDirectDraw:用使用DirectDraw工作就必須創建這個主接口。從IDirectDraw的字麵意思便暗示了顯卡以及相關硬件支持。十分有趣的是,由於win98/ME/XP/NT2000均支持MMS(多顯示器支持),現在你可以在你的係統中裝不止一個顯卡於是便有了不止一個DirectDraw對象。但是,本書隻假設電腦中隻有一塊顯卡,而且即使係統中有多塊顯卡也總是選擇默認的顯卡來表示DirectDraw對象。
IDirectDrawSurface:其代表你使用DirectDraw創建、操作和顯示的真實顯示表麵。一個DirectDraw表麵可以存在於顯卡自身的VRAM(顯存)也可以存在於係統內存中。有兩種基本的表麵類型:主表麵和副表麵。
主表麵用來表示顯卡當前正被光柵化並顯示的真實視頻緩存。而副表麵則常常是不顯示的。大多數情況下,你將創建單個主表麵以表示真實的視頻播放,然後用一個或更多的副表麵來表示位圖對象和/或用後備緩存來代表用來畫下一幀動畫的非屏幕做圖區(offscreen drawing areas)。在下一章我們將討論關於表麵的細節。但現在,還是看看圖6.2的詳細圖示。
圖6.2 DirectDraw表麵
IDirectDrawPalette:DirectDraw可以用來處理任何色深,從1位單色到32位真彩色。故而,DirectDraw支持IDirectDrawPalette接口從而支持了256色或更少顏色數的視頻模式。在製作一係列demo時256色模式得到廣泛使用,因為用軟件光柵化是256色是最快的視頻模式。在第二卷對DirectX3D立即模式的討論中你將轉向24位色,因為那是D3D工作的基本視頻模式。無論那種情況,IDirectDrawPalette接口用來創建、加載、操作調色板,以及把調色板附著到那些你為DirectDraw程序創建的表麵上比如主表麵或副表麵。圖6.3展示了繪圖表麵與調色板之間的關係。
圖6.3 繪圖表麵與調色板間的關係
IDirectDrawClipper:這個接口用來幫助在可視顯示表麵的子集中進行DirectDraw光柵和位圖的剪切操作。大多數情況下,你隻需在窗口化的DirectX程序中和/或在剪切位圖超出顯示表麵(無論是主表麵還是副表麵)的部分時使用DirectDraw剪切器。IDirectDrawClipper接口的酷表現在其將盡可能地利用硬件加速功能,以及替你完成在剪切位圖以適應屏幕尺寸時那個麻煩的逐像素(pixel-by-pixel)或微潛像過程。
現在,在你開始創建一個DirectDraw對象前,讓我們重新回顧一下上一章學到的關於如何使用COM對象的知識。DirectDraw和所有DirectX組件不斷的發展著,所以其接口也不斷地更新著。唉,盡管到本章為止我提到DirectDraw接口時都用IDirectDraw, IDirectDrawSurface, IDirectDrawPalette,和 IDirectDrawClipper來指代,但大多數情況下這些接口已經被更新過了。必要像前麵提到過的,在DirectX7.0時IDirectDraw 已經更新到 IDirectDraw7。
這一切意味著,如果你要使用最新的軟硬件功能,你必須總是用IUnknown::QueryInterface()查詢到最新的接口修訂版。但是要想確定最新版的接口,你還不得不查一下DirectX SDK文檔。當然,在本書中你使用的是DirectX8.0,所以你已經知道這最新已經最新到哪了,但記住,當你升級到DirectX9.0是你將有一下更新的接口可供使用了。不過,本書的兩卷都是關於寫你自己的光柵化與3D化軟件,所以我將盡可能簡短些。大多數情況下,你用不到新版本DirectX的大多數新增功能。
綜合使用各接口
下麵,我將簡單地綜合使用各接口以創建一個DirectDraw程序。
1。創建一個主DirectDraw對象並通過QueryInterface()獲得一個IDirectDraw7接口。或者使用DirectDrawCreateEx()直接獲得一個IDirectDraw7接口。使用接口設置合作級別以及視頻模式。
2。使用IDirectDrawSurface7接口創建至少一個用以繪圖的主表麵。針對表麵的色深和視頻模式,可能需要一個調色板。如果視頻模式的色深小於8位每像素,則要用調色板。
3。用IDirectDrawPalette接口創建一個調色板,用RGB格式的信息初始化調色板,並把調色板與相關表麵關聯起來。
4。如果DirectDraw程序是窗口程序,或者如果你渲染的位圖有可能超出可視DirectDraw表麵的邊界時,你至少要創建一個剪切器並將其調整到可視窗口的大小。見圖6.4。
圖6.4 DirectDraw剪切器
5。在主表麵上繪圖。
當然,我省略了大量技術性的細節,但是以上五點是使用不同接口共同的要點。記住這五點之後,讓我們了解些細節並真正地使用下這些接口……
創建一個DirectDraw對象
用C++創建一個DirectDraw1.0對象時,你所要做的便是調用DirectDrawCreate(),如下所示:
HRESULT WINAPI DirectDrawCreate(GUID FAR *lpGUID, // guid of object
LPDIRECTDRAW FAR *lplpDD, // receives interface
IUnknown FAR *pUnkOuter ); // com stuff
lpGUID:這是你要使用的那個顯示驅動的GUID。大多數情況下隻需簡單設為NULL以使用默認的硬件。
lplpDD:如果調用成功這將是個IDirectDraw接口的指針的指針。注意,這個函數返回一個IDirectDraw接口而非IDirectDraw7接口。
pUnkOuter:高級特性,總是設為NULL。
下麵是你可能要用到的:創建一個基於IDirectDraw接口的默認DirectDraw對象。
LPDIRECTDRAW lpdd = NULL; // storage for IDirectDraw
// create the DirectDraw object
DirectDrawCreate(NULL, &lpdd, NULL);
如果函數調用成功,lpdd將是一個可用的IDirectDraw 1.0對象接口。但是,你可能喜歡最新版本的接口,IDirectDraw7。不過在你學習如何做到這之前讓我們來談談錯誤處理問題吧。
DirectDraw的錯誤處理
DirectX的錯誤處理非常簡潔。有許多的宏可以用來檢測各種函數調用的成功與失敗。Microsoft推薦在測試DirectX函數錯誤時使用兩個宏:
FAILED():檢測調用成功。
SUCCEEDED():檢測調用失敗。
知道了這些,你可以通過加入錯誤處理代碼來使你的程序更正規。
if (FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
// error
} // end if
或者類似的,你可以對成功進行檢測:
if (SUCCEEDED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
// move onto next step
} // end if
else
{
// error
} // end else
我經常使用FAILED()宏,因為我不喜歡同時從兩個邏輯方向考慮問題,不過你使用什麼宏那可就隨你了。使用宏的唯一問題是,宏告訴你的信息太少,其隻能檢查一些常規錯誤。如果你想要知道確切的錯誤,你可以察看函數的返回碼。表6.1列出DirectX6.0 DirectDrawCreate()的可能的返回碼。
表 6.1. DirectDrawCreate() 的返回碼
返回碼 描述
DD_OK 完全成功
DDERR_DIRECTDRAWALREADYCREATED DirectDraw對象以及創建過了
DDERR_GENERIC 未知錯誤
DDERR_INVALIDDIRECTDRAWGUID GUID設備未知
DDERR_INVALIDPARAMS 非法的參數
DDERR_NODIRECTDRAWHW 沒有任何硬件
DDERR_OUTOFMEMORY 大膽猜測一些?
使用這些常量的唯一問題是Microsoft不保證他們不會完全改變所有的錯誤代碼。但是,我想按下麵的方式編碼是絕對安全的。
if (DirectDrawCreate(...)!=DD_OK)
{
// error
} // end if
在任何時候,DirectDraw的所有函數都會定義DD_OK,所以你盡可放心使用。
對接口進行改進
如我所說,你可以用從調用DirectDrawCreate()返回的lpdd存儲基本的IDirectDraw接口。或者,你可以通過任何DirectDraw接口都實現了的IUnknown接口的QueryInterface()方法查詢一個新的接口從而達到使用最新版本接口的目的(不該這個接口是什麼)。最新的DirectDraw接口,在DirectX7.0中,叫IDirectDraw7。所以你以下麵方式獲得這個接口的指針。
LPDIRECTDRAW lpdd = NULL; // standard DirectDraw 1.0
LPDIRECTDRAW lpdd7 = NULL; // DirectDraw 7.0 interface 7
// first create base IDirectDraw interface
if (FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
// error
} // end if
// now query for IDirectDraw7
if (FAILED(lpdd->QueryInterface(IID_IDirectDraw7,
(LPVOID *)&lpdd7)))
{
// error
} // end if
現在,這裏有些重要的東西需要引起注意:
·調用QueryInterface()的方式。
·在訪問IDirectDraw7接口時常量的使用,即IID_IDirectDraw7.
通常,從一個接口的所有函數調用都有如下的形式:
interface_pointer->method(parms...);
所有接口都以如下形式被標識:
IID_IDirectCD
這裏,
C代表組件,其他的諸如Draw代表DirectDraw,Sound代表DirectSound,Input代表DirectInput,等等。
D代表一個從2到n的數,表示你想要的那個接口。你可以從DDRAW.H文件中找到所有這些常量。
繼續這個例子。你現在可能對既有IDirectDraw接口又有IDirectDraw7接口感到困惑吧。幹麻要這麼做?既然你不需要舊版本的接口,簡單的像下麵這樣扔掉它就是了:
lpdd->Release();
lpdd = NULL; // set to NULL for safety
從現在起,從新的接口IDirectDraw7進行所有的函數調用。
警告:由於有了IDirectDraw7的新功能,編碼變的自由了而且所要做的瑣碎的事也少了。之所以能這樣,除了IDirectDraw7接口更加成熟和先進了之外,還因為大多數情況下其需要並且返回新的數據類型,而不是為DirectX1.0定義的數據類型。唯一確定這種不規則變化的途徑便是去查看DirectX SDK文檔,核實任何具體函數需要和(或)返回的數據類型的版本。但是這隻是一般的警告,我將告訴你本書中你遇到的所有例子中的正確數據類型。我就是那麼好。順便說一句,我的生日是6.14。
除了使用QueryInterface()從一個初始化了的IDirectDraw 接口指針(lpdd),還有一個更直接的“COM 途徑”去獲得IDirectDraw7 接口。在COM中,隻要你有接口ID或稱IID,你便可以得到任何接口的接口指針,也即你想要的那個接口。大多數情況下,我個人不推崇使用低級COM函數,因為對此我已經深惡痛絕了。盡管如此,當你使用DirectMusic時你仍然不得不使用低級COM方法,所以此時至少也該介紹給你這種低級調用的過程。下麵的代碼直接獲得IDirectDraw7 接口:
// first initialize COM, this will load the COM libraries
// if they aren't already loaded
if (FAILED(CoInitialize(NULL)))
{
// error
} // end if
// Create the DirectDraw object by using the
// CoCreateInstance() function
if (FAILED(CoCreateInstance(&CLSID_DirectDraw,
NULL,
CLSCTX_ALL,
&IID_IDirectDraw7,
&lpdd7)))
{
// error
} // end if
// now before using the DirectDraw object, it must
// be initialized using the initialize method
if (FAILED(IDirectDraw7_Initialize(lpdd7, NULL)))
{
// error
} // end if
// now that we're done with COM, uninitialize it
CoUninitialize();
上麵這段代碼是Microsoft推薦的創建DirectDraw對象的方法。但是這裏它耍了一點小花樣,它用了一個宏:
IDirectDraw7_Initialize(lpdd7, NULL);
你可以不這樣,而完全用COM的方法取而代之:
lpdd7->Initialize(NULL);
不管上麵兩個調用的哪一個,NULL所在位置的參數表示顯卡,而NULL代表使用默認的顯卡驅動。總之,不難明白上麵的宏是如何在代碼中擴展開的吧。我想大概是這樣做是想使事情變得簡單點吧。現在,好消息是,Microsoft已經創建了一個無須中間步驟一下子就建出IDirectDraw7的函數。通常,通常這是不可能的,但在DirectX7.0中,他們創建了一個叫DirectDrawCreateEx()的新函數。關於這個函數,我在上一章提到過。他的原型聲明如下:
HRESULT WINAPI DirectDrawCreateEx(
GUID FAR *lpGUID, // the GUID of the driver, NULL for active display
LPVOID *lplpDD, // receiver of the interface
REFIID iid, // the interface ID of the interface you are requesting
IUnknown FAR *pUnkOuter // advanced COM, NULL
);
這與DirectDrawCreate()很相似,但其多了一個參數,以使你能夠創建任何版本的DirectDraw接口。於是,創建IDirectDraw7接口的函數調用如下:
LPDIRECTDRAW7 lpdd; // version 7.0
// create version 7.0 DirectDraw object interface
DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL);
唯一詭異的地方是接口指針的類型轉換--注意那個(void **),此外你必須在參數idd中傳一個接口。除此之外,它還是蠻簡單高效的。
讓我們花幾分鍾重複一些所有東西。如果我們想要用最新版本的DirectDraw,即7.0版(因為在DirectX8.0中Microsoft去除了DirectDraw),我們需要一個IDirectDraw7接口。我們可以使用基本的DirectDrawCreate() 創建一個IDirectDraw1.0版接口,然後使用QueryInterface()以獲得IDirectDraw7接口。另一方麵,我們可以使用低級COM技術直接獲得IDirectDraw7。再或者,我們可以使用函數DirectDrawCreateEx()(從DirectX7.0才開始有這個函數)直接創建之。不錯吧?DirectX有點像X Windows了,做同一件可以有5000種途徑。
現在你知道了如何創建一個DirectDraw對象以及如何獲得最新版本的方法。讓我們在使DirectDraw工作起來的征程中再向前走一步,即設置合作級別。
與windows合作
如你所知,Windows是一個合作、共享的環境。盡管作為一個程序員,我一直不知道如何讓係統與我的代碼合作,但至少這是個理想狀態。總之,DirectX與其他任何Win32應用程序一樣,最起碼的,它必須通知Windows它所要使用的各種資源,以便讓讓其他程序不在DirectX使用這些資源時申請(以及得到)這些資源。基本上,隻要DirectX告訴Windows它在做什麼,它便能得到它想要的資源。這看上去對我很公平。
在DirectDraw方麵,你唯一感興趣的是視頻顯示硬件。有兩樣東西你要關心的:
·全屏模式
·窗口模式
在全屏模式中,DirectDraw像老式的DOS程序那般行事。就是說,整個熒屏表麵都分配給你的遊戲,你直接寫到視頻硬件上。別的程序不會接觸到視頻硬件。窗口模式有點小不同。在窗口模式中,DirectDraw必須更多的與Windows合作,因為其他的程序可能需要更新他們各自的對用戶可見的客戶區窗口。因此,在窗口模式中,你對視頻硬件的控製和獨占是收限製的。
第七章“高級DirectDraw和位圖繪圖”將進一步討論窗口DirectDraw應用程序,但他們可能有點複雜。本章的大部分將與全屏模式打交道,因為他們更容易操作,要記住這點。
現在,你知道一些關於為什麼需要在Windows和DirectX之間建立合作的重要性了。讓我們來看一下如何告訴Windows你需要的合作方式。要設置DirectDraw的合作級別,使用函數IDirectDraw7:: SetCooperativeLevel(),其是IDirectDraw7中的一個方法。
C++:如果你是C程序員,語法IDirectDraw7:: SetCooperativeLevel()可能有點神秘。::操作符叫做域操作符。該語法簡單地表示SetCooperativeLevel()是IDirectDraw7接口的一個方法(或成員函數)。接口是個類,該類不過是一個具有虛函數的結構體罷了。有時我會省略掉前綴而直接寫成SetCooperativeLevel()。但是,記住,所有DirectX函數都是接口的一部分,且必須使用函數指針類型的調用,入lpdd->function(...)
這裏是IDirectDraw7:: SetCooperativeLevel()的原型聲明:
HRESULT SetCooperativeLevel(HWND hWnd, // window handle
DWORD dwFlags);// control flags
如果成功返回DD_OK,否則返回錯誤代碼。
有趣的是,這是第一次在DirectX函數中需要窗口句柄。DirectX(具體說是DirectDraw)需要hWnd參數以確定關聯的窗口。你隻需簡單地使用你程序的主窗口句柄即可。
SetCoopertiveLevel()的第二個參數是dwFlags,其是影響DirectDraw與Windows協同方式的控製標誌。表6.2列出了經常使用的一些標誌,這些標誌可以通過或運算聯合在一起使用,以獲得一個所要的合作級別。
表 6.2. SetCooperativeLevel()的控製標誌
值 說明
DDSCL_ALLOWMODEX 允許使用Mode X (320x200,240,400)顯示模式。隻當使用DDSCL_EXCLUSIVE 和 DDSCL_FULLSCREEN 標誌時方可使用。
DDSCL_ALLOWREBOOT 允許在獨占模式(全屏模式)下檢測 Ctrl+Alt+Del組合鍵。
DDSCL_EXCLUSIVE 請求使用獨占級別。該標誌必要與DDSCL_FULLSCREEN 標誌同時使用。
DDSCL_FPUSETUP 表明調用函數希望使用優化了的DirectX3D浮點運算單元(FPU)(單精度且禁用異常處理)(single precision and exceptions disabled
DirectX3D就不需要每次都顯式地設置FPU了。要了解更多信息,請查閱DirectX SDK文檔“DirectDraw合作級別與FPU精度”。
DDSCL_FULLSCREEN 表明使用全屏模式。其他程序對GDI函數的調用將無法在屏幕上畫圖。本標誌必須與DDSCL_EXCLUSIVE 標誌同時使用。
DDSCL_MULTITHREADED 要求DirectDraw的行為滿足多線程安全這一要求。就目前你的學習階段而言還無需對此過於深究。
DDSCL_NORMAL 表明程序是一個普通的窗口程序。本標誌不能和 DDSCL_ALLOWMODEX, DDSCL_EXCLUSIVE, 或 DDSCL_FULLSCREEN 同時使用
DDSCL_NOWINDOWCHANGES 表明不允許DirectDraw最小化或還原激活狀態下的程序窗口。
如果你對上麵的諸多標誌都看的很仔細的話,你可能會發現好些標誌好像有點多餘。基本上,DDSCL_FULLSCREEN 和 DDSCL_EXCLUSIVE 必須同時使用。如果你想用 Mode X 模式,那必須同時使用DDSCL_FULLSCREEN, DDSCL_EXCLUSIVE, 和 DDSCL_ALLOWMODEX 。除此之外,標誌的名字和其做的事情還是比較貼切的。大多數情況下,你將像下麵這樣設置全屏模式的程序:
lpdd7->SetCooperativeLevel(hwnd,
DDSCL_FULLSCREEN |
DDSCL_ALLOWMODEX |
DDSCL_EXCLUSIVE |
DDSCL_ALLOWREBOOT);
而普通窗口程序則如下般設置:
lpdd7->SetCooperativeLevel(hwnd, DDSCL_NORMAL);
當然,當你在本書後麵學習多線程編程技術時,你可能就要加上標誌DDSCL_MULTITHREADED 以使程序運行得更安全。總之,現在讓我們看看創建DirectDraw對象和設置合作級別的完整過程:
LPDIRECTDRAW lpdd = NULL; // standard DirectDraw 1.0
LPDIRECTDRAW7 lpdd7 = NULL; // DirectDraw 7.0 interface 7
// first create base IDirectDraw interface
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd7, IID_IDirectDraw7, NULL)))
{
// error
} // end if
// now set the cooperation level for windowed DirectDraw
// since we aren't going to do any drawing yet
if (FAILED(lpdd7->SetCooperativeLevel(hwnd, DDSCL_NORMAL)))
{
// error
} // end if
注意:為了節省筆墨,我將省去諸如FAILED() 和(或) SUCCEEDED() 這些錯誤處理。但是你在編程時可要對錯誤處理時刻掛在心上哦。
此刻,你已經學會了創建一個生成窗口的完整DirectX程序的所有知識--先從DirectDraw開始,然後設置合作級別。盡管你還不知道如何繪圖,但畢竟已經開始上路子了。作為一個例子,請看CD上的對應著DEMO6_1.EXE的DEMO6_1.CPP。當你運行程序時,你將看到如圖6.5所示的畫麵。這個程序是基於前麵我們寫的T3D遊戲控製台模板的,我試試在Game_Init() 和 Game_Shutdown() 中做了些修改以創建DirectDraw和設置合作級別。
圖6.5 運行DEMO6_1.EXE
下麵是在DEMO6_1.CPP中增加了DirectDraw代碼的這兩個函數,你會看到,創建DirectDraw是多麼簡單的一件事。
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// first create base IDirectDraw interface
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
{
// error
return(0);
} // end if
// set cooperation to normal since this will be a windowed app
lpdd->SetCooperativeLevel(main_window_handle, DDSCL_NORMAL);
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here
// simply blow away the IDirectDraw interface
if (lpdd)
{
lpdd->Release();
lpdd = NULL;
} // end if
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
技巧:如果你打算自己從頭寫DEMO6_1.CPP並試圖編譯它,請記得從DirectX8.0 SDK的LIB\目錄中手動include DDRAW.LIB。同時把DirectX頭文件所在文件夾增加到你的編譯器的編譯搜索路徑中,並使給文件夾為編譯器搜索頭文件時最先搜索的文件夾。此外,你當然應當創建一個Win32應用程序,我每天至少從使用編譯器的新手那裏收到10封email,忘了include .lib文件的。你可別成下一個發此類email的人……
學習有關模式的一些知識
有關設置DirectDraw的下一步可能是所有設置步驟中最酷的一部分。一般的地,在DOS中把視頻模式設置為ROM BIOS模式是十分自然的。但由於模式轉換的弊端使得在Windows下想像DOS這般設置視頻模式幾乎是不可能的。不過,由於有DirectX,那就小菜一碟了。DirectDraw的一個主要設計目標便是使視頻模式轉換對於程序員而言簡便且透明。無需進行更多的VGA/CRT控製注冊編碼,隻需做一個簡單的調用即可。任何類型的Presto模式{?Presto mode},隻要你想--你可以。(當然,顯卡得支持)
設置視頻模式的函數叫 SetDisplayMode(),其為IDirectDraw7接口的成員函數,或用C++的話說:IDirectDraw7::SetDisplayMode()。下麵是它的原型聲明。
HRESULT SetDisplayMode(DWORD dwWidth, // width of mode in pixels
DWORD dwHeight, // height if mode in pixels
DWORD dwBPP, // bits per pixel, 8,16,24, etc.
DWORD dwRefreshRate, // desired refresh, 0 for default
DWORD dwFlags); // extra flags (advanced) 0 for default
與往常一樣,函數的如果調用成功則返回DD_OK。
你現在可能想說:“天哪,棒極了!”。你曾經試圖設置一個Mode X模式,比如320×400或800×600模式嗎?即使你成功了,嗬嗬,渲染視頻緩存是可要祝你好運了。不過,現在有了DirectDraw函數,你隻要把寬、高、色深、發給函數,嗬嗬,搞定!DirectDraw處理所有具體顯卡的細節特性,如果你要求的模式可以建立,那它就建立之。並且,視頻模式建立後保證有一段線性的內存緩存……當然它還保證其他一些東西,晚些時候再說。
看看表6.3,對常用的視頻模式和對應色深來次簡要的複習。
Table 6.3. 常用視頻模式
寬 高 色深 Mode X
320 200 8 *
320 240 8 *
320 400 8 *
512 512 8,16,24,32
640 480 8,16,24,32
800 600 8,16,24,32
1024 768 8,16,24,32
1280 1024 8,16,24,32
有趣的是,你可以要求任何你想要的模式。比如你可以選擇400×400,而且如果顯卡支持這種模式那這種模式就能工作。但,最好還是建成選擇表6.3列出的模式,因為透明是最常用的。
技巧:事實上,有一個Win32 API函數可以用來設置視頻模式,而且我過去也用過。但是這個函數對係統容易危害到係統,而且總把事情搞的一團糟。
回到SetDisplayMode()函數,前三個參數意如所表,自不多言。但是最後兩個就需要一點解釋了。dwRefreshRate用來對你要求的模式設定刷新率。因此,如果你要求一個320×200模式,默認的係統會把刷新率設為70Hz,不過通過這個函數,如果你想,你可以強製的設為60Hz。而且說實在的,我將不深入討論刷新率,其實簡單設為0就行了(這樣將使用驅動默認的值)
最後一個參數dwFlags是一個額外的標誌,用來表示一些瑣碎的東西,而且用處也不大。目前,通過給這個參數傳DDSDM_STANDARDVGAMODE標誌,你可以為320×200使用VGA模式13h{?}從而代替Mode X 320×200。再次的,我不會深究這個問題,如果你寫一個使用320×200的遊戲,那你可以用這個標誌做下試驗,看看到底是VGA模式13h快還是Mode X 320×200快。不過說真的,差別不大。所以現在,就設成0吧。
預備知識到此結束,讓我們了轉換模式吧。要轉換模式,你必須首先創建DirectDraw對象,設置合作級別,然後設置顯式模式如下:
lpdd->SetDisplayMode(width,height,bpp,0,0);
比如,創建一個640×480 256色(8位色深),你將這樣做:
lpdd->SetDisplayMode(640,480,8,0,0);
設置 800x600 16位色深, 你該這麼做:
lpdd->SetDisplayMode(800,600,16,0,0);
現在,兩種模式的區別要比設置它們時的區別大得多了。8位色深模式的工作方式與16位、24位模式完全不同。如果你還記得,前幾章關於win32 GDI編程時提到調色板(第三章 高級Windows編程,和第四章 Windows GDI,控件,和雜感),同樣的道理在DirectDraw中也適用。即當你創建一個8位模式時,你其實是在創建堆模式{?palletized mode},此刻你必須創建一個調試板並用RGB的8.8.8格式填充調色板。
另一方麵,如果你創建一個色深為16、247、32色深的完全RGB模式,你便不需要想調色板了。你可以把數據編碼直接寫到視頻緩存中。不過至少,你應當學會如何與DirectDraw調色板協同工作(這上下一個話題所要討論的)。但在進一步學習前,讓我們看看用640×480×8的模式創建一個全屏DirectX程序的完整例子。
CD上的DEMO6_2.CPP及其對應的可執行文件就是這個例子。我隻是給你一個程序的輪廓,你所看到的不過是一個空白熒屏,因為這個demo是全屏程序麻。但是我確實向你展示了達到全屏功能的代碼。與往常一樣,這個demo是基於以前我們寫的遊戲控製台程序,不過做了些小修改,在Game_Init() 和 Game_Shutdown() 部分添加了一些與DirectX有關的改動。這些改動在下麵從DEMO6_2.CPP節選的代碼中可看出。仔細研究下哦,你會驚奇於它的簡潔的……
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// first create base IDirectDraw interface
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
{
// error
return(0);
} // end if
// set cooperation to full screen
if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX |
DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
{
// error
return(0);
} // end if
// set display mode to 640x480x8
if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH,
SCREEN_HEIGHT, SCREEN_BPP,0,0)))
{
// error
return(0);
} // end if
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all your cleanup and shutdown here
// simply blow away the IDirectDraw7 interface
if (lpdd)
{
lpdd->Release();
lpdd = NULL;
} // end if
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
此刻,有兩件事你沒做:控製調色板以及訪問顯式緩存。讓我們先關照下顏色問題。
奇妙的顏色
DirectDraw支持許多不同的色深,包括1、2、4、8、16、24、以及32位。顯然,1、2和4位色深有點過時了,所以你就別對這些色深過多的研究了。另一方麵,8、16、24、和32位模式非常流行。你寫的許多遊戲可能因為速度上的原因要在8位調色板模式(也是一個不錯的用於學習的模式)下運行。或在16或在24以利用全RGB顏色。RGB模式通過向緩存中寫入相同字節的數據來工作,如圖6.6所示。調色板模式則通過使用一個對緩存中每個像素都索引化了的查找表來工作。你應該很熟悉這種工作方式不是嗎,前文有所涉及的。
圖 6.6. 不同色深的比較
你要學的是,創建一個256色調色板,並通知DirectDraw你要用它。下麵讓我們來看看所需步驟:
1 創建一個或多個調色板數據結構,比如一個有256個PALETTENTRY結構的數組。
2 從DirectDraw對象創建一個DirectDraw調色板接口對象IDirectDrawPalette。大多數情況下,這個調色板對象將直接映射到已注冊的VGA硬件調色板上。
3 把調色板與繪圖表麵關聯起來,比如與主表麵關聯。這樣所有渲染到表麵的數據均將以合適的顏色顯式。
4 (可選)如果你想,你可以更改部分或全部調色板項。如果你跳過步驟1,在步驟2中創建的是一個空的調色板,那你就得在這步中做點什麼了。基本上,我所要說的是:當你創建了一個調色板接口是,你需要同時設置調色板顏色。如果你不同時做,那你稍後也的做。所以,隻要你記得在後麵初始化調色板,那步驟2可以變成步驟1。
讓我們開始創建調色板數據結構吧。它無非是是一個有256個調色板項的數組。調色板項是一個叫PALETTENTRY Win32結構,如下所示:
typedef struct tagPALETTEENTRY
{
BYTE peRed; // red component 8-bits
BYTE peGreen; // green component 8-bits
BYTE peBlue; // blue component 8-bits
BYTE peFlags; // control flags: set to PC_NOCOLLAPSE
} PALETTEENTRY;
看上去很眼熟嗎?很好。
總之,建個調色板,你隻需簡單的創建一個該結構的數組,如下:
PALETTEENTRY palette[256];
然後,你用你喜歡的方式填充這個數組。但是,這裏有條規矩:你必需把peFlags 域填為PC_NOCOLLAPSE.。這是必需的,因為你並不需要Win32或DirectX替你優化調色板。記得這些後,下麵是一個創建隨機調色板的例子。在第一個數組元素是黑色,最後一個元素是白色:
PALETTEENTRY palette[256]; // palette storage
// fill em up with color!
for (int color=1; color < 255; color++)
{
// fill with random RGB values
palette[color].peRed = rand()%256;
palette[color].peGreen = rand()%256;
palette[color].peBlue = rand()%256;
// set flags field to PC_NOCOLLAPSE
palette[color].peFlags = PC_NOCOLLAPSE;
} // end for color
// now fill in entry 0 and 255 with black and white
palette[0].peRed = 0;
palette[0].peGreen = 0;
palette[0].peBlue = 0;
palette[0].peFlags = PC_NOCOLLAPSE;
palette[255].peRed = 255;
palette[255].peGreen = 255;
palette[255].peBlue = 255;
palette[255].peFlags = PC_NOCOLLAPSE;
這就是全部的工作拉。當然,你可以創建多個調色板並分別對它們任意填充,這完全取決於你。
繼續,下一步是創建實際的IDirectDrawPalette接口。幸運的是,這個接口自DirectX6.0以來就沒怎麼變過,所以你無需使用 QueryInterface() 或其他任何方法來查詢最新版本的接口。下麵是IDirectDraw7:: CreatePalette()的原型聲明,其創建一個調色板對象。
HRESULT CreatePalette(DWORD dwFlags, // control flags
LPPALETTEENTRY lpColorTable, // palette data or NULL
LPDIRECTDRAWPALETTE FAR *lplpDDPalette, // received palette interface
IUnknown FAR *pUnkOuter); // advanced, make NULL
如果函數調用成功則返回DD_OK
讓我們看看各參數吧。第一個參數dwFlags,其用來控製各種調色板的各種屬性。下一個參數是一個指向用來初始化調色板的數據的指針,如果你不想指定則設為NULL即可。如果函數調用成功,則傳給第三個參數的指針將存儲真實的IDirectDrawPalette接口。最後,pUnkOuter是高級COM參數,一般簡單的設為NULL即可。
唯一讓人感興趣的參數,當然,便是dwFlags。讓我們更深入地看看你能對這個參數做些什麼。表6.4列出了可能的值,你可以用或運算綜合使用它們。
表 6.4. CreatePalette()的控製標誌
值 描述
DDPCAPS_1BIT 1位色深,在調色板中有2項。
DDPCAPS_2BIT 2位色深,在調色板中有4項。
DDPCAPS_4BIT 4位色深,在調色板中有16項。
DDPCAPS_8BIT 5位色深,最常用的,在調色板中有256項。
DDPCAPS_8BITENTRIES 這是一個索引化調色板的高級屬性,其用在1、2、4位色深的調色板上。記得別用它就是了。
DDPCAPS_ALPHA 表明,PALETTEENTRY結構的 peFlags 域被解釋為在一個8位值中用於控製透明度的alpha值。一個用該標誌創建的調色板隻能與由 DDSCAPS_TEXTURE標誌創建的D3D素材表麵關聯。再次的,這是個給高手準備的高級標誌。
DDPCAPS_ALLOW256 表明,這個調色板的256項均可被定義。通常,0和255項分別留給黑色和白色,在某些係統上,比如NT係統,在任何情況下你都是不可以 向這兩項寫入值的。但是,大多數情況下,你並不需要這個標誌,因為0項一般總是黑色,而大多數調色板把第255項定義成白色。當然用 不用,怎麼用,隨你。
DDPCAPS_INITIALIZE 由lpDDColorArray傳遞過來的數組初始化調色板的顏色。這個標誌被用來是調色板數據能被下載到硬件調色板上。
DDPCAPS_PRIMARYSURFACE 這個調色板將被關聯到主表麵上。在這個調色板上改變顏色將立即反應在顯式上,除非使用DDPSETPAL_VSYNC標誌並且DDPSETPAL_VSYNC 是被支持的功能。
DDPCAPS_VSYNC 強迫調色板隻在垂直空白間隙期間才更新顏色。這可以減小顏色異常和屏幕閃爍。目前還不是完全支持。
{譯者注:顯示器顯式圖形的過程是這樣的,先從屏幕左上角開始自左向右水平移動電子槍;到達右端時電子槍移到下一行左端再水平移 動,如此往複。電子槍從上一行右端到下一行左端這個空隙是不發電子的,但時間也很短,故也無法利用。但當電子槍移到右下角時,此時 下一步它將移動到第一行的左端,即要行走右下角到左上角這個對角線,這時這個時間就長一些了,這個時間就是“垂直空白間隙”簡記 為VBI。隨便說說,不對的還請指正。yew98。}
如果你問我,你一定會說:好多費解的控製字啊。其實,你隻需用8位調色板工作,所以你隻要對幾個控製字使用或運算即可。
DDPCAPS_8BIT | DDPCAPS_ALLOW256 | DDPCAPS_INITIALIZE
然後,如果你不在乎讓0項和255項,你可以忽略DDPCAPS_ALLOW256。進一步的,如果你不想在調用CreatePalette()期間初始化調色板,則你可忽略DDPCAPS_INITIALIZE。
把這些知識都吸進腦子裏吧。下麵便是你如何用剛才的隨機調色數據創建一個調色板對象。
LPDIRECTDRAWPALETTE lpddpal = NULL; // palette interface
if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT |
DDPCAPS_ALLOW256 |
DDPCAPS_INITIALIZE,
palette,
&lpddpal,
NULL)))
{
// error
} // end if
如果函數調用成功,lpddpal將指向一個有效的IDirectDrawPalette接口。同時,硬件調試板也會用發送給它的調色數據更新自己。在這裏,便是256個隨機顏色。
通常,在一節的末尾我都會給你一個demo。不過很不幸,這次我們遇到了個所謂“雞與蛋”的難題。也即,你必需先會在屏幕上花東西然後才能看看畫的顏色是什麼,而畫圖你還沒學呢。下麵就學吧。
創建一個顯式表麵
如你所知,在屏幕上顯式的圖形無非是一係列著了色的像素,而這些像素的顏色又是由內存中的一些格式化數據所描述,要麼是調色板,要麼是RGB。換句話說,要讓任何事情在屏幕上發生,你就需要知道如何在內存中繪畫。不過,DirectDraw是設計者決定對顯存進行一點抽象,這樣不管你係統中(別人係統中)使用什麼樣的顯卡,訪問視頻表麵的方法對於你(程序員觀點)而言是不變的。這樣,DirectDraw便支持了我們所謂的“表麵”概念。
看圖6.7,表麵是可以含有位圖數據的矩形內存區域。進一步的,有兩種不同的表麵:主表麵和副表麵。
圖 6.7. 表麵可以是任何尺寸
主表麵直接與顯卡上真實的顯存對應,並且始終可見。因此,在DirectDraw程序中你隻能有一個主表麵,並且它直接反應屏幕圖形而且存儲於VRAM(顯存)中。當你操作主表麵時,你會立刻看到結果顯示在屏幕上。比如,如果你設置視頻模式為640×480×256,則你必需創建一個同樣是640×480×256的主表麵,並且把它於顯示設備,即IDirectDraw7對象,關聯起來。
副表麵,則相當靈活。它們可以是任何尺寸,可以或保存在VRAM中或保存在係統內存中,而且你愛可以創建任意多個,隻要內存夠用。大多數情況下,你需要一個到兩個副表麵(緩衝區)用以平滑動畫。和主表麵一樣,副表麵也有色深和繪圖的工作要做。你在離屏(offscreen)表麵為下一幀動畫繪圖,然後快速的把離屏表麵拷貝或翻頁(page flip)到主表麵,這樣便平滑了動畫。這就是所謂的雙或三緩存。下一章你會學到更多相關知識。
副表麵的第二個用途是存儲遊戲中的位圖或動畫。這是DirectDraw的一個非常重要的特性,因為隻有使用DirectDraw表麵你才能在位圖數據上調用硬件加速功能。如果你你自己去寫位塊傳輸程序以繪位圖,那你將失去所有的加速功能帶來的好處。
現在我有點超前了,所以我得放慢點腳步。我超前的原因隻是想讓你有機會思考思考。現在,讓我們來看看如何創建一個與你的顯示模式相同大小的主表麵,然後你將學習如何在主表麵上寫數據以及在屏幕上繪像素。
創建一個主表麵
好拉,要創建任何表麵你必需安如下步驟進行:
1 填充DDSURFACEDESC2 數據結構,其是用來描述你創建的表麵的。
2 調用IDirectDraw7::CreateSurface()以創建表麵
下麵是CreateSurface()的原型聲明:
HRESULT CreateSurface(
LPDDSURFACEDESC2 lpDDSurfaceDesc2,
LPDIRECTDRAWSURFACE4 FAR *lplpDDSurface,
IUnknown FAR *pUnkOuter);
基本上,函數需要一個對你想創建的DirectDraw表麵的描述、一個接收接口的指針,以及最後把表征高級COM屬性的pUnkOuter設為NULL。嗬嗬,填寫對表麵進行描述的數據結構是件煩人的事情,但我會幫助你一步一步來的。首先,讓我們看看 DDSURFACEDESC2:
typedef struct _DDSURFACEDESC2
{
DWORD dwSize; // size of this structure
DWORD dwFlags; // control flags
DWORD dwHeight; // height of surface in pixels
DWORD dwWidth; // width of surface in pixels
union
{
LONG lPitch; // memory pitch per row
DWORD dwLinearSize; // size of the buffer in bytes
} DUMMYUNIONNAMEN(1);
DWORD dwBackBufferCount; // number of back buffers chained
union
{
DWORD dwMipMapCount; // number of mip-map levels
DWORD dwRefreshRate; // refresh rate
} DUMMYUNIONNAMEN(2);
DWORD dwAlphaBitDepth; // number of alpha bits
DWORD dwReserved; // reserved
LPVOID lpSurface; // pointer to surface memory
DDCOLORKEY ddckCKDestOverlay; // dest overlay color key
DDCOLORKEY ddckCKDestBlt; // destination color key
DDCOLORKEY ddckCKSrcOverlay; // source overlay color key
DDCOLORKEY ddckCKSrcBlt; // source color key
DDPIXELFORMAT ddpfPixelFormat; // pixel format of surface
DDSCAPS2 ddsCaps; // surface capabilities
DWORD dwTextureStage; // used to bind a texture
// to specific stage of D3D
} DDSURFACEDESC2, FAR* LPDDSURFACEDESC2;
如你所見,很複雜的一個結構。更有甚者,70%的域都相當詭異。不過幸運的是你隻需知道我加粗的那幾項,讓我們一個一個看:
dwSize:在任何DirectX數據結構中這個域都很重要。許多DirectX數據結構都是用地址來傳遞的,所以獲得數據結構的函數或方法是不知道數據結構的大小的。但是如果這個32位域的值總是數據結構的大小,那麼獲得數據結構的函數將總能通過訪問這第一個域來獲知數據結構的大小。所以,DirectDraw以及DirectX數據結構在第一個域中標識數據結構自身尺寸。這乍看上去好像有點多餘,但相信我,這確實是個很好的設計。填充這個域你隻需像如下這麼做:
DDSURFACEDESC2 ddsd;
ddsd.dwSize = sizeof(DDSURFACEDESC2);
dwFlags:這個域用來告訴DirectDraw,你給的數據是用來填充DDSURFACEDESC2的哪個域的。或者,如果你在一個查詢操作中用這個結構,告訴DirectDraw你要獲得DDSURFACEDESC2的哪個域的信息。看看表6.5,其列出了這個標誌字可取的值。比如,你如果你要在 dwWidth 和 dwHeight 兩個域中填入有效數據則應該像下麵這樣設置dwFlags域:
ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;
這樣,DirectDraw便知道去查找 dwWidth 和 dwHeight 域,然後放入有效值。就把單位dwFlags看成是引導數據的指示器吧。
表 6.5. DDSURFACEDESC2的dwFlags域的各種可能的標誌
值 描述
DDSD_ALPHABITDEPTH 表明 dwAlphaBitDepth 有效
DDSD_BACKBUFFERCOUNT 表明 dwBackBufferCount 有效
DDSD_CAPS 表明 ddsCaps 有效
DDSD_CKDESTBLT 表明 ddckCKDestBlt 有效
DDSD_CKDESTOVERLAY 表明 ddckCKDestOverlay 有效
DDSD_CKSRCBLT 表明 ddckCKSrcBlt 有效
DDSD_CKSRCOVERLAY 表明 ddckCKSrcOverlay 有效
DDSD_HEIGHT 表明 dwHeight 有效
DDSD_LINEARSIZE 表明 dwLinearSize 有效
DDSD_LPSURFACE 表明 lpSurface 有效
DDSD_MIPMAPCOUNT 表明 dwMipMapCount 有效
DDSD_PITCH 表明 lPitch 有效
DDSD_PIXELFORMAT 表明 ddpfPixelFormat 有效
DDSD_REFRESHRATE 表明 dwRefreshRate 有效
DDSD_TEXTURESTAGE 表明 dwTextureStage 有效
DDSD_WIDTH 表明 dwWidth 有效
dwWidth:表明表麵的像素寬度。當你創建一個表麵是,這裏便是你設定寬度的地方。320,640等等。此外,如果你要查詢表麵的屬性,這個域將返回表麵的寬度(如果你這麼要求的話)。
dwHeight:表麵表麵的像素高度。與dwWidth類似,這裏是你在創建表麵時設定其高度的地方。300,240,480等等。
lPitch:這是個有趣的域。基本上它是你所選擇模式的水平內存間距。看圖6.8。lPitch,也被稱為步幅或內存寬度,是在給定視頻模式下每行的的字節數。基於如下原因,這是個非常重要的數據域:當你要求一個640×480×8的顯示模式,你知道每行有640個像素,每個像素占8位內存(即1個字節)。所以,每行有640個字節,於是lPitch似乎便該設為640。對嗎?不一定哦。
圖 6.8. 訪問一個表麵
技巧:lPitch將根據VRAM的不同設計而不同。故而,當你在DirectDraw表麵上從一行訪問另一行的內存時,你必需使用lPitch來移動到下一行,而不是用寬度乘上每像素的字節數。這一點非常之重要。
大多數新顯卡支持我們所謂的“線性內存模式”而且有硬件尋址功能,這些屬性已經是現實了,但並不保證每塊顯卡都實現之。所以,你不能假設一個640×480×8視頻模式在內存中每行占640個字節。而這便是lPitch域存在的理由。你必需在你計算內存地址時應用它以保證計算正確,這樣你便可以從一行移動到另一行了。比如,要訪問640×480×8(256色)視頻模式下的任何一個像素,你可以使用下麵的代碼。假設你已經從DirectDraw得到lPitch值,並且lpSurface已經指向表麵內存(我會在下麵解釋這個參數的)
ddsd.lpSurface[x + y*ddsd.lPitch] = color;
是不是很簡單啊?大多數情況下,ddsd.lPitch 設為640以代表640x480x8模式。而對於640×480×16模式,ddsd.lPitch 將設為1280(兩字節每像素=640×2)。但是,對於某些顯卡,出於顯存的布局原因,比如內在緩存的設立或其他什麼東西,就使得事情不像上述這樣了。所以最正規的方式是:在計算內存時總是使用lPitch,你便總是安全的。
技巧:盡管lPitch值並不總是等於你設置的視頻模式的水平值,但使用其來測試水平值可以使你能夠調用到其他優化函數。比如,在你完成初始化部分功能的代碼中,你可以去獲得lPitch值並與你選擇的視頻模式水平值比較。如果它們相等,則你可以切換到你為優化程序而硬編碼每行字節數的函數上。
lpSurface:這個域獲得指向你隨創建表麵的真實內存地址的指針。這些內存可能是VRAM也可能是係統內存,但是你無需為次擔心。一旦你獲得了指向該內存的指針,你便可以像操作其他內存一樣操作它了,比如向它寫數據或讀數據,等等,這完全取決於你想如何填充像素。嗬嗬,讓這個指針有效多容易啊!但是我們還是要在這裏多停留一會。一般,你必需“鎖定”表麵內存,並且告訴DirectX你要在該內存上麵工作了而其他的進程不許試圖在該內存上讀或寫。進一步的,當你獲得了這個指針時,根據不同的色深(8,16,24,32)你要經常對其進行類型轉換並將其複值給一個工作指針。
dwBackBufferCount:這個域被用來設置和讀取後備緩存(或與主表麵關聯的副離屏翻頁緩存)的數目。如果你能回憶起來,通過創建一個或更多的虛主表麵(與表麵擁有同樣的圖樣和色深的緩存),後備緩存可用來實現動畫的平滑化,這也便是離屏緩存。然後你在後備緩存上繪圖,這個後備緩存對於用戶而言是不可見的。接著快速的翻頁或拷貝後備緩存到主表麵以供顯示。如果你隻有一個後備緩存,這個技術便成為“雙緩存技術”。使用兩個緩存便叫“三緩存技術”,當然後者比前者擁有更好的效果但也占用更多的內存。為了是事情簡單點,大多數情況下,你將創建一個包括一個主表麵和一個後備緩存的翻頁鏈。
ddckCKDestBlt:這個域被用來控製目標顏色鍵。當進行塊傳輸操作時,這個域控製寫入顏色的方式。更多信息將在後麵第七章“高級DirectDraw和位圖圖形”上介紹。
ddckCKSrcBlt:這個域表明源顏色鍵。即當你進行bitmapping操作時{?後麵好像有這個術語,一時想不起來了,但翻到後麵再該過來吧。yew98}你不想被塊傳遞的顏色。這是一個你如何設置你位圖透明色的的方法。詳見第七章。
ddpfPixelFormat:這個域用來獲得表明像素的格式。當你去查詢表明的屬性是這就非常重要了。下麵的是其結構。你可能要去查詢DirectX SDK以獲得更多的細節信息。因為這些信息實在太多了,而且和現在的討論關心也不大。
typedef struct _DDPIXELFORMAT
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwFourCC;
union
{
DWORD dwRGBBitCount;
DWORD dwYUVBitCount;
DWORD dwZBufferBitDepth;
DWORD dwAlphaBitDepth;
DWORD dwLuminanceBitCount; // new for DirectX 6.0
DWORD dwBumpBitCount; // new for DirectX 6.0
} DUMMYUNIONNAMEN(1);
union
{
DWORD dwRBitMask;
DWORD dwYBitMask;
DWORD dwStencilBitDepth; // new for DirectX 6.0
DWORD dwLuminanceBitMask; // new for DirectX 6.0
DWORD dwBumpDuBitMask; // new for DirectX 6.0
} DUMMYUNIONNAMEN(2);
union
{
DWORD dwGBitMask;
DWORD dwUBitMask;
DWORD dwZBitMask; // new for DirectX 6.0
DWORD dwBumpDvBitMask; // new for DirectX 6.0
} DUMMYUNIONNAMEN(3);
union
{
DWORD dwBBitMask;
DWORD dwVBitMask;
DWORD dwStencilBitMask; // new for DirectX 6.0
DWORD dwBumpLuminanceBitMask; // new for DirectX 6.0
} DUMMYUNIONNAMEN(4);
union
{
DWORD dwRGBAlphaBitMask;
DWORD dwYUVAlphaBitMask;
DWORD dwLuminanceAlphaBitMask; // new for DirectX 6.0
DWORD dwRGBZBitMask;
DWORD dwYUVZBitMask;
} DUMMYUNIONNAMEN(5);
} DDPIXELFORMAT, FAR* LPDDPIXELFORMAT;
注意:我加粗的域是常用域
ddsCaps:這個域用來使那些被要求的但尚未在表麵屬性中定義的項有效。事實上,這個域又是另一個數據結構。下麵顯示了DDSCAPS2:
typedef struct _DDSCAPS2
{
DWORD dwCaps; // Surface capabilities
DWORD dwCaps2; // More surface capabilities
DWORD dwCaps3; // future expansion
DWORD dwCaps4; // future expansion
} DDSCAPS2, FAR* LPDDSCAPS2;
在99.9%的情況下,你隻需設置該結構的第一個域。dwCaps.dwCaps2 是為3D準備的,而其他域dwCaps3 和 dwCaps4是為未來擴展之用,尚未使用。總之,表6.6列出了dwCaps可能設置的標誌值。要看完整的列表,到DirectX SDK裏去。
比如當要創建一個主表麵是,你可以像下麵這樣設置ddsd.ddsCaps
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
我知道上麵這種表達方式很複雜,在某種程度上確實如此。雙重嵌套的控製標誌確實有點痛苦。但是,忍受吧……
表 6.6. DirectDraw表麵的屬性控製設置
值 描述
DDSCAPS_BACKBUFFER 表明這個表麵是表麵翻頁結構中的後備緩存。
DDSCAPS_COMPLEX 表明是一個複習表麵。一個複雜表麵是一個與一或多個串成翻頁鏈的後備緩存關聯的主表麵。
DDSCAPS_FLIP 表明這個表麵是一個表麵翻頁結構中的一部分。但這個屬性被傳遞給 CreateSurface() 方法時, 一個前端緩存和一個或多個後備 緩存將被創建。
DDSCAPS_LOCALVIDMEM 表明這個表麵優先存在於顯存中。如果這個標誌選中則 DDSCAPS_VIDEOMEMORY 也必需選中。
DDSCAPS_MODEX 表明這個表麵是一個320x200 或 320x240 的Mode X 表麵。
DDSCAPS_NONLOCALVIDMEM 表明這個表麵優先存在於非本地顯存中。如果這個標誌選中則 DDSCAPS_VIDEOMEMORY 也必需選中。
DDSCAPS_OFFSCREENPLAIN 表明這個表麵是個離屏表麵。這樣該表麵便不能是某種特殊表麵了,比如不是一個覆蓋層,素材,z序,前端緩存,後備緩存,或者 alpha 表麵。(Usually used for sprites。)
DDSCAPS_OWNDC 表明這個表麵將長期與一個設備上下文關聯。
DDSCAPS_PRIMARYSURFACE 表明這個表麵是主表麵。其描述了此刻為用戶所見的東西。
DDSCAPS_STANDARDVGAMODE 表明該表麵是一個標準的VGA模式的表麵,而非 Mode X 模式。這個標誌不能與 DDSCAPS_MODEX 標誌同時使用。
DDSCAPS_SYSTEMMEMORY 表明這個表麵創建在係統內存中。
DDSCAPS_VIDEOMEMORY 表明這個表麵創建在顯存中。
現在你對當你在創建表麵時DirectDraw賦予你的強大而複雜的功能有個概念了吧。讓我們把知識付諸實踐,創建一個簡單的與顯示模式同樣尺寸和色深(默認操作即如此)的主表麵吧。下麵是創建主表麵的代碼:
// interface pointer to hold primary surface, note that
// it's the 7th revision of the interface
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL;
DDSURFACEDESC2 ddsd; // the DirectDraw surface description
// MS recommends clearing out the structure
memset(&ddsd,0,sizeof(ddsd)); // could use ZeroMemory()
// now fill in size of structure
ddsd.dwSize = sizeof(ddsd);
// enable data fields with values from table 6.5 that we
// will send valid data in
// in this case only the ddsCaps field is enabled, we
// could have enabled the width, height etc., but they
// aren't needed since primary surfaces take on the
// dimensions of the display mode by default
ddsd.dwFlags = DDSD_CAPS;
// now set the capabilities that we want from table 6.6
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
// now create the primary surface
if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL)))
{
// error
} // end if
如果函數調用成功,則lpddsprdimary將指向新的表麵接口,而你便可以從它上麵調用函數了(有很多函數可用它上麵調用的,比如在256色模式下關聯調色板)。讓我們來再通過重複調色板的例子來看看吧。
關聯調色板
在前麵關於調色板的章節中你已經做了所以的工作,除了把調色板與一個表麵關聯起來。你創建了一個調色板並填充了各個顏色項,但你並沒有把調色板與某個表麵關聯起來,因為當時你還沒有表麵呢。現在,你有一個表麵了(主表麵),你終於可以完成整個步驟了。
要不調色板關聯到任何一個表麵上,你所要做的便是使用IDirectDrawSurface7::SetPalette()函數,如下所示:
HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette);
這個函數隻需要一個你想要被關聯的調色板指針即可。就用你在前麵調色板章節中創建的調色板吧。下麵是你如何把調色板與主表麵關聯的代碼:
if (FAILED(lpddsprimary->SetPalette(lpddpal)))
{
// error
} // end if
還不壞,對吧。此時,你已經具備了任何使你能效仿DOS32遊戲的條件。你可以轉換視頻模式,設置調色板,已經創建主繪圖表麵以描繪生動的視頻圖像。但是,仍然有一些細節你需要學習,例如鎖定主表麵內存並且獲得訪問VRAM的能力以及繪一個像素。讓我們現在就來學下這些東西。
繪製像素
想要在全屏DirectDraw模式下繪製一個(或一些)像素,你必需首先創建Dir
最後更新:2017-04-03 14:54:08