Sep 27

Minimal DirectX demo

Minimal DirectX demo

With the advent of .NET, Managed DirectX makes graphical programming easy under Windows. This program is a minimal DirectX demo with several interesting features:

  • Lighting
  • Perspective
  • Mouse control
  • Mesh rendering

Despite having all of these features, the entire program requires only 100 lines of source code to render a 3D teapot that can be rotated with the mouse:

This example program is freely available in two forms:

The program has several interesting aspects:

GUI

This is a Windows application that uses WinForms to create a window containing a DirectX device.

The WinForms-related code is written in an object oriented style, forming a Viewer class:

type Viewer = class
  inherit Form
  ...
end

This class encapsulates the creation and handling of a window containing a DirectX device. The constructor for this class accepts the title of the window as a string and a rendering function:

new(title, render) as form = {device=null; render=render; world=Matrix.Identity; drag=None} then
form.SetStyle(Enum.combine [ControlStyles.AllPaintingInWmPaint; ControlStyles.Opaque], true);
form.Text 
              

Note that the ability to pass the rendering function as an argument to the constructor is functional programming.

Mouse control

Dragging is achieved by storing a mutable value in the Viewer object:

val mutable drag : (Matrix * int * int) option

When the scene is not being dragged, drag is None. During dragging, drag is Some(m, x, y) where m is the original world transformation matrix and x, y is the window coordinate where the drag started.

Inside the Viewer class, the OnMouseDown event is used to set drag:

override form.OnMouseDown e = form.drag 
              

and the OnMouseUp event is used to reset drag:

override form.OnMouseUp e = form.drag 
              

The OnMouseMove event uses drag to update the world transformation matrix during a drag and then force redraw:

override form.OnMouseMove e = match form.drag with
  | Some(world, x, y) ->
      let scale = 5.f / float32(min form.device.Viewport.Width form.device.Viewport.Height) in
      form.world  ()

This is a very succinct way to handle the mouse control of a scene. Note the use of variant types (option) and pattern matching.

Rendering

The OnPaint event of the window calls boiler-plate DirectX functions and uses the render function that the object was instantiated with to perform the actual rendering:

override form.OnPaint _ =
  if form.device = null then form.make_device();
  try
    form.device.BeginScene();
    form.device.Clear(Enum.combine [ClearFlags.Target; ClearFlags.ZBuffer], Color.Black, 1.f, 0);
    form.device.Transform.World  form.make_device()

Note that this function is careful to replace the DirectX device if there is an error. This handles DirectX device loss and reset, although there are probably more elegant ways to do so. For example, if the render function threw one of our own exceptions, the device might not need replacing.

The render function, responsible for rendering the scene, is defined outside the Viewer class. The render function sets a light:

device.RenderState.SpecularEnable 
              

initialises the transformation matrices (representing perspective projection and the camera position and orientation relative to the scene):

let aspect = float32 device.Viewport.Width / float32 device.Viewport.Height in
device.Transform.Projection 
              

sets materials propertes ready to render a red teapot with a white specular highlight:

let mutable material = new Material() in
material.Diffuse 
              

and finally renders the built-in teapot mesh:

Idioms.using (Mesh.Teapot(device)) (fun teapot -> teapot.DrawSubset(0))

Note the use of Idioms.using to ensure that the teapot mesh is deallocated immediately. If the mesh is create and then left for garbage collection every frame, many meshes accumulate and stall the program when the garbage collector kicks in. In this case, a better practice is to store the mesh along with the device and invalidate it when the device is reset.

Despite the sophistication of this application, the entire program is tiny thanks to the expressiveness of the F# programming language.

Sep 27

Rule 30 cellular automaton

This 1D cellular automaton is the simplest automaton to exhibit complex and unpredictable behaviour. Each cell is in one of two states. Cells transition between states based upon the states of themselves and their two neighbours in the previous generation:

As a programming example, this 70-line F# program uses Windows Forms to render a bitmap, handles GUI events in a functional style and uses pattern matching and higher-order functions to implement the cellular automaton itself.

This simple .NET program fills a window with generations of the rule 30 cellular automaton:

This example program is freely available in three forms:

Jun 26

A Taste of F# Interactive in Visual Studio

Here’s a taste of some great new functionality that will be in the next release of F#, which we should have out sometime in the next week or so. The cool thing here is not just the pretty graphics (which you’ve been able to do in F# for a while now), but F# Interactive (fsi.exe) embedded as a tool window in Visual Studio. Click on the screen shot for an enlargement.

Also shown is something I haven’t made much noise about on this blog, and that’s the ability to display windows interactively from F# Interactive. The windows are fully active - they can paint and respond to interactions while the programmer evaluates new expressions, code, types and classes in the interactive session. You can also dynamically load new .NET components on-the-fly.

At the bottom of the screen you’ll see a tool window containing the F# Interactive session. The WinForms/DirectX window in the foreground was created by running simple F# code such as

let form = new Form();;
form.Text F# surface plot”;;

as well as some introductory DirectX triangle creation.

The code shown in the editor is part of this script - we’ve just executed the command that specifies a new function to display (this is the line highlighted in the editor). We did this just by evaluating the code in the F# Interactive Session below using a key short-cut. (I’ve scrolled the F# Interactive window back up to the top so you can see the banner printed out when F# Interactive starts up.)

What’s really striking is the combination of interactive visualization, Visual Studio, .NET programming and efficiently executing F# code (remember, .the floating point code is running as optimized native code, often close to C++ speed). We have a few things to add before this is complete: for example some form of intellisense in the interaction window. But this combination feels like it is bringing many things together nicely. (Aside: you can of course do interactive visualization when using F# Interactive from Emacs too :-) I’m not so sure about “vi” !!)