DirectX5.0最新遊戲程式設計指南 DirectDraw教程篇 四、使用覆蓋表面 (轉)

worldblog發表於2007-12-04
DirectX5.0最新遊戲程式設計指南 DirectDraw教程篇 四、使用覆蓋表面 (轉)[@more@]

四、使用覆蓋表面
  本例將使用 SDK包含的Mosquito範例一步一步地說明怎樣在程式中使用DirectDraw和支援的覆蓋表面。Mosquito使用覆蓋表面的翻轉鏈而沒有位塊傳輸到主表面將運動點陣圖顯示在桌面上。Mosquito程式調整覆蓋表面的特徵以適應硬體的限制。

1、建立一個主表面
  要使用覆蓋表面,必須先要初始化一個主表面,覆蓋表面將顯示在該主表面上。Mosquito用如下程式碼建立了一個主表面:
// Zero-out the structure and set the dwSize member.
  ZeroMemory(&ddsd, sizeof(ddsd));
  ddsd.dwSize = sizeof(ddsd);

  // Set flags and create a primary surface.
  ddsd.dwFlags = DDSD_CAPS;
  ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
  ddrval = g_lpdd->CreateSurface(&ddsd, &g_lpddsPrimary, NULL );
 
  程式先初始化將要使用的DDSURFACEDESC結構,然後設定適當的標誌IDirectDraw2::CreateSurface方法建立主表面。在對該方法的呼叫中,第一個引數是描述將要建立的表面的DDSURFACEDESC結構的指標;第二個引數是一個變數的指標,如果呼叫成功,該變數將接收IDirectDrawSurface介面的指標;第三個引數設為NULL表明沒有COM集合。

2、檢測硬體對覆蓋的支援
  初始化DirectDraw後,需要檢測裝置是否支援覆蓋表面。因為DirectDraw不能模擬覆蓋,所以如果硬體不支援覆蓋,就不能繼續下面的工作。你可以用IDirectDraw2::GetCaps方法獲取硬體裝置程式的能力檢測覆蓋支援。在呼叫該方法之後,檢視DDCAPS結構中的dwFlags成員是否包含有DDCAPS_OVERLAY標誌。若有就表明支援覆蓋,否則就不支援。
  下面的程式碼是Mosquito程式中的一部分,它表明了怎樣檢測硬體的覆蓋支援能力:
BOOL AreOverlaysSupported()
{
  DDCAPS  capsDrv;
  HRESULT ddrval;

  // Get capabilities to detene Overlay support.
  ZeroMemory(&capsDrv, sizeof(capsDrv));
  capsDrv.dwSize = sizeof(capsDrv);

  ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
  if (FAILED(ddrval))
  return FALSE;

  // Does the driver support overlays in the current mode?
  // (Currently the DirectDraw emulation layer does not support overlays.
  // Overlay related s will fail without hardware support). 
  if (!(capsDrv.dwCaps & DDCAPS_OVERLAY))
  return FALSE;

  return TRUE;
}
  程式首先呼叫IDirectDraw2::GetCaps方法獲取裝置驅動程式的能力。第一個引數是DDCAPS結構的地址指標;因為程式不需要關模擬的資訊,所以第二個引數就設為NULL。獲取驅動程式的能力後,程式使用了邏輯“與”來檢查dwFlags成員是否包含有 DDCAPS_OVERLAY標誌。若否,程式返回FALSE表明失敗。若是,就返回TRUE表明顯示裝置支援覆蓋表面。

3、建立一個覆蓋表面
  如果知道顯示裝置支援覆蓋表面,就可以建立一個。因為沒有指明裝置怎樣支援覆蓋表面的標準,所以不能夠期望建立任意大小的畫素格式的表面。另外,也不要期望第一次建立覆蓋表面就會成功。因此,必須作好準備進行多次建立的嘗試,直到有一個能夠工作為止。
  Mosquito程式在建立表面時遵循“best case to worst case”的原則,首先嚐試建立一個三緩衝頁翻轉複雜覆蓋表面。如果嘗試失敗,程式就改變方法嘗試用其它通用的迅速格式來。下面的程式碼就是這一思路的表現:
  ZeroMemory(&ddsdOverlay, sizeof(ddsdOverlay));
  ddsdOverlay.dwSize = sizeof(ddsdOverlay);

  ddsdOverlay.dwFlags= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |
  DDSD_BACKBUFFERCOUNT| DDSD_PIXELFORMAT;
  ddsdOverlay.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_FL|
  DDSCAPS_COMPLEX | DDSCAPS_VOMEMORY;
  ddsdOverlay.dwWidth  =320;
  ddsdOverlay.dwHeight =240;
  ddsdOverlay.dwBackBufferCount=2;

  // Try to create an overlay surface using one of the pixel formats in our
  // global list.
  i=0;
  do{
  ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
  // Try to create the overlay surface
  ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
  } while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS) );
 
  程式設定DDSURFACEDESC結構中的標誌和值以反映三緩衝頁翻轉複雜覆蓋表面,然後迴圈。在迴圈中,程式嘗試用各種常用的畫素格式建立要求的表面。如果嘗試成功,迴圈就終止。如果嘗試失敗,說明很有可能是顯示硬體沒有足夠的顯示支援三緩衝的方案或者硬體根本就不支援翻轉覆蓋表面。在這種情況下,在最小要求的配置下使用一個單一的非翻轉覆蓋表面,程式碼如下:
  // If we failed to create a triple buffered complex overlay surface, try
  // again with a single non-flippable buffer.
  if(FAILED(ddrval))
  {
  ddsdOverlay.dwBackBufferCount=0;
  ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY;
  ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT;

  // Try to create the overlay surface
  ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
  i=0;
  do{
  ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
  ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
  } while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS) );

  // We couldn't create an overlay surface.  Exit, returning failure.
  if (FAILED(ddrval))
  return FALSE;
  }
 
上面的程式碼對DDSURFACEDESC結構中的標誌和值復原來反映一個單一的非翻轉覆蓋表面,然後透過畫素格式的迴圈嘗試建立表面。如果建立表面成功,迴圈就停止。如果不成功,程式返回FALSE表明建立表面失敗。在成功地建立覆蓋表面之後,就可將點陣圖裝入其中以供顯示。

4、顯示覆蓋表面
  建立了覆蓋表面之後就可以顯示它了。通常,硬體在用於顯示覆蓋的矩形的位置和畫素格式上加上對齊。另外,還需要經常透過調整目的矩形的寬度來說明最小要求的拉伸因子以成功地顯示覆蓋表面。Mosquito程式按照以下的步驟準備和顯示覆蓋表面。
4.1、檢測顯示的最小要求
  大部分的顯示硬體在顯示覆蓋時都會加上約束。你必須很仔細地調整覆蓋使之滿足這些約束。可以透過呼叫IDirectDraw2::GetCaps方法獲得有關這些約束的資訊。該方法填充的結構DDCAPS包含了有關覆蓋能力和使用約束的資訊。不同硬體的約束是不同的,因此必須始終要檢視包含在dwFlags成員的標誌以確定附加的是哪一種約束。
Mosquito程式開始時先獲取硬體的能力,然後採用基於最小拉伸因子的方法,如下所示:
  // Get driver capabilities
  ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
  if (FAILED(ddrval))
  return FALSE;

  // Check the minimum stretch and set the local variable accordingly.
  if(capsDrv.dwCaps & DDCAPS_OVERLAYSTRETCH)
  uStretchFactor1000 = (capsDrv.dwMinOverlayStretch>1000) ? capsDrv.dwMinOverlayStretch : 1000;
  else
  uStretchFactor1000 = 1000;
 
  上面的程式碼呼叫IDirectDraw2::GetCaps方法獲取硬體的能力。在本例中,第一個引數是DDCAPS結構的指標;第二個引數是NULL,表明了不需要獲取有關模擬的資訊。程式在一個臨時變數中保留了最小拉伸因子以備以後之用。如果驅動程式報告出的拉伸因子大於1000,就表明驅動程式要求所有的目的矩形沿X軸的方向拉伸。例如,若拉伸因子是1.3,源矩形寬320個畫素,目的矩形就必須至少要有416(320X1.3=416)個畫素的寬。如果驅動程式報告出的拉伸因子小於1000,就表明驅動程式能夠顯示比源矩形小的覆蓋,但不能伸展覆蓋。
  下面的程式碼是測定描述驅動程式的大小對齊約束的值:
  // Grab any alignment restrictions and set the local variables acordingly.
  uSrcSizeAlign = (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)?capsDrv.dwAlignSizeSrc:0;
  uDestSizeAlign= (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)?capsDrv.dwAlignSizeDest:0;
 
  例中使用了更多的臨時變數來儲存從dwAlignSizeSrc和dwAlignSizeDest成員中獲得的大小對齊約束。這些值提供了有關畫素寬度對齊約束的資訊,並且在以後設定源矩形和目的矩形的大小時需要用到。源矩形和目的矩形必須是這些值的倍數。
最後,程式測定描述目的矩形邊界對齊約束的值:
  // Set the "destination position alignment" global so we won't have to
  // keep calling GetCaps() every time we move the overlay surface.
  if (capsDrv.dwCaps & DDCAPS_ALIGNBOUNDARYDEST)
  g_dwOverlayXPositionAlignment = capsDrv.dwAlignBoundaryDest;
  else
  g_dwOverlayXPositionAlignment = 0;
 
  上面的程式碼使用了一個全域性變數來儲存目的矩形邊界約束的值,該值是從dwAlignBoundaryDest成員中的得來的,在以後程式重新放置覆蓋時將會用到。你必須設定目的矩形左上角的X座標在畫素格式上同該值對齊。也就是說,如果該值是4,就只能指定左上角的X座標為0,4,8,12等畫素寬的目的矩形。Mosquito程式首先在0,0處顯示覆蓋,於是在第一次顯示覆蓋之前就不需要獲取約束資訊。但是因為不同應用程式的實現過程可能不同 ,所以你可能需要在顯示覆蓋之前檢查這些資訊以調整目的矩形。
2、設定源矩形和目的矩形
  在獲得了驅動程式的覆蓋約束之後,就應該設定有關源矩形和目的矩形的值,確保能夠正確顯示覆蓋。下面的程式碼就設定了源矩形的特徵:
  // Set initial values in the RECT.
  rs.left=0; rs.top=0;
  rs.right = 320;
  rs.bottom = 240;

  // Apply size alignment restrictions, if necessary.
  if (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC && uSrcSizeAlign)
  rs.right -= rs.right % uSrcSizeAlign;
 
  上面的程式碼設定了包含整個表面大小的初始值。如果裝置驅動程式要求大小對齊,程式就調整源矩形來保證。程式調整了源矩形的寬度使之比初始值要小,這是因為如果不是完全重新建立表面,就不能夠擴充套件寬度。
  在設定了源矩形的大小後,需要設定和調整目的矩形的大小。這一過程需要稍微多一點的工作,因為目的矩形可能需要先拉伸再調整以符合大小對齊約束。下面的程式碼根據最小拉伸因子來設定和調整目的矩形的大小:
  // Set up the destination RECT, starting with the source RECT values.
  // We use the source RECT dimensions instead of the surface dimensions in
  // case they differ.
  rd.left=0; rd.top=0;
  rd.right  = (rs.right*uStretchFactor1000+999)/1000;  // (Adding 999 avoids integer truncation problems.)

  // (This isn't required by DDraw, but we'll stretch the
  //  height, too, to maintain ect ratio).
  rd.bottom = rs.bottom*uStretchFactor1000/1000;
  前面的程式碼先設定目的矩形的左上角位置,再根據最小拉伸因子設定目的矩形的寬度。根據拉伸因子調整矩形時,注意程式在寬度和拉伸因子的乘積上又加了999,這是為了避免出現整數截斷,整數截斷會導致矩形同最小拉伸因子的要求不一致。程式在拉伸寬度後也拉伸了矩形的高度。不過,對高度的拉伸並不是必須的,這裡只是為了保持點陣圖的長寬比率避免出現失真的現象。
拉伸目的矩形後,程式對其進行調整以保持和大小對齊約束一致,下面是相應的程式碼:
  // Adjust the destination RECT's width to comply with any imposed
  // alignment restrictions.
  if (capsDrv.dwCaps & DDCAPS_ALIGNSIZEDEST && uDestSizeAlign)
  rd.right = (int)((rd.right+uDestSizeAlign-1)/uDestSizeAlign)*uDestSizeAlign;
 
  程式檢測硬體能力的標誌,檢視驅動程式是否加了矩形大小對齊約束。如果是,就增加目的矩形的寬度使之滿足大小對齊約束。這裡對矩形的調整是擴充套件其寬度而不能減少其寬度,因為減少寬度可能會導致目的矩形比最小拉伸因子要求的還要小,從而引起顯示覆蓋表面失敗。
3、顯示覆蓋表面
  在設定了源矩形和目的矩形後,就可以顯示覆蓋了。如果顯示覆蓋之前的準備工作正確,顯示覆蓋會很簡單。Mosquito程式用如下的程式碼來顯示覆蓋:
  // Set the flags we'll send to UpdateOverlay
  dwUpdateFlags = DDOVER_SHOW | DDOVER_DDFX;

  // Does the overlay hardware support source color keying?
  // If so, we can hide the black background around the image.
  // This probably won't work with YUV formats
  if (capsDrv.dwCKeyCaps & DDCKEYCAPS_SRCOVERLAY)
  dwUpdateFlags |= DDOVER_KEYSRCOVERRIDE;

  // Create an overlay FX structure so we can specify a source color key.
  // This information is ignored if the DDOVER_SRCKEYOVERRIDE flag isn't set.
  ZeroMemory(&ovfx, sizeof(ovfx));
  ovfx.dwSize = sizeof(ovfx);

  ovfx.dckSrcColorkey.dwColorSpaceLowValue=0; // Specify black as the color key
  ovfx.dckSrcColorkey.dwColorSpaceHighValue=0;

  // Call UpdateOverlay() to displays the overlay on the screen.
  ddrval = g_lpddsOverlay->UpdateOverlay(&rs, g_lpddsPrimary, &rd, dwUpdateFlags, &ovfx);
  if(FAILED(ddrval))
  return FALSE;
 
  程式開始在臨時變數dwUpdateFlags中設定了DDOVER_SHOW和DDOVER_DDFX標誌,指明該覆蓋是第一次顯示,硬體應該使用包含在DDOVERLAYFX結構中的效果資訊完成這一工作。然後,程式檢查DDCAPS結構確定覆蓋是否支援源Color Key。如果是,DDOVER_KEYSRCOVERRIDE標誌就包含在dwUpdateFlags變數中利用源Color Key,程式也據此設定Color Key。
  準備工作完成之後,程式呼叫IDirectDrawSurface3::UpdateOverlay方法來顯示覆蓋。在對該方法的呼叫中,第一個引數和第三個引數是已調整的源矩形和目的矩形的地址。第二個引數是覆蓋顯示在其上的主表面的地址。第四個引數是 包括了放置於此前準備的dwUpdateFlags變數中的標誌。第五個引數是DDOVERLAYFX結構的地址,該結構中的成員將設定同那些標誌相匹配。
  如果硬體只支援一個覆蓋表面而且該表面正在使用,UpdateOverlay方法就會失敗,並返回DDERR_OUTOFCAPS。另外,有可能硬體報告出的最小拉伸因子過小,在UpdateOverlay方法失敗後,你就需要嘗試減少目的矩形的寬度來應付這種可能性。不過,這種情況很少發生,在Mosquito中也只是簡單地返回一個錯誤資訊。

5、覆蓋的顯示位置
  顯示覆蓋表面之後,有時可能就不需要對覆蓋左其它的操作了。但有些還需要重新放置覆蓋,改變覆蓋的顯示位置。Mosquito程式就使用IDirectDrawSurface3::SetOverlayPosition方法重新放置覆蓋,程式碼如下:
  // Set X- and Y-coordinates
  .
  .
  .
  // We need to check for any alignment restrictions on the X position
  // and align it if necessary.
  if (g_dwOverlayXPositionAlignment)
  dwXAligned = g_nOverlayXP- g_nOverlayXPos % g_dwOverlayXPositionAlignment;
  else
  dwXAligned = g_nOverlayXPos;

  // Set the overlay to its new position.
  ddrval = g_lpddsOverlay->SetOverlayPosition(dwXAligned, g_nOverlayYPos);
  if (ddrval == DDERR_SURFACELOST)
  {
  if (!RestoreAllSurfaces())
  return;
  }
 
  程式開始對齊矩形以滿足可能存在的任何目的矩形邊界對齊約束。當程式此前呼叫IDirectDraw2::GetCaps方法時,全域性變數 g_dwOverlayXPositionAlignment已經設定為同DDCAPS結構中dwAlignBoundaryDest成員所報告出的值相等。如果存在目的矩形約束,程式就據此調整新的X座標為畫素對齊的。若不滿足要求,覆蓋表面就不能顯示。
  在完成了對X座標的調整之後,程式呼叫IDirectDrawSurface3::SetOverlayPosition方法重新放置覆蓋。在呼叫中第一個引數是對齊的新的X座標,第二個引數的新的Y座標。這些值表明了覆蓋左上角新的位置。這裡並不需要取得寬度和高度資訊,因為DirectDraw在開始用
IDirectDrawSurface3::UpdateOverlay方法顯示覆蓋時就已經獲得了表面大小的資訊。如果因為一個或多個表面丟失而引起的重新放置覆蓋表面的失敗,Mosquito程式就呼叫一個應用定義的來恢復這些表面並重新裝入它們的點陣圖。
  注意,不要使用太靠近目標表面的右、下邊界的座標。因為IDirectDraw2::SetOverlayPosition方法並不執行剪下功能,所以使用那些可能導致覆蓋超出目標表面邊界的座標會引起呼叫的失敗,並返回DDERR_INVALIDPOSITION。

6、隱藏覆蓋表面
  如果不再需要一個覆蓋表面或只想不讓覆蓋可見,就可以設定適當的標誌呼叫IDirectDrawSurface3::UpdateOverlay方法來隱藏該覆蓋表面。Mosquito用以下程式碼隱藏覆蓋表面並準備關閉應用程式:
void DestroyOverlay()
{
  if (g_lpddsOverlay){
  // Use UpdateOverlay() with the DDOVER_HIDE flag to remove an overlay
  // from the display.
  g_lpddsOverlay->UpdateOverlay(NULL, g_lpddsPrimary, NULL, DDOVER_HIDE, NULL);
  g_lpddsOverlay->Release();
  g_lpddsOverlay=NULL;
  }
}
  在呼叫IDirectDrawSurface3::UpdateOverlay時,對源矩形和目的矩形指定了NULL,因為在隱藏覆蓋的過程中不需要源矩形和目的矩形。
同理,第五個引數也被指定為NULL是因為不使用覆蓋效果。第二個引數是目標表面的指標。最後,程式在第四個引數使用 DDOVER_HIDE標誌表明該覆蓋將從視口中取消。
  程式在隱藏覆蓋之後,釋放了它的IDirectDrawSurface3介面,並且將全域性變數設為NULL使之變得無效。對於Mosquito程式來說,覆蓋就不再需要了。如果在應用程式中還需要使用該覆蓋,就只需簡單地隱藏覆蓋,而不要釋放它,然後在需要的時候再重新顯示。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-987898/,如需轉載,請註明出處,否則將追究法律責任。

相關文章