遊戲開發基礎(4)

第四章 diectxdarw基礎篇 
第一節 DirectDraw簡介

Grubers的一個觀點是DirectDraw“只是一個bltting發動機”。這是相當準確的,但卻太簡化了。更準確地講,DirectDraw是一個可以提供軟件仿真測試的獨立於硬件設備的bltting發動機。DirectDraw的主要用途是儘可能快、儘可能可靠並且儘可能連續地將圖形考貝到視頻顯示設備上。
  另外一個定義DirectDraw的方式是把它作爲一個視頻存儲器管理器,同常規的存儲器管理器一樣,DirectDraw發放存儲器信息包,跟蹤每一個信息包的狀態。信息包可以隨意地創建、複製、修改或破壞,同時這些操作的細節被程序員隱含起來,這樣講是過於簡單了。此外,DirectDraw是能夠使用系統RAM和視頻RAM的。存儲器管理器也經常被設計成和主要目標一樣強健,而不只是追求性能。對於DirectDraw,性能只是設計目標之一。
從技術角度講,DirectDraw是隨同設備驅動器集合的便攜式API。DirectDraw設計成完全避開傳統意義上的Windows圖形機構(GDI,或稱圖形設備接口)。GDI由於性能低而名聲不好,所以DirectDraw的設備獨立性在提供最佳性能方面是至關重要的。

第二節 DirectDraw基本概念
1. 顯示模式
顯示模式是由允許將要顯示的圖形輸出的顯示硬件支持的可視配置。最常用的顯示模式屬性是分辨率。Windows使用的顯示模式的默認值是640×480的分辨率。這意味着,水平方向有640個像素,垂直方向有480個像素。其他一些常見的顯示模式分辨率有800×600,1024×768。一些顯示卡支持Mode X顯示模式。一個典型的Mode X顯示模式的分辨率爲320×200。
  顯示模式也隨像素深度的變化而變化。像素深度決定着每一個像素所容納的多少不同的值,因而也就可以顯示多少種顏色。例如對於8位像素深度的顯示模式,每個像素能夠再現256種顏色的一種。像素深度爲16位的顯示模式支持65536種顏色(即2的n次方),典型的像素深度爲8、16、24和32位。
  顯示模式由安裝在機器中的顯示設備或視頻卡支持。顯示設備有自己的RAM,從計算機的RAM中分離出來。我們把位於顯示設備中的存儲器稱爲顯示RAM,而把常規存儲器稱爲系統RAM。
支持一個給定的顯示模式的RAM的容量取決於顯示模式的分辨率和像素深度。例如,640×480×8(640×480像素,深度爲8位)的顯示模式需要307200字節。1024×768×16的顯示模式需要1572864字節。支持顯示模式的存儲器必須是顯示RAM。一個給定顯示設備所支持的顯示模式因此也被可以利用的顯示RAM的容量所限制。例如,1024×768×16的顯示模式因爲需要一兆字節以上的內存,所以就不能被只有一兆字節RAM的顯示設備所支持。
DirectDraw的一個主要特徵是顯示模式切換。這允許一個DirectDraw應用程序檢測和激活所安裝的顯示設備所支持的任何顯示模式。我們將在第4章中討論顯示模式切換的細節。
2. 硬件加速
DirectDraw具有最優性能的最重要的原因是,它儘可能地使用硬件加速來進行設計。硬件加速發生在當顯示設備能夠用建立在顯示設備之中的處理功能執行操作時。硬件加速具有兩個優點,首先,當硬件加速出現的時候,硬件按指定要求設計成支持圖形操作,這提供了執行給定任務的最快的方法:其次,硬件加速使得計算主處理器從執行操作中解放出來,這使得主處理器可以執行其他任務。
3. 表面
表面是存儲器的一個矩形部分的DirectDraw術語,通常包括圖像數據。該存儲器通常用來表示一個存在於顯示RAM或系統RAM中的表面。駐留在顯示RAM中的表面享有超高性能,因爲絕大多數顯示硬件不能對系統RAM直接存取。
表面分爲向大類,最簡單的類型是脫離屏幕表面。脫離屏幕表面可以駐留在顯示RAM中或系統RAM中,但卻不能被顯示。這類表面一般用於存儲子畫面和背景。
另一方面,一個主表面是可在屏幕上看到的視頻RAM的一部分部分。所有的DirectDraw程序(可以提供視頻輸出)都擁有主表面。主表面必須駐留在顯示RAM中。
主表面通常很複雜,或是可翻轉的。可翻轉表面允許頁面翻轉,這是一項整個表面的內容可以通過一個硬件操作而瞬時可見的技術。頁面翻轉用於許多基於DirectDraw或其他的圖形應用程序中,因爲它可以產生相當平滑、不閃爍的動畫。一個可翻轉的主表面實際上是兩個表面,一個可見,另一個不可見。不可見的表面稱爲後備緩衝區。當發生頁面翻轉時,以前是後備緩衝區的表面就成爲可見的,而以前可見的表面則成爲後備緩衝區。
  離屏表面和主表面都有兩類:調色板的和無調色板的。在DirectDraw中,只有8位表面是調色板表面。調色板表面並不包含色彩數據,但是卻引入一個色彩表。該表稱爲調色板。像素深度爲16、24或32位的表面是無調色板表面。無調色板表面存儲實際色彩值,而不引入調色板。
因爲在無調色板表面中的每一個像素都存儲色彩數據,所以知道表面的像素格式是很重要的。像素格式描述了存儲於像素中的紅色、綠色和藍色(RGB)元件的方式。像素格式隨像素深度、顯示模式和硬件設計的不同而不同,在第5章中可以瞭解所有的像素格式。
4. Bltting
Bltting是用於複製的圖形語言。典型的blt操作是將離屏表面的內容拷貝到一個後備緩衝區中。當Bltting通過硬件完成的時候,執行速度相當快。如果無法得到硬件加速,DirectDraw將使用一個軟件操作來仿真blt。這種仿真操作雖然也能夠完成任務,但卻比硬件慢得多.一般只有駐留在顯示RAM中的表面能夠通過使用顯示硬件來完成blt。
  blt操作調用一個源表面和一個目標表面,源表面的內容被拷貝到目標表面中。源表面中的內容在操作中不會改變,只有目標表面受blt的影響。blt操作也並不需要使用全部的源表面或目標表面。源表面中的任何矩形區域可以被放置於目標表面中的任何位置。
不規則形狀表面的bltting(例如典型的子畫面)是以透明方式完成的。透明性是通過指定表面中某個不被blt操作拷貝的像素而獲得的。像素值通過使用色彩鍵碼給以標誌。
色彩鍵碼可以附加到源表面或目標表面上。源色彩鍵碼是很普遍的。源色彩鍵碼允許透明性,因爲源表面中的像素值並未被考貝。至於目標色彩,只有目標表面中通過色彩所指定的像素值能夠被源表面的內容所覆蓋。
DirectDraw也支持一些特定的操作,包括拉伸、壓縮、鏡像映射,以及混合等。這些功能的實現往往取決於顯示硬件。DirectDraw能夠仿真其中的某些操作,但是跟性能相比,價格往往是昂貴的。
DirectDraw也有不能仿真的功能(例如目標色彩鍵碼)。使用這些功能是冒險的,除非該功能爲所安裝的顯示硬件支持,否則使用該功能的操作將失敗。這給DirectDraw的開發者帶來兩種基本選擇:要麼放棄使用這些功能:要麼往應用程序中增加定製軟件。
5. 調色板
  使用8位顯示模式的應用程序需要提供調色板。調色板就是任何時候都可以使用的色彩表。如果8位顯示模式不需要調色板,應用程序將被迫使用256種顏色的固定設置。調色板允許用戶定義將要使用的256種顏色之一。
  當你使用調色板顯示模式時,必須保證在應用程序中的圖像也使用同一調色板。如果沒有做到這一點,所顯示的一些或全部圖像中將出現錯誤的顏色。調色板也會帶來麻煩,尤其是用一個調色板來顯示大量圖像的時候。調色板也有一些優勢。正如前面提到的,調色板允許在一個有限色彩的場合使用最多的色彩。調色板也允許調色板動畫。
  調色板動畫是動畫通過改變調色板項目,而不是改變像素值來執行的技術,這就使得一個屏幕上的很多像素可以瞬時改變顏色。對於一些有限的應用程序,諸如分配的、重複的動畫,調色板動畫很有用處。
6. 剪裁
理想狀態下,一個blt操作就是整個表面被blt成爲另一個表面。通常源表面被blt成爲目標表面的邊,或者目標表面被另一個表面或窗口遮蔽。像這樣的情況就需要進行剪裁。剪裁只允許一部分或一個表面的一部分被blt。
在編寫窗口DirectDraw應用程序時經常用到剪裁,因爲這些應用程序必須遵守Windows桌面的規則。我們將在本章後面討論窗口應用程序。
DirectDraw提供全矩形剪裁支持。也有這種情況,就是付費提供定製剪裁例程,我們將在第3章中研究定製剪裁解決方案。
7. 其他表面
離屏表面和主表面(具有任選的後備緩衝區)是絕大多數DirectDraw應用程序的主幹。然而一些其他的表面就有不同,包括重疊表面、alpha通道表面、Z-緩衝區以及3D設備表面等。
重疊表面是硬件單色畫面,因而也就在僅在支持重疊的顯示硬件上獲得。和軟件單色畫面不同,它可以被移動而不需要背景圖像被恢復。
alpha通道表面用來執行alpha調配。Alpha調配是透明的高級形式。允許表面以透明度或半透明方式來拷貝。alpha通道表面可用來控制每一像素的透明度設置。alpha通道表面的深度有1、2、4、8位。1位深度alpha通道表面僅支持兩種透明設置,不透明(非透明)或不可見(全透明)。另一方面,8位alpha通道表面允許256種不同的透明度設置。Alpha調配是不被DirectDraw仿真的功能的一個例子。爲了使用alpha調配,因而就需要有支持它的顯示硬件或建立在應用程序之中的定製調配方案。
Z-緩衝區和3D設備表面用於3D應用程序中。這些類型的表面已被特別地加入到DirectDraw之中,以支持Direct3D。Z-緩衝區用於景象繪製時期,以跟蹤景象中離瀏覽者最近的對象,從而該對象可以在其他對象的前面出現。3D設備表面可以用來作爲Direct3D繪製目標的表面。本書並不包括Z-緩衝區或3D設備。

第三節 元件對象模型(COM)
1.Microsoft的COM規格
DirectDraw根據Microsoft的COM(Component Object Model)規格得以實現。COM設計成用來提供完全便攜的、安全的、可升級的軟件結構,COM是一個大的項目,但是它並不是本軟件討論的對象。我們討論COM只是爲了方便使用DirectDraw進行編程。
COM使用一個面向對象的模型,這要比像C++等語言所用的模型更嚴格。例如,COM對象經常通過成員函數進行存取,而且並不具備公共數據成員。COM對繼承性的支持與C++相比也是有限的。
2. 對象和接口的比較
COM在對象和接口之間具有很大的區別。COM對象提供實際函數性,而COM接口則提供存取函數性的方法。COM對象不能被直接存取,相反,所有的存取者通過接口來完成。這條規則是如此強有力的得到遵守,以致於我們不能給任何COM對象以名稱。我們只能給用來存取對象的接口名稱。因爲我們無法存取COM對象,所以這裏絕大多數時候是根據接口來講的。
一個COM對象能夠支持多個接口。這聽起來像個特例,但是它經常出現,因爲根據COM規格,一個COM接口一旦定義之後,就不能再被改變或增加。這樣做是爲保證舊程序在一個COM對象升級的時候不會被停止使用。這個初始接口始終存在,一個新的、替換的接口在提供存取對象的新的函數性的時候才被提供。
3. IUnknown接口
所有的COM接口都是從IUnknown接口中衍生出來的。“I”標誌經常用於命名COM接口(它代表Interface即界面)。DirectDraw接口總以“I”開頭。但是在文獻中經常看不到這個標誌。以後提到接口時也將省略“I”標誌。
  IUnknown接口將提供3個成員函數,所有COM接口因而繼承這些函數:
●AddRef()
●Release()
●QueryInterface()
AddRef()和Release()成員函數爲稱爲生命期封裝(lifetime encapsulation)的COM功能提供支持。生命期封裝是一個將每一個對象根據它自己的結構放置的協議。
生命期封裝通過引用值來實現。每一個對象擁有一個可以跟蹤對象的指針數,或者引用的內部值。當對象創建之後,該值爲1。如果附加的接口或接口的指針被創建,則該值遞增。與此類似,如果接口的指針被破壞,則該值遞減。當它的引用數到0的時候,該對象自行破壞。
AddRef()函數用來使對象的內部引用值遞增。絕大部分時間裏,該函數通過DirectDraw API被用戶調用。例如,當你使用DirectDrawaw API創建一個新的接口時,創建函數就自動調用AddRef()。
Release()函數用來給對象的內部引用值遞減。用戶應該在接口的指針將要超出範圍時或者要通過使用接口指針來結束時使用這個函數。AddRef()和Release()函數都返回一個值,表示對象新的引用值。
QueryInterface()函數允許COM對象就它們是否支持特定接口進行查詢。例如,升級的COM對象提供附加的接口,而非現有接口的修改版。QueryInterface()函數可以用來確定舊的接口,以決定新的接口是否被支持。如果被查詢的對象不支持有問題的接口,則更替接口的指針就返回。
4. GUID
爲了查詢一個對象是否支持使用QueryInterface()函數的指定接口,就有祕要識別有問題的接口。這通過接口的GUID(Globally Unique IDentifier)來實現。一個GUID是一個128位的值,也就是說,對於所有意圖和目的是唯一的。所有DirectDraw接口的GUIDs都包含在DirectX頭文件中。
上述對於COM的簡單介紹,就是爲有效使用DirectDraw API所需要的全部內容。以後當我們再討論DirectDraw API時,你會發現這些內容是有聯繫的。

第四節 DirectDraw接口函數
1.關於 DirectDraw API
衡量API的一個方法就是看它的大小。一個龐大複雜的API可能就是計劃不周的結果。另一方面,一個龐大的API有時就意味着每一種情況都有可能出現。一個小的API就是一個新的、缺乏功能的軟件包的證據。它也意味着,一個API只能做它所需要做的,而不能多做一點。
DirectDraw API是比較小的,因此本章中所討論的每一個函數不致於使本章看起來像一本參考手冊。DirectDraw提供很少的方便,也很少有限制。
DirectDraw由個COM對象構成,每個對象可以通過一個或多個接口存取。這些接口包括:
●DirectDraw
●DirectDraw2
●DirectDrawSurface
●DirectDrawSurface2
●DirectDrawSurface3
●DirectDrawPalette
●DirectDrawClipper
我們將討論每一個接口,並隨後討論它們的成員函數。但我們並不討論每個函數的細節,因爲我們並不是向您提供一份參考手冊。相反,我們將討論每個函數是幹什麼的,爲什麼這樣使用,以及你有可能如何去使用它。
當DirectX首次推出的時候(早先它被稱作Games SDK),DirectDraw核心函數性以DirectDraw接口表示。當DirectX2推出的時候,DirectDraw也已經被升級了。DirectDraw遵守COM規格而未被改變。新的函數性可能通過DirectDraw2接口存取。
特別要注意的是,DirectDraw2接口是DirectDraw接口的超級設置。DirectDraw2接口可提供DirectDraw接口的所有函數,另外還增加了一些新的函數。如果你正在使用DirectX或更高版高,那麼你可以隨意選用DirectDraw接口或DirectDraw2接口。但是,由於DirectDraw2接口較DirectDraw接口的功能更強,所以沒有必要使用DirectDraw接口。同樣,Microsoft並不主張使用這些無組織的、網絡可變的接口。因此,在本書以後的程序中我們只使用DirectDraw2接口。
DirectDraw和DirectDraw2接口提供的成員函數如下(按字母順序排列):
●Compact()
●CreateClipper()
●CreatePalette()
●CreateSurface()
●DuplicateSurface()
●EnumDisplayModes()
●EnumSurfaces()
●FlipToGDISurface()
●GetAvailableVidMem()
●GetCaps()
●GetDisplayMode()
●GetFourCCCodes()
●GetGDISurface()
●GetMonitorFrequency()
●GetScanline()
●GetVerticalBlankStatus()
●RestoreDisplayMode()
●SetCooperativeLevel()
●SetDisplayMode()
●WaitForVerticalBlank()
接下來我們討論DirectDraw接口函數。注意,在本章以後的內容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。只有在區分DirectDraw接口和DirectDraw2接口的函數時,才加以區別。
1. 接口創建函數
DirectDraw接口表示DirectDraw本身。該接口在被用於創建其他DirectDraw接口實例時,是一個主接口。DirectDraw接口提供三個這樣的接口實例創建函數:
●CreateClipper()
●CreatePalette()
●CreateSurface()
CreateClipper()函數用於創建DirectDrawClipper接口實例。並非所有的DirectDraw應用程序都用到剪裁器,所以該函數並不是在所有的程序中都出現。我們將很快討論DirectDrawClipper的細節。
CreatePalette()函數用於創建DirectDrawPalette接口實例。同DirectDrawClipper一樣,並非所有的DirectDraw應用程序都用到調色板。比如,應用程序使用16位顯示模式時,就不用調色板。但是,當應用程序使用8位顯示模式時,就必須創建至少一個DirectDrawPalette實例。
CreateSurface()函數用於創建DirectDrawSurface接口實例。任何一個DirectDraw應用程序都要用表面來生成圖像數據,因此經常要用到這一函數。
DirectDraw接口自己的實例是由DirectDrawCreate()函數創建的。DirectDrawCreate()是DirectDraw函數中少有的幾個常規函數之一,但並不是COM接口成員函數。
2. GetCaps()函數
DirectDraw接口允許準確確定軟硬件都支持的特徵。GetCaps()函數可以對兩個DDCAP結構實例進行初始化。一個結構表明哪些特徵由顯示硬件直接支持,另一個結構表明哪些特徵由軟件仿真支持。最好是用GetCaps()函數來決定你將用到的特徵是否被支持。
提示:DirectX瀏覽器
DirectX SKD是與DXVIEW程序同時推出的。DXVIEW說明了DirectX組件的功能,包括DirectDraw。大多數系統中,有兩個DirectDraw項目:主顯示驅動器和硬件仿真層。第一項說明了顯示硬件的功能。第二項說明了在缺乏硬件支持的情況下,DirectDraw將要仿真的一些特徵。在具有兩個以上的DirectDraw支持的顯示卡的計算機中,DXVIEW會顯示卡的功能。
3. SetCooperativeLevel()函數
SetCooperativeLevel()函數用於指定應用程序所要求的對顯示硬件的控制程度。比如,一個正常合作度意味着應用程序既改變不了當前顯示模式,也不能指定整個系統調色板的內容。而一個專有的合作度允許顯示模式切換,並能完全控制調色板。不管你決定使用哪種合作度,都必須調用SetCooperativeLevel()函數。
4. 顯示模式函數
DirectDraw接口提供4種顯示模式操作函數。它們是:
●EnumDisplayModes()
●GetDisplayMode()
●RestoreDisplayMode()
●SetDisplayMode()
EnumDisplayModes()函數可用於查詢DirectDraw使用何種顯示模式。通過設置EnumDisplayModes()函數默認值可以得到所有的顯示模式,而且可以通過顯示模式描述消除那些不感興趣的模式。進行顯示模式切換的過程中最好使用EnumDisplayModes()函數。現在市場上有各種各樣的顯示設備,每種顯示設備都有自己的特徵和侷限。除了默認的640×480×8窗口顯示模式,最好不要依靠任何給定的顯示模式的支持。
  GetDisplayMode()函數可以檢索到有關當前顯示模式的信息,並在DDSURFACEDESC結構實例中顯示當前顯示模式的寬度、高度、像素深度以及像素格式等信息。還有別的途徑可以檢索到同樣的信息(比如檢索主表面描述),因此該函數並不出現在所有的程序中。
  SetDisplayMode()函數用於激活所支持的顯示模式。SetDisplayMode()函數的DirectDraw2版本還允許設定顯示模式的刷新率。而DirectDraw接口版本的SetDisplayMode()函數只能進行顯示模式寬度、高度和像素深度的設置。任何一個要進行顯示模式切換的程序都要用到SetDisplayMode()函數。
RestoreDisplayMode()函數用於存儲調用SetDisplayMode()函數之前的顯示模式。SetDisplayMode()和RestoreDisplayMode()函數都要求優先使用SetCooperativeLevel()函數得到的專有合作存取。
5. 表面支持函數
除了CreateSurface()函數之外,DirectDraw接口還提供了以下向個表面相關函數:
●DuplicateSurface()
●EnumSurfaces()
●FlipToGDISurface()
●GetGDISurface()
●GetAvailableVidMem()
●Compact()
DuplicateSurface()函數用於考貝當前表面。該函數只複製表面接口,不復制內存。被複制的表面與源表面共享內存,因此改變內存的內容就同時改變了兩個表面的圖像。
EnumSurfaces()函數可用於迭代所有滿足指定標準的表面。如果沒有指定標準,那麼所有當前表面都被枚舉。
FlipToGDISurface()函數的作用是在終止頁面翻轉應用程序前確保主表面得以正確存儲。取消頁面翻轉時,有兩個表面交替顯示。這就是說,在終止應用程序之前有可能沒有保存最初的可見表面。這種情況下,Windows通過繪製一個不可見表面來恢復。利用FlipToGDISurface()函數就可以輕而易舉地避免發生這種情況。
GetGDISurface()函數可以向只被GDI認可的表面返回一個提針。GDI表面是Windows用於輸出的表面。在進行屏幕捕捉時,這個函數非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。
GetAvailableVidMem()函數用於檢索正在使用中的視頻存儲器(顯示RAM)的數量。這一函數由DirectDraw2接口提供,而不是由DirectDraw接口提供。該函數用於確定應用程序利用顯示RAM可創建表面的數量。
Compact()函數不是通過DirectX5實現的,但它可以爲視頻存儲器提供碎片整理技巧。在基於顯示RAM的表面被不斷創建或受到破壞的時候,可以釋放大量內存。
6. 監視器刷新函數
DirectDraw接口提供了4種適於計算機顯示設備或監視器的函數,但這些函數不適於顯示卡,它們是:
●GetMonitorFrequency()
●GetScanLine()
●GetVerticalBlankStatus()
●WaitForVerticalBlank()
這些函數尤其與監視器的刷新機制緊密機連。這在確保生成動畫時儘可能不產生閃爍和圖像撕裂現象時是至關重要的。但必須注意,並非所有的顯示卡/監視器組合都支持這些函數。
GetMonitorFrequency()函數用於檢索監視器當前的刷新率。刷新率通常用赫茲表示,縮寫爲Hz。例如,60Hz的刷新率表示屏幕每秒更新60次。
GetScanLine()函數用於向監視器返回當前正在被刷新的掃描行(水平像素行)。不是所有的顯示設備/監視器組合都支持該函數。如果這一功能得不到支持,該函數將返回DDERR-UNSUPPORTED。
對於高性能圖形應用程序來說,通常要求利用垂直刷新同步地更新屏幕。尤其是,當顯示器剛完成屏幕刷新時,最好能夠更新主表面。否則,屏幕的一部分顯示新的圖像數據,而另一部分仍顯示舊的圖像數據,這種現象就是所謂的圖像撕裂。DirectDraw默認利用垂直刷新同步更新屏幕。如果不是這樣還可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函數實現同步刷新。
7. GetFourCCCodes()函數
DirectDraw接口提供的最後一個函數是GetFourCCCodes()函數。該函數用於返回顯示卡所支持的FourCC代碼。FourCC代碼用於描述非RGB或YUV表面。我們不在此討論YOV表面,它們已超出本書的範圍。
第五節 DirectDrawSurface接口函數
同DirectDraw接口一樣,DirectDrawSurface接口也遵守COM規格.最初,表面支持是由DirectDrawSurface接口提供的。DirectX2介紹了DirectDrawSurface2接口的新的函數性,DirectX5介紹了DirectDrawSurface3接口。
儘管本軟件中討論的是DirectDraw2接口,而不是DirectDraw接口,但我們仍忠於最初的DirectDrawSurface接口,因爲DirectDrawSurface2和DirectDrawSurface3接口新增的函數並不十分重要。在以後的內容裏,我們將用DirectDrawSurface接口表示這3種接口,除非特別註明。
DirectDrawSurface是最大的DirectDraw接口,它允許表面內容的拷貝、清除以及被調用程序直接存取。DirectDrawSurawSurface接口總共提供36個成員函數,按字母順序排列如下:
●AddAttachedSurface()
●AddOverlayDirtyRect()
●Blt()
●BltBatch()
●BltFast()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●EnumOverlayZOrders()
●Flip()
●GetAttachedSurface()
●GetBltstatus()
●GetCaps()
●GetClipper()
●GetColorKey()
●GetDC()
●GetDDInterface()
●GetFlipStatus()
●GetOverlayPosition()
●GetPalette()
●GetPixelFormat()
●GetSurfaceDesc()
●IsLost()
●Lock()
●PageLock()
●PageUnlock()
●ReleaseDC()
●Restore()
●SetClipper()
●SetColorKey()
●SetOverlayPosition()
●SetPalette()
●SetSurfaceDesc()
●Unlock()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
1. 表面描述函數
我們首先討論的個可用於檢索表面自身信息的函數,它們是:
●GetCaps()
●GetPixelFormat()
●GetSurfaceDesc()
●SetSurfaceDesc()
同DirectDraw接口提供的GetCaps()函數一樣,DirectDrawSurface接口提供的GetCaps()函數用於輸出表徵哪些特徵可被表面支持的數據。該信息包括:表面是主表面還是離屏表面;表面使用的存儲器定位於顯示RAM還是系統RAM。
GetPixelFormat()函數在用於高彩和真彩表面時是非常重要的,這是由於像素格式因顯示卡的不同而不同。該函數返回表徵碼,這些表徵碼錶明每一種顏色部件是如何存儲的。
GetSurfaceDesc()函數返回一個表面描述。該信息包括表面的寬度、高度和深度。表面像素格式(同樣被GetPixelFormat()函數檢索)也包含在其中。
SetSurfaceDesc()函數(對於DirectX5)來講是新增的,只由DirectDrawSurface3接口提供)允許設定某些表面屬性。該函數可用於指定表面使用的內存。這一點在設計定製表面存儲器管理器策略時非常有用。
2。 表面Blt函數
DirectDrawSurface接口提供3個支持blt操作的函數:
●Blt()
●BltBatch()
●BltFast()
Blt()函數是一個主要函數。Blt()函數能夠進行常規的blting(無特殊影響的簡單的表面到表面的blt),同時支持延伸、旋轉、鏡像和顏色填充的操作。當用於同剪裁器關聯的表面時,Blt()可進行剪裁blt操作。
BltBatch()函數不是在DirectX3下實現的(你可以調用該函數,但什麼也不會發生)。執行BltBatch()函數時,如果可能,它可同時進行多blt操作。
BltFast()函數是Blt()函數的優化版本。BltFast()函數的效率提高了,但性能卻下降了。BltFast()函數不能進行一些特殊的blt操作,而Blt()函數可以。而且,BltFast()函數不能用於剪裁。但是BltFast()函數支持源和目標色彩鍵碼blt的操作。在遵循定製剪裁例程的情況下,BltFast()函數可進行DirectDraw能夠提供的最快捷、靈活的blt操作。在下章中我們將執行一個定製剪裁例程。
以上3個blt函數均將源表面和目標表面作爲變量。其他的數據,例如blt在目標表面上的理想定位,是通過指定理想blt操作的確切屬性來提供的。一旦可能,這3個函數將進行硬件加速blt。
3. Flip()函數
Flip()函數用於頁面翻轉操作。調用Flip()函數可隱藏屏幕上先前可見的表面,並使一個後備緩衝區顯現。只有被明確地創建爲翻轉表面的表面,才響應該函數的調用。
必須牢記,真正的翻轉操作不可能總是成功。頁面翻轉要求有足夠的顯示RAM容納兩整屏有效數據。如果滿足不了這一要求,系統RAM中將創建一個後備緩衝區。這時調用Flip()函數進行的是blt操作而不是頁面翻轉。基於系統RAM的後備緩衝區中的內容被拷貝到主表面上。這樣會嚴重影響性能,但是,在真正的頁面翻轉中如果沒有足夠的顯示RAM,又不退出程序,也只能如此了。如果你的應用程序要求最佳性能,就得設法避免激活不能進行真正頁面翻轉的顯示模式。
4. 表面狀態函數
下面討論兩個能檢索有關操作和翻轉操作信息的函數,它們是:
●GetBltStatus()
●GetFlipStatus()
GetBltStatus()函數用於確定當前是否進行blt操作。這一點很重要,因爲正被blt的表面不能進行其他操作。該函數表明,給定的表面是否正是一個進行blt操作的源表面或目標表面。
同樣地,GetBltStatus()函數表明是否正在進行翻轉操作。即使DirectDraw通過blt操作仿真頁面翻轉,該函數而不GetBltStatus()也必須用於由Flip()函數初始化的監視器頁面翻轉。
5. 色彩鍵碼函數
DirectDrawSurface接口提供了以下兩個函數,來設置和檢查表面色彩鍵碼或色彩鍵碼的範圍,它們是:
●GetColorKey()
●SetColoKey()
默認狀態下,表面沒有色彩鍵碼。一個色彩鍵碼只對應一種顏色,但某些硬件支持色彩鍵碼範圍。色彩鍵碼和色彩鍵碼範圍是DDCOLORKEY結構定義的。GetColorKey()和SetColoKey()函數都將該結構的指針作爲變量。在要求表面的一部分透明時或需要進行目標色彩鍵碼操作時,可以使用這兩個函數。
6. Lock和Unlock()函數
DirectDraw的一個主要特點,就是能夠提供對圖像數據的直接存取。直接存取可以提供最佳性能和更好的靈活性,因爲沒有中間API影響運行速度,並且開發人員呆任意使用圖像數據。對錶面存儲器的中間存取通過以下出衆個函數實現:
●Unlock()
●Lock()
Lock()函數向組成表面的存儲器返回一個指針,不管表面存儲器位於顯示RAM還是系統RAM。存儲器一般按線性風格排列,以便能簡單地進行圖像 數據存取。Unolock()函數在完成表面存儲器的存取之後指定給DirectDraw。
對圖像數據的直接存取必須付出代價。爲了支持這種存取方式,DirectDraw在表面鎖定的時候必須關閉基本的Windows機構。在Windows95狀態下,如果忘記解鎖表面,必定會損壞機器。
因此,表面鎖定的時間應儘量縮短。測試前應仔細檢查Lock()和Unlock()函數之間的程序調用。因爲這一程序無法用傳統的調試程序進行調試。
鎖定表面不能被blt和翻轉,因此試圖保持表面處於鎖定狀態沒有任何有益之處。而且,一旦表面解鎖,由Lock()函數檢索的指針就失效了。
表面鎖定後不能再次被鎖定。在表面鎖定時也就無法調用Lock()函數。
7. GetDC()ReleaseDC()函數
對表面的直接存取佔用很大內存,有時候把表面作爲一個常規的Windows設備會更好。在此,DirectDrawSurface接口提供以下兩個函數:
●GetDC()
●ReleaseDC()
GetDC()函數提供了一個DC(設備環境),可以用常規的Win32函數寫到表面上。例如,DC可以用Win32的TextOut()函數在表面上繪製文本。用完DC後必須馬上調用ReleaseDC()函數。
同Lock()和Unlock()函數使用一樣,在調用完GetDC()函數後必須馬上調用ReleaseDC()函數。這是因爲GetDC()函數內部調用Lock函數,而ReleaseDC()函數內部調用Unlock()函數。
8. PageLock()和PageUnlock()函數
接下來,我們討論另外兩個與Lock()函數和Unlock()函數看上去非常相像的函數:
●PageLock()
●PageUnlock()
儘管這兩個函數看上去很像Lock()和Unlock()函數,但它們卻有完全不同的作用。PageLock()和PageUnlock()函數用於控制Windows對基於系統RAM的表面的處理方式。這兩個函數由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。
當Windows認爲當前正在運行的其他應用程序或進程更適於使用內存時,Windows會向硬盤釋放部分內存。這種緩衝對於整個系統內存都起作用,因此駐留在系統內存中的DirectDraw表面有可能被存到硬盤上。如果要用到這樣的表面,Windows需要花費一定的時間從硬盤上讀取表面數據。
PageLock()函數提示Windows哪些給定的表面不應該釋放到硬盤上。這樣,在使用表面時就不用耗費時間進行硬盤存取了。相反地,PageUnlock()函數用於告知Windows哪些表面內存可被釋放。
過程調用PageLock()函數會減少緩衝內存的總量,從而導致Windows的速度大大降低。至於這種情況何時發生,取決於頁面鎖定系統內存量及機器提供的系統內存量。
PageLock()和PageUnlock()函數主要是由DirectDraw提供而非DirectDraw應用程序。舉個例子來說,DirectDraw自動使用PageLock()函數,以確保運行blt操作時,基於系統RAM的表面不被釋放到硬盤。
PageLock()函數可以被同一個表面多次調用。DirectDraw用參考計數法記錄PageLock()函數被調用的次數,因此多次調用PageUnlock()函數就必須避免多次調用PageLock()函數。
PageLock()和PageUnlock()函數對於駐留在顯示RAM中的表面不起作用。
9. IsLost()的Restore()函數
現在討論兩個與使用駐留在顯示RAM中的表面有關的函數:
●IsLost()
●Restore()
讓我們來看一看下面這種情況。應用程序正在運行時,儘量把表面分配到顯示RAM中,剩下的創建到系統RAM中。應用程序在運行一段時間之後,用戶執行或切換到另一個應用程序。該應用程序是任意的,可以是一個常規的Windows程序,如Windows開發程序或記事本。它也可以是另外的DirectDraw應用程序,該程序也試圖將儘可能多地分配顯示RAM。如果DirectDraw不接受顯示RAM,那麼新的應用程序就很可能根本運行不了。相反的情況就意味着,應用程序不允許分配到任何顯示RAM中。
因此,DirectDraw可以隨意將任何一個或者所有的基於顯示RAM的表面從非激活應用程序中移走。這種情況就是所謂的表面丟失。從技術上講,程序仍具有表面,但它們不再同任何內存相關。要使用丟失的表面會導致DDERR-SURFACELOST錯誤。IsLost()函數可用於確定一個表面是否丟失了內存。
表面內存丟失後可通過Restore()函數恢復,但只能在應用程序被重新激活之後纔可恢復。這會導致應用程序無法將處於最小化狀態的所有表面復原。
Restore()函數可恢復附屬於表面的任一內存,但並不恢復內存的內容。表面被複原後,應用程序就可以恢復表面內容了。
注意,這種用法不適合利用系統RAM創建的表面。如果需要用到基於系統RAM的表面所佔內存,那麼Windows會立即將這些表面釋放到硬盤上。Windows自動地處理存儲和恢復,包括恢復表面的內容。
10. GetDDInterface()函數
GetDDInterface()函數可檢索用於創建給定表面的DirectDraw接口的指針。由於程序中大多數情況下只有一個DirectDraw接口實例,所以GetDDInterface()函數並不常用。但是一個應用程序中有可能使用多個DirectDraw接口,在這種情況下,GetDDInterface()函數會起到重要作用。
11. 表面連接函數
DirectDrawSurface接口提供以下4個函數,用來維持表面間的連接:
●AddAttachedSurface()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●GetAttachedSurface()
DirectDraw支持大量的用於表面間的連接情況。最常見的情況就是頁面翻轉。進行頁面翻轉時,兩個或兩個以上的表面連接成環狀,每次調用Flip()函數時,都會使連成環狀的表面中的下一個表面顯現。
表面連接函數用於創建、檢查或消除表面間的連接,但這些函數並非必不可少的。DirectDraw往往是自動創建連接表面。比如,當創建一個主翻轉表面時,可以指定用於連接表面的後備緩衝區的數量。DirectDraw就會創建這些表面,並將它們連接起來。
12. 重疊函數
DirectDrawSurface接口用於支持重疊的函數如下:
●AddOverlayDirtyRect()
●EnumOverlayZOrders()
●GetOverlayPosition()
●SetOverlayPosition()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
GetOverlayPosition()和SetOverlayPosition()函數用於控制重疊的位置。UpdateOverlay()函數用於更新大量的重疊設置,包括重疊是否可見,以及重疊是以色彩鍵碼還是用alpha混合到背景表面上。
UpdateOverlayDisplay()函數用於更新顯示設置。該函數用於更新整個重疊顯示,或者只更新由AddOverlayDirtyRect()函數指定的矩形重疊部分。EnumOverlayZOrders()函數可根據重疊的Z值(Z值控制哪一個重疊位於最上面)重複重疊。重疊可按從前到後或從後到前的順序枚舉。
13. 剪裁器函數
DirectDraw支持的剪裁是將DirectDrawClipper接口(該接口我們尚未討論)的一個實例連接到表面。一旦連接完畢,剪裁器對象就會有規律地blt到表面。剪裁器/表面的連接由以下兩個DirectDrawSurface函數控制:
●GetClipper()
●SetClipper()
SetClipper()函數用來將剪裁器對象連接到表面。GetClipper()函數用於向前一個連接的剪裁器對象返回一個指針。SetClipper()函數還可用於解除表面同剪裁器的連接,具體的做法是通過指定NULL來替代DirctDrawClipper接口指針。
14。 調色板函數
像剪裁器一樣,調色板也可連接到表面。DirctDrawSurface接口爲此提供以下兩個函數:
●GetPalette()
●SetPalette()
SetPalette()函數用來將DirctDrawPalette接口(該接口我們接下來就要討論)的一個實例連接到表面。GetPalette()函數用於檢索前一個連接調色板的指針。
調色板可被連接到任何表面,但只有連接到主表面時,調色板才起作用。當與主表面連接時,調色板決定顯示硬件調色板的設置。
第六節 DirectDrawPlette接口函數
DirctDraw提供DirctDrawPalette接口用於調色板顯示模式和表面。儘管Windows支持幾種低於8位像素深度的顯示模式,但DirctDraw所支持的唯一的調色板顯示模式是8位模式。
DirctDrawPalette接口實例由DirctDraw CreatePalette()函數創建。CreatePalette()函數用大量的標誌來定義調色板屬性。
DirctDrawPalette接口只提供以下3個函數:
●GetCaps()
●GetEntries()
●SetEntries()
GetCaps()函數用於檢索有關調色板的信息,包括調色板項目數量,調色板是否支持同步垂直刷新,以及在8位調色板狀態下是否所有的256個調色板項目都能被設定。 
SetEntries()函數允許在程序中設置調色板的色彩值。該數據從文件中讀取。而這些項目在運行過程中可被計算和設定。GetEntries()函數用於檢索先前設定的調色板項目。
DirctDrawPalette()接口實例可利用DirctDrawSurface SetPalette()函數連接到表面。將不同調色板連接到主表面或利用SetEntries()函數改變調色板項目都可激活調色板。

第七節 DirectDrawClipper接口函數
DirctDrawClipper接口支持剪裁。將剪裁器對象連接到表面並在blt操作中將其當作目標表面就可以進行剪裁。
directDrawClipper實例由DirectDraw CreateClipper()函數創建。DirectDrawClipper接口支持以下5個函數:
●SetHWnd()
●GetHWnd()
●IsClipListChanged()
●SetClipList()
●GetClipList()
剪裁器對象一般用於出現在窗口中的DirctDraw應用程序必需的剪裁。要求剪裁器必須確保在blt操作過程中考慮到桌面上其他的窗口。比如,當應用程序的全部或一部分被另一個窗口遮蔽,剪裁就必須確保被遮蔽的窗口不被DirctDraw應用程序破壞。
桌面剪裁由SetWnd()函數完成。SetHWnd()函數將剪裁器對象連接到一個窗口句柄。這樣就初始化了Windows和剪裁器對象之間的通訊。當桌面上的任何一個窗口發生變化時,剪裁器對象就會得到通知,並作出反應。GetHWnd()函數用於決定剪裁器同哪一個窗口句柄連接。IsClipListChanged()函數用於決定內部剪裁清單是否因桌面的改變而被更新。
SetClipList()和GetClipList()函數爲DirectDrawClipper接口提供便利的定製使用。SetClipList()函數用於定義一些矩形區域,這些矩形區域用來定義blt操作的合法區域。GetClipList()函數用於檢索剪裁器的內部剪裁數據。
一旦被連接到表面,Blt(),BltBatch()以及UpdateOverlay()函數所進行的blt操作將根據DirctDraw Cliper接口中包含的數據被自動地剪裁。注意,Blt Fast()函數在此被忽略。BltFast()函數不支持剪裁。

第八節 附加DirectDraw接口
DirctDraw還提供了另外個我們沒有討論的接口,它們是:
●DDVideoPortContainer
●DirectDrawColorControl
●DirectDrawVideoport
這些接口由DirectX5介紹,它們提供低水平視頻端口控制。這些接口還向DirctDraw表面提供流活動視頻的方法。儘管利用這些接口可以爲DirctDraw應用程序增加視頻支持,但除非高水平視頻APIs不能滿足需要,否則最好不用這一方法。

第九節 DirectDraw結構
我們已討論過DirctDraw接口及其成員函數了,接下來再看看DirctDraw定義的結構。DirctDraw總共定義了8全結構:
●DDBLTFX
●DDCAPS
●DDOVERLAYFX
●DDPIXELFORMAT
●DDSURFACEDESC
●DDSCAPS
●DDBLTBATCH
●DDCOLORKEY
我們已經見過其中的一些結構了,比如在討論DirctDrawSurface SetColorKey()函數時我們就接觸過DDCOLORKEY結構。在此,我們不詳細討論每一個結構的細節,但必須指出,DirctDraw quirk被忘記時會導致失敗。
以上所列結構中的前5個有一個稱作dwSize的字段。該字段用於存儲結構的大小,設定該字段的工作由你來做。另外,該字段如果沒有被正確設定的話,那麼任何一個將這5個結構作爲變量的DirctDraw函數都會失效。
以DDSURFACEDESC結構爲例,使用結構的代碼如下:
DDSURFACEDESC surfdesc;
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
首先聲明結構,然後用sizeof()關鍵字設定dwSize字段。最後結構傳遞給DirctDrawSurface GetSurfaceDesc()函數。忘記設定dwSize字段將導致這段代碼失效。
到底爲什麼DirctDraw堅持要求給出它所定義的結構的大小?原因在於這5個包含dwSize字段的結構將來有可能會改變。DirctDraw會檢查結構的大小,以便確定正在使用的版本。DirctDraw堅持要求給出一個有效的大小值,是爲了讓開發者提供有效的結構大小。這樣做是有好處的,因爲DirctDraw的新版本可以正確運用舊版本的DirctDraw程序。
在使用結構之前,最好將結構初始化爲零。這樣,前面的代碼就變成:
DDSURFACEDESC surfdesc;
ZeroMemory (&surfdesc,sizeof(surfdesc));
surfdesc.dwSize=sizeof(surfdesc);
surf->GetSurfaceDesc(&surfdesc);
ZeroMemory()函數是一個Win32函數,它將作爲第一個參數的存儲器設定爲零.ZeroMemory()函數的第二個參數表明有多少存儲器應被初始化。這一做法的好處是,通過GetSurfaceDesc()函數調用可以知道結構的哪些字段被更新了。如果沒有對結構進行初始化,就有可能將結構字段中不可預測的值當作DirectDraw的設定值。

第十節 窗口應用程序
DirctDraw應用程序主要有兩種型式:窗口的和全屏的。窗口DirctDraw應用程序看起來就像一個常規的Windows程序。我們很快將討論到全屏應用程序。
窗口應用程序包括窗口邊界、標題框以及菜單,這些都是傳統的Windows應用程序中常見的部分。由於窗口應用程序同其他窗口一起出現在桌面上,因此它們被迫使用Windows當前所使用的分辨率和比特深度。
窗口程序有一個主表面,但只在進行真實頁面翻轉時才顯現。而且,主表面並不代表窗口的客戶區域(該區域在窗口邊界內)。主表面還代表整個桌面。這就是說,你的程序必須追蹤窗口的位置和大小,以便在窗口內正確顯示可見的輸出。換言之,利用窗口化的應用程序中可以在整個桌面上進行繪圖。
如果不允許頁面翻轉,那麼圖像就必須從離屏緩衝區blt到主表面上。這就增加了圖像撕裂的可能性,因爲blt比頁面翻轉速度慢。爲了避免圖像撕裂,blt操作可以與監視的刷新率保持同步。
如果與窗口客戶區域同樣大小的離屏緩衝區被創建到顯示RAM中,窗口應用程序就可以很好地運行。這樣,窗口的內容可利用離屏表面合成。然後離屏表面可以通過硬件加速很快地到主表面上。
由於顯示存儲器的缺乏而不得不將離屏緩衝區創建到系統RAM中時,會嚴重影響性能。不幸的是,這種情況常常發生,尤其是在只有2MB顯示卡的時候,這是因爲人們總希望爲自己Windows的桌面設置高分辨的顯示模式。例如,採用1024×768×16顯示模式的主表面自己就要佔用2MB的RAM。在一個2MB顯示卡上,幾乎沒有顯示RAM留給離屏表面。
窗口應用程序的另一個問題是剪裁。一個性能良好的應用程序必須有一個連接到主表面的剪裁對象。這是有損性能的,原因在於爲了檢查剪裁器的剪裁清單內容,blt操作只能在一個小的矩形部分內進行。而且,不能使用優化的BltFast()函數。Bltting必須用較慢的,(而且更笨重的)Blt()函數。
最後要講的是,窗口應用程序不允許全調色板控制。由於Windows保留了20個調色板項,所以在256種顏色只有236種顏色可被設定。被Windows保留的顏色只用系統調色板的前10個項和後10個項。因此在給圖像上色時,只能使用中間236個調色板項。

第十一節全屏應用程序
包含對顯示設備進行專有存取的DirctDraw應用程序就是全屏應用程序。這種應用程序可以任意選取顯示卡所支持的顯示模式,並享有全調色板控制。另外,全屏應用程序還可進行頁面翻轉。因此同窗口應用程序相比,全屏應用程序速度更快,靈活性更好。
典型的全屏應用程序首先檢查所支持的顯示模式,並激活其中一個。然後創建具有一個或更多後備緩衝區的可翻轉主表面,剩下的顯示RAM用於創建離屏表面。當顯示RAM耗盡時,就啓用系統RAM。屏幕被在後備緩衝區中合成的第一個場景更新,然後進行頁面翻轉。即使主表面佔用了所有的可用的顯示RAM,全屏應用程序還可輸出執行其窗口化的副本,這是因爲全屏應用程序可進行真實頁面翻轉。
由於全屏應用程序不一定非得使用Windows的當前顯示模式,所以顯示RAM的可用與否不存在多少問題。如果只檢測到2MB的顯示RAM,就可使用低分辨率顯示模式來保留內存。如果檢測到4MB的顯示RAM,應用程序就可使用要求的顯示模式並仍有保持良好性能。
全調色板控制也是個有利因素。這樣可以使用所有256個調色板項而無需根據Windows保留的20中顏色重新分配位圖。

第十二節混合應用程序
混合應用程序既可以在全屏方式下運行也可在窗口方式下運行。混合應用程序內部非常複雜,但卻可以提供良好的性能。用戶可在窗口方式下運行,如果太慢,則可切換到全屏方式下運行。
編寫混合應用程序最好的方法是編寫一個定製庫,該庫包括那些與應用程序使用全屏方式還是窗口方式無關的函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章