Using EGI with DirectDraw
To use the EGI player with DirectDraw, what one has to do, broadly, is:
- Set up a DirectDraw application.
- Create a secondary surface that is compatible with the FLIC file and pass that surface to EPLAY32.
- Tell EPLAY32 not to draw each frame, but instead to notify the DirectDraw application of each new frame, so that the application can blit (or flip) the secondary surface to the primary surface.
- For 8-bit FLIC files, the DirectDraw application should also create a palette.
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:
- Set up the window (register a class, create the window). The DirectDraw initialization needs a window handle.
- Open the FLIC file, so you can query its size and colour depth to switch to an appropriate mode.
- 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.
- 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.
FlicAnim Flic; Flic = new FlicAnim("gears.flc"); if (Flic == NULL) return InitFail(hwnd, E_FAIL, "Opening FLIC animation FAILED");
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:
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:
- The colour table of a FLIC animation is stored in the first frame. To access it, you must therefore first decode a frame.
- 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.
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:
- create a callback function
- pass that function to the EGI player
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
- Download the EGIDDRAW example, including source code in C++ (64 KiB).
To run this example, you also need the development files of the EGI animation engine. The EGI evaluation version will do fine.