Topic : DrawPrimitive
Author : Mark Feldman
Page : 1 Next >>
Go to page :


Introduction


This article is divided into the following sub-sections:

1.DrawPrimitive Overview - provides a general description of DrawPrimitive's capabilities.
2.Error Handling - discusses various conventions used in the remainder of this article to handle errors.
3.Create DirectDraw Surfaces - shows how to initialize DirectDraw and set up surfaces on which to render.
4.The Device - shows basic code for finding and creating an appropriate Direct3D device.
5.The Viewport - shows how to set up a camera viewport.
6.Rendering A Scene - discusses the concept of sandwhiching DrawPrimitive calls between BeginScene() and EndScene().
7.Primitive Types - discusses the various types of primitives Direct3D can render.
8.Vertex Types - discusses the different vertex types and what Direct3D does with them.
9.Rendering a 2D Primitive - shows how to render a 2D triangle.
10.Matrices - discusses the various matrices used by DrawPrimitive and how to modify them yourself.
11.Rendering a 3D Primitive - shows how to render a 3D triangle.
12.The Render State - discusses rendering states and how to modify them.
13.Applying a Texture - shows how to apply a texture map to a triangle primitive.
14.Motion - shows basic animation by adjusting the matrices.
15.Z_Buffering - shows how to create a z-buffer, attach it to the backbuffer and use it.
16.Summary - wraps things up and provides a link to the demo.
At the end of this article I provide an MFC demo application (MSVC++ 5.0) with full source which demonstrates the techniques discussed in this tutorial.

DrawPrimitive Overview


DrawPrimitive is Microsoft's latest addition to the DirectX SDK and is supplied with version 5.0. Contrary to popular opinion, DrawPrimitive is not a wrapper for execute buffers. Although it still falls under the category of "Immediate Mode", it completely sidesteps execute buffers to provide a new way of directly accessing 3D accelerated hardware, and emulating it when not present. By eliminating the extra overhead of execute buffers, it is also slightly faster than the old method on many display cards. (I've heard rumors that a couple of vendors have supported DrawPrimitive in their drivers via their existing execute-buffer based code. IMHO, said vendors should be shot!).

DrawPrimitive currently allows you to render points, lines and triangles onto a DirectDraw surface. In order to render a scene you first select an appropriate device and set up a viewport. Primitives are then rendered by setting up an array of vertex structures and passing them in to the DrawPrimitive() or DrawIndexedPrimitive() function, along with information about what kind of primitives should be rendered and how.

Throughout this article I use numerous pointer variables. I use the C++ Microsoft Foundation Classes which are supplied with MSVC++ 5.0, so all pointers which must be saved for later use are prefixed with "m_" (for example, m_pPrimary is used to store a pointer to the primary surface). I show the variable declarations in the source code that follows, but keep in mind that any variable with the "m_" prefix should in fact be a member of the parent class, rather than a local variable.

Error Handling


Before going any further, let me discuss how I perform error handling and handle hardware acceleration problems. A given machine may or may not have hardware acceleration, but even when it is present you can still run into problems. Hardware devices can only perform acceleration when all surfaces reside in video memory, so if you create a hardware rendering device and then run out of VRAM when creating a surface then the device will not be able to use it (there are ways around this, but I'm keeping things simple here). Another problem can arise if your device performs software rendering and your surfaces wind up in VRAM. Video memory is typically much slower than system memory (particularly for reads) and VRAM surfaces can significantly degrade performance.

In the source code presented with this article I'll assume that all DirectX initialization is done in a single routine which accepts a boolean variable called bHardware. If bHardware is TRUE, then DirectX attempts to use a hardware accelerated device and VRAM surfaces, otherwise it tries to do everything in system memory. I call the inititalize routine once with bHardware set to TRUE. If it returns false due to an error then I clean everything up and try again, this time setting bHardware to FALSE.

Create DirectDraw Surfaces


Unlike OpenGL, DrawPrimitive renders to a DirectDraw surface rather than a device context. This is a good thing because it results in high performance and you can lock down the surface yourself to perform custom rendering if you want. This is also not such a good thing because it requires a bit more work to create a surface than to simply grab a window's device context.

First of all you need to create the main DirectDraw object. You'll also need a top-level window to set the cooperative level. In the first section of this demo I've decided to use 320x240x16bpp fullscreen mode, so the top level window must be created with the WS_POPUP style. If you're using MFC and a CFrameWnd based window then make sure you explicitly set the style to WS_POPUP, otherwise MFC will use the default style WS_OVERLAPPEDWINDOW. Here's the code I use:

#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define SCREEN_BITS 16

LPDIRECTDRAW m_pDD;
// Create main DirectDraw object
if (DirectDrawCreate(NULL, &m_pDD, NULL) != DD_OK)
return FALSE;
if (m_pDD->SetCooperativeLevel(GetSafeHwnd(), DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWMODEX) != DD_OK)
return FALSE;
if (m_pDD->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BITS))
return FALSE;


The next step is to create the primary display surface and a back buffer on which to render. If you're familiar with DirectDraw then this should all be relatively straightforward. In order for DIrect3D to use a DirectDraw surface, the surface must be created with the DDSCAPS_3DDEVICE caps flag set. In the code that follows I create back buffer with this flag set, and then attach it to the primary surface with a call to AddAttachedSurface(). Once this is done we can can call the primary surface's Flip() member function to display the back buffer on screen.

I have a very fast display adapter, and I find I can simply blt the backbuffer to the primary surface without getting image tear. I also often switch between full-screen and windowed mode (particularly if I need to debug something), so I prefer to create a seperate back-buffer which I blt, rather than setting up a complex flippable surface. Which method you use is up to you, just remember that when you create the back-buffer you must specify the DDSCAPS_3DDEVICE cap bits in order for Direct3D to be able to use it. Here's my initialization code for creating the primary and offscreen surfaces, which are stored in the m_pPrimary and m_pBackBuffer member variables respectively (the code presented here uses the Flip function rather than my own method of Bltting):

LPDIRECTDRAWSURFACE m_pPrimary;
LPDIRECTDRAWSURFACE m_pBackBuffer;
// Get the primary display surface
TRACE("DrawPrim Demo: Creating the primary surface\n");
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
if (hardware)
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
else
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_SYSTEMMEMORY;
if (m_pDD->CreateSurface(&ddsd, &m_pPrimary, NULL) != DD_OK)
return FALSE;

// Create a back buffer and attach it to the primary display surface to make a flippable surface
TRACE("DrawPrim Demo: Creating the back buffer\n");
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.dwWidth = SCREEN_WIDTH;
ddsd.dwHeight = SCREEN_HEIGHT;
if (hardware)
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY;
else
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_SYSTEMMEMORY;
if (m_pDD->CreateSurface(&ddsd, &m_pBackBuffer, NULL) != DD_OK)
return FALSE;
if (m_pPrimary->AddAttachedSurface(m_pBackBuffer) != DD_OK)
return FALSE;


Very Important Point: In the first version of this article, I was always creating the primary display surface with only the DDSCAPS_PRIMARYSURFACE flag. While this works fine on some cards, it doesn't work on others (notably Matrox cards) when you're using the software renderer. If the hardware flag is set to FALSE, then you need to also specify the DDSCAPS_SYSTEMMEMORY flag when creating the primary surface. This modification has been included in the demo at the end of this article. (Thanks to Scott Cottrille for pointing this out to me).

To flip between the two surfaces we call the Flip() member function:

// Flip the back buffer to the primary surface
m_pPrimary->Flip(NULL,DDFLIP_WAIT);


BTW, in my clean up code I use the same RELEASE() macro defined in the DirectX sample applications:

#define RELEASE(x) if (x) {x->Release(); x=NULL;}

The Device


To start creating D3D objects we need to obtain a master IDirect3D object, kinda like the equivelent of DirectDraw's LPDIRECTDRAW object. We can do this by calling QueryInterface() on the DirectDraw object itself:

LPDIRECT3D2 m_pD3D;
if (m_pDD->QueryInterface(IID_IDirect3D2, (LPVOID *)&m_pD3D) != S_OK)
return FALSE;


The next step is to find a compatible device we can used to render to the back buffer. A device is a bit like the D3D equivelent of a device context. It stores information for the current device doing the rendering (e.g. software renderer or hardware accelerated board) and maintains information about the current render state.

// Find a device we can use
D3DFINDDEVICESEARCH search;
D3DFINDDEVICERESULT result;
ZeroMemory(&search, sizeof(search));
search.dwSize = sizeof(search);
search.dwFlags = D3DFDS_HARDWARE;
search.bHardware = hardware;
ZeroMemory(&result, sizeof(result));
result.dwSize = sizeof(result);
if (m_pD3D->FindDevice(&search, &result) != D3D_OK)
return FALSE;

// Create the D3D device
if (m_pD3D->CreateDevice(result.guid, m_pBackBuffer, &m_pDevice) != D3D_OK)
return FALSE;


The Viewport


A viewport defines how the scene will be projected onto the screen. It does not not define the camera

Page : 1 Next >>