使用位塊傳輸
位塊傳輸是顯示卡操控位元影像資料的一部分,你同樣可以用它來進行顏色填充。就像我們過一會兒看到的,隨著硬體的效能提高,會有很多經典的技巧。DirectX有權使用硬體的加速功能,但要記住,如果DirectX使用的加速功能不被機器硬體支援,將自動啟用硬體模擬層(HEL),但這也並非萬無一失,因為有些功能靠硬體模擬層是無法實現的(否則誰還買3D加速卡^_^),所以你需要檢測你的函數是否調用成功。
GDI位塊傳輸可以在DirectDraw編程中使用,而且有時也的確是這樣做的。然而DirectDraw具有其自身的位塊傳輸函數,它們通常更加適合於編程環境,而且比GDI的相應的函數執行得更快。DirectDraw位塊傳輸函數名為Blt()和BltFast(),都是有IDirectDrawSurface7介面提供的。兩者不同處是BltFast()不處理剪下、放縮等其它Blt()做的有趣的事情。如果在硬體模擬層上,BltFast()要比Blt()快10%左右,但如果有硬體加速卡支援(硬體加速卡主要就是為位塊傳輸服務的),二者的速度就差不多了,而且現在大多數的機器都有硬體加速卡,所以我總是使用Blt()。讓我們仔細看看這個神奇的東東:
HRESULT Blt(
LPRECT lpDestRect,
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwFlags,
LPDDBLTFX lpDDBltFx
);
由於Blt()所擁有的最後一個參數,使其能做很多特殊的事兒。該參數配有一個標誌常量列表,我將會向你介紹其中最有用的幾個。另外,注意在把位元影像從一個表面向另一個表面傳遞時,你應該調用目的表面的Blt(),不是源表面的。好了嗎?以下是函數的參數說明:
※ LPRECT lpDestRect:參數lpDestRect為指向結構RECT的指標,它給出了位塊傳輸操作的目標表面的左上方和右下角的座標。如果源表面和目標(目的)表面的大小不一致,Blt()將把源表面的圖象自動按照比例適應目標表面的大小。如果此參數為NULL,則使用整個目標表面。
※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:參數lpDDSrcSurface為指向DirectDraw表面的指標,該DirectDraw表面為位塊傳輸之源表面。如果你只是要用顏色填充目的表面,你可以把它設定為NULL。
※ LPRECT lpSrcRect:參數lpSrcRect為指向結構RECT的指標,它給出了位塊傳輸(有的書上也叫作“位轉換”)操作的源表面的左上方和右下角的座標。如果此參數為NULL,則使用整個源表面。
※ DWORD dwFlags:對於這個參數有一個巨大的標誌常量列表,可以用“|”組合使用標誌常量。其中一些是為Direct3D服務的,所以我將把我們常用的列出來: · DDBLT_ASYNA:位塊傳輸非同步以先入先出(FIFO)的順序接收。如果沒有空間可用於FIFO硬體,則該調用失敗。
· DDBLT_COLORFILL:使用DDBLTFX結構的資料成員dwFillColor作為RGB顏色填充目標表面的矩形。
· DDBLT_DDFX:DDBLTFX結構的dwDDFX成員指定了位塊傳輸的使用效果。
· DDBLT_DDROPS:DDBLTFX結構的dwDDROP成員指定了光柵操作(ROPS),該操作不是Win32 API的一部分。
· DDBLT_KEYDEST:色鍵與目標表面相關聯。
· DDBLT_KEYDESTOVERRIDE:DDBLTFX結構的dckDestColorkey成員是目標表面的色鍵。
· DDBLT_KEYSRC:色鍵與源表面相關聯。
· DDBLT_KEYSRCOVERRIDE:DDBLTEX結構的dckSrcColorkey成員是源表面的色鍵。
· DDBLT_ROP:DDBLTFX結構的dwROP成員是位塊傳輸的ROP(光柵作業碼),這些ROP與Win32 API中定義的那些相同。
· DDBLT_ROTATIONANGLE:DDBLTFX結構的dwRotationAngle成員是表面的旋轉角度,其單位為1/100度。
· DDBLT_WAIT:在位塊傳輸器忙的情況下,延遲DDERR_WASSTILLDRAWING傳回值(位塊傳輸函數調用失敗返回的值之一),而當位塊傳輸開始或發生另一個錯誤時立即返回。
我幾乎總是使用DDBLT_WAIT標誌。色鍵標誌也是很重要的,我們過一會兒再說它。現在,還有最後一個Blt()參數需要說一下:
※ LPDDBLTFX lpDDBltFX:這是一個指向DDBLTFX結構的指標,它可以包含各種特殊要求的資訊。如果沒有什麼特殊要求,你就設定為NULL好了。讓我們仔細看看這個結構。我警告你,它是很魁梧的:^_^
typedef struct _DDBLTFX{
DWORD dwSize;
DWORD dwDDFX;
DWORD dwROP;
DWORD dwDDROP;
DWORD dwRotationAngle;
DWORD dwZBufferOpCode;
DWORD dwZBufferLow;
DWORD dwZBufferHigh;
DWORD dwZBufferBaseDest;
DWORD dwZDestConstBitDepth;
union {
DWORD dwZDestConst;
LPDIRECTDRAWSURFACE lpDDSZBufferDest;
};
DWORD dwZSrcConstBitDepth;
union {
DWORD dwZSrcConst;
LPDIRECTDRAWSURFACE lpDDSZBufferSrc;
};
DWORD dwAlphaEdgeBlendBitDepth;
DWORD dwAlphaEdgeBlend;
DWORD dwReserved;
DWORD dwAlphaDestConstBitDepth;
union {
DWORD dwAlphaDestConst;
LPDIRECTDRAWSURFACE lpDDSAlphaDest;
};
DWORD dwAlphaSrcConstBitDepth;
union {
DWORD dwAlphaSrcConst;
LPDIRECTDRAWSURFACE lpDDSAlphaSrc;
};
union {
DWORD dwFillColor;
DWORD dwFillDepth;
DWORD dwFillPixel;
LPDIRECTDRAWSURFACE lpDDSPattern;
};
DDCOLORKEY ddckDestColorkey;
DDCOLORKEY ddckSrcColorkey;
} DDBLTFX, FAR* LPDDBLTFX;
如果我整個詳細的介紹這個結構,恐怕我們都會受不了的,並且也沒有這個必要。所以我只告訴你一些重點的部分。謝天謝地,該結構的大部分都是為z緩衝區(z-buffers)和αMessage Service的,我們不用理會它。嘻嘻,我的工作量變得很小了:
※ DWORD dwSize:象所有的DirectX的結構一樣,當你初始化這個結構時,該成員放置結構的大小。
※ DWORD dwDDFX:這些是位塊傳送所能接受的一些特殊操作。列表並不長,別擔心喔!
· DDBLTFX_ARITHSTRETCHY:位塊傳輸時,在Y軸算術展開位元影像。
· DDBLTFX_MIRRORLEFTRIGHT:y軸上的鏡像變換。表面從左至右完成鏡像效果。
· DDBLTFX_MIRRORUPDOWN:x軸上的鏡像變換。表面從上到下完成鏡像效果。
· DDBLTFX_NOTEARING:把動畫映像塊轉移到前段緩衝時可以使用這個參數,這樣位塊傳輸操作的時間會與螢幕重新整理率相一致,並使畫面撕裂的可能性減小到最小。
· DDBLTFX_ROTATE180:位塊傳輸時,把表面順時針旋轉180度。
· DDBLTFX_ROTATE270:位塊傳輸時,把表面順時針旋轉270度。
· DDBLTFX_ROTATE90:位塊傳輸時,把表面順時針旋轉90度。
需要詳細解釋的可能只有DDBLTFX_NOTEARING。遊戲離不開動畫,動畫製作者主要關心的通常是動畫的速度和效能,速度太快會導致圖象品質的惡化。光柵掃描顯示系統(我們基本上用的都是這種顯示器)利用電子束掃描每一條水平線上的螢幕象素點。象素行從螢幕的左上方開始更新,到螢幕的右下角結束。各象素行都被稱為掃描線。電子束在每一行掃描線的末端被關掉,而電子槍重新瞄準下一行的起始點,這個過程成為水平回掃。當過程執行到螢幕掃描線的最後一行時,電子束再次被關掉,電子槍重新瞄準螢幕的左上方。電子槍從螢幕的右下角重新瞄準到左上方的過程所需的時間被稱為垂直迴歸或者螢幕空白周期。如果在視頻控制器顯示視頻資料的同時,視頻資料被CPU做了更改,這時就會產生問題。在PC機中,螢幕的重新整理率通常在60Hz到100Hz之間,而現在的CPU則可以在每秒處理成百上千的指令,這樣就很可能導致位於視頻記憶體區的圖象在視頻系統完成顯示之前發生更改。圖象斷裂的結果被稱為圖象撕裂。就我的經驗而言,使用了DDBLTFX結構的DDBLTFX_NOTEARING後,圖象撕裂就不是什麼問題了。
※ DWORD dwROP:使用這個標誌來指定Win32模式的光柵作業碼。同GDI函數BitBlt()和StretchBlt()中的相對應參數的功能一樣。可以通過IDirectDraw7::GetCaps()函數得到可能的光柵巨集指令清單,可以通過“|”組合標誌常量,確定源矩形表面和目標矩形表面是怎樣結合的。
※ DWORD dwRotationAngle:這是用來旋轉位元影像角度的,可以旋轉任意角度。這是非常棒的,但不幸的是,它只能在HAL層(硬體抽象層)上工作,這就意味著使用者的顯示卡要支援加速旋轉,否則……,但不能保證每個使用者都有這種高檔的顯示卡,所以你需要考慮周全。如果你真的需要旋轉處理,你只好自己寫這樣的函數了,這可是一個大話題,需要另寫一部指南了,所以我們就越過它。但請注意,如果是90度倍數的角度,你可以使用DDBLTFX_ROTATE90等。這將使你迴避顯示卡不幹活的風險。
※ DWORD dwFillColor:如果你要使用位塊傳輸來填充顏色,你必須把顏色放入到這個參數中。
※ DDCOLORKEY ddckDestColorKey,ddckSrcColorKey:你要使用色鍵時必須要指定這些成員。這兩個傢伙是很重要的。但暫時我們還不討論它們,因為我們過一會兒才講色鍵。
以上這些就是DDBLTFX結構中比較有用的成員了,這就意味著你現在擁有足夠的知識進行位塊傳輸了!如果你現在感覺有些混亂,不要緊,你實踐過一段時間就會好了。讓我們看看幾個例子。假設你已經有了一個叫作lpddsBack的後緩衝區,你想把它裡面的內容傳輸到主表面,很簡單的,你看:
lpddsPrimary->Blt(NULL, lpddsBack, NULL, DDBLT_WAIT, NULL);
再輕輕回憶一下,第一個參數和第三個參數分別是位塊傳輸的目標矩形和源矩形。由於我把它們都設定為NULL,就說明是對全部的表面進行拷貝。現在,讓我們在看看,假設你有一個在離屏表面裡的名字叫作lpddsTileset的16×16大小的圖形,你想把它傳輸到後緩衝區,變成32×32大小的,你需要這樣做:
RECT dest, src;
SetRect(&src, 0, 0, 16, 16); // the coordinates of the tile
SetRect(&dest, 0, 0, 32, 32); // where you want it to end up on the back buffer
lpddsBack->Blt(&dest, lpddsTileset, &src, DDBLT_WAIT, NULL);
這個例子同上一個例子不同處在與這個例子設定了位塊傳輸的座標。由於這兩個矩形區的大小不同,Blt()依照比例適當的變更了圖形的大小。最後,我們還得舉例說明一下DDBLTFX結構,就做一個顏色填充的例子吧。假設你在16-bit色彩模式下,是565象素格式,你要把你的後緩衝區填充為藍色。下面就是你應該做的:
DDBLTFX fx;
INIT_DXSTRUCT(fx); // zero out the structure and set dwSize
fx.dwFillColor = RGB_16BIT565(0, 0, 31); // set fill color to blue
lpddsBack->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
注意參數的設定,前三個都是NULL,你自己想想原因吧!好了,讓我們看看另一個位塊傳輸函數BltFast()吧。它只是Blt()的簡化版本,所以我們不需要太多的時間解釋它。下面是它的原形:
HRESULT BltFast(
DWORD dwX,
DWORD dwY,
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwTrans
);
你可以看得出來,它同Blt()極其相似。它也是IDirectDrawSurface7介面的成員函數,被目標表面調用。來看看它的參數:
※ DWORD dwX,dwY:這是Blt()和BltFast()之間不同的地方。是目標表面上進行位塊傳輸的x和y座標。如果源矩形大於目標矩形,則調用失敗,因為BltFaxt()不能幹按比例變換的事兒及其它能夠通過Blt()完成的工作。
※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:這是源表面,同Blt()的一樣。
※ LPRECT lpSrcRect:這個也同Blt()的一樣。是在源表面中定義了矩形左上方和右下角的RECT結構。
※ DWORD dwTrans:定義了位塊傳輸類型,它的標誌列表很簡單,只有四個標誌:
· DDBLTFAST_DESTCOLORKEY:使用目標色鍵的透明位塊傳輸。
· DDBLTFAST_NOCOLORKEY:沒有透明的普通複製位塊傳輸。
· DDBLTFAST_SRCCOLORKEY:使用源色鍵的透明位塊傳輸。
· DDBLTFAST_WAIT:如果位塊傳輸忙的話,不產生DDERR_WASSTILLDRAWING訊息。一旦位塊傳輸能夠開始或者發生另一個錯誤時返回。
就這些!BltFast()支援色鍵。下面讓我們看一個簡單的樣本。把整個後緩衝區拷貝到主表面:
lpddsPrimary->BltFast(0, 0, lpddsBack, NULL, DDBLTFAST_WAIT);
到現在為止,你已經是一個位塊傳輸的專家了,還有幾件事情對於DirectX程式很重要:色鍵和剪裁板。你可能知道在某些情況下關於色鍵有上百萬種標誌,那麼到底怎樣使用它呢?讓我們一起看看吧!