• entries
    58
  • comments
    218
  • views
    114343

SlimDX - Direct3D 10 Basics

Sign in to follow this  

2129 views

SlimDX - Direct3D 10 Basics
Setting up Direct3D 10

I've decided to start a short series of journal entries that deal with learning Direct3D 10, specifically as it relates to SlimDX. I'm mainly going to be following the progression of the tutorials included in the DirectX SDK, with an emphasis on anything that differs when using SlimDX.


Getting Started
Getting started with SlimDX is remarkably easy these days. Just a few simple downloads and you're up and running:
  • If you don't have a version of Visual Studio already, go download Visual C# Express 2008.

  • SlimDX SDK

  • While not strictly necessary, the DirectX SDK will be helpful when developing SlimDX applications, since it includes the debug runtimes as well as a ton of documentation and samples.

  • If you have an express edition of Visual Studio, be sure to grab DebugView as well so that you can see unmanaged debugging hints.


After you've installed everything, open up Visual Studio and create a new Windows Application project. You can delete the default Form that gets added to your project; we won't be needing to customize it through the designer. Open up the Add Reference dialog and once you've waited several years for it to load, browse through the list and add the SlimDX DLL.

Warning: There could be two DLLs in the list, one for x86 and one for x64. Choose the one that is relevant for you and your project.

Now before we get started, I want to talk about debugging your SlimDX applications. If you are using the standard edition of Visual Studio or higher, you can go into the project options and under the Debug tab check Enable Unmanaged Debugging. This will allow you to receive messages from DirectX when the debug runtimes are enabled, which is a valuable source of debugging information. If you have an express edition of Visual Studio, you will simply need to use DebugView when you are debugging, since the express editions don't contain support for unmanaged debugging.

Note: Using unmanaged debugging, and the debugger in general, can slow down an application quite significantly. This isn't usually an issue, since when you're debugging you usually don't care if your game runs a little slower than normal. Just don't worry about SlimDX or C# being too slow; running the same program in release mode outside of Visual Studio will show a big increase in performance.

For more information about debugging in SlimDX, see the relevant wiki page: Debugging Tips.


Basic Application Structure
Just like in C++ and Win32, you need a message loop for your game to respond properly and play well with the OS. While in C++ this is relatively straightforward, in C# there are seemingly many valid ways of accomplishing this. I've seen people use everything from Application.DoEvents to doing all drawing in the Paint event of a Form and then calling Invalidate to cause the message to fire again. Luckily, someone has gone ahead and done the leg work to determine the optimal method of implementing a message loop in C#, which involves using P/Invoke to call the native PeekMessage method. Here's how the skeleton C# game application should look:


using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MyProject
{
[StructLayout(LayoutKind.Sequential)]
struct Message
{
public IntPtr hWnd;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public Point p;
}

static class Program
{
[DllImport("User32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

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

[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

using (var form = new Form() { ClientSize = new Size(800, 600), Text = "SlimDX - Direct3D 10 Basics" })
{
using (var game = new Game(form))
{
Application.Idle += (s, e) => { while (AppStillIdle) game.Render(); };
Application.Run(form);
}
}
}
}
}



Sidebar: Visual Basic While my instructions and code give details for C# only, you should be able to follow along and translate them for Visual Basic, or any other .NET language for that matter. If you are going to be working in one of the less popular .NET languages, having the skill to translate to and from C# is a must.

I'm not going to go into the details behind this particular method. If you want more information about why this is the optimal method, see Tom Miller's blog posts on the subject. Suffice it to say, other methods, including DoEvents, either leak memory or are suboptimal in terms of performance. As you can see here, I also added a simple class called Game, which is just a simple abstraction so that you can hide all of the messy message loop details in the Program class. The contents of the Game class are shown below:


using System;
using System.Windows.Forms;

namespace MyProject
{
class Game : IDisposable
{
public Game(Form form)
{
}

public void Render()
{
}

public void Dispose()
{
}
}
}



Sidebar: C# 3.0: As you can see, I make use of C# 3.0 features whenever I feel they help keep the code concise and/or more elegant. You can, of course, use SlimDX with C# 2.0 if you wish.

As you can see, there isn't much to the Game class right now, but it's where we will be adding all of our code to set up Direct3D, render our scenes, and then clean everything up.


Device Creation
In order to do any rendering in Direct3D 10, we need two important objects: a device and a swap chain. The device is used by the application to render onto any buffer. The swap chain takes that buffer and then presents it to the screen. From the DirectX SDK:

Quote:
The swap chain contains two or more buffers, mainly the front and the back. These are textures that the device renders to, for displaying on the monitor. The front buffer is what the user currently is looking at. This buffer is read-only and cannot be modified. The back buffer is the render target that the device will draw to. Once it finishes the drawing operation, the swap chain will present the backbuffer, by swapping the two buffers. The back buffer becomes the front buffer, and vice versa.


Since creating a swap chain alongside a device is such a common operation, DirectX, and consequently SlimDX, provides a Device.CreateWithSwapChain method to create both in one call. In order to do so, we need to first build up a SwapChainDescription structure to describe how we want it to behave.


var description = new SwapChainDescription()
{
BufferCount = 1,
Usage = Usage.RenderTargetOutput,
OutputHandle = form.Handle,
IsWindowed = true,
ModeDescription = new ModeDescription()
{
Width = form.ClientSize.Width,
Height = form.ClientSize.Height,
Format = Format.R8G8B8A8_UNorm,
RefreshRate = new Rational(60, 1),
Scaling = DisplayModeScaling.Unspecified,
ScanlineOrdering = DisplayModeScanlineOrdering.Unspecified
},
SampleDescription = new SampleDescription()
{
Count = 1,
Quality = 0
},
Flags = SwapChainFlags.None,
SwapEffect = SwapEffect.Discard
};



Next we create the device and the swap chain. Because DXGI and Direct3D10 both have a Device class, we must fully qualify the name.


SlimDX.Direct3D10.Device.CreateWithSwapChain(null, DriverType.Hardware, DeviceCreationFlags.None, description, out device, out swapChain);



Also, in order to render to anything we need a render target view, which binds the back buffer as a render target. We do that as follows:


using (var resource = swapChain.GetBuffer2D>(0))
renderTarget = new RenderTargetView(device, resource);
device.OutputMerger.SetTargets(renderTarget);



The final step before rendering is to set a viewport to map clip space coordinates. In Direct3D9 this is done by default for you, but in Direct3D10 we are required to do it ourselves.


viewport = new Viewport(0, 0, form.ClientSize.Width, form.ClientSize.Height, 0.0f, 1.0f);
device.Rasterizer.SetViewports(viewport);



That's all there is to setting up a minimal device and getting ready to render.


Rendering
For this first post I'm going to show the simplest act of rendering possible: filling the screen with color. That can be accomplished quite easily using Device.ClearRenderTargetView. Once that's done, we call SwapChain.Present to present the scene to the screen.


device.ClearRenderTargetView(renderTarget, Color.CornflowerBlue);
swapChain.Present(0, PresentFlags.None);



Of course we need a screen shot, even if it's rather boring right now:


That's all there is to it. In the next installment, I'll cover rendering some actual geometry. I've uploaded a sample project for this article here: Tutorial01. In case you were wondering, I swiped the format of the article from ApochPiQ's Memory Management series.

Let me know what you think!
Sign in to follow this  


11 Comments


Recommended Comments

Well, you just can't use implicit typing via var. You could always do this:

Expression<Func<int, int>> f = (x) => x + 1;

You just need to provide the type so it knows what's getting passed in and passed out.

In fact, you could always compile the call inline to get the delegate you want:

var f = ((Expression<Func<int, int>>)(x => x + 1)).Compile();

int result = f(5);

Share this comment


Link to comment
but then I could not go var z = f(3) or pass f as a function to an argument.

My example was too simple. I want more complex lambdas with bodies:

var f = (x) => { var p = x + 1; return p * 2; } was the original type of function that i gave me difficulties.

Share this comment


Link to comment
Huh. thanks it worked. But Im still not happy about the lack of type inference and the fact that my lambdas cant have parametric polymorphism.

Thanks at least now I can have some semblance of local functions when I work in C#. I hope I dont sound like a grumpy overly functional programmer. heh

Share this comment


Link to comment
Very nice, very simple. Any chance you could squeeze the Sprite batch into your next tutorial? The SlimDX samples seem to neglect almost entirely its use in developing 2-D applications and GUI's.

Edit:
When using the Application.Idle technique presented here, I get a fairly solid 1380 FPS. Application.DoEvents runs slight faster at around 1390 FPS, but I trust there is a great deal of undesirable junk going on in the background. When using the Form.Paint technique, however, I get around 3000 FPS. What would cause such a discrepancy?

Edit #2:
Okay, I see. The Paint event updates immediately and causes a great deal of tearing, while the App.Idle technique respects a more regular update interval, alleviating the tearing. So, never mind!

Edit #3:
After further observation, it seems to have something to do with my timing code. I played around with it some more and managed to get the App.Idle implementation faster than Form.Paint. I'll leave well enough alone.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now