Click here to Skip to main content
15,860,859 members
Articles / Desktop Programming / Windows Forms
Article

Simple Managed DirectX Render Loop

Rate me:
Please Sign up or sign in to vote.
4.79/5 (23 votes)
14 Jul 20055 min read 98.5K   546   47   15
A simple framework for implementing the best possible Managed DirectX render loop for games or simulations written in Microsoft .NET.

Introduction

This is the framework of a Managed DirectX render loop. These loops are used to cause a game or simulation to render graphics and update state with the highest possible performance while still being responsive to user input and system events, without bogging down the entire system. This article does not explain how to use DirectX or Direct3D, only how to best arrange the main engine loop of your game or simulation for the best performance and responsiveness.

Background

This implementation comes directly from Tom Miller's blog entry on May 05, 2005. Tom Miller is a well known author and the lead developer on the Managed DirectX team at Microsoft. After many iterations, he has discovered that this pattern will provide the best possible performance that a .NET managed application can attain without resorting to WinAPI tricks. A more complete framework is included in the June '05 version of the DirectX SDK, but I'm going to show you the simplest possible implementation and explain why/how it works and why it is the best. Using C++ and avoiding the .NET WinForms base classes can be even faster, but there are many published techniques already available. The purpose here is to create a 100% managed solution using only .NET code.

Using the code

I'm going to analyze the whole class by looking at snippets at a time. The downloadable source includes a complete MainForm class.

When the application starts up, while still in the Main method, we go ahead and bind to an odd event: Application.Idle.

C#
[STAThread]
public static void Main(){
    MainForm myForm = new MainForm();
    Application.Idle += new EventHandler(myForm.Application_Idle);
    Application.Run(myForm);
}

If you are unfamiliar with Windows messages, they are simple notifications of events like key-presses and mouse movements, but can also cover things like system shutdown, minimizing your application, etc. Almost everything that happens within your computer ends up as a Windows message.

The Application.Idle event is fired whenever our application is done processing all incoming Windows-generated messages. Our goal here is to allow our application to process as much as possible as quickly as possible, but we don't want to stop the flow of incoming Windows messages.

Here is our Application_Idle event handler:

C#
protected void Application_Idle(object sender, EventArgs e){
    while(AppStillIdle){
        // TODO: Main game loop goes here.
    }
}

public bool AppStillIdle{
   get{
      PeekMsg msg;
      return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
   }
}

The AppStillIdle property makes use of a simple Win32 API function (PeekMessage) which checks to see if there are any pending Windows messages that our application needs to process. The reason we go into the while loop is that Application.Idle only gets fired once when the application is finishing up processing all possible Windows message and the queue of pending messages is empty. So we want to continue looping until Windows happens to give us another message to process (when PeekMessage returns true). Then we'll drop out of the loop and leave Application_Idle. The normal .NET WinForms Windows message handler will pick up the pending messages that we saw when PeekMessage returned true.

According to the Win32 API documentation, PeekMessage passes out a structure different from the one that .NET defines in System.Windows.Forms.Message. We have to redefine our own version of the Message class called PeekMsg:

C#
[StructLayout(LayoutKind.Sequential)]
public struct PeekMsg {
   public IntPtr hWnd;
   public Message msg;
   public IntPtr wParam;
   public IntPtr lParam;
   public uint time;
   public System.Drawing.Point p;
}

The StructLayout attribute is required when using .NET structs to pass into Win32 or COM.

Here's the native method definition for PeekMessage:

C#
[DllImport("User32.dll", CharSet=CharSet.Auto)]
private static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, 
        uint messageFilterMin, uint messageFilterMax, uint flags);

Making it Faster

There are a few enhancements we can make to this application. Some of them aren't hard to do, but the reasons for them aren't very clear. The first, is setting the ControlStyle of our form. This has to be done in the form constructor:

C#
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

These lines cause the form to not use the standard .NET and Windows form-drawing code, as well as bypass extra drawing-related messages and events. Obviously if we are going to be using DirectX to draw our game or simulation, we don't need Windows and GDI+ to be doing a whole bunch for us that will never be visible. This is the case for Full-Screen as well as Windowed games and simulations.

Another thing we can do is to modify our native method definition for PeekMessage:

C#
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("User32.dll", CharSet=CharSet.Auto)]
private static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, 
         uint messageFilterMin, uint messageFilterMax, uint flags);

By adding the SuppressUnmanagedCodeSecurity attribute to the call, .NET will skip the security-related checks that it would make before allowing your application to make a call out to a native Windows method. This is extremely dangerous and should not be done without carefully considering the security ramifications of making the call. In this case, making the method declaration private is good enough, especially since the call we are making is merely going to tell us if there is a message waiting for us or not. Unfortunately, the method in question passes back the next Windows message if there is one. Malicious code could use this to intercept keystrokes, watch network traffic, or interpret and redirect mouse clicks. Be sure not to leave this method call somewhere where malicious code can access it by keeping it private.

Points of Interest

Did you notice that we did nothing at all with the Control.Paint event? Well, the reason is that the event is not needed. Since the UserPaint style is set, .NET's control painting code gets disabled. Windows will still send the WM_PAINT message, but as soon as your application gets done doing ... nothing, then Application_Idle will fire and you'll be refreshing your DirectX surface anyways. If you really wanted do, you could make an additional call to render your scene from within the Control.Paint event handler, but I do not know what this will do to performance or responsiveness.

History

  • Released on July 14, 2005.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ivaylo Slavov16-Oct-12 8:14
Ivaylo Slavov16-Oct-12 8:14 
GeneralThis example in c++ Pin
aerocount8-Mar-10 12:15
aerocount8-Mar-10 12:15 
GeneralApplication.Run Pin
Dmitri Nеstеruk15-Nov-07 1:58
Dmitri Nеstеruk15-Nov-07 1:58 
Questionhoe to create a parser filter. Pin
amiya das27-Sep-07 0:42
amiya das27-Sep-07 0:42 
QuestionNo longer works properly after WindowsXP update? Pin
seefer25-Nov-06 12:02
seefer25-Nov-06 12:02 
AnswerRe: No longer works properly after WindowsXP update? Pin
seefer26-Nov-06 7:33
seefer26-Nov-06 7:33 
GeneralNot sure why this worked Pin
CAyuso21-Jul-06 8:10
CAyuso21-Jul-06 8:10 
GeneralJune '05 SDK update comment Pin
Proudest19-Mar-06 14:42
Proudest19-Mar-06 14:42 
GeneralRe: June '05 SDK update comment Pin
Eric Falsken28-Mar-06 11:00
Eric Falsken28-Mar-06 11:00 
QuestionVB.NET conversion? Pin
Kal_Torak2-Feb-06 4:15
Kal_Torak2-Feb-06 4:15 
Generalbut watch out for those darn menu controls... Pin
Ruud van Eeghem6-Jan-06 3:58
Ruud van Eeghem6-Jan-06 3:58 
Question100% managed??? Pin
Pop Catalin12-Aug-05 2:24
Pop Catalin12-Aug-05 2:24 
AnswerRe: 100% managed??? Pin
Eric Falsken12-Aug-05 8:47
Eric Falsken12-Aug-05 8:47 
AnswerRe: 100% managed??? Pin
The_Mega_ZZTer22-Mar-09 8:10
The_Mega_ZZTer22-Mar-09 8:10 
In theory you could remove the while loop, then Application.Idle would just be raised again. Thus no call to PeekMessage would be needed.

Of course I wouldn't put all my game code in an Application.Idle handler, I'd put it in Main and use Application.DoEvents to handle incoming messages periodically but that's just me. Smile | :)

Disclaimer: I actually haven't tried this so it might not work exactly like that.
GeneralExcellent Pin
Ryan Beesley2-Aug-05 2:14
Ryan Beesley2-Aug-05 2:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.