Creating an Immediate Mode Application

[Previous] [Next]

Setting up your program to use Direct3D involves the following steps, several of which were covered in Chapter 3. I'll go over a few additional aspects of the first five steps and then describe the final process of creating the Direct3D device and the depth buffer.

  1. Initializing the application
  2. Determining the version of DirectX that's available (if you choose not to assume that DirectX 7 is available as we do in our code for this book)
  3. Enumerating the available Direct3D devices
  4. Selecting the Direct3D device
  5. Getting an IDirectDraw7 interface with DirectDrawCreateEx
  6. Getting an IDirect3D7 interface by using the QueryInterface method of the IDirectDraw7 interface, then creating a Direct3D device
  7. Enumerating the depth-buffer format and creating a depth buffer to use for rendering your 3D world

In this chapter, you'll find all the code necessary to perform these steps and create a valid Direct3D device, thus enabling you to render your 3D world.

We now need to modify the CD3DFramework7::CreateEnvironment routine from Chapter 3 to create the Direct3D, Direct3DDevice, and depth buffer objects. Here's the revised version of this routine:

 //------------------------------------------------------------------- // Name: CreateEnvironment // Desc: Creates the internal objects for the framework //------------------------------------------------------------------- HRESULT CD3DFramework7::CreateEnvironment( GUID* pDriverGUID,                                             GUID* pDeviceGUID,                                            DDSURFACEDESC2* pMode,                                                DWORD dwFlags ) {     HRESULT hr;     // Select the default memory type, depending on whether the      // device is a hardware device or a software device.     if( IsEqualIID( *pDeviceGUID, IID_IDirect3DHALDevice) )         m_dwDeviceMemType = DDSCAPS_VIDEOMEMORY;     else if( IsEqualIID( *pDeviceGUID, IID_IDirect3DTnLHalDevice) )         m_dwDeviceMemType = DDSCAPS_VIDEOMEMORY;     else         m_dwDeviceMemType = DDSCAPS_SYSTEMMEMORY;     // Create the DirectDraw object.     hr = CreateDirectDraw( pDriverGUID, dwFlags );     if( FAILED( hr ) )         return hr;     // Create the front and back buffers, and attach a clipper.     if( dwFlags & D3DFW_FULLSCREEN )         hr = CreateFullscreenBuffers( pMode, dwFlags );     else         hr = CreateWindowedBuffers( pMode, dwFlags );     if( FAILED( hr ) )         return hr;     // Create the Direct3D object and the Direct3DDevice object.     hr = CreateDirect3D( pDeviceGUID, dwFlags );     if( FAILED( hr ) )         return hr;     // Create and attach the z-buffer.     if( dwFlags & D3DFW_ZBUFFER )         hr = CreateZBuffer( pDeviceGUID, dwFlags );     if( FAILED( hr ) )         return hr;     return S_OK; } 

We'll go over the added calls to this routine later in the chapter.

Depending on the device type you create (HAL, RGB, and so on), you should have your software select the memory type used for your main surfaces, textures, and vertex buffers that works most effectively with your code design. In addition to standard video memory, DirectDraw provides support for the Accelerated Graphics Port (AGP) architecture that allows creating surfaces in nonlocal video memory. There are two AGP architecture implementations: the execute model and the DMA model. With the execute model, display devices support the same features for both nonlocal (AGP) and local (standard) video memory surfaces. With the DMA model, DirectDraw uses nonlocal video memory for any texture surfaces you create if there is no local video memory remaining (unless you ask for local video memory). For other surface types, surfaces will be created only in local video memory unless your program requests that nonlocal video memory be used.

To determine whether the device driver supports texturing from nonlocal video memory surfaces, you can check to see whether the D3DDEVCAPS_TEXTURENONLOCALVIDMEM flag is set. You can acquire this flag from the 3D device capabilities list, which you can acquire by using the IDirect3DDevice7::GetCaps method.

Step 1: Initializing the Application

The first step in creating the Direct3D portion of your program is to initialize all the variables you'll be using. The main objective of this step is to clear out all the objects and structures so that you can create the objects you need and then fill in the structures with the settings you want to use for your 3D world, such as shading parameters and clippers. The CD3DFramework7 constructor shown below initializes all its member variables, defining items such as the z-buffer, the back buffers, and the device.

 //------------------------------------------------------------------- // Name: CD3DFramework7 // Desc: The constructor, which clears static variables //------------------------------------------------------------------- CD3DFramework7::CD3DFramework7() {      m_hWnd             = NULL;      m_bIsFullscreen    = FALSE;      m_bIsStereo        = FALSE;      m_dwRenderWidth    = 0L;      m_dwRenderHeight   = 0L;      m_pddsFrontBuffer  = NULL;      m_pddsBackBuffer   = NULL;      m_pddsBackBufferLeft = NULL;      m_pddsZBuffer      = NULL;      m_pd3dDevice       = NULL;      m_pDD              = NULL;      m_pD3D             = NULL;      m_dwDeviceMemType  = NULL; } 

Step 2: Determining the DirectX Version

Version checking is the process of determining which version of DirectX is installed on the target system. The amount of version checking you need to do depends greatly on how you're going to use Direct3D. If you're just a hobbyist developing for your own system, you already know that you have the latest and greatest version of DirectX installed, so you don't have to check. If you're writing a completely new DirectX 7 application, you'll probably ship your product with the latest DirectX run-time library, which your setup program will install if the target system is running Microsoft Windows 95 or Windows 98. If it's running Microsoft Windows NT 4 or earlier, you can have your software just refuse to install or run. Windows 2000 ships with full DirectX 7 support, so if the target system is running Windows 2000, your software can use the newest DirectX capabilities available. Most companies shipping 3D games and other software rely on your having the newest version of DirectX installed—since they install it from their game CD during the game install.

However, if you choose to support previous versions of DirectX (for whatever reason), you need to perform more elaborate version checking. You might also want to avoid the standard error messages that pop up when you link to ddraw.lib rather than using LoadLibrary and GetProcAddress. If you don't want the user to receive the standard system error message, just use QueryInterface for the interfaces you need, handle failures with an error message, and then exit. Your best option is to create a Windows 95 or Windows 98 setup program that updates the target system to the latest version of DirectX during setup. Keep in mind, however, that on Windows NT and Windows 2000, only administrators are allowed to update critical portions of the operating system (such as DirectX), so your setup programs can't upgrade the DirectX version.

If you choose to use version checking so that your software runs on the greatest number of platforms, you can check for the availability of various DirectX interfaces. By working your way up from interfaces supported by DirectX 2 to interfaces supported only by DirectX 7, you can determine the most current version of DirectX available. By finding out exactly which version of DirectX is available, you can have your application take advantage of all the features a target platform supports. If the target platform has a version of DirectX that isn't as new as you hoped for, you can have your software gracefully degrade the effects it supports. For example, if you wanted DirectX 7 but only DirectX 3 was available, you could drop the use of multitexturing since it isn't supported in DirectX 3. In this way, your application can run properly, albeit with a slightly lesser quality of the visuals. However, as mentioned above, your best bet is to install the newest version of DirectX on the target platform so that your software performs to its potential.

Step 3: Enumerating the Available Direct3D Devices

To create a device you can use later for rendering, you first need to enumerate the Direct3D devices. As described in Chapter 3, this means enumerating DirectDraw devices, then enumerating all Direct3D devices attached to each DirectDraw device. To do this, you can use the D3DEnum_EnumerateDevices routine described in Chapter 3.

Step 4: Selecting the Direct3D Device

The next step in setting up your program to use Direct3D involves selecting the Direct3D device that best fits the needs of your application and then creating the device. The code to handle the selection and creation of the device must determine if you want to run in windowed or full-screen mode. This process was shown in Chapter 3 in the DeviceEnumCallback routine.

Step 5: Getting an IDirectDraw7 Interface

After initializing your variables and determining the DirectX version, the next step is to set up a DirectDraw object. Chapter 3 describes in detail how to acquire the new IDirectDraw7 interface by using the CD3DFramework7::CreateDirectDraw member function.

Step 6: Obtaining an IDirect3D7 Interface from IDirectDraw7 and Creating the Direct3D Device

To create a Direct3D object, you use the QueryInterface method of the DirectDraw object to determine whether it supports the IID_IDirect3D7 interface, which was introduced in DirectX 7. If the DirectDraw object supports that interface, QueryInterface retrieves a pointer to the interface and calls AddRef to add this reference.

The following code in d3dframe.cpp shows how to create the Direct3D object and the Direct3DDevice object. (We'll cover the final segment of the routine, creating a viewport, in Chapter 5.)

 //------------------------------------------------------------------- // Name: CreateDirect3D // Desc: Creates the Direct3D interface //------------------------------------------------------------------- HRESULT CD3DFramework7::CreateDirect3D( GUID* pDeviceGUID ) {     // Query DirectDraw for access to Direct3D.     if( FAILED( m_pDD->QueryInterface( IID_IDirect3D7,                                         (VOID**)&m_pD3D ) ) )     {         DEBUG_MSG( _T("Couldn't get the Direct3D interface") );         return D3DFWERR_NODIRECT3D;     }     // Create the device.     if( FAILED( m_pD3D->CreateDevice( *pDeviceGUID, m_pddsBackBuffer,                                       &m_pd3dDevice) ) )     {         DEBUG_MSG( _T("Couldn't create the D3DDevice") );         return D3DFWERR_NO3DDEVICE;     }     // Set the viewport for the newly created device.     D3DVIEWPORT7 vp = { 0, 0, m_dwRenderWidth, m_dwRenderHeight,                          0.0f, 1.0f };     if( FAILED( m_pd3dDevice->SetViewport( &vp ) ) )     {         DEBUG_MSG(              _T("Error: Couldn't set current viewport to device") );         return D3DFWERR_NOVIEWPORT;     }     return S_OK; } 

Once this routine returns successfully, you'll have a valid Direct3D object and a valid Direct3D device object to use for your application.

Creating Direct3D Devices

Earlier in this chapter, you saw the types of Direct3D devices available. With DirectX 7, the IDirect3DDevice7 interface, retrieved with a call to the IDirect3D7::CreateDevice method, includes methods for supporting materials, lights, and viewports rather than requiring the separate Direct3DLight, Direct3DMaterial, and Direct3DViewport objects previous releases of DirectX required.

As we discussed, the device you create must match the capabilities available on the target platform. Direct3D provides rendering capabilities by using 3D hardware on the target computer or through emulating the 3D capabilities in software, so it provides support for both hardware access and software emulation.

You should design your software to use any hardware capabilities available rather than software emulation because the hardware runs much faster. Also keep in mind that software devices don't necessarily support the same features as hardware devices. Because of this, you should query for device capabilities to find out which are supported in hardware and then use software emulation only for those systems without hardware accelerators.

The HAL and TnLHAL devices are hardware-accelerated, so you should use them whenever possible. Direct3D applications access the 3D hardware through Direct3D methods via the HAL. If the target system has a Direct3D-capable 3D hardware accelerator, you should use it for any Direct3D features the hardware implements. If no hardware acceleration is available, the RGB device (which will use MMX if it's available) is the next best choice. The RGB device is a software rasterizer, so it will be slower than a hardware-accelerated device. However, the RGB device uses any CPU extensions that are available, such as MMX, 3DNow!, and Katmai, to run as quickly as possible.

The calls to acquire the various device types are as follows:

 CreateDevice(IID_IDirect3DTnLHalDevice, lpDirectDrawSurface,              lplpDirect3DDevice); CreateDevice(IID_IDirect3DHALDevice, lpDirectDrawSurface,              lplpDirect3DDevice); CreateDevice(IID_IDirect3DRGBDevice, lpDirectDrawSurface,              lplpDirect3DDevice); CreateDevice(IID_IDirect3DRefDevice, lpDirectDrawSurface,              lplpDirect3DDevice); 

The IDirect3DDevice7 interface's member function, IDirect3DDevice7::GetCaps, obtains all the capabilities of the Direct3D device. This method stores the data that describes those capabilities in a D3DDEVICEDESC7 structure.

The D3DDEVICEDESC7 structure is defined as follows:

 typedef struct _D3DDeviceDesc7 {     DWORD       dwDevCaps;               // Capabilities of device     D3DPRIMCAPS dpcLineCaps;     D3DPRIMCAPS dpcTriCaps;     DWORD       dwDeviceRenderBitDepth;  // One of DDBB_8, 16, etc.      DWORD       dwDeviceZBufferBitDepth; // One of DDBD_16, 32, etc.     DWORD       dwMinTextureWidth, dwMinTextureHeight;     DWORD       dwMaxTextureWidth, dwMaxTextureHeight;     DWORD       dwMaxTextureRepeat;     DWORD       dwMaxTextureAspectRatio;     DWORD       dwMaxAnisotropy;     D3DVALUE    dvGuardBandLeft;     D3DVALUE    dvGuardBandTop;     D3DVALUE    dvGuardBandRight;     D3DVALUE    dvGuardBandBottom;     D3DVALUE    dvExtentsAdjust;     DWORD       dwStencilCaps;     DWORD       dwFVFCaps;     DWORD       dwTextureOpCaps;     WORD        wMaxTextureBlendStages;     WORD        wMaxSimultaneousTextures;     DWORD       dwMaxActiveLights;     D3DVALUE    dvMaxVertexW;     GUID        deviceGUID;     WORD        wMaxUserClipPlanes;     WORD        wMaxVertexBlendMatrices;     DWORD       dwVertexProcessingCaps;     DWORD       dwReserved1;     DWORD       dwReserved2;     DWORD       dwReserved3;     DWORD       dwReserved4; } D3DDEVICEDESC7, *LPD3DDEVICEDESC7; 

The members of this structure are defined as follows:

  • dwDevCaps Includes flags identifying the device's capabilities.
  • dpcLineCaps and dpcTriCaps D3DPRIMCAPS structures that define the device's support for line-drawing and triangle primitives.
  • dwDeviceRenderBitDepth The device's rendering bit depth. This member can be one or more of the DirectDraw bit-depth constants: DDBD_8, DDBD_16, DDBD_24, or DDBD_32.
  • dwDeviceZBufferBitDepth Indicates the device's depth-buffer bit depth. This member can be one of the DirectDraw bit-depth constants: DDBD_8, DDBD_16, DDBD_24, or DDBD_32.
  • dwMinTextureWidth , dwMinTextureHeight Indicates the minimum texture width and height for this device.
  • dwMaxTextureWidth , dwMaxTextureHeight Indicates the maximum texture width and height for this device.
  • dwMaxTextureRepeat Includes the full range of the integer (nonfractional) bits of the postnormalized texture indices. If the D3DDEVCAPS_TEXREPEATNOTSCALEDBYSIZE bit is set, the device defers scaling by the texture size until after the texture address mode is applied. If it isn't set, the device scales the texture indices by the texture size (largest level of detail) prior to interpolation.
  • dwMaxTextureAspectRatio Indicates the maximum texture aspect ratio that the hardware supports; this value will typically be a power of 2.
  • dwMaxAnisotropy Indicates the maximum valid value for the D3DRENDERSTATE_ANISOTROPY render state.
  • dvGuardBandLeft , dvGuardBandTop, dvGuardBandRight, and dvGuardBandBottom Define the screen-space coordinates of the guard-band clipping region. Coordinates inside this rectangle but outside the viewport rectangle are automatically clipped.
  • dvExtentsAdjust Indicates the number of pixels required to adjust the extents rectangle outward to accommodate antialiasing kernels.
  • dwStencilCaps Includes flags that specify which stencil-buffer operations are supported.
  • dwFVFCaps Indicates the flexible vertex format capabilities.
  • dwTextureOpCaps Includes the combination of flags that describe the texture operations this device supports.
  • wMaxTextureBlendStages Indicates the maximum number of texture-blending stages this device supports.
  • wMaxSimultaneousTextures Indicates the maximum number of textures that can be simultaneously bound to this device's texture-blending stages.
  • dwMaxActiveLights Indicates the maximum number of lights that can be active simultaneously.
  • dvMaxVertexW Indicates the maximum w-based depth value that the device supports.
  • deviceGUID Indicates the globally unique identifier (GUID) that identifies this device.
  • wMaxUserClipPlanes Indicates the maximum number of user-defined clipping planes this device supports. This member can range from 0 to D3DMAXUSERCLIPPLANES. User-defined clipping planes are manipulated by using the IDirect3DDevice7:: GetClipPlane and IDirect3DDevice7:: SetClipPlane methods.
  • wMaxVertexBlendMatrices Indicates the maximum number of matrices that this device can apply when performing multimatrix vertex blending.
  • dwVertexProcessingCaps Indicates the device's vertex processing.
  • dwReserved1 through dwReserved4 Reserved for future use.

As you can see, many capabilities are available. You'll have to decide which of these to check for based on the requirements of your application. You should use as many as possible, however, to produce the most realistic 3D environment you can. As an example, the RoadRage code we're developing in this book uses fog and alpha blending to provide as realistic a rendered and animated 3D world as possible.

You also need to provide the user with a way to select one of the other available devices (or modes) so that he or she can change from the initially selected ones (for example, RGB vs. HAL; full-screen mode vs. windowed mode; a lower resolution mode, such as 800 × 600 vs. 640 × 480; and so on).

The following code will handle user selection of the Change Device/Mode item from the File menu. It produces a dialog box that presents the available devices and modes, allows the user to select which device and mode to switch to, and then makes the switch. Here's the code we need to add to the CD3DApplication::MsgProc member function, contained in the d3dapp.cpp file, to handle user selection of this menu item:

 case IDM_CHANGEDEVICE:     // Display the device-selection dialog box.     if (m_bActive && m_bReady)     {         Pause(TRUE);         if (SUCCEEDED( D3DEnum_UserChangeDevice(&m_pDeviceInfo)))         {             if( FAILED( hr = Change3DEnvironment() ) )                 return 0;         }         Pause(FALSE);     }     return 0; 

Figure 4-1 shows the drop-down menu the code produces for selecting a new device or mode.

click to view at full size.

Figure 4-1 Selecting a new device or mode

The dialog box this menu selection produces when the user selects it is shown in Figure 4-2. The user can select the desired device (for example, the primary device or any of the secondary devices) and the mode.

Figure 4-2 The change device dialog box

The new case statement added to the CD3DApplication::MsgProc member function for this chapter calls D3DEnum_UserChangeDevice to produce a dialog box that presents all the available devices and modes on the host system and allows the user to select a new device or mode. As you saw above, each device is displayed in a list box at the top of the dialog box and each mode is displayed in a list box at the bottom of the dialog box. The D3DEnum_UserChangeDevice routine follows:

 //------------------------------------------------------------------- // Name: D3DEnum_UserChangeDevice // Desc: Pops up a dialog box that allows the user to select a new //       device //------------------------------------------------------------------- HRESULT D3DEnum_UserChangeDevice( D3DEnum_DeviceInfo** ppDevice ) {     if( IDOK == DialogBoxParam( (HINSTANCE)GetModuleHandle(NULL),                                 MAKEINTRESOURCE(IDD_CHANGEDEVICE),                                 GetForegroundWindow(),                                 ChangeDeviceProc, (LPARAM)ppDevice ) )         return S_OK;     return E_FAIL; } 

This routine calls the Windows DialogBoxParam function and creates a modal dialog box from a dialog box template resource you specify. It takes a callback function in the lpDialogFunc parameter. The modal dialog box won't return control until this callback function terminates the modal dialog box by calling the EndDialog function. The ChangeDeviceProc callback specified in the call to DialogBoxParam handles all Windows messages for the device-selection dialog box:

 //------------------------------------------------------------------- // Name: ChangeDeviceProc // Desc: Windows message-handling function for the device-selection //       dialog box //------------------------------------------------------------------- static BOOL CALLBACK ChangeDeviceProc( HWND hDlg, UINT uiMsg,      WPARAM wParam, LPARAM lParam ) {     static D3DEnum_DeviceInfo** ppDeviceArg;     static D3DEnum_DeviceInfo* pCurrentDevice;     static DWORD dwCurrentMode;     static BOOL  bCurrentWindowed;     static BOOL  bCurrentStereo;     // Get access to the enumerated device list.     D3DEnum_DeviceInfo* pDeviceList;     DWORD               dwNumDevices;     D3DEnum_GetDevices( &pDeviceList, &dwNumDevices );     // Handle the initialization message.     if( WM_INITDIALOG == uiMsg )     {         // Get the application's current device, passed in as an         // lParam argument.               ppDeviceArg = (D3DEnum_DeviceInfo**)lParam;         if( NULL == ppDeviceArg )             return FALSE;         // Set up temporary storage pointers for the dialog box.         pCurrentDevice = (*ppDeviceArg);         dwCurrentMode    = pCurrentDevice->dwCurrentMode;         bCurrentWindowed = pCurrentDevice->bWindowed;         bCurrentStereo   = pCurrentDevice->bStereo;         UpdateDialogControls( hDlg, pCurrentDevice, dwCurrentMode,                               bCurrentWindowed, bCurrentStereo );         return TRUE;     }     else if( WM_COMMAND == uiMsg )     {         HWND hwndDevice   = GetDlgItem( hDlg, IDC_DEVICE_COMBO );         HWND hwndMode     = GetDlgItem( hDlg, IDC_MODE_COMBO );         HWND hwndWindowed = GetDlgItem( hDlg, IDC_WINDOWED_CHECKBOX );         HWND hwndStereo   = GetDlgItem( hDlg, IDC_STEREO_CHECKBOX );         // Get current user interface state.         DWORD dwDevice   = ComboBox_GetCurSel( hwndDevice );         DWORD dwModeItem = ComboBox_GetCurSel( hwndMode );         DWORD dwMode =                 ComboBox_GetItemData( hwndMode, dwModeItem );         BOOL  bWindowed =                 hwndWindowed ? Button_GetCheck( hwndWindowed ) : 0;         BOOL  bStereo =                 hwndStereo   ? Button_GetCheck( hwndStereo )   : 0;         D3DEnum_DeviceInfo* pDevice = &pDeviceList[dwDevice];                  if( IDOK == LOWORD(wParam) )         {             // Handle the case in which the user clicks OK. Check to              // see whether the user changed any options.             if( pDevice   != pCurrentDevice   ||                 dwMode    != dwCurrentMode    ||                 bWindowed != bCurrentWindowed ||                 bStereo   != bCurrentStereo )             {                 // Return the newly selected device and its new                 // properties.                 (*ppDeviceArg)              = pDevice;                 pDevice->bWindowed          = bWindowed;                 pDevice->bStereo            = bStereo;                 pDevice->dwCurrentMode      = dwMode;                 pDevice->ddsdFullscreenMode =                     pDevice->pddsdModes[dwMode];                 EndDialog( hDlg, IDOK );             }             else                 EndDialog( hDlg, IDCANCEL );             return TRUE;         }         else if( IDCANCEL == LOWORD(wParam) )         {             // Handle the case in which the user clicks Cancel.              EndDialog( hDlg, IDCANCEL );             return TRUE;         }         else if( CBN_SELENDOK == HIWORD(wParam) )         {             if( LOWORD(wParam) == IDC_DEVICE_COMBO )             {                 // Handle the case in which the user chooses the                 // device combo.                 dwMode    = pDeviceList[dwDevice].dwCurrentMode;                 bWindowed = pDeviceList[dwDevice].bWindowed;                 bStereo   = pDeviceList[dwDevice].bStereo;             }         }         // Keep the user interface current.         UpdateDialogControls( hDlg, &pDeviceList[dwDevice], dwMode,                               bWindowed, bStereo );         return TRUE;     } 

When the dialog box is created, the ChangeDeviceProc callback calls the GetDlgItem function to get the handle of the various list box controls. Once it has the handles for the various controls, it calls the D3DEnum_GetDevices routine to build a list of devices and modes. Each device is added to the device list box by looping through the g_d3dDevices global array of devices created during enumeration. All the modes for each device are then added to the modes list box.

If the device can render to a window, it adds a windowed mode. After this, it loops through each full-screen mode (the number of which is stored in the pDevice->dwNumModes global variable). Each mode, stored in the pDevice->pddsdModes array, which is an array of DDSURFACEDESC2 structures that each contains a description of a mode, is added to the mode list box. Finally, the selected item in the list box is set to the current mode.

 //------------------------------------------------------------------- // Name: UpdateDialogControls // Desc: Builds the list of devices and modes for the combo boxes in  //       the device-selection dialog box //------------------------------------------------------------------- static VOID UpdateDialogControls( HWND hDlg,      D3DEnum_DeviceInfo* pCurrentDevice,                                   DWORD dwCurrentMode, BOOL bWindowed,                                   BOOL bStereo ) {     // Get access to the enumerated device list.     D3DEnum_DeviceInfo* pDeviceList;     DWORD               dwNumDevices;     D3DEnum_GetDevices( &pDeviceList, &dwNumDevices );     // Get access to the UI controls.     HWND hwndDevice         = GetDlgItem( hDlg, IDC_DEVICE_COMBO );     HWND hwndMode           = GetDlgItem( hDlg, IDC_MODE_COMBO );     HWND hwndWindowed       =         GetDlgItem( hDlg, IDC_WINDOWED_CHECKBOX );     HWND hwndStereo         = GetDlgItem( hDlg, IDC_STEREO_CHECKBOX );     HWND hwndFullscreenText = GetDlgItem( hDlg, IDC_FULLSCREEN_TEXT );     // Reset the content in each combo box.     ComboBox_ResetContent( hwndDevice );     ComboBox_ResetContent( hwndMode );     // Don't let non-GDI devices be windowed.     if( FALSE == pCurrentDevice->bDesktopCompatible )         bWindowed = FALSE;     // Add a list of devices to the device combo box.     for( DWORD device = 0; device < dwNumDevices; device++ )     {         D3DEnum_DeviceInfo* pDevice = &pDeviceList[device];         // Add device name to the combo box.         DWORD dwItem = ComboBox_AddString( hwndDevice,                                            pDevice->strDesc );                  // Set the remaining UI states for the current device.         if( pDevice == pCurrentDevice )         {             // Set the combo box selection on the current device.             ComboBox_SetCurSel( hwndDevice, dwItem );             // Enable/set the full-screen checkbox, as appropriate.             if( hwndWindowed )             {                 EnableWindow( hwndWindowed,                               pDevice->bDesktopCompatible );                 Button_SetCheck( hwndWindowed, bWindowed );             }                          // Enable/set the stereo check box, as appropriate.             if( hwndStereo )             {                 EnableWindow( hwndStereo,                                pDevice->bStereoCompatible && !bWindowed );                 Button_SetCheck( hwndStereo, bStereo );             }             // Enable/set the full-screen modes combo box, as             // appropriate.             EnableWindow( hwndMode, !bWindowed );             EnableWindow( hwndFullscreenText, !bWindowed );             // Build the list of full-screen modes.             for( DWORD mode = 0; mode < pDevice->dwNumModes; mode++ )             {                 DDSURFACEDESC2* pddsdMode =                                     &pDevice->pddsdModes[mode];                 // Skip nonstereo modes, if the device is in stereo                 // mode.                 if( 0 == ( pddsdMode->ddsCaps.dwCaps2 &                                 DDSCAPS2_STEREOSURFACELEFT ) )                     if( bStereo )                         continue;                 TCHAR strMode[80];                 wsprintf( strMode, _T("%ld x %ld x %ld"),                           pddsdMode->dwWidth, pddsdMode->dwHeight,                           pddsdMode->ddpfPixelFormat.dwRGBBitCount );                 // Add the mode description to the combo box.                 DWORD dwItem =                     ComboBox_AddString( hwndMode, strMode );                 // Set the item data to identify this mode.                 ComboBox_SetItemData( hwndMode, dwItem, mode );                 // Set the combo box selection on the current mode.                 if( mode == dwCurrentMode )                     ComboBox_SetCurSel( hwndMode, dwItem );                 // Not all modes support stereo, so select a default                  // mode in case none was chosen yet.                 if( bStereo &&                      ( CB_ERR == ComboBox_GetCurSel( hwndMode )))                     ComboBox_SetCurSel( hwndMode, dwItem );             }         }     } } 

Step 7: Creating a Depth Buffer

A depth buffer is a DirectDraw surface that stores depth information that Direct3D uses. When a Direct3D application renders a 3D scene to a target surface, the memory in an attached depth-buffer surface is used to determine how the pixels of rasterized polygons end up occluding others. Direct3D uses an off-screen DirectDraw surface as the target that the final color values are written to. The depth-buffer surface attached to the render-target surface stores the depth information indicating how far away every visible pixel in the scene is. The depth buffer is often what is called a z-buffer. A related type of depth buffer is called a w-buffer, which can be more accurate in some cases. Some Direct3D devices support a structure called a stencil buffer, which can be used to generate some interesting effects such as shadows. (Stencil buffers are covered in Chapter 12.) Stencil buffers are usually interleaved with the depth buffer. For example, a 32-bit depth buffer might consist of a 24-bit z-buffer and an 8-bit stencil buffer.

Before rendering a 3D scene, you usually have Direct3D set all depth values in the depth buffer to the greatest value for the scene. The render target's color can be set to a constant color at this time as well. Then, as each polygon in the scene is rasterized (converted to pixels), the z-value or w-value at each pixel is computed. Then the depth value (the z coordinate in the z-buffer case and the w coordinate in the w-buffer case) at the current point is tested to determine whether it is smaller than the value already in the depth buffer at that location. If it is smaller (nearer to the viewer than any other polygon), this new value is stored in the depth buffer and the color from the polygon is stored in the current point on the rendering surface. In the case that the depth value of the polygon at that point is larger (farther from the viewer than another polygon), nothing is written and the next polygon in the list is tested.

Almost every 3D accelerator supports z-buffering, but not all 3D accelerators support w-buffering. The advantage of w-buffering is that the z values generated for z-buffers aren't always evenly distributed across the z-buffer range (usually 0.0 through 1.0 inclusive). The ratio between the far and near clipping planes affects how the z values are distributed. Using a far-plane distance to near-plane distance ratio of 100, 90 percent of the depth-buffer range is spent on the first 10 percent of the scene depth range. At higher ratios, a much higher percentage (97 to 98 percent) can be spent in the first 2 percent of the depth range.

If you use a w-buffer instead of a z-buffer, the depth values tend to be more evenly distributed between the near and far clipping planes. The benefit is that this lets programs support large maximum ranges while achieving accurate depth close to the eye point. A limitation is that it can sometimes produce hidden surface artifacts for near objects. You'll need to decide which approach you like based on your application's design.

A few 3D accelerators have special hardware that can resolve which surfaces are visible without a depth buffer. These hardware devices set a caps bit named D3DPRASTERCAPS_ZBUFFERLESSHSR ("HSR" stands for "hidden surface removal"). When you detect a device with this bit set, you shouldn't create a depth buffer so that more memory is available on the accelerator for textures or other objects.

To create and attach a depth buffer, you need to perform the tasks listed in the following code:

 //------------------------------------------------------------------- // Name: CreateZBuffer // Desc: Internal function called by Create to make and attach a  //       depth buffer to the renderer //------------------------------------------------------------------- HRESULT CD3DFramework7::CreateZBuffer ( GUID* pDeviceGUID ) {     HRESULT hr;     // Check whether the device supports z-bufferless hidden surface      // removal. If so, a z-buffer isn't necessary.     D3DDEVICEDESC7 ddDesc;     m_pd3dDevice->GetCaps( &ddDesc );     if( ddDesc.dpcTriCaps.dwRasterCaps &             D3DPRASTERCAPS_ZBUFFERLESSHSR )         return S_OK;     // Get z-buffer dimensions from the render target.     DDSURFACEDESC2 ddsd;     ddsd.dwSize = sizeof(ddsd);     m_pddsBackBuffer->GetSurfaceDesc( &ddsd );     // Set up the surface description for the z-buffer.     ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT |                    DDSD_CAPS  | DDSD_PIXELFORMAT;     ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | m_dwDeviceMemType;     // Tag the pixel format as uninitialized.     ddsd.ddpfPixelFormat.dwSize = 0;       // Get an appropriate pixel format from enumeration of the      // formats. On the first pass, look for a z-buffer depth equal      // to the frame buffer depth (unfortunately, some cards     // require this).     m_pD3D->EnumZBufferFormats( *pDeviceGUID,                                 EnumZBufferFormatsCallback,                                 (VOID*)&ddsd.ddpfPixelFormat );     if( 0 == ddsd.ddpfPixelFormat.dwSize )     {         // Try again, just accepting any 16-bit z-buffer.         ddsd.ddpfPixelFormat.dwRGBBitCount = 16;         m_pD3D->EnumZBufferFormats( *pDeviceGUID,                                      EnumZBufferFormatsCallback,                                     (VOID*)&ddsd.ddpfPixelFormat );                      if( 0 == ddsd.ddpfPixelFormat.dwSize )         {             DEBUG_MSG( _T(                    "Device doesn't support requested z-buffer format") );             return D3DFWERR_NOZBUFFER;         }     }     // Create and attach a z-buffer.     if( FAILED( hr = m_pDD->CreateSurface( &ddsd,                                            &m_pddsZBuffer,                                            NULL )))     {         DEBUG_MSG( _T("Error: Couldn't create a ZBuffer surface") );         if( hr != DDERR_OUTOFVIDEOMEMORY )             return D3DFWERR_NOZBUFFER;         DEBUG_MSG( _T("Error: Out of video memory") );         return DDERR_OUTOFVIDEOMEMORY;     }     if( FAILED( m_pddsBackBuffer->                     AddAttachedSurface( m_pddsZBuffer ) ) )     {         DEBUG_MSG(            _T("Error: Couldn't attach z-buffer to render surface") );         return D3DFWERR_NOZBUFFER;     }     // For stereoscopic viewing, attach z-buffer to left surface     // as well.     if( m_bIsStereo )     {         if( FAILED( m_pddsBackBufferLeft->                         AddAttachedSurface( m_pddsZBuffer )))         {             DEBUG_MSG(_T(              "Error: Couldn't attach z-buffer to left render surface"              ) );             return D3DFWERR_NOZBUFFER;         }     }     // Finally, this call rebuilds internal structures.     if( FAILED( m_pd3dDevice->                     SetRenderTarget( m_pddsBackBuffer, 0L ) ) )     {         DEBUG_MSG(_T(          "Error: SetRenderTarget() failed after attaching z-buffer!"          ) );         return D3DFWERR_NOZBUFFER;     }     return S_OK; } 

In the first segment of this routine, we check the capabilities (the D3DPRASTERCAPS_ZBUFFERLESSHSR member of the dwRasterCaps member) to see whether this device supports z-bufferless hidden surface removal. If it does, the function exits without creating a depth buffer. If not, the function sets up the surface description for the z-buffer by setting the ddsd.ddsCaps.dwCaps member to DDSCAPS_ZBUFFER | m_dwDeviceMemType to indicate that it's setting up a z-buffer.

Before you can create a depth buffer, you need to verify which depth-buffer formats (if any) the rendering device supports. To enumerate the depth-buffer formats that the device supports, call IDirect3D7::EnumZBuffersFormats and specify the EnumZBufferFormatsCallback routine.

When the system calls the callback function, it will pass the function a DDPIXELFORMAT structure that describes the pixel format of the depth buffer. The dwFlags member will be set to DDPF_ZBUFFER for any pixel formats that include depth-buffer bits. If this is the case, the dwZBufferBitDepth member will hold an integer specifying the number of bits in the pixel format reserved for depth information, and the dwZBitMask member will mask the relevant bits.

The IDirect3D7::EnumZBufferFormats method is defined as follows:

 HRESULT EnumZBufferFormats(      REFCLSID riidDevice,      LPD3DENUMPIXELFORMATSCALLBACK lpEnumCallback,      LPVOID lpContext   ); 

ParameterDescription
riidDeviceReference to a GUID for the device whose depth-buffer formats will be enumerated
lpEnumCallbackAddress of a D3DEnumPixelFormatsCallback callback function that will be called for each supported depth-buffer format
lpContextApplication-defined data passed to the callback function

If the attempt to get a particular pixel format fails (that is, isn't found during enumeration), you'll need to look for a z-buffer depth equal to the frame-buffer depth since some 3D cards require this.

After an attempt to get a pixel format succeeds, check the m_bIsStereo flag. If you're creating stereo flipping surfaces (meaning that you're creating a stereo video application for a virtual reality, such as a head-mounted display, or other application purpose), you need to call the AddAttachedSurface method to attach the depth buffer to this additional back buffer. The IDirectDrawSurface7::AddAttachedSurface method is defined as follows:

 HRESULT AddAttachedSurface(     LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface   ); 

This method has one parameter, lpDDSAttachedSurface, which is the address of an IDirectDrawSuface7 interface for the surface to be attached.

The IDirectDrawSurface7::AddAttachedSurface method increments the reference count of the surface being attached. You can explicitly unattach the surface and decrement its reference count by using the IDirectDrawSurface7::DeleteAttachedSurface method. Unlike complex surfaces that you create with a single call to IDirectDraw7::CreateSurface, surfaces attached with this method are not automatically released. It is your responsibility to release these surfaces.

Several interactions and rules define the surface attachment of z-buffers and back buffers:

  • Attachment isn't bidirectional.
  • A surface can't be attached to itself.
  • Emulated surfaces (in system memory) can't be attached to nonemulated surfaces.
  • A flipping surface can't be attached to another flipping surface of the same type.
  • Two surfaces of different types can be attached. For example, you can attach a flipping z-buffer to a regular flipping surface.
  • If a nonflipping surface is attached to another nonflipping surface of the same type, the two surfaces will become a flipping chain.
  • If a nonflipping surface is attached to a flipping surface, it becomes part of the existing flipping chain. Additional surfaces can be added to this chain, and each call of the IDirectDrawSurface7::Flip method will advance one step through the surfaces.

The callback function you create for the z-buffer enumeration process needs to specify the capabilities you want for z-buffering. In the code for this chapter, just check the RGB bits per pixel (4, 8, 16, 24, or 32) to make sure it matches the z-buffer bits per pixel. This is because some cards require us to match a 16bit zbuffer with a 16-bit RGB buffer, and so on. If it does, we return, indicating that the format is OK. As with any enumeration, it's up to you to request the capabilities you absolutely need for your application but to skip those that aren't important.

The EnumZBufferFormatsCallback routine is defined as follows:

 //------------------------------------------------------------------- // Name: EnumZBufferFormatsCallback // Desc: Returns the first matching enumerated z-buffer format //------------------------------------------------------------------- static HRESULT WINAPI EnumZBufferFormatsCallback(                           DDPIXELFORMAT* pddpf,                           VOID* pContext ) {     DDPIXELFORMAT* pddpfOut = (DDPIXELFORMAT*)pContext;     if( pddpfOut->dwRGBBitCount == pddpf->dwRGBBitCount )     {         (*pddpfOut) = (*pddpf);         return D3DENUMRET_CANCEL;     }     return D3DENUMRET_OK; } 



Inside Direct3D
Inside Direct3D (Dv-Mps Inside)
ISBN: 0735606137
EAN: 2147483647
Year: 1999
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net