directshow基本概念

DirectDraw 提供了一個直接訪問顯示設備的軟件接口,並且關於圖形方面的概念與gdi相同.
Ddraw中的五大對象
DirectDraw 對象代表顯示適配器,並且通過 IDirectDraw, IDirectDraw2, 和 IDirectDraw4 接口將其方法暴露於開發者.在大多數情況下,開發者使用 DirectDrawCreate 函數創建一個 DirectDraw 對象,但也可以通過使用 CoCreateInstance COM 函數創建之. DirectDraw 對象 。DirectDraw 對象是 DirectDraw 應用程序的核心.它是你在建立 DirectDraw 應用程序時所要創建的第一個對象,再用它來創建所有其它相關的對象.通過調用 DirectDrawCreate 函數可以創建一個 DirectDraw 對象.
DirectDrawSurface 對象,通常簡稱爲"頁面(Surface)",代表了內存中的一塊區域,它存儲了可以顯示在顯示器上的圖象數據.通過調用 DirectDraw 對象的 IDirectDraw4::CreateSurface 函數可以創建一個與該 DirectDraw 對象相關聯的頁面.
DirectDrawPalette 對象,通常簡稱爲"調色板(Palette)",代表了一個可以爲頁面所使用的16或256色的調色板.它包含了一組 RGB 值的索引,用來描述頁面上的像素所使用的顏色值.對於像素位深度大於8的頁面,不需要使用調色板.通過調用 IDirectDraw4::CreatePalette 函數,可以創建一個 DirectDrawPalette 對象. DirectDrawPalette 對象通過 IDirectDrawPalette 接口爲開發者提供其函數性.
DirectDrawClipper 對象,通常簡稱爲"裁剪器(Clipper)",幫助開發者使 Blit(位塊傳送)操作限定在頁面的某一區域內,或不超出頁面的邊界範圍.通過調用 IDirectDraw4::CreateClipper 函數可以創建一個 DirectDrawClipper 對象. DirectDrawClipper 對象通過 IDirectDrawClipper 接口爲開發者提供其函數性.
DirectDrawVideoPort 對象代表了當前某些系統上的視頻端口(Vedio port)硬件.這個硬件允許直接的訪問幀緩存,而不需要通過 CPU 或使用 PCI 總線.通過對 DirectDRaw 對象調用 QueryInterface 函數(指定 IID_IDDVideoPortContainer 標誌符),可以創建一個 DirectDrawVideoPort 對象. DirectDrawVideoPort 對象通過 IDDVideoPortContainer 和 IDirectDrawVideoPort 接口爲開發者提供其函數性.

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


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

使用DirectDraw創建一個基本的應用程序
要使用 DirectDraw,你要先創建一個 DirectDraw 對象的實例,它代表了計算機的顯示適配器.接着你就可以通過接口函數來管理這個對象.另外,你還需要創建一個或更多 DirectDrawSurface 對象來使你的應用程序能顯示圖形.
步驟 1: 創建一個 DirectDraw 對象
要創建一個 DirectDraw 對象的實例,要先使用 DirectDrawCreate 函數. DirectDrawCreate 包含三個參數. 第一個參數獲得了一個代表顯示設備的全局唯一標識符(GUID). 這個 GUID 在大多數情況下被設爲 NULL, 表示 DirectDraw 使用系統缺省的顯示驅動. 第二個參數包含了 DirectDraw 創建以後的指針的地址. 第三個參數留作將來的擴展之用只能爲 NULL.

下面的例子演示瞭如何創建一個 DirectDraw 對象以及確定是否創建成功:

ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
if(ddrval == DD_OK)
{
    // lpDD 是一個可用的 DirectDraw 對象.
}
else
{
    // DirectDraw 對象不能被創建.
}

步驟 2: 確定應用程序的行爲
在你能夠改變顯示率之前, 你必須至少爲 IDirectDraw::SetCooperativeLevel 函數的 dwFlags 參數指定 DDSCL_EXCLUSIVE 和 DDSCL_FULLSCREEN 標誌. 這使得你的應用程序能完全控制顯示設備,而其它應用程序不能共享之.另外, DDSCL_FULLSCREEN 標誌設置應用程序爲獨佔(全屏)模式. 你的應用程序覆蓋了整個桌面,而且只有你的應用程序能夠寫到屏幕上. 然而桌面仍然是可用的.
以下的例子演示瞭如何使用 SetCooperativeLevel 函數:

HRESULT      ddrval;
LPDIRECTDRAW lpDD;     // 已用 DirectDrawCreate 創建
 
ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE |
    DDSCL_FULLSCREEN);
if(ddrval == DD_OK)
{
    // 獨佔模式成功.
}
else
{
    // 獨佔模式不成功.
    // 應用程序仍可運行.
}
 
如果 SetCooperativeLevel 沒有返回 DD_OK,你仍然可以運行你的程序.然而程序將不能取得獨佔模式,並且有可能不能實現你所需要的功能.在這種情況下,你也許應該顯示信息讓用戶來決定退出還是繼續.

如果你設置全屏,獨佔的控制等級,你必須把你的應用程序的窗口句柄傳遞給 SetCooperativeLevel 來允許 Windows 確定你的應用程序是否異常終止.例如,如果發生了一個一般保護(GP)錯誤而 GDI 換頁到 後臺緩存(back buffer),使用者將不能返回 Windows 屏幕.要防止這種現象的發生, DirectDraw 提供了一個在後臺運行的進程來捕獲發向 window 的消息. DirectDraw 使用這些消息來測定何時應用程序終止.然而這一特性強加了一些限制.你不得不指明爲你的應用程序獲取消息的窗口句柄,這意味着,如果你創建另一個窗口,你必須確定你指明瞭活動窗口.否則,你將會遇到問題,包括 GDI 的不可預知的行爲,或你按了 ALT + TAB 而沒有響應.

步驟 3:改變顯示模式
在你設置了應用程序的行爲之後,你就能使用 IDirectDraw::SetDisplayMode 函數來改變顯示分辨率.以下的例子演示瞭如何把顯示模式設爲 640×480×8 bpp:

HRESULT      ddrval;
LPDIRECTDRAW lpDD;  // 已創建
 
ddrval = lpDD->SetDisplayMode(640, 480, 8);
if(ddrval == DD_OK)
{
    // 顯示模式改變成功.
}
else
{
    // 顯示模式不能被改變.
    // 這種模式不被支持或
    // 另一個程序取得了獨佔模式.
}
 
當你設置顯示模式時,你應該確定如果用戶的硬件不能支持更高的顯示模式,你的應用程序將恢復到被大數顯示適配器所支持的模式. 例如,你的可以把應用程序製作爲把 640×480×8 作爲一個後備顯示模式來運行與所有支持它的系統上.

注意  如果顯示適配器不能被設置到想要的分辨率 IDirectDraw::SetDisplayMode 將返回一個 DDERR_INVALIDMODE 錯誤值.應此,在設置顯示模式前你應該使用 IDirectDraw::EnumDisplayModes 函數來確定用戶的顯示適配器的性能.

步驟 4:創建換頁頁面
在你設置了顯示模式之後,你應該創建頁面來安放你的應用程序.因爲 DDEx1 例子使用了 IDirectDraw::SetCooperativeLevel 函數設置成獨佔(全屏)模式,你就能創建換頁的頁面.你如果使用 SetCooperativeLevel 把模式設置爲 DDSCL_NORMAL, 你將只能創建位塊傳送的頁面.創建換頁頁面(flipping surfaces)需要兩步:

1、定義頁面參數
創建換頁頁面(flipping surfaces)的第一步是在一個 DDSURFACEDESC 結構中定義頁面參數.以下的例子演示了結構的定義以及創建換頁頁面所需的標誌.

// 創建帶有一個後臺緩存的主頁面.
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
    DDSCAPS_FLIP | DDSCAPS_COMPLEX;
 
ddsd.dwBackBufferCount = 1;
 
在這個例子中, dwSize 成員被設爲 DDSURFACEDESC 結構的大小.者可以預防任何 DirectDraw 函數返回不可用成員錯誤. (dwSize 成員是爲 DDSURFACEDESC 結構將來的擴展提供的.)

dwFlags 成員確定了 DDSURFACEDESC 結構中的哪一個成員將被填充有效信息.對於 DDEx1 例子, dwFlags 被設置爲指明你要使用 DDSCAPS 結構(DDSD_CAPS)以及你要創建一個後臺緩存(back buffer)(DDSD_BACKBUFFERCOUNT).

在這個例子中 dwCaps 成員指出這些標誌將在 DDSCAPS 結構中使用.在這種情況下,它指明瞭一個主頁面(primary surface)(DDSCAPS_PRIMARYSURFACE),一個換頁頁面(flipping surface)(DDSCAPS_FLIP),和一個複雜頁面(complex surface)(DDSCAPS_COMPLEX).

最後,這個例子指明瞭一個後臺緩存.後臺緩存是背景和精靈將被實際寫入的地方.然後後臺緩存被換頁到主頁面.在 DDEx1 例子中,後臺緩存的個數被設爲 1.然而,你可以創建顯存所允許的個數的後臺緩存.要得到關於創建一個或更多後臺緩存的信息,請參閱三緩衝(Triple Buffering).

頁面內存可以是顯示內存或系統內存.如果顯存不夠 DirectDraw 使用系統內存(例如,如果你在只有 1M 顯存的顯示適配器上指明超過一個後臺緩存(back buffer)).你也可以通過設置 DDSCAPS 結構的 dwCaps 成員爲 DDSCAPS_SYSTEMMEMORY 或 DDSCAPS_VIDEOMEMORY 指明只用系統內存或顯示內存.(如果你指明 DDSCAPS_VIDEOMEMORY,而創建頁面的有用顯存不夠, IDirectDraw::CreateSurface 將返回一個 DDERR_OUTOFVIDEOMEMORY 錯誤.)
2、創建頁面
在 DDSURFACEDESC 結構被填充後,你可以使用它和由 DirectDrawCreate 函數創建的指向 DirectDraw 對象的指針 lpDD,來調用 IDirectDraw::CreateSurface 函數,如下所示:

ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);
if(ddrval == DD_OK)
{
    // lpDDSPrimary 指向新頁面.
}
else
{
    // 頁面未創建.
    return FALSE;
}
 
lpDDSPrimary 參數將指向如果 CreateSurface 調用成功返回的主頁面(primary surface).

在指向主頁面的指針可用後,你可以使用 IDirectDrawSurface3::GetAttachedSurface 函數來提取一個指向後臺緩存(back buffer)的指針,如下所示:

ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddcaps, &lpDDSBack);
if(ddrval == DD_OK)
{
    // lpDDSBack 指向後臺緩存.
}
else
{
    return FALSE;
}
 
通過提供主頁面(primary surface)的地址以及使用 DDSCAPS_BACKBUFFER 標誌設置特性值, 如果 IDirectDrawSurface3::GetAttachedSurface 調用成功 lpDDSBack 參數將指向後臺緩存(back buffer).

步驟 5:着色到頁面
在一個主頁面(primary surface)和一個後臺緩存(back buffer)被創建之後, DDEx1 例子通過使用標準的 Windows GDI 函數向主頁面和後臺緩存着色了一些文本,如下所示:

if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
{
    SetBkColor(hdc, RGB(0, 0, 255));
    SetTextColor(hdc, RGB(255, 255, 0));
    TextOut(hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg));
    lpDDSPrimary->ReleaseDC(hdc);
}
 
if (lpDDSBack->GetDC(&hdc) == DD_OK)
{
    SetBkColor(hdc, RGB(0, 0, 255));
    SetTextColor(hdc, RGB(255, 255, 0));
    TextOut(hdc, 0, 0, szBackMsg, lstrlen(szBackMsg));
    lpDDSBack->ReleaseDC(hdc);
}
 
這個例子使用了 IDirectDrawSurface3::GetDC 函數來獲得設備描述句柄,同時它內在地瑣定了頁面.如果你不使用需要設備描述句柄的 Windows 函數, 你可以使用 IDirectDrawSurface3::Lock 和 IDirectDrawSurface3::Unlock 函數來鎖定和解鎖後臺緩存(back buffer).

鎖定頁面內存(不論整個頁面或一部分頁面)使得你的應用程序和系統位塊傳送器不能同時獲得對主頁面的訪問.這預防了由於你的應用程序正在寫向頁面內存而引發的錯誤.另外,在主頁面被解鎖前你的應用程序不能換頁.

在頁面解鎖後,這個例子使用標準的 Windows GDI 函數: SetBkColor 來設置背景顏色, SetTextColor 來選擇放在背景上的文本的顏色, 以及 TextOut 來把文本和背景顏色打印在頁面上.

在文本被寫到緩存後,這個例子使用 IDirectDrawSurface3::ReleaseDC 函數來解鎖頁面和釋放句柄.一旦你的應用程序結束寫向後臺緩存(back buffer),你必須調用 IDirectDrawSurface3::ReleaseDC 或 IDirectDrawSurface3::Unlock,視你的程序而定.在頁面被解鎖前你的應用程序不能換頁.

典型的,你寫向後臺緩存,然後換頁到主頁面(primary surface)以被顯示.對於 DDEx1,在第一次換頁前沒有明顯的延遲,所以 DDEx1 在初始函數中寫向主緩存來預防顯示頁面前的延遲. 就如你在這個指南後面的步驟中將要看到的那樣, DDEx1 只在 WM_TIMER 期間寫向後臺緩存.通常你只在初始函數或標題頁中寫向主頁面.

注意  在通過使用 IDirectDrawSurface3::Unlock 解鎖頁面後,頁面內存的指針是不可用的. 你必須重新使用 IDirectDrawSurface3::Lock 來獲得一個可用的頁面內存的指針.

步驟 6:寫向頁面
DDEx1 中 WM_TIMER 消息的前一半專門寫向後臺緩存(back buffer),如下所示:

case WM_TIMER:
    // 換頁.
    if(bActive)
    {
        if (lpDDSBack->GetDC(&hdc) == DD_OK)
        {
            SetBkColor(hdc, RGB(0, 0, 255));
            SetTextColor(hdc, RGB(255, 255, 0));
            if(phase)
            {
                TextOut(hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg));
                phase = 0;
            }
            else
            {
                TextOut(hdc, 0, 0, szBackMsg, lstrlen(szBackMsg));
                phase = 1;
            }
            lpDDSBack->ReleaseDC(hdc);
        }
 
調用 IDirectDrawSurface3::GetDC 函數的那行代碼鎖定後臺緩存(back buffer)來預備寫操作. SetBkColor 和 SetTextColor 函數設置背景和文本的顏色.

接着, phase 變量決定了應該寫主緩存消息還是後臺緩存消息. 如果 phase 等於 1,將寫主頁面(primary surface)消息,同時 phase 被設爲 0.如果 phase 等於 0,將寫後臺緩存消息,同時 phase 被設爲 1.注意,其實兩種情況下消息都是寫到後臺緩存裏的.

在消息被寫到後臺緩存後,後臺緩存被使用 IDirectDrawSurface3::ReleaseDC 函數解鎖.

步驟 7:換頁
在頁面內存解鎖後,你就可以使用 IDirectDrawSurface3::Flip 函數來換頁後臺緩存(back buffer)到主頁面(primary surface)了,如下所示:

while(1)
{
    HRESULT ddrval;
    ddrval = lpDDSPrimary->Flip(NULL, 0);
    if(ddrval == DD_OK)
    {
        break;
    }
    if(ddrval == DDERR_SURFACELOST)
    {
        ddrval = lpDDSPrimary->Restore();
        if(ddrval != DD_OK)
        {
            break;
        }
    }
    if(ddrval != DDERR_WASSTILLDRAWING)
    {
        break;
    }
}
 
在這個例子裏, lpDDSPrimary 參數指明瞭主頁面(primary surface) 和與之關聯的後臺緩存(back buffer). 當調用 IDirectDrawSurface3::Flip 後,前後頁面被交換(只改變了頁面的指針,數據並未實際移動). 如果換頁成功返回 DD_OK,程序從 while 循環跳出.

如果換頁返回 DDERR_SURFACELOST,可以嘗試使用 IDirectDrawSurface3::Restore 函數來恢復頁面.如果恢復成功,程序跳回 IDirectDrawSurface3::Flip 調用並再次嘗試.如果恢復不成功,程序從循環跳出,並返回一個錯誤.

注意  當你調用 IDirectDrawSurface3::Flip, 換頁並不立即完成,而是在系統發生下一次垂直空掃時.如果前一次換頁還未發生, IDirectDrawSurface3::Flip 返回 DDERR_WASSTILLDRAWING.在這個例子裏, IDirectDrawSurface3::Flip 持續調用直到返回 DD_OK.

步驟 8: 釋放 DirectDraw 對象
當你按 F12 時, DDEx1 程序在退出前執行 WM_DESTROY 消息.這個消息調用 finiObjects 函數,它包含了所有的 IUnknown::Release 調用,如下所示:

static void finiObjects(void)
{
    if(lpDD != NULL)
    {
        if(lpDDSPrimary != NULL)
        {
            lpDDSPrimary->Release();
            lpDDSPrimary = NULL;
        }
        lpDD->Release();
        lpDD = NULL;
    }
} // finiObjects
 
程序檢查 DirectDraw 對象(lpDD)和 DirectDrawSurface 對象(lpDDSPrimary)的指針是否不等於 NULL. 然後 DDEx1 調用 IDirectDrawSurface3::Release 函數把 DirectDrawSurface 對象的 引用計數(reference count)減 1.因爲這使得引用計數變爲 0, DirectDrawSurface 對象被釋放. DirectDrawSurface 指針通過值被設爲 NULL 而被釋放.然後,程序調用 IDirectDraw::Release 把 DirectDraw 對象的引用計數減爲 0,釋放 DirectDraw 對象.它的指針同樣通過值被設爲 NULL 而被釋放.


注意  要在你的應用程序中成功地使用 GUID, 你必需要麼在所有的 include 和 define 語句前先定義 INITGUID, 要麼連接到 Dxguid.lib 庫.你只要在你的源文件的一個模塊中定義 INITGUID 就行了.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章