Part B - Direct3D

Display Modes

Introduce the Direct3D COM object
Use Direct3D to interrogate the host


Sample | Framework | UserInput | Display | Utilities | References



The first step in building a digital game is identifying the configuration options that are accessible to the user on their particular host.  These options include the display devices that are attached and the resolution modes that each device supports.  Once we determine the set of accessible combinations, we can report them to the user and retrieve their selection. 

This chapter shows how to implement this configuration stage using the Direct3D COM object.  The code presented here

  1. identifies all of the installed display devices
  2. identifies all of the resolution modes for each of the installed display devices
  3. retrieves the user's selection through a dialog box communication

The methods on the Direct3D COM object provide all of the hardware information needed for this stage. 


Display Modes Sample

The Display Modes sample, like the Selection sample, displays a dialog box that contains two combo boxes and two buttons.  The topmost box lists the installed display devices.  The lower box lists the resolution modes available on the currently selected device:

dialog box for display modes sample


Framework

Upgrades for this sample include:

  • UserInput - its APIUserInput class creates a APIDisplaySet object and interrogates the host through this object
  • Translation.h now holds the minimum window resolution mode and the maximum number of characters in a string

The new additions to the framework are

  • Display component - contains the APIDisplaySet class.  This class handles communication with the hardware through the Direct3D COM object
  • Utilities module - this component provides the utilities for the model and the translation layers
  • APIPlatform.h file - includes the header files for the Direct3D COM object and defines the Direct3D specific settings

framework components for display modes sample


User Input

The UserInput component manages all communications with the user.  Through this component, the user selects the configuration that the framework implements. 

The iAPIUserInput interface exposes two new methods to the framework:

 // iAPIUserInput.h

 class iAPIUserInput {
   public:
     virtual bool getConfiguration()             = 0;
     virtual void populateAPIUserInput(void*)    = 0;
     virtual bool populateAdapterModeList(void*) = 0;
     virtual bool saveUserChoices(void*)         = 0;
     virtual void Delete() const                 = 0;
 };
 iAPIUserInput* CreateAPIUserInput(void*);
 iAPIUserInput* APIUserInputAddress();

The APIUserInput class now includes:

  • a pointer to the APIDisplaySet object
  • the identifier of the selected pixel format
  • a flag identifying run in window mode
  • a C-style string describing the currently selected display
  • a C-style string describing the currently selected mode
 // APIUserInput.h

 class APIUserInput : public iAPIUserInput {
     static iAPIUserInput* address;  // points to this singleton
     iAPIDisplaySet* displaySet;     // the attached displays
     void*           application;    // points to the application
     void*           hwnd;           // points to the dialog window
     unsigned        displayId;      // display adapter identifier
     unsigned        modeId;         // resolution mode identifier
     unsigned        pixelId;        // pixel format identifier
     bool            runinwndw;      // run in a window?
     // most recent configuration memory
     wchar_t         dispStr [MAX_DESC + 1];
     wchar_t         modeStr [MAX_DESC + 1];

     APIUserInput(const APIUserInput&);            // prevent copies
     APIUserInput& operator=(const APIUserInput&); // prevent assignments
     virtual ~APIUserInput();
   public:
     static iAPIUserInput* Address() { return address; }
     APIUserInput(void*);
     bool getConfiguration();
     void Delete() const             { delete this; }
     void populateAPIUserInput(void*);
     void populateAdapterList(void*);
     bool populateAdapterModeList(void*);
     bool saveUserChoices(void*);
     void error(const wchar_t*) const;
 };
 

Implementation

Construct

The constructor creates the APIDisplaySet object and initializes the other instance variables: 

 APIUserInput::APIUserInput(void* hinst) : application(hinst) {
     address       = this;
     hwnd          = nullptr;
     displaySet    = CreateAPIDisplaySet();
     // default memory string settings
     strcpy(dispStr,  RUN_IN_WINDOW_DESC, MAX_DESC);
     strcpy(modeStr,  L"", MAX_DESC);
     displayId   = 0;
     modeId      = 0;
     pixelId     = 0;
     runinwndw   = true;
 }

Get Configuration

The getConfiguration() method now interrogates the host for display devices before requesting the user's selection:

 bool APIUserInput::getConfiguration() {
     bool rc;
     displaySet->interrogate();
     rc = DialogBox((HINSTANCE)application, MAKEINTRESOURCE(IDD_DLG), nullptr,
      dlgProc) == TRUE;
     hwnd = nullptr;
     return rc;
 }

Populate User Dialog

The populateAPIUserInput() method populates the dialog box before the operating system displays it.  This method stores the dialog window's address in an instance pointer and populates the adapter list:

 void APIUserInput::populateAPIUserInput(void* hwnd) {
     this->hwnd = hwnd;
     populateAdapterList(hwnd);
 }

Populate Adapter List

The populateAdapterList() method populates the display adapter combo box.  This method

  • empties the combo box
  • retrieves the number of adapters and their descriptions
  • populates the line items in the display combo box with those descriptions and corresponding identifiers
  • sets the cursor to the line item that contains the previously selected description
 void APIUserInput::populateAdapterList(void* hwndw) {
     int dev; // index of the current line item
     HWND hwnd = (HWND)hwndw; // handle to current window

     SendDlgItemMessage(hwnd, IDC_DIS, CB_RESETCONTENT, 0, 0L);
     int nd = displaySet->noAdapters();
     for (int id = 0; id < nd; id++) {
         if (displaySet->adapterDesc(id)[0]) {
             dev = SendDlgItemMessage(hwnd, IDC_DIS, CB_ADDSTRING, 0,
              (LPARAM)displaySet->adapterDesc(id));
             SendDlgItemMessage(hwnd, IDC_DIS, CB_SETITEMDATA, dev, id);
         }
     }
     dev = SendDlgItemMessage(hwnd, IDC_DIS, CB_ADDSTRING, 0,
      (LPARAM)RUN_IN_WINDOW_DESC);
     SendDlgItemMessage(hwnd, IDC_DIS, CB_SETITEMDATA, dev,
      RUN_IN_WINDOW);
     dev = SendDlgItemMessage(hwnd, IDC_DIS, CB_FINDSTRINGEXACT, -1,
      (LPARAM)display);
     if (dev == CB_ERR) dev = 0;
     SendDlgItemMessage(hwnd, IDC_DIS, CB_SETCURSEL, dev, 0L);
 }

The SendDlgItemMessage function is fully described in the chapter on Windows Programming.

Populate Adapter Mode List

The populateAdapterModeList() method populates the resolution combo box.  This method

  • empties the resolutions combo box
  • retrieves the number of modes and pixel formats available on the selected display and their descriptions
  • fills the line items in the combo box with these descriptions and the corresponding mode and pixel identifiers
  • sets the cursor to the line item that contains the previously selected description
 bool APIUserInput::populateAdapterModeList(void* hwndw) {
     bool rc = false;
     HWND hwnd = (HWND)hwndw; // handle to current window

     SendDlgItemMessage(hwnd, IDC_RES, CB_RESETCONTENT, 0, 0L);
     int dev = SendDlgItemMessage(hwnd, IDC_DIS, CB_GETCURSEL, 0, 0L);
     if (dev == CB_ERR)
         error(L"APIUserInput::30 No adapter selected");
     else {
         int id = SendDlgItemMessage(hwnd, IDC_DIS, CB_GETITEMDATA, dev, 0L);
         if (id == RUN_IN_WINDOW) {
             // if window mode, disable the resolutions combo box
             EnableWindow(GetDlgItem(hwnd, IDC_RES), FALSE);
             rc = true;
         }
         else {
             // fullscreen modes
             bool noModes = true; // assume no modes available
             int np = displaySet->noPixelFormats();
             int nr = displaySet->noModes();
             for (int ip = 0; ip < np; ip++) { // for each pixel format...
                 for (int ir = 0; ir < nr; ir++) { // for each mode...
                     int i = (id * nr + ir) * np + ip;
                     if (displaySet->modeDesc(id, ir, ip)[0]) {
                         int res = SendDlgItemMessage(hwnd, IDC_RES,
                          CB_ADDSTRING, 0,
                          (LPARAM)displaySet->modeDesc(id, ir, ip));
                         // store mode|format in the data part of line item
                         unsigned fmMd = (ip << 16) | ir;
                         SendDlgItemMessage(hwnd, IDC_RES, CB_SETITEMDATA,
                          res, fmMd);
                         noModes = false;
                     }
                 }
             }
             if (noModes)
                 error(L"APIUserInput::31 Selected display has no modes");
             else {
                 int res = SendDlgItemMessage(hwnd, IDC_RES, CB_FINDSTRINGEXACT,
                  -1, (LPARAM)resolution);
                 if (res == CB_ERR) res = 0;
                 SendDlgItemMessage(hwnd, IDC_RES, CB_SETCURSEL, res, 0L);
                 EnableWindow(GetDlgItem(hwnd, IDC_RES), TRUE);
                 rc = true;
             }
         }
     }
     return rc;
 }

saveUserChoices

The saveUserChoices() method:

  • retrieves the selected display and resolution modes from the combo boxes
  • stores the display, resolution mode and pixel format selections in the instance variables
  • saves the selected line item strings in the instance variables for future reference
 bool APIUserInput::saveUserChoices(void* hwndw) {
     bool rcd = false;
     HWND hwnd = (HWND)hwndw; // handle to current window

     int dev = SendDlgItemMessage(hwnd, IDC_DIS, CB_GETCURSEL, 0, 0L);
     if (dev == CB_ERR)
         error(L"APIUserInput::50 No display adapter selected");
     else {
         int  res  = 0; // selected resolution line item
         displayId = SendDlgItemMessage(hwnd, IDC_DIS, CB_GETITEMDATA, dev, 0L);
         runinwndw = displayId == RUN_IN_WINDOW;
         if (!runinwndw) {
             res = SendDlgItemMessage(hwnd, IDC_RES, CB_GETCURSEL, 0, 0L);
             if (res == CB_ERR) {
                 error(L"APIUserInput::51 Resolution selection failed");
                 // revert to run in a window
                 runinwndw = true;
                 displayId = 0;
                 modeId    = 0;
                 pixelId   = 0;
             }
             else {
                 unsigned fmMd = SendDlgItemMessage(hwnd, IDC_RES,
                  CB_GETITEMDATA, res, 0L);
                 // extract the mode and format from the data item
                 modeId = fmMd & 0xFFFF;
                 pixelId = (fmMd >> 16);
             }
         }
         else {
             displayId = 0;
             modeId    = 0;
             pixelId   = 0;
         }
         SendDlgItemMessage(hwnd,IDC_DIS,CB_GETLBTEXT,dev,(LPARAM)display);
         SendDlgItemMessage(hwnd,IDC_RES,CB_GETLBTEXT,res,(LPARAM)resolution);
         rcd = true;
     }
     return rcd;
 }

error

The error() method on the APIUserInput object pops up a message box with the received message and calls the logError() utility function (described below) to record the error:

 void APIUserInput::error(const wchar_t* msg) const {
     if (hwnd) MessageBox((HWND)hwnd, msg, L"Dialog Error", MB_OK);
     logError(msg);
 }

Window Procedure

The window procedure for the APIUserInput component handles all messages that the system sends to the dialog box.  Beforethe system displays this dialog box, this window procedure calls the populateAPIUserInput() method on the APIUserInput object. 

The first time that the system displays the dialog box or whenever the user selects a display device, this procedure calls the populateAdapterModeList() method and enables the GO button.  Once the user presses GO, the procedure calls the saveUserChoices() method: 

 BOOL CALLBACK dlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
     BOOL           rc        = FALSE;
     static bool    firsttime = true;
     iAPIUserInput* apiDialog = APIUserInputAddress();

     switch (msg) {
       case WM_INITDIALOG:
         SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE)
          | WS_EX_LAYERED);
         SetLayeredWindowAttributes(hwnd, 0, (255 * 95) / 100, LWA_ALPHA);
         apiDialog->populateAPIUserInput(hwnd);
         rc = true;
         break;
       case WM_COMMAND:          // user accessed a control
         switch (LOWORD(wp)) {   // which control?
           case IDC_DIS:         // accessed the display combo box
             // only process this if it is the first time or the user
             // has changed their selection.  This block resets the static
             // variable "firsttime".  Exiting resets the variable to true
             if (firsttime && HIWORD(wp) == CBN_SETFOCUS ||
              HIWORD(wp) == CBN_SELCHANGE ) {
                 firsttime = false;  // won't be 1st time again!
                 // populate resolutions combo box for the selected adapter
                 if (rc = apiDialog->populateAdapterModeList(hwnd))
                     EnableWindow(GetDlgItem(hwnd, IDC_GO), TRUE);
             } else
                 rc = TRUE;
             break;
           case IDC_GO:  // user pressed the Go button
             if (apiDialog->saveUserChoices(hwnd)) {
                 EndDialog(hwnd, TRUE);
                 firsttime = true;
                 rc = TRUE;
             }
             break;
           case IDCANCEL:  // user pressed Cancel button, or Esc, etc.
             EndDialog(hwnd, FALSE);
             firsttime = true;
             rc = TRUE;
             break;
         }
         break;
     }
     return rc;
 }

Display

The Display component manages the communications with the graphics APIs.  For this sample, it consists of one class - the APIDisplaySet class.  Its APIDisplaySet object interrogates the host for the accessible configurations using the Direct3D API and stores descriptions of these configurations in its instance variables. 

The iAPIDisplaySet interface exposes seven methods to the APIUserInput class:

 // iAPIDisplaySet.h

 class iAPIDisplaySet {
     virtual bool  interrogate()                                   = 0;
     virtual int   noAdapters() const                              = 0;
     virtual int   noModes() const                                 = 0;
     virtual int   noPixelFormats() const                          = 0;
     virtual const wchar_t* adapterDesc(int id) const              = 0;
     virtual const wchar_t* modeDesc(int id, int ir, int ip) const = 0;
     virtual void  Delete()                                        = 0;
 };
 iAPIDisplaySet* CreateAPIDisplaySet();

The APIDisplaySet class defines seven instance variables: 

 // APIDisplaySet.h

 class APIDisplaySet : public iAPIDisplaySet {
     IDirect3D9* d3d;       // points to the Direct3D interface
     int     nAdapters;     // number of available adapters
     int     nModes;        // number of available resolution modes
     int     nPixelFmts;    // number of pixel formats
     int     (*modeDim)[2]; // points to list of mode widths and heights
     wchar_t (*modeDes)[MAX_DESC + 1]; // points to mode descriptions
     wchar_t (*adptDes)[MAX_DESC + 1]; // points to adapter descriptions
     APIDisplaySet(const APIDisplaySet& d);               // prevents copying
     APIDisplaySet& operator=(const APIDisplaySet& d); // prevents assignments
     virtual ~APIDisplaySet();
   public:
     APIDisplaySet();
     bool interrogate();
     int  noAdapters() const                  { return nAdapters; }
     int  noModes() const                     { return nModes; }
     int  noPixelFormats() const              { return nPixelFmts; }
     const wchar_t* adapterDesc(int id) const { return adptDes[id]; }
     const wchar_t* modeDesc(int id, int ir, int ip) const;
     void Delete()                            { delete this; };
 };

The APIPlatform.h header file includes the header file for Direct3D and defines macros used in the interrogation:

 // APIPlatform.h

 #include <d3d9.h>      // for basic D3D
 #define D3D_NO_DOC_FORMATS 6
 #define D3D_DOC_FORMATS { D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8,\
      D3DFMT_A2R10G10B10, D3DFMT_X1R5G5B5, D3DFMT_A1R5G5B5,\
      D3DFMT_R5G6B5 }
 // short descriptions for each format
 #define D3D_FORMAT_DESC { L"X32", L"A32", L"A30+2", L"X15", L"A16", L"16" }

Implementation

Construct

The constructor retrieves an interface to the installed Direct3D COM object and initializes the other instance pointers:

 APIDisplaySet::APIDisplaySet() {
     d3d = Direct3DCreate9(D3D_SDK_VERSION);
     if (!d3d) error(L"APIDisplaySet::01 Failed to make Direct3D interface");
     modeDim = nullptr;
     modeDes = nullptr;
     adptDes = nullptr;
 }

Interrogate

The interrogate() method:

  • retrieves the display and resolution mode options from the host using methods on the Direct3D COM object
  • allocates dynamic memory for the configuration options
  • stores the display and resolution mode descriptions within the dynamic arrays
 bool APIDisplaySet::interrogate(void* hwnd) {
     bool     rc = false;
     wchar_t  str[MAX_DESC + 1];
     D3DADAPTER_IDENTIFIER9 d3di;
     D3DDISPLAYMODE         mode;
     D3DFORMAT              Format[]  = D3D_DOC_FORMATS;
     wchar_t*               fmtdesc[] = D3D_FORMAT_DESC;

     nAdapters  = d3d->GetAdapterCount();
     nModes     = 0;
     nPixelFmts = D3D_NO_DOC_FORMATS;
     // determine maximum number of modes for any adapter - and allocate memory
     for (int id = 0; id < nAdapters; id++)
         if (SUCCEEDED(d3d->GetAdapterIdentifier(id, 0, &d3di)))
             for (int ip = 0; ip < nPixelFormats; ip++) {
                 int i = d3d->GetAdapterModeCount(id, Format[ip]);
                 if (i > nModes)
                     nModes = i;
             }
     if (nAdapters > MAX_ADAPTERS) {
         wchar_t str[MAX_DESC + 1];
         sprintf(str, nAdapters, L" Adapters found - increase MAX_ADAPTERS");
         error(str);
         nAdapters = MAX_ADAPTERS;
     }
     if (nModes > MAX_MODES) {
         wchar_t str[MAX_DESC + 1];
         sprintf(str, nModes, L" Modes found - increase MAX_MODES");
         error(str);
         nModes = MAX_MODES;
     }
     if (nPixelFormats > MAX_P_FORMATS) {
         wchar_t str[MAX_DESC + 1];
         sprintf(str, nPixelFormats,
          L" Pixel Formats found - increase MAX_P_FORMATS");
         error(str);
         nPixelFormats = MAX_P_FORMATS;
     }
     if (modeDim) delete [] modeDim;
     if (modeDes) delete [] modeDes;
     if (adptDes) delete [] adptDes;
     modeDim = new int[nAdapters * nModes * nPixelFmts][2];
     modeDes = new wchar_t[nAdapters * nModes * nPixelFmts][MAX_DESC + 1];
     adptDes = new wchar_t[nAdapters][MAX_DESC + 1];
     // enumerate and store display and mode descriptions
     for (int id = 0; id < nAdapters; id++) {
         if (SUCCEEDED(d3d->GetAdapterIdentifier(id, 0, &d3di))) {
             rc = false;
             for (int ip = 0; ip < nPixelFormats; ip++) {
                 int nr = d3d->GetAdapterModeCount(id, Format[ip]);
                 nr = (nr > nModes) ? nModes : nr;
                 for (int ir = 0; ir < nr; ir++) {
                     int i = (id * nModes + ir) * nPixelFmts + ip;
                     if (SUCCEEDED(
                      d3d->EnumAdapterModes(id, Format[ip], ir, &mode))
                      && mode.Width >= WND_WIDTH && mode.Height >= WND_HEIGHT) {
                         wchar_t hz[20] = L"";
                         if (mode.RefreshRate)
                             wsprintf(hz, L"(%d Hz)", mode.RefreshRate);
                          wsprintf(str, L"%dx%d %ls %ls bits", mode.Width,
                           mode.Height, hz, fmtdesc[ip]);
                         rc = true;
                         modeDim[i][0] = mode.Width;
                         modeDim[i][1] = mode.Height;
                         strcpy(modeDes[i], str, MAX_DESC);
                     }
                     else {
                         modeDim[i][0] = 0;
                         modeDim[i][1] = 0;
                         modeDes[i][0] = L'\0';
                     }
                 }
             }
             if (rc)
                 strcpyFromMB(adptDes[id], d3di.Description, MAX_DESC);
             else
                 adptDes[id][0] = L'\0';
         }
     }
 }

The GetAdapterIdentifier() method on the Direct3D interface returns the display identification information for the specified adapter.  The GetAdapterModeCount() method on the Direct3D interface returns the number of resolution modes accessible for the specified adapter and pixel format.  The EnumAdapterModes() method on the Direct3D interface returns the mode information for the specified adapter, mode, and pixel format.  The wsprintf() function is the wide-character Windows version of the standard sprintf() function. 

The sprintf() utility function constructs a string from an integer and a suffix string.  The error() utility function notifies the user that a maximum has been reached and that the threshold needs to be increased. 

Mode Description

The modeDesc() method returns the address of a user friendly description of the mode for the specified display, pixel format, and resolution:

 const wchar_t* APIDisplaySet::modeDesc(int id, int ir, int ip) const {
     return modeDes[ (id * nModes + ir) * nPixelFmts + ip ];
 }

Destroy

The destructor releases the interface to the Direct3D COM object and deallocates the dynamically allocated arrays:

 APIDisplaySet::~APIDisplaySet() {
     if (d3d) {
         d3d->Release();
         d3d = nullptr;
     }
     if (modeDim) {
         delete [] modeDim;
         modeDim = nullptr;
     }
     if (modeDes) {
         delete [] modeDes;
         modeDes = nullptr;
     }
     if (adptDes) {
         delete [] adptDes;
         adptDes = nullptr;
     }
 }

Utilities

The Utilities module consists of global functions available to all classes within the framework.  The prototypes are:

 void     logError(const wchar_t*);
 unsigned strlen(const wchar_t* str);
 wchar_t* strcat(wchar_t* dest, const wchar_t* src, int sizeDest);
 wchar_t* strcpy(wchar_t* dest, const wchar_t* src, int sizeDest);
 wchar_t* strcpyFromMB(wchar_t* dest, const char* src, int sizeDest);
 int      sprintf(wchar_t* str, int a, const wchar_t* suffix);
 const wchar_t* itowc(wchar_t* s, int a);

The logError() function appends the message in the string pointed to by its parameter as a new record to error.log:

 void logError(const wchar_t* msg) {
     std::wofstream fp("error.log", std::ios::app);
     if (fp) {
          fp << msg << std::endl;
          fp.close();
     }
 }

wofstream is the output file stream class for wide-character strings.


References




Previous Reading  Previous: COM and DirectX Next: Event Iteration   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo