Managed DirectX First Steps: Direct 3D Basics and DirectX vs. GDI+ - Second Step: Coding Your First Windowed Test
(Page 9 of 14 )
This first test is very important, because it will establish the base of all future tests and programs. So you’ll make it very simple: Just initialize the Direct3D, create the device, draw a simple image, and count the frame rate. In order to allow you to see something happening, you’ll load an array of images (loaded as textures) and render them one at a time, over a square (composed of two triangles), so you’ll see the illusion of a walking guy.
You’ll use the basic Direct3D program structure, explained in the “Creating a Simple Direct3D Program” section, dividing the code into two groups:
- In your main window (coded in the click event for the corresponding button): The code that simply creates (and destroys) the test window and call to the initialization, finalization, and rendering routines defined in the test window
- In the test window: All the Direct3D routines—initialization, finalization, and rendering
The code for the main window, which will be very similar to other tests, is show here:
using (WindowTest windowTest = new WindowTest()) {
int desiredFrameRate = 10;
int lastTick = 0;
windowTest.Show();
// Initialize Direct3D and the Device object.
if(!windowTest.InitD3D(windowTest.Handle)) {
MessageBox.Show("Could not initialize Direct3D.");
windowTest.Dispose();
return;
}
else {
// Load the textures and create the square to show
them.
if(!windowTest.CreateTextures()) {
MessageBox.Show("Could not initialize vertices
and textures.");
return;
}
}
// Uncomment the lines below to see a smooth walking
man.
//desiredFrameRate = 10;
if(System.Environment.TickCount - lastTick >= 1000 /
desiredFrameRate) {
windowTest.Render();
// Frame rate calculation.
windowTest.Text = "Window Test. Frame rate: " +
DirectXLists.CalcFrameRate().ToString();
lastTick = System.Environment.TickCount;
}
Application.DoEvents();
// If you have no errors, then enter the rendering
loop.
while(!windowTest.EndTest) {
// Uncomment the lines below to see a smooth
walking man.
// Force a Frame rate of 10 frames to second on
maximum
//if(System.Environment.TickCount - lastTick >=
1000 / desiredFrameRate)
{
windowTest.Render();
// Frame rate calculation.
windowTest.Text = "Window Test. Frame rate: " +
DirectXLists.CalcFrameRate().ToString();
//lastTick = System.Environment.TickCount;
//}
Application.DoEvents();
}
}
In the rendering procedure you use a helper function, CalcFrameRate, that you create in order to make your code cleaner. In this function (shown in the next code listing), you use System.Environment. Tick Count to retrieve the current tick of the processor clock (with the precision rate of about 15 milliseconds), so you can calculate the frame rate. Note that this function isn’t very accurate, but since you’ll only use frame rate calculations to give you an idea of the speed at which you’re drawing the scene, and won’t include it in your final games, we think that using it is a valid approach.
private static int FrameRate, LastFrameRate, LastTick;
public static int CalcFrameRate() {
// Frame rate calculation.
if(System.Environment.TickCount - LastTick >= 1000) {
LastFrameRate = FrameRate;
FrameRate = 0;
LastTick = System.Environment.TickCount;
}
FrameRate++;
return LastFrameRate;
}
Following the sequence of the code just shown, let’s see the initialization routines for the WindowTest class. The InitD3D procedure will create the Direct3D object, define the presentation parameters for the window creation based on the current display mode, and create the Device object. If you don’t understand any part of the following code, refer to the first sections of this chapter for detailed explanations.
private Device device;
public bool InitD3D(IntPtr winHandle) {
DisplayMode dispMode =
Manager.Adapters[Manager.Adapters.Default. Adapter].
CurrentDisplayMode;
PresentParameters presentParams= new PresentParameters
();
// Define the presentation parameters.
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferFormat = dispMode.Format;
// Try to create the device.
try {
device = new Device(
Manager.Adapters.Default.Adapter,
DeviceType.Hardware,
winHandle,
CreateFlags.SoftwareVertexProcessing,
presentParams);
device.VertexFormat = customVertexFlags;
return true;
}
catch {
return false;
}
}
The most important part in the preceding code is the definition of the presentation parameters, which will rule the device creation. Let’s analyze this one line at a time.
In the first line of the code listing, you create the presentation parameters as an object of the PresentParameters type:
PresentParameters presentParams = new PresentParameters();
Then you state that you want to run in windowed mode. Because you didn’t specify the window size, the device will use the whole client area of the target window (defined by the handle used when creating Device).
presentParams.Windowed = true;
In the next line, you instruct the device to choose the best memory allocation when doing the screen flips, even if your back buffer got discarded. Note that this option doesn’t force the back buffer to be discarded, it just tells the device that you are re-creating the whole scene in the Render procedure, so it doesn’t need to preserve the contents of the back buffer when flipping.
presentParams.SwapEffect = SwapEffect.Discard;
The last line specifies the format of your back buffer. Because you’re running in windowed mode, it’s a must for you to use the current display mode format, because the window will be rendered using the same resolution and colors of the rest of the screen.
presentParams.BackBufferFormat = DispMode.Format;
The next function, following the main program sequence, is the one that will load the textures from disk and create a square in which to display them. To create such a function, first refer to the flexible vertices format (FVF) definition in the “Coloring and Texturing with Flexible Vertex Formats” section. You see that you’ll need to create a custom vertex type that will hold texture information in addition to the x, y, and z coordinates. And because you don’t want to make any 3-D transformations, you’ll create the vertex with an extra flag (rhw, which stands for “reciprocal of homogeneous w”) that informs the device that the coordinates are already transformed (they are screen coordinates). The definition of your VertexFormat is made using a constant value and creating the corresponding structure.
// Simple textured vertices constant and structure.
private const VertexFormats customVertexFlags =
VertexFormats.Transformed |
VertexFormats.Texture1;
private struct CustomVertex {
public float X;
public float Y;
public float Z;
public float rhw;
public float tu;
public float tv;
}
In order to help you fill the VertexFormat structure for each new vertex, it’s a good idea to create a helper function that fills the structure members and returns the vertex, as show in the following code snippet:
private CustomVertex CreateFlexVertex(float X, float Y,
float Z,
float rhw, float tu, float tv) {
CustomVertex cv = new CustomVertex();
cv.X = X;
cv.Y = Y;
cv.Z = Z;
cv.rhw = rhw;
cv.tu = tu;
cv.tv = tv;
return cv;
}
Now you can start thinking about the CreateTextures routine. Based on the basic concepts shown earlier, you can create a draft for the function as follows:
- Define the array of textures (must be public to the form, because it’ll be used in the Render procedure).
- Create the textures for each array element.
- Create and open the vertex buffer.
- Define the vertices.
- Close the buffer.
The textures you’ll be using show a draft of the walking man, and are numbered from walk1.bmp to walk10.bmp, as shown in Figure 3-24.
The code for the previous steps is shown next.
NOTE Notice that you create a separate function to generate the vertices, so the code becomes more readable and more easy to expand with different vertices.

Figure 3-24. Walking man textures, from walk1.bmp to walk 10.bmp (courtesy of Igor Sinkovec)
private const int numVerts = 4;
private VertexBuffer vertBuffer = null;
private Texture[] Textures = new Texture[10];
public bool CreateTextures() {
CustomVertex[] verts;
try {
string textureFile;
// Load the textures, named from walk1.bmp to
walk10.bmp.
for(int i=1; i<=10; i++) {
textureFile = Application.StartupPath
+@"\walk"+i.ToString() +".bmp";
textures[i-1] = TextureLoader.FromFile(device,
textureFile);
}
// Define the vertex buffer to hold your custom
vertices.
vertBuffer = new VertexBuffer(typeof(CustomVertex),
numVerts, device, Usage.WriteOnly, customVertexFlags,
Pool.Default);
// Locks the memory, which will return the array to
be filled.
verts = vertBuffer.Lock(0, 0) as CustomVertex[];
// Defines the vertices.
SquareVertices(verts);
// Unlocks the buffer, which saves your vertex
information to the device.
vertBuffer.Unlock();
return true;
}
catch {
return false;
}
}
private void SquareVertices(CustomVertex[] vertices) {
// Create a square, composed of 2 triangles.
vertices[0] = CreateFlexVertex(60, 60, 0, 1, 0, 0);
vertices[1] = CreateFlexVertex(240, 60, 0, 1, 1, 0);
vertices[2] = CreateFlexVertex(60, 240, 0, 1, 0, 1);
vertices[3] = CreateFlexVertex(240, 240, 0, 1, 1, 1);
}
This chapter is from Beginning .NET Game Programming in C# by David Weller et. al.(Apress, 2004, ISBN: 1590593197). Check it out at your favorite bookstore today. Buy this book now.
|
Next: Code the Render Procedure >>
More ASP.NET Articles
More By Apress Publishing