Improved Direct3D Initialization

  CU_MDX_D3DInitImproved.zip (12.4 KiB, 3,808 hits)

In the last tutorial, we initialized Direct3D the quick and dirty way. We ignored some settings and told the CPU to handle a lot of calculations instead of the GPU. This is bad since the GPU is a lot faster at doing graphical calculations. To get the most hardware acceleration, we need to query the hardware to see what it is capable of. To achieve this performance boost, we’ll set some additional settings in the PresentParameters class. We’ll also do a little hardware querying to see if we can enable certain features that depend on the hardware capabilities. When we query the hardware, it’s capabilities are stored in a Capabilities structure. If you want to view your own display adapter’s capabilities, you can run the DirectX Caps Viewer utility that comes with the DirectX SDK.

Besides the extra initialization settings, we’ll also added fullscreen capabilities. Windows are exciting and all, but when I’m playing a game, it’s either fullscreen or death! In this tutorial, the fullscreen option is hard coded in the source. In the next tutorial, we’ll add a fullscreen toggle.

///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{

using ( Form1 form = new Form1( false, 640, 480 ) )

{

if ( !form.InitializeGraphics() )
{
MessageBox.Show( “Unable to initialize DirectX.” );
form.Dispose();
return;

}

Application.Idle += new EventHandler( form.OnApplicationIdle );
Application.Run( form );

}

}

I added a new constructor so we can specify whether we want to run in window mode or fullscreen mode. If we’re running in window mode, we can also specify the size of the window.

///

Creates a new Form
/// true for windowed mode, false for fullscreen.
/// Window width.

/// Window height.
public Form1( bool windowed, int width, int height )

{

InitializeComponent();
m_windowed = windowed;
this.ClientSize = new Size( width, height );

}

To set the size of the window, we just set the Form.ClientSize member to the specified dimensions.

/// Initializes DirectX graphics
/// true on success, false on failure
public bool InitializeGraphics()

{

m_displayMode = Manager.Adapters[0].CurrentDisplayMode;
m_caps = Manager.GetDeviceCapabilities( 0, DeviceType.Hardware );

// Check for hardware T&L
CreateFlags flags;
if ( m_caps.DeviceCaps.SupportsHardwareTransformAndLight )

{

flags = CreateFlags.HardwareVertexProcessing;
// Check for pure device
if ( m_caps.DeviceCaps.SupportsPureDevice )
{
flags |= CreateFlags.PureDevice;

}

}
else

{

flags = CreateFlags.SoftwareVertexProcessing;

}

if ( !BuildPresentParameters() )
{

return false;

}
try
{

m_device = new Device( 0, DeviceType.Hardware, this.Handle, flags, m_pp );
return true;

}
catch ( DirectXException )
{

return false;

}

}

The first thing we’ll do when we initialize Direct3D is get the current display mode and the video card’s capabilities from the Manager class. The DisplayMode holds information such as the screen resolution, refresh rate, etc. We’ll use this when we’re filling out the PresentParameters. The Capabilities structure describes what the hardware is capable of. We will enable certain features depending on what the hardware supports.

The first capability we check for is hardware transformation and lighting. Hardware T&L is a hardware calculation that greatly speeds up the performance of the application. It performs all vertex transformation and lighting calculations on the GPU. So we definately want to enable it if the hardware supports it. We also check if the hardware supports a pure device. A pure device is one that supports rasterization, transformations, lighting, and shading in hardware. MSDN has the following explanation of pure devices:

A pure device does not save the current state (during state changes), which often improves performance; this device also requires hardware vertex processing. A pure device is typically used when development and debugging are completed, and you want to achieve the best performance.

One drawback of a pure device is that it does not support Get* API calls; this means you can not use a pure device to query the pipeline state. This makes it more difficult to debug while running an application.

A second drawback of a pure device is that it does not filter any redundant state changes. When using a pure device, your application should reduce the number of state changes in the render loop to a minimum; this may include filtering state changes to make sure that states do not get set more than once. This trade-off is application dependent; if you use more than a 1000 Set calls per frame, you should consider taking advantage of the redundancy filtering that is done automatically by a non-pure device.

As with all performance issues, the only way to know whether or not your application will perform better with a pure device is to compare your application’s performance with a pure vs. non-pure device. A pure device has the potential to speed up an application by reducing the CPU overhead of the API. But be careful! For some scenarios, a pure device will slow down your application (due to the additional CPU work caused by redundant state changes). If you are not sure which type of device will work best for your application, and you do not filter redundant changes in the application, use a non-pure device.

So usually, while you’re developing your application, you would want to use a non-pure device. But I’m just going to go ahead and create a pure device now. Call me a rebel.

///

Builds the PresentParameters class.
/// true if the PresentParameters were successfully created, false otherwise.
public bool BuildPresentParameters()

{

m_pp = new PresentParameters();
Format adaptorFormat = ( m_windowed ) ? m_displayMode.Format : Format.X8R8G8B8;
if ( Manager.CheckDeviceFormat( 0, DeviceType.Hardware, adaptorFormat, Usage.DepthStencil, ResourceType.Surface, DepthFormat.D24S8 ) )
{
m_pp.AutoDepthStencilFormat = DepthFormat.D24S8;

}
else if ( Manager.CheckDeviceFormat( 0, DeviceType.Hardware, adaptorFormat, Usage.DepthStencil, ResourceType.Surface, DepthFormat.D24X8 ) )
{

m_pp.AutoDepthStencilFormat = DepthFormat.D24X8;

}
else if ( Manager.CheckDeviceFormat( 0, DeviceType.Hardware, adaptorFormat, Usage.DepthStencil, ResourceType.Surface, DepthFormat.D16 ) )

{

m_pp.AutoDepthStencilFormat = DepthFormat.D16;

}
else
{

return false;

}

m_pp.BackBufferWidth = ( m_windowed ) ? 0 : m_displayMode.Width;
m_pp.BackBufferHeight = ( m_windowed ) ? 0 : m_displayMode.Height;
m_pp.BackBufferFormat = adaptorFormat;
m_pp.BackBufferCount = 1;
m_pp.MultiSampleType = MultiSampleType.None;

m_pp.MultiSampleQuality = 0;
m_pp.SwapEffect = SwapEffect.Discard;
m_pp.DeviceWindowHandle = this.Handle;
m_pp.IsWindowed = m_windowed;
m_pp.EnableAutoDepthStencil = true;
m_pp.PresentFlag = PresentFlag.DiscardDepthStencil;

m_pp.FullScreenRefreshRateInHz = ( m_windowed ) ? 0 : m_displayMode.RefreshRate;
m_pp.PresentationInterval = PresentInterval.Immediate;

return true;

}

Here, I fill out the rest of the members of the PresentParameters class. I’m not going to go through and explain all the members because MSDN already does that. Just take note that some members are different depending on whether the application is running in window mode or fullscreen node.

For the PresentParameters.AutoDepthStencilFormat, we need to choose a format that the hardware supports. We can do that by calling Manager.CheckDeviceFormat. I cascade through a few format values and if none of them are supported, I return failure.

/// Keypress event.

/// Event arguments
protected override void OnKeyDown( KeyEventArgs e )
{

base.OnKeyDown( e );

switch ( e.KeyCode )
{

case Keys.Escape:
this.Close();
break;

}

}

Since we’re allowing fullscreen mode, we need a way to quit the application when we’re running in fullscreen mode. Here, I’m just telling the form to close when the user hits escape.

Direct3D is all initialized and ready to go, so now let’s create a framework that we will be able to reuse in future projects.