Post by ATC on Oct 13, 2012 15:48:02 GMT -6
Hello everyone, and welcome to the forums!
Right now it's feeling like a ghost town around here because the forums just opened yesterday. However, we do have a couple new members so far and I'm out on the web trying to get more. I think that once we get some discussions and activity going on the forums will start to become fun! So I figured a good way to attract some new members and get some activity going would be to provide some basic programming tutorials. Programming newbies are the life-blood of any programming community/forums. So this one is for the newbs! :-)
What we're going to be learning about is how to implement a basic game structure in SlimDX with DirectX10. There are plenty tutorials out there that show how to initialize DirectX or draw a triangle, but isn't the point of learning DirectX to write an actual game?! Yes! So we're going to explore the basics of how a game program is shaped. The class we're going to write could be used in a project, but it's not a very robust class for a high-quality game. It was designed to be simple and easy to understand for learning purposes. So I recommend that you do not just copy+paste and use it verbatim to try to write games. Instead, use it as a learning aid to write your own, better code. So without further ado, let's begin...
If you've ever written an XNA application you're probably quite familiar with the "Game" class; a very handy base class that helps you get a game up and running in minutes because it already provides the basic program structure you need. Today we're going to develop our own "Game" class; just a very simple one you can expand to fit your needs.
If you haven't already, start a new Visual Studio C# project and use the Windows Forms Application template. Then delete the "Form1" class so that you have only Program.cs left. Then open Program.cs and delete everything inside the Main(...) method, leaving you with this:
static void Main() { }
We're going to fill in our Main() method later, but for now leave it completely empty. We have to get rid of the default Winforms Application code there to get our SlimDX game up and running properly.
Now let's add a new C# class to our project. Name it "Game.cs"... And let's make sure we have the proper references so our code will work. System.Drawing should already be referenced by default, since we used the Winforms Application template. But we need to reference the SlimDX API so we can access DirectX. So go to References >> Add Reference and make sure you have the ".NET" tab selected. Scroll down until you see "SlimDX". You may see several different versions of the assembly, so let's be sure to pick the right one. I suggest you pick the latest version that is installed on your machine, which should be (at this point in time) the January 2012 release. If you don't have Jan 2012 you need to visit the SlimDX website and download it. With that out of the way we need to make our selection of which SlimDX assembly to use based on what version of .NET we're using. If you're using Visual Studio 2008 or Express 2008 we have to use the .NET 2.0 version. Everyone else can use the .NET 4.0 version. You can tell which is which by reading the path. For instance:
C:\Program Files (x86)\SlimDX SDK (January 2012)\Bin\net20\x86\SlimDX.dll
I highlighted the parts of the path in red to show you how to tell the version and .NET platform target from simply reading the assembly's path...
Now that we have the proper .NET assembly references we can add the using directives to our Game.cs file. First, I always like to reference the most commonly used System assemblies I use when game programming (all of these are not necessary, but I find it easier to just use them all by default):
using System;
using System.IO;
using System.Linq;
using System.Drawing;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
Next we need to include the SlimDX references we need in our file. So add the following using directives after our System includes:
using SlimDX;
using SlimDX.DXGI;
using SlimDX.Windows;
using SlimDX.Direct3D10;
But before we continue there's one more thing we need to do: disambiguations! Since some of these SlimDX libraries have classes with the exact, same names we have to disambiguate them so the compiler knows what to make of our code:
//! Disambiguations ::
using Device = SlimDX.Direct3D10.Device;
using Buffer = SlimDX.Direct3D10.Buffer;
Now we've got the proper references and include directives, so we can begin writing our class and getting our basic game shaped up! :-)
Our class is going to need a few basic fields. We need a Direct3D10 Device interface for rendering, a DXGI SwapChain interface for flipping the front/back-buffers and presenting the frames we render, a DXGI Factory interface (helps with several things), a SlimDX.Windows.RenderForm to act as our game window and a D3D10 RenderTargetView to act as our render target. So let's add these fields to our class:
Device device;
Factory factory;
SwapChain swapChain;
RenderForm gameWindow;
RenderTargetView renderTarget;
The first thing we need to consider is what's going to happen when the game must shutdown. We're creating native Direct3D resources through the SlimDX wrapper (the fields we just added to our class, for instance). These native D3D resources are NOT under the control of the .NET CLR or garbage collector, and they will NOT automatically be disposed of when we no longer need them. So we will need to manually delete them to release the memory they consume. But there is, thankfully, a very easy way to do this that .NET provides: the IDisposable interface! With a little extra work we can code up our Game class to automatically dispose of its Direct3D resources and other resources when we're done with it or whenever we want to release it. All we need to do is implement the IDisposable interface. So let's do it!
First, let's "inherit" the interface:
The IDisposable interface only requires us to implement one method: Dispose(). However, we need to do a little extra work to really make it work properly and conveniently. So first, let's add the public IDisposable method we must implement:
Now we need to implement the private version of Dispose which takes a bool as a parameter. This method is what will actually do the disposal of our D3D resources. Here is how we write it:
You'll notice the last two lines set the "IsRunning" and "Disposed" properties. We have not defined these yet, but will get to them in a moment. But now let's do the final part of our IDisposable implementation: the object destructor! A destructor is similar to a constructor, except it runs when the garbage collector destroys an object. Here is our destructor definition:
Now we have our IDisposable implementation complete! This ensures that our DirectX resources will be properly deleted whether we remember to call Game.Dispose or not. If the object destructor is invoked by the GC it will suppress finanalization of the instance after calling Dispose(false), and releasing D3D objects. If we call Game.Dispose() then Dispose(true) will be called which will delete our D3D objects and free the RenderForm we're using. Pretty cool, huh? Now all we need to do is add the "Disposed" property, and IDisposable is totally complete:
Now let's add a few more handy properties to this class which will help us further down the road:
You will soon see how these are used. Let's move on to our Game class constructor which will allow us to create instances of the Game class and get our game going. This is how we define the constructor:
The constructor saves the desired screen width and height, and initializes our game window. We want to initialize the window here so that when we are ready to initialize DirectX our Form is already created and has a valid handle. So now we have everything done except for DirectX initialization, a way to start the game and a main message loop. So let's get to it and get our class finished!
First, let's handle graphics initialization. This is a pretty big method so we will go over it bit by bit. So create an empty method that returns void, takes no parameters and is named "InitializeGraphics", like so:
And we're going to start filling in this method with the code we need to initialize DirectX and get it ready for drawing things! First, let's initialize our DXGI Factory interface, which is quite easy:
Now that we've gotten that out of the way, we need to create our ModeDescription. To put it simply, a ModeDescription describes the display mode we want the game to use. Creating a ModeDescription is pretty easy and straight-forward:
In a real-world video game we cannot expect that the refresh rate will always be 60fps. This will work for our example and won't hurt anything, but will be an issue for large/complex games or developing an engine. To figure out the proper display mode settings for create a ModeDescription and SwapChainDescription you have to enumerate the machine's hardware through the DXGI layer. This is no trivial topic and can be quite complicated, and it is well beyond the scope of this tutorial. I just want you to be aware of it so you know not to do this in a real video game and you can prepare yourself by learning more about DXGI and enumerating devices (MSDN and the DirectX Documentation is a good starting place).
Next we need to create our SwapChainDescription. It is also quite easy and straight-forward:
I need to explain two things here. First, the SampleDescription field... We're simply hard-coding the value to 1:0, meaning one sample with a quality of zero. In other words, this amounts of multi-sample anti-aliasing being disabled. You could change these values to enable MSAA, however certain hardware configurations only support certain count and quality levels. For instance, 8:32 is valid on my machine with my GTX-580, but that may not work on someone else's machine. This is another thing that must be determined by enumerating the hardware with the DXGI layer. Make a mental note of it but let's move on. The other thing is just for clarification. I used the value of "nBuffers" for the BufferCount property of our SwapChainDescription. "nBuffers" is just a const int field I added to the Game class to make the buffer count easy to modify. So add the following to your class's fields:
I chose 2 buffers so we will have both a front and back-buffer. You could choose just 1 and draw to a single buffer. Or you could choose 4 and do stereoscopic rendering. The choice is yours. But let's just stick with double-buffering for now! :-)
Now it is time to create our D3D10 Device interface and our DXGI SwapChain interface! Luckily, it's not hard either because we've already got the things we need readied and set up!
Alternatively, you can call the Device.CreateWithSwapChain( ... ) method to create your device and swapchain simultaneously. However, that method requires you to pass in a DXGI Adapter instance you obtain from enumerating hardware. I didn't want to make you go through all of that just to get our simple Game class running.
The only thing left to do is to create our RenderTargetView and a Viewport, and bind them to the Device. Then we will be ready for rendering! First let's obtain the back-buffer from the SwapChain and use it to create our RenderTargetView:
Next, we create a Viewport:
Then we need to bind them both to our Device. We bind our RenderTargetView to the Device's OutputMerger, and the Viewport is bound to the Device's Rasterizer. This is a simple operation as well:
And that's it! Graphics initialization is complete and Direct3D is ready to render things to the screen! But before we hit the F5 key and try to see the results we have a couple more things to do. It won't actually work yet! :-)
Now let's define the "StartGame" method, which is what we use to actually start the game and fire up our main message loop. We define it like so:
Let's examine what's going on here. The first thing we do is check the "IsRunning" property. If the value is 'true' then the game has already been started and whatever code called it as at fault! So if that happens we will throw an InvalidOperationException so the offending programmer will know to fix their application. If IsRunning is false then it's the first time the method was called and we know it is ok to proceed. This prevents any nasty errors from occurring. So we then set IsRunning to 'true', so other objects can be aware that the game is running. And finally, we call MessagePump.Run, and pass it our game window and our main message loop method (in this case, a method called "_frameStep" which we will write next).
What exactly does this MessagePump.Run thing do? The best explanation can be found on the SlimDX website in the Basic Window Creation Tutorial. It explains the pitfalls of other methods of creating a main message loop and why their MessagePump.Run implementation is optimal (believe me, it is). If you don't care about that (shame on you! lol) then let's just proceed. But I suggest you read that tutorial and learn about why we do things this way.
Anyway, this particular overload of MessagePump.Run takes our game window and a callback function as parameters. It will automatically start a Windows message loop, make our game window pop up and start the application! And it will automatically call the callback function we give it when the application is free to perform rendering and game updates! So now we need to define that callback method called "_frameStep". It should have the following signature:
As I explained a moment ago, this _frameStep method will be called every time our application is free to perform rendering and updates. Basically, this is any time the application isn't busy processing important windows messages, so it's called pretty much constantly. In a real game you would hook into this callback method to implement your main game loop, run clocks/timers, etc. But for our example we're going to use it directly to clear the back-buffer and present the front-buffer. In other words we're going to directly use it for rendering. But we will do so with the knowledge that this is bad practice for a real game! So let's add the code:
The first bit of code clears our RenderTargetView with the specified System.Drawing.Color. In this case, we use the "CornflowerBlue" color. You can change that color to whatever you like, but I advise against using black in real games because it will make it impossible to see objects that aren't being lit properly by lighting in your shaders. Other than that it doesn't really matter what you use (you could use a shade of hot pink if you like). Doing this does serve a purpose. It overwrites all the left-over pixels from the last frame. If you fail to do this and the scene you're rendering doesn't cover every single pixel of the back-buffer you will see remnants of the last frame still on the screen! So yes, it's important to clear the back-buffer!
The final bit of code is the call to SwapChain.Present(...). This method makes the SwapChain "flip" the front and back-buffers around, so that front becomes back and back becomes front. And thus the frame that was just rendered becomes visible, and the device can begin rendering the next frame to the back-buffer while the front-buffer is being seen by the user. For the first parameter I passed it the value 1. If you pass it the value of 0 it will cause the swapChain to present frames as fast as possible. But with the value of 1 it will synchronize with the monitor's screen refreshes. In other words, we have a form of vertical synchronization (VSync). :-)
You can, however, change this value to 0 without hurting anything. Your frame rate will go up drastically, because the code won't slow down to sync up with the monitor. In a real video game, however, you almost ALWAYS want vertical synchronization. Without v-sync objects may appear to "tear" and other queer effects may be present. Besides, the human eye cannot distinguish frame-rates beyond about 30fps, so there's no reason not to synchronize other than to test how fast your code can go during development.
Anyway, that concludes our game class! Yes, it is finished! All we have left to do is actually use it! So let's edit our Main(...) method in Program.cs, so we can run our code and see the results:
Now our very basic Game class is done and we are ready to run it and see the results! Make sure you've done everything properly and then hit the F5 key to run our empty game in Debug mode! You should get an empty game window with a light-bluish background (that is the actual front-buffer you're seeing as the SwapChain flips it around to present it...the color is the Color "CornflowerBlue" that we're writing onto the buffer)! Pretty cool, huh? :-)
You should now be able to expand on this tutorial and write your own, more robust game class. You will, of course, need to do a good bit of work (for instance, implementing a robust main loop). However you now have a good foundation and you are armed with the knowledge you need to write games for SlimDX with DirectX10. Converting this sample to use DirectX11 is trivial and can be done in about 2-3 minutes or less. I hope you enjoyed this tutorial and will come back for more. If you have any questions this is the time and the place to ask.
The Game.cs class file is attached for download and use! :-)
Regards,
--ATC--
Right now it's feeling like a ghost town around here because the forums just opened yesterday. However, we do have a couple new members so far and I'm out on the web trying to get more. I think that once we get some discussions and activity going on the forums will start to become fun! So I figured a good way to attract some new members and get some activity going would be to provide some basic programming tutorials. Programming newbies are the life-blood of any programming community/forums. So this one is for the newbs! :-)
What we're going to be learning about is how to implement a basic game structure in SlimDX with DirectX10. There are plenty tutorials out there that show how to initialize DirectX or draw a triangle, but isn't the point of learning DirectX to write an actual game?! Yes! So we're going to explore the basics of how a game program is shaped. The class we're going to write could be used in a project, but it's not a very robust class for a high-quality game. It was designed to be simple and easy to understand for learning purposes. So I recommend that you do not just copy+paste and use it verbatim to try to write games. Instead, use it as a learning aid to write your own, better code. So without further ado, let's begin...
If you've ever written an XNA application you're probably quite familiar with the "Game" class; a very handy base class that helps you get a game up and running in minutes because it already provides the basic program structure you need. Today we're going to develop our own "Game" class; just a very simple one you can expand to fit your needs.
If you haven't already, start a new Visual Studio C# project and use the Windows Forms Application template. Then delete the "Form1" class so that you have only Program.cs left. Then open Program.cs and delete everything inside the Main(...) method, leaving you with this:
static void Main() { }
We're going to fill in our Main() method later, but for now leave it completely empty. We have to get rid of the default Winforms Application code there to get our SlimDX game up and running properly.
Now let's add a new C# class to our project. Name it "Game.cs"... And let's make sure we have the proper references so our code will work. System.Drawing should already be referenced by default, since we used the Winforms Application template. But we need to reference the SlimDX API so we can access DirectX. So go to References >> Add Reference and make sure you have the ".NET" tab selected. Scroll down until you see "SlimDX". You may see several different versions of the assembly, so let's be sure to pick the right one. I suggest you pick the latest version that is installed on your machine, which should be (at this point in time) the January 2012 release. If you don't have Jan 2012 you need to visit the SlimDX website and download it. With that out of the way we need to make our selection of which SlimDX assembly to use based on what version of .NET we're using. If you're using Visual Studio 2008 or Express 2008 we have to use the .NET 2.0 version. Everyone else can use the .NET 4.0 version. You can tell which is which by reading the path. For instance:
C:\Program Files (x86)\SlimDX SDK (January 2012)\Bin\net20\x86\SlimDX.dll
I highlighted the parts of the path in red to show you how to tell the version and .NET platform target from simply reading the assembly's path...
Now that we have the proper .NET assembly references we can add the using directives to our Game.cs file. First, I always like to reference the most commonly used System assemblies I use when game programming (all of these are not necessary, but I find it easier to just use them all by default):
using System;
using System.IO;
using System.Linq;
using System.Drawing;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
Next we need to include the SlimDX references we need in our file. So add the following using directives after our System includes:
using SlimDX;
using SlimDX.DXGI;
using SlimDX.Windows;
using SlimDX.Direct3D10;
But before we continue there's one more thing we need to do: disambiguations! Since some of these SlimDX libraries have classes with the exact, same names we have to disambiguate them so the compiler knows what to make of our code:
//! Disambiguations ::
using Device = SlimDX.Direct3D10.Device;
using Buffer = SlimDX.Direct3D10.Buffer;
Now we've got the proper references and include directives, so we can begin writing our class and getting our basic game shaped up! :-)
Our class is going to need a few basic fields. We need a Direct3D10 Device interface for rendering, a DXGI SwapChain interface for flipping the front/back-buffers and presenting the frames we render, a DXGI Factory interface (helps with several things), a SlimDX.Windows.RenderForm to act as our game window and a D3D10 RenderTargetView to act as our render target. So let's add these fields to our class:
Device device;
Factory factory;
SwapChain swapChain;
RenderForm gameWindow;
RenderTargetView renderTarget;
The first thing we need to consider is what's going to happen when the game must shutdown. We're creating native Direct3D resources through the SlimDX wrapper (the fields we just added to our class, for instance). These native D3D resources are NOT under the control of the .NET CLR or garbage collector, and they will NOT automatically be disposed of when we no longer need them. So we will need to manually delete them to release the memory they consume. But there is, thankfully, a very easy way to do this that .NET provides: the IDisposable interface! With a little extra work we can code up our Game class to automatically dispose of its Direct3D resources and other resources when we're done with it or whenever we want to release it. All we need to do is implement the IDisposable interface. So let's do it!
First, let's "inherit" the interface:
public class Game : IDisposable
{
// our fields are here
}
The IDisposable interface only requires us to implement one method: Dispose(). However, we need to do a little extra work to really make it work properly and conveniently. So first, let's add the public IDisposable method we must implement:
public void Dispose() {
Dispose( true );
}
Now we need to implement the private version of Dispose which takes a bool as a parameter. This method is what will actually do the disposal of our D3D resources. Here is how we write it:
private void Dispose(bool disposeResources) {
swapChain.Dispose();
device.Dispose();
factory.Dispose();
if (disposeResources)
{
if (gameWindow != null)
{
gameWindow.Hide();
gameWindow.Dispose();
}
}
this.IsRunning = false;
this.Disposed = true;
}
You'll notice the last two lines set the "IsRunning" and "Disposed" properties. We have not defined these yet, but will get to them in a moment. But now let's do the final part of our IDisposable implementation: the object destructor! A destructor is similar to a constructor, except it runs when the garbage collector destroys an object. Here is our destructor definition:
~Game()
{
Dispose(false);
GC.SuppressFinalize(this);
}
Now we have our IDisposable implementation complete! This ensures that our DirectX resources will be properly deleted whether we remember to call Game.Dispose or not. If the object destructor is invoked by the GC it will suppress finanalization of the instance after calling Dispose(false), and releasing D3D objects. If we call Game.Dispose() then Dispose(true) will be called which will delete our D3D objects and free the RenderForm we're using. Pretty cool, huh? Now all we need to do is add the "Disposed" property, and IDisposable is totally complete:
public bool Disposed { get; private set; }
Now let's add a few more handy properties to this class which will help us further down the road:
public int ScreenWidth { get; protected set; }
public int ScreenHeight { get; protected set; }
public bool IsFullScreen { get; protected set; }
public bool IsRunning { get; protected set; }
You will soon see how these are used. Let's move on to our Game class constructor which will allow us to create instances of the Game class and get our game going. This is how we define the constructor:
public Game(int width, int height, string title) {
this.ScreenWidth = width;
this.ScreenHeight = height;
gameWindow = new RenderForm(title)
{
ClientSize = new Size(ScreenWidth, ScreenHeight)
};
}
The constructor saves the desired screen width and height, and initializes our game window. We want to initialize the window here so that when we are ready to initialize DirectX our Form is already created and has a valid handle. So now we have everything done except for DirectX initialization, a way to start the game and a main message loop. So let's get to it and get our class finished!
First, let's handle graphics initialization. This is a pretty big method so we will go over it bit by bit. So create an empty method that returns void, takes no parameters and is named "InitializeGraphics", like so:
public void InitializeGraphics() { }
And we're going to start filling in this method with the code we need to initialize DirectX and get it ready for drawing things! First, let's initialize our DXGI Factory interface, which is quite easy:
this.factory = new Factory();
Now that we've gotten that out of the way, we need to create our ModeDescription. To put it simply, a ModeDescription describes the display mode we want the game to use. Creating a ModeDescription is pretty easy and straight-forward:
var mode = new ModeDescription()
{
Width = ScreenWidth,
Height = ScreenHeight,
Format = Format.R8G8B8A8_UNorm,
RefreshRate = new Rational(60, 1),
};
In a real-world video game we cannot expect that the refresh rate will always be 60fps. This will work for our example and won't hurt anything, but will be an issue for large/complex games or developing an engine. To figure out the proper display mode settings for create a ModeDescription and SwapChainDescription you have to enumerate the machine's hardware through the DXGI layer. This is no trivial topic and can be quite complicated, and it is well beyond the scope of this tutorial. I just want you to be aware of it so you know not to do this in a real video game and you can prepare yourself by learning more about DXGI and enumerating devices (MSDN and the DirectX Documentation is a good starting place).
Next we need to create our SwapChainDescription. It is also quite easy and straight-forward:
var scDesc = new SwapChainDescription()
{
ModeDescription = mode,
BufferCount = nBuffers, // this is a const int field in our class
OutputHandle = gameWindow.Handle,
SwapEffect = SwapEffect.Discard,
IsWindowed = !this.IsFullScreen,
Usage = Usage.RenderTargetOutput,
SampleDescription = new SampleDescription(1, 0),
Flags = SwapChainFlags.AllowModeSwitch,
};
I need to explain two things here. First, the SampleDescription field... We're simply hard-coding the value to 1:0, meaning one sample with a quality of zero. In other words, this amounts of multi-sample anti-aliasing being disabled. You could change these values to enable MSAA, however certain hardware configurations only support certain count and quality levels. For instance, 8:32 is valid on my machine with my GTX-580, but that may not work on someone else's machine. This is another thing that must be determined by enumerating the hardware with the DXGI layer. Make a mental note of it but let's move on. The other thing is just for clarification. I used the value of "nBuffers" for the BufferCount property of our SwapChainDescription. "nBuffers" is just a const int field I added to the Game class to make the buffer count easy to modify. So add the following to your class's fields:
const int nBuffers = 2;
I chose 2 buffers so we will have both a front and back-buffer. You could choose just 1 and draw to a single buffer. Or you could choose 4 and do stereoscopic rendering. The choice is yours. But let's just stick with double-buffering for now! :-)
Now it is time to create our D3D10 Device interface and our DXGI SwapChain interface! Luckily, it's not hard either because we've already got the things we need readied and set up!
this.device = new Device(DriverType.Hardware, DeviceCreationFlags.None);
this.swapChain = new SwapChain(factory, device, scDesc);
Alternatively, you can call the Device.CreateWithSwapChain( ... ) method to create your device and swapchain simultaneously. However, that method requires you to pass in a DXGI Adapter instance you obtain from enumerating hardware. I didn't want to make you go through all of that just to get our simple Game class running.
The only thing left to do is to create our RenderTargetView and a Viewport, and bind them to the Device. Then we will be ready for rendering! First let's obtain the back-buffer from the SwapChain and use it to create our RenderTargetView:
using (var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0))
this.renderTarget = new RenderTargetView(this.device, backBuffer);
Next, we create a Viewport:
var viewport = new Viewport(0, 0, ScreenWidth, ScreenHeight);
Then we need to bind them both to our Device. We bind our RenderTargetView to the Device's OutputMerger, and the Viewport is bound to the Device's Rasterizer. This is a simple operation as well:
device.OutputMerger.SetTargets(renderTarget);
device.Rasterizer.SetViewports(viewport);
And that's it! Graphics initialization is complete and Direct3D is ready to render things to the screen! But before we hit the F5 key and try to see the results we have a couple more things to do. It won't actually work yet! :-)
Now let's define the "StartGame" method, which is what we use to actually start the game and fire up our main message loop. We define it like so:
public void StartGame() {
if (this.IsRunning)
throw new InvalidOperationException("Game is already running!");
this.IsRunning = true;
MessagePump.Run(gameWindow, _frameStep);
}
Let's examine what's going on here. The first thing we do is check the "IsRunning" property. If the value is 'true' then the game has already been started and whatever code called it as at fault! So if that happens we will throw an InvalidOperationException so the offending programmer will know to fix their application. If IsRunning is false then it's the first time the method was called and we know it is ok to proceed. This prevents any nasty errors from occurring. So we then set IsRunning to 'true', so other objects can be aware that the game is running. And finally, we call MessagePump.Run, and pass it our game window and our main message loop method (in this case, a method called "_frameStep" which we will write next).
What exactly does this MessagePump.Run thing do? The best explanation can be found on the SlimDX website in the Basic Window Creation Tutorial. It explains the pitfalls of other methods of creating a main message loop and why their MessagePump.Run implementation is optimal (believe me, it is). If you don't care about that (shame on you! lol) then let's just proceed. But I suggest you read that tutorial and learn about why we do things this way.
Anyway, this particular overload of MessagePump.Run takes our game window and a callback function as parameters. It will automatically start a Windows message loop, make our game window pop up and start the application! And it will automatically call the callback function we give it when the application is free to perform rendering and game updates! So now we need to define that callback method called "_frameStep". It should have the following signature:
void _frameStep() { }
As I explained a moment ago, this _frameStep method will be called every time our application is free to perform rendering and updates. Basically, this is any time the application isn't busy processing important windows messages, so it's called pretty much constantly. In a real game you would hook into this callback method to implement your main game loop, run clocks/timers, etc. But for our example we're going to use it directly to clear the back-buffer and present the front-buffer. In other words we're going to directly use it for rendering. But we will do so with the knowledge that this is bad practice for a real game! So let's add the code:
device.ClearRenderTargetView(renderTarget, Color.CornflowerBlue);
/* Drawing code would go here... */
swapChain.Present(1, PresentFlags.None);
The first bit of code clears our RenderTargetView with the specified System.Drawing.Color. In this case, we use the "CornflowerBlue" color. You can change that color to whatever you like, but I advise against using black in real games because it will make it impossible to see objects that aren't being lit properly by lighting in your shaders. Other than that it doesn't really matter what you use (you could use a shade of hot pink if you like). Doing this does serve a purpose. It overwrites all the left-over pixels from the last frame. If you fail to do this and the scene you're rendering doesn't cover every single pixel of the back-buffer you will see remnants of the last frame still on the screen! So yes, it's important to clear the back-buffer!
The final bit of code is the call to SwapChain.Present(...). This method makes the SwapChain "flip" the front and back-buffers around, so that front becomes back and back becomes front. And thus the frame that was just rendered becomes visible, and the device can begin rendering the next frame to the back-buffer while the front-buffer is being seen by the user. For the first parameter I passed it the value 1. If you pass it the value of 0 it will cause the swapChain to present frames as fast as possible. But with the value of 1 it will synchronize with the monitor's screen refreshes. In other words, we have a form of vertical synchronization (VSync). :-)
You can, however, change this value to 0 without hurting anything. Your frame rate will go up drastically, because the code won't slow down to sync up with the monitor. In a real video game, however, you almost ALWAYS want vertical synchronization. Without v-sync objects may appear to "tear" and other queer effects may be present. Besides, the human eye cannot distinguish frame-rates beyond about 30fps, so there's no reason not to synchronize other than to test how fast your code can go during development.
Anyway, that concludes our game class! Yes, it is finished! All we have left to do is actually use it! So let's edit our Main(...) method in Program.cs, so we can run our code and see the results:
static void Main() {
using (var game = new Game(screenWidth, screenHeight, "My SlimDX Game"))
{
game.InitializeGraphics();
game.StartGame();
}
}
Now our very basic Game class is done and we are ready to run it and see the results! Make sure you've done everything properly and then hit the F5 key to run our empty game in Debug mode! You should get an empty game window with a light-bluish background (that is the actual front-buffer you're seeing as the SwapChain flips it around to present it...the color is the Color "CornflowerBlue" that we're writing onto the buffer)! Pretty cool, huh? :-)
You should now be able to expand on this tutorial and write your own, more robust game class. You will, of course, need to do a good bit of work (for instance, implementing a robust main loop). However you now have a good foundation and you are armed with the knowledge you need to write games for SlimDX with DirectX10. Converting this sample to use DirectX11 is trivial and can be done in about 2-3 minutes or less. I hope you enjoyed this tutorial and will come back for more. If you have any questions this is the time and the place to ask.
The Game.cs class file is attached for download and use! :-)
Regards,
--ATC--