Formeln

Sunday, May 20, 2012

The Scene Class

Up to now we created a triangle and called its render method in the RenderManager:


public void renderScene()
{
  Renderables.Triangle triangle = new Renderables.Triangle();

  while (true)
  {
    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));

    triangle.render();
                
    dm.swapChain.Present(0, PresentFlags.None);
  }
}

The Renderable Class


But what you usually want to do is add and remove objects to render dynamically. So we have to have a data structure to hold all our render objects. For the sake of simplicity lets start with a list. To be able to iterate through the List, all renderable objects have to inherit from a base class called Renderable.

So this is the bare minimum we need:


public abstract class Renderable
{
  public abstract void render();
}

Now let us derive our Triangle Class from this abstract class override our render method:


class Triangle : Renderable
{
  ShaderSignature inputSignature;
  VertexShader vertexShader;
  PixelShader pixelShader;
  .
  .
  .


public override void render()
{
  // configure the Input Assembler portion of the pipeline with the vertex data
  DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
  DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
  DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));

  // set the shaders
  DeviceManager.Instance.context.VertexShader.Set(vertexShader);
  DeviceManager.Instance.context.PixelShader.Set(pixelShader);

  // render the triangle
  DeviceManager.Instance.context.Draw(3, 0);
}

The Scene Class

Now lets add a Singleton Class called Scene. This holds a List of Renderables, a method to add Renderables, a method to remove Renderables and a method to iterate our List to render the objects.
To prevent altering the List while the render-loop is calling the render method, I use the lock Statement.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apparat.Renderables;

namespace Apparat
{
    public class Scene
    {
        #region Singleton Pattern
        private static Scene instance = null;
        public static Scene Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Scene();
                }
                return instance;
            }
        }
        #endregion

        #region Constructor
        private Scene() { }
        #endregion

        List<Renderable> RenderObjects = new List<Renderable>();

        public void addRenderObject(Renderable renderObject)
        {
            lock (RenderObjects)
            {
                RenderObjects.Add(renderObject);
            }
        }

        public void removeRenderObject(Renderable renderObject)
        {
            lock (RenderObjects)
            {
                if( RenderObjects.Contains( renderObject ))
                {
                    RenderObjects.Remove( renderObject );
                }
            }
        }

        public void render()
        {
            lock (RenderObjects)
            {
                foreach (Renderable renderable in RenderObjects)
                {
                    renderable.render();
                }
            }
        }
    }
}

Using the Scene

Now we can change the code in our renderScene method in our RenderManager to call the render method of our Scene (compare with the first method at the top):

public void renderScene()
{
  while (true)
  {
    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));

    Scene.Instance.render();
                
    dm.swapChain.Present(0, PresentFlags.None);
  }
}

While we created the triangle at first in our renderScene method, we are now able to add and remove Renderables from our Application!

For example:


public Form1()
{
  InitializeComponent();
  DeviceManager.Instance.createDeviceAndSwapChain(this);
  RenderManager.Instance.init();

  Triangle triangle = new Triangle();
  Scene.Instance.addRenderObject(triangle);
}

You can download the code to this project here:

http://apparat.codeplex.com/SourceControl/changeset/changes/ba452d5587ac

Saturday, May 19, 2012

Rendering a Triangle: Cleaning Up

The SlimDX tutorial about rendering a triangle puts all the code for setting up the device and the swapchain
into one file. Let's organize the code some more by creating a Class for the Triangle. I added a Folder
for Renderables, where I will place all further Classes, that are responsible for holding the resources and methods to render things.

First let's copy and paste the code for rendering the triangle into the contructor of the Triangle class a method called render. After adding the needed using directices the Triangle class looks like this:




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;

namespace Apparat.Renderables
{
    class Triangle
    {
        ShaderSignature inputSignature;
        VertexShader vertexShader;
        PixelShader pixelShader;

        public Triangle()
        {
            
            #region shader and triangle


            // load and compile the vertex shader
            using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
            {
                inputSignature = ShaderSignature.GetInputSignature(bytecode);
                vertexShader = new VertexShader(device, bytecode);
            }

            // load and compile the pixel shader
            using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
                pixelShader = new PixelShader(device, bytecode);

            // create test vertex data, making sure to rewind the stream afterward
            var vertices = new DataStream(12 * 3, true, true);
            vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
            vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
            vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
            vertices.Position = 0;

            // create the vertex layout and buffer
            var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
            var layout = new InputLayout(device, inputSignature, elements);
            var vertexBuffer = new SlimDX.Direct3D11.Buffer(device, vertices, 12 * 3, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

            // configure the Input Assembler portion of the pipeline with the vertex data
            context.InputAssembler.InputLayout = layout;
            context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));

            // set the shaders
            context.VertexShader.Set(vertexShader);
            context.PixelShader.Set(pixelShader);

            #endregion
        }

        public void render()
        {
        }

        public void dispose()
        {
            pixelShader.Dispose();
            vertexShader.Dispose();
            inputSignature.Dispose();
        }
    }
}

Now we move all the code that configures the input assembler and sets the shaders to the render method.
If we have more objects, shaders have to be set for every object on every render call.

Further the calls to context and device have to be adjusted, because those are member variables of the DeviceManager.
So for example
context.VertexShader.Set(vertexShader);
becomes
DeviceManager.Instance.context.VertexShader.Set(vertexShader);

Finally we have to create an object of the triangle and call its render method in the render loop.

public void renderScene()
{
  Renderables.Triangle triangle = new Renderables.Triangle();

  while (true)
  {
    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));

    triangle.render();
                
    dm.swapChain.Present(0, PresentFlags.None);
  }
}

You can download the source code here: http://apparat.codeplex.com/SourceControl/changeset/changes/3882dce2a134

Thursday, May 17, 2012

Rendering a Triangle

Now let's integrate the last of the SlimDX tutorials into our Engine. It is called
Direct3D11 - SimpleTriangle. I will start by hardcoding the relevant code from
the SlimDX tutorial into the Engine. I will not go into any detail about shaders, vertices and
the render pipeline in this post.

Take the following code and copy-paste it into the DeviceManager Class of our Engine
just behind the line


context.Rasterizer.SetViewports(viewport);


#region shader and triangle


// load and compile the vertex shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
{
    inputSignature = ShaderSignature.GetInputSignature(bytecode);
    vertexShader = new VertexShader(device, bytecode);
}

// load and compile the pixel shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
                pixelShader = new PixelShader(device, bytecode);

// create test vertex data, making sure to rewind the stream afterward
var vertices = new DataStream(12 * 3, true, true);
vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
vertices.Position = 0;

// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
var layout = new InputLayout(device, inputSignature, elements);
var vertexBuffer = new SlimDX.Direct3D11.Buffer(device, vertices, 12 * 3, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

// configure the Input Assembler portion of the pipeline with the vertex data
context.InputAssembler.InputLayout = layout;
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));

// set the shaders
context.VertexShader.Set(vertexShader);
context.PixelShader.Set(pixelShader);

#endregion

Add this using directive to your usings:


using SlimDX.D3DCompiler;

Add the following declarations to the member variables of the DeviceManager Class:


ShaderSignature inputSignature;
VertexShader vertexShader;
PixelShader pixelShader;

And add the following lines at the beginning of our shutDown Method in our DeviceManager Class:


pixelShader.Dispose();
vertexShader.Dispose();
inputSignature.Dispose();

Now add a new Element to our Engine Class Library by right-clicking on it and select Add->New Item.
Type "triangle.fx" as name and create the element.

Open the file in your Solution Explorer, delete all content and copy-paste the following code:


float4 VShader(float4 position : POSITION) : SV_POSITION
{
 return position;
}

float4 PShader(float4 position : SV_POSITION) : SV_Target
{
 return float4(1.0f, 1.0f, 0.0f, 1.0f);
}

Right-click on the file "triangle.fx" in your Solution Explorer and click on Properies.
Select "Copy to Output Directory" and select "copy always".

Change the code in the RenderManager Class in the renderScene Method and call
dm.context.Draw(3,0) :


public void renderScene()
{
  while (true)
  {
    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));
    dm.context.Draw(3, 0);
    dm.swapChain.Present(0, PresentFlags.None);
  }
}

After compiling the Class Library you can recompile your Main Project and should get the following result:


You can download the Project here: http://apparat.codeplex.com/SourceControl/changeset/changes/a13132898eaa

Tuesday, May 15, 2012

Organizing your Code: A Class Library for the Engine

You may want to use your Engine for several projects. You could always copy and paste your code again and again, but this is just braindead. By creating a Class Library you just have to reference that library in your new code. Furthermore a Class Library helps you to structure your code and make you think about whats general and what is specific to your project.

Create a Class Library


Rightclick on your solution and choose Add->New Project:



Select Class Library and type a name for the library at the bottom of the dialogue (I will call it Apparat).


Your Solution Explorer should now look like this, our old Project and our Class Library:


Migrating the Code to the Class Library

Drag and drop RenderManager.cs and the DeviceManager.cs to the library project and delete Class1.cs from your Class Library.

Delete DeviceManager.cs and RenderManager.cs from your MDX11Form Project.

Your Solution Explorer should look like this now:


Add a reference to the SlimDX.dll to your library project by right-clicking References in your Class Library, selecting Add Reference and selecting the SlimDX Component.

Add further References to the System.Windows.Form and System.Drawing Components.

Now you can right-click on your Class Library and select Build and you built a dll.

You still can't use your Class Library in this way for your Main Project, so lets make the final changes to our Class Library.

The namespaces of RenderManager.cs and DeviceManager.cs are still called MDX11Form, I rename them to Apparat:


The second step is to and make the two classes  DeviceManager.cs and  RenderManager.cs public:


Changes to the Main Project

Add a reference to your library project to your application project by right-clicking on References->Add Reference. But instead of using the .NET tab use the Projects tab and select your Class Library:


Now you can delete the reference to SlimDX. Your References should look like this now:





Now you have a lot of red markers in your using directices in the Form1.cs of your Main Project.
These are using directives of the former referenced libraries and you have to delete them.

Finally you have to add a using directive to your Class Library to the Form1.cs. The code of your Form1.cs now looks like this:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat;

namespace MDX11Form
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
      
            DeviceManager.Instance.createDeviceAndSwapChain(this);
            RenderManager.Instance.init();
        }

        public void shutDown()
        {
            RenderManager.Instance.shutDown();
            DeviceManager.Instance.shutDown();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            shutDown();
        }
    }
}

Congratulations! You created a Class Library and made the first real step to a dedicated Library for your Engine ;)

Saturday, May 12, 2012

C# Singleton Pattern Snippet

Download

I prepared a snippet that you can use in your snippet manager to automatically make
a class a Singleton.

You can download the snippet here: Singleton.Snippet.
Copy it to your Visual Studio Snippets Folder (in my case .../Documents/Visual Studio 2010/Code Snippets/Visual C#/My Code Snippets/)


Just add a new Class, put your cursor below the class keyword,
type ston and hit tab twice.

.

Organizing your Code: Singletons

If you have an object, that has to be referenced by many other objects thoughout your code it becomes annoying to pass this object every time as a reference. One solution to this problem is using the Singleton Design Pattern. You can read more about the Singleton Pattern here:
http://en.wikipedia.org/wiki/Singleton_pattern

Basically you use this pattern, if you have just one object of this Class in your whole program.
The DeviceManager is a good candidate for a Singleton, because you create the device just once and use it until your program ends. All Classes, that use the Singleton Pattern will be called Managers in my code.


public class DeviceManager
{
    private static DeviceManager instance = null;
    public static DeviceManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new DeviceManager();
            }
            return instance;
        }
    }

    private DeviceManager(){}

    
    Device device;

}

The device in the DeviceManager can then be accessed over your whole code with:

DeviceManager.Instance.device

Now also change the RenderManager to the Singleton Pattern and then our Form1 looks like this:

public Form1()
{
    InitializeComponent();
    DeviceManager.Instance.createDeviceAndSwapChain(this);
    RenderManager.Instance.init();
}

public void shutDown()
{
    RenderManager.Instance.shutDown();
    DeviceManager.Instance.shutDown();
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    shutDown();
}

Wednesday, May 9, 2012

Organizing your Code: Classes

In this tutorial we will add two classes: the DeviceManager and the RenderManager. In theses Classes we will organize our variables for the device and our rendering process.

Let's start with the DeviceManager:

The DeviceManager Class


Navigate in Visual C# to your Solution Explorer and right click on our Project. A dialogue will appear and click Add and in the next dialogue Class.

The following dialogue should appear:


Select Class and choose a name for this Class at the bottom of the dialogue. I will name it DeviceManager.

Copy the following in Form1.cs declared variables to the DeviceManager Class and delete them in Form1:


 
public Device device;
public SwapChain swapChain;
public Viewport viewport;
public RenderTargetView renderTarget;
public DeviceContext context;

Same with the Method createDeviceAndSwapChain.


Now add the Method shutDown to our Class DeviceManager and add the objects you want to dispose to it:

public void shutDown()
{
  renderTarget.Dispose();
  swapChain.Dispose();
  device.Dispose();
}

The RenderManager Class


Now let's add another Class to our Project called RenderManager just like you added your DeviceManager Class to the Project.

Copy the renderThread variable over to the RenderManager Class and the init Method.
Now add a shutDown Method where we tell our renderThread to abort:

public void shutDown()
{
  renderThread.Abort();
}

Final Steps

We have organized our Methods and variables in dedicated Classes and need to declare them in our Form1.cs and create Objects of them. Finally we call the according Methods of our Objects and
Form1cs now looks like this:

public partial class Form1 : Form
{
  DeviceManager dm;
  RenderManager rm;

  public Form1()
  {
    InitializeComponent();
    dm = new DeviceManager();
    rm = new RenderManager();

    dm.createDeviceAndSwapChain(this);
    rm.init();
  }

  public void shutDown()
  {
    rm.shutDown();
    dm.shutDown();
  }

  private void Form1_FormClosing(object sender, FormClosingEventArgs e)
  {
    shutDown();
  }
}

Much cleaner this way!
But there is still one problem: it won't compile, because our RenderManager Class knows nothing about 
our variables context, renderTarget and swapChain, because they were declared in our DeviceManager Class.

We will help ourselves by adding a Constructor to our RenderManager Class that takes a reference to our
DeviceManager Class. Second we add a Member variable to our RenderManager Class that holds a reference to our DeviceManager Object:

public RenderManager(DeviceManager dm)
{
  this.dm = dm;
}

DeviceManager dm;

Finally we have to change our Form1 Constructor and pass the DeviceManager Object to our RenderManager as a parameter:

public Form1()
{
  InitializeComponent();
  dm = new DeviceManager();
  rm = new RenderManager(dm);

  dm.createDeviceAndSwapChain(this);
  rm.init();
}

Now everything compiles again. In the next Tutuial I will tell you why it is a bad idea to pass our DeviceManager as a Reference to other Objects that depend on it and how this can be solved in an elegant way. 

Download

You can download the code to this project here: download
















Organizing your code: Outline

Up to now we just threw all our code into our Form.cs class. This is very bad style and we will begin to organize our code by using classes for the device and the rendering process. I will give you a short outline what will happen in the next tutorials to organize our code:


  • using Classes
  • using the Singleton pattern
  • creating a Class Library for our engine
  • creating a User Control for our RenderWindow

Creating the Device

Setting up the project


On the SlimDX website there a up to now 3 simple tutorials for the basic setup of a windows form with SlimDX. I will use parts of this tutorials to show you how to setup a simple form.

After installation of Visual C# Express and SlimDX SDK, start Visual C# Express and go to File
and click New Project.

Select Windows Forms Application and choose a name for your Project (MDX11Form in my case).


Add a reference to the SlimDX.dll by navigating to the Solution Explorer, right click on References and
choose Add Reference.


I will use the .NET Framework v4.0 and use the x64 Platform.


Now you have a Windows Forms Application with Reference to the SlimDX library in it.
You can hit the compile Button already but this will not look any different than your default application
after creating a new Windows Forms Project.

Setting up the device

Navigate to the Solution Explorer again, right click on Form1.cs and choose View Code.


Your code should look like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MDX11Form
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Add the following lines to your using Directives:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Resource = SlimDX.Direct3D11.Resource;
using Device = SlimDX.Direct3D11.Device;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Threading;

Add these declarations to your form:

 public Device device;
 public SwapChain swapChain;
 public Viewport viewport;
 public RenderTargetView renderTarget;
 public DeviceContext context;

Add the method createDeviceAndSwapChain to your code:

public void createDeviceAndSwapChain(System.Windows.Forms.Control form)
        {
            var description = new SwapChainDescription()
            {
                BufferCount = 1,
                Usage = Usage.RenderTargetOutput,
                OutputHandle = form.Handle,
                IsWindowed = true,
                ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
                SampleDescription = new SampleDescription(1, 0),
                Flags = SwapChainFlags.AllowModeSwitch,
                SwapEffect = SwapEffect.Discard
            };
            Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, description, out device, out swapChain);

            // create a view of our render target, which is the backbuffer of the swap chain we just created
            using (var resource = Resource.FromSwapChain<Texture2d>(swapChain, 0))
                renderTarget = new RenderTargetView(device, resource);

            // setting a viewport is required if you want to actually see anything
            context = device.ImmediateContext;
            var viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
            context.OutputMerger.SetTargets(renderTarget);
            context.Rasterizer.SetViewports(viewport);

            // prevent DXGI handling of alt+enter, which doesn't work properly with Winforms
            using (var factory = swapChain.GetParent<Factory>())
                factory.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);

            // handle alt+enter ourselves
            form.KeyDown += (o, e) =>
            {
                if (e.Alt && e.KeyCode == Keys.Enter)
                {
                    swapChain.IsFullScreen = !swapChain.IsFullScreen;
                }
            };
        }


Adding a render method

public void renderScene()
{
  while (true)
  {
    context.ClearRenderTargetView(renderTarget, new Color4(0.25f, 0.75f, 0.25f));
    swapChain.Present(0, PresentFlags.None);
  }
}

Adding a render thread


Add a declaration for a render thread:

Thread renderThread;

public void init()
{
  renderThread = new Thread(new ThreadStart(renderScene));
  renderThread.Start();
}


Finishing Up


public Form1()
{
  InitializeComponent();
  createDeviceAndSwapChain(this);
  init();
}




Cleaning Up

public void shutDown()
{
  renderThread.Abort();
  renderTarget.Dispose();
  swapChain.Dispose();
  device.Dispose();
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
  shutDown();
}

Download

You can download the Project here:






















Friday, May 4, 2012

I will present in this blog articles about programming games and simulations. The goal is to give you an idea how you can develop a game- or simulation engine, show you architectures and patterns that help you to organize your code. I will use C# and managed DirectX. I use for this tutorials Visual C# 2010 Express and SlimDX, a wrapper for the .NET framework. You can get Visual C# 2010 Express here: http://www.microsoft.com/visualstudio/en-us/products/2010-editions/visual-csharp-express and SlimDX here: http://slimdx.org/. I would be happy to hear from you if the following tutorials are helpful for you and am thankful for suggestions. Have fun!