We will create the primary and backbuffer surfaces simultaneously, creating what is known as a complex surface. A complex surface is one surface with a series of separate surfaces that are implicitly attached to it. These surfaces are arranged in a linear fashion. Typically, page flipping applications create only two surfaces (the primary and one backbuffer). However, some applications may need three or more.
Note: The primary surface and all backbuffers must reside in video memory. Thus, the number of backbuffers that can be created is dependent upon the size of the surfaces, the color depth, and the available display memory.
The primary surface is always visible to the user. Animation is accomplished by drawing any graphical output to the backbuffer surface, which is offscreen. When the next frame of animation needs to be displayed, the backbuffer surface memory and the primary surface memory change places (or flip). Only the memory referenced by these two objects change; no other attributes or other attached objects are affected. Your BackBuffer object will always point to the backbuffer surface, and your PrimarySurface object will always point to the primary surface. This is illustrated below.
Figure 4-4: Page flipping the primary and backbuffer surfaces
We can create the primary and backbuffer surfaces by calling the IDirectDraw4 method CreateSurface. The CreateSurface method is defined as:
function CreateSurface(
var lpDDSurfaceDesc: TDDSurfaceDesc2; // a surface description structure var lplpDDSurface: IDirectDrawSurface4; // the IDirectDrawSurface4 object pUnkOuter: IUnknown // unused, set to nil
): HResult; // returns a DirectX error code The first parameter is a data structure that defines the type of surface we wish to create. The second parameter is a pointer to the IDirectDrawSurface4 object that is instantiated by the function. The last parameter is used for COM aggregation and should be set to nil.
The TDDSurfaceDesc2 structure is a very complex data structure as we examined above, but for the purposes of this function, we are concerned only with a few members. To begin, we should initialize all members of the surface by calling FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0). We need to indicate that we are interested in the capabilities and buffer count members of this structure, so we need to set its dwFlags member to DDSD_CAPS or DDSD_ BACKBUFFERCOUNT (by ORing the values, we combine them). We then must set its ddsCaps.dwCaps member to DDSCAPS_COMPLEX or DDSCAPS_FLIP or DDSCAPS_PRIMARYSURFACE. The
DDSCAPS_COMPLEX flag indicates we are creating a complex surface with implicitly attached surfaces,
DDSCAPS_FLIP indicates this will be a flipping structure, and DDSCAPS_PRIMARYSURFACE indicates that the root surface in the flipping structure will be the primary surface. We must also set the dwBackBufferCount to indicate how many backbuffers we will need. In our case, we can set this to one. Finally, we must set the dwSize member to SizeOf(TDDSurfaceDesc2).
Caution: DirectX methods use lots and lots of very complex data structures. These data structures may vary from version to version, and some methods may use only specific members of any particular structure. It is imperative that any data structure with a member denoting its size must have this member initialized by calling SizeOf. Failing to perform these two
requirements can lead to failed method calls.
Previous Table of Contents Next [an error occurred while processing this directive]
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.
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
When this method returns, the IDirectDrawSurface4 object will point to the primary surface. However, we really want to perform all of our animation on the backbuffer surface, so we must instantiate an object to point to this surface. This is accomplished through the IDirectDrawSurface4’s GetAttachedSurface method. When this method is called on a complex surface with implicitly attached surfaces, it instantiates an IDirectDrawSurface4 object to point to the specified surface. The GetAttachedSurface method is defined as:
function GetAttachedSurface(
var lpDDSCaps: TDDSCaps2; // surface capabilities var lplpDDAttachedSurface: IDirectDrawSurface4 // a surface object ): HResult; // returns an error code The first parameter is a TDDSCaps2 structure whose dwCaps member must be set to DDSCAPS_BACKBUFFER.
The second parameter is an IDirectDrawSurface4 object that will be set to point to the backbuffer surface when the function returns. This function returns the first attached surface whose capabilities match those defined in the function call. In our case, we receive a pointer to our only backbuffer surface. The TDDSCaps2 structure is defined as:
TDDSCaps2 = packed record
dwCaps: DWORD; // capabilities flags dwCaps2 : DWORD; // more capability flags dwCaps3 : DWORD; // yet more capability flags dwCaps4 : DWORD; // still more capability flags end;
The use of the CreateSurface and GetAttachedSurface functions will be demonstrated below.
Rendering with GDI
We’ve created a page flipping surface structure that will demonstrate DirectDraw’s animation capabilities, but unless we actually draw something to the screen, we won’t be able to tell if anything is happening when we actually flip the surfaces. Let’s cover a cool DirectDraw and Delphi trick that will make some tasks incredibly easy.
The IDirectDrawSurface4 object contains a method called GetDC which returns a handle to a GDI device context.
This allows us to use any GDI function call to render graphics, or text, directly onto the surface. If we create a TCanvas object and set its handle to the value returned by GetDC, we can take full advantage of all TCanvas methods. This is a very powerful Delphi technique that can make graphics programming incredibly simple. The
Title
---GetDC method is defined as:
function GetDC(
var lphDC: HDC // a variable that receives the DC ): HResult; // returns a DirectX error code The only parameter to this method is a variable that will receive the device context handle for the surface.
After we are through using the device context, we must release it before performing any DirectDraw methods that work on the surface, such as flipping or copying surface memory to or from the surface in question. We accomplish this by calling the IDirectDrawSurface4 method ReleaseDC, which is defined as:
function ReleaseDC(
hDC: Windows.HDC // a variable containing the DC to release ): HResult; // returns a DirectX error code
This method’s only parameter must be set to the device context handle returned from the previous call to GetDC.
Caution: It is absolutely imperative that every call to GetDC be matched with a call to ReleaseDC. When a device context is retrieved, the normal execution of Windows is put on hold. Therefore, any code between GetDC and ReleaseDC must execute as quickly as possible, as messages and other system events could get backed up. Also, any other DirectDraw methods that work directly on the surface must not be called until the device context has been released. Therefore, it is good practice to use a try..finally block, putting the ReleaseDC call in the Finally part to ensure that the device context is released.
Otherwise, your system will crash violently.
Any GDI function or TCanvas method can be used for rendering graphics to a surface. Bear in mind that you will be suffering from GDI’s sluggishness, but you may be able to reuse proprietary GDI graphics rendering methods and leverage your former development investments. Specifically, we’ll be using these techniques to perform text output in a number of examples. We will also examine how this makes the process of loading and displaying bitmaps incredibly simple.
Flipping the Surfaces
OK, we’ve created our complex flipping structure, we’ve drawn some text on the front and backbuffer in order to differentiate them, now we’re ready for action. To actually flip the contents of the surfaces, call the Flip method of the primary surface object. The Flip method is defined as:
function Flip(
lpDDSurfaceTargetOverride: IDirectDrawSurface4; // surface to flip dwFlags: DWORD // wait flags
): HResult; // returns an error code Typically, the first parameter is set to nil as the default behavior is to flip to the next buffer in the chain. However, if there is more than one backbuffer, this parameter can be set to the particular buffer in order to flip it to the screen.
The last parameter is set to either zero or DDFLIP_WAIT. If it is set to zero, the function immediately returns, regardless if the flip operation could be set up. If the DDFLIP_WAIT flag is specified, the function does not return until the flip operation occurs. Setting this parameter to zero allows the application to perform extra processing while waiting for the display hardware to set up the flip operation. However, the Flip method must continuously be called in order to set up the flip operation. If the flip operation could not be set up, this function will return
DDERR_WASSTILLDRAWING. In most cases, it is easiest to set this parameter to DDFLIP_WAIT, which is the flag we will use in the examples throughout this book.
Previous Table of Contents Next [an error occurred while processing this directive]
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.
Table 4-1: Memory location flags
Flag Memory Location
DDSCAPS_LOCALVIDMEM DDSCAPS_NONLOCALVIDMEM DDSCAPS_VIDEOMEMORY DDSCAPS_SYSTEMMEMORY
Conventional video memory. Must be combined with DDSCAPS_VIDEOMEMORY.
Accelerated Graphics Port (AGP) display memory. Must be combined with DDSCAPS_VIDEOMEMORY.
Video memory.
System memory.
Note: DDSCAPS_NONLOCALVIDMEM is applicable only to AGP hardware, and only if the DirectDraw driver for that hardware supports it. You can determine if AGP is supported by DirectDraw by retrieving the hardware capabilities as discussed below.
Previous Table of Contents Next [an error occurred while processing this directive]
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 following example demonstrates the creation of the primary and backbuffer surfaces, rendering using GDI functions, and page flipping.
Listing 4-4: A simple page flipping application
var
Form1: TForm1;
FDirectDraw: IDirectDraw4;
FPrimarySurface,
FBackBuffer: IDirectDrawSurface4;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
Title
{initialize form properties. note that the FormStyle property must be
{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;
{these variables are used in GDI rendering}
TempCanvas: TCanvas;
SrfcDC: HDC;
begin
{create a temporary DirectDraw object. this is used to create the desired DirectDraw4 object}
DirectDrawCreate(nil, TempDirectDraw, nil);
try
{we can only get a DirectDraw4 interface through the QueryInterface method of the DirectDraw object}
TempDirectDraw.QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
{now that we have the desired DirectDraw object, the temporary DirectDraw object is no longer needed}
TempDirectDraw := nil;
end;
{set the cooperative level}
FDirectDraw.SetCooperativeLevel(Handle, DDSCL_FULLSCREEN or DDSCL_EXCLUSIVE);
{set the display resolution and color depth}
FDirectDraw.SetDisplayMode(640, 480, 8, 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 := DDSCAPS_COMPLEX or DDSCAPS_FLIP or DDSCAPS_PRIMARYSURFACE;
DDSurface.dwBackBufferCount := 1;
{create the primary surface object}
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}
FPrimarySurface.GetAttachedSurface(DDSCaps, FBackBuffer);
{create a temporary canvas object and retrieve an HDC for the primary surface}
TempCanvas := TCanvas.Create;
FPrimarySurface.GetDC(SrfcDC);
try
{set the canvas’s handle to the surface’s DC}
TempCanvas.Handle := SrfcDC;
{clear the surface to black}
TempCanvas.Brush.Color := clBlack;
TempCanvas.FillRect(Rect(0, 0, 640, 480));
{draw some lime colored text}
TempCanvas.Font.Color := clLime;
TempCanvas.TextOut(100, 100, ‘Primary Surface. Press Escape to close’);
finally
{don’t forget to release the DC}
TempCanvas.Handle := 0;
FPrimarySurface.ReleaseDC(SrfcDC);
end;
{get the backbuffer’s DC}
FBackBuffer.GetDC(SrfcDC);
try
{set up the canvas, and clear the backbuffer to blue}
TempCanvas.Handle := SrfcDC;
TempCanvas.Brush.Color := clBlue;
TempCanvas.FillRect(Rect(0, 0, 640, 480));
{draw some yellow colored text}
TempCanvas.Font.Color := clYellow;
TempCanvas.TextOut(200, 200, ‘Backbuffer Surface. Press Escape to close’);
finally
{release the DC and destroy the temporary canvas object}
TempCanvas.Handle := 0;
{flip the contents of the primary and backbuffer surfaces}
FPrimarySurface.Flip(nil, DDFLIP_WAIT);
end;
Displaying Bitmaps
The ability to draw text, or even lines and polygons, into a surface is cool, but to really create anything visually exciting, we need to be able to display bitmaps. DirectDraw, unfortunately, does not have any built-in functionality for loading a bitmap, but using GDI techniques and Delphi’s TBitmap object, we can easily remedy the situation.
The most common application for loading a bitmap is to store background or sprite images off screen, which will later be copied into the primary or, most likely, the backbuffer surface. Therefore, the first order of business is to create the off-screen surface, which will hold the bitmap image. Again, we will use the CreateSurface method. However, this time we will initialize the dwFlags member of the TDDSurfaceDesc2 structure to DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH. We must then set the dwWidth and dwHeight members to the desired width and height of the surface, typically reflecting the width and height of the bitmap image. Finally, we set the ddsCaps.dwCaps member to
DDSCAPS_OFFSCREENPLAIN, indicating that this is a plain, off-screen surface. The color depth of the surface will match that of the primary surface.
Caution: You cannot create an off-screen surface wider than the primary surface in video memory unless the hardware specifically supports it. We cover retrieving the hardware capabilities below. However, you can create off-screen surfaces wider than the primary surface in system memory.
Surface Memory Location
When creating a surface, the application can request that the memory for a surface be allocated from a specific area.
This allows an application to create surfaces, and thus load bitmaps, into memory on the display hardware, or to system memory. Video memory has the advantage of being incredibly fast when copied into another surface also located on the video memory; display memory has the advantage of being directly accessed by the CPU faster than video memory.
When creating a surface, the following four flags can be combined with other flags in the ddsCaps.dwCaps member of the TDDSurfaceDesc2 structure, thereby controlling where the memory for the surface is located.
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.
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
You do not have to indicate a memory location for surfaces. By default, surfaces will be created in video memory.
When video memory is depleted, or a surface is too big tofit into what’s left, it is automatically created in system memory. However, if a location is specified in the CreateSurface call and there is not enough memory, it will fail.
Loading and Displaying the Bitmap
Actually loading the bitmap is quite easy. We simply create a TBitmap object and use its LoadFromFile method to pull the bitmap into memory. Of course, we must still somehow copy the bitmap into our off-screen surface. This is where the previous information about GDI comes in. Using GDI, we can retrieve a device context for our off-screen surface and use the GDI BitBlt function to copy the bitmap into the surface itself. Alternatively, we can create another TCanvas object, assign the surface’s device context to the TCanvas’s handle, and use the TCanvas.Draw method to copy the bitmap into the surface. With that accomplished, we can release the surface’s device context and destroy the TBitmap object. This is illustrated in Listing 4-5.
Blitting Surfaces
OK, now that the bitmap has been loaded into our off-screen surface, which should be located in display memory if there was enough room, we need to copy it from the off-screen surface onto our primary surface for display. Usually we would copy it into the backbuffer surface as part of our animation rendering engine, but for this example, we will simply copy it directly to the primary surface.
To copy one portion of a surface into another surface, we use the IDirectDrawSurface4’s BltFast method. The BltFast method is defined as:
function BltFast(
dwX: DWORD; // the destination horizontal coordinate dwY: DWORD; // the destination vertical coordinate lpDDSrcSurface: IDirectDrawSurface4; // the source surface
var lpSrcRect: TRect; // the rectangular area on the source dwTrans: DWORD // transfer flags
): HResult; // returns a DirectX error code The first two parameters determine the horizontal and vertical coordinates where the source surface area will be copied onto the destination surface. The third parameter is a pointer to the source surface, and the fourth parameter defines the rectangular area in the source surface that is to be copied onto the destination surface. The last parameter contains a series of flags that control certain aspects about the transfer. In particular, the DDBLTFAST_WAIT flag can be specified to indicate that the function should not return until the copying has been accomplished. Other flags
Title
---indicate transparency options, but these will be covered in the chapter on sprite techniques.
If the resulting bitmap looks somewhat corrupted, don’t worry. Our current example makes no use of palettes, and thus the resulting appearance of the bitmap will be at the mercy of the current contents of the system palette. We will examine palettes at length in the next chapter.
Listing 4-5: Loading and displaying a bitmap
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;
{these variables are used in GDI rendering}
TempCanvas: TCanvas;
SrfcDC: HDC;
{the bitmap object}
TempBitmap: TBitmap;
{defines the area to be copied}
SrcRect: TRect;
begin
{create a temporary DirectDraw object. this is used to create the desired DirectDraw4 object}
DirectDrawCreate(nil, TempDirectDraw, nil);
try
{we can only get a DirectDraw4 interface through the QueryInterface method of the DirectDraw object}
TempDirectDraw.QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
{now that we have the desired DirectDraw object, the temporary DirectDraw object is no longer needed}
TempDirectDraw := nil;
end;
{set the cooperative level}
FDirectDraw.SetCooperativeLevel(Handle, DDSCL_FULLSCREEN or DDSCL_EXCLUSIVE);
{set the display resolution and color depth}
FDirectDraw.SetDisplayMode(640, 480, 8, 0, 0);
{initialize the DDSurface structure to indicate that we will be creating a
{initialize the DDSurface structure to indicate that we will be creating a