Playing with GAPI

Game API (GAPI) is by far not the newest technology. It was released by Microsoft for the Pocket PC and later platforms. You probably already use it. If you don’t, this article may help you realize whether it’s suitable for your needs.

What is GAPI?

Game API is not so much of a reach. It exposes dozen of functions you may see below in the gx.h header file.

GXDLL_API int GXOpenDisplay(HWND hWnd, DWORD dwFlags);
GXDLL_API int GXCloseDisplay();
GXDLL_API void * GXBeginDraw();
GXDLL_API int GXEndDraw();
GXDLL_API int GXOpenInput();
GXDLL_API int GXCloseInput();
GXDLL_API GXDisplayProperties GXGetDisplayProperties();
GXDLL_API GXKeyList GXGetDefaultKeys(int iOptions);
GXDLL_API int GXSuspend();
GXDLL_API int GXResume();
GXDLL_API int GXSetViewport( DWORD dwTop, DWORD dwHeight,
                             DWORD dwReserved1, DWORD dwReserved2 );
GXDLL_API BOOL GXIsDisplayDRAMBuffer();

If you need to develop high-performance applications proceeding massive graphical operations (e.g. games), GAPI may be quite suitable. MSDN also states that “GAPI is also useful for other applications that need to use the device in a way that is not supported by the operating system”. Well, as you may see, GAPI is pretty small (about an 8K footprint) and limited, but still offers a nice solution for game writers. That’s because it doesn’t use GDI at all. You get direct access to the video buffer, so all operations may be obviously handled much faster. No more WM_PAINT and other such stuff. The cost is that you should program all basic operations on your own. Sounds like coding in assembler, eh? Well, at least GAPI provides an easy way to deal with hardware buttons. Thus, coding a game becomes real fun.

How to Use GAPI with Regular Applications

First, let’s take a look at a common working flow of a Game API program.

Initialization

  • GXOpenDisplay
  • GXOpenInput
  • GXGetDisplayProperties
  • GXGetDefaultKeys

Application loop

  • GXBeginDraw
  • Actual drawing
  • GXEndDraw
  • WM_KEYDOWN messages processing
  • Call GXSuspend/GXResume when needed

Releasing

  • GXCloseInput
  • GXCloseDisplay

This is a high-level schema of the application life cycle. As a matter of fact, you may use GAPI in either Win API or MFC applications. The following sample illustrates how to initalize and release GAPI in both cases:

Win API case:

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
...
   // hWnd here is handle of main window
   if (GXOpenDisplay( hWnd, GX_FULLSCREEN) == 0)
      return FALSE;
   gx_displayprop = GXGetDisplayProperties();

   // We will deal with 16 bit per pixel only
   if (gx_displayprop.cBPP != 16)
   {
   GXCloseDisplay();
   MessageBox(hWnd,L"16 BPP mode is supported",L"Warning", MB_OK);
   return FALSE;
   }

   // Capture user input
   GXOpenInput();
...
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
                         LPARAM lParam)
{
   switch (message)
   {
      case WM_TIMER:
         // e.g. do some drawing here
         break;

      case WM_KEYDOWN:
         // here you may handle hardware button taps
         break;

      case WM_DESTROY:
         GXCloseInput();
         GXCloseDisplay();
         PostQuitMessage(0);
         break;

      case WM_KILLFOCUS:
         GXSuspend();
         break;

      case WM_SETFOCUS:
         GXResume();
         break;

      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

MFC case:

BOOL CGAPIApp::InitInstance()
{
...
  if (GXOpenDisplay( m_pMainWnd->hWnd, GX_FULLSCREEN) == 0)
      return FALSE;
   gx_displayprop = GXGetDisplayProperties();

   // We will deal with the most common case: 16 bit per pixel only
   if (gx_displayprop.cBPP != 16)
   {
   GXCloseDisplay();
   MessageBox(hWnd,L"The only mode supported is 16 BPP",
              L"Test GAPI", MB_OK);
   return FALSE;
   }

   // Capture user input
   GXOpenInput();
...
}

int CGAPIApp::ExitInstance()
{
...
   GXCloseInput();
   GXCloseDisplay();
...
   return 1;
}
...
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
   if (pMsg->message == WM_KILLFOCUS)
   {
      GXSuspend();
   }
   else if (pMsg->message == WM_SETFOCUS)
   {
      GXResume();
   }
   return CFrameWnd::PreTranslateMessage(pMsg);
}

The preceding code is not so tricky. All it does is some initialization or deinitialization of the Game API library. The important thing here is the GXDisplayProperties call. This function returns the current display capabilities, so you have the following information:

struct GXDisplayProperties {
   DWORD cxWidth;
   DWORD cyHeight;    // notice lack of 'th' in the word height.
   long cbxPitch;     // number of bytes to move right one x pixel
                      // - can be negative.
   long cbyPitch;     // number of bytes to move down one y pixel
                      // - can be negative.
   long cBPP;         // # of bits in each pixel
   DWORD ffFormat;    // format flags.
};

Most of recently available devices use 16 bits per pixel. Therefore, to reach some pixel with coordinates (X,Y), you may use the following simple formula:

Pixel = X * cbxPitch + Y * cbyPitch;

In addition, the color value of each pixel is packed into 2 bytes (remember that we’re in 16 BPP mode?). You may find several supported formats in gx.h. In most cases, the ffFormat field has kDirect565 value. The following sample shows how to make the required conversion:

unsigned short nColor;

// you may do it this way...
nColorRed   >>= 3;
nColorGreen >>= 2;
nColorBlue  >>= 3;
nColor = (unsigned short)( ( nColorRed & 0xff )   << 11 |
                           ( nColorGreen & 0xff ) << 5 |
                           ( nColorBlue & 0xff ) );
// or by one shot...
nColor = (unsigned short)( ( nColorRed & 0xf8 )   << 8 |
                           ( nColorGreen & 0xfc ) << 3 |
                           ( nColorBlue & 0xf8 )  << 3 );

Well, that’s actually all the significant information you need to start coding. There are many holes we haven’t touched here, e.g. different image operations/loading, etc. Let me leave these for your fun. The only thing I may promise is that it will work at least twice as fast than using the GDI API.

Conclusions

As we have seen above, GAPI is a small yet powerful library. While DirectDraw is not supported on all WinCE devices, GAPI serves us pretty well. Well, well, it doesn’t provide text output, but who cares about it…

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers’ lives in the mobile jungles a little bit simpler.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read