DirectX 8 Graphics and Video: A Fresh Start
by Toby "Ace" Jones

Sometimes it is nice to start with a clean sheet of paper. While I can make jokes about Microsoft starting over eight times when it comes to DirectX, the latest version of DirectX is Microsoft’s freshest start since DrawPrimitive came into style.

DirectX 8 (DX8) really shows its maturity. Many operations have been simplified and streamlined, and it is now embarrassingly easy to access advanced functions. As an example, I used to write short OpenGL applications in under 500 lines. My pre-DX8 Direct3D initialization code was around 1000 lines. Now, with DX8, my OpenGL and Direct3D applications look almost identical.

Since DX8 is so different, I won’t spend a lot of time talking about what has changed. Instead, I will discuss what the API looks like now, and how you can take advantage of it in no time.

The DX8 Foundation consists of 6 APIs: DirectX Graphics (which includes Direct3D and the D3DX library), DirectX Audio (which includes DirectSound and DirectMusic), DirectInput, DirectPlay, DirectSetup, and DirectShow. DX8 is huge, so I won’t cover it all here. This article will get you started programming graphics and video using DX8.

I do not know if there is going to be an updated version of DirectX Media. DirectShow was formerly included in DirectX Media, but now it is part of the base DirectX runtime. The advantage to this is that now developers can take advantage of this API without having to install a separate runtime. Direct3D Retained Mode was part of DirectX Media, but the D3DX library mostly replaces this API. I suspect that DX transforms, another component of DirectX Media, has remained unchanged, and can be used from the old DirectX Media 6.1 SDK.

DirectX Graphics

Perhaps the most glaring change to DirectX is the lack of DirectDraw. DirectDraw is dead, replaced completely by Direct3D.

Direct3D is super-streamlined, and contains many new features. No longer do you have to enumerate everything under the sun. Direct3D consists of only 12 interfaces. The inheritance graph is very simple:

One of the coolest features is the addition of a shader language. Microsoft’s new shader language looks more like assembly language than it looks like Renderman or Quake 3’s shader language. However, the concepts are all the same.

Direct3D comes with a high level library called D3DX. The D3DX library is very slick, containing APIs to create everything from sprites, to fonts, to textures. Using D3DX is an easy way to jump-start your development.

Matrix operations are very clean (especially with D3DX), and it is far easier to work with than OpenGL. There are many other similarities with OpenGL:

Direct3D OpenGL
BeginScene glBegin
EndScene glEnd
DrawPrimitive glDrawElements
SetRenderState glEnable
SetTexture glBindTexture
Clear glClear

Many of these similarities appeared in earlier versions of DirectX, but with the new simplifications of DX8, it is very apparent as to how similar Direct3D and OpenGL now are.

2D programming is not dead even with the removal of DirectDraw. DX8 does have a sprite interface in its D3DX library. However, the preferred way to do 2D graphics is with simple textures. Since chroma keying has been removed, the only way to do transparency is with alpha blending.

You can probably tell that I am quickly becoming a fan of this API. I used to be an OpenGL die hard, but with all the improvements, there is little reason not to use Direct3D in your games. We’ll write some code shortly that will let you hitting the ground running.

DirectX Graphics is just plain awesome. The rest of DX8 looks old in comparison. After working with DX8 for a few days, you will wish other DX8 APIs worked like it.

DirectShow

DirectShow is Microsoft’s API for everything video. Anything to do with VCR’s, digital camcorders, and DVD players can be found here. It is no surprise that playback of video files also falls under this category, and game developers can now use this API with ease to add FMV to their games.

There are some new things added to DirectShow, but few are of interest to game developers. Support for European PAL video has been enhanced (meaning that it works now). Filters can be added and removed while a filter graph is running. Native support for Microsoft’s streaming format, ASF, has been added as well. However, even though DirectShow has many improvements, it remains the buggiest portion of DX8 due to its complexity.

Since playback of video is the primary reason why a game developer would use DirectShow, we’ll do some code that does just that a little bit later.

Our Basic Application (dxtest.cpp and d3d1.cpp)

I will present several demos along the way. To make things simple, each of the demos will share the same basic application.

The base application I will start with is a simple Win32 skeleton that is around 90 lines long. All it does is create a window and call the DirectX functions that I will define a bit later. These functions are InitDirect3D, ShutdownDirect3D, and DrawScene. This shell can be used with all the sample demos I will work with in this article, so I separated it from the rest of the code. This code is nothing you can’t find in Petzold, so I won’t reproduce it here. It is included in the code pack that comes with this article.

All the demos share the same ShutdownDirect3D function and related variables:

#define HELPER_RELEASE(x) { if(x) { (x)->Release(); (x) = NULL; }} IDirect3D8 * pID3D = NULL; IDirect3DDevice8 * pID3DDevice = NULL; IDirect3DVertexBuffer8 * pStreamData = NULL; IDirect3DIndexBuffer8 * pIndexBuffer = NULL; IDirect3DTexture8 * pTexture = NULL; void ShutdownDirect3D() { HELPER_RELEASE(pTexture); HELPER_RELEASE(pIndexBuffer); HELPER_RELEASE(pStreamData); HELPER_RELEASE(pID3DDevice); HELPER_RELEASE(pID3D); }

First I declared all the interfaces I will use. Take note that not all the demos will use all the interfaces. They are simply included to simplify and unify the code base.

ShutdownDirect3D simply releases all these interfaces. In the future, you may need to add extra code to shutdown these interfaces, but for now this is all we will need.

Let us start our initialization code, which is located in the InitDirect3D function. InitDirect3D and DrawScene are functions that you will change as we go on, so be sure to experiment with them. IDirect3D is the first interface that you need to instantiate. To do this you write:

IDirect3D8 * pID3D = Direct3DCreate8(D3D_SDK_VERSION);

Unlike most DirectX methods, there is no return code that you need to check here. You do, however, need to check that your pointer is non-NULL before you reference it.

Your next step would usually be to create a device interface. You can’t do this until you first call the GetAdapterDisplayMode method to get some needed information:

D3DDISPLAYMODE d3ddm; pID3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

This will get the parameters of the current display mode. The parameter you are looking for is the surface format. You can use this to build a D3DPRESENT_PARAMETERS structure:

D3DPRESENT_PARAMETERS present; ZeroMemory(&present, sizeof(present)); present.SwapEffect = D3DSWAPEFFECT_COPY; present.Windowed = TRUE; present.BackBufferFormat = d3ddm.Format;

D3DPRESENT_PARAMETERS describes information such as the format of a display’s surfaces, the type of swapping mechanism, and whether the app is windowed or full screen.

In this example, surface copying is used instead of page flipping because the app is windowed. The back buffer is set to match the surface of the current video mode. A surface represents an area that can be drawn upon. Surfaces have properties like resolution and color depth. It is important that our back buffer and our primary buffer match in these properties.

You can now create an IDirect3DDevice8 interface:

pID3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &present, &pID3DDevice);

This function has six parameters, but luckily, none of them are complex. D3DADAPTER_DEFAULT tells Direct3D to use the primary monitor. This is only an issue if you are using a multi-monitor system. You can use a secondary monitor by specifying the number of the monitor you wish to use. Calling GetAdapterCount on the IDirect3D interface will return the count of adapters in the system.

The next parameter, D3DDEVTYPE_HAL, tells Direct3D to use the HAL for display purposes. Other options include D3DDEVTYPE_REF and D3DDEVTYPE_SW, which are the reference software rasterizer and a user specified software rasterizer respectively. Usually you will want to use D3DDEVTYPE_HAL, but you may want to use reference rasterizer for some testing purposes. You certainly should ship with the HAL version.

You then specify the focus window. For a full screen application, you need to make this a top-level window.

D3DCREATE_SOFTWARE_VERTEXPROCESSING specifies the type of vertex processing. You can also use hardware, or a combination, but I chose software for maximum compatibility. You will want to use hardware vertex processing if you want hardware assisted T & L.

The last two parameters are simple. You pass in the structure that you built above, and you are returned the IDirect3DDevice8 interface. If the method returns D3DERR_NOTAVAILABLE, then you passed in valid parameters, but the device does not support them.

The nicest part about this method is that it automatically creates all your needed back buffers and depth buffers. Clipping is automatically enabled, as is backface culling. Lighting is also enabled, and since you will specify our own vertex colors later, you want to disable this:

pID3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

That is the end of our InitDirect3D function. Let’s see it in its entirety:

HRESULT InitDirect3D(HWND hwnd) { pID3D = Direct3DCreate8(D3D_SDK_VERSION); HRESULT hr; do { // we need the display mode so we can get // the properties of our back buffer D3DDISPLAYMODE d3ddm; hr = pID3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm); if(FAILED(hr)) break; D3DPRESENT_PARAMETERS present; ZeroMemory(&present, sizeof(present)); present.SwapEffect = D3DSWAPEFFECT_COPY; present.Windowed = TRUE; present.BackBufferFormat = d3ddm.Format; hr = pID3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &present, &pID3DDevice); if(FAILED(hr)) break; hr = pID3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); } while(0); return hr; }

We now turn our attention to the DrawScene function. For our first exercise, I want to just get something on the screen. Once we get to this point, adding to it should be trivial.

This is the DrawScene function we will start with:

HRESULT DrawScene() { HRESULT hr; do { // clear back buffer hr = pID3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0,63,0,0), 0, 0); if(FAILED(hr)) break; // start drawing hr = pID3DDevice->BeginScene(); if(FAILED(hr)) break; // Put all drawing code here hr = pID3DDevice->EndScene(); if(FAILED(hr)) break; // flip back buffer to front hr = pID3DDevice->Present(NULL, NULL, NULL, NULL); } while(0); return hr; }

This code is pretty simple, if you look beyond all the error handling. Clear will flood fill the buffers you specify. You can fill the z-buffer, the back buffer, or the stencil buffer. In this example, you want to fill the back buffer with the color green. So we set the flags to D3DCLEAR_TARGET, and the color to green.

BeginScene and EndScene don’t do anything in this example, but we will be using them in future versions. These functions will wrap all of our primitive drawing routines.

The Present function will cycle to the next back buffer. Since we only have one back buffer and one front buffer, the buffers simply flip. The back buffer will be displayed and we can now draw on the front buffer (actually, since we not doing page flipping, we are actually still drawing on the back buffer, but the concept is the same).

If you now run the program, you should get a window that is filled green. If everything worked okay, we can start writing code to draw triangles, the primitive that is the heart of game programming.

Drawing Triangles (d3d2.cpp)

Triangles have a few interesting properties that make them attractive to 3D programming. They are always planar. A combination of triangles can make up any shape. In the upcoming examples, we will use triangles to build a cube. I used a rotating cube as the base for my first 3D engine. If its good enough for one programmer, its good enough for another.

In its simplest form, a triangle consists of three vertices. How these vertices are defined is up to the programmer. A 2D triangle may be as plain as x and y coordinates for each point. A beefy 3D program may specify coordinates for position, transformed coordinates, color, several texture coordinates, and possibly other information.

The exact semantics of how to use this information is slightly different between OpenGL and Direct3D. Drawing discrete triangles would use this information raw and define each triangle separately. However, when drawing a model, vertices are shared between triangles, so storing all three vertices for each triangle would be inefficient. For both OpenGL and Direct3D, you can specify all the vertices of a model in a huge array. Triangles are defined as a triple of indices into this array. You can take this approach to the extreme by specifying many indices in another array, and passing the index array to a function, such as DrawIndexedPrimitive, which will draw a large part of the model at once.

We will get this far eventually, but for the next example we will just set up the foundations for this approach. To define your vertex format, Direct3D introduces the concept of a flexible vertex format (FVF). In FVF, you define a structure that includes just the components of the vertex that we need. This structure will change as your program changes, but you will initially define it as:

struct MYVERTEX { FLOAT x, y, z; // The transformed position FLOAT rhw; // 1.0 (reciprocal of homogeneous w) DWORD color; // The vertex color };

Go ahead and instantiate an array of this structure, named vertices, defining each of the three vertices for a triangle. In your InitDirect3D function, you have to create a vertex buffer:

int num_elems = sizeof(vertices) / sizeof(vertices[0]); pID3DDevice->CreateVertexBuffer(sizeof(MYVERTEX) * num_elems, D3DUSAGE_WRITEONLY, D3DFVF_XYZRHW|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &pStreamData);

Here, the size of the vertex array in bytes is the first parameter. Since the app won’t read from these vertices, you pass in D3DUSAGE_WRITEONLY. There are various other flags that you could pass here to specify how your vertex array would be used, but you can go ahead and trust Direct3D to do the right thing for now.

Next you specify the FVF that we are using. Since you are using pre-transformed coordinates (meaning that you won’t be doing matrix operations), you first specify D3DFVF_XYZRHW. Later when you do your own matrix transformations, this will change to D3DFVF_XYZ. D3DFVF_DIFFUSE tells Direct3D that you will specify a color for each of the vertices.

The next parameter represents the type of memory management you require. You can trust Direct3D again, and pass D3DPOOL_DEFAULT.

Lastly, you pass a pointer to your vertex buffer. You may recall that this was defined in our first example, but it went unused.

Your vertex buffer is useless without filling it with meaningful data:

MYVERTEX *v; pStreamData->Lock(0, 0, (BYTE**)&v, 0); for(int ii = 0; ii < num_elems; ii++) { v[ii].x = vertices[ii].x; v[ii].y = vertices[ii].y; v[ii].z = vertices[ii].z; v[ii].rhw = vertices[ii].rhw; v[ii].color = vertices[ii].color; } pStreamData->Unlock();

This isn’t that complicated. Lock returns a pointer to where you can write your vertex data. Next, you copy your vertex data verbatim from your vertices array. Then you give the pointer back.

A pair of calls will tell Direct3D about your FVF and set your vertex array as your active vertex array (you can have multiple vertex arrays).

pID3DDevice->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);

pID3DDevice->SetStreamSource(0, pStreamData, sizeof(MYVERTEX));

These parameters should be obvious. SetVertexShader tells Direct3D to use the same format that was specified in the CreateVertexBuffer call above. Since SetVertexShader and CreateVertexBuffer will use the parameter for the FVF, you can, and should, use a macro to make sure these stay the same.

SetStreamSource tells Direct3D to use pStreamData as the active vertex array, and gives the size of each element.

That was easy. You can now add the code to draw a triangle. In between the BeginScene and EndScene calls in the DrawScene function, insert this:

int num_elems = sizeof(vertices) / sizeof(vertices[0]); pID3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, num_elems / 3);

D3DPT_TRIANGLELIST will command Direct3D to draw discrete triangles, with each vertex specified individually. You start at index zero, and specify the number of triangles to draw as the last parameter.

If everything has been done correctly, you should have a triangle drawn on your previous green background.

Indexed Triangles (d3d3.cpp)

In the above code, you told DirectX to draw straight from the vertex array. The main issues with this are size and, indirectly, speed. Take a cube for example. A cube has eight vertices. Using the above code, you would need to draw 12 triangles, each with three vertices, for a total of 36 vertices in your vertex array. This is more than four times the number of vertices in the cube itself!

It would be better if you could just list each vertex once and index into this array. This way, you only have to transform eight vertices instead of 36. As it turns out, you can do this.

First you set up an index buffer. This is the list of indices into the vertex array.

num_elems = sizeof(indices) / sizeof(indices[0]); pID3DDevice->CreateIndexBuffer(sizeof(WORD) * num_elems, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &pIndexBuffer);

The variable indices is defined as:

WORD indices[] = { 0, 1, 2 };

CreateIndexBuffer is similar to your CreateVertexBuffer call above. First you pass the size of the buffer in bytes. Other flags are the same as before; D3DUSAGE_WRITEONLY because you only write to the buffer, D3DPOOL_DEFAULT to use the default memory configuration, and a pointer to receive the interface. D3DFMT_INDEX16 is the only new flag. This simply specifies the size of each element in the buffer. Since indices is defined as WORDs and since a WORD is 16 bits in Windows, you pass D3DFMT_INDEX16. You could pass D3DFMT_INDEX32, but a cube does not need that many indices.

Next you fill in this buffer, just as you did with the vertex buffers:

WORD *pIndex; pIndexBuffer->Lock(0, 0, (BYTE **)&pIndex, 0); for(ii = 0; ii < num_elems; ii++) { pIndex[ii] = indices[ii]; } pIndexBuffer->Unlock();

You lock the buffer, copy the elements into the buffer, and unlock it. This is the same as before. The second parameter to Lock is supposed to be the count of bytes to lock, but sending 0 (which is undocumented) locks the whole buffer.

Now you set this buffer as our index buffer, and then you can draw:

pID3DDevice->SetIndices(pIndexBuffer, 0);

In your DrawScene function, you can get rid of the DrawPrimitive method in exchange for a DrawIndexedPrimitive method:

pID3DDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, sizeof(indices) / sizeof(indices[0]), 0, sizeof(indices) / sizeof(indices[0]) / 3);

You are still drawing a triangle list as before. You also pass the minimum vertex index used (zero in this case), the number of indices used (three), the index number to start on (zero), and the number of triangles to render (one).

If all goes well, this program should produce the exact same output as the last one. It’s a bit more work, but it is also more scalable.

Adding Texture (d3d4.cpp)

Texturing is just one of those things that adds so much visual bang for the buck, that is would be ludicrous not to add it. Luckily for us, doing this in DX8 is painless.

First you add texture coordinates, tu and tv, into your MYVERTEX structure. Then add the appropriate values to your vertices array.

Next, in your CreateVertexBuffer and SetVertexShader methods, you have a parameter that looks like D3DFVF_XYZRHW|D3DFVF_DIFFUSE. You add D3DFVF_TEX1 to these flags to tell DirectX that you have one set of texture coordinates.

Add the code to copy tu and tv into the vertex array (between your Lock and Unlock methods).

DirectX will now draw the texture, but you have to tell it what texture to draw. In your InitDirect3D function, you add:

D3DXCreateTextureFromFile(pID3DDevice, "dx5_logo.bmp", &pTexture); pID3DDevice->SetTexture(0, pTexture);

Change "dx5_logo.bmp" to whatever bitmap you want to display. Here you are using the D3DX library to build an IDirect3DTexture8 interface. Next you put this texture into stage zero. You can have up to eight stages of textures, but for now, you will just use the one. You could also use the SetTextureStageState to add different features like MIP mapping and bump mapping, but you will just use the default values for now.

Now you have a texture-mapped triangle.

Using Matrices and Extra Texture Coordinates (d3d5.cpp)

It is time to build our cube. Now that you are entering the 3rd dimension (the previous examples were on a single plane), you have to enable your z-buffer. You also have to set up some matrices for model, world, and projection transformations.

Enabling the z-buffer is fairly easy. In your call to CreateDevice, you must add some extra fields to your D3DPRESENT_PARAMETERS structure:

present.EnableAutoDepthStencil = TRUE; present.AutoDepthStencilFormat = D3DFMT_D16;

This tells DirectX to use a 16 bit z-buffer. Next you call

pID3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);

and your z-buffer is set up. Lastly, in your DrawScene function, you must modify the Clear method to clear the z-buffer in addition to the back buffer:

pID3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0,63,0,0), 1.0, 0);

You add the flag D3DCLEAR_ZBUFFER to enable z-buffer clearing, and you pass 1.0 as the fill value for the z-buffer. Now all of your polygons will be drawn correctly.

Since you will be doing some transformations to your vertices, you can go ahead and remove the extra rhw parameter from your MYVERTEX structure. Remove the values from your vertices array, and the reference between your Lock and Unlock calls. Lastly, change your D3DFVF_XYZRHW references to D3DFVF_XYZ.

Direct3D has several types of matrices available, but we will use only three: World, View, and Projection. The World transformation will move the cube into world coordinates. The View transformations will move the world into view space. The Projection matrix will scale the world to make it look as if it has depth.

Now add a call to a new function, BuildMatrices, to your DrawScene function. BuildMatrices will build and activate your three matrices as described above. After you build each matrix, you call SetTransform, passing the matrix itself and the type of matrix it is.

As an example, we will build a no-op matrix:

D3DXMATRIX matrix; D3DXMatrixIdentity(&matrix); pID3DDevice->SetTransform(D3DTS_WORLD, &matrix);

As you can see, this fills the matrix structure with an identity matrix, and then tells DirectX to use this as the World transformation. The D3DX library does all the menial work for us.

In the example program, your model coordinates are already transformed to world coordinates, so you could just leave this code as is. However, this needs a bit of flavor. In your code, start your BuildMatrices function with:

D3DXMATRIX matrix; D3DXMatrixRotationY(&matrix, timeGetTime() / 1000.0f); pID3DDevice->SetTransform(D3DTS_WORLD, &matrix);

This setup will rotate the cube about the Y-axis. The parameter timeGetTime() / 1000.0f is the angle in radians. Doing this will give a smooth constant rotation.

Now you build the other two matrices:

D3DXMatrixLookAtLH(&matrix, &D3DXVECTOR3(0.0f, 3.0f, -5.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); pID3DDevice->SetTransform(D3DTS_VIEW, &matrix); D3DXMatrixPerspectiveFovLH(&matrix, D3DX_PI / 4, 4.0f / 3.0f, 1.0f, 100.0f); pID3DDevice->SetTransform(D3DTS_PROJECTION, &matrix);

D3DXMatrixLookAtLH builds a left-handed view matrix (some textbooks call this the camera). You pass three vectors: the position of the camera, the point you are looking at, and a vector that points up. Then you tell DirectX to use this as the view matrix.

D3DXMatrixPerspectiveFovLH builds a left-handed projection matrix that uses a variable field of view. D3DX_PI / 4 is the field of view in radians, which is 45 degrees. Then you pass the aspect ratio (most monitors are 4:3), and values representing our near and far clip plane. Lastly, you tell DirectX to use this as your projection matrix.

After adding the rest of the vertices to your vertex array, you are ready to go. The result should be a spinning textured cube.

Note, you are not reusing the vertices as described in example three. This is because you need up to three texture coordinates per vertex and you only have specified the one.

Full Screen Graphics

Okay, windowed programs are great, but most games run full screen. Full screen is not tough at all. All you have to do is build the correct D3DPRESENT_PARAMETERS structure before you call CreateDevice.

D3DPRESENT_PARAMETERS present; ZeroMemory(&present, sizeof(present)); present.SwapEffect = D3DSWAPEFFECT_FLIP; present.Windowed = FALSE; present.BackBufferFormat = d3ddm.Format; present.BackBufferWidth = d3ddm.Width; present.BackBufferHeight = d3ddm.Height; present.EnableAutoDepthStencil = TRUE; present.AutoDepthStencilFormat = D3DFMT_D16; present.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; present.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

Here we see that we change the swap effect to flip so that we are page flipping instead of copying the back buffer. You can continue to copy if you wish.

The back buffer now requires a height and width. We also set the refresh rate and the presentation speed, which is how often to page flip.

You can exit the example by pressing Alt+F4.

Shaders

One could write a book on shaders and barely scratch the surface of their power. In DX8, shaders come in two varieties: vertex and pixel.

Vertex shaders, of course, operate on vertices. You can change position, color, texture coordinate, or any other property of a vertex. This is great for all kinds of procedural effects.

Pixel shaders operate on pixels and can do all kinds of texture blending, noise generation, or anything else you can think of.

Shaders do come at a price. They can get expensive if they are complex. But every vertex and pixel passes through a shader at some point, even if the shader is simple. The call you made to SetVertexShader above did just that. Those shaders are simple, and since they are so common, have been optimized.

A shader script is simply a text file written in a sort of assembly language. Microsoft made no attempt to make it readable, and may result in write-only code (code that even you can’t read after you have written it), if you are not careful. The Microsoft documentation has some errors in it, so use it as a guideline rather than a rule. I highly recommend using MFCPixelShader and ShaderCity for shader testing purposes.

Video Playback

The Microsoft engineers added the DirectShow API to the main DirectX runtime. There is an interface, IDDrawExclModeVideo, that is supposed to coordinate between DirectShow and exclusive mode apps, but it requires a DirectDraw surface. As you may recall, DX8 has removed DirectDraw.

It is possible for a video to play while an IDirect3DDevice8 interface is running, so it doesn’t seem like you need to query for a DirectDraw interface. This awkward scenario is a big oversight of the DirectX developers.

Another oddity is that the DirectShow libraries needed to play video must be built by hand. In previous versions of DirectX Media, the DirectShow libraries (the "Base Classes") included source code as well as the .LIBs needed to link. In DX8, the Base Classes are moved to the Samples folder, and no .LIBs are included. Microsoft does include a VC6 workspace to build these, but you may run into difficulties if you have other SDKs installed (Platform SDK, previous DX Media, etc.).

Conclusion

DirectX 8 Graphics are awesome. The rest of DirectX would do well to follow its lead, especially DirectShow. The most glaring problem with DirectX Graphics is its lack of an existing extension mechanism, like OpenGL’s glGetString. Otherwise, it is an incredibly mature API, one that I look forward to using for some time. This was simply an introduction, and there is much more to be discovered.

I’d love to hear what people have to say. Drop me a line at tjones@hot-shot.com.

A note on the examples

The examples were built and tested with DirectX 8. When textures and video files were needed, files from the SDK were used. All six examples are included in the sample workspace. There may be some minor changes in order to build with your configuration.

Get the source code for this article

Discuss this article in the forums


Date this article was posted to GameDev.net: 11/30/2000
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
DirectX Graphics

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!