Using EGI with DirectDraw

Skip to main content (skip navigation menu)
Letterhead logo






Using EGI with DirectDraw

 

To use the EGI player with DirectDraw, what one has to do, broadly, is:

From the vantage point of the EGI player it is just that simple, and this paper would be a short one if it were not for DirectDraw. Indeed setting up a "hello world" style of program using DirectDraw already requires an impressive amount of code. Fortunately, much of this is "boilerplate" code.

The complete source code for this application note can be downloaded from a link at the end of this article.

At first, I set out to create a sample that could be compiled both as a full-screen ("exclusive") DirectDraw application and as a windowed ("non-exclusive") DirectDraw application. However, I turned out that these are entirely different beasts, altogether. Discussing them both along the same lines would only add to the confusion. In addition, DirectDraw is not that useful in windowed mode. So the reduced goal was to discuss EGI with DirectDraw in exclusive mode.

That said, it is quite possible to make DirectDraw application that runs both in exclusive mode and in windowed mode. The DirectDraw part of that application, however, will be split into two components with little common code.

This is by no means a tutorial (or a critique) of DirectDraw; this paper only shows how to create a DirectDraw application that uses EGI.

Set up an EGI - DirectDraw application

An appropriate order of initialisations is:

  1. Set up the window (register a class, create the window). The DirectDraw initialization needs a window handle.
  2. Open the FLIC file, so you can query its size and colour depth to switch to an appropriate mode.
  3. Create the DirectDraw object and surfaces. This procedure includes setting the cooperative level and the display mode. You may also need to create a palette.
  4. Pass the secondary surface to the EGI player, set the callback function or the callback window (a function is faster) and start playing.

The source code for the example application contains all of the above in that order. Here, I show only the segments that are specific to EGI: opening the FLIC file and passing the secondary buffer to the player.

Opening a FLIC file
  FlicAnim Flic;

  Flic = new FlicAnim("gears.flc");
  if (Flic == NULL)
    return InitFail(hwnd, E_FAIL, "Opening FLIC animation FAILED");
Setting a DirectDraw surface in the EGI player
  DDSCAPS ddscaps;
  HRESULT hRet;
  BOOL okay;

  ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
  hRet = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBackground);
  if (FAILED(hRet))
    return InitFail(hwnd, hRet, "Retrieving secondary surface FAILED");

  okay = Flic->SetData(FLIC_DATA_DDSURFACE, lpDDSBackground);
  if (!okay)
    return InitFail(hwnd, hRet, "FlicSetData (DDSURFACE) FAILED");
  Flic->SetParam(FLIC_PARAM_FRNOTIFY, TRUE);
  Flic->Play(hwnd);

The SetData method fails if the surface that you pass to the EGI player is incompatible with the FLIC file. That is, you must create an 8-bpp surface to play an 8-bpp FLIC file and a 16-bpp surface to play a HiColor FLIC file.

Since you requested a notification message for each frame (parameter FLIC_PARAM_FRNOTIFY), the window procedure will receive a FLIC_NOTIFY message at regular intervals. At reception of such message, you display the next frame, as in the snippet below:

Monitoring frame messages
long FAR PASCAL WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
  case FLIC_NOTIFY:
    if (wParam == FN_FRAME)
      NextFrame();
    break;

  (other cases)

  } /* switch */
  return DefWindowProc(hwnd, message, wParam, lParam);
}

void NextFrame(void)
{
  for (
    HRESULT hRet = lpDDSPrimary->Flip(NULL, 0);
    if (hRet == DD_OK)
      return;
    if (hRet != DDERR_WASSTILLDRAWING)
      return;
  } /* for */
}

Note that the window procedure will probably need to handle other messages (such as WM_DESTROY) too. I have removed anything that is unrelated to EGI.

Monitoring palette changes

In fact, the above code is too simple: it does not deal with a palette. Most FLIC files use 256 colours and 256 colour modes use a palette. The least a FLIC player should do is to read the palette from the animation and to create a DirectDraw palette from that. There are however two complications:

  1. The colour table of a FLIC animation is stored in the first frame. To access it, you must therefore first decode a frame.
  2. The palette may change halfway the animation.

Both issues are solved by creating a dummy DirectDraw palette after creating the surfaces and to set the palette entries at every time that the EGI player indicates that the animation's colour table changed. Below, I show only the modification of the FLIC_NOTIFY message handling; you will find more about creating a DirectDraw palette in the DirectDraw documentation.

Handling the palette
  case FLIC_NOTIFY:
    if (wParam == FN_FRAME) {
      if ((Flic->GetParam(FLIC_PARAM_FRAMEBITS) & FLIC_FRAME_PAL)!=NULL) {
        PALETTEENTRY pPaletteEntry[256];
        int          index;
        LPRGBQUAD    rgbq;

        // Get the colour table of the animation
        rgbq = (LPRGBQUAD)Flic->GetData(FLIC_DATA_PALETTE);
        // Convert this to the PALETTEENTRY structure
        for (index = 0; index < 256; index++) {
          pPaletteEntry[index].peFlags = PC_NOCOLLAPSE|PC_RESERVED;
          pPaletteEntry[index].peRed = rgbq[index].rgbRed;
          pPaletteEntry[index].peGreen = rgbq[index].rgbGreen;
          pPaletteEntry[index].peBlue = rgbq[index].rgbBlue;
        } /* for */
        // All entries are filled. Set them.
        lpDDPalette->SetEntries(0, 0, 256, pPaletteEntry);
      } /* if */
      NextFrame();
    } /* if */
    break;

Set a callback function instead of a window message

The code snippets above did not install a callback function and, as a result, the frame notifications will arrive as Windows messages. While this works, it is not optimal. Windows messages cause the player thread to be synchronized with the thread that created the Window. Delays in the Windows kernel for message queue processing may also cause irregularities in the interval at which the messages arrive.

To install a callback routine, you need to:

Creating a callback function
DWORD CALLBACK EGIcallback(LPFLIC /*lpFlic*/, int code, DWORD /*param*/)
{
  // use the global variable Flic (instance of the FlicAnim class) instead
  // of the passed in variable, so one can continue to use the C++ interface
  switch (code) {
  case FN_FRAME:
    if ((Flic->GetParam(FLIC_PARAM_FRAMEBITS) & FLIC_FRAME_PAL)!=NULL) {
      // There is a new palette for this frame, adapt the DirectDraw palette
      PALETTEENTRY pPaletteEntry[256];
      int          index;
      LPRGBQUAD    rgbq;

      // Get the colour table of the animation
      rgbq = (LPRGBQUAD)Flic->GetData(FLIC_DATA_PALETTE);
      // Convert this to the PALETTEENTRY structure
      for (index = 0; index < 256; index++) {
        pPaletteEntry[index].peFlags = PC_NOCOLLAPSE|PC_RESERVED;
        pPaletteEntry[index].peRed = rgbq[index].rgbRed;
        pPaletteEntry[index].peGreen = rgbq[index].rgbGreen;
        pPaletteEntry[index].peBlue = rgbq[index].rgbBlue;
      } /* for */
      // All entries are filled. Set them.
      lpDDPalette->SetEntries(0, 0, 256, pPaletteEntry);
    } /* if */
    NextFrame();
    break;
  } /* switch */
  return 0L;
}

The callback function also monitors for palette changes, just like the message based handler (actually, this is most of the code; the frame refresh is in the call to NextFrame()).

To install the callback, just add
    Flic->SetCallback(EGIcallback);
just before the call to
    Flic->SetParam(FLIC_PARAM_FRNOTIFY, TRUE);
in the initialization.

Handling lost surfaces

The EGI player automatically restores a surface when it finds that the surface is lost. If the surface that you passed to the EGI player was an implicitly created back buffer, the player restores the primary surface (front buffer) attached to that back buffer. If the surface passed to the EGI player was an independent surface, the player restores just that surface.

You may also want to monitor the WM_ACTIVATEAPP message to stop the animation when the DirectDraw application becomes inactive.

Downloads

To run this example, you also need the development files of the EGI animation engine. The EGI evaluation version will do fine.