Search Tips Advanced Search |
| Delphi Graphics and Game Programming Exposed with DirectX 7.0 by John Ayres Wordware Publishing, Inc. ISBN: 1556226373 Pub Date: 12/01/99
Search this book:
Previous | Table of Contents | Next
### The DirectX Baseline Application In an effort to expedite the creation of DirectX applications, this book contains a baseline DirectX application that instantiates DirectDraw, sets up a page flipping architecture, and implements a high-speed rendering loop that can attain the highest animation speed possible. This is implemented by providing an event handler for the TApplication’s OnIdle event. The OnIdle event is called whenever the application becomes idle; if the Done parameter of this method is set to False, this event will continuously be called by the application, providing a perfect spot for a rendering loop. There are stubbed out methods for drawing surface content and restoring surface content in the event that it is lost. A number of constants are defined at the top of the main unit that control the display resolution and color depth, as well as the cooperative level and the flags for the complex flipping surface. The indicated display resolution and color depth is checked to see if it is supported before setting the display mode. It will also shut off the rendering loop automatically when the application is Alt+Tabbed out of, and restart it when Alt+Tabbed back into. Theoretically, all one must do is load off-screen surfaces in the area indicated in the Activate method, flesh out the RestoreSurfaces method to restore any off-screen surface memory, and complete the DrawSurfaces method to render animation to the backbuffer surface. The act of flipping the surfaces is carried out by the rest of the application. The Escape key will exit the application and return to Windows. Bear in mind that this is a general architecture meant to assist the reader in quickly getting a DirectX application up and running. It also provides a standardized framework from which every other example throughout the rest of this book will be based, cutting down on the amount of code that must be illustrated in example listings. This is not the best architecture for every game. Indeed, you may need to alter it dramatically depending on the requirements of your application. However, the baseline application tries to be as unimposing as possible, taking care of standard functionality while providing plenty of room for application-specific logic. What follows is a complete listing of the baseline application. Future examples will be based on this application, and thus will not contain any of the code for initializing DirectDraw; code listings will only contain code pertinent to the current discussion. This baseline application will run as is, but the output will be a black screen. However, it is a simple task to implement code in the DrawSurfaces method that will output animation to the screen, and we will see many examples of this in the coming chapters. Listing 4-7: The DirectX baseline application
    unit DDrawSkelU;

    {******************************************************************************

        Skeleton DirectDraw Application

        Author: John Ayres

        This is a bare bones DirectDraw application that creates a primary surface

        with one backbuffer and implements a continuous flip between them. It

        contains some minimal error checking code as well as an application

        exception handling routine that flips back to the GDI surface so that

        exception messages can be seen. The Application.OnIdle event is used to

        implement the loop that flips the surfaces. This uses a large chunk of

        CPU time (which is ok in a game), but could potentially be replaced

        with a While..Do loop or some other looping architecture.

        This baseline code is used throughout the book as a starting point for

        examples. It was designed to be easily extensible, and should provide

        all of the initialization and startup code that you will need for your

        own DirectX applications.

        Code that is marked by ‘///’ can and should be replaced by your own code.

     ******************************************************************************

        Copyright © 1999 by John Ayres, All Rights Reserved

        This code is freeware and can be used for any personal or commercial

        purposes without royalty or license.  Use at your own risk.

     ******************************************************************************}

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

      DDraw;

    {these constants are used to modify specific attributes of the DirectX

     application, such as the resolution and color depth}

    const

      DXWIDTH      = 640;

      DXHEIGHT     = 480;

      DXCOLORDEPTH = 8;

      BUFFERCOUNT  = 1;

      COOPERATIVELEVEL = DDSCL_FULLSCREEN or DDSCL_ALLOWREBOOT or DDSCL_ALLOWMODEX

                         or DDSCL_EXCLUSIVE;

      SURFACETYPE      = DDSCAPS_COMPLEX or DDSCAPS_FLIP or DDSCAPS_PRIMARYSURFACE;

    const

      {this user-defined message is used to start the flipping loop}

      WM_DIRECTXACTIVATE = WM_USER + 200;

    type

      TfrmDXAppMain = class(TForm)

        procedure FormCreate(Sender: TObject);

        procedure FormDestroy(Sender: TObject);

        procedure FormKeyDown(Sender: TObject; var Key: Word;

          Shift: TShiftState);

        procedure FormActivate(Sender: TObject);

      private

        { Private declarations }

        {flips back to the GDI surface to display the exception error message}

        procedure ExceptionHandler(Sender: TObject; ExceptionObj: Exception);

        {the main rendering loop}

        procedure AppIdle(Sender: TObject; var Done: Boolean);

        {intercepts certain messages to provide appropriate functionality}

        procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

        {flips the DirectDraw surfaces}

        procedure FlipSurfaces;

        {restores any lost surfaces}

        procedure RestoreSurfaces;

        {draws the contents of surfaces}

        procedure DrawSurfaces;

      public

        { Public declarations }

      end;

    var

      frmDXAppMain: TfrmDXAppMain;

      {the main DirectDraw interface}

      FDirectDraw: IDirectDraw4;

      {the interfaces for the primary and backbuffer surfaces}

      FPrimarySurface,

      FBackBuffer: IDirectDrawSurface4;

    implementation

    uses

      DXTools;

    {$R *.DFM}

    { *–->>  BASELINE APPLICATION CODE <<–-* }

    { – the callback function used to ensure that the selected graphics –

      – mode is supported by DirectX                                    – }

    function EnumModesCallback(const EnumSurfaceDesc: TDDSurfaceDesc2;

                               Information: Pointer): HResult; stdcall;

    begin

      {if the height, width, and color depth match those specified in the

       constants, then indicate that the desired graphics mode is supported}

      if (EnumSurfaceDesc.dwHeight = DXHEIGHT) and

         (EnumSurfaceDesc.dwWidth = DXWIDTH) and

         (EnumSurfaceDesc.ddpfPixelFormat.dwRGBBitCount = DXCOLORDEPTH) then

        Boolean(Information^) := TRUE;

      Result := DDENUMRET_OK;

    end;

    { –> Events hooked to the application <– }

    { – this event is called when an exception occurs, and simply flips back –

      – to the GDI surface so that the exception dialog box can be read      – }

    procedure TfrmDXAppMain.ExceptionHandler(Sender: TObject;

                                             ExceptionObj: Exception);

    begin

      {disconnect the OnIdle event to shut off the rendering loop}

      Application.OnIdle := nil;

      {if the DirectDraw object was successfully created, flip to the GDI surface}

      if Assigned(FDirectDraw) then

        FDirectDraw.FlipToGDISurface;

      {display the exception message}

      MessageDlg(ExceptionObj.Message, mtError, [mbOK], 0);

      {reconnect the OnIdle event to reenter the rendering loop}

      Application.OnIdle := AppIdle;

    end;

    { – this method is continuously called by the application, and provides  –

      – the main rendering loop.  this could be replaced by a custom         –

      – while..do loop                                                       – }

    procedure TfrmDXAppMain.AppIdle(Sender: TObject; var Done: Boolean);begin

      {indicates that the application should continuously call this method}

      Done := FALSE;

      {if DirectDraw has not been initialized, exit}

      if not Assigned(FDirectDraw) then Exit;

      {note that at this point game logic could be inserted that controls things

       like sprite movement and collision detection}

      {draw surface content and flip the surfaces}

      DrawSurfaces;

      FlipSurfaces;

    end;

    { – handles certain messages that are required to make DirectX function  –

      – properly within Delphi                                               – }

    procedure TfrmDXAppMain.AppMessage(var Msg: TMsg; var Handled: Boolean);

    begin

      case Msg.Message of

        WM_ACTIVATEAPP:

          {unhook the OnIdle event when the application is being deactivated.

           this will stop all rendering}

          if not Boolean(Msg.wParam) then

             Application.OnIdle := nil

          else

            {upon activating the application, send ourselves the user-

             defined message}

            PostMessage(Application.Handle, WM_DIRECTXACTIVATE, 0, 0);

        WM_DIRECTXACTIVATE:

          begin

            {upon activating, restore all surfaces (reloading their memory as

             necessary), hook up the OnIdle event, and redraw the contents of

             all surfaces}

            RestoreSurfaces;

            Application.OnIdle := AppIdle;

            DrawSurfaces;

          end;

        WM_SYSCOMMAND:

          begin

            {do not allow a screen saver to kick in}

            Handled := (Msg.wParam = SC_SCREENSAVE);

          end;

      end;

    end;

    { –> Form Events <– }

    { – initialize essential form properties – }

    procedure TfrmDXAppMain.FormCreate(Sender: TObject);

    begin

      {set up the application exception handler}

      Application.OnException := ExceptionHandler;

      {initialize form properties.  note that the FormStyle property must be

       set to fsStayOnTop}

      BorderStyle := bsNone;

      BorderIcons := [];

      FormStyle   := fsStayOnTop;

      Color       := clBlack;

      Cursor      := crNone;

      {turn off the mouse cursor}

      ShowCursor(FALSE);

    end;

    { – provides essential cleanup functionality – }

    procedure TfrmDXAppMain.FormDestroy(Sender: TObject);

    begin

      {disengage our custom exception handler}

      Application.OnException := nil;

      {display the mouse cursor}

      ShowCursor(TRUE);

      {remember, we do not have to explicitly free the DirectX objects,

       as they will free themselves when they go out of context (such as

       when the application is closed)}

    end;

    { – this method initializes DirectX and creates all necessary objects – }

    procedure TfrmDXAppMain.FormActivate(Sender: TObject);

    var

      {we can only get a DirectDraw4 interface from the DirectDraw interface, so we

       need a temporary interface}

      TempDirectDraw: IDirectDraw;

      {structures required for various methods}

      DDSurface: TDDSurfaceDesc2;

      DDSCaps: TDDSCaps2;

      {flag used to determine if the desired graphics mode is supported}

      SupportedMode: Boolean;

    begin

      {if DirectDraw has already been initialized, exit}

      if Assigned(FDirectDraw) then exit;

      {create a temporary DirectDraw object. this is used to create the

       desired DirectDraw4 object}

      DXCheck( DirectDrawCreate(nil, TempDirectDraw, nil) );

      try

        {we can only get a DirectDraw4 interface through the QueryInterface

         method of the DirectDraw object}

        DXCheck( TempDirectDraw.QueryInterface(IID_IDirectDraw4, FDirectDraw) );

      finally

        {now that we have the DirectDraw4 object, the temporary DirectDraw

         object is no longer needed}

        TempDirectDraw := nil;

      end;

      {hook up the application message handler}

      Application.OnMessage := AppMessage;

      {call EnumDisplayModes and verify that the desired graphics mode

       is indeed supported}

      FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0);

      DDSurface.dwSize   := SizeOf(TDDSurfaceDesc2);

      DDSurface.dwFlags  := DDSD_HEIGHT or DDSD_WIDTH or DDSD_PIXELFORMAT;

      DDSurface.dwHeight := DXHEIGHT;

      DDSurface.dwWidth  := DXWIDTH;

      DDSurface.ddpfPixelFormat.dwSize := SizeOf(TDDPixelFormat_DX6);

      DDSurface.ddpfPixelFormat.dwRGBBitCount := DXCOLORDEPTH;

      SupportedMode := FALSE;

      DXCheck( FDirectDraw.EnumDisplayModes(0, @DDSurface, @SupportedMode,

                                            EnumModesCallback) );

      {if the desired graphics mode is not supported by the DirectX drivers,

       display an error message and shut down the application}

      if not SupportedMode then

      begin

        MessageBox(Handle, PChar(The installed DirectX drivers do not support a ‘+

                   ‘display mode of: ’+IntToStr(DXWIDTH)+‘ X ’+

                   IntToStr(DXHEIGHT)+’, ‘+IntToStr(DXCOLORDEPTH)+’ bit color‘),

                   Unsupported Display Mode Error’, MB_ICONERROR or MB_OK);

        Close;

        Exit;

      end;

      {set the cooperative level to that defined in the constants}

      DXCheck( FDirectDraw.SetCooperativeLevel(Handle, COOPERATIVELEVEL) );

      {set the display resolution and color depth to that defined in the constants}

      DXCheck( FDirectDraw.SetDisplayMode(DXWIDTH, DXHEIGHT, DXCOLORDEPTH, 0, 0) );

      {initialize the DDSurface structure to indicate that we will be creating a

       complex flipping surface with one backbuffer}

      FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0);

      DDSurface.dwSize  := SizeOf(TDDSurfaceDesc2);

      DDSurface.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;

      DDSurface.ddsCaps.dwCaps := SURFACETYPE;

      DDSurface.dwBackBufferCount := BUFFERCOUNT;

      {create the primary surface object}

      DXCheck( FDirectDraw.CreateSurface(DDSurface, FPrimarySurface, nil) );

      {indicate that we want to retrieve a pointer to the backbuffer (the surface

       immediately behind the primary surface in the flipping chain) }

      FillChar(DDSCaps, SizeOf(TDDSCaps2), 0);

      DDSCaps.dwCaps := DDSCAPS_BACKBUFFER;

      {retrieve the surface}

      DXCheck( FPrimarySurface.GetAttachedSurface(DDSCaps, FBackBuffer) );

      {at this point, offscreen buffers and other surfaces should be created. other

       DirectDraw objects should be created and initialized as well, such as

       palettes. the contents of all offscreen surfaces should also be initialized

       at this point}

      {post a message that will hook up the OnIdle event and start the main

       rendering loop}

      PostMessage(Handle, WM_ACTIVATEAPP, 1, 0);

    end;

    { –> Form Methods <– }

    { – this method is called in order to flip the surfaces – }

    procedure TfrmDXAppMain.FlipSurfaces;

    var

      DXResult: HResult;

    begin

      {perform the page flip. note that the DDFLIP_WAIT flag has been used,

       indicating that the function will not return until the page flip has been

       performed. this could be removed, allowing the application to perform other

       processing until the page flip occurs. however, the application will need

       to continuously call the Flip method to ensure that the page flip takes

       place}

      DXResult := FPrimarySurface.Flip(nil, DDFLIP_WAIT);

      {if the surfaces were lost, restore them. on any other error,

       raise an exception}

      if DXResult = DDERR_SURFACELOST then

        RestoreSurfaces

      else if DXResult <> DD_OK then

        DXCheck(DXResult);

    end;

    { – this method is called when the surface memory is lost  –

      – and must be restored.  surfaces in video memory that   –

      – contain bitmaps must be reinitialized in this function – }

    procedure TfrmDXAppMain.RestoreSurfaces;

    begin

      {restore the primary surface, which in turn restores any implicit surfaces}

      FPrimarySurface._Restore;  {here you should reload any bitmaps stored in video memory surfaces}

    end;

    { – this method is called when the contents of the surfaces need to be  –

      – drawn. it will be continuously called by the AppIdle method, so any –

      – rendering or animation could be done within this method             – }

    procedure TfrmDXAppMain.DrawSurfaces;

    begin

    end;

    { –> Deletable Events <– }

    ///

    { – as a matter of convenience this framework will terminate when the –

      – Escape key is pressed, but this should probably be deleted and    –

      – replaced with your own terminate methods                          – }

    procedure TfrmDXAppMain.FormKeyDown(Sender: TObject; var Key: Word;

      Shift: TShiftState);

    begin

      if Key = VK_ESCAPE then

        Close;

    end;

    ///

    { *–->>  END BASELINE APPLICATION CODE <<–-* }

    end.
Previous | Table of Contents | Next

Products | Contact Us | About Us | Privacy | Ad Info | Home Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.