Michi’s blog » OpenGL programming in Haskell – a tutorial (part 1)

## OpenGL programming in Haskell – a tutorial (part 1)

• September 14th, 2006
• 4:17 pm

After having failed following the googled tutorial in HOpenGL programming, I thought I’d write down the steps I actually can get to work in a tutorial-like fashion. It may be a good idea to read this in parallell to the tutorial linked, since Panitz actually brings a lot of good explanations, even though his syntax isn’t up to speed with the latest HOpenGL at all points.

## Hello World

First of all, we’ll want to load the OpenGL libraries, throw up a window, and generally get to grips with what needs to be done to get a program running at all.

import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT

main = do
(progname, _) <- getArgsAndInitialize
createWindow "Hello World"
mainLoop

This code throws up a window, with a given title. Nothing more happens, though. This is the skeleton that we’ll be building on to. Save it to HelloWorld.hs and compile it by running
 ghc -package GLUT HelloWorld.hs -o HelloWorld 

However, as a skeleton, it is profoundly worthless. It doesn’t even redraw the window, so we should definitely make sure to have a function that takes care of that in there somewhere. Telling the OpenGL-system what to do is done by using state variables, and these, in turn are handled by the datatype Data.IORef. So we modify our code to the following

import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT

main = do
(progname, _) <- getArgsAndInitialize
createWindow "Hello World"
displayCallback $= clear [ ColorBuffer ] mainLoop This sets the global state variable carrying the callback function responsible for drawing the window to be the function that clears the color state. Save this to the HelloWorld.hs, recompile, and rerun. This program no longer carries the original pixels along, but rather clears everything out. The displayCallback is a globally defined IORef, which can be accessed through a host of functions defined in Data.IORef. However, deep within the OpenGL code, there are a couple of definition providing the interface functions$= and get to fascilitate interactions with these. Thus we can do things like

height = newIORef 1.0
currentheight <- get height
height $= 1.5 ## Using the drawing canvas So, we have a window, we have a display callback that clears the canvas. Don’t we want more out of it? Sure we do. So let’s draw some things. import Graphics.Rendering.OpenGL import Graphics.UI.GLUT myPoints :: [(GLfloat,GLfloat,GLfloat)] myPoints = map (\k -> (sin(2*pi*k/12),cos(2*pi*k/12),0.0)) [1..12] main = do (progname, _) <- getArgsAndInitialize createWindow "Hello World" displayCallback$= display
mainLoop

display = do
clear [ColorBuffer]
renderPrimitive Points $mapM_ (\(x, y, z)->vertex$Vertex3 x y z) myPoints
flush

Now, the important thing to notice in this codeextract is that last line. It starts a rendering definition, gives the type to be rendered, and then a sequence of function calls, each of which adds a vertex to the rendering canvas. The statement is basically equivalent to something along the lines of

renderPrimitive Points do
vertex Vertex3 …
vertex Vertex3 …

for appropriate triples of coordinate values at the appropriate places. This results in the rendition here:

We can replace Points with other primitives, leading to the rendering of:

### Triangles

Each three coordinates following each other define a triangle. The last n mod 3 coordinates are ignored.
Keyword Triangles

### Triangle strips

Triangles are drawn according to a “moving window” of size three, so the two last coordinates in the previous triangle become the two first in the next triangle.
Keyword TriangleStrip

### Triangle fans

Triangle fans have the first given coordinate as a basepoint, and takes each pair of subsequent coordinates to define a triangle together with the first coordinate.
Keyword TriangleFan

### Lines

Each pair of coordinates define a line.
Keyword Lines

### Line loops

With line loops, each further coordinate defines a line together with the last coordinate used. Once all coordinates are used up, an additional line is drawn back to the beginning.
Keyword LineLoop

### Line strips

Line strips are like line loops, only without the last link added.
Keyword LineStrip

And a Quadstrip works as the trianglestrip, only the window is 4 coordinates wide and steps 2 steps each time, so each new pair of coordinates attaches a new quadrangle to the last edge of the last quadrangle.

### Polygon

A Polygon is a filled line loop. Simple as that!
Keyword Polygon

There are more things we can do on our canvas than just spreading out coordinates. Within the command list constructed after a renderPrimitive, we can give several different commands that control what things are supposed to look like, so for instance we could use the following

display = do
clear [ColorBuffer]
renderPrimitive Quads $do color$ (Color3 (1.0::GLfloat) 0 0)
vertex $(Vertex3 (0::GLfloat) 0 0) vertex$ (Vertex3 (0::GLfloat) 0.2 0)
vertex $(Vertex3 (0.2::GLfloat) 0.2 0) vertex$ (Vertex3 (0.2::GLfloat) 0 0)
color $(Color3 (0::GLfloat) 1 0) vertex$ (Vertex3 (0::GLfloat) 0 0)
vertex $(Vertex3 (0::GLfloat) (-0.2) 0) vertex$ (Vertex3 (0.2::GLfloat) (-0.2) 0)
vertex $(Vertex3 (0.2::GLfloat) 0 0) color$ (Color3 (0::GLfloat) 0 1)
vertex $(Vertex3 (0::GLfloat) 0 0) vertex$ (Vertex3 (0::GLfloat) (-0.2) 0)
vertex $(Vertex3 ((-0.2)::GLfloat) (-0.2) 0) vertex$ (Vertex3 ((-0.2)::GLfloat) 0 0)
color $(Color3 (1::GLfloat) 0 1) vertex$ (Vertex3 (0::GLfloat) 0 0)
vertex $(Vertex3 (0::GLfloat) 0.2 0) vertex$ (Vertex3 ((-0.2::GLfloat)) 0.2 0)
vertex $(Vertex3 ((-0.2::GLfloat)) 0 0) flush in order to produce these four coloured squares where each color command sets the color for the next item drawn, and the vertex commands give vertices for the four squares. ## Callbacks – how we react to changes We have already seen one callback in action: displayCallBack. The Callbacks are state variables of the HOpenGL system, and are called in order to handle various things that may happen to the place the drawing canvas lives. For a first exercise, go resize the latest window you’ve used. Go on, do it now. I bet it looked ugly, didn’t it? This is because we have no code handling what to do if the window should suddenly change. Handling this is done in a callback, residing in the IORef reshapeCallback. Similarily, repainting is done in displayCallback, keyboard and mouse input is in keyboardMouseCallback, and so on. We can refer to the HOpenGL documentation for window callbacks and for global callbacks. Window callbacks are things like display, keyboard and mouse, and reshape. Global callbacks deal with timing issues (for those snazzy animations) and the menu interface systems. In order for a callback to possibly not be defined, most are typed within the Maybe monad, so by setting the state variable to Nothing, a callback can be disabled. Thus, setting callbacks is done using the keyword Just. We’ll add a callback for reshaping the window to our neat code, changing the main function to main = do (progname, _) <- getArgsAndInitialize createWindow "Hello World" displayCallback$= display
reshapeCallback $= Just reshape mainLoop reshape s@(Size w h) = do viewport$= (Position 0 0, s)
postRedisplay Nothing

Here, the code for the reshape function resizes the viewport so that our drawing area contains the entire new window. After setting the new viewport, it also tells the windowing system that something has happened to the window, and that therefore, the display function should be called.

## Summary

So, in conclusion, so far we can display a window, post basic callbacks to get the windowhandling to run smoothly, and draw in our window. Next installment of the tutorial will bring you 3d drawing, keyboard and mouse interactions, the incredible power of matrices and the ability to rotate 3d objects for your leisure. Possibly, we’ll even look into animations.

### 15 People had this to say...

• Leif D
• September 14th, 2006
• 17:48

Thank you!

Keep more comming

• Kyle
• September 14th, 2006
• 20:23

Awesome. Like Leif said, keep it going.
I’ve been waiting for someone to fill the OpenGL tutorial void in haskell.

Also, I happen to think it’s important work.
OpenGL is everywhere and is an excellent example of a low level popular C library for haskell to interface with. The more use it, the better the OpenGL bindings get, the better haskell’s interaction with normal libraries get.

• Michi
• September 15th, 2006
• 12:19

One point worth noting is that the added call to postRedisplay I used in the reshape callback is completely redundant. This wasn’t clear to me when writing, however, since I would do most of my testing over an ssh-tunneled X session. Thus updating was slow in general, and in particular resulted in redisplays not occuring as fast as they should.

A locally run test showed me that the behaviour asked actually does occur with reshape defined as
 

 El Seed September 25th, 2006 23:29 
 (x, y, z)->vertex$Vertex3 x y z does not compile for me. Maybe the intention was \(x, y, z)->vertex$Vertex3 x y z 
 El Seed September 25th, 2006 23:50 er, in general your backslashes are disappearing, as in map (k -> (sin(2*pi*k/12),cos(2*pi*k/12),0.0)) [1..12] Also, the keyword is Quads not Quad. Michi September 26th, 2006 7:45 Thanks for the corrections. I think the case with the disappearing backslashes is to a certain extent the blogpost being “hung over” from problems I had when editing – where all of a sudden ALL “special” characters ended up escaped with \ all over the place; and at some point I just removed all backslashes (without thinking about the consequences). I’ll incorporate your points in an edit now. Thanks. Michi’s blog » Blog Archive » OpenGL programming in Haskell, a tutorial (Part 2) September 26th, 2006 7:51 [...] As we left off the last installment, we were just about capable to open up a window, and draw some basic things in it by giving coordinate lists to the command renderPrimitive. The programs we built suffered under a couple of very infringing and ugly restraints when we wrote them – for one, they weren’t really very modularized. The code would have been much clearer had we farmed out important subtasks on other modules. For another, we never even considered the fact that some manipulations would not necessarily be good to do on the entire picture. [...] alex November 16th, 2006 21:38 With all due respect, the code above is pretty ugly. Is there any real advantage to using Haskell for OpenGL programming? Michi November 17th, 2006 8:47 First off, there are definitive ways to write the code prettier than I did – this tutorial is written almost as much for my own learning as it is for teaching purposes. Secondly, I wouldn’t be able to pinpoint a good knock-out argument why OpenGL in Haskell would be a better idea than, say, OpenGL in C or OpenGL with Python bindings et.c. other than at most being able to manhandle lists and what-not with the Haskell built-in tools. You also will not get the most funky and modern stuff at the current state of HOpenGL, so I’d explicitly recommend against it for, say, games programming in general. I remember OpenGL in C as being unwieldy and annoying when I coded it, and the way it’s encapsulated in Haskell to be neat in comparison, but this may be taken care of in other language bindings just as well. Rick November 19th, 2006 21:32 Thanks for the tutorial Haskell Vs. Lisp » Blog Archive » A game in haskell, day one February 4th, 2007 20:24 [...] [1] http://blog.mikael.johanssons.org/archive/2006/09/opengl-programming-in-haskell-a-tutorial-part-1/ [...] Rasmus Ulvund October 23rd, 2007 0:45 Very cute! I should take some time to play with this. Mathematical Pamphlet » Visualizing 2D convex hull using Gtk and OpenGL in Haskell November 20th, 2007 10:06 [...] best OpenGL tutorial for Haskell that I’ve found is this one from Michi’s blog, using GLUT to interface with X. For this tutorial we are going to use the Gtk GLDrawingArea [...] Functor Salad March 9th, 2009 18:05 Thanks a lot for this tutorial This is exactly how a tutorial should be — begin by showing how to draw some frigging points rather than with 15 pages of boilerplate, side remarks and whatnot. I can usually figure out much of a library from the docs once I got the ball rolling like this, but getting there is a pain from a reference manual. Minor point: As of now (GLUT-2.1.1.2) the resizing works just fine without the reshape callback. More QuickCheck and some geometric probability « leino's blog January 17th, 2012 20:34 [...] an nice introduction to OpenGL under haskell, see here, and also the [...] Want your say? * Required fields. Your e-mail address will not be published on this site Name * Mail * Website You can use the following XHTML tags:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> Comments 
 Monthly Report: Conference summer draws to an end   Hello, Planet Haskell 
 About me Michi is a recent PhD working in applied algebraic topology and computational homological algebra. This blog is his outlet for texts with some manner of thought put into them. Over at his LiveJournal intimate details and streams of consciousness might be found. Not all here is mathematics. All here, though, are my personal thoughts and opinions. Please read the about page (linked above) for more details. This blog uses statcounter.com for logging and traffic analysis. In order to identify return visitors, this site will issue a cookie on viewing the blog. Travel plans AIM and research visitsToposys meetingGöteborg Categories 9th grade topology 10th grade topology A-infinity Administrative Algebra Algebraic geometry Blogs Carnival of Mathematics Category theory Combinatorics Communicating science Computer Conferencing Data Analysis Differential geometry English Geometry Haskell Homology and Homotopy Improbable research JMM2008 LaTeX Liveblogging Mathematics Metablogging Modular Representation Theory Operads and PROPs PhD Politics Postdoc Carnival Privacy Programming Python Recipe Research Ruby Ruby on Rails Security Sillyness Svenska Teaching Topology Travel Travelogue Weekly report Blogroll Andart Michi’s moat Family Fragmented Visions Mathematician blogs - d - 3d pancakes A mathematician’s scratchpad A neighbourhood of infinity Antimeta Blog of a math teacher eon Epsilon Delta Few but ripe Good Math, Bad Math Gooseania Halfway There Learning Curves Maths weblog Numerical Notes Tall Dark and Mysterious The real sqrt Think Again Archives May 2014 September 2012 July 2012 May 2012 March 2012 February 2012 November 2011 July 2011 March 2011 January 2011 December 2010 August 2010 April 2010 March 2010 February 2010 December 2009 November 2009 October 2009 September 2009 August 2009 July 2009 June 2009 May 2009 March 2009 February 2009 January 2009 December 2008 November 2008 October 2008 September 2008 August 2008 July 2008 June 2008 May 2008 March 2008 February 2008 January 2008 December 2007 November 2007 October 2007 September 2007 August 2007 July 2007 June 2007 May 2007 April 2007 March 2007 February 2007 January 2007 December 2006 November 2006 October 2006 September 2006 July 2006 June 2006 May 2006 April 2006 March 2006 February 2006 January 2006 December 2005 May 2005 
 RSS feeds available for Entries and Comments Powered by WordPress