DirectX中文手冊


 

第一章 DirectX基礎(初級篇)

第一節  什么是DirectX

一、什么是DirectX ?

二、DirectX的組成部分

三、關於DirectDraw

四、為什么要使用DirectDraw?

五、DirectX5.0的新特性?

六、什么是部件對象模型(COM)

七、自我檢測

第二節  如何安裝和使用DirectX

一、編譯庫和運行庫

二、安裝 VC++ 5.0

三、安裝 DirectX5.0 的 SDK

四、DirectX 5.0 的文件說明

五、卸載 DirectX

第三節  一個DirectDraw入門程序

一、一個小測驗

二、牛刀小試

三、分析代碼

1)程序結構

2)定義和創建DirectDraw對象

3)設置控制級和顯示模式

4)創建主頁面

5)輸出文字

6)釋放對象

7)主窗口類型

四、小結

第四節DirectDraw圖形編程基礎知識

一、像素和分辨率

二、RGB色彩

三、設備無關位圖(DIB)

四、位深度(Bit depth)

五、抖動處理(Dithering)

六、調色板(Palette)

七、GDI與DirectDraw

八、位塊傳送(Blit)

九、翻頁(Page flipping)

十、矩形(Rectangle)

十一、精靈動畫(Sprite animation)

十二、關鍵色(Color Key)

十三、補丁(Patching)

十四、范圍檢查與碰撞檢測

第二章DirectDraw核心(高級篇)

第一節、      DirectDraw架構

一、DirectDraw結構概覽

二、DirectDraw對象類型

三、硬件抽象層(HAL)

四、軟件仿真層(HEL)

五、系統集成

    第二節、控制級

第三節 顯示模式

一、關於顯示模式

二、測定支持的顯示模式

三、設置顯示模式

四、恢復顯示模式

五、Mode X和Mode 13顯示模式

六、對高分辨率和真彩色的支持

第四節  DirectDraw對象

一、什么是DirectDraw對象?

二、IDirectDraw2接口的新特性?

三、單進程的多DirectDraw對象

四、使用CoCreateInstance創建DirectDraw對象

第五節  頁面

一、頁面的基本概念

    1)什么是頁面

    2)頁面接口

    3)寬度和寬距

    4)關鍵色

    5)像素格式

二、創建頁面

    1)創建主頁面

    2)創建離屏頁面

    3)創建復雜頁面和換頁鏈

    4)創建超寬頁面

三、換頁

四、頁面丟失

五、釋放頁面

六、更新頁面屬性

七、直接訪問幀緩存 

八、使用非本地視頻RAM頁面

九、色彩和格式轉換

十、覆蓋頁面

    1)覆蓋頁面概覽

    2)DDCAPS的重要成員和標志

    3)源和目標矩形

    4)邊界和大小限制

    5)最小和最大縮放系數

    6)覆蓋頁面關鍵色

    7)覆蓋頁面的定位

    8)創建覆蓋頁面

    9)覆蓋頁面的Z軸次序

    10)覆蓋頁面的換頁

十一、Blit到多窗口 

第六節  調色板

一、什么是調色板?

二、調色板的種類

三、對非主頁面設置調色板

四、共享調色板

五、調色板動畫

第七節  裁減器

一、什么是“裁減器(Clipper)”對象

二、裁減清單(Clip list)

三、共享DirectDrawClipper對象

四、獨立的DirectDrawClipper對象

五、用CoCreateInstance創建DirectDrawClipper對象

六、對系統鼠標使用裁減器

七、對多窗口使用Clipper

第八節  多顯示器系統

一、在多顯示器系統中列舉顯示設備

二、焦點窗口和設備窗口

三、設置焦點窗口和設備窗口

四、缺省設備窗口

五、多顯示器系統中的顯示設備與加速特性

第九節、高級DirectDraw主題

一、對Mode 13的支持

    1)關於Mode 13

    2)設置Mode 13

    3)Mode 13與頁面特性

    4)使用Mode 13模式

二、從DMA中獲益

    1)關於DMA設備支持

    2)對DMA支持的檢測

    3)典型的DMA方案

    4)利用DMA

三、在窗口模式下使用調色板

    1)窗口模式的調色板入口類型

    2)在窗口模式下創建調色板

    3)在窗口模式下設置調色板入口

四、獲得換頁和Blit操作的狀態

五、使用Blit進行單色填充

六、測定顯示硬件的能力

七、在視頻RAM中儲存位圖

八、Triple Buffering(三緩沖)

九、DirectDraw應用程序和窗口風格

十、將真彩色匹配到幀緩沖區的色彩空間

 

 

第一章 DirectX基礎(初級篇)

第一節什么是DirectX

一、什么是DirectX

微軟的DirectX軟件開發工具包(SDK)提供了一套優秀的應用程序編程接口(APIs),這個編程接口可以提供給你開發高質量、實時的應用程序所需要的各種資源。DirectX技術的出現將極大的有助於發展下一代多媒體應用程序和電腦游戲。

總的說來,使用DirectX的主要有兩個好處:

  • 為軟件開發者提供硬件無關性;
  • 為硬件開發提供策略。

    1、為軟件開發者提供硬件無關性

微軟開發DirectX,其最主要的目的之一是促進在Windows操作系統上的游戲和多媒體應用程序的發展。在DirectX出現以前,主要的游戲開發平台是MS-DOS,游戲開發者們為了使他們的程序能夠適應各種各樣的硬件設備而絞盡腦汁。自從有了DirectX,游戲開發者們便可以獲益於Windows平台的設備無關性,而又不失去直接訪問硬件的特性。DirectX主要的目的就是提供象MS-DOS一樣簡潔的訪問硬件的能力,來實現並且提高基於MS-DOS平台應用軟件的運行效果,並且為個人電腦硬件的革新掃除障礙。

另一方面,微軟公司開發DirectX是為了在當前或今后的計算機操作系統上提供給基於Windows平台的應用程序以高表現力、實時的訪問硬件的能力。DirectX在硬件設備和應用程序之間提供了一套完整一致的接口,以減小在安裝和配置時的復雜程度,並且可以最大限度的利用硬件的優秀特性。通過使用DirectX所提供的接口,軟件開發者可以盡情的利用硬件所可能帶來的高性能,而不用煩惱於那些復雜而又多變的硬件執行細節。

一個高表現力的基於Windows平台的游戲將得益於以下幾種技術:

  • 專為提高圖形運算及快速反應能力而設計的加速卡(Accelerator cards)
  • 即插即用以及其它Windows軟硬件
  • 內建於Windows的通信服務, 包括DirectPlay

    2、為硬件開發提供策略

DirectX的另外一個重要的目的是給硬件廠商提供開發策略,他們可以從高性能程序的開發者和獨立的硬件供應商(independent hardware vendors IHVs)那里得到反饋。所以,在DirectX 程序員參考書中有時可能會提供那些還不存在的硬件加速設備的技術細節。在很多時候,軟件可以模擬這些特性,在另外一些情況下,軟件根據硬件的指標判斷出其特性,並且可以忽略那些硬件並不支持的性能。

已經和將要實現的顯示設備的特性包括:

  • 覆蓋(Overlays),由於它的被支持,在圖形設備接口中,窗口中的換頁(page flipping)將成為可用。換頁是用來在整個屏幕上顯示畫面的雙緩沖(double-buffer)方案。
  • 精靈引擎(Sprite engines),使精靈(不規則圖形)覆蓋更容易。
  • 插補延展(Stretching with interpolation),它可以更有效的保存顯示內存,因為它可以使小幅畫面延展到整個屏幕。
  • Alpha 融合(Alpha blending),它可以在硬件像素層(hardware-pixel level)上混合顏色。
  • 帶有透視修正(perspective-correct)貼圖的3D加速器,它允許你在3D表面上貼圖。比如,你可以在用3D軟件制作的城堡的走廊里貼上磚塊的位圖來顯示出透視效果。
  • 為3D圖象進行的位操作將 Z 方向考慮在內。
  • 標准2兆顯示內存,這在3D游戲中是最小需求。
  • 壓縮標准,這將允許你在顯存中儲存更多的信息。這個標准不論是在軟件還是硬件中執行都會相當快。它將被用在貼圖中並且包含透明壓縮。

將要被包括的聲音設備的新特性包括:

  • 硬件及其外設可以提供空間環繞立體聲效果。
  • 聲卡  上內置音頻內存。
  • 音頻-視頻一體卡可共享其上的內存。

另外,視頻回放將得益於今后的與DirectX相兼容硬件加速設備。今后的DirectX版本中的一個特性是將支持硬件加速YUV視頻解碼。

 

二、DirectX的組件

DirectX SDK為基於Windows平台的應用程序提供了以下幾個組件。

  • DirectDraw :通過直接訪問顯示硬件來提供高級的圖象處理能力。
  • DirectSound :它提供了軟硬件的低延遲聲音混頻(low_latency sound mixing)和回放(Playback),硬件加速,以及直接訪問音頻設備的能力。
  • DirectPlay :它明確的提供了通用環境連接能力(generalized communication capabilities),來簡化你應用程序之間的通訊服務。
  • Direct3D :它為主流的桌上型計算機和Internet用戶提供實時的、交互的3D技術。
  • DirectInput :它簡化你的應用程序訪問鼠標、鍵盤和操縱桿設備的能力。
  • DirectSetup :一套簡單的API向你提供安裝DirectX部件的功能。
  • AutoPlay :它也是Win95操作系統的一個特性,當你在光驅內放上光盤,指定的應用程序會自動執行。

 

三、關於DirectDraw

DirectDraw是DirectX SDK大家族中的一員,也是其中最主要的一個部件。DirectDraw允許程序員直接的操作顯存、硬件位圖映射以及硬件覆蓋和換頁技術。它在提供這些功能的同時,也使其與現在的基於Microsoft Windows的應用程序和設備驅動程序相兼容。

DirectDraw是一個軟件接口,它在提供直接訪問顯示設備的同時,與Windows圖形設備接口(GDI)相兼容。DirectDraw不是一個高層的圖形程序編程接口,它為游戲和Windows子系統軟件(例如:3D圖形包和數字視頻編碼)提供了一種與設備無關的途徑,以獲得訪問特定的顯示設備的某些高級特性的能力。

DirectDraw適用於種類眾多的的顯示設備,從簡單的SVGA顯示器到提供裁剪、縮放、和支持非RGB顏色格式的高級硬件實現設備。設計這樣的接口是為了讓你的應用程序能夠列舉低層硬件的能力,並且對那些支持的硬件加速特性加以利用。那些在硬件設備中不能實現的特性,DirectX將仿真出來。

DirectDraw提供了以下幾個優點,這些好處在以前只有那些專為特定顯示設備所寫的軟件才能利用。

  • 支持雙緩沖和換頁圖形
  • 訪問、控制顯示卡的位圖映射
  • 支持3D z-buffers (z緩存)
  • 支持z方向(z-ordering)硬件輔助覆蓋。
  • 訪問圖形縮放硬件
  • 仿真訪問標准的和增強的顯示設備內存空間

DirectDraw的任務是用與設備無關的途徑來提供依賴於設備的訪問顯示內存的方法。本質上,DirectDraw管理顯示內存。你的應用程序只需要懂得那些一般的關於硬件與設備有關的知識,比如RGB和YUV色彩格式和兩條光柵線之間的pitch(寬距) 。在需要利用位轉換或操作調色板寄存器時,你不需要為調用過程中的細節而煩惱。使用DirectDraw,你可以方便的操作顯示內存,充分的利用不同類型的顯示設備的位轉換和顏色壓縮能力,而不需要依賴於某一個特定的硬件。

DirectDraw給運行於Windows 95和Windows NT 4.0或更高版本的計算機提供了一個高性能的的游戲圖象引擎。

 

四、為什么要使用DirectDraw

無庸置疑,人們之所以利用DirectDraw來開發各種游戲以及多媒體應用軟件,是因為DirectDraw可以為他們的應用程序帶來許多強大的功能提升。

DirectDraw可以充分評估視頻硬件的能力,只要可能,它就會對其某一特性加以利用。例如,如果你的顯卡支持硬件Blit,DirectDraw就會將位圖映射這一操作分派給顯卡來完成,極大的提升運行速度。此外,當某硬件不支持某項特性時,DirectDraw還提供了硬件仿真層(HEL)以完成這項操作。

DirectDraw的硬件抽象層(HAL)提供了一個統一的接口,通過它,開發者可以直接的操作顯示存儲器和視頻存儲器,從系統硬件中獲取最大的表現能力。

DirectDraw運行於Windows95操作系統之上,從系統所提供的32位內存尋址和平面內存模型中獲益。DirectDraw將視頻和系統存儲器視為整塊的空間,而不是碎片的集合。如果你曾使用過區段偏移尋址,你將很快就喜愛上這種“平面”內存模型。

對於全屏模式的應用程序,DirectDraw使得多后台緩存的換頁操作變得極為容易。

  • 支持窗口和全屏模式應用程序的裁減。
  • 支持3-D z緩存。
  • 支持帶z軸方向的硬件輔助覆蓋。
  • 可訪問圖象縮放硬件。
  • 可同時訪問標准的和增強的顯示設備內存區。
  • 其它的特性,包括動態改變調色板、獨占訪問硬件、和分辨率切換等等。

合理和有效的利用DirectDraw的這些特性將使開發者很容易就能夠寫出比基於標准的Windows GDI應用程序,甚至MS-DOS應用程序還要優秀的作品。

 

五、DirectX 5.0的新特性

DirectX5.0版本比3.0版擁有更多的創新和新特性(注意:並不存在4.0版)。盡管這樣,你用以前版本寫的DirectX應用程序同樣可以不加修改的運行於DirectX5.0環境中。以下將介紹的是DirectX 5.0中DirectDraw接口的新特性。

首先,DirectDraw支持Windows98和Windows NT 5.0的多顯示器系統。例如,在多顯示器環境下,同一台電腦使用兩套不同的顯示設備和顯示器,用戶可以將不同的畫面顯示在兩個顯示屏幕上,甚至將一個窗口從一個顯示器拖拽到另一個顯示器上。DirectDraw5.0支持這樣一個系統,允許應用程序在這樣的環境中直接的訪問和操縱硬件加速特性。

其次,DirectDraw已經使視頻端口(Video-port)具有新的性能,它允許應用程序直接控制數據流從一個硬件視頻端口流向一個位於顯存中的DirectDraw頁面上。這樣,使得視頻圖象的輸入和輸出對DirectDraw來說都變得極為便利和快速。

       除此之外,DirectDraw的HEL(硬件仿真層)現在可以充分利用Pentium MMX處理器所帶來的多媒體性能的提升。當你第一次創建頁面的時候,DirectDraw會測試你的電腦芯片是否支持MMX。而在一台非Pentium MMX級別電腦上,這個測試會在調試程序的時候給出一個良性的異常信息,而這個異常並不會影響到你的應用程序執行的穩定性。

       DirectDraw接口現在還支持離屏頁面的寬度大於主頁面的寬度。你可以創建這些頁面而不用顧及它的大小,而在以前的版本中,你經常會為了不使頁面寬度大於主頁面而將其分割開來。

DirectDraw接口現在已經能夠支持高級圖形端口(AGP)構架。在裝備了AGP顯卡的系統中,DirectDraw程序開發者將從AGP顯卡的大容量和高傳輸速率中獲益。

 

六、部件對象模型(COM

DirectX是基於COM的一套軟件編程接口。

部件對象模型(Component Object Model, COM)是OLE 的基礎。COM 為OLE提供了編程模型和二進制標准。COM定義並實現了軟部件(如應用程序、數據對象、控件及服務)機制,並把它們統稱為“對象”。每個軟部件對象由數據以及訪問數據的函數組成,訪問軟部件對象數據的函數的集合稱為“接口”。

從這里可以看出,COM 的設計與C++類非常相似,即一個軟部件對象具有一個內部數據結構和一組外部接口函數,允許通過接口函數對數據進行訪問。因此Microsoft公司把根據COM執行的對象統稱為Windows對象。但Windows對象與C++中的對象有明顯的差別,Windows對象中沒有公共數據,也沒有成員函數,因此不能直接訪問數據,也就是說數據是全封裝的。

對象的提供者或服務器必須指明一個或多個接口的定義,每個接口都是相互關聯的一組函數,執行對象的一個特性。每個對象的一個用戶(或稱為“客戶”)必須擁有一個接口指針才能訪問對象。當客戶有了這個指針后,就可以使用這個對象而無需知道對象的含義,即使客戶運行在不同的進程、不同的機器、不同的操作系統、由不同的軟件開發而使用不同的語言,或者版本不同。

DirectX SDK接口被創建在COM編程層次表中很基礎的一層。每一個代表設備的對象的接口,比如IDirectDraw、IDirectSound、和IDirectPlay,是直接從IUnknown OLE接口中派生下來的。這些基本對象的創建被操作於在該對象的動態連接庫中,要比用Win32中專門用來創建COM對象的CoCreateInstance函數要好得多。

特別的是,DirectX SDK對象模型為每一個設備提供一個主要的對象。其它的設備對象是由這個主要對象派生下來的。例如,DirectDraw對象代表顯示設備。你可以用它來創建代表顯存的主頁面(DirectDrawSurface)對象,和代表硬件調色板的調色板(DirectDrawPalette)對象;相似的,DirectSound對象代表聲卡,並且創建DirectSoundbuffer對象來代表聲卡上的聲音數據。

 

七、自我檢測

本章所講的內容都是關於DirectX的較為抽象的概念。其實,在你真正開始自己編制程序,進入這一領域之前,有些概念現在還是很難接受的。但是,當你在有了一定的實踐經驗,在回過頭看這些文字,你會發現它們其實還是很好理解的。實踐出真知,掌握DirectDraw知識的最佳途徑只有一個,那就是“編程”。在下一章里,我們將正式開始DirectDraw編程的學習。

通過這一章的學習,你應該對以下內容有所了解:

  • DirectX是做什么用的?
  • DirectX包括哪些組件,以及它們各自的作用。
  • DirectDraw的好處是什么,我們為什么要使用它。
  • COM的一般解釋。

 

第二節如何安裝和使用DirectX

一、編譯庫和運行庫

為了能夠使用MS VC++5.0進行DirectX編程,必須擁有一套DirectX的SDK(軟件開發工具包),它包括了編譯DirectX應用程序所需要的編譯庫文件(*.lib)、頭文件(*.h)、示例,還有幫助。MS VC++5.0自己帶有3.0版的DirectX SDK,所以只要按照缺省的安裝,你的VC就已經可以編制基於DirectX的應用程序了。不過,缺省的安裝並不會把DirectX的在線幫助包括在內,沒有這個功能強大的在線幫助,你常常會陷入孤立無援的境地。這一章將教你如何安裝一個全功能的MS VC++5.0 DirectX編譯環境。

如果你有DirectX5.0版或更高版的SDK,本章還將教你如何更新舊版的DirectX編譯環境。且慢,如果你辛辛苦苦編了一個漂亮的動畫程序,興高采烈的拷給你的朋友,第二天他們卻告訴你根本就運行不了,那將是何等的尷尬與無奈。原來,要讓DirectX應用程序能在脫離VC的環境下執行,你的電腦還必須安裝有DirectX運行庫(Runtime library),這也就是我們常說的DirectX引擎(Engine)或驅動程序(Device Driver)。好在Microsoft為了推行其DirectX標准,將這套驅動程序庫免費奉送,你可以從Microsoft公司的Web站點的DirectX頁上下載,當然,這顯然是個很愚蠢的方法,因為,在很多高速圖形游戲如Need For Speed(極品飛車)、Motor Racer(摩托英豪)、Diablo(暗黑破壞神)、Red Alert(紅色警戒)等中,都有DirectX驅動程序提供。安裝驅動程序只用運行Setup文件就可以了,它默認的包括了在Windows注冊表中注冊DirectX部件,並且將動態連接庫文件(DLL)復制到Windows系統目錄中。只不過,這些驅動程序包只包括了DirectX引擎,並無編譯DirectX程序所需要的庫文件和頭文件。

 

二、安裝Visual C++ 5.0

1、電腦配置

本教程所有的DirectX程序都是在Visual C++ 5.0環境中編譯完成的,所以,建議用戶安裝Visual C++ 的5.0版本,請不要使用以前的VC版本,雖然程序可以正常運行,但也會發生不可預知的錯誤。而且,VC5.0以其強大的功能和基於超文本(HTML)的在線幫助,定會使你事半功倍,大大提高編程效率。

安裝Visual C++5.0的DirectX所需要的軟、硬件最低配置:

Windows95或WindowsNT 3.0;

IBM PC 極其兼容機,最好具有80486以上的微處理器;

8MB內存;

最小安裝需要140MB的可用硬盤空間,典型安裝需要200MB的可用硬盤空間,CD-ROM安裝需要50MB的可用硬盤空間,完整安裝需要300MB的可用硬盤空間;

高密軟盤驅動器;

VGA顯示器;

CD-ROM驅動器。

以上是Microsoft所給出的最低配置,但在實際情況下,如果你的電腦還是一台486、8MB內存,甚至奔騰100、16MB內存的配置,你完全沒有理由猶豫是繼續使用這台老牛拉破車的電腦呢,還是該為自己重新配一台(如果你的錢包夠鼓的話)。以下是為了更加突出的體現DirectX的硬件加速特性,建議讀者至少有以下配置:

Windows95、Windows98或WindowsNT 4.0;

奔騰166、AMDK6-166或Cyrix166以上的微處理器,PII或AMDK6-3D尤佳;

至少16MB內存;

SVGA顯示器,至少支持800*600*16K,刷新率75HZ以上;

PCI或AGP顯示卡,至少2MB顯存;

16位聲卡;

至少4速CD-ROM驅動器;

擁有一塊VOODOO2或G200圖形加速卡(鑒於其不菲的價格,這並不是必選配件)。

2、開始安裝

運行Visual C++5.0的安裝光盤上的Setup.exe文件。

彈出“Welcome”對話框,單擊“Continue”按鈕,安裝程序顯示“Registration”對話框,要求用戶輸入姓名(User)、公司名稱(Organization)和10位的CD號。

正確輸入注冊信息后單擊“Continue”按鈕,從彈出的畫面中單擊“Microsoft Visual C++ 5.0”,再從彈出的畫面單擊“Install Visual C++ 5.0 Enterprise Edition”。

稍后彈出“Welcome”對話框,單擊“Next”按鈕,安裝程序顯示軟件授權協議。

單擊“Yes”按鈕,安裝程序顯示“Installation Options”對話框,可以對Visual C++5.0的安裝路徑和安裝類型進行設置,選擇用戶定制(Custom)型安裝。

單擊“Next”按鈕,出現如下對話框,如圖選擇要安裝的部件,最后選中“Books Online”項,再單擊“Details”,以安裝DirectX幫助文件。

在出現的以下對話框中,選中“Win32SDK”,其它項也如圖選擇。

接着,單擊“Next”按鈕,安裝程序開始安裝Visual C++5.0,並顯示進度畫面。

安裝完畢后,請重新啟動Windows95。

 

三、安裝DirectX5.0SDK

很多地方都提供DirectX的SDK,你可以在某些三維游戲的光盤上找到,也可以直接從Microsoft的Web站點上下載(http:\\www.microsoft.com)。

由於本教程的所有示例都是用DirectX5.0編譯成功的,所以讀者最好也使用DirectX的5.0版。安裝方法如下:

運行SDK中的Setup.exe文件,出現Welcome窗口,按Next按鈕,出現軟件許可協議窗口,按YES按鈕。此時出現Setup Type對話框,有三種安裝選擇,選擇其中的Compelete(完全安裝),然后按Next。

在出現的下一個對話框中,在Retail or Debug Runtime選項中選擇Debug,在Force Install中做上打勾標記,如下圖。按Next按鈕。

在這里有兩個安裝選擇:一個是Retail(零售)安裝,推薦給僅用於單獨運行游戲的用戶,並不會幫助程序員在編譯過程中發現錯誤;另一個是Debug(調試)安裝,推薦給開發者,運行雖然較前者稍慢,但可以幫助調試程序。選中Force Install,表示將覆蓋這台電腦上的任何更新的DirectX版本。

在出現的對話框中,讓你選擇是否安裝IE和ActiveMovie,讀者請隨意。然后按Next按鈕。

在余下的步驟中,讓你選擇安裝路徑和快捷方式夾名稱,按Next即可。設置完畢后,安裝程序開始正式安裝直至結束。

安裝完畢后,你將在你的SDK路徑中看到以下目錄:

SDK:軟件開發工具包。

INC:頭文件

LIB:庫文件。*.LIB,用於VC++編譯器;*.LBW,用於Watcom編譯器

SAMPLES:例程源代碼

BIN:例程的可執行文件

DOCS:幫助和Readme文件。

FOXBEAR:Fox & Bear演示游戲Demo

ROCKEM:Direct3D演示游戲Demo

DXBUG:DirectX 調試報告工具。

可以看到,一個完整的SDK已經安裝至你的電腦。但是為了讓VC能夠調用到SDK中的頭文件和庫文件,你還要做以下的工作以打通路徑。有兩種方法供你選擇:

方法一:將sdk\inc和sdk\lib目錄中所有的文件分別復制到VC的include和lib目錄中去。這是最簡單也是最有效的方法。

方法二:打開VC,選擇Tools菜單的Options選項,在Include files列表的開頭添加“c:\dxsdk\sdk\inc”,再在Library files列表的開頭添加“c:\dxsdk\sdk\lib”。這是微軟所推薦的方法。

必須把這兩個路徑加在最開頭是因為:VC在尋找頭文件和庫文件時是按照列表中的先后順序,VC以前所打通的路徑中已經包含了DirectX3.0的頭文件和庫文件,如果把這兩個路徑加在最后,VC在編譯時所使用的仍是舊版的DirectX 3.0的文件。

好了,一切就緒,DirectX已經在向你招手。如果你想趕緊實驗你的第一個程序,可以跳過下面的部分,直接進入下一章。

 

四、DirectX5.0文件說明

如果按照缺省路徑安裝,DirectX將在C:\Program Files\directx\setup目錄中加入三個可執行文件:DxInfo.exe、DxSetup.exe、DxTool.exe。對那些諳熟DirectX的人來說,運行這些個文件並加以評價和指點,是很容易在同事中建立起高手形象的。這三個文件用來透視和評價電腦硬件系統2D、3D、音頻等加速性能的高低。

DxInfo.exe:顯示計算機的軟、硬件信息極其驅動程序文件信息,從這里你可以了解到你的計算機的顯卡、聲卡、芯片、操作系統和DirectX各組件的驅動程序和特性。

DxSetup.exe:DirextX安裝程序,可以設置是否啟用Direct3D硬件加速以及還原音頻、顯示器驅動程序。

DxTool.exe:這是DirectX中最有用的一個文件,從它你可以對你的電腦的DirectX硬件加速特性有一個全面而深入的了解,還可以設置是否啟用Direct3D硬件加速以及DirectDraw硬件加速。

這三個文件隨着版本不同,也會與其上所述不盡相同。

 

五、卸載DirectX

由於DirectX驅動程序自己不帶反安裝程序,所以一旦安裝之后,是無法將其卸載的,只有一步一步往上升級,否則只能重新安裝Windows95,這就象一旦你選擇了一條道路,就要貫穿始終的走下去一樣。試着打開“控制面板”里的“添加/刪除程序”,會發現“DirectX驅動程序”赫然其中,小心翼翼的按過“刪除”按鈕,彈出的卻是DirextX的安裝程序,你始終無法用常規的方法將其卸載。

是不是就無能為力了呢?當然不是,本站的軟件下載中提供了一個反安裝DirectX的軟件,可以用來刪除你的電腦上的DirectX(它只是卸載DirectX驅動程序,並不會卸載其SDK)。目錄位於光盤:\dxuninstall。

應該注意的是,由於某些顯卡和聲卡,幾乎包括現在出的所有顯卡或聲卡,它們的驅動程序中都已經自帶了DirectX驅動程序,所以,除非你重新啟動Win95,切換到安全模式下,再運行DirectX的卸載程序,否則,你始終無法將DirectX完全卸載。

 

第三節一個DirectDraw入門程序

一、一個小測驗

在正式進入本章主題之前,先對讀者進行一次小小的入學考試,不用緊張,其實幾道題都非常的簡單。

1、根據你對左邊這三個函數的直觀感覺,選出它們的正確對應關系。

WinMain()         a、初始化窗口

InitWindow()       b、處理Windows消息

WinProc()          c、應用程序入口

2、找出與其它三個沒有共同點的一個。

a、HINSTANCE     b、HWND

c、HBITMAP         d、HELLO

3、HWND之於窗口,相當於什么之於蘋果。

a、蘋果皮     b、蘋果核

c、蘋果把兒     d、整個蘋果

4、如果要創建一個最普通的窗口,應該用以下的哪一個標志。

a、WS_OVERLAPPEDWINDOW     b、WS_STRANGE

c、WS_BEAUTIFUL              d、WS_UGLY

如果你有一道題做錯了,說明你對Win32編程還不是十分了解,那么你需要事先預習一下。請跳轉到本教程的“Win32編程基礎知識”一章,學習一下Win32編程的基礎知識。(這四道題的答案分別是:cab、d、c、a。其實它們真的是非常簡單,只要你仔細閱讀一下題目,就是猜也能猜出來。)

如果這些題對你來說不成問題,祝賀你,你可以繼續本章的內容了。

 

二、牛刀小試

只要是介紹編程的書,似乎有一個不成文的規定,即第一個例子由“Hello World”開始,本教程也不例外。那么,如果你早已迫不及待想初嘗DirectDraw程序編譯成功后的“0 error(s), 0 warning(s)”的喜悅,就讓我們開始吧!

在下面的例子中,我們將利用Visual C++5.0來生成一個簡單的DirectDraw應用程序。程序的創建將不使用方便的MFC(Microsoft Foundation Class Library,微軟的C++基礎類庫)向導,而是使用最原始的Win32 應用程序開發環境。熟悉VC++的讀者可能會問,為什么舍先進的MFC工具不用,而去使用最原始的方法呢?這是因為,MFC主要是用於基於窗口和文檔的應用軟件的編程,它集成了大量的數據和方法,將許多煩瑣的任務,如:應用程序初始化、文檔處理、磁盤IO封裝起來,雖然這樣可以給你的編程帶來了極大的便利,但是在你編制基於圖形顯示和多媒體的應用程序的時候,這卻會給你帶來極大的麻煩。首先,你無法觸及系統的內核,如:你需要自己來處理每一個消息循環時,而MFC並沒有為你留出這樣一個接口;而且,MFC為你事先建好的類,它們的許多功能對你來說是沒用和低效率的,使用它們只會給你的程序帶來冗余和不便。

總之,MFC為你隱藏了太多技術細節,而DirectDraw編程需要系統對於開發者具有一定的透明度。 所以,在大多數情況下,我們用最基本的Win32應用程序開發環境來開發我們的DirectDraw應用程序,本教程中幾乎所有的例程都是使用Win32開發環境。當然,這並不是說用MFC就不能編制基於DirectDraw的應用程序了,它也是可以的,這將在本教程的“用MFC創建DirectDraw應用程序”一章中做介紹。

使用Win32開發環境表明,你必須從WinMain()開始編程,自己寫每一個消息的處理程序,這的確是一項很繁重的工作。但是當你理解和熟悉了這一套方法時,你會發現它其實是相當直觀和容易的。

打開Visual C++ 5.0。

選擇File菜單的New,在出現的對話框中,選擇Projects欄目(新建工程),並點取其下的Win32 Application項,表示使用Win32環境創建應用程序。先在Locatin(路徑)中填入“c:\”,然后在Project Name(項目名稱)中填入“Hello”,其它按照缺省設置,使對話框如圖所示。單擊OK按鈕。

此時,一個基於Win32的工程已經創建完畢,但是它還沒有包括任何文件。你需要新建一個C++文件增加到工程中。

再次選擇File菜單的New,在出現的對話框中,選擇Files欄目(新建文件),並點取其下的C++ Source File項,表示新建一個C++源文件。在右邊的File欄中輸入“Hello”,最后確定讓Add to project檢查框打上勾,使整個對話框如圖所示。單擊OK按鈕。

在Hello.cpp文件中輸入以下源程序代碼,當然,你最好的做法是將以下的代碼復制到你的文件中去,確保能用。

//*******************************************************************

// 工程:hello

// 文件:hello.cpp

// 內容:創建第一個DirectDraw應用程序,

//*******************************************************************

#include <windows.h>

#include <windowsx.h>

#include <ddraw.h>

LPDIRECTDRAW lpDD; // DirectDraw對象

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw主頁面

char szMsg1[] = "Hello World, I am DirectDraw boy !";

char szMsg2[] = "按 ESC 退出";

//函數聲明

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );

BOOL InitDDraw( void );

void FreeDDraw( void );

//*******************************************************************

//函數:WinMain()

//功能:Win32應用程序入口函數。進行初始化工作,處理消息循環

//*******************************************************************

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,

 

                      LPSTR lpCmdLine, int nCmdShow)

{

    MSG msg;

    //初始化主窗口

    if ( !InitWindow( hInstance, nCmdShow ) )

        return FALSE;

    //初始化DirectDraw環境,並實現DirectDraw功能

    if ( !InitDDraw())

    {

        MessageBox(GetActiveWindow(), "初始化DirectDraw過程中出錯!", "Error", MB_OK );

        FreeDDraw();

        DestroyWindow(GetActiveWindow());

        return FALSE;

    }

    //進入消息循環

    while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

    return msg.wParam;

}

//******************************************************************

//函數:InitWindow()

//功能:創建主窗口。

//******************************************************************

static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )

{

    HWND hwnd; //窗口句柄

    WNDCLASS wc; //窗口類結構

    //填充窗口類結構

    wc.style = 0;

    wc.lpfnWndProc = WinProc;

    wc.cbClsExtra = 0;

    wc.cbWndExtra = 0;

    wc.hInstance = hInstance;

    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );

    wc.hCursor = LoadCursor( NULL, IDC_ARROW );

    wc.hbrBackground = GetStockObject(BLACK_BRUSH);

    wc.lpszMenuName = NULL;

    wc.lpszClassName = "dxHello";

    //注冊窗口類

 

    RegisterClass( &wc );

    //創建主窗口

    hwnd = CreateWindowEx(

    0,

    "dxHello",

    "",

    WS_POPUP,

    0, 0,

    GetSystemMetrics( SM_CXSCREEN ),

    GetSystemMetrics( SM_CYSCREEN ),

    NULL,

    NULL,

    hInstance,

    NULL );

    if( !hwnd ) return FALSE;

    //顯示並更新窗口

    ShowWindow( hwnd, nCmdShow );

    UpdateWindow( hwnd );

    return TRUE;

    }

//******************************************************************

//函數:WinProc()

//功能:處理主窗口消息

//******************************************************************

LRESULT CALLBACK WinProc( HWND hWnd, UINT message,

                             WPARAM wParam, LPARAM lParam )

{

    switch( message )

    {

        case WM_KEYDOWN://擊鍵消息

            switch( wParam )

            {

                case VK_ESCAPE:

                    PostMessage(hWnd, WM_CLOSE, 0, 0);

                    break;

            }

            break;

        case WM_DESTROY://退出消息

            FreeDDraw();

            PostQuitMessage( 0 );

            break;

    }

    //調用缺省消息處理過程

    return DefWindowProc(hWnd, message, wParam, lParam);

}

//******************************************************************

//函數:InitDDraw()

//功能:初始化DirectDraw環境並實現其功能。包括:創建DirectDraw對象,

// 設置顯示模式,創建主頁面,輸出文字。

//******************************************************************

BOOL InitDDraw(void)

{

    DDSURFACEDESC ddsd; //頁面描述

    HDC hdc; //設備環境句柄

    //創建DirectCraw對象

    if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;

   // 取得獨占和全屏模式

    if ( lpDD->SetCooperativeLevel( GetActiveWindow(),

                    DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

        return FALSE;

    //設置顯示模式

    if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;

    //填充主頁面信息

    ddsd.dwSize = sizeof( ddsd );

    ddsd.dwFlags = DDSD_CAPS;

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    //創建主頁面對象

    if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)

        return FALSE;

    //輸出文字

    if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE;

    SetBkColor( hdc, RGB( 0, 0, 255 ) );

    SetTextColor( hdc, RGB( 255, 255, 0 ) );

    TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1));

    TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));

    lpDDSPrimary->ReleaseDC(hdc);

    return TRUE;

}

//******************************************************************

//函數:FreeDDraw()

//功能:釋放所有的DirectDraw對象。

//******************************************************************

void FreeDDraw( void )

{

    if( lpDD != NULL )

    {

        if( lpDDSPrimary != NULL )

        {

            lpDDSPrimary->Release();

            lpDDSPrimary = NULL;

        }

        lpDD->Release();

        lpDD = NULL;

    }

}

為了簡化代碼,這第一個入門程序沒有頭文件。在進行編譯之前,還得進行最后的設置。選擇Project菜單的Settings…,出現工程設置對話框。選擇Link欄,在Object/Library modules中添入“Ddraw.lib”。使對話框如圖所示。

請注意:這一步驟是將DirectDraw的靜態連接庫文件連接到工程中,否則,程序雖然可以正常編譯,但是在連接時會產生一個“unresolved external symbol”(沒有定義的外部符號)的錯誤。在以后所有的DirectDraw程序中,都必須將與你所用到的DirectDraw組件相應的靜態連接庫添入到這個設置中。

至此,一個最基本的DirectDraw應用程序已創建完畢,你現在不必去深究這些代碼的含義,在下面及以后的章節中我們會對它們進行詳細的分析。這雖不是一個最簡單的DirectDraw應用程序,但它確實是一個能夠實現最基本的輸出功能的DirectDraw程序。

按F7編譯成功后,按Ctrl+F5,執行該程序,顯示器將切換到640*480*256色模式,黑屏后,屏幕中央會打印出藍底黃字“Hello World, I am DirectDraw boy !”,除了輸出這些字符外,這個程序什么也不做。按ESC可退出程序。程序運行結果如下圖

這就是DirectDraw?有的人也許會對DirectDraw感到很失望,因為它並沒有為我們表現出神奇的功能啊?但有的人卻會對此感到異常興奮,他們覺得一扇充滿誘惑的房間的大門正向他們打開。這就象透過天窗,有些人只會看到黑暗的夜空,有些人卻能看見滿天的星星一樣。第一個例子,為了使程序不至於太長而讓那些初學者望而生畏,所以只能一再簡化(盡管這樣,整個程序還是占用了相當大的篇幅)。在后續章節的例子中,你們會看到程序會一個比一個更精彩。

 

三、分析代碼

下面,讓我們來逐一分析一下這個程序。

1)程序結構

分析程序應該是一個由外而內,逐步求精的過程。首先從大的方面來看,這個程序一共用到了五個函數,如果按照正常順序,排除程序中出錯的可能,它們的調用順序依次是這樣的。

WinMain ----> InitWindow ----> InitDDraw ----> WinProc ----> FreeDDraw

WinMain:所有Win32應用程序的入口函數,它也是應用程序關閉時的出口,一個應用程序的全生命周期就是在它的控制之下。所以,確切的說,其它四個函數是被包括在WinMain之內的。消息循環也是在這個函數中啟動。

InitWindow:初始化和創建一個與程序的HINSTANCE(實例句柄)相關聯的主窗口,這個窗口的HWND(窗口句柄)在初始化DirectDraw環境時需要用到。

InitDDraw:初始化和創建DirectDraw對象,並執行一定的功能。它里面包括了創建DirectDraw對象,創建頁面,設置顯示模式,創建主頁面,輸出文字。

WinProc:是應用程序感知外來動作和產生反應的神經中樞,相當於人的大腦。這是程序中最主要的部件之一,它和在WinMain中所啟動的消息循環是一起工作的。

 

FreeDDraw:釋放DirectDraw的各種對象,以使其不再占用內存空間。

上面這段話如果使你感到迷惑,就象是在你小學的時候有人給你講什么是微積分,那么你仍需要事先預習一下Win32編程。請跳轉到本教程的“Win32編程基礎知識”一章,復習一下Win32編程的基礎知識。

如果你對Win32編程和Windows的消息機制有一定的了解的話,以上概念是比較容易理解的。

 

2)定義和創建DirectDraw對象

分析完程序的總結構,再讓我們從最開頭看起。

#include <ddraw.h>

這是把DirectDraw的頭文件包含到文件里來。這一步在以后所有的例程中都是必不可少的。

LPDIRECTDRAW lpDD; // DirectDraw 對象

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主頁面

接着定義了三個全局指針變量,它們都是指向對象的指針。第一個是DirectDraw對象,表示顯示硬件,它包括了顯示器和顯卡還有顯存,用它來代表整個顯示系統。第二個是DirectDrawSurface對象,表示頁面,你可以在大腦中把它想象成一張矩形的白紙,你可以在上面繪制圖象。現在你暫且不用去深究它們的含義,看下去就是了,在后面的課程中,我還會更加詳細的介紹。

LPDIRECTDRAW和LPDIRECTDRAWSURFACE是在ddraw.h頭文件里預定義的指向DirecctDraw和DirectDrawSurface對象的長型指針,所以前面加了LP,代表Long Point。因為這些DirectDraw對象指針變量經常用到,所以給它起名為lpDD和lpDDSPrimary,DD是DirectDraw的縮寫。

給一個變量命名時,在變量名前加上該變量的類型標志,在Windows編程中是一個默認約定,稱為匈牙利表示法,該名稱來源於微軟的一個匈牙利籍資深程序員,因為他慣用此表示法,后來便成了規范。如lpDD表示一個長型指針變量,dwHeight代表一個DWORD型變量。這樣的好處就在於你可以一眼就辨認出某變量的類型,而不用去追溯它的定義。

這里所說的“對象”(Object),並不完全等同於C++中對象的概念,盡管它們使用的是同一個英文單詞Object。這里的對象指的是COM,COM是Component Object Model 的縮寫,代表“部件對象模型”,它在DirectX中貫穿始終,無處不是它的身影。

       在DirectX SDK中,大多數APIs(應用程序編程接口)由對象和基於COM的接口組成。COM是致力於可重復利用接口資源的面向對象系統的基礎,並且是OLE編程的核心模型。它也是一個接口規范,通過它可以設計出許多接口。它是建立在操作系統層次的對象模型。

COM與C++類也有許多相同之處。對一個C++程序員來說,COM接口就象是一個抽象基礎類。這就是說,它定義了一套關鍵符號(signatures)和語法(semantics),但不是執行語句,並且沒有與接口相關聯的狀態數據。在C++抽象類中,所有的方法被定義成純虛(pure virtual)函數,它們並沒有實際的代碼。在這一點上,COM和基礎類是一致的。

COM對象與C++對象的另一個相似點是:一個函數的第一個引用是接口或類的名稱,在C++中叫做this引用。因為COM對象和C++對象是完全二進制兼容的,編譯器把COM接口當作C++抽象類來看,而且采取同樣的語法。這樣就可以減少代碼的復雜程度。例如,this引用在C++中被當作可識元素,並且被暗中的操作,COM中也是如此。

COM 是DirectX的基礎,雖然它和C++中的類不近相同,但你完全可以把它當成C++的類來看待,在實際編程中,它們的語法和接口也是完全一樣的。所以,對於C++程序員,進行DirectX編程並不需要你去學習新的編程方法,繼續沿用你所熟知的C++,DirectX也能為你所用。

只要是你要用到DirectDraw接口的特性,都必須創建一個DirectDraw對象,它是DirectDraw接口的核心。它是這樣被創建的:

if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;

DirectDrawCreate()函數是在ddraw.h中定義的,關於這個函數的詳細解釋,請參看本教程的“DirectDraw參考手冊”一章,它的原型如下:

HRESULT DirectDrawCreate(GUID FAR * lpGUID, LPDIRECTDRAW FAR * lplpDD, IUnknown FAR * pUnkOuter);

第一個參數是lpGUID:指向DirectDraw接口的全局唯一標志符(GUID:Global unique identify)的指針。在這里,我們給它NULL,表示我們將使用當前的DirectDraw接口。

第二個參數是lplpDD:這個參數是用來接受初始化成功的DirectDraw對象的地址。在這里,我們給它&lpdd。

第三個參數是pUnkOuter:千萬不要追問這個參數是干嘛使的,如果你不想惹麻煩,就給它NULL吧。Microsoft的說明書上是這么寫的“考慮到與將來的COM集合特性保持兼容,當前,不管怎樣,如果這個參數不是NULL ,DirectDrawCreate將返回一個錯誤”。

所有的DirectDraw函數的返回值都是HRESULT類型,它是一個4字節(32位)的值,用來代表某個錯誤或警告。DirectDraw頭文件中已經預定義了所有可能的返回值常量,僅函數返回成功的值是用 “DD_OK”表示,所有的錯誤值標志開頭都為“DDERR”,如:

DDERR_DIRECTDRAWALREADYCREATED

DDERR_GENERIC

DDERR_OUTOFMEMORY

Windows編程中,有一項讓中國軟件開發者大撓其頭的就是函數名、常量名和數據結構名稱中往往有長串的字符。這時,就要考驗讀者的英文斷句水平了。如:DDERR_OVERLAYCOLORKEYONLYONEACTIVE,應該斷為:DDERR-OVERLAY-COLORKEY-ONLY-ONE-ACTIVE。這對於老外來說也許並不成什么問題,但對於我們,有時候要看懂一個語句,簡直就象在做一道英文題。除非你對一個名稱有十足的把握,否則就應該盡量使用CP規則(原是離散數學里的一條規則,現引申為Copy-Paste,即:復制-粘貼)。而且,給讀者一點忠告,在起函數、變量或常量名稱時,也應該沿用老外的這條原則,以盡量清楚的表達意義為宗旨,不要擔心它是否太長(當然,對於那些重復利用率很高的函數、變量或常量,還應該盡量使用縮寫),否則,總是以i、n為變量,程序的可讀性會變得極差,而在編程的初學者當中,這是很常見的。

 

3)設置控制程度和顯示模式

DirectDrawCreate函數調用成功后,lpDD已經指向了一個DirectDraw對象,它是整個DirectDraw接口的最高層領導,以后的步驟都是在它的控制之下。

if ( lpDD->SetCooperativeLevel( GetActiveWindow(),

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

return FALSE;

       這個語句用來設置應用程序對操作系統的控制程度。它的原型如下:

HRESULT SetCooperativeLevel( HWND hWnd, DWORD dwFlags )

第一個參數是hWnd,我們調用Win32的API函數GetActiveWindow獲得應用程序主窗口的句柄,這將使DirectDraw對象與主窗口的消息掛上勾。

第二個參數是dwFlags,控制級的標志符。我們給它DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN,表示我們期望DirectDraw以獨占和全屏方式工作。

這個函數有很多用法,而且它必須在創建DirectDraw對象之后,立即調用。你可以用它來設置應用程序是運行於全屏還是窗口模式,是獨占還是共享模式。具體用法將在以后逐步介紹。可參閱“DirectDraw參考手冊”。

if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;

顯而易見,這是設置顯示器的顯示模式,它把顯示模式設為640*480,8位色彩模式(即256色)。這是絕大多數顯示器所能夠支持的顯示模式,所以我們不用擔心它會出什么問題。在以后的例程中,我們將看到可以調用EnumDisplayModes()來列舉出顯示器所支持的所有顯示模式。絕不要輕易嘗試直接設置一個新的顯示模式,而應從列舉出的顯示模式中選擇,這也將在后續章節講到。

要注意的是,只有當DirectDraw對象為獨占訪問的控制程度時才能改變顯示器的顯示模式,如果DirectDraw對象運行為窗口模式,調用該函數會返回一個錯誤。

 

4)創建主頁面

然后的任務是要創建一個DirectDrawSurface對象。

DirectDrawSurface對象代表了一個頁面。頁面可以有很多種表現形式,它既可以是可見的(屏幕的一部分或全部),稱之為主頁面(Primary Surface);也可以是作換頁用的不可見頁面,稱之后台緩存(Back Buffer),在換頁后,它成為可見;還有一種始終不可見的,稱之為離屏頁面(Off-screen Surface),用它來存儲圖象。其中,最重要的頁面是主頁面,每個DirectDraw應用程序都必須創建至少一個主頁面,用它來代表屏幕上可見的區域,說白了,就是你的顯示屏幕。

創建一個頁面要分兩步走,這里,我們以創建主頁面為例,簡要介紹一下這兩個步驟,其它類型頁面的創建也與之類似,在以后的例程中還會着重講解。

在調用CreateSurface()函數創建一個頁面之前,首先需要填充一個DDSURFACEDESC的結構,它是DirectDraw Surface Description的縮寫,意思是DirectDraw的頁面描述。該結構的詳細資料請參看本教程“DirectDraw參考手冊”一章。

//填充主頁面信息

ddsd.dwSize = sizeof( ddsd ); //結構的大小

       ddsd.dwFlags = DDSD_CAPS; //指定DDSURFACEDESC結構的ddsCaps成員為可用

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; //指定要創建的是主頁面

       這就象是你到銀行取款,必須事先填寫一張取款單,你要在上面詳細描述你的姓名,你的存折序號,以及你要取的錢數等等,然后把它遞給銀行工作人員。

頁面描述填充完畢后,把它傳遞給CreateSurface()函數即可。

//創建主頁面對象

if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)

return FALSE;

CreateSurface()函數的第一個參數是被填充了頁面信息的DDSURFACEDESC結構的地址,為&ddsd;第二個參數是接收主頁面指針的地址,此處為&lpDDSPrimary;第三個參數現在必須為NULL,為該函數所保留。

如果函數調用成功,lpDDSPrimary將代表一個合法的主頁面對象。由於在前面已經設置了該程序的工作模式為獨占和全屏,所以,此時主頁面所代表的實際上是你的整個顯示屏幕。在主頁面上所繪制的圖形將立即反映到你的顯示屏幕上。

 

5)輸出文字

下面開始在主頁面上輸出文字。

//輸出文字

if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE; //獲得設備環境句柄

SetBkColor( hdc, RGB( 0, 0, 255 ) ); //設置背景顏色

SetTextColor( hdc, RGB( 255, 255, 0 ) ); //設置文字顏色

TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1)); //輸出文字

TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));

lpDDSPrimary->ReleaseDC(hdc); //釋放資源

如果你十分熟悉Windows的GDI(圖形設備接口),你會發現上面這段程序居然和在窗口中輸出文字一模一樣。這是因為,DirectDraw頁面和GDI的設備環境(DC)其實是兼容的。在調用主頁面的GetDC()函數獲得頁面的設備環境句柄(HDC)之后,就可以使用Win32的API繪圖函數來進行繪圖操作了。同樣,最后也必須調用主頁面的ReleaseDC()函數來釋放設備環境資源。

 

6)釋放對象

在程序結束之前,DirectDraw還必須做一項掃尾工作,即:把已經創建的所有DirectDraw對象從內存中清除出去。這就是FreeDDraw()函數的作用。

void FreeDDraw( void )

{

if( lpDD != NULL ) //判斷DirectDraw對象是否為空

       {

if( lpDDSPrimary != NULL ) //判斷主頁面對象是否為空

{

lpDDSPrimary->Release(); //釋放

lpDDSPrimary = NULL;

}

lpDD->Release(); //釋放

       lpDD = NULL;

}

}

每一個DirectDraw接口的對象都有Release()函數,以將其所引用的對象釋放。這其實相當於C++中的delete方法。及時的將不用的對象釋放掉是每一個優秀的程序員都應當養成的良好習慣。

 

6)主窗口的類型

以上關於DirectDraw接口的編程似乎與應用程序的主窗口沒有一點聯系,而且執行后,也看不到窗口的影子,是不是就可以不用創建程序的主窗口了呢?當然不是。

其實,程序執行后,漆黑的背景就是程序的主窗口。在注冊窗口類時,已經給hbrBackground成員指定了黑色。

wc.hbrBackground = GetStockObject(BLACK_BRUSH);

而且,在創建主窗口時,窗口的類型用的是WS_POPUP,表明創建的是一個沒有標題欄,沒有邊框的窗口;而不是常用的WS_OVERLAPPEDWINDOW。

hwnd = CreateWindowEx(

……

WS_POPUP,

……

值得特別注意的是:在設置DirectDraw對象控制級的時候,使用了主窗口的句柄(HWND)。這就是說,DirectDraw將用主窗口來接收各種消息,這樣,就可以利用主窗口來實現DirectDraw對用戶操作的反饋。如,用戶想用光標鍵來控制游戲圖象的運動,光標鍵按下的消息被發往主窗口,然后在主窗口的消息處理過程中操作DirectDraw,使之對光標鍵產生反應。所以,在大多數情況下,創建一個主窗口是必不可少的。

if ( lpDD->SetCooperativeLevel( GetActiveWindow() /*主窗口句柄*/ ,

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

return FALSE;

 

四、小結

DirectX是一個功能強大而且使用復雜的工具,在這一章里,我們僅學習了一個最簡單的利用DirectDraw接口的例子,對於龐大的DirectX來說,它只是冰山一角。要掌握DirectDraw編程是一個復雜的過程,需要從大量的實踐中摸索出經驗,任何讀者想一蹴而就是不可能的。但是,好的開端意味着成功的一半,如果你能理解和掌握本章所學到的內容,繼續下去,堅持不泄,你會發現你將一天比一天更加充實。

通過本章的學習,讀者應能掌握:

  • 在VC++5.0環境中使用Win32應用程序開發環境新建一個DirectDraw工程以及添加源文件。
  • 用DirectDrawCreate()函數創建DirectDraw對象,並立即調用SetCooperativeLevel()設置其控制級,注意其第一個參數為應用程序主窗口句柄。
  • 只有在獨占的控制級下才能調用SetDisplayMode()以改變顯示器的顯示模式。
  • 在調用CreateSurface ()創建DirectDrawSurface對象之前,必須先填充一個DDSURFACEDESC結構以描述頁面的信息。
  • 頁面對象的種類,只有主頁面代表了顯示屏幕,為可見頁面。
  • 用GetDC()獲得主頁面的HDC,然后可以使用Windows API的GDI函數進行繪圖或輸出文字,最后必須調用ReleaseDC()以釋放資源。
  • DirectDraw接口的對象必須被及時的用Release()函數釋放。

 

第四節 DirectDraw基本圖形概念

    在上一章里,我們嘗試了一個非常簡單的DirectDraw應用程序,可以看到,DirectDraw編程對一個C或C++程序員來說並不是陌生和難以接近的。在這一章里,我們將介紹Directdraw編程中所經常用到的圖形方面的知識,在深入進行DirectDraw編程之前,領悟這些最基本的圖形概念是非常必要的。

 

一、像素(Pixel)和分辨率(Resolution)

首先,讓我們從電腦顯示器屏幕上的基本單位棗像素說起。對用戶來說,像素指的是顯示器在某種分辨率狀態下所能表達圖象的最小單位。你可以把它簡單的理解為一種類似於馬賽克的東西。像素的大小與你當前的顯示分辨率有關,現在所有的SVGA顯示器都支持多種分辨率,比如通常所說的640x480、800x600等。許多人錯誤的將像素的大小與顯示器的點間距(Dot Pinch)混淆了,其實它們指的不是同一個東西。當你用放大鏡觀察顯示器屏幕時,你會發現圖象實際是由許許多多紅,綠,藍整齊排列的小點組成,你可能會認為這就是像素,那么你就錯了。這實際上是顯示器光柵的一個掃描點,它是熒光屏后部的三束電子槍發射電子透過一層致密的網打到熒屏反面而發出的熒光,人們常說的0.28的彩顯意即:屏幕上相臨兩個掃描點的平均間距為0.28毫米。一個像素是由若干個這樣的掃描點組成的。專業的說,在某種分辨率狀態下,顯示器的水平(垂直)像素的個數,實際上等於一次水平(垂直)掃描期間,電子束的通短強弱狀態能夠發生變化的次數。一台彩顯所能達到的最大分辨率受到這台彩顯的尺寸和點距的限制。

顯然,顯示器型號越大,點間距越小,則它所能達到的分辨率就越高,那么,它所顯示的圖象就越清晰,表現得越細膩。

 

二、RGB色彩

現實生活中,我們所能分辨的所有顏色都能用三原色:紅、綠、藍合成出來,稱為RGB色彩模式。用RGB方式合成顏色主要用於發光設備,如計算機顯示器。通過分別調節紅,綠,藍三束電子槍的強度,就可產生一個廣泛的色彩范圍。由於色彩的產生來源於RGB三種色彩光強度的疊加,所以,這種方法被稱為彩色疊加。

值得一提但與我們的DirectDraw編程基本上沒有什么關系的另一種色彩表示模式是CMYK色彩模式,代表青,粉,黃三種可減基色,加上黑色作對比,常用於打印,感光材料,膠片等。每種基本色從照射到打印頁的白光中吸取某些色彩,由於CMYK方式通過從白光中吸取特定的色彩,所以被稱為彩色削減。此外,還有HSI(也稱HSL)色彩模式,它代表Hue , Saturation 和 Intensity 。Hue 是我們所認為的色調,如紅橙黃綠青藍紫等,Saturation描述該色調顏色的飽和度,Intensity 描述亮度。

RGB 顏色方式對於顯示器來說是唯一適用的彩色混合方法,而CMYK則是適用於打印和印刷的彩色混合方法,HSI 則是一個很直觀的定義彩色的方法。現在的許多圖形軟件中都同時具有這三種定義顏色方法的應用。但是,不論用那種方法給顏色取值,最終都必須轉化為RGB方式才能為顯示器所認識接收並顯示出來。

在幾乎所有的編程中,當然也包括DirectDraw,都是采用標准的24位RGB模式定義一個顏色,用三個從0到255的數值來分別代表R、G、B三個分量,每個分量都有256種亮度級別。當三種元素全被置為0,像素顯示黑,通常表示為RGB(0,0,0);當全部置為255,像素顯示為白,表示為RGB(255,255,255)。

 

三、設備無關位圖(DIB

位圖圖象(也稱點陣圖象)准確的說是什么呢?它們是數據元素的集合,這些數據元素決定在了在圖片的某個具體位置是什么顏色。這就好比我們用許許多多的馬賽克來拼圖案一樣,每個馬賽克雖然只有一種顏色,但你看到的只是整個美麗的圖案,而不是某一個馬賽克。當圖象顯示在顯示器屏幕上時,正如前面提到的那樣,這一個個的馬賽克就是像素。

Windows中,當然還包括DirectX,都使用設備無關位圖DIB(Device-Independent Bitmap)作為其最基本的圖象文件格式。

之所以稱之為設備無關位圖(DIB),其實是為了與設備相關位圖(DDB:Device dependent bitmap)相區別。由於只有Windows 3.0以前的版本才廣泛使用設備相關位圖DDB,Windows 95和Windows NT及以后的版本使用的都是設備無關位圖DIB,所以,“設備無關位圖”這個名稱在當前已沒有其現實的含義。在大多數情況下,我們將其簡稱為位圖(Bitmap)。

從本質上說,DIB是一個包含了圖象的各種信息的文件,包括圖象的尺寸,使用的顏色個數及其顏色值,還有每個像素的顏色數據等。此外,一個DIB中還包含了一些極少使用的參數,象文件壓縮類型、重要顏色,還有圖象的物理維數等。DIB文件通常具有“.bmp”擴展名,偶爾也會將“.dib”作為其擴展名。

Windows對DIB位圖有規定的格式,在我們學習了位深度和調色板的概念之后,對DIB格式再做詳細的解釋。

與位圖格式相對的是矢量格式,由於它是用形狀和相互關系來描述圖象,WMF文件就是一種典型的矢量圖,它的特點是具有極大的靈活性,可以任意縮放不失真,且存儲空間相對較小,但矢量圖由於其描述圖象的方式與位圖完全不同,不適用於圖象處理的領域。在以后的DirectDraw編程中,我們所指的圖象特指位圖圖象。

 

四、位深度(Bit depth

計算機中,一個字節(Byte)是由8個位(Bit)組成的。位深度指的是用來描述某狀態值所使用的計算機位的個數。在DirectDraw中,通常用位深度來代表位圖中的顏色值所使用的位個數,從另一個意義上講,位深度表示了位圖中顏色的豐富程度。

1字節等於8位二進制數,所以一字節所能表達的十進制整數的范圍是從0到255,即一字節能且最多只能反映出256(2的8次方)種不同的狀態。在位圖中,如果用其中每一種狀態代表圖象中某一個點的顏色,那么最多可以得到256種不同的顏色,這就是我們常見的8位(256色)的位圖。同理,如果用1位二進制數來表示某一點的顏色,那么只能得兩種(2的1次方)顏色,這就是一幅兩位的黑白位圖;如果用四位二進制數則產生16種顏色(2的4次方),這是一幅4位(16色)的位圖,用24位產生16M種顏色(2的24次方)等,這就是一幅24位真彩位圖。由於每幅圖形文件都用確定的位數來代表顏色(1、2、4、8、16、24或32位),所以我們所見的圖片顏色數都為2的n次方(n=位深度)。

顯然,一幅圖象的位深度越高,那么它所能表現的顏色也越多,色彩也就越豐富。通常,在一般情況下,8位(256色)的圖片已可滿足需要,但在一些要求高質量圖象的場合,16或24位的圖象是必須的。當然,顏色位深度的增加,也勢必帶來所需存儲空間的膨脹,沒有經過壓縮的相同大小的24位圖象所需的存儲空間是8位圖象的3倍,這也會帶來文件讀取和操作速度的降低,所以在需要高速度顯示圖象的場合,使用低位深度的圖象是必要的。

      

五、抖動處理(Dithering

在低位深度的圖象中,由於顏色總數的限制,有些顏色無法顯示出來,為了模擬出那些顏色以提高顯示效果,廣泛采用了一種稱作抖動處理(dithering)的方法,也稱半色調處理(Halftoning)。它是指用交替的點圖案去模擬在圖象中不能使用的顏色的過程。單色圖象是最簡單的格式,一般由黑色和白色組成,在一些單色圖象如黑白照片和有深淺的圖案中,會使用各種灰度,這種圖象常被稱為灰度圖象(Grayscale Image)。由於人眼會把一個很細致的黑白相間的圖案解釋成灰色,所以灰度圖象也可使用單色文件格式,數據仍然可以是黑和白。使用黑色或某一種單色的點獲得連續的該色灰度的過程就是抖動處理。抖動處理被更多的用在那些低位數彩色圖象文件中,與不采用這種處理相比,它具有更好的顯示效果。

 

六、調色板(Palette)

顯示器及顯示卡與顯示器的接口都采用模擬方式來處理色彩,因此,它們都具有幾乎無限的色彩顯示和傳輸能力,但主機和顯示卡只能用數字方式來表示和處理色彩,在用數字方式表示色彩時,如果要獲得更豐富,更細膩的色彩就需要增加色彩的位深度,這就需要更大容量的顯示存儲器,相應的也就需要更高的處理速度,同時分辨率的提高也對顯示存儲器的容量提出了很高的要求。

為了盡量降低對顯存的需求,在Windows中可以使用一種間接的色彩表示方法,這就是調色板(Palette)表示法。它的含義是:用一個顏色索引(Color Index)來代表各個像素點的顏色,而不是直接用紅,綠,藍三基色的亮度值來確定每個像素點的顏色。色彩表(Color table)是一個包含了若干顏色索引和該索引所對應的真實顏色值的表,這個色彩表就是調色板。

這種方法就好比給學校里的每一個學生規定一個專用的學號,在很多場合,並不需要知道某個同學的實際姓名,只要知道他的學號即可,顯而易見,使用學號無疑會給學校的學員管理工作帶來了極大的便利。調色板就好比一張記載了學生學號及其姓名的名冊,顏色索引值就是學生的學號,RGB顏色值就是學生的姓名。

使用調色板的好處就在於:索引值占用較少的數據位(1、2、4或8位),而真實顏色值占用較長的數據位(24位,即3字節,分別代表紅,綠,藍三基色的顏色亮度值,從0到255)。由於使用了調色板,既提高了圖象顯示效率,又減少了對顯存的需求。

調色板的顏色索引主要采用4或8兩種位深度:4位位深度可有16種不同取值,對應於顯示器的16色顯示模式,這時一屏最多只能顯示16種不同的顏色;8位位深度可有256種不同的取值,對應於顯示器的256色顯示模式,一屏最多只能顯示256種顏色。於是,問題就開始出現了,當你在16色模式下顯示一幅256色或更高位深度的的圖象時,你將會看到本應色彩鮮艷的圖片變得面目全飛,罪魁禍首就是調色板,由於它使得同時顯示在屏幕上的不同顏色最多只能有16種(對應於16色模式下),於是Windows首先從圖象中挑選了16種使用頻率最高的顏色指定給調色板,對於所有其它的顏色,從調色板中挑選一個最接近的顏色顯示出來。在256色模式下,要同時顯示兩幅不同調色板的256色位圖時,也會發生這種圖象失真的情況。

 

七、GDIDirectDraw

在大多數的Windows編程中,開發者們使用的是Win32的函數以獲得訪問繪圖頁面的能力,例如,使用GetDC函數,可以獲得設備環境(DC?/FONT>Device context)。在獲得設備環境之后,你就可以開始進行對屏幕的繪圖了。Win32的所有圖形函數都是由Windows系統的一個獨立完整的模塊所提供,這就是圖形設備接口(GDI?/FONT>Graphics device interface)。GDI為計算機用戶和計算機硬件之間提供了一個抽象層,在此層的基礎上,用戶可以通過簡單的調用Win32的圖形函數進行圖形顯示。

GDI的一大缺憾就是,它不是為具有高表現力的多媒體軟件和游戲而設計的,設計者們開發它的主要用途是運行商業應用軟件諸如:Word字處理軟件、Excel電子表格、Explorer瀏覽器等。GDI只提供了訪問系統主存的能力,而不提供直接訪問顯存的能力,並不能從具有某些加速特性的顯卡中獲得其優良特性。簡而言之,GDI對絕大多數的商業軟件來說是相當完美的,但對於多媒體軟件和游戲來說,它卻是低速和低效的。

另一方面,DirectDraw可以提供給開發者代表了真實顯示內存的繪圖頁面。這意味着,只要你使用了DirectDraw,你就可以直接操縱顯卡上的內存,圖形顯示變得出奇的快速。而且這些頁面代表了顯存中連續的內存塊,使得在頁面中尋址和讀寫變得非常方便。

 

八、位塊傳送(Blit

“Blit”是“Bit block transfer”的縮寫,意為“位塊傳送”。顧名思義,Blit的作用是:將某一內存塊的數據傳送到另一內存塊,前一內存塊被稱為“源”,后一內存塊被稱為“目標”。這里用的是“傳送”一詞,而不是“復制”,因為在Blit過程中,數據並不是被原封不動的轉移,而是經過了一定的轉換。

在許多書籍中或程序中經常可以見到的“Bitblt”、“Blt"或“Bltting”,其實也是同一個意思,前者讀作['bitb'lit];后者讀作[b'lit]。

在絕大多數情況下,Blit操作是針對位圖圖象的,因此,源數據代表的是“源位圖”,目標數據代表的就是“目標位圖”。圖象程序開發者使用Blit的函數在內存中將某頁面上的一幅位圖經過一定的變換轉移到另一個頁面上。這種變換有很多種,每一種都有一個代碼與之對應,我們稱該代碼為光柵操作代碼(ROP?/FONT>Raster operation code)。

Blit操作被廣泛的用於圖形程序中,如在顯示位圖、移動、復制位圖、位圖合成、位圖特效以及精靈的實現中都有blit的身影。GDI和DirectDraw中,都提供了blit的函數,如GDI中的BitBlt、StretchBlt、PatBlt等,DirectDraw中的IDirectDrawSurface3::Blt和IDirectDrawSurface3::BltFast等。

下面我們來分析一個典型的Blit的函數,GDI的BitBlt是Win32 API中一個重要的函數,關於它的詳細資料可以參閱VC中的幫助,位於:Platform,SDK,and DDK Document\Platform SDK\Reference\Functions\Win32 Functions,它的原型如下:

BOOL BitBlt(                                                     

HDC hdcDest, //目標設備環境的句柄

int nXDest, //目標設備環境的矩形區域的左上角的x坐標

int nYDest, //目標設備環境的矩形區域的左上角的y坐標

int nWidth, //目標設備環境的矩形區域的寬度值

int nHeight, //目標設備環境的矩形區域的高度值

HDC hdcSrc, //源設備環境的句柄

int nXSrc, //源設備環境的矩形區域的左上角的x坐標

int nYSrc, //源設備環境的矩形區域的左上角的y坐標

DWORD dwRop //光柵操作符

);

dwRop參數是光柵操作代碼(Rop),它是指源位圖與目標位圖以及圖案刷的顏色值進行布爾運算的方式,以下列出了常用的光柵操作符。

光柵操作代碼       含義

 

BLACKNESS       用黑色填充目標矩形區域。

DSTINVERT       將目標矩形圖象進行反相。

      MERGECOPY      將源矩形圖象與指定的圖案刷(Pattern)進行布爾“與”運算。

      MERGEPAINT     將源矩形圖形經過反相后,與目標矩形圖象進行布爾“或”運算。

      NOTSRCCOPY    將源矩形圖象經過反相后,復制到目標矩形上。

NOTSRCERASE   先將源矩形圖象與目標矩形圖象進行布爾“或”運算,然后再將

得圖象進行反相。

      PATCOPY        將指定的圖案刷復制到目標矩形上。

    PATINVERT       將指定的圖案刷與目標矩形圖象進行布爾“異或”運算。

PATPAINT        先將源矩形圖象進行反相,與指定的圖案刷進行布爾“或”運算,

 再與目標矩形圖象進行布爾“或”運算。

      SRCAND         將源矩形圖象與目標矩形圖象進行布爾“與”運算。

      SRCCOPY           將源矩形圖象直接復制到目標矩形上。

      SRCERASE           將目標矩形圖象進行反相,再與源矩形圖象進行布爾“與”運算。

      SRCINVERT                將源矩形圖象與目標矩形圖象進行布爾“異或”運算。

      SRCPAINT            將源矩形圖象與目標矩形圖象進行布爾“或”運算。

      WHITENESS               用白色填充目標矩形區域。

      表中,提到了三種的基本布爾運算,分別是:反相(NOT)、與(AND)、或(OR)。還提到了異或(XOR),學過數理邏輯的人都知道,異或運算其實也是可以由前三種組合出來的。可以看出,所有的光柵操作代碼都是由三種基本布爾運算組合而成。實際上,一共有256種光柵操作代碼,但最常用的就是以上這15種。若想使用這15種以外的任何一種時,可以查閱VC的幫助,但在實際中,它們是極少被用到的。

圖案刷(英文是Pattern, 也可翻譯成模式刷,直接的意思是布料上圖案的花樣)是Windows資源的一種,屬於Brush,它其實是一個固定大小的位圖(通常為8x8像素),用來平鋪填充設備環境(DC)的某一區域。它的用法與普通的刷子是沒有區別的,當把一個圖案刷用SelectObject函數選定給某設備環境,再在該設備環境中進行填充操作,那么所填充的不是一個單純的顏色(選用普通的刷子時的情況),而是連續平鋪的圖案。

GDI的blit函數還有PatBlt和StretchBlt。它們與BitBlt大同小異,BitBlt具有Patblt的所有功能,StretchBlt除了具有BitBlt的一切功能外,它還可以將位圖放大或縮小,實現縮放功能。

DirectDraw最常用的blit函數是IDirectDrawSurface3::Blt,它的原型如下:

HRESULT IDirectDrawSurface3::Blt(

LPRECT lpDestRect, //目標矩形區

LPDIRECTDRAWSURFACE3 lpDDSrcSurface, //源頁面

LPRECT lpSrcRect, //源矩形區

DWORD dwFlags, //標志符

LPDDBLTFX lpDDBltFx //光柵操作代碼及特效

);

DirectDraw中的Blt函數比GDI中的BitBlt函數雖然在形式上極為相似,但在內容上卻有了質的飛躍。首先,從運行速度上來比較,IDirectDrawSurface3::Blt可以利用到一切可以利用的硬件加速特性,而且該操作在缺省情況下是異步執行的,這意味着程序不需要等待blit結束才返回,而是在給系統發出了blit的指令后,立即返回,然后系統在后台進行blit操作,這使程序的運行變得極為高效、快速;其次,它還支持帶源和目標關鍵色以及z緩存和alpha 通道的blit操作,這使得用該blit函數來完成各種效果變得極更為容易。

該函數的目標頁面就是調用者本身,而且矩形區域用RECT結構表示。第四個參數dwFlays表示該blit操作的類型,最后一個參數lpDDBltFx是一個包含了光柵操作代碼和其它特效的結構,其中的光柵操作代碼成員與Win32的是兼容的。

DirectDraw中的blit函數還有IDirectDrawSurface3::BltFast和IDirectDrawSurface3:: BltBatch。前者完成一次顯存中的blit操作,在沒有硬件加速的情況下,其速度可以較Blt快一些,后者完成一組blit操作。

要得到更多的關於DirectDraw中的blit函數的資料,請參閱本教程的DirectDraw參考手冊。

 

九、翻頁(Page flipping

在多媒體動畫、動感游戲等軟件中,翻頁是一個相當關鍵的概念。與繪畫師繪制動畫片相比,計算機中的翻頁技術與其具有相似之處。例如:繪畫師在一疊相同的紙上繪畫,畫完一張再畫下一張,在每一張上,繪畫師使畫面有略微的改動,於是,當你在這一疊紙上快速的翻頁時,靜止的圖象便開始運動起來。

DirectDraw中的翻頁與上面這個過程極為相似。首先,你得設置好一個翻頁鏈結構,它由一組DirectDraw頁面組成,每一個頁面都可以被輪流翻頁至顯示屏幕。當前正好位於顯示屏幕的頁面,我們稱之為主頁面(primary surface),其后等待翻頁至屏幕的頁面,我們稱之為后台緩存(Back buffers)。應用程序在后台緩存上進行繪圖操作,然后“翻一頁”,將此頁面翻頁成為主頁面,原來的主頁面成為后台緩存,翻頁后,你所進行的修改可以立即顯示在屏幕上。與此同時,你可以在下一個即將翻頁成為主頁面的后台緩存上進行繪圖。將這個翻頁過程一直持續下去,直到動畫結束。

有了DirectDraw,整個翻頁的任務並不是一件十分困難的工作。你既可以創建一個簡單的雙緩存翻頁鏈結構(一個主頁面和一個后台緩存),也可以創建一個使用起來更為靈活的多后台緩存翻頁鏈結構。

由於DirectDraw更多的使用的是硬件的特性,使得翻頁變得極為快速,這個過程在屏幕上不會產生絲毫的閃爍,其速度可以達到與顯示器的刷新率一樣的數量級。在后面的DirectDraw教程中,我們將具體介紹如何在DirectDraw程序中實現翻頁。

 

十、矩形(Rectangle

在Windows編程中,屏幕上的對象都是以一個封閉矩形的形狀出現的。一個封閉的矩形可以用兩個點來確定,左上角和右下角。絕大多數應用程序使用RECT結構來定義一個封閉矩形,用於blit操作或碰撞檢測(hit detection)。RECT結構定義如下:

typedef struct tagRECT {

LONG left; //矩形左上角的X坐標

              LONG top; //矩形左上角的Y坐標

LONG right; //矩形右下角的X坐標

LONG bottom; //矩形右下角的Y坐標

} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

應該注意的是:這個矩形區域是排除邊線的,並不包括它的右邊線和下邊線。所以,這個矩形的寬度應該等於right-left,而不是right-left+1;同理,矩形的高度等於bottom-top。

      

十一、精靈動畫(Sprite animation

精靈動畫被廣泛的用於多媒體及游戲軟件中。從最基本的意義上說,精靈是一個可以在背景屏幕上四處移動的圖象,通常,這個圖象的形狀是不規則的。它的實現方法可以簡單的這樣來描述:將精靈畫在可見的背景頁面上,然后將所畫的上一個精靈從頁面上抹去,再將精靈畫在頁面的另一個地方,依次類推。於是,對觀察者來說,精靈在屏幕上就動起來了。

然而,在絕大多數情況下,我們所使用的精靈圖象並不是規則的矩形,它們可能是多邊形,也可能是一個支離破碎、完全不規則的圖形。這就給我們實現精靈動畫帶來一個巨大的挑戰,因為所有的blit函數所使用的都是規則的矩形區域,只有對於矩形區域,才能使blit操作更高效、流暢和便於調用。

於是,在GDI編程中,使用了一種稱為屏蔽的方法,這也跟電影的特級合成用的方法差不多,先讓一個人在一塊藍布前表演一些看上去很驚險的動作,然后再將這些影片與事先拍好的背景合成一體,有藍布的地方成了背景的畫面,人就好象置身其中了。

要在計算機中實現這樣的動畫,得有兩幅圖:一幅是精靈圖(Sprite),一幅是屏蔽圖(Mask:也稱掩碼圖)。精靈圖中使要顯示的部分顏色保持不變,其余全為黑色,屏蔽圖中使精靈的部分都為黑色,其余全為白色。

有了這兩幅圖,經過兩次blit運算,就可以把想要的精靈貼到背景上去了。第一次blit是讓屏蔽圖與背景進行布爾“與”(光柵操作代碼為SRCAND)運算,第二次讓精靈圖與背景進行“異或”(光柵操作代碼為SRCINVERT)運算。

偽代碼如下:

BitBlt( hDCdest, 0, 0, width, height, hDCMask, 0, 0, SRCAND );

BitBlt( hDCdest, 0, 0, width, height, hDCSprite, 0, 0, SRCINVERT);

可以看出,在GDI中實現精靈動畫是個比較復雜的過程,主要原因就是因為GDI的Blt函數不支持透明方式。在下面我們將看到,在DirectDraw中,由於其blit函數支持關鍵色,可以實現透明blit,所以,與GDI相比,在DirectDraw中實現精靈動畫要變得簡單得多。

 

十二、關鍵色(Color Key

DirectDraw的blit函數,支持帶關鍵色(Color key)的透明blit操作。這就是說,如果在源頁面上指定了一個顏色為關鍵色,那么在blit操作中,將視具有這種顏色的區域為透明,不會被傳送到目標頁面上。

所以,不管你的精靈看起來是否象一個矩形,你必須首先使它們可以放在一個合適大小的矩形區域內。然后在這個包含了精靈圖象的矩形內,將不屬於精靈圖象的部分全部使用同一種顏色,或使其在一個顏色范圍之內。這個顏色或顏色范圍必須是精靈圖象中所沒有用到的,它就是關鍵色。

使用IDirectDrawSurface3::SetColorKey函數,將這個關鍵色設置給該頁面。之后,調用IDirectDrawSurface3::Blt或IDirectDrawSurface3::BltFast函數,將該精靈blit到另一個頁面上去,矩形區域中只有屬於精靈的像素被映射了,其余帶關鍵色的像素全被視為透明,對目標頁面不會產生任何影響。這種在blit操作中,用於源頁面表示透明區域的關鍵色稱為源關鍵色(Source color key)。

除此之外,你還可以使用一種目標關鍵色,它所影響的只是目標頁面。目標關鍵色定義了在blit操作中,目標頁面上可以被覆蓋的像素。舉例來說,如果你要設計一個有一棵大樹的背景,一個兔子在樹后活動,當它經過樹的時候,會被樹遮住它的一部分或全部,以創造一種前后效果。那么,同樣,你應該給背景頁面上所有除了樹的像素使用關鍵色,表示只有這些像素才能允許在blit中被使用。使用IDirectDrawSurface3::SetColorKey函數,將這個關鍵色設置給背景頁面。然后調用IDirectDrawSurface3::Blt或IDirectDrawSurface3::BltFast函數並且指定了使用目標關鍵色,那么,當你把包含了兔子的頁面blit到目標頁面上大樹所在的位置,大樹總是完整的,兔子的某部分會被大樹遮擋住,就象出現在樹后一樣,前后效果出現了。

 

十三、補丁(Patching

在精靈動畫的過程中,當你把精靈畫到一個新的位置之前,你必須將上一個位置的精靈從背景上抹掉。當然,最簡單的做法是莫過於重新刷新整個背景,使其還原,然后再將精靈畫上去。但是,這樣做會使你失去很多的時間,而時間對於動畫來說是非常寶貴的,而且最主要的一點就是,你的屏幕會因為大面積的刷新而閃爍。所以,你應該跟蹤精靈所在的上一個矩形位置,然后在畫下一個精靈之前,只重畫這一個區域。這種方法被形象的稱為打“補丁”(patching)”。

要給精靈的上一個位置打上補丁,你必須在上一步繪制該精靈之前,把這個矩形區域的背景內容保存下來,在你要畫下一個精靈之前,用這個補丁去還原這塊區域。這個過程會進行得非常和諧,因為與刷新整個背景相比,它不會占用太多的時間。

打補丁的方法可以按以下幾個步驟進行:

將保存的精靈的上一個位置的背景圖象復制到背景上。

把將要繪制精靈的位置的背景圖象保存下來。     

將精靈blit到背景圖象上。

重復步驟1

 

十四、范圍檢查(Bounds Checking)與碰撞檢測(Hit Detection

范圍檢查和碰撞檢測是編程中與精靈動畫相關聯的兩個重要任務。

范圍檢查用來限制精靈的可能的活動范圍,例如,在程序中,你可能想讓某一個精靈限制在屏幕的某個矩形區域內活動,要完成這一步,你應該在精靈每移動一步之前檢查精靈所在的位置,使這個位置的坐標保持在一個矩形范圍之內(通常是一個RECT結構),並且阻止精靈移出這個矩形區域。DirectDraw並沒有提供范圍檢查的服務函數,但是你可以用很簡單的程序實現這樣一個功能。

碰撞檢測,或稱為沖突檢測,用來判斷某一時刻是否有多個精靈處於同一位置。大多數的碰撞檢測是檢查某個精靈的包絡矩形是否與另一個精靈的包絡矩形有重合部分。因為有太多種不同的方法來實現不同種類的碰撞檢測,所以DirectDraw也沒有為用戶提供碰撞檢測的服務函數。用戶可以根據自己的需要編制碰撞檢測的程序。

 

 

第二章DirectDraw核心(高級篇)

第一節 DirectDraw架構

這一章介紹了DirectDraw與操作系統和系統硬件之間的關系。

一、DirectDraw架構概覽

多媒體應用程序及游戲需要高表現力的圖形引擎。Microsoft公司通過DirectDraw,為廣大開發者提供了一個比GDI層次更高、功能更強、操作更有效、速度更快的應用程序圖象引擎,與此同時,也努力使其保持了設備無關的優良特性。DirectDraw主要提供了完成以下任務的工具。

  • 管理多頁面
  • 直接訪問視頻RAM
  • 換頁(Page flipping)
  • 后台緩存(Back buffering)
  • 管理調色板(Palette)
  • 裁剪(Clipping)
  • 視頻端口(Video port)

       除此之外,DirectDraw允許開發者在應用程序運行期測定顯示硬件的特性,然后,充分利用主機硬件設備的加速特性為用戶提供可能的最優的顯示速度和效果。

與DirectX其它組件一樣,只要可能,DirectDraw就會最高程度的利用硬件執行某特定功能,並且讓那些該硬件還不支持的特性也能用軟件仿真的方式加以實現。設備無關性通常是通過硬件抽象層(HAL:Hardware abstraction layer)實現的,要得到更多的關於HAL的資料,請參閱“硬件抽象層(HAL)”。

DirectDraw是通過基於COM的接口提供服務。在DirectX 5.0版本中,這些接口分別是:IDirectDraw2、IDirectDrawSurface3、IDirectDrawPalette、IDirectDrawClipper和IDirectDrawVideoPort。DirectX的這些組件是向下兼容的,它們仍然支持舊版本中的所有功能。要得到更多的關於COM的概念,以有助於理解和創建DirectX應用程序,請參閱“DirectX與部件對象模型(COM)”。

DirectDraw對象代表顯示適配器,並且通過IDirectDraw或IDirectDraw2接口將其函數性暴露於開發者。在大多數情況下,開發者使用DirectDrawCreate函數創建一個DirectDraw對象,但也可以通過使用CoCreateInstance COM函數創建之。要得到更的資料,請參閱“用CoCreateInstance創建DirectDraw對象”。

在DirectDraw對象創建好之后,你可以通過使用IDirectDraw2::CreateSurface方法為該DirectDraw對象創建頁面。頁面代表了位於顯示硬件上的內存,但是它既可以存在於視頻RAM,也可以存在於系統RAM中。DirectDraw還擴展了對調色板、裁剪(主要用於基於窗口的應用程序)和視頻端口(Video port)的支持。

 

二、DirectDraw的對象類型

你可以將DirectDraw視為由若干個協同工作的對象所組成。DirectDraw所使用的對象有以下五個:

對象                          含義

DirectDraw            DirectDraw對象是DirectDraw應用程序的核心。它是你在建立DirectDraw應用程序時所要創建的第一個對象,再用它來創建所有其它相關的對象。通過調用DirectDrawCreate函數可以創建一個DirectDraw對象。DirectDraw對象通過IDirectDraw和IDirectDraw2接口為開發者提供其函數性。要得到更多的資料,請參閱“DirectDraw對象”。

DirectDrawSurface       DirectDrawSurface對象,通常簡稱為“頁面(Surface)”,代表了內存中的一塊區域,它存儲了可以顯示在顯示器上的圖象數據。通過調用DirectDraw對象的IDirectDraw2::CreateSurface函數可以創建一個與該DirectDraw對象相關聯的頁面。DirectDrawSurface對象通過IDirectDrawSurface、IdirectDrawSurface2和IDirectDrawSurface3接口為開發者提供其函數性。要得到更多的資料,請參閱“頁面”。

DirectDrawPalette   DirectDrawPalette對象,通常簡稱為“調色板(Palette)”,代表了一個可以為頁面所使用的16或256色的調色板。它包含了一組RGB值的索引,用來描述頁面上的像素所使用的顏色值。對於像素位深度大於8的頁面,不需要使用調色板。通過調用IDirectDraw2::CreatePalette函數,可以創建一個DirectDrawPalette對象。DirectDrawPalette對象通過IDirectDrawPalette接口為開發者提供其函數性。要得到更多的資料,請參閱“調色板”。

DirectDrawClipper   DirectDrawClipper對象,通常簡稱為“裁剪器(Clipper)”,幫助開發者使Blit(位塊傳送)操作限定在頁面的某一區域內,或不超出頁面的邊界范圍。通過調用IDirectDraw2::CreateClipper函數可以創建一個DirectDrawClipper對象。DirectDrawClipper對象通過IDirectDrawClipper接口為開發者提供其函數性。要得到更多的資料,請參閱“裁剪器”。

DirectDrawVideoPort DirectDrawVideoPort對象代表了當前某些系統上的視頻端口(Vedio

port)硬件。這個硬件允許直接的訪問幀緩存,而不需要通過CPU或使用PCI總線。通過對DirectDRaw對象調用QueryInterface函數(指定IID_IDDVideoPortContainer標志符),可以創建一個DirectDrawVideoPort對象。DirectDrawVideoPort對象通過IDDVideoPortContainer和IDirectDrawVideoPort接口為開發者提供其函數性。要得到更多的資料,請參閱“視頻端口”。

 

三、硬件抽象層(HALHardware Abstraction Layer

DirectDraw通過硬件抽象層(以后簡稱為:HAL)來提供設備無關的特性。HAL是由設備生產商提供的指定設備的接口,DirectDraw用來直接操作顯示硬件。應用程序從來不會直接與HAL打交道,相反,而是與HAL所提供的下屬函數打交道。

DirectDraw HAL可以以16位、32位或在Win95中兩者兼而有之的形式執行。HAL在WinNT中通常以32位方式執行。HAL可以是顯示設備驅動程序的一部分,或獨立的DLL,通過驅動程序編寫者定義的一個私有接口聯系顯示驅動。

DirectDraw HAL是由芯片制造商、板卡生產商或原始設備制造商(OEM)實現的。HAL只執行硬件有關代碼而不進行仿真。如果硬件不能實現某個功能,HAL不會將其反映在自己的硬件特性中。

 

四、硬件仿真層(HELHardware Emulation Layer

當硬件抽象層(HAL)不支持某種特性時,DirectDraw會試圖進行軟件仿真。仿真的函數是由硬件仿真層(HEL)提供的。HEL與HAL一樣,代表了DirectDraw的特性,並且應用程序從來不直接與HEL一起工作。結果是,DirectDraw對硬件的主要特性都提供了透明的支持,而不管這個特性是通過HAL硬件支持的還是通過HEL軟件仿真的。

很顯然,軟件仿真不能與硬件所提供的特性等效。可以調用IDirectDraw2::GetCaps函數以查詢硬件支持什么特性。在應用程序初始化的時候檢查這些特性,你可以調整應用程序的參數以提供優化的性能。

在有些情況下,硬件特性與軟件仿真的組合操作反而會比單純使用軟件仿真效率更低。例如,如果顯示設備驅動程序支持DirectDraw,但不支持帶縮放的Blit操作,在從視頻RAM頁面進行帶縮放的Blit操作時,將導致明顯的速度降低。這是因為有些視頻RAM的速度要比系統RAM慢,迫使在訪問視頻RAM頁面的時候,CPU進入等待狀態。如果你的應用程序使用硬件不支持的特性,某些時候,在系統RAM中創建頁面更為合適,這樣才能避免CPU訪問視頻RAM時的效率損失。

 

五、系統集成

下圖展示了DirectDraw,圖形設備接口(GDI),硬件抽象層(HAL)和硬件仿真層(HEL)四者之間的關系。

如上圖所示,DirectDraw對象與GDI位於同一層次,都通過一個設備相關的抽象層來直接訪問硬件設備。與GDI不同的是,DirectDraw會盡可能的利用硬件的加速特性。如果硬件不支持某特性,DirectDraw會使用HEL試圖將該特性進行軟件仿真。DirectDraw可以以設備環境(DC)的形式提供頁面內存,使得開發者可以使用GDI的函數操作頁面對象。

 

第二節控制級(Cooperative Levels

控制級描述了DirectDraw是怎樣與顯示設備相互作用的,它如何對系統事件產生反應。使用IDirectDraw2::SetCooperativeLevel函數可以設置DirectDraw的控制級。在很大程度上,開發者使用DirectDraw控制級來決定其應用程序是運行於全屏模式(具有獨占的訪問視頻RAM的特性),還是運行於窗口模式。不管怎樣,DirectDraw的控制級具有以下作用。

  • 允許DirectDraw使用Mode X分辨率。要得到更多的信息,請參閱“Mode X 和Mode 13顯示模式”。
  • 阻止DirectDraw釋放對顯示設備的獨占控制,或按Ctrl + Alt + Del以重新啟動計算機。(僅用於獨占模式)
  • 允許DirectDraw對應用程序進行最小化或最大化控制,作為對系統事件的反應。
  • 普通的控制級表明你的DirectDraw應用程序將以窗口的形式運行。在這種控制級下,你將不能改變顯示器分辨率,主頁面的調色板,或進行換頁操作。除此之外,你也不能夠調用那些會使視頻RAM或視頻RAM產生激烈反應的函數,例如:IDirectDraw2::Compact等。

當應用程序為全屏並且獨占的控制級時,你就可以充分的利用硬件資源了。在這種控制級下,你可以設置自定義和動態的調色板,改變顯示器分辨率,緊湊內存,和實現換頁操作等。獨占模式(也可稱為全屏模式)不會妨礙其它的應用程序分配頁面內存,也不會阻止它們使用DirectDraw或GDI的函數性。然而,它的確會阻止除了它自己(為活躍狀態時)以外的應用程序改變顯示模式或調色板。

因為DirectDraw應用程序可以具有多窗口,所以,在調用IDirectDraw2::SetCooperativeLevel設置控制級時,如果應用程序請求了DDSCL_NORMAL模式(表明應用程序以普通窗口的形式運行),則不需要提供一個指定窗口的句柄。給窗口句柄參數為NULL,所有的窗口的消息進程都可以同時被使用。

IDirectDraw2::SetCooperativeLevel函數在內部捆綁了消息進程和一個窗口句柄。如果IDirectDraw2::SetCooperativeLevel函數在一個進程中被調用了一次,那么,這個進程就會和一個窗口句柄捆綁起來。如果該函數在同一進程中再次被調用,並且指定了另一個合法的窗口句柄,那么會返回一個DDERR_HWNDALREADYSET錯誤。當DirectSound在設置控制級時指定了與DirectDraw不同的窗口時,有些應用程序也可能會返回這個錯誤值棗它們必須被設為同一個、頂層的窗口句柄。

 

第三節顯示模式

一、關於顯示模式

顯示模式指的是顯示器的當前設置,描述了顯示器的分辨率和位深度,這個信息通常是由顯示硬件從主頁面傳遞給顯示器的。顯示模式是由三個特征定義的:寬、高、位深度。例如,大多數的顯示器可以顯示寬為640像素、高為480像素的圖象,每一個像素的位深度是8。通常我們把這個顯示模式稱作640x480x8。隨着顯示模式的尺寸和位深度的增加,它所需要的視頻RAM也隨之增加。

有兩種顯示模式:調色板式和非調色板式。對於調色板式顯示模式來說,每一個像素的顏色值是以一個相關調色板的索引值來代表。顯示模式的位深度決定了調色板中可容納的顏色數量。舉例來說,在8位的調色板顯示模式中,每一個像素的值從0到255,該調色板可容納256個顏色入口。

非調色板式顯示模式,就象它的名稱所表示的那樣,不需要使用調色板。在這種顯示模式下,像素的位深度為16、24或32,每個像素分別占用2字節、3字節或4字節,用來描述像素的真實顏色。

主頁面、以及在換頁鏈中的所有頁面必須符合顯示模式的尺寸,位深度和像素格式(請參閱“像素格式”)。

 

二、測定支持的顯示模式

因為顯示硬件(包括顯示卡和顯示器)的不同,不是所有的顯示設備都支持所有的顯示模式。要測定某系統所支持的顯示模式,應該調用IDirectDraw2::EnumDisplayModes函數。設置正確的參數和標志符,IDirectDraw2::EnumDisplayModes可以列舉出該系統所支持的所有的顯示模式,或判斷是否支持用戶所指定的顯示模式。該函數的第一個參數,dwFlags,控制該函數的額外選項,在大多數情況下,你應該設置dwFlags為0以表明忽略額外的選項。第二個參數,lpDDSurfaceDesc,是一個DDSURFACEDESC結構的地址,包含了要被測定的顯示模式信息,通常,該參數被設為NULL,以列舉出該系統所支持的所有顯示模式。第三個參數,lpContext,是你想讓DirectDraw傳遞給其回調函數的一個指針,如果在回調函數中不需要任何數據,給該參數值為NULL。最后一個參數,lpEnumModesCallback,一個應用程序定義的回調函數的地址,在DirectDraw每列舉出一個顯示模式的時候,該回調函數將被調用。

在調用IDirectDraw2::EnumDisplayModes時所提供的回調函數必須符合EnumModesCallback函數的原型。每當找到一個硬件所支持的顯示模式的時候,DirectDraw調用該回調函數,並且傳遞了兩個參數。第一個參數是一個DDSURFACEDESC結構的地址,包含了一個支持的顯示模式的描述。第二個參數是一個應用程序定義的數據的地址,是在調用IDirectDraw2::EnumDisplayModes時所指定的第三個參數。

檢查DDSURFACEDESC結構中的值以獲得它所描述的顯示模式,關鍵的成員是dwWidth、dwHeight、和ddpfPixelFormat。dwWidth和dwHeight成員代表了顯示模式的長和寬,ddpfPixelFormat成員是一個DDPIXELFORMAT結構的地址,它包含了顯示模式的位深度信息。

DDPIXELFORMAT結構不僅包含了顯示模式的位深度,還可以告訴你該顯示模式是否使用調色板,以及像素格式。如果dwFlags成員包含了PALETTEINDEXED1、DDPF_PALETTEINDEXED2、DDPF_PALETTEINDEXED4、或DDPF_PALETTEINDEXED8標志,顯示模式的位深度為1、2、4或8,並且每個像素是一個相關調色板的索引。如果dwFlags成員包含了DDPF_RGB標志,那么該顯示模式是非調色板式的,並且它的位深度由DDPIXELFORMAT 結構中的dwRGBBitCount成員所提供。

 

三、設置顯示模式

你可以用IDirectDraw2::SetDisplayMode來設置顯示器的顯示模式。該函數的前四個參數用來描述要設置的顯示模式的尺寸、位深度以及顯示器的刷新率。函數的第五個參數是用來指定額外的選項,目前,唯一可用的標志是DDSDM_STANDARDVGAMODE,它將使顯示模式被設為Mode 13,而不是Mode X 320x200x8。如果你要設置另一種分辨率,位深度或Mode X模式,不要使用這個參數,並且將其設為0

盡管你可以指定所需要的顯示模式的位深度,但是你不能指定顯示硬件的像素格式。要測定顯示硬件用於該位深度的RGB位掩碼,在設置好顯示模式之后,調用IDirectDraw2::GetDisplayMode。如果當前的顯示模式不是基於調色板的,你可以從dwRBitMask、dwGBitMask和dwBBitMask中獲得掩碼值。

要正確的測定red、green和blue的所在的位,請參閱“DirectDraw參考手冊”中的“像素格式掩碼”。

顯示模式可以由多於一個的應用程序改變,只要它們共享同一塊顯卡。只有當應用程序擁有對DirectDraw對象獨占的訪問,你才可以改變顯示模式的位深度。當顯示模式被改變的時候,所有的DirectDrawSurface對象將丟失它們的頁面內存,並且對任何操作不起反應。這時,一個頁面的內存必須被重新分配,調用IDirectDrawSurface3::Restore函數。

必須重聲的是:DirectDraw的獨占模式並不阻止其它的應用程序分配DirectDrawSurface對象對顯示模式或調色板的訪問。

 

四、還原顯示模式

在應用程序結束的時候,你可以明確的調用IDirectDraw2::RestoreDisplayMode函數,使顯示器還原到原始的顯示模式。如果你使用的是IDirectDraw2::SetDisplayMode函數來改變顯示模式,並且應用程序具有獨占的控制級,那么,當重新設置控制級為普通時,原始的顯示模式會自動還原。如果你使用的是IDirectDraw::SetDisplayMode函數,那么你必須明確的調用RestoreDisplayMode以還原顯示模式。

 

五、ModeXMode 13顯示模式

DirectDraw同時支持Mode 13和Mode X顯示模式。Mode 13是一種線性不可換頁的320x200x8的基於調色板的顯示模式,因為它的16進制BIOS模式編號是13,而被廣泛的稱之為Mode 13模式。要得到更多關於它的資料,請參閱“Mode 13的支持”。Mode X是從標准的VGA Mode 13模式演化而來的。通過使用VGA顯示適配器的EGA多圖象平面系統,它允許開發者使用最多可達256K字節的視頻RAM(而Mode 13僅為64K)。

在Windows 95系統上,DirectDraw為所有的顯示卡提供了兩種Mode X模式:320x200x8和320x240x8。某些顯卡同樣也支持線性低分辨率模式。在這種模式中,主頁面可以被鎖定和直接訪問,這在Mode X模式中是不可能的。

應用程序在調用IDirectDraw2::SetCooperativeLevel函數時,只有使用了DDSCL_ALLOWMODEX、DDSCL_FULLSCREEN、和DDSCL_EXCLUSIVE標志符,才能使用Mode X模式。如果沒有指定DDSCL_ALLOWMODEX標志符,IDirectDraw2::EnumDisplayModes將不會列舉出Mode X模式,並且調用IDirectDraw2::SetDisplayMode函數以請求一個Mode X模式,將會失敗。

Windows 95和Windows NT不直接支持Mode X模式,因此,當你的應用程序處於Mode X模式時,你將無法使用IDirectDrawSurface3::Lock或IDirectDrawSurface3::Blt以鎖定或Blit到主頁面。你同樣也不能對主頁面,或GDI的屏幕設備環境使用IDirectDrawSurface3::GetDC函數。Mode X模式是在DDSCAPS結構中由DDSCAPS_MODEX標志符指定的,該結構是DDSURFACEDESC結構的一部分(DDSURFACEDESC結構是由IDirectDrawSurface3::GetCaps和IDirectDraw2::EnumDisplayModes函數返回的)。

目前,Windows NT還不能支持Mode X模式和某些線性低分辨率模式。

 

六、對高分辨率和真彩色的支持

DirectDraw支持顯示設備驅動所支持的所有屏幕分辨率和色彩位深度。DirectDraw允許應用程序改變顯示模式到計算機顯示驅動所支持的任何一個模式,包括24或32位色彩模式(也稱為真彩色)。

DirectDraw同樣也支持對真彩色頁面的硬件仿真層(HEL)的Blit操作。如果顯示設備驅動支持這些分辨率的Blit操作,那么硬件Blitter(位塊傳送器)將被用來進行視頻RAM對視頻RAM的Blit操作。否則,HEL將被用來完成此項操作。

       Windows 95和Windows NT允許用戶指定它們所使用的顯示器類型。DirectDraw核對已知的顯示模式與已安裝的顯示器所限制使用的顯示模式。如果DirectDraw發現所請求的模式與顯示器不兼容,對IDirectDraw2::SetDisplayMode函數的調用失敗。當你調用IDirectDraw2::EnumDisplayModes函數時,只有顯示器支持的模式才可以被列舉出來。

 

第四節 DirectDraw對象

該節包含了關於DirectDraw對象的信息,以及如何通過IDirectDraw或IDirectDraw2接口對該對象進行操作。

 

一、什么是DirectDraw對象?

DirectDraw對象是所有DirectDraw應用程序的核心,並且與Direct3D應用程序形成一個整體。它是你要創建的第一個對象,通過它,你可以創建所有其它相關的對象。典型的,通過調用DirectDrawCreate函數可以創建一個DirectDraw對象,它代表了IDirectDraw接口。如果你想使用該接口的另一個更高級的版本(比如:IDirectDraw2接口),以獲得更加優秀的性能,你可以請求獲得該接口。應注意的是,你可以創建若干個DirectDraw對象,每一個都代表了系統已安裝的顯示設備。

DirectDraw對象代表顯示設備,並且可以利用硬件的加速特性。如果DirectDraw對象所實例化的顯示設備具有硬件加速,則該對象是硬件加速的。DirectDraw對象可以創建三種對象:DirectDrawSurface(頁面)、DirectDrawPalette(調色板)、和DirectDrawClipper(裁剪器)創建這些對象的函數分別是:IDirectDraw2::CreateSurface、IDirectDraw2::CreatePalette和IDirectDraw2::CreateClipper。

每次,多於一個的DirectDraw對象可以被實例化。最簡單的例子是在Windows 95系統上使用兩台顯示器。盡管Windows 95並不支持雙顯示器,但為每一種顯示驅動程序配置一個DirectDraw硬件抽象層(HAL)是可能的。當缺省的DirectDraw對象被實例化時,Windows 95和GDI將使用它所認識的顯示驅動程序。Windows 95和GDI不認識的顯示驅動程序可與另外一個設備相匹配,獨立的DirectDraw對象必須通過第二個顯示驅動程序的全局唯一標志符(GUID)來創建。這個GUID可由DirectDrawEnumerate函數獲得。

DirectDraw對象管理它所創建的所有對象。它控制缺省的調色板(如果主頁面是8位色彩模式)、缺省的關鍵色,和硬件顯示模式。它跟蹤哪些資源已經被分配了,以及哪些資源正有待分配。

 

二、IDirectDraw2接口的新特性?

IDirectDraw2接口擴展了原先IDirectDraw接口的函數性:增加了一個IDirectDraw2::GetAvailableVidMem函數。該函數可以詢問所有可用的視頻RAM容量值,和當前可以為某種指定類型的頁面所用的空余視頻RAM容量值。

DirectX使用COM模型表明,可以通過提供新的接口而給舊的接口加入新的函數特性。在DirectX3的版本中,IDirectDraw2接口取代了原先的IDirectDraw接口。這個新的接口可以通過調用IDirectDraw::QueryInterface方法來獲得,如下例所示:

……

//*********************************************************************

//本例程片段演示如何創建一個IDirectDraw2接口.

//*********************************************************************

LPDIRECTDRAW lpDD;

LPDIRECTDRAW2 lpDD2;

//首先創建一個IDirectDraw接口

ddrval = DirectDrawCreate(NULL, &lpDD, NULL);

if(ddrval != DD_OK)

return;

ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_NORMAL);

if(ddrval != DD_OK)

return;

//獲得新的接口

ddrval = lpDD->QueryInterface(IID_IDirectDraw2, (LPVOID *)&lpDD2);

if(ddrval != DD_OK)

return;

ddscaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

ddrval = lpDD2->GetAvailableVidMem(&ddscaps, &total, &free);

if(ddrval != DD_OK)

return;

       這個子演示了用C++創建一個IDirectDraw接口的方法,然后再調用IDirectDraw::QueryInterface方法來創建一個IDirectDraw2接口。這個新接口包含了IDirectDraw2::GetAvailableVidMem函數,而試圖從IDirectDraw接口調用該函數將會在編譯的時候導致一個錯誤。

IDirectDraw2::GetAvailableVidMem是唯一一個被加入IDirectDraw接口的新方法。而且,IDirectDraw2::SetDisplayMode 和 IDirectDraw2::EnumDisplayModes,這兩個方法被修改和擴展。

IDirectDraw::SetCooperativeLevel 和 IDirectDraw::SetDisplayMode之間的相互關系與 IDirectDraw2::SetCooperativeLevel 和 IDirectDraw2::SetDisplayMode之間的相互關系有一些改變。如果你使用舊的IDirectDraw接口,而且應用程序通過調用設置有DDSCL_EXCLUSIVE標志的IDirectDraw::SetCooperativeLevel取得了獨占(全屏)顯示模式,用IDirectDraw::SetDisplayMode來改變模式,再調用設置有DDSCL_NORMAL標志的IDirectDraw::SetCooperativeLevel來釋放獨占模式,原始的顯示模式不會被還原。除非應用程序明確的調用IDirectDraw::RestoreDisplayMode方法或DirectDraw對象被銷毀時,原始的顯示模式才能恢復。然而,如果你使用新的IDirectDraw2接口,然后按着與上面同樣的方法,當DirectDraw對象失去獨占模式時,顯示器的原始顯示模式將會自動被恢復。

因為有些接口可能會因新接口的發布而該動,混合使用一個接口和它的替代者的方法(比如IDirectDraw 與IDirectDraw2)可以導致意想不到的錯誤。你必須只使用某接口的同一個版本的函數或方法。

 

三、單進程的多DirectDraw對象

DirectDraw允許一個進程在需要的時候,可以任意多次的調用DirectDrawCreate函數。每次調用后,返回一個唯一的與設備無關的接口。每一個DirectDraw對象可以隨心所欲的使用;在對象與對象之間沒有相互依賴的關系。每個對象的行為就象它是由一個唯一的進程創建的。

因為DirectDraw對象之間是不相互依賴的,由一個特定的DirectDraw對象創建的DirectDrawSurface、DirectDrawPalette、和DirectDrawClipper對象不應該與其它的DirectDraw對象一起使用,因為這些對象會在它的DirectDraw對象銷毀時而自動被釋放。如果它們與其它的DirectDraw對象一起使用,調用它們的函數將在它們被銷毀時退出。

例外的是,DirectDrawClipper對象是由DirectDrawCreateClipper函數創建的。這些對象與任何特定的DirectDraw對象是沒有關系的,可以與一個或多個DirectDraw對象一起使用。

 

四、使用CoCreateInstance創建DirectDraw對象

除了用常規的DirectDrawCreate方法創建一個DirectDraw對象外,你還可以使用CoCreateInstance函數,再調用IDirectDraw2::Initialize來創建一個DirectDraw對象。以下的例程片段描述了這個方法的各步驟。

第一步、在程序的最開始調用CoInitialize來初始化COM對象,參數為NULL。

if ( FAILED( CoInitialize( NULL )))

return FALSE;

第二步、然后,調用CoCreateInstance和IDirectDraw2::Initialize來創建DirectDraw對象。

ddrval = CoCreateInstance( &CLSID_DirectDraw,

NULL, CLSCTX_ALL, &IID_IDirectDraw2, &lpdd );

在CoCreateInstance函數中,第一個參數CLSID_DirectDraw,是DirectDraw驅動對象類的類標志符;IID_IDirectDraw2參數指定了要創建的特定的DirectDraw對象;最后的lpdd參數接收創建的對象。如果調用成功,這個函數返回一個沒有初始化的對象。

第三步、在你使用這個DirectDraw對象之前,你必須調用IDirectDraw2::Initialize。在此之后,你就可以操作和釋放該對象,就象它是用DirectDrawCreate創建的一樣。如果在使用之前,你沒有調用IDirectDraw2::Initialize,將返回DDERR_NOTINITIALIZED的錯誤。

if( !FAILED ( ddrval ))

ddrval = IDirectDraw2_Initialize( lpdd, NULL );

第四步、在關閉應用程序之前,使用CoUninitialize來關閉COM。

CoUnitialize();

      

第五節頁面

該節包含了關於DirectDrawSurface對象的信息.

 

一、頁面的基本概念

1、什么是頁面?

頁面,或被我們稱作DirectDrawSurface對象,代表了內存里的一個連續的線性的數據區。這個數據區可以被代表顯示硬件的DirectDraw對象所識別和確認。通常,DirectDrawSurface對象被置於顯卡上的視頻RAM中,而這並不是絕對的。除非明確的指定是在視頻RAM還是系統RAM中創建DirectDrawSurface對象,DirectDraw可以將其放置在其中任一位置,條件是這樣可以獲得最佳性能。

DirectDrawSurface對象可以從顯卡上的特效處理器上獲得好處,不僅僅是通常意義上的加快處理速度,而是可以與系統CPU並行工作,以達到最優的效率和速度。

調用IDirectDraw2::CreateSurface函數可以創建若干類型的DirectDrawSurface對象,包括最簡單的單頁面對象,復雜的由若干個頁面組成的換頁鏈,以及三維頁面等等。CreateSurface函數創建我們所請求的頁面或換頁鏈,並且返回指向主頁面的IDirectDrawSurface接口的指針,通過該接口可以暴露DirectDrawSurface對象的函數性。如果你想使用該接口的較高級的版本,如IDirectDrawSurface3,你也可以詢問系統並且得到它。

IDirectDrawSurface3接口通過Blit函數可以使你間接的訪問頁面內存,例如:IDirectDrawSurface3::BltFast函數。DirectDrawSurface對象可以創建Windows的GDI設備環境句柄(HDC),這樣,就可以允許使用Win32的API函數來訪問代表DirectDrawSurface對象的頁面。GDI識別這些HDC(設備環境句柄),如果它們存在於視頻RAM中,那么就可以獲得硬件的加速特性。除此之外,你還可以使用IDirectDrawSurface3接口的函數直接訪問頁面內存。例如:可以使用IDirectDrawSurface3::Lock函數鎖定頁面內存,並且獲得指向該頁面上相應區域(用戶指定的矩形區域)的內存區的地址。視頻RAM上的地址可以指向可見的禎緩存(存儲了當前顯示畫面的緩沖區,也稱作主頁面),也可以是不可見的緩存(離屏頁面或覆蓋頁面)。不可見的緩存通常被置於視頻RAM中,但是如果是受硬件限制或DirectDraw正以仿真模式運行,它也可以被置於系統RAM中。IDirectDrawSurface3接口還擴展了另外一些函數,比如可以用來設置或獲得調色板的函數,專門用於某特定類型頁面的函數(如換頁鏈或覆蓋頁面)。

從下面這個例圖中,你可以看到所有的DirectDrawSurface頁面對象都是由DirectDraw對象創建的,並且與調色板協同工作。盡管每一個頁面對象都可以被分配一個調色板,除了像素格式的位深度小於等於8的主頁面以外,調色板並不總是必須的。

 

2、頁面接口

前面已經提到過,DirectDrawSurface對象是通過IDirectDrawSurface、IdirectDrawSurface2和IDirectDrawSurface3接口來暴露其函數性的。接口的每一個新的版本與舊的版本相比,除了提供所有原有的函數並且擴充其功能之外,還提供了一些新的函數。

三種接口中,IDirectDrawSurface接口是最早的一個版本,當你調用IDirectDraw2::CreateSurface函數時,系統會為你缺省的創建一個該接口的頁面對象。要利用新版接口的函數性,你必須通過調用QueryInterface函數來詢問是否存在新版本,並獲得它。下面的例程為你展示了這是怎樣完成的。

LPDIRECTDRAWSURFACE lpSurf;

LPDIRECTDRAWSURFACE2 lpSurf2;

//填充頁面結構

memset(&ddsd, 0, sizeof(ddsd)); //調用Win32 API函數清空ddsd結構

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DSAPS_SYSTEMMEMORY;

ddsd.dwWidth = 10;

ddsd.dwHeight = 10;

//創建頁面,該頁面使用IDirectDrawSurface接口

ddrval = lpDD2->CreateSurface(&ddsd, &lpSurf, NULL);

if(ddrval != DD_OK)

return;

//詢問IDirectDrawSurface3接口

ddrval = lpSurf->QueryInterface( IID_IDirectDrawSurface3, (LPVOID *)&lpSurf2);

if(ddrval != DD_OK)

return;

//調用IDirectDrawSurface3接口特有的函數

ddrval = lpSurf2->PageLock(0);

if(ddrval != DD_OK)

return;

ddrval = lpSurf2->PageUnlock(0);

if(ddrval != DD_OK)

return;

       上面的例子通過調用QueryInterface函數(指定IID_IDirectDraw2引用標志符)獲得一個DirectDrawSurface對象的IDirectDrawSurface3接口。要得到IDirectDrawSurface3接口,使用IID_IDirectDrawSurface3引用標志符即可。

 

3、寬度(Width)和寬距(Pitch)

如果你的應用程序要寫視頻RAM,內存中的位圖並不需要占據連續的內存塊。在這種情況下,一條線的width和pitch含義是不同的。width是指內存中位圖的一條線的開始和結束位置的內存地址之差。這個距離只代表了內存中位圖的寬度,它不包括位圖中到達下一條線開始位置所需要的任何額外的內存。pitch是指內存中位圖的一條線到下一條線開始位置的內存地址之差。

對矩形內存來說,比如,視頻RAM的pitch將包括位圖的寬度加上一部分緩存。下面的例圖表示了矩形內存中width和pitch的區別。

在這個例圖中,前台緩存和后台緩存大小都是640x480x8,高速緩存是384x480x8。要到達下一條線的地址,你必須在640后加上384,得到1024,這就是下一條線的地址。

因此,當直接向頁面內存中着色時,一般用IDirectDrawSurface3::Lock(或IDirectDrawSurface3::GetDC)方法返回的pitch值。不要認為pitch只是基於顯示模式的。如果你的應用程序在某些顯示器上發生顯示混亂,這多半是因為pitch使用錯誤造成的。

 

4、關鍵色

DirectDraw支持帶源或目標關鍵色的Blit操作和覆蓋頁面。這個關鍵色可以是單個的顏色值,也可以是一個顏色范圍。要得到關於關鍵色的詳細介紹,請參閱前一章的“關鍵色”一節。通過調用IDirectDrawSurface3::SetColorKey函數,可以為一個頁面設置一個關鍵色。

源關鍵色(Source color key)指定了一個顏色或一個顏色范圍,在Blit過程中,不被復制,或在覆蓋頁面中,對目標層來說是不可見的。目標關鍵色(Destination color key)指定了一個顏色或一個顏色范圍,在Blit過程中,將被替換,或在覆蓋頁面中,將被目標層所覆蓋。源與目標關鍵色的一個顯著的區別就是:源關鍵色指定了在源頁面上什么是可以讀和什么是不可以讀的;目標關鍵色指定了在目標頁面上,什么是可以寫和什么是不可以寫的。如果目標頁面有關鍵色,則只有那些符合關鍵色的像素可以被改變(在Blit操作中),或被覆蓋(在覆蓋頁面中)。

除了與Blit相關的關鍵色之外,覆蓋頁面還可以使用覆蓋關鍵色。要得到更多信息,請參閱“覆蓋關鍵色”。

有些硬件只支持YUV像素數據的顏色范圍。YUV數據通常用於視頻顯示的像素格式,並且其透明背景不是一個特定顏色而導致在數值轉換過程中發生錯誤。所以,只要可能,就應該將數據寫到一個特定的透明顏色上,而不管它是什么像素格式。

關鍵色是按頁面的像素格式指定的。如果一個頁面是調色板格式,關鍵色是以一個調色板索引或一組調色板索引指定的。如果頁面的像素格式是按FOURCC代碼指定的,描述了一個YUV格式,YUV關鍵色是由DDCOLORKEY結構的dwColorSpaceLowValue 和dwColorSpaceHighValue成員的低三位字節指定的。最低的字節包含V數據,下一個包含U數據,第三個包含Y數據。IDirectDrawSurface3::SetColorKey的dwFlags參數指定了關鍵色是用在Blit操作中還是覆蓋頁面中,以及它是源還是目標關鍵色。以下是一些合法的關鍵色的例子。

8位調色板模式:

//調色板登錄項26是關鍵色。

dwColorSpaceLowValue = 26;

dwColorSpaceHighValue = 26;

24位真彩模式:

//(255,128,128)顏色是關鍵色

dwColorSpaceLowValue = RGBQUAD(255,128,128);

dwColorSpaceHighValue = RGBQUAD(255,128,128);

FourCC YUV模式:

//只要Y在100和110之間,並且U或V在50和55之間的的任何一個YUV顏色為透明。

dwColorSpaceLowValue = YUVQUAD(100,50,50);

dwColorSpaceHighValue = YUVQUAD(110,55,55);

      

5、像素格式

像素格式規定了頁面內存中的每個像素的數據是怎樣進行編碼的。DirectDraw使用DDPIXELFORMAT結構來描述各式各樣的像素格式(請參閱“DirectDraw參考手冊中關於該結構的幫助”)。DDPIXELFORMAT結構中的成員包含了各種像素格式相互區別的以下幾個顯著的特點:

  • 像素格式是基於調色板的還是非調色板式的
  • 如果是非調色板式,像素是RGB,還是YUV格式
  • 位深度
  • 位掩碼

通過調用IDirectDrawSurface3::GetPixelFormat函數,你可以獲得關於當前頁面的像素格式的信息。

 

二、創建頁面

DirectDrawSurface對象代表了一個頁面,調用IDirectDraw2::CreateSurface函數可以創建一個DirectDrawSurface對象,也可以同時創建由若干個頁面組成的復雜頁面結構,最典型的就是換頁鏈。調用該函數時,需要提供要創建的頁面的描述,如頁面的尺寸、是單個頁面還是復雜頁面、所采用的像素格式(如果頁面將不使用索引調色板)等等。所有這些描述信息存儲在一個DDSURFACEDESC結構中,在調用CreateSurface函數時必須提供。如果硬件不支持所請求的頁面特性,或是要創建的頁面已經存在,則該函數返回一個錯誤。

要創建單個的頁面或若干個頁面其實是一項並不復雜的工作,只需要少許的幾行代碼即可。主要有以下四種類型的頁面可以被創建:

  • 創建主頁面
  • 創建離屏頁面
  • 創建復雜頁面和換頁鏈
  • 創建超寬頁面

       在缺省的情況下,DirectDraw試圖在本地的視頻RAM中創建頁面。如果恰好沒有足夠的本地(local)視頻RAM來容納要創建的頁面的話,DirectDraw將試圖使用非本地(non-local)的視頻RAM(在某些裝備了AGP設備的系統上),如果仍舊無法實現,那么DirectDraw將只能將其創建於系統RAM中。當然,在調用CreateSurface函數時,你也可以在相關的DDSCAPS結構中指定適當的標志符向DirectDraw明確的表明你想將頁面置於哪種類型的內存中。

 

1、創建主頁面

主頁面(primary surface)代表的是在顯示器的當前可見屏幕,它在頁面描述中具有PRIMARYSURFACE標志符。對於每一個DirectDraw對象來說,你只可能擁有一個主頁面。

主頁面對用戶來說是可見的。當你創建一個主頁面時,實際上,你創建的這個DirectDrawSurface對象,訪問的是由GDI正在使用的已經可見的頁面(即顯示屏幕),主頁面的大小以及像素格式暗中符合當前顯示器的顯示模式。因此,盡管創建所有其它類型的頁面要求填充DDSURFACEDESC結構的dwHeight 和 dwWidth值以及像素格式,而創建主頁面時一定不能自己指定它們,甚至你知道它們與當前屏幕是同樣大小,否則,CreateSurface函數將調用失敗,並且返回DDERR_INVALIDPARAMS。要創建一個主頁面,DDSURFACEDESC 結構(以下為ddsd )的成員是如下填充的。

DDSURFACEDESC ddsd;

ddsd.dwSize = sizeof(ddsd);

//告訴DirectDraw哪些成員是可用的。

ddsd.dwFlags = DDSD_CAPS;

//請求一個主頁面

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

       然后,就可以調用CreateSurface函數並且提供了這個頁面描述,如果函數調用成功,可以返回一個指向主頁面的指針。如果你想要得到關於這個主頁面的尺寸和像素格式信息,應該調用IDirectDrawSurface3::GetSurfaceDesc函數。相關信息,請參閱“顯示模式”。

 

2、創建離屏頁面

離屏頁面(off-screen surface),通常被用來存儲位圖,用於后來的將位圖圖象Blit到主頁面或后台緩存上。因為離屏頁面是一個相互獨立的頁面,不與任何對象產生隸屬關系,所以你必須指定你所要創建的離屏頁面的大小,這是通過在DDSURFACEDESC結構中包含進DDSC_WIDTH和DDSD_HEIGHT標志符,並且給dwWidth和dwHeight成員填充正確的參數完成的。除此之外,你還必須包含進DDSCAPS_OFFSCREENPLAIN標志符以表明創建的是一離屏頁面。

在缺省的情況下,DirectDraw將會在視頻RAM中創建離屏頁面,除非視頻RAM容量不夠,那么,DirectDraw將會把離屏頁面置於系統RAM中。你可以在DDSURFACEDESC結構的dwCaps成員中包含進DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY標志符,以明確的表明你希望將頁面置於何處。如果DirectDraw不能滿足你所提供的要求,CreateSurface函數將調用失敗,並且返回錯誤。

下面面的例程展示了要創建一個離屏頁面,DDSURFACEDESC 結構(以下為ddsd )的成員是如何填充的。

DDSURFACEDESC ddsd;

ddsd.dwSize = sizeof(ddsd);

//告訴DirectDraw哪些成員是可用的

ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;

//請求一個離屏頁面,大小為100x100

//(這假定了要創建的離屏頁面的像素格式將符合主頁面的像素格式)

ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

dwHeight = 100;

dwWidth = 100;

       除此之外,你也可以創建一個頁面的像素格式與主頁面不同的離屏頁面。然而,在這種情況下,有一個缺點棗該離屏頁面將只能被限制於系統RAM中。下面的例程片段展示了如何准備DDSURFACEDESC結構的各成員,以用於創建一個8位的調色板式頁面(假定當前的顯示模式不是8位格式)。

ZeroMemory(&ddsd, sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |

DDS_PIXELFORMAT;

ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |

DDSCAPS_SYSTEMMEMORY;

ddsd.dwHeight = 100;

ddsd.dwWidth = 100;

ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);

ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;

// 設置頁面的位深度為8,但是絕對不要設置任何RGB mask值,

// 因為對調色板式頁面來說,該值一定是0。

ddsd.ddpfPixelFormat.dwRGBBitCount = 8;

       在DirectX以前的版本中,離屏頁面的寬度的最大值被限制於主頁面的寬度值之內。而在DirectX 5.0版中,你可以隨心所欲的創建任何寬度的離屏頁面,如果顯示硬件能夠承受的話。在請求一個超寬離屏頁面時,要小心的是,如果顯卡上的內存不能夠容納該頁面,頁面將被置於系統RAM中。如果你明確的指定在視頻RAM中創建超寬頁面,而硬件又無法承受的話,調用失敗。要得到更多關於超寬頁面的信息,請參閱“創建超寬頁面”。

 

3、創建復雜頁面和換頁鏈

除了上面介紹的由單獨的一個頁面組成的主頁面和離屏頁面之外,你還可以創建由若干個頁面組成的復雜頁面(complex surfaces),它同樣也是由一步調用IDirectDraw2::CreateSurface函數所創建的。如果你在頁面描述中設置了DDSCAPS_COMPLEX標志符,那么在調用CreateSurface函數后,DirectDraw除了創建你所明確要創建的頁面之外,還將暗中的為你創建一個或多個附加頁面。對復雜頁面的管理與對單頁面的管理基本上是沒有區別的:一步調用IDirectDraw::Release函數將釋放復雜頁面中所有的頁面,並且一步調用IDirectDrawSurface3::Restore函數將恢復所有頁面。然而,這些暗中創建的頁面不能被脫離,即解除隸屬關系,要得到更多的信息,請參閱“DirectDraw參考手冊”中關於IDirectDrawSurface3::DeleteAttachedSurface函數的幫助。

你所能創建的最常用的復雜頁面之一就是換頁鏈(flipping chain)。通常,一個換頁鏈是由一個主頁面以及隸屬於它的若干個后台緩存組成。DDSCAPS_FLIP標志符表明頁面是一個換頁鏈的一部分。創建一個換頁鏈的頁面描述中,同樣必須包含進DDSCAPS_COMPLEX標志符。

下面的例程片段展示了要創建一個換頁鏈結構,如何填充頁面描述的各成員。

DDSURFACEDESC ddsd;

ddsd.dwSize = sizeof(ddsd);

// 告訴DirectDraw哪些成員是可用的

ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

// 請求一個主頁面,以及后台緩存數量為1

ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDS      APS_PRIMARYSURFACE;

ddsd.dwBackBufferCount = 1;

       上面的例程構造了一個雙緩沖區換頁(double-buffered flip)環境:單步調用IDirectDrawSurface3::Flip函數可以交換主頁面和后台緩存。如果給DDSURFACEDESC 結構的dwBackBufferCount成員設置了2,將會創建兩個后台緩存,在每次調用Flip函數時,主頁面將在三個頁面間循環,這就構成了一個三緩沖區換頁(triple-buffered flip)環境。

 

4、創建超寬頁面

DirectDraw允許你在顯卡內存中創建超寬離屏頁面(頁面的寬度大於主頁面)。這只在顯示設備支持超寬頁面的情況下才能實現。

要判斷DirectDraw是否支持超寬頁面,調用IDirectDraw2::GetCaps函數,檢查你所提供的第一個DDCAPS結構的dwCaps2成員中是否存在DDCAPS2_WIDESURFACES標志。如果存在,表明你的DirectDraw支持超寬頁面。

如果你試圖在視頻RAM中創建一個寬度大於主頁面的頁面,而DDCAPS2_WIDESURFACES標志並不存在,函數調用將失敗,並且返回一個DDERR_INVALIDPARAMS錯誤。

超寬頁面通常被系統RAM頁面、視頻端口頁面、和可執行緩存所支持。

      

三、換頁

DirectDraw里的任何頁面都可以構造為換頁頁面(Flipping surface)。一個換頁頁面是位於內存里的任何一個可以在前台緩存(front buffer)和后台緩存(back buffer)之間交換的頁面,這個換頁環境就是我們所稱的換頁鏈(flipping chain)。通常,前台緩存指的就是主頁面,當然,這並不是絕對的。

典型的,當你調用IDirectDrawSurface3::Flip函數以請求一次換頁操作,指向主頁面和后台緩存的指針相互交換。這就是說,換頁的操作,是通過交換顯示設備用來代表頁面內存的指針,而不是通過相互復制頁面的實際內存來完成的。但是,也有例外的時候,那就是當DirectDraw以仿真的方式進行換頁操作時,在這種情況下,它所做的就是簡單的相互復制頁面內存。只有在后台緩存不能容納進視頻RAM,或硬件不支持DirectDraw的時候,DirectDraw才會以仿真的方式進行換頁操作,當然,這只是極其少見的情況。

當換頁鏈中包含了一個主頁面和一個以上的后台緩存時,在換頁操作中,指向它們的指針將按前后順序依次轉換。如下圖所示:

隸屬到DirectDraw對象上的其它類型的頁面,只要不是換頁鏈中的一部分,在換頁過程中都不會受到任何影響。

請牢記,DirectDraw進行換頁,是通過交換指向DirectDrawSurface對象的指針。而不是交換DirectDrawSurface對象本身。這意味着,在任何類型的換頁方案中,如果你想將圖象Blit到后台緩存,你所使用的始終是同一個DirectDrawSurface對象,而不用去考慮原先的后台緩存已經換頁到哪兒了。同樣的,你應該始終使用主頁面作為Flip函數的調用者,以完成一次換頁操作,而不用去管最開始的主頁面換頁到哪兒了。

當換頁對象是可見的頁面,比如主頁面換頁鏈或一個可見的覆蓋頁面換頁鏈,進行換頁的Flip函數與系統CPU是異步執行的。這就是說,在這些可見的頁面上,調用Flip函數,它只是簡單的告訴顯示硬件該進行換頁了,並不需要等待換頁操作在硬件設備中實際完成后才返回。這是因為顯示硬件(顯示器)只有在完成一次垂直刷新后才能進行一次換頁。所以,Flip函數調用成功,並不意味着換頁已經完成,在實際的換頁操作進行之前,對即將成為主頁面的后台緩存是不能鎖定和進行Blit操作的,如果在這時調用以下這些函數,調用將失敗,並且返回DDERR_WASSTILLDRAWING的錯誤,如IDirectDrawSurface3::Lock、IDirectDrawSurface3::Blt、IDirectDrawSurface3::BltFast和IDirectDrawSurface3::GetDC。但是,對於三緩沖區換頁環境,最后一個后台緩存仍是可用的。

要讓Flip函數成為與系統CPU同步的操作,在調用時指定DDFLIP_WAIT標志即可。

 

四、頁面丟失

當代表頁面內存的DirectDrawSurface對象被不得已的釋放時,與該對象相關聯的頁面內存也會被釋放。當一個DirectDrawSurface對象丟失其頁面內存的時候,它的許多函數將返回DDERR_SURFACELOST,並且不進行任何其它操作。

頁面可能被丟失是因為:.顯示設備(顯示器)顯示模式的改變,或另一個應用程序獲得了對顯卡的獨占訪問模式,並且釋放了顯卡上當前被分派其它應用程序的所有頁面內存。對頁面調用IDirectDrawSurface3::Restore方法可以為這些丟失了內存的頁面重新分配內存,並且將這些內存與DirectDrawSurface對象聯系上。重建內存並不會使以前存在於該頁面上的圖象重新顯現出來,因此,如果你的頁面丟失了其內存,在調用Restore函數重建之后,必須親手重新繪制所有的圖象。

要得到更多資料,請參閱“設置顯示模式”。

 

五、釋放頁面

與所有的COM接口一樣,在你不再需要某頁面的時候,你可以通過調用Release方法釋放它。

每一個單獨創建的頁面必須逐個的明確的釋放掉。然而,如果頁面是通過單步調用IDirectDraw2::CreateSurface或IDirectDraw::CreateSurface函數創建一個多頁面結構(例如一個換頁鏈)時暗中形成的,那么,你只需要明確的釋放前台緩存就可以了。在這種情況下,所有的后台緩存都被暗中的釋放了,指向它們的指針將不再合法。

 

六、更新頁面屬性

你可以通過調用IDirectDrawSurface3::SetSurfaceDesc函數來更新一個現存頁面的屬性。有了這個函數,你可以更改頁面的像素格式,還可以使該DirectDrawSurface對象指針重定位,使其指向一塊應用程序已經明確分配了的系統RAM。這是很有用的,因為它使得你的頁面可以直接使用一個已經存在的緩沖區的數據,而不用進行復制操作。新的頁面內存是由客戶程序所分配,同樣的,這些內存也必須由客戶程序釋放掉。要得到更多關於如何使用SetSurfaceDesc函數的資料,請參閱“DirectDraw參考手冊”中關於此函數的幫助。

在調用IDirectDrawSurface3::SetSurfaceDesc函數時,lpddsd參數必須是一個DDSURFACEDESC結構的地址,描述了新的頁面內存並且提供了指向該內存的指針。在這個結構中,你只能設置dwFlags成員為反映了頁面內存的地址、大小、寬距、和像素格式的標志符。因此,dwFlags只能是以下標志符的集合:DDSD_WIDTH、DDSD_HEIGHT、DDSD_PITCH、DDSD_LPSURFACE、和DDSD_PIXELFORMAT。

在向DDSURFACEDESC結構中填充數據之前,你必須為新的頁面分配內存。你所分配的內存的大小是非常重要的,它不僅要能容納滿足頁面的長和寬所需要的內存,還必須能夠容納頁面的寬距,寬距必須是WORD(8位)的倍數。應該注意的是,寬距是以字節為單位,而非像素。

在向DDSURFACEDESC結構中填充數據的時候,lpSurface成員是一個指向你剛分配的內存的指針,並且dwHeight和dwWidth成員描述了頁面的大小(以像素為單位)。如果你指定了頁面的大小,你還必須填充lPitch成員以反映頁面寬距的大小。Pitch必須是DWORD的倍數。同樣的,如果你指定了寬距,你還必須為其指定一個寬度值。最后,ddpfPixelFormat成員描述了頁面的像素格式。如果你沒有給這些成員指定新的值,那么,SetSurfaceDesc函數將使用當前頁面的原始值,只有lpSurface成員是例外。

在使用IDirectDrawSurface3::SetSurfaceDesc方法的過程中,你還應當注意到這樣一些細節,當然,它們只是常識。舉例來說,DDSURFACEDESC結構的lpSurface成員必須是一個指向系統RAM的合法的指針(SetSurfaceDesc函數目前還不支持指向視頻RAM的指針)。同樣,dwWidth和dwHeight成員的值不能為0。最后一點,你不能為主頁面或換頁鏈中的任何頁面調用此函數。

你可以將同一塊內存設置給若干個DirectDrawSurface對象,但是,你必須注意到,這塊內存被所有的頁面對象所使用,它不會因為某一個頁面的釋放而被釋放掉。

不正確的使用SetSurfaceDesc函數將導致不可預知的行為。因為DirectDrawSurface對象不會釋放並不是它分配的頁面內存,因此,當頁面內存不再需要的時候,將其及時的釋放掉是你的責任。但是,不管怎樣,當SetSurfaceDesc函數被調用的時候,DirectDraw將釋放掉該頁面在創建的時候被暗中分配的原始的頁面內存。

 

七、直接訪問幀緩存

一個DirectDrawSurface對象允許應用程序通過調用IDirectDrawSurface3::Lock鎖定頁面以獲得對頁面內存的直接的訪問。當應用程序調用這個函數的時候,需要給lpDestRect參數提供一個指向RECT結構的指針,描述了頁面中你所想要直接訪問的矩形區域。如果應用程序需要訪問整個頁面,設置這個參數為NULL即可。兩個線程或進程可以同時鎖定同一個頁面上的若干個矩形區域,條件是這些矩形區域沒有相互重疊。

Lock函數調用成功的話,將填充一個DDSURFACEDESC結構,描述了你要正確的訪問頁面內存所需要的所有信息。如果頁面的像素格式與主頁面的不一樣,該結構中還包含了關於頁面的寬距(pitch)和像素格式的信息。當應用程序結束了對頁面內存的訪問,可以調用IDirectDrawSurface3::Unlock以解鎖頁面。

當你鎖定了一個頁面,你就可以對頁面內存中的數據進行直接的操作。以下介紹了一些小技巧,可以避免在頁面被鎖定的過程中,直接向頁面內存進行繪圖的時候發生的絕大多數一般的錯誤。

決不要假想頁面的寬距(pitch)為一恆定值,每次調用IDirectDrawSurface3::Lock函數的時候都要檢查返回信息中的寬距值。這個值的改變可以有各種各樣的原因,包括頁面內存在內存中的位置,顯卡的類型,甚至是DirectDraw引擎的版本。

確保你要進行Blit操作的目標頁面是沒有被鎖定的。如果對一個鎖定的頁面調用DirectDraw的Blit函數,調用將失敗,並且返回DDERR_SURFACEBUSY或DDERR_LOCKEDSURFACES錯誤。相似的,如果對視頻RAM中一個鎖定的頁面調用了GDI的Blit函數,調用也將失敗,但不會返回錯誤值。

盡量減少你的程序在IDirectDrawSurface3::Lock 和IDirectDrawSurface3::Unlock之間的執行活動。在一個頁面被鎖定的過程中,DirectDraw通常控制住Win 16鎖,於是使得對頁面內存的訪問得以安全的進行。Win 16鎖使對GDI和USER的訪問串行化,在Lock和UnLock函數的調用過程中,暫停執行Windows。IDirectDrawSurface3::GetDC函數暗中的調用了IDirectDrawSurface3::Lock以鎖定頁面,並且IDirectDrawSurface3::ReleaseDC函數暗中的調用了IDirectDrawSurface3::Unlock以使頁面解鎖。

對齊復制到顯示存儲器。Windows 95使用了一個頁錯誤處理器棗Vflatd.386文件,來為具有堆交換存儲器的顯示卡實現虛擬的平面楨緩沖區。該處理器允許這些顯示設備為DirectDraw提供線性楨緩沖區。如果復制是跨存儲堆的,非對齊復制到顯示存儲器會導致系統掛起。

鎖定頁面通常導致DirectDraw控制住Win 16鎖。在Win 16鎖被控制期間,所有其它的應用程序,包括Windows系統,都會暫停執行。因為這個原因,標准的調試器在此期間都不可能工作,但是只有內核(kenerl)調試器仍可以正常工作。

如果在你調用IDirectDrawSurface3::Lock函數的時候,對於該頁面的一次Blit操作還在進行之中,函數將立即返回一個錯誤值。要防止這種情況的發生,可以在調用Lock函數的過程中指定DDLOCK_WAIT標志,以表明該函數將等待,直到成功的獲得鎖定之后才返回。

 

八、使用非本地視頻RAM頁面

DirectDraw支持高級圖形端口(AGP)結構,所以它可以在非本地的視頻RAM中創建頁面。在裝備了AGP的系統中,如果本地的視頻RAM被耗盡,或用戶明確的請求非本地的視頻RAM,DirectDraw將使用非本地的視頻RAM,這依賴於當時的AGP執行模式。

目前,有兩種AGP結構的執行模式,這就是通常所說的“執行模式”(execute model)和“DMA模式(DMA model)”。在執行模式中,對於非本地的視頻RAM和本地的視頻RAM,顯示設備支持同樣的特性。因此,當你調用IDirectDraw2::GetCaps方法以獲取硬件特性的時候,在DDCAPS 結構的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成員中所包含的與Blit操作相關的標志符,與本地視頻RAM的那些標志符是一樣的。在執行模式中,如果本地的視頻RAM被耗盡,DirectDraw將自動的退回到使用非本地的視頻RAM,除非調用者明確的指定使用本地的視頻RAM。

在DMA模式執行過程中,對非本地視頻RAM的Blit操作和材質貼圖(texturing)的支持是有限度的。如果顯示設備使用DMA模式,當你詢問設備特性的時候,dwCaps2成員將會被設置了DDCAPS2_NNLOCALVIDMEMCAPS標志。DDCAPS結構中的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成員中所包含的Blit相關的標志描述了DirectDraw所支持的特性;這些特性通常是本地的視頻RAM頁面所支持的特性的一個子集。DMA模式下,當本地的視頻RAM被耗盡的時候,對於材質頁面,DirectDraw將自動的退回到使用非本地的視頻RAM,除非調用者明確的請求使用本地的視頻RAM。材質頁面是唯一的一種會被這樣處理的頁面,所有其它類型的頁面不能被創建在非本地的視頻RAM中,除非調用者明確的指定。

DMA執行模式對材質貼圖的支持,因非本地視頻RAM頁面的不同而不同。如果驅動程序支持從非本地視頻RAM頁面的材質貼圖,當你調用IDirect3DDevice2::GetCaps方法詢問3-D驅動程序的特性的時候,D3DDEVCAPS_TEXTURENONLOCALVIDMEM標志符將會被設置其中。

 

九、色彩和格式轉換

非RGB色彩空間的頁面格式是由四字符代碼(FOURCC codes)定義的。如果應用程序調用IDirectDrawSurface3::GetPixelFormat方法詢問頁面的像素格式,並且該頁面是一個非RGB頁面,那么DDPF_FOURCC標志符將會被設置,並且DDPIXELFORMAT結構中的dwFourCC成員的值是有效的。如果頁面的四字符代碼(FOURCC code)代表了一個YUV格式,那么DDPF_YUV標志符也將被設置,並且dwYUVBitCount、dwYBits、dwUBits、dwVBits、和dwYUVAlphaBits成員的值是有效的,它們可以被用來提取出頁面像素格式的信息。

如果當前的頁面是RGB格式,那么DDPF_RGB標志符將被設置,並且dwRGBBitCount、dwRBits、dwGBits、dwBBits、和dwRGBAlphaBits成員的值是有效的,它們可以被用來提取出頁面像素格式的信息。DDPF_RGB標志符可以與DDPF_FOURCC標志符一起使用,如果被描述的頁面是非標准的RGB格式。

在色彩和格式轉換過程中,兩套四字符代碼可以為應用程序所使用。一套代表了Blit硬件的特性,另一套代表了覆蓋硬件的特性。

要得到更多的信息,請參閱“DirectDraw參考手冊”中的“四字符代碼”部分的幫助。

 

十、覆蓋頁面

該節包含了關於DirectDraw支持覆蓋頁面的資料。

 

1)覆蓋頁面概覽

覆蓋頁面(Overlay),通常也被稱作重疊頁面或覆蓋層,是一種需要特定的硬件支持的頁面。覆蓋頁面通常被用於顯示實時視頻、視頻錄制、或靜止的位圖於主頁面之上,而不需要進行Blit操作到主頁面上或用任何方法改變主頁面的內容。覆蓋頁面完全是由硬件支持的;DirectDraw支持由顯示設備驅動程序報告的任何一種特性。DirectDraw不支持以軟件仿真的方式實現覆蓋頁面。

可以用一張透明的塑料片來比喻一個覆蓋頁面,你可以將它放在顯示器屏幕前,並且在上面進行繪圖。同樣的道理,當覆蓋頁面位於顯示器屏幕的上層時,你可以同時看到覆蓋頁面和主頁面的內容,當你將覆蓋頁面挪開時,主頁面的內容不會發生任何改動。實際上,正如前面提到的那樣,一個覆蓋頁面的執行機制與一張透明的塑料片基本上是一樣的。當你要顯示一個覆蓋頁面的時候,你要告訴設備驅動程序在哪兒和怎樣去顯示這個覆蓋頁面。當顯示設備在顯示器上繪制掃描線的時候,它會檢查主頁面上的每一個像素的位置,來判斷該處是否應該讓覆蓋頁面可見。如果是的話,顯示設備從覆蓋頁面上取得該處的像素顏色,並且顯示出來。整個過程如下圖所示:

通過這個方法,顯示適配器在顯示屏幕上產生了一個由主頁面和覆蓋頁面合成的圖象,並且在提供透明和縮放特效的同時,不用修改任何一個源頁面的內容。這個合成的頁面被注入視頻數據流中,直接顯示在屏幕上。因為這是一個懸空(on the fly-我們暫且這么稱呼它)的操作過程,並且像素的轉換是在硬件層中完成的,所以對用戶來說,在顯示覆蓋頁面的過程中,並沒有可以察覺的時間損失。除此之外,這個方法還允許將具有不同的像素格式的主頁面和覆蓋頁面合成起來。

你可以通過調用IDirectDraw2::CreateSurface函數創建一個覆蓋頁面,在相應的DDSCAPS結構中指定DDSCAPS_OVERLAY標志符。覆蓋頁面只能存在於視頻RAM中,所以,你還必須包含DDSCAPS_VIDEOMEMORY標志符。與創建其它類型的頁面一樣,通過包含進適當的標志符,你可以創建一個單獨的覆蓋頁面,或一個由若干個覆蓋頁面組成的換頁鏈。

 

2)DDCAPS結構中的重要成員和標志

通過調用IDirectDraw2::GetCaps方法,你可以獲得關於DirectDraw支持的覆蓋頁面特性的信息。該函數將描述了所有這些特性的信息填充到兩個DDCAPS結構中。

當報告硬件特性時,如果硬件對某特定類型的限制加以實施,設備驅動程序會在DDCAPS 結構的dwCaps成員中設置相應的標志符來表明。在獲得驅動程序的能力之后,檢查dwCaps成員中的標志符,以判斷硬件實施了哪些限制。DDCAPS結構中包含了九個描述了硬件對覆蓋頁面的限制信息的成員。下面的這張表列出了與覆蓋頁面相關的成員及其相應的標志符。

成員                                        標志符

      dwMaxVisibleOverlays       該成員總是可用的

      dwCurrVisibleOverlays         該成員總是可用的

      dwAlignBoundarySrc           DDCAPS_ALIGNBOUNDARYSRC

      dwAlignSizeSrc                   DDCAPS_ALIGNSIZESRC

      dwAlignBoundaryDest          DDCAPS_ALIGNBOUNDARYDEST

      dwAlignSizeDest                  DDCAPS_ALIGNSIZEDEST

      dwMinOverlayStretch           DDCAPS_OVERLAYSTRETCH

      dwMaxOverlayStretch          DDCAPS_OVERLAYSTRETCH

      dwMaxVisibleOverlays和dwCurrVisibleOverlays成員攜帶了硬件設備可以顯示的覆蓋頁面的最大個數,以及其中的多少個可以同時被顯示出來的數據。

除此之外,硬件設備將覆蓋頁面矩形的位置和大小限制反映在dwAlignBoundarySrc、dwAlignSizeSrc、dwAlignBoundaryDest、dwAlignSizeDest、和dwAlignStrideAlign成員中。這些成員中的值表明了在顯示覆蓋頁面的時候,你必須怎樣控制源和目標矩形的大小和位置。要得到更多的資料,請參閱“源和目標矩形,以及邊界和大小限制”。

同樣,硬件將縮放系數反映在dwMinOverlayStretch和dwMaxOverlayStretch成員中。要得到更多資料,請參閱“最小和最大縮放系數”。

 

3)源和目標矩形

要顯示一個覆蓋頁面,調用覆蓋頁面的IDirectDrawSurface3::UpdateOverlay函數,在dwFlags參數中指定DDOVER_SHOW標志。該方法需要你在lpSrcRect和lpDestRect參數中指定源和目標矩形。源矩形描述了將顯示在主頁面上的覆蓋頁面的區域。要請求該方法使用整個頁面,設置lpSrcRect參數為NULL即可。目標矩形描述了在主頁面上將被用來顯示覆蓋頁面的區域。

源和目標矩形不需要是同樣的大小。通常,你所設置的目標矩形是小於或者大於源矩形的,那么在顯示出覆蓋頁面的時候,硬件將會自動的將其進行縮小或放大。

要成功的顯示覆蓋頁面,你可能需要調整源矩形和目標矩形的大小和位置。這是否必要,取決於你的設備驅動所強加的限制。要得到更多的資料,請參閱“邊界和大小限制,以及最小和最大縮放系數”。

      

4)邊界(Boundary)和大小(Size)限制

由於不同的硬件的能力所限,在顯示覆蓋頁面的時候,某些設備驅動對源和目標矩形的位置和大小強加了一些限制。要找出某個設備受到了哪些限制,調用IDirectDraw2::GetCaps函數,並且在DDCAPS的dwCaps成員中檢查那些與覆蓋頁面相關的標志。下面的表展示了表明邊界和大小限制的的成員極其標志。

類型                                        標志                     成員變量

      邊界(位置)限制              DDCAPS_ALIGNBOUNDARYSRC  dwAlignBoundarySrc

                                          DDCAPS_ALIGNBOUNDARYDEST       DwAlignBoundaryDest

      大小限制                            DDCAPS_ALIGNSIZESRC                     dwAlignSizeSrc

                                    DDCAPS_ALIGNSIZEDEST               DwAlignSizeDest

      有兩種類型的限制:邊界限制和大小限制。每一種限制都是以像素為單位(而不是以字節為單位),並且可以提供給源和目標矩形。同樣,這些限制會因為覆蓋頁面和主頁面的像素格式的不同而不同。

邊界限制影響的是你可以放置一個源或目標矩形的位置。dwAlignBoundarySrc和dwAlignBoundaryDest成員所報告的值分別告訴你應如何對齊相應的矩形的左上角。矩形左上角的x坐標(RECT結構的left成員)必須是所報告的該限制值的整數倍。

大小限制影響的是你可以設置一個源或目標矩形的合法的大小。dwAlignSizeSrc和dwAlignSizeDest成員所報告的值分別告訴你應如何對齊相應矩形的寬度(以像素為單位)。源或目標矩形的寬度必須是這個所報告的值的整數倍。如果你按照最小的縮放系數縮放一個矩形,還必須保證縮放后的矩形仍是滿足大小限制的。在縮放完成后,用上舍入的方法(只取不舍)調整矩形的的寬度,而不是下舍入,這樣才能保證滿足最小縮放系數。要得到更多的資料,請參閱“最小和最大縮放系數”。

 

5)最小和最大縮放系數

由於硬件的能力所限,某些設備對於覆蓋頁面從源矩形到目標矩形Blit操作的縮放比例存在一定的限制。DirectDraw將這種限制稱之為縮放系數。要獲得設備縮放系數的數據,調用IDirectDraw2::GetCaps函數,如果DDCAPS成員中設置了DDCAPS_OVERLAYSTRETCH標志,那么,有關縮放系數的數據就存在dwMinOverlayStretch和dwMaxOverlayStretch成員中。應該注意的是,縮放系數是按照實際數值的1000倍給出的,所以,1300實際上表示縮放系數為1.3,750表示0.75。

對於那些對覆蓋頁面的縮放不存在限制的設備來說,最小和最大縮放系數的值都被設為0。

最小縮放系數報告的是目標矩形必須至少是源矩形的寬度乘以該最小縮放系數值。如果最小縮放系數比1000大,那么,你所指定的目標矩形必須比源矩形大。舉例來說,如果設備報告的最小縮放系數是1300,你必須保證目標矩形的寬度至少是源目標矩形寬度的1.3倍。同樣,最小縮放系數小於1000表明目標矩形可以比源矩形小。

最大縮放系數報告了目標矩形必須至多是源矩形的寬度乘以該最大縮放系數。舉例來說,如果最大縮放系數為2000,你可以指定目標矩形的大小至多是源矩形大小的兩倍。如果最大縮放系數小於1000,那么,你總是必須使目標矩形比源矩形要小,才能正常的實現覆蓋頁面。

覆蓋頁面在進行完縮放操作之后,目標矩形還必須滿足設備所需要的大小和位置限制。因此,先將目標矩形進行縮放,然后再進行大小和位置的調整是一個好主意。要得到更多的資料,請參閱“邊界和大小限制”。

硬件不需要你對目標矩形的高度進行調整。你可以增加一個目標矩形的高度來保證顯示正常的外觀比例,而不會產生負面影響。

 

6)覆蓋頁面的關鍵色

與其它類型的頁面一樣,覆蓋頁面使用了源和目標關鍵色,以控制完成在不同頁面間的透明Blit操作。因為覆蓋頁面本身在Blit操作時是不可見的,因此,當你調用IDirectDrawSurface3::UpdateOverlay函數的時候,需要一個另外的方法來控制該覆蓋頁面是如何被顯示到主頁面之上的。這種要求是通過覆蓋頁面的關鍵色來滿足的。覆蓋頁面的關鍵色,與其它Blit相關的關鍵色一樣,被分為源關鍵色和目標關鍵色,可以通過IDirectDrawSurface3::SetColorKey函數來設置。使用DDCKEY_SRCOVERLAY或DDCKEY_DESTOVERLAY標志來指定是源關鍵色還是目標關鍵色。覆蓋頁面可以使Blit操作、覆蓋頁面關鍵色、以及覆蓋頁面顯示操作同時正常的進行,兩種類型的關鍵色不會發生相互沖突。

IDirectDrawSurface3::UpdateOverlay函數使用覆蓋頁面的源關鍵色來指定在覆蓋頁面上哪些像素被視為透明,以使在這些像素覆蓋下的主頁面的內容可以被顯示出來。同樣的,該函數還使用覆蓋頁面的目標關鍵色來指定在主頁面上哪些部分可以被覆蓋頁面所占據。產生的這些視覺效果與由Blit相關的關鍵色的是一樣的。要得到更多的資料,請參閱“透明Blit,以及源和目標關鍵色”。

 

7)覆蓋頁面的定位

在調用IDirectDrawSurface3::UpdateOverlay函數初始化顯示一個覆蓋頁面之后,你可以通過調用IDirectDrawSurface3::SetOverlayPositio函數來重新定位覆蓋頁面的目標矩形。

必須保證你所指定的位置要滿足硬件設備所強加的邊界限制。要得到更多的資料,請參閱“邊界和大小限制”;同時,也要記住IDirectDraw2::SetOverlayPosition函數不會幫你進行裁減操作,函數中使用的坐標值如果有可能使你的覆蓋頁面超出目標頁面的邊緣,將會導致該函數調用失敗,返回一個DDERR_INVALIDPOSITION的錯誤。

 

8)創建覆蓋頁面

與其它的頁面一樣,通過調用IDirectDraw2::CreateSurface函數可以創建一個覆蓋頁面,條件是必須在DDSCAPS結構中指定DDSCAPS_OVERLAY標志。

對覆蓋頁面的支持隨顯示設備的不同而存在着巨大的差異。因此,絕對不要認為存在一個被絕大多數硬件設備所支持的像素格式,必須作好准備與各種各樣不同的像素格式打交道。通過調用IDirectDraw2::GetFourCCCodes函數,你可以得到設備驅動所支持的有關非RGB格式的信息。

當你創建一個覆蓋頁面的時候,先試着創建一個被大多數設備所認可的像素格式,如果失敗,再試着使用其它的格式,這種方法是比較好的。

你可以創建一個覆蓋頁面換頁鏈。要得到更多的資料,請參閱“創建復雜頁面和換頁鏈”。

 

9)覆蓋頁面的Z軸排序(Z-order)

覆蓋頁面在DirectDraw中被認為是位於屏幕上所有內容的最上一層,但是,當你需要顯示多個覆蓋頁面的時候,你需要有一個辦法來組織這些頁面。為此,DirectDraw支持覆蓋頁面的Z軸排序,來管理這些覆蓋頁面層次關系和相互裁減。Z軸排序值代表了從主頁面到觀察者之間的一個抽象的距離。這些值的可能范圍是從0,緊靠着主頁面的一層,直到40億,最靠近觀察者的一層,並且沒有任何兩個覆蓋頁面可以共享同一個Z軸次序值。通過調用IDirectDrawSurface3::UpdateOverlayZOrder可以設置一個覆蓋頁面的Z軸次序。

目標關鍵色只受主頁面的影響,而不會受到被另一個覆蓋頁面所覆蓋的覆蓋頁面的影響。源關鍵色作用於覆蓋頁面,而不考慮該覆蓋頁面是否被設置了Z軸次序。

沒有指定Z軸次序的覆蓋頁面,其Z軸次序被默認為0。沒有指定Z軸次序的多個覆蓋頁面間的層次關系是不可預知的,當它們被映射到主頁面上時,會產生混亂。

一個DriectDraw對象不能跟蹤由另一個應用程序顯示的覆蓋頁面的Z軸次序。

 

10)覆蓋頁面的換頁

和其它各類型的頁面一樣,你可以創建覆蓋頁面的換頁鏈。在創建好一個覆蓋頁面的換頁鏈之后,你可以通過調用IDirectDrawSurface3::Flip函數實現換頁。要得到更多的資料,請參閱“換頁”。

利用覆蓋頁面的軟件視頻解碼在調用Flip函數實現換頁的時候,可以使用DDFLIP_ODD和DDFLIP_EVEN標志,以利用減少運動噪聲的特性。如果設備驅動支持奇偶(odd-even)隔行換頁,在調用GetCaps函數以獲取設備驅動能力的DDCAPS結構中將會被設置DDCAPS2_CANFLIPODDEVEN標志。如果是這樣的話,那么,在調用IDirectDrawSurface3::UpdateOverlay函數時,你可以包含進DDOVER_BOB標志,以告知設備驅動你希望它使用BOB(擺動)算法來使運動噪聲達到最小。隨后,在你調用Flip函數實現換頁時,你可以帶上DDFLIP_ODD或DDFLIP_EVEN標志,設備驅動將會自動的調整覆蓋頁面的源矩形來補償不穩定的噪聲。

獲取設備驅動能力時,如果DDCAPS結構中沒有設置DDCAPS2_CANFLIPODDEVEN標志,那么,指定了DDOVER_BOB 標志的UpdateOverlay函數調用將會失敗。

要得到更多的關於Bob算法的資料,請參閱“普通視頻噪聲的解決方案”。

 

十一、Blit到多窗口

你可以使用一個DirectDraw對象和一個DirectDrawClipper對象Blit到由運行於普通控制級的應用程序創建的若干個窗口中。要得到更多的信息,請參閱“對多窗口使用裁剪器”。

創建多個DirectDraw對象,並且讓它們Blit到相互的主頁面的作法是不推薦的。

 

 

第六節調色板

一、什么是調色板?

基於調色板的頁面需要調色板才能真正有意義的顯示出來。一個基於調色板的頁面,通常也被稱作一個“色彩索引”頁面,僅僅是一些數字的集合,其中的每一個數字代表一個像素。每一個數字的值都對應於一個色彩表中的項,這個表告訴DirectDraw對這個像素使用什么樣的顏色。DirectDrawPalette對象,通常簡稱為“調色板”,給你提供了一個及其方便的途徑來管理調色板。那些使用16位或更高位像素格式的頁面並不使用調色板。

提供DirectDrawPalette對象是為了擁有直接操作16和256色調色板的特性(一個DirectDrawPalette對象通常與一個DirectDrawSurface對象相依屬)。一個DirectDrawPalette對象保留了一個從0到255的256色調色板的入口;它不保留16色調色板的任何入口。它允許直接對色彩表(color table)的直接操作。一個色彩表是一系列顏色值(典型的是RGB三個一組)。這個表可以包含16或24位的RGB色彩入口,代表與每一個索引相對應的顏色。對16色調色板來說,色彩表可以包含另一個256色調色板的索引。

調色板被材質圖、離屏頁面、覆蓋頁面所支持,它們並不需要與主頁面擁有同樣的調色板。

你可以通過調用IDirectDraw2::CreatePalette函數來創建一個調色板。該函數將返回一個指向IdirectDrawPalette接口的調色板對象的指針。你可以使用該接口的函數來操作調色板入口、獲得關於調色板對象能力的信息、或初始化該對象(如果你用的是CoCreateInstance函數創建的它)。

你可以通過調用IDirectDrawSurface3::SetPalette函數將一個調色板連接到一個頁面上。一個調色板可以被連接到若干個頁面。

通過調用IDirectDrawPalette::GetEntries,應用程序可以獲得這些表的入口,並且可以通過IDirectDrawPalette::SetEntries改變這些入口。這個函數有一個dwFlags參數,指定對於調色板的改動是否立即生效。

DirectDrawPalette對象為一個8位的調色板保留了從0到255的入口,除非你指定了DDPCAPS_ALLOW256標志請求所有這些入口都可以被你所用。

SDK中的Ddutil.cpp源文件包含了一些關於操作調色板的唾手可得的應用程序定義的函數(非庫函數)。要得到更多的信息,請參閱源文件中的DDLoadPalette函數。

 

二、調色板的種類

DirectDraw支持1位(2個入口)、2位(4個入口)、4位(16個入口)、和8位(256個入口)的調色板。一個調色板只能依附於符合其像素格式的頁面。例如,一個由DDPCAPS_1BIT標志創建的2入口(2-entry)的調色板只能被依附於一個由DDPF_PALETTEINDEXED1標志創建的1位(1-bit)頁面。

除此之外,你可以創建一個不包含色彩表的調色板,這就是“索引調色板(indexed palettes)”。一個索引調色板是指:其入口並不包含RGB色彩值,而是另一個調色板的PALETTEENTRY結構的索引值。一個索引調色板的色彩表(color table)是一個2、4、16、256字節的序列,每一個字節是另一個調色板的索引。

要創建一個索引調色板,在調用IDirectDraw2::CreatePalette時指定DDPCAPS_8BITENTRIES標志。例如,要創建一個4位的索引調色板,指定DDPCAPS_4BIT |DDPCAPS_8BITENTRIES。當你創建一個索引調色板,傳遞一個指向一系列字節的指針,而不是一個指向PALETTEENTRY結構的指針。當你使用IDirectDraw2::CreatePalette時,必須將一個指向字節組的指針轉換成LPPALETTEENTRY類型。

注意:在Blit操作的過程中,DirectDraw不會解除索引調色板,而使其成為普通調色板。

 

三、對非主頁面(Non-Primary Surfaces)設置調色板

調色板可以依屬於任何一種基於調色板的頁面(主頁面,后台緩存,離屏平面,或材質圖)。只有那些被依屬到主頁面(primary surface)的調色板才能對系統調色板(system palette)產生影響。注意到這一點是很重要的:DirectDraw位圖映射決不會導致色彩轉換;任何依屬於源或目標頁面的調色板在位圖映射中被忽略。此外,IDirectDrawSurface3::GetDC函數同樣忽略被選入頁面的任何DirectDrawPalette對象。

非主頁面調色板可以被應用程序或Direct3D(或其它3D渲染引擎)所使用。

 

四、共享調色板

調色板可以被多個頁面所共享。同一個調色板可以設置給一個換頁鏈的前台緩存(front buffer)和后台緩存(back buffer),或在多個材質頁面中共享。當應用程序調用IDirectDrawSurface3::SetPalette使一個調色板依屬於一個頁面,頁面將遞增該調色板的引用記錄(reference count)。當頁面的引用記錄達到0時,頁面將遞減依屬於它的該調色板的引用記錄。除此之外,如果通過IDirectDrawSurface3::SetPalette使調色板脫離頁面,則該調色板的引用記錄將減1。

注意:如果對同一頁面反復調用IDirectDrawSurface3::SetPalette以脫離同一調色板,則該調色板的引用記錄只會遞減一次。后來的調用不會影響調色板的引用記錄。

 

五、調色板動畫

調色板動畫指的是:用修改隸屬於一個頁面的調色板的方法來實現顯示頁面色彩的變化,而不是通過實際修改頁面的內容來實現頁面的變化。為了這個目的,調色板動畫給你提供了一個不需要改變頁面內容,改變頁面所顯示的圖象的途徑,而只會產生很少的消耗。

有兩種方法可以直接實現調色板動畫:

  • 修改調色板的入口
  • 在兩個調色板之間切換

你可以使用以下兩種方法之一來實現簡單的調色板動畫。前一個方法:改變為實現動畫所需要改變顏色的調色板入口。這種方法,你可以通過調用IDirectDrawPalette::SetEntries來實現調色板的重置。第二個方法需要兩個DirectDrawPalette對象。應用程序通過輪流將兩個DirectDrawPalette與DirectDrawSurface對象相依屬,來實現動畫。這種方法,你可以通過調用IDirectDrawSurface3::SetPalette來實現。

沒有一種技術會使硬件產生激烈反應,所以,你可以隨心所欲的選擇適合於你的程序的那種。

 

 

第七節裁減器

一、什么是“裁減器(Clipper)”對象

裁減器,或DirectDrawClipper對象,限定你的Blit操作到一個指定的頁面區域。一個裁減器對象擁有一個或多個裁減清單。一個裁減清單是一個或一系列封閉的矩形,描述了頁面上的一個或一系列區域,只有在該區域內才允許Blit。這些區域是用RECT結構來描述的,按屏幕坐標。

裁減清單是一個頗有價值的工具。它們最通常的作用是阻止你的應用程序在Blit操作時,超出屏幕的邊界。舉例來說,想象一個子圖形從屏幕的一邊逐漸進入屏幕。你不會想讓你的子圖形是突然出現在屏幕上的;你想讓它從屏幕的邊上一點一點的出現。如果沒有裁減器對象,你應該需要在程序中加入一個邏輯判斷和運算,逐步修改Blit操作的參數,以保護那些超出屏幕邊緣的頁面內存,如果沒有這些邏輯操作的話,你的應用程序會產生內存訪問異常(memory access violations)的錯誤。,

下面的插圖展示了這種類型的裁減。

你可以使用裁減器對象來指定目標頁面的特定區域為可寫的。DirectDraw在這些區域中裁減Blit操作,以保護那些指定裁減矩形以外的區域不被改寫。

下面的例圖展示了這種類型的裁減。

 

二、裁減清單

DirectDraw用DirectDrawClipper對象來管理裁剪表。一個裁剪表是描述頁面可見區域的一系列矩形。一個DirectDrawClipper對象可以被依附於任何一個頁面。一個窗口句柄同樣也可以與一個DirectDrawClipper對象相依附,在這種情況下,DirectDraw更新窗口的DirectDrawClipper對象的裁減表。

盡管在DirectDraw HAL看來,裁減表是可見的,但DirectDraw調用HAL只用於符合裁剪表所需要的矩形區域。比如,如果一個頁面的右上方被裁減,並且應用程序指示DirectDraw將這個頁面映射到主頁面(primary surface)上,則DirectDraw將會使HAL進行兩個映射,首先是頁面的左上角,其次是頁面的下部區域。

通過IDirectDrawClipper::SetClipList函數,你可以將整個裁減清單連接到裁減器上(如果設備支持該操作),而不是多次調用該函數,每次連接裁減清單中的一個矩形。除此之外,通過調用IDirectDrawClipper::SetHWnd函數,並且指定一個窗口的句柄,你還可以將該窗口連接到裁減器上。如果你已經讓裁減器使用一個窗口句柄,那么,你將不能給該裁減器再設置另外的矩形。

覆蓋頁面的裁減只有在覆蓋硬件可以支持,並且目標關鍵色沒有被激活的情況下才能使用。

 

三、共享DirectDrawClipper對象

DirectDrawClipper對象可以被多個頁面所共享。例如,同一個DirectDrawClipper對象可以被設置到一個換頁鏈的前台緩存(front buffer)和后台緩存(back buffer)上。當應用程序使用IDirectDrawSurface3::SetClipper,將一個DirectDrawClipper對象依屬到一個頁面,則頁面將遞增該DirectDrawClipper對象的引用記錄(reference count)。當頁面的引用記錄達到0時,頁面將遞減DirectDrawClipper對象的引用記錄。除此之外,如果調用IDirectDrawSurface3::SetClipper,並使其裁減接口指針為NULL,則DirectDrawClipper對象將從頁面上分離,其引用記錄也將遞減1。

注意:如果在同一頁面多次調用IDirectDrawSurface3::SetClipper以依屬同一個DirectDrawClipper對象,則它的引用記錄只會遞增一次。以后的調用不會影響到該對象的引用記錄。

 

四、獨立的DirectDrawClipper對象

你可以創建不直接屬於任何特定DirectDraw對象的DirectDrawClipper對象。這些DirectDrawClipper對象可以被多個DirectDraw對象所共享。驅動無關(Driver-independent)的DirectDrawClipper對象是用DirectDraw的新函數DirectDrawCreateClipper創建的。應用程序可以在任何DirectDraw對象創建之前調用此函數。

因為DirectDraw對象不擁有這些DirectDrawClipper對象,所以它們不會在應用程序的DirectDraw對象被釋放時自動被釋放。如果應用程序沒有明確的釋放這些DirectDrawClipper對象,DirectDraw引擎將在應用程序結束時釋放它們。

你仍然可以使用IDirectDraw2::CreateClipper函數創建DirectDrawClipper對象。這些DirectDrawClipper對象會在創建它們的DirectDraw對象被釋放時,被自動釋放。

 

五、用CoCreateInstance創建DirectDrawClipper對象

DirectDrawClipper對象擁有COM所支持的全部類功能。除了使用標准的DirectDrawCreateClipper 和 IDirectDraw2::CreateClipper創建一個DirectDrawClipper對象外,你同樣可以使用CoGetClassObject來獲得一個類對象,然后再調用CoCreateInstance,或者直接調用CoCreateInstance來創建它。下面的例子展示了如何使用CoCreateInstance 和 IDirectDrawClipper::Initialize來創建一個DirectDrawClipper對象。

ddrval = CoCreateInstance(&CLSID_DirectDrawClipper,

NULL, CLSCTX_ALL, &IID_IDirectDrawClipper, &lpClipper);

if (!FAILED(ddrval))

ddrval = IDirectDrawClipper_Initialize(lpClipper,

lpDD, 0UL);

       在CoCreateInstance調用中,第一個參數,CLSID_DirectDrawClipper,是DirectDrawClipper對象類的類標志符,IID_IDirectDrawClipper 參數指名了當前所支持的接口,lpClipper 參數指向返回的DirectDrawClipper對象。

在使用這種類機制創建DirectDrawClipper后,應用程序必須調用IDirectDrawClipper::Initialize來初始化該對象,否則該對象無法使用。dwFlags參數的值是0UL,在這種情況下,該值為0,因為現在還沒有可以支持的標志符。在這個例子種,lpDD是擁有該DirectDrawClipper對象的DirectDraw對象。然而,你也可以提供NULL值,這將創建一個獨立的DirectDrawClipper對象。(這相當於使用DirectDrawCreateClipper函數來創建一個DirectDrawClipper對象)

在關閉應用程序之前,用CoUninitialize函數來關閉COM。

CoUnitialize();

 

六、對系統鼠標使用裁減器

DirectDraw應用程序通常需要為用戶提供一個鼠標指針。對於全屏獨占模式的應用程序來說,如果使用了換頁操作,那么要實現鼠標指針的唯一選擇是使用子圖,通過接受由DirectInput設備或由Windows鼠標消息傳來的數據控制子圖的移動。然而,任何應用程序,只要沒有使用換頁操作,仍然是可以使用Windows系統的缺省鼠標指針的。

當你使用系統的鼠標指針時,在某些情況下,當你在Blit到主頁面的某些區域時,你將淪為圖形雜點的犧牲品。這些圖形雜點象鼠標指針那樣出現在屏幕上,就好象是系統留下的。

通過阻止鼠標指針圖象在Blit操作過程中“擋道”, 一個DirectDrawClipper對象可以防止這樣的雜點出現。這也是一個相對而言較簡單的解決辦法。步驟是這樣的:首先,調用IDirectDraw2::CreateClipper創建一個DirectDrawClipper對象;然后,通過調用IDirectDrawClipper::SetHWnd函數將你的應用程序的窗口句柄連接到該裁減器對象上。一旦一個裁減器對象被連接到一個窗口后,任何后續的對主頁面進行的Blit操作(通過調用IDirectDrawSurface3::Blt函數)將不會出現任何鼠標雜點。

注意:IDirectDrawSurface3::BltFast函數,以及它的IDirectDrawSurface和IdirectDrawSurface2接口的版本,將不能對隸屬於頁面的裁減器進行操作。

 

七、對多窗口使用Clipper

你可以使用一個單獨的DirectDrawClipper對象,對由單獨的應用程序創建的多個窗口進行Blit操作。

這個方法是這樣的。首先,創建一個單獨的DirectDraw對象以及一個主頁面。然后,創建一個DirectDrawClipper對象,並且將它連接到你的主頁面上(通過調用IDirectDrawSurface3::SetClipper函數)。要使Blit操作僅限於窗口的客戶區,在Blit到主頁面之前,將該窗口的句柄設置給該裁減器(通過調用IDirectDrawClipper::SetHWnd函數)。只要你需要Blit到另一個窗口的客戶區,再次調用IDirectDrawClipper::SetHWnd函數,將另一個窗口的句柄設置給裁減器。

創建多個DirectDraw對象,並且讓它們Blit到相互的主頁面是不推薦的做法。上面所介紹的技術提供了一個有效和可靠的Blit到多客戶區的方法,針對的是單獨的DirectDraw對象。

 

 

第八節多顯示器系統

Windows 98和Windows NT 5.0可以支持在一個單獨的系統中存在多個顯示設備和顯示器。多顯示器架構(通常簡稱為“MultiMon”)使操作系統使用兩個或多個顯示設備和顯示器來建立一個邏輯桌面成為可能。舉例來說,在一個有兩台顯示器的系統中,用戶可以在其中的任何一個顯示器上顯示他的應用程序,或者將窗口從一個窗口中拖到另一個窗口中。這一章的內容包含了如何在一個多顯示器系統中使用DirectDraw的信息。

DirectX SDK的\Samples\Misc目錄中包含了一些文件,提供了用於多顯示器系統中的輔助性的函數。Multimon.h頭文件使在Windows 98多顯示器系統下編譯的源代碼能夠在使用Windows 95的機器中順利的編譯和執行。除此之外,Ddmm.cpp文件Ddmm.h文件提供了輔助性的函數,允許你從一個窗口句柄或設備字符串中創建或獲得DirectDraw對象。

 

一、在多顯示器系統中列舉顯示設備

使用DirectDrawEnumerateEx函數可以列舉出在多顯示器系統中的各顯示設備,指定一個標志來決定哪種類型的DirectDraw設備可以被列舉出來。該函數每列舉出一個設備,就調用一次應用程序定義的DDEnumCallbackEx型回調函數。

因為顯示設備包括了顯示卡和顯示器,而且在通常情況下,一個顯卡對應一個顯示器,為了便於理解,以后我們談到的顯示設備,讀者可以簡單的將其直接視為顯示器。

DirectDrawEnumerateEx函數被Windows 98和Windows NT 5.0(或更高版本)所支持,並且只能通過從動態連接庫中獲得函數地址來調用。在運行期,它是這樣實現的:調用GetProcAddress Win32函數,從Ddraw.dll動態連接庫中載入函數的地址。下面的例程演示了這個方法。

HINSTANCE h = LoadLibrary("ddraw.dll");

// 如果ddraw.dll不存在搜索的路徑中,

// 那么,可能還沒有安裝DirectX,返回失敗。

if (!h)

return FALSE;

// 注意:你必須知道要獲得的函數的版本,

// 在這個例子中,我們使用ANSI版本。

LPDIRECTDRAWENUMERATEEX lpDDEnumEx;

lpDDEnumEx = (LPDIRECTDRAWENUMERATEEX) GetP    ocAddress(h,"DirectDrawEnumerateExA");

// 如果函數存在,調用它,列舉出所有連接到桌面的顯示設備,

// 以及所有非顯示的DirectDraw設備。

if (lpDDEnumEx)

lpDDEnumEx(Callback, NULL,

DDENUM_ATTACHEDSECONDARYDEVICES |

DDENUM_NONDISPLAYDEVICES

);

else

{

/*

* 我們一定是運行在一個較老版本的DirectDraw中。

* 因此,該操作系統必定不支持多顯示器。

* 於是,返回到DirectDrawEnumerate函數以列舉出單顯示器的標准顯示設備。

*/

DirectDrawEnumerate(OldCallback,NULL);

/* 注意:這里有一個小的技巧,

* 讓OldCallback 回調函數包裝進DDEnumCallbackEx回調函數。

* 最后的回調函數是下面的這個樣式:

* BOOL FAR PASCAL OldCallback(GUID FAR *lpGUID,

* LPSTR pName,

* LPSTR pDesc,

* LPVOID pContext)

       * {

              * return Callback(lpGUID,pName,pDesc,pContext,NULL);

* }

*/

       }

       // 最后,必須調用FreeLibrary函數釋放動態連接庫。

FreeLibrary(h);

上面的例程代碼可以在運行期或裝載期為連接到Ddraw.dll的應用程序所用。

應該注意的是,你必須獲得DirectDrawEnumerateEx 函數的ANSI或Unicode版本中的哪一個,依賴於你的應用程序的字符串類型。當聲明相應的回調函數時,為字符串參數使用LPTSTR數據類型。如果你聲明了_UNICODE符號,LPTSTR數據類型在編譯時使用Unicode字符串,否則使用ANSI字符串。通過使用LPTSTR數據類型,該函數能夠正常工作,而不論你的應用程序使用的是什么樣的字符串。

 

二、焦點窗口與設備窗口

在單獨顯示器系統中,當你為一個應用程序設置了全屏獨占的控制級時(通過調用IDirectDraw2::SetCooperativeLevel函數),你必須指定你的應用程序窗口句柄。DirectDraw使用應用程序的窗口句柄來勾住系統的消息,用來獲得應用程序的狀態信息、傳遞鍵盤輸入的消息、和調整窗口大小的消息等。DirectDraw不需要為普通控制級(窗口模式)的應用程序設置一個窗口句柄。

因為多顯示器系統必不可少的會用到至少一個顯示設備,因此,要用DirectDraw訪問到每一個設備, 你必須為每一個顯示設備創建一個DirectDraw對象。然而,你所創建的每一個DirectDraw對象需要知道哪一個應用程序的窗口是它可以控制的,以及哪一個是可以接收到擊鍵消息的。換句話說,應用程序需要有一個方法來告訴DirectDraw哪一個窗口將被用於哪些操作。這個特性是通過焦點窗口(Focus window)和設備窗口(Device window)的概念提供的。IDirectDraw2::SetCooperativeLevel函數支持三個標志符使這個特性連接到一個DirectDraw對象:DDSCL_SETFOCUSWINDOW、DDSCL_SETDEVICEWINDOW、和DDSCL_CREATEDEVICEWINDOW。

一個“設備窗口”只是一個簡單的可見窗口,DirectDraw可以調整其大小以占據顯示器的整個顯示區域。你可以手工創建與安裝與顯示設備一樣多的設備窗口,或者你可以讓DirectDraw來為你處理這些細節(當你讓DirectDraw來管理這些設備窗口的創建時,會存在一些協定,這些協定將在“缺省設備窗口”一節中討論)。

一個焦點窗口指的是DirectDraw對象可以對其發送擊鍵消息的窗口。一個DirectDraw應用程序只能擁有一個焦點窗口。

要得到更多的資料,請參閱“設置焦點和設備窗口”和“缺省設備窗口”。

 

三、設置焦點窗口和設備窗口

調用IDirectDraw2::SetCooperativeLevel函數,並且指定DDSCL_SETDEVICEWINDOW或DDSCL_SETFOCUSWINDOW標志,你可以設置一個DirectDraw對象的焦點窗口和設備窗口。(SetCooperativeLevel函數同樣接受DDSCL_CREATEDEVICEWINDOW標志,要得到更多的資料,請參閱“缺省設備窗口”。)

注意:只有當你想要獲得對多顯示器的全屏獨占的訪問時,才能設置焦點和設備窗口。如果你只想對主顯示設備和顯示器獲得獨占訪問,你可以調用SetCooperativeLevel函數,在dwFlags參數中僅僅指定DDSCL_FULLSCREEN和DDSCL_EXCLUSIVE標志。

設置焦點和設備窗口是一個兩步的過程:在為一個顯示設備創建了DirectDraw對象之后,你必須首先為它設置焦點窗口。焦點窗口對你的應用程序中所有DirectDraw對象來說都是同一個,並且通過調用SetCooperativeLevel函數來設置。在調用中,第一個參數是獲取擊鍵消息的窗口的句柄,第二個參數是DDSCL_SETFOCUSWINDOW標志。

在設置了DirectDraw對象的焦點窗口后,你應該設置設備窗口和控制級(在這種情況下,為全屏獨占模式)。這一步是通過另一次調用SetCooperativeLevel函數完成的。在這一次的調用中,第一個參數是DirectDraw將調整其大小為全屏的窗口的句柄,第二個參數是DDSCL_SETDEVICEWINDOW、DDSCL_FULLSCREEN、和DDSCL_EXCLUSIVE標志的結合。

盡管你必須指定一個焦點窗口,你仍然可以讓DirectDraw來操縱你的設備窗口的創建和管理工作。要得到更多的資料,請參閱“缺省設備窗口”。

 

四、缺省設備窗口

你可以選擇讓DirectDraw來為你完成創建、管理、和銷毀設備窗口的工作。簡單的說,DirectDraw管理的窗口被稱為是“缺省設備窗口”。盡管使用缺省設備窗口可以把你從創建、管理、銷毀設備窗口的繁重任務中解放出來,但是你的應用程序將不能夠獲得操作系統發送到那些缺省設備窗口的鼠標消息。因此,只有在你的應用程序不需要從那些設備窗口接收鼠標消息的情況下,你才應該選擇使用缺省設備窗口。

啟用缺省設備窗口的過程是很簡單的。在首先調用IDirectDraw2::SetCooperativeLevel函數設置了一個DirectDraw對象的焦點窗口之后,再次調用該函數,將dwFlags參數指定為DDSCL_CREATEDEVICEWINDOW、DDSCL_FULLSCREEN和DDSCL_EXCLUSIVE標志符的結合。

注意:在一個單步調用SetCooperativeLevel函數的過程中,設置一個DirectDraw對象的焦點窗口、告訴DirectDraw創建一個缺省設備窗口、並且設置DirectDraw的控制級也是可能的。下面的例程演示了這是怎樣完成的:

/*

* 該代碼片段只在使用缺省設備窗口時才可用.

*/

HRESULT hr;

hr = g_lpDD->SetCooperativeLevel(

// 該窗口句柄僅僅是焦點窗口的

hwndFocus,

DDSCL_SETFOCUSWINDOW | DDSCL_FULLSCREEN |

DDSCL_EXCLUSIVE | DDSCL_CREATEDEVICEWINDOW);

      

六、多顯示器系統中的顯示設備與加速特性

一個DirectDraw應用程序應該列舉出所有顯示設備,並且從中選擇一個顯示設備(或讓用戶來選擇),然后通過使用它的硬件全局唯一標志符(GUID)為該顯示設備創建一個DirectDraw對象。這種技術可以使無論是在多顯示器系統還是單顯示器系統中,以及在任何一種控制級上都能獲得最好的顯示效果。

當前所激活的顯示設備也被稱為“缺省設備(Default device)”,或“空設備(Null device)。后一個名稱的得來是因為當前所激活的顯示設備是將NULL作為它的GUID而列舉出來的。許多現有的應用程序為缺省設備創建一個DirectDraw對象,並且假定該設備是硬件加速的。然而,在多顯示器系統上,缺省設備並不一定是硬件加速的,這取決於當時對DirectDraw所設置的控制級。

在全屏獨占模式中,缺省設備是硬件加速的,但是已安裝的其它顯示設備則不是。這意味着,具有全屏獨占模式的應用程序在多顯示器系統中可以和任何其它系統一樣運行得非常快速,但是不能利用系統內置的跨顯示器的圖形操作。需要使用到多顯示設備(顯示器)的全屏獨占模式的應用程序,可以為每一個他想要使用的顯示設備(顯示器)創建一個DirectDraw對象。應注意的是:為一個特定的設備創建一個DirectDraw對象,你必須提供該設備的GUID(在調用DirectDrawEnumerate函數時可以被列舉出來)。

當你把控制級設置為普通(窗口模式)時,缺省設備則不具有硬件加速性;此時的缺省設備成為了一個有效的、將兩個物理顯示設備的資源聯合起來的邏輯仿真顯示設備,因此,當設置了控制級為普通時,缺省設備是根本沒有被硬件加速的。從另一個方面來說,當設置了控制級為普通時,缺省設備自動具有了跨越各顯示器(顯示設備)的圖形操作的能力。典型的實例是,當第二顯示器的邏輯地址位於主顯示器的左邊時,對主顯示器進行帶負坐標的Blit操作是有效的。

如果了你給應用程序設置控制級為普通模式,卻仍需要利用硬件的加速特性,你必須只創建一個單獨的DirectDraw對象,使用一個特定顯示設備的GUID。應注意的是:如果你不使用缺省設備,你不會獲得自動跨越設備的能力。這就是說,對主頁面的Blit操作超出了顯示器邊界的部分將會被裁減掉(如果你使用了裁減器),或操作失敗,返回DDERR_INVALIDRECT的錯誤。

作為所有系統上的慣例,在創建了DirectDraw對象后,獲得其能力或查詢其它接口之前,你應該立即設置其控制級。除此之外,還應該避免在多顯示器系統中多次設置控制級。如果你的應用程序需要從全屏模式切換到窗口模式運行,最好的做法是新建一個DirectDraw對象。

 

 

第九節高級DirectDraw主題

一、對Mode 13的支持

1、關於Mode 13

DirectDraw支持線性非換頁的320x200的8位調色板顯示模式,人們對它使用更為廣泛的名稱是Mode 13棗它的16進制BIOS模式編號。DirectDraw將這種模式視為一種Mode X模式,但是它們之間又有着重要的區別,這是由Mode 13模式的物理性質所強加的。

 

2、設置Mode 13

Mode 13模式與Mode X模式擁有一樣的被列舉和模式設置的行為特性。如果DDSCL_ALLOWMODEX 標志被傳給IDirectDraw2::SetCooperativeLevel 函數,DirectDraw將只會列舉出Mode 13模式。

你可以象列舉其它顯示模式那樣列舉出Mode 13模式,但是你必須在調用IDirectDraw2::EnumDisplayModes函數之前做一次頁面能力的檢測。要完成這一步,調用IDirectDraw2::GetCaps函數,並且檢查DDSCAPS 結構中的DDSCAPS_STANDARDVGAMODE標志。如果該標志不存在,表示不支持Mode 13模式,那么,試圖帶DDEDM_STANDARDVGAMODES標志的列舉將會失敗,返回一個DDERR_INVALIDPARAMS錯誤。

       numDisplayModes函數現在支持一個新的列舉標志棗DDEDM_STANDARDVGAMODES,它會讓DirectDraw除了列舉出320x200x8的Mode X模式外,還可以列舉出Mode 13模式。同樣,IDirectDraw2::SetDisplayMode函數也有一個新的標志棗DDSDM_STANDARDVGAMODE,為了將顯示模式設置成Mode 13模式而不是320x200x8的Mode X模式,你必須傳遞此標志。

注意:有些顯示卡提供了線性加速的320x200x8模式。在這種顯示卡上,DirectDraw將不會列舉出Mode 13模式,而列舉出線性模式。在這種情況下,如果你試圖通過將DDSDM_STANDARDVGAMODE標志傳遞給SetDisplayMode函數而設置顯示模式為Mode 13,函數會調用成功,但是使用了線性模式。這是低分辨率模式替換Mode X模式的一個相似的方式。

      

3、Mode 13與頁面特性

當DirectDraw調用一個應用程序定義的EnumModesCallback回調函數,相應DDSURFACEDESC 結構的ddsCaps成員包含了反映正被列舉模式的標志。在Mode X模式中,它會是DDSCAPS_MODEX,在Mode 13模式中,它會是DDSCAPS_STANDARDVGAMODE。這些標志是相互排斥的。如果沒有任何一個標志被設置,那么,該模式是線性加速的。這些特征同樣可應用於由調用IDirectDraw2::GetDisplayMode函數獲得的標志。

 

4、使用Mode 13模式

因為Mode 13是線性模式,DirectDraw可以給應用程序提供對幀緩沖區的直接訪問。與Mode X模式不一樣,你可以調用IDirectDrawSurface3::Lock、IDirectDrawSurface3::Blt、和IDirectDrawSurface3::BltFast函數直接訪問主頁面。

當使用Mode 13模式時,DirectDraw支持一種模擬的IDirectDrawSurface3::Flip函數,這是通過將后台緩存的內容直接復制到主頁面來實現的。你可以自己編寫這些代碼,使用Blt或BltFast函數復制一個后台緩存中的矩形區域到主頁面上。

有一個有關鎖定和Mode 13的誤解。盡管DirectDraw允許對Mode 13 VGA幀緩沖區的直接線性訪問,但絕不要認為該緩沖區的地址總是0xA0000,因為DirectDraw可以返回一個指向的地址並不是0xA0000的幀緩沖區的別名虛擬內存指針。同樣,絕不要認為Mode 13頁面的寬矩(Pitch)是320,因為支持320x200x8加速的顯示卡很可能會使用不同的寬矩值。

 

二、從DMA中獲益

本節的內容是關於如何從設備對DMA(直接內存訪問)的支持中獲得好處,以提高應用程序的運行效果。

1、關於DMA設備支持

某些顯示卡可以完成在系統RAM頁面中的Blit或其它操作,這些操作一般被稱為“直接內存訪問(DMA:Direct Memory Access)”。你可以利用DMA支持來加速某些集合操作。舉例來說,你可以利用系統設備對DMA的支持,完成一次從系統RAM到顯示RAM的Blit操作,而與此同時,處理器正在准備下一幀。為了使用這個功能,你必須承擔一些責任,以下將對此進一步詳細探討。

 

2、對D  MA支持的檢測

在使用DMA操作之前,你必須檢測系統設備是否支持DMA操作,如果支持的話,支持的程度如何。首先調用IDirectDraw2::GetCaps函數查詢設備能力,然后檢查相應DDCAPS 結構的dwCaps 成員中是否存在DDCAPS_CANBLTSYSMEM標志。如果該標志存在的話,表示顯示設備支持DMA。

如果你已知道顯示設備支持DMA,你還需要知道顯示設備支持的程度如何。這可以通過檢查另一些結構成員來完成,它們提供了有關從系統RAM到視頻RAM,視頻RAM到系統RAM,以及系統RAM到系統RAM的Blit操作的DMA特性。這些特性由12個DDCAPS結構的成員提供,它們依照Blit操作的DMA方式來命名。下面的表展示了這些成員。

系統RAM到視頻RAM 視頻RAM到系統RAM 系統RAM到系統RAM

dwSVBCaps dwVSBCaps dwSSBCaps 

dwSVBCKeyCaps dwVSBCKeyCaps dwSSBCKeyCaps 

dwSVBFXCaps dwVSBFXCaps dwSSBFXCaps 

dwSVBRops dwVSBRops dwSSBRops 

舉例來說,系統RAM到視頻RAM的Blit特性標志由dwSVBCaps、dwSVBCKeyCaps、dwSVBFXCaps和dwSVBRops成員提供。類似的,視頻RAM到系統RAM的Blit特性是由“dwVSB”打頭的成員提供;系統RAM到系統RAM的Blit特性是由“dwSSB”打頭的成員提供。檢查存在於這些成員中的標志,可以確定硬件對Blit操作的DMA方式的支持程度。

就這些成員的Blit類型來說,這些成員中的標志與在dwCaps、dwCKeyCaps和dwFXCaps成員中Blit相關的標志是一致的。例如,dwSVBCaps成員中包含了常規的Blit操作特性,你可以在dwCaps成員中找到同樣的標志。同樣,dwSVBRops、dwVSBRops和dwSSBRops成員中也提供了有關特定類型的Blit的光柵操作(Raster operation)的光柵操作碼。

從這些成員可以檢測到的特性中,有一個關鍵的特性,用來表明系統對異步DMA Blit操作的支持。如果顯示設備支持頁面間的異步DMA Blit操作,那么DDCAPS_BLTQUEUE標志會被設置在dwSVBCaps、dwVSBCaps或dwSSBCaps成員中(一般來說,你將看到的是從系統RAM到視頻RAM頁面的最好的支持)。如果該標志不存在,表明系統並不報告支持異步DMA Blit操作。

 

3、典型的DMA方案

從系統RAM到視頻RAM的SRCCOPY(光柵操作碼之一,表示“復制”)傳送是被硬件支持的最基本的Blit操作類型。因此,該操作最典型的用法是將系統RAM頁面中的數據傳送到視頻RAM的頁面中,以備后用。系統RAM到視頻RAM的DMA傳送與CPU控制的傳送(比如:HEL的Blit)一樣快速,但是又具有極大的利用價值,因為它們可以與主CPU進行並行的操作。

 

4、利用DMA

硬件的Blit操作使用的是物理內存地址,而不是虛擬地址,后者是應用程序的本地地址。某些設備驅動要求你提供頁面的物理內存地址。這種機制是由IDirectDrawSurface3::PageLock函數實現的。如果設備驅動不需要進行頁鎖定(Page locking),當你調用IDirectDraw2::GetCaps函數以測定硬件特性時,返回值中將會被設置DDCAPS2_NOPAGELOCKREQUIRED標志。

鎖定一個頁面可以阻止系統將該頁面的物理內存給其它的進程使用,並且保證該頁面的物理內存地址始終保持不變,直到調用了相應的IDirectDrawSurface3::PageUnlock函數。如果設備驅動需要進行頁鎖定,那么,DirectDraw將只能允許系統RAM中頁面的DMA操作,該頁面是被應用程序鎖定的。如果在這種情況下,你不調用IDirectDrawSurface3::PageLock函數,DirectDraw將會使用軟件仿真的方式來完成Blit操作。應該注意的是:鎖定大塊的系統RAM將會導致Windows系統運行阻塞。因此強烈建議,如果你的應用程序不是運行於全屏獨占模式,那么,不要調用IDirectDrawSurface3::PageLock函數以鎖定大塊的系統RAM,而且,當應用程序最小化時,必須及時將這些頁面解鎖,當然,當應用程序恢復最大化時,你可以再次鎖定頁面。

管理頁鎖定的任務完全落在應用程序開發者手中,DirectDraw絕不會來插手來鎖定或解鎖一個頁面。而且,決定究竟將多少頁面內存鎖定才不會對系統運行產生嚴重的負面影響,也是對開發者的一項考驗。

 

三、在窗口模式下使用DirectDraw調色板

當應用程序為獨占(全屏)模式時,IDirectDrawPalette接口的函數擁有直接寫硬件的能力。然而,當應用程序處於非獨占(窗口)模式時,IDirectDrawPalette接口的調用的是GDI的調色板函數句柄來與其它應用程序協同工作。

對以下主題的討論,我們假使桌面是8位調色板模式,並且你已經創建了一個主頁面和一個標准的窗口。

 

1、窗口模式的調色板入口類型

與全屏模式的應用程序不同,窗口模式的應用程序必須與其它的應用程序共享桌面調色板。這就給開發者帶來了一些困難,如怎樣修改調色板入口,以及如何才能安全的修改等。用於DirectDrawPalette對象和GDI的PALETTEENTRY結構中,包含了一個peFlags成員,它攜帶了系統如何解釋PALETTEENTRY結構的描述信息。

PALETTEENTRY結構是這樣定義的:

typedef struct tagPALETTEENTRY { // pe

BYTE peRed;

BYTE peGreen;

       BYTE peBlue;

BYTE peFlags;

} PALETTEENTRY;

前三個成員我們都很熟悉,它們是該入口所代表的顏色的三原色分量的值,分別是紅、綠、藍。最后一個成員peFlags描述了該調色板入口是以下三種類型的哪一種:

Windows靜態入口

動態入口

非動態入口

Windows靜態入口:

在窗口模式中,Windows保留了調色板的前10個(0到9)和后10個(246到255)共20個入口,將它們作為系統保留色來顯示窗口,如:菜單條、菜單文字、窗口邊框、按鈕底色等。為了使你的應用程序與系統保持一致的外觀,以及避免破壞其它的應用程序,你有必要保護主頁面調色板的這些入口。通常,開發者調用Win32的GetSystemPaletteEntries函數獲得系統調色板的入口,然后,在將用戶定制的調色板指派給主頁面之前,將系統調色板中這20個保留入口的值明確的匹配給該定制調色板。在定制調色板中復制系統調色板的入口項在應用程序初始化時可以正常工作,但如果用戶更改了桌面的配色方案,它會變得無法使用。

要避免你的調色板在用戶更改了配色方案之后變得面目全非,你可以通過提供一個指向系統調色板的引用,而不是直接指定一個顏色值來保護這些被保留的入口。用這種方法,不管系統對某一個保留入口使用什么樣的顏色,你的定制調色板將總會符合系統顏色,而且不需要做任何更新工作。在peFlags成員中使用PC_EXPLICIT標志,使你將定制調色板的入口直接指向一個系統調色板成為可能。當你使用這個標志,系統將不會再認為其它的成員中包含顏色信息,你應該設置peRed成員的值為系統調色板的一個索引,並且將其它成員的值設為0。

舉例來說,如果你想要確保你定制調色板的保留入口與系統調色板相一致,你應該使用如下的代碼:

// 設置前10項和后10項入口與系統調色板相匹配

PALETTEENTRY pe[256];

ZeroMemory(pe, sizeof(pe));

for(int i=0;i<10;i++){

pe[i].peFlags = pe[i+246].peFlags = PC_EXPLICIT;

pe[i].peRed = i;

pe[i+246].peRed = i+246;

}

你可以強迫Windows僅保留使用調色板入口的第一項和最后一項(0和255),方法是調用Win32的SetSystemPaletteUse函數。在這種情況下,你應該設置0號與255號PALETTEENTRY結構的peFlags成員值為PC_EXPLICIT。

動態入口:

在相應的PALETTEENTRY結構的peFlags成員中設置PC_RESERVED標志,表示調色板的該項入口即為用戶所保留,Windows將不會允許任何其它的應用程序將它們的邏輯調色板映射到這個物理入口中。因此,只有你的應用程序能夠修改這個入口的顏色值,實現調色板動畫,而其它的應用程序卻無法修改它。

非動態入口:

在相應的PALETTEENTRY結構的peFlags成員中設置PC_NOCOLLAPSE標志,表示該調色板入口為普通、非動態的調色板入口。PC_NOCOLLAPSE標志通知Windows不要用另一些已經分配了的物理調色板入口來替換該入口。

 

2、在窗口模式下創建調色板

下面的例程演示了如何在非獨占模式(既窗口模式)下創建一個DirectDraw調色板。為了讓你的調色板能夠正常工作,一個關鍵的任務是你要設置256個入口(PALETTEENTRY結構)中的每一個入口,然后將其提交給IDirectDraw2::CreatePalette函數。

LPDIRECTDRAW lpDD; // 假定它已經被初始化

PALETTEENTRY pPaletteEntry[256];

int index;

HRESULT ddrval;

LPDIRECTDRAWPALETTE lpDDPal;

// 首先設置Windows的靜態入口

for (index = 0; index < 10 ; index++)

{

// 前10個靜態入口

pPaletteEntry[index].peFlags = PC_EXPLICIT;

pPaletteEntry[index].peRed = index;

pPaletteEntry[index].peGreen = 0;

pPaletteEntry[index].peBlue = 0;

// 后10個靜態入口

pPaletteEntry[index+246].peFlags = PC_EXPLICIT;

pPaletteEntry[index+246].peRed = index+246;

pPaletteEntry[index+246].peGreen = 0;

pPaletteEntry[index+246].peBlue = 0;

}

// 現在,設置用戶私有的入口,在這個例程中為前16個

// 這些入口是可以動態修改的

for (index = 10; index < 26; index ++)

{

pPaletteEntry[index].peFlags = PC_NOCOLLAPSE|PC_RESERVED;

pPaletteEntry[index].peRed = 255;

pPaletteEntry[index].peGreen = 64;

pPaletteEntry[index].peBlue = 32;

}

// 現在,設置其余的入口,非動態入口

for (; index < 246; index ++)

{

pPaletteEntry[index].peFlags = PC_NOCOLLAPSE;

       pPaletteEntry[index].peRed = 25;

pPaletteEntry[index].peGreen = 6;

pPaletteEntry[index].peBlue = 63;

}

// 所有256個入口已經被填充,下面將其提交,以創建調色板。

ddrval = lpDD->CreatePalette(DDPCAPS_8BIT, pPaletteEntry,

&lpDDPal,NULL);

      

3、在窗口模式下設置調色板入口

在IDirectDraw2::CreatePalette函數中應用於PALETTEENTRY結構的規則,同樣適用於IDirectDrawPalette::SetEntries函數。典型的做法是,保存住你一開始創建的PALETTEENTRY結構組,以后你就不必每次都重建它。在必要的時候,你可以修改這個組中的個別項,然后調用IDirectDrawPalette::SetEntries函數以更新調色板。

在大多數情況下,如果你的應用程序運行於窗口模式,你就不應該去觸動任何一個Windows靜態入口,否則,后果是你所無法預知的。

對於調色板動畫,通常你需要修改的只是PALETTEENTRY結構組的一個子集,然后將這些子集入口提交給IDirectDrawPalette::SetEntries函數,這些入口必須具有PC_NOCOLLAPSE和PC_RESERVED標志。試圖對其它的入口進行調色板動畫,后果同樣是不可預知的。

下面的例程演示了如何在非獨占模式(窗口)下進行調色板動畫。

LPDIRECTDRAW lpDD;

PALETTEENTRY pPaletteEntry[256];

LPDIRECTDRAWPALETTE lpDDPal; // 以上三個對象為已初始化的

int index;

HRESULT ddrval;

PALETTEENTRY temp;

// 修改調色板入口,將靠前的16個入口進行循環

temp = pPaletteEntry[10];

for (index = 10; index < 25; index ++)

{

pPaletteEntry[index] = pPaletteEntry[index+1];

}

pPaletteEntry[25] = temp;

       // 更新調色板,不需要傳遞指向整個調色板的指針,

// 只用提供那些修改了的入口。

ddrval = lpDDPal->SetEntries(

0, // 該標志必須為0

10, // 要更新的第一個入口

16, // 要更新的入口個數

& (pPaletteEntry[10])); // 數據來源

      

四、獲得換頁和Blit操作的狀態

當你調用IDirectDrawSurface3::Flip函數,你所期望的結果是將前台緩沖區與后台緩存進行交換。然而,這個函數調用並不一定都會成功,而且如果成功也並不是說就會立即進行換頁的,舉例來說,如果上一次的換頁操作還沒有完畢或沒有成功,那么這次調用的換頁函數將返回一個名為DDERR_WASSTILLDRAWING的錯誤。對於這個問題,僅僅依靠該函數本身的一個較為簡單的解決辦法是:用While循環反復調用IDirectDrawSurface3::Flip函數,直到返回DD_OK為止。還應注意的一點是,即使Flip函數調用成功,也不是說馬上就能進行換頁,換頁被顯示系統安排在顯示器的下一次垂直回掃發生的時候進行,這就是說,換頁一定是與顯示器的刷新同步進行的。

然而,上面的這種方法其實是非常低效的,因為在循環的過程中,你將有可能多次調用Flip函數,而該函數不管是否成功,都會占用相對較長的CPU時間,於是就會產生這樣一種情況:雖然上一次循環調用Flip函數沒有成功,而在調用的過程中正在進行的換頁完畢了,所以當再次循環調用Flip函數的時候,此時已經離上次換頁結束有一段時間了,那么在這兩次的換頁中將會存在一個較長的時間差(這里所指的時間其實都相當短,可以說是在瞬間完成,其數量級為毫秒,遠遠超出人的感覺器官的靈敏度)。

最好的方法就是使用另一個用以檢測換頁操作行進狀態的GetFlipStatus函數。該函數相對於Flip函數來說只占用相當短的時間,所以可以使兩次換頁的時間間隔達到最小。如果上一次的換頁尚未結束,並且返回DDERR_WASSTILLDRAWING,你的應用程序可以利用這段時間來進行另外的任務,然后再次調用IDirectDrawSurface3::GetFlipStatus函數檢測換頁是否完畢。一直到函數返回DD_OK,就表示你被獲准可以進行下一次的換頁操作了。下面的例程片段演示了這種方法:

while(lpDDSBack->GetFlipStatus(DDGFS_ISFLIPDONE) ==

       DDERR_WASSTILLDRAWING)

// 等待上一次的換頁操作完畢,

// 應用程序可以在這里進行另外的任務。

ddrval = lpDDSPrimary->Flip(NULL, 0);

       這種方法打個比方說就是在你攻城之前先派個小兵去打探敵情,直到探得條件已經成熟再大軍壓境,而不必每次都興師動眾卻無功而返。

對於基於窗口模式的應用程序,你會取而代之使用Blit函數,這時,你可以使用另一個與GetFlipStatus函數相類似的IDirectDrawSurface3::GetBltStatus函數以檢測正在進行的Blit操作的狀態,這個函數與GetFlipStatus函數一樣,都會占用相當短的CPU時間,並且立即返回,利用它們可以使你的應用程序達到最快的換頁或Blit速度,而其間只有很少的時間損失。

      

五、使用Blit進行單色填充

Blt函數有很多用法,對你來說最熟悉不過的莫過於將圖象從一個頁面復制到另一個頁面,然而,Blt函數也可用於單色填充卻是鮮為人知的。不熟悉DirectDraw的程序員經常使用Win32的FillRect函數來實現這一功能,要知道FillRect是GDI的函數,其速度受到GDI模式的限制,其實是非常低效的。

在頁面上,你可以將最常用的顏色作為該頁面的底色,使用IDirectDrawSurface3::Blt函數可以為該頁面“打底”,或者用該函數來實現清屏(使整個頁面為黑色)。例如,如果你的應用程序畫面的底色是蘭色,首先填充一個DDBLTFX結構,並且設置其dwFillColor成員,該值必須與目標頁面的像素格式一致。對於基於調色板的頁面來說,該值應該是一調色板索引;對於一個16位RGB像素格式的頁面來說,該值應該是一個16位的像素顏色值。然后,調用Blt函數,指定其dwFlags成員為DDBLT_COLORFILL,表示使用該函數的單色矩形填充功能。“打底”之后,你就可以在該頁面上繪制圖象了。使用DirectDrawSurface的Blt函數是進行單色填充最快捷的一種方法,而調用常規的FillRect函數繪制實心矩形將耗時得多。

應該注意的是,如果頁面是8位像素格式的頁面,將一個顏色如紅色,用RGB(255,0,0)的三原色方式賦給DDBLTFX結構的dwFillColor成員是錯誤的,你必須給它紅色所對應的調色板索引值,可以用DDColorMatch函數獲得。DDColorMatch函數是在隨微軟DirectX SDK附帶的Ddutil.cpp文件中提供的,你必須將該文件連結到你的工程中去。如何連結在前面的章節中已有詳細的介紹。

下面的例程演示了如何對頁面進行清屏,即進行黑色填充:

DDBLTFX ddbltfx;

ddbltfx.dwSize = sizeof(ddbltfx);

ddbltfx.dwFillColor =0 ;

ddrval = lpDDSPrimary->Blt(

NULL, // 目標矩形

NULL, NULL, // 源頁面和源矩形

DDBLT_COLORFILL, &ddbltfx);

switch(ddrval)

{

case DDERR_WASSTILLDRAWING:

.     case DDERR_SURFACELOST:

case DD_OK:

default:

}

      

六、測定顯示硬件的能力

DirectDraw使用軟件仿真的方式來實現那些用戶的顯示硬件不支持的DirectDraw函數。為了加速你的DirectDraw應用程序的圖象顯示,你應該在創建了DirectDraw對象之后,測定用戶顯示硬件的能力,然后指揮你的應用程序盡可能的利用這些能力。

通過調用IDirectDraw2::GetCaps函數可以測定這些能力。不是所有的需要硬件支持的特性都可以由軟件仿真出來。如果你想要利用那些只有某些高級顯示設備(如高級的3D圖形加速卡,如VooDoo等)才能支持的特性,在你的用戶沒有裝備這些硬件設備時,你必須給他們提供一個可以選擇的方案,是退出還是用軟件仿真模式來玩,當然,后者是一個不得以而為之的辦法,有時候你的程序會變得跟蝸牛爬一樣。

 

七、在視頻RAM中儲存位圖

從視頻RAM到視頻RAM的Blit操作通常比從系統RAM到視頻RAM更有效。因此,你應該在視頻RAM中儲存盡可能多的用於顯示的圖象。

目前,絕大多數顯示適配器(顯卡)都可以在容納主頁面及其后台緩存之后還有一些富余。調用IDirectDraw2::GetCaps函數以獲得用戶的顯示硬件的能力,其中,DDCAPS結構的dwVidMemTotal和dwVidMemFree成員可以用來測定視頻RAM中可用內存的數量。如果你想親眼看一下它是怎樣工作的,使用包含在DirectX SDK中的DirectX Viewer(DirectX 查看器)例子。運行后,打開DirectDraw Devices文件夾,再打開主顯示驅動程序文件夾,於是就會顯示出視頻RAM的總量以及空余視頻RAM的數量。每當向DirectDraw對象中增加一個頁面,空余視頻RAM數量將減少,其減少量為該新增的頁面所占用的視頻RAM。

 

八、Triple Buffering(三緩沖)

在某些情況下,那就是,當顯示適配器(顯卡)擁有足夠的視頻RAM,那么,你就可以能夠使用Triple Buffering(三緩沖區)來加速你的應用程序的顯示速度。三緩沖區使用一個主頁面和兩個后台緩存。下面的例子演示了如何初始化一個三緩沖區方案:

// lpDDSPrimary,lpDDSMiddle和lpDDSBack是全局變量,

// 定義成LPDIRECTDRAWSURFACE型。

DDSURFACEDESC ddsd;

ZeroMemory (&ddsd, sizeof(ddsd));

// 創建一個主頁面,帶有兩個后台緩存。

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |

DDSCAPS_FLIP | DDSCAPS_COMPLEX;

ddsd.dwBackBufferCount = 2;

ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);

// 如果我們成功的創建了換頁鏈,

// 取出指向這些頁面的指針,用來進行換頁和Blit。

if(ddrval == DD_OK)

{

// 得到直接連結到主頁面的頁面(后台緩存)。

ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;

ddrval = lpDDSPrimary->GetAttachedSurface(&ddsd.ddsCaps,

&lpDDSMiddle);

if(ddrval != DD_OK) ;

              // 在此處顯示出錯信息。

}                 

因為在調用Flip函數進行換頁的時候,系統會自動將主頁面在三緩沖區換頁鏈中的三個頁面中循環,所以你並不需要關心三緩沖區換頁鏈中的每一個頁面。你只需要保持住指向主頁面和緊接主頁面的一個后台緩存的指針就可以了。指向主頁面的指針用來調用Flip函數進行換頁,指向后台緩存頁面的指針用來准備將要顯示的下一個畫面。要得到更多的信息,請參閱“換頁”。

建立三緩沖區換頁鏈的好處在於:你可以不用象雙緩沖區換頁鏈那樣,必須等到換頁操作結束后,才能向后台緩存繪制圖象(因為換頁時頁面被鎖定,不能被其它讀寫工作);相反,你可以在調用完Flip函數之后立即對后台緩存進行新的繪制工作,從而使換頁成為一種非同步的事件。如果你只使用一個后台緩存,那么在等待Flip函數返回DD_OK前,你的應用程序將讓這些空閑的時間白白浪費掉。

 

九、DirectDraw應用程序和窗口風格

如果你的DirectDraw應用程序是運行於窗口模式的,你可以用任何窗口風格來創建你的窗口。然而,全屏獨占模式的DirectDraw應用程序的窗口不能設置有WS_EX_TOOLWINDOW風格,否則可能會遭遇到不可預知的行為。WS_EX_TOOLWINDOW風格原是阻止一個窗口成為頂層窗口,而在DirectDraw的全屏獨占模式應用程序中,這卻是必須的。

為了正確的顯示,全屏獨占模式的應用程序最好帶有WS_EX_TOPMOST擴展窗口風格和WS_VISIBLE基本窗口風格。這些風格使得DirectDraw應用程序成為所有窗口的上層窗口,並且阻止GDI寫向主頁面。

下面的例子演示了在全屏獨占模式的應用程序中如何正確的初始化一個窗口:

////////////////////////////////////////////////////////

// 注冊窗口類,顯示窗口,

// 並且初始化所有DirectX和圖形對象。

////////////////////////////////////////////////////////

BOOL WINAPI InitApp(INT nWinMode)

{

WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

              wcex.hInstance = g_hinst;

wcex.lpszClassName = g_szWinName;

wcex.lpfnWndProc = WndProc;

wcex.style = CS_VREDRAW|CS_HREDRAW|CS_DBLCLKS;

wcex.hIcon = LoadIcon (NULL, IDI_APPLICATION);

wcex.hIconSm = LoadIcon (NULL, IDI_WINLOGO);

wcex.hCursor = LoadCursor (NULL, IDC_ARROW);

wcex.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);

wcex.cbClsExtra = 0 ;

wcex.cbWndExtra = 0 ;

wcex.hbrBackground = GetStockObject (NULL_BRUSH);

RegisterClassEx(&wcex);

g_hwndMain = CreateWindowEx(

WS_EX_TOPMOST,

g_szWinName,

g_szWinCaption,

              WS_VISIBLE|WS_POPUP,

0,0,CX_SCREEN,CY_SCREEN,

NULL,

NULL,

              g_hinst,

NULL);

if(!g_hwndMain)

return(FALSE);

SetFocus(g_hwndMain);

ShowWindow(g_hwndMain, nWinMode);

UpdateWindow(g_hwndMain);

return TRUE;

}

 

十、將真彩色匹配到幀緩沖區的色彩空間

當顯示設備不是處於24位真彩模式時,應用程序需要知道一個真彩RGB顏色是怎樣映射到一個幀緩沖區的色彩空間中去的。舉例來說,如果你正在編寫一個可運行於8、16或24位顯示模式的應用程序,在設置關鍵色或用Blit進行單色填充的時候,你所提供的顏色值必須與該頁面當前的色彩空間相一致,也就是與幀緩沖區的色彩空間一致。這就是說,如果你的應用程序運行於8位色彩模式,那么你必須提供一個調色板索引;如果運行於16位色彩模式,你必須提供一個相應的16位值;如果運行於24位真彩模式,你必須提供一個24位值。用戶可以選擇他所期望的顯示模式,那么你的程序就必須可以根據當前的顯示模式,將一個特定的顏色值(通常是真彩24位值)映射到正確的色彩空間中去。

盡管DirectDraw不會為你自動完成顏色匹配的任務,但是仍有許多方法來解決如何把你的關鍵色映射到幀緩沖區中去。這些方法是比較復雜的。在絕大多數情況下,你可以使用GDI內建的色彩匹配服務,加上DirectDraw的直接幀緩沖區訪問,來解決將一個24位顏色值匹配到不同的色彩空間中去。實際上,DirectX SDK中的Ddutil.cpp源文件包含了一個DDColorMatch函數,用它就可以完成任何色彩空間的匹配工作。它的工作原理十分巧妙,你並不需要了解每一種色彩空間的格式,它用的是“埋種子”的方法,其步驟如下:

  • 調用Win32的GetPixel函數,取頁面左上角(坐標為0,0)的像素值。
  • 調用Win32的SetPixel函數,用COLORREF結構來描述你的24位RGB顏色,將該色設置給頁面左上角。
  • 使用DirectDraw的Lock函數鎖定頁面,取得指向幀緩沖區內存的指針。
  • 從幀緩沖區中獲得真實的顏色值,調用Unlock函數將頁面解鎖。
  • 還原頁面左上角像素顏色為原始值,使用SetPixel函數。

DDColorMatch函數的速度並不是很快,這是因為它調用了Win32的GetPixel和SetPixel函數。然而,它提供了一個可靠和值得信賴的方法來求解顏色在不同的RGB色彩空間中是如何映射的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM