**DirectDraw7 / Direct3D Hybrid Engine
**Sample
Project

There comes a point in most 2D game projects using DirectDraw
when you start wondering how you can do all those cool alpha
blending and rotation effects you've seen in [insert your
favorite 2D game here]. Now, you could switch over to DirectGraphics8
to do all your 2D graphics, but the major drawback with that
is that a.) It is more complicated, and you have a learn a whole
different structure (No DirectDraw!), and b.) You can't *easily
*do surfaces of any size - D3D surfaces need to be square and
a power of 2 size no bigger than 256x256 (this is due to
hardware limitations of your users). But! What if
you could use DirectDraw for the parts of your game that need
big surfaces, and use Direct3D in conjunction for the parts that
need alpha blending, rotation, scaling, etc.? You can!
This tutorial will explain (hopefully) simply how to do this.

**Initializing DirectX**

You initialize DirectX almost the same as a normal DirectDraw project, (See how in this tutorial) with a few exceptions:

**1.) Declaring the D3D variables:**

Dim d3d As
Direct3D7

Public dev As Direct3DDevice7

**2.) Initializing the primary surface to be D3D capable:**

ddsdPrimary.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_3DDEVICE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX Or DDSCAPS_VIDEOMEMORY

**3.) Initializing Direct3D:**

Set d3d =
dd.GetDirect3D

Set dev = d3d.CreateDevice("IID_IDirect3DHALDevice",
BackBuffer)

**Loading Surfaces**

For loading surfaces, you will now have to take into account two kinds of surfaces:

**Normal DirectDraw Surfaces:**

- Can be any size - they don't need to be square or have power of 2 dimensions
- Can't use alpha-blending, scaling, rotation, or vertex coloring

**Direct3D Surfaces**

- Must be square, must have a power of 2 size, and can't be bigger than 256x256 (Possible sizes: 2x2, 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256)
- You can blit different sized portions from the square surface, if you need a non-square sprite.
- Supports Alpha Blending - This makes the the surface semi-transparent, for window or smoke type effects.
- Supports Rotation - You can manipulate the vertices of a sprite to rotate it.
- Supports Scaling - You can manipulate the vertices of a sprite to scale it bigger or smaller. In fact, you can manipulate the vertices to do all kinds of things, like skewing, distorting, and stretching.
- Supports Vertex Coloring - You can assign any color to each of the four corners of a D3D sprite, to "colorize" it.

So you will need a surface-loading routine that can load both types of surfaces. For the sample project, I first create a dynamic array of surfaces:

'The
element type of the Surfaces() array

Public
Type Surface

Surface As DirectDrawSurface7

Width As Integer

Height As Integer

D3DSurface As Boolean

End Type

'Array
of surfaces

Public
Surfaces() As Surface

'Current
number of surfaces in Surfaces()

Public
NumSurfaces As Long

This organizes the surfaces you use nicely. Here is my routine to load surfaces:

Public Sub
LoadSurface(File As String, Optional D3DSprite As Boolean)

Dim CKey As DDCOLORKEY

Dim SurfaceDesc As DDSURFACEDESC2

On Error GoTo ErrOut

NumSurfaces = NumSurfaces + 1

ReDim Preserve Surfaces(NumSurfaces)

'If
this is a D3D sprite, load it accordingly

If
D3DSprite = True Then

SurfaceDesc.lFlags = DDSD_CAPS Or DDSD_WIDTH
Or DDSD_HEIGHT Or DDSD_CKSRCBLT

SurfaceDesc.ddsCaps.lCaps = DDSCAPS_TEXTURE

SurfaceDesc.ddsCaps.lCaps2 =
DDSCAPS2_TEXTUREMANAGE

'Set
the color key

SurfaceDesc.ddckCKSrcBlt.high = ColorKey

SurfaceDesc.ddckCKSrcBlt.low = ColorKey

'Create
the surface

Set Surfaces(NumSurfaces).Surface =
dd.CreateSurfaceFromFile(File, SurfaceDesc)

'Set
the information for this surface

Surfaces(NumSurfaces).Width =
SurfaceDesc.lWidth

Surfaces(NumSurfaces).Height =
SurfaceDesc.lHeight

Surfaces(NumSurfaces).D3DSurface = True

'Normal
DirectDraw surface

Else

SurfaceDesc.lFlags = DDSD_CAPS Or DDSD_WIDTH
Or DDSD_HEIGHT

SurfaceDesc.ddsCaps.lCaps =
DDSCAPS_VIDEOMEMORY Or DDSCAPS_OFFSCREENPLAIN

'Create
the surface

Set Surfaces(NumSurfaces).Surface =
dd.CreateSurfaceFromFile(File, SurfaceDesc)

'Set
up the color key

CKey.low = ColorKey

CKey.high = ColorKey

Surfaces(NumSurfaces).Surface.SetColorKey
DDCKEY_SRCBLT, CKey

'Set
the information for this surface

Surfaces(NumSurfaces).Surface.SetForeColor
vbBlack

Surfaces(NumSurfaces).Width =
SurfaceDesc.lWidth

Surfaces(NumSurfaces).Height =
SurfaceDesc.lHeight

End If

ErrOut:

Exit Sub

End Sub

So whether or not the surface created is a D3D surface is determined by the second argument passed to the LoadSurface subroutine. An example of using this routine would be:

Private Sub
LoadSurfaces()

'Load
the sprite surface - D3D Surface is true so we can use alpha
blending, etc.

LoadSurface
App.Path & "\object.bmp", True

'Load
the background image surface - Not D3D surface so it can be any
size (This one is 640x480)

LoadSurface
App.Path & "\stars.bmp", False

End Sub

**Drawing Surfaces**

Now that we have loaded our surfaces, we need to display them on the screen! To do this, we must again take into account the two surface kinds. Drawing D3D surfaces is going to be much more complicated than normal DirectDraw surfaces. Bltting a normal DirectDraw surface is simple:

BackBuffer.Blt DestRect, Surfaces(SurfIndex).Surface, SrcRect, DDBLT_KEYSRC Or DDBLT_WAIT

D3D surfaces work differently. They are actually made up of two triangles that form the rectangle of the surface:

The corners are called vertices (Singular, vertex), and these are what allow us to do rotation and scaling. To set up for drawing a D3D sprite, we must first set up some vertices:

'The
vertices that will define this sprite

Dim
TempVerts(3) As D3DTLVERTEX

I created a beefy little subroutine called SetUpGeom() that will set up a supplied D3DTLVERTEX array according to the passed information:

Public Sub
SetUpGeom(Verts() As D3DTLVERTEX, SurfIndex As Integer, Src As
RECT, Dest As RECT, R As Single, G As Single, B As Single, A As
Single, Angle As Single)

'This
sub sets up the vertices for a sprite, taking into account

'width, height, vertex color, and rotation angle

'NOTE: R, G, and B dictate the color that the sprite will be -

'1, 1, 1 is normal, lower values will colorize the vertices

Dim SurfW As Single

Dim SurfH As Single

Dim XCenter As Single

Dim YCenter As Single

Dim Radius As Single

Dim XCor As Single

Dim YCor As Single

'Width
of the surface

SurfW =
Surfaces(SurfIndex).Width

'Height
of the surface

SurfH =
Surfaces(SurfIndex).Height

'Center
coordinates on screen of the sprite

XCenter
= Dest.Left + (Dest.Right - Dest.Left - 1) / 2

YCenter = Dest.Top + (Dest.Bottom - Dest.Top - 1) / 2

'Calculate
screen coordinates of sprite, and only rotate if necessary

If
Angle = 0 Then

XCor = Dest.Left

YCor = Dest.Bottom

Else

XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Bottom - YCenter) * Cos(Angle)

YCor = YCenter + (Dest.Bottom - YCenter) *
Sin(Angle) - (Dest.Left - XCenter) * Cos(Angle)

End If

'0 -
Bottom left vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

Src.Left / SurfW, _

(Src.Bottom + 1) / SurfH, _

Verts(0)

'Calculate
screen coordinates of sprite, and only rotate if necessary

If
Angle = 0 Then

XCor = Dest.Left

YCor = Dest.Top

Else

XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Top - YCenter) * Cos(Angle)

YCor = YCenter + (Dest.Top - YCenter) *
Sin(Angle) - (Dest.Left - XCenter) * Cos(Angle)

End If

'1 -
Top left vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

Src.Left / SurfW, _

Src.Top / SurfH, _

Verts(1)

'Calculate
screen coordinates of sprite, and only rotate if necessary

If
Angle = 0 Then

XCor = Dest.Right

YCor = Dest.Bottom

Else

XCor = XCenter + (Dest.Right - XCenter) *
Sin(Angle) + (Dest.Bottom - YCenter) * Cos(Angle)

YCor = YCenter + (Dest.Bottom - YCenter) *
Sin(Angle) - (Dest.Right - XCenter) * Cos(Angle)

End If

'2 -
Bottom right vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

(Src.Right + 1) / SurfW, _

(Src.Bottom + 1) / SurfH, _

Verts(2)

'Calculate
screen coordinates of sprite, and only rotate if necessary

If
Angle = 0 Then

XCor = Dest.Right

YCor = Dest.Top

Else

XCor = XCenter + (Dest.Right - XCenter) *
Sin(Angle) + (Dest.Top - YCenter) * Cos(Angle)

YCor = YCenter + (Dest.Top - YCenter) *
Sin(Angle) - (Dest.Right - XCenter) * Cos(Angle)

End If

'3 -
Top right vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

(Src.Right + 1) / SurfW, _

Src.Top / SurfH, _

Verts(3)

End Sub

To do scaling, simply supply a bigger or smaller destination width and/or height to this sub.

Now let's go through one of the vertices, step by step, to understand what's going on:

'Calculate
screen coordinates of sprite, and only rotate if necessary

If
Angle = 0 Then

XCor = Dest.Left

YCor = Dest.Bottom

Else

XCor = XCenter + (Dest.Left - XCenter) *
Sin(Angle) + (Dest.Bottom - YCenter) * Cos(Angle)

YCor = YCenter + (Dest.Bottom - YCenter) *
Sin(Angle) - (Dest.Left - XCenter) * Cos(Angle)

End If

This part calculates the correct destination coordinates of this vertex and puts them in the XCor and YCor variables. This is where rotation calculations are made. If angle is 0, then no rotation calculations are made. If a non-zero angle is supplied the destination coordinates will be rotated by the amount in the Angle variable, which is in radians. This is just a simple matter of trigonometry calculations, which, if you don't understand them, don't worry, you don't have to for them to work :) (BTW, thanks to W-Buffer for the trig. code here ;) Remember, if you want to rotate a sprite to a specific angle in degrees, multiply the degree angle by (pi / 180) to get the correct radian angle to supply to this subroutine.

'0 - Bottom
left vertex

dx.CreateD3DTLVertex
_

**XCor,
_
YCor, _
**0,
_

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

Src.Left / SurfW, _

Src.Bottom / SurfH, _

Verts(0)

These are the coordinates on the screen of this vertex, calculated previously to take into account rotation.

'0 - Bottom
left vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

**0, _
1, _
**dx.CreateColorRGBA(R,
G, B, A), _

0, _

Src.Left / SurfW, _

Src.Bottom / SurfH, _

Verts(0)

These will just always be set this way, don't ask why :)

'0 - Bottom
left vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

**dx.CreateColorRGBA(R,
G, B, A), _
**0,
_

Src.Left / SurfW, _

Src.Bottom / SurfH, _

Verts(0)

The R, G, and B variables sent to this sub determine what color the sprite will be made. If they are all 1, the color will be normal. If R is 1, and the rest are 0, the sprite will be tinted red, and so on, according to RGB color standards. The A variable determines what level of visibility the sprite will have for alpha blending, 1 being completely solid, and 0 being invisible. So .5 would be a half-translucent state.

'0 - Bottom
left vertex

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

**0, _
**Src.Left
/ SurfW, _

Src.Bottom / SurfH, _

Verts(0)

Always gonna be zero :)

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

**Src.Left
/ SurfW, _
Src.Bottom / SurfH, _
**Verts(0)

These are the TU and TV values. These show where the source coordinates for this sprite are on it's surface - TU is the X coordinate, and TV is the Y coordinate. This is calculated by dividing the source coordinate by the size of the surface, which will give a number between 0 and 1. So for this vertex, which is vertex 0; the bottom left corner, the TU will be 0 (0 / 64 = 0), and the TV will be 1 (64 / 64 = 1). If the sprite started half-way across the surface, the TU would be .5 (32 / 64 = .5) If this is a little confusing, you probably don't have to worry about it, because you don't need to understand it to use the subroutine.

dx.CreateD3DTLVertex
_

XCor, _

YCor, _

0, _

1, _

dx.CreateColorRGBA(R, G, B, A), _

0, _

Src.Left / SurfW, _

Src.Bottom / SurfH, _

**Verts(0)**

This is the vertex array that this information will all be stored in.

Whew! Now that the geometry is all set up, we can actually draw the surface (Remember to call Dev.BeginScene before drawing any D3D surfaces, and call Dev.EndScene when you're all done):

'Enable
alpha-blending

dev.SetRenderState
D3DRENDERSTATE_ALPHABLENDENABLE, True

'Enable
color-keying (ColorKey is drawn transparent)

dev.SetRenderState
D3DRENDERSTATE_COLORKEYENABLE, True

dev.SetRenderState D3DRENDERSTATE_COLORKEYBLENDENABLE, True

'Use
Alpha Blend One alpha blending if the ABOne variable is true
(you can use any variable)

If
ABOne = True Then

dev.SetRenderState D3DRENDERSTATE_SRCBLEND,
D3DBLEND_ONE

dev.SetRenderState D3DRENDERSTATE_DESTBLEND,
D3DBLEND_ONE

'Or
Alpha blend to a certain fade value (0 - 1)

Else

dev.SetRenderState D3DRENDERSTATE_SRCBLEND,
D3DBLEND_SRCALPHA

dev.SetRenderState D3DRENDERSTATE_DESTBLEND,
D3DBLEND_INVSRCALPHA

dev.SetRenderState
D3DRENDERSTATE_TEXTUREFACTOR, dx.CreateColorRGBA(1, 1, 1, Alpha)

dev.SetTextureStageState 0, D3DTSS_ALPHAARG1,
D3DTA_TFACTOR

End If

'Set
the texture on the D3D device

dev.SetTexture
0, Surfaces(SurfIndex).Surface

dev.SetTextureStageState 0, D3DTSS_MIPFILTER, 3

'Draw
the triangles that make up our square texture

dev.DrawPrimitive
D3DPT_TRIANGLESTRIP, D3DFVF_TLVERTEX, TempVerts(0), 4,
D3DDP_DEFAULT

'Turn
off alphablending after we're done

dev.SetRenderState
D3DRENDERSTATE_ALPHABLENDENABLE, False

The alpha blending is specified here - either Alpha Blend One, or blending to a certain fade value (Determined by the Alpha variable - a fraction from 0 to 1, 0 being invisible, and 1 being solid). You can check the sample project to see how these different techniques look. Then the texture is set on the device, the triangles that make up the surface are drawn, and we're done!

Check out the sample project with source code, which demonstrates all these concepts in action, using a cool sprite engine demo! :)

-Matt Hafermann