---
date: 2015-03-25 08:41:23
tags: haskell, graphics, gl, pragmatic-primers, literate-programs
title: The Haskell gl package
subtitle: a pragmatic primer
---
This post demonstrates how to get to a useful, working foundation for an
OpenGL application using the recent [`gl` package][gl] with the minimum of
fuss. By the end of the post, this is what we'll have:
![](/posts/2015/03/25/getting-up-and-running-with-gl/spinningCube.gif "A spinning cube")
Which is to say:
- A window, created and managed by [`GLFW-b`][glfw-b].
- A cube mesh, with positions, colours, normals, and *uv* co-ordinates.
- A single directional light, calculated using the fragment shader.
- A texture, alpha blended with the underlying colours.
- Some very simple animation (the cube spins).
When trying to get set up with OpenGL, I've found that while there are a lot
of resources out there, I've often had to piece together various blog posts in
order to get a working application that I can build off. Many of these blog
posts also make use of immediate mode, which may be quick and easy to learn,
but is quite outdated and ultimately sets you down the wrong path if you want
to learn modern OpenGL programming. This post aims to give you a solid
jumping-off point to start on the interesting stuff straight away.
As well as that, this post is an opportunity for me to try the [gl
package][gl], introduced relatively recently by Edward Kmett and others. `gl`
attempts to be a low-level but *complete* set of bindings to the OpenGL API --
as opposed to the rather more longstanding [OpenGL package][OpenGL], which
tries to be a bit more "Haskelly" but at the cost of certain missing parts of
the OpenGL specification.
[OpenGL] is built on the [OpenGLRaw] package, which as the name implies is
supposed to be a "raw" binding for OpenGL much as [gl] is. As I understand
it, the problems with this package are as follows:
- It doesn't work well as an "escape hatch" for the higher-level OpenGL
package because many of the abstractions don't translate between the two
libraries.
- It is not as complete as [gl] in terms of the number of extensions it
supports.
- Because it is part of the Haskell Platform, fixes to the above issues can
take a year to make their way into the library.
For more information about the reasons behind the creation of the [gl]
package, [this video][gl-rant] makes for interesting viewing.
To me, though, the greatest advantage of the [gl] package is that *you can
google it*. Because it is machine-generated from the actual OpenGL API, all
the symbol names match, and you use them in the same way as you would in C.
The *vast* majority of OpenGL tutorials on the internet are written in C or
C++, so having a common vocabulary with them is immensely useful.
Setting up the project
----------------------
In case you want to follow along, here is the relevant part of my cabal file:
```cabal
executable glTutorial
main-is: 2015-03-25-the-haskell-gl-package.lhs
build-depends: base >= 4.7 && <4.8,
base-unicode-symbols == 0.2.2.4,
transformers == 0.4.3.0,
vector == 0.10.12.2,
text == 1.2.0.3,
gl == 0.7.2.4,
GLFW-b == 1.4.7.1,
linear == 1.18.0.1,
distributive == 0.4.4,
lens == 4.6.0.1,
JuicyPixels == 3.2.3
default-language: Haskell2010
```
I am including absolute version numbers here so that you can see exactly what
I was working with, but you could probably be a lot more lenient with your own
projects.
This post is a literate Haskell file -- lines preceded by `>` are executable
code, so you should be able to run and test the file directly.
Breakdown of tasks
------------------
This post is, by necessity, quite long. There is a lot that needs to be set
up in order to get a spinning cube on the screen! This is basically how I've
started every games/graphics project I've done in the last ten years, and
*every time* I spend the majority of my time staring into the abyss of a
bright pink window with nothing rendering in it, wondering which trivial step
I've forgotten in my initial setup which is **breaking everything**. By
collecting all the steps together in this one, massive blog post, I hope to
save others (as well as my future self) from this pain.
To help navigate, here's a breakdown of what we're going to be doing:
- [Set up language pragmas / import the required modules](#preliminaries)
- [Set up some handy error handling utilities](#error-handling-utilities)
- [Create a window and associate it with our GL context](#setting-up-the-window)
- [Define the mesh for our cube](#constructing-our-cube-mesh)
- [Load Resources:](#resource-loading)
- [Load in the texture and upload it to the GPU](#load-texture)
- [Compile and link the shader, and retrieve its uniform and attribute locations](#load-shader)
- [Convert our mesh definition to GL buffer objects](#load-mesh)
- [Initialise OpenGL](#setting-up-gl)
- [Update state every frame to rotate the cube](#handling-state)
- [Actually render the scene](#actually-drawing-things)
- [Cleanup](#resource-cleanup)
Let's get started!
Preliminaries
-------------
We start with language pragmas. We will overload both string and list syntax
to provide us with convenient access to Haskell's faster `Text` and `Vector`
containers.
> {-# LANGUAGE OverloadedStrings #-}
> {-# LANGUAGE OverloadedLists #-}
The gl package makes quite heavy use of pattern synonyms to reproduce GL's
native enums.
> {-# LANGUAGE PatternSynonyms #-}
I'm also going to make use of Unicode symbols in this file.
> {-# LANGUAGE UnicodeSyntax #-}
> import Prelude.Unicode
> import Control.Monad.Unicode
Obviously we'll begin by importing the `Graphics.GL` namespace exposed by the
`gl` package. This package follows the GL C API convention of prefixing
its function names with `gl`, so I won't bother with a qualified import; for
all other modules I will either import them qualified or explicitly name the
imported symbols, so that you can see where they're coming from.
> import Graphics.GL
To make things easier, I'm going to make use of GLFW to deal with opening the
window and getting keypresses. This will allow us to concentrate on the GL
side of things.
> import qualified Graphics.UI.GLFW as GLFW
Edward Kmett's [linear] library is a nice, flexible library for vector and
matrix maths, and the *`Storable`* instances it supplies for everything make
it a good fit for working with GL.
> import Linear ( V2(..), V3(..), M44, Quaternion,
> perspective, lookAt, axisAngle,
> mkTransformation, (!*!), inv33,
> column, _xyz, negated, identity)
The following two functions also come in handy when working with Linear.
`distribute` gives you the transpose of a matrix, and `(^.)` will give you
access to certain fields which are expressed as lenses.
> import Data.Distributive (distribute)
> import Control.Lens ((^.))
I'm going to use the [JuicyPixels] library for loading texture data.
> import Codec.Picture (readPng, Image(Image),
> DynamicImage(ImageRGBA8))
After this we import some standard libraries which we'll be making use of
later.
> import Control.Monad ( void, when, unless, liftM2)
> import Control.Applicative ( (<$>), (<*>), pure)
> import System.IO ( hSetBuffering, stdout,
> BufferMode(LineBuffering))
> import Data.IORef ( IORef, newIORef,
> writeIORef, readIORef)
> import Data.Bits ( (.|.))
We'll be working with strings a little bit to load our shaders and send them
into GL, so we'll need `Data.Text` and the `Data.Text.Foreign` utilities for
communicating with C. We'll also include `Data.Vector` while we're at it.
> import qualified Data.Text as T
> import qualified Data.Text.IO as T
> import qualified Data.Text.Foreign as T
> import qualified Data.Vector as V
Because `gl` works at quite a low level, you have to do quite a lot of
marshalling between Haskell and C. Haskell provides a number of convenient
utilities for doing this within the *`Foreign`* hierarchy.
`Data.Vector.Storable` also gives us a directly serializable form of
*`Vector`*.
> import qualified Data.Vector.Storable as SV
> import Foreign.Marshal.Alloc (alloca, allocaBytes)
> import Foreign.Marshal.Array (allocaArray, peekArray)
> import Foreign.Marshal.Utils (with)
> import Foreign.Storable (peek, sizeOf)
> import Foreign.Ptr (Ptr, nullPtr, castPtr, wordPtrToPtr)
Finally, some monad transformers which will ease some of the boilerplate.
> import Control.Monad.Trans.Maybe (MaybeT(..))
> import Control.Monad.Trans.Cont (ContT(..), evalContT)
> import Control.Monad.Trans.Class (lift)
> import Control.Monad.IO.Class (liftIO)
Error-handling utilities
------------------------
I don't actually make use of these functions anywhere within this post, but
you can bet I used them while I was writing it! Debugging graphical issues
can be extremely frustrating as the GPU doesn't have anything akin to a
`printf`, and GL itself is basically a gigantic state machine where subtle
mistakes can lead to strange errors down the line. Sometimes it can be useful
to take a scattergun approach and just sprinkle error-checking facilities
throughout your code in the hope of getting a clue as to what might be the
problem. These functions help you do that.
First, `getErrors` collects all the errors GL is currently reporting. Since
GL allows certain operations to be performed concurrently, it holds multiple
error registers, and a single call to `glGetError` just gives you the value
from one of them. Here, we keep calling it until there are no errors left, at
which point we return all the available errors as a list.
> getErrors :: IO [GLuint]
> getErrors = do
> err ← glGetError
> if err == GL_NO_ERROR
> then return []
> else do
> errs ← getErrors
> return $ err:errs
The errors themselves are, like most things in GL, just a `GLuint` that maps
to some enumerated value. [The documentation for `glGetError`][glGetError]
gives us a clue as to what values might be returned, so we can use that to
convert the errors to a more useful *`String`* value.
> showError :: GLuint → String
> showError GL_INVALID_ENUM =
> "GL_INVALID_ENUM"
> showError GL_INVALID_VALUE =
> "GL_INVALID_VALUE"
> showError GL_INVALID_OPERATION =
> "GL_INVALID_OPERATION"
> showError GL_INVALID_FRAMEBUFFER_OPERATION =
> "GL_INVALID_FRAMEBUFFER_OPERATION"
> showError GL_OUT_OF_MEMORY =
> "GL_OUT_OF_MEMORY"
> showError GL_STACK_UNDERFLOW =
> "GL_STACK_UNDERFLOW"
> showError GL_STACK_OVERFLOW =
> "GL_STACK_OVERFLOW"
> showError x = "GL Error " ++ show x
Finally, `printErrors` is the function we'll actually use. It uses the above
two functions to collect the errors and output them. I found it useful just
to crash straight away at these point, so I report the errors using `error`.
If you wanted to try and continue despite the errors you could use `putStrLn`
instead.
> printErrors :: String → IO ()
> printErrors prefix = do
> es ← map showError <$> getErrors
> when (not $ null es) $
> error (prefix ++ ": " ++ show es)
Note the `prefix` parameter, which just lets you put in a little string
describing where in the code the error occurred. Armed with this function,
you can scatter error checks all over the place to help narrow down the
cause of a problem to specific regions of code.
Setting up the window
---------------------
The `main` function of our application begins by setting up the window using
GLFW and binding it to our current GL context. Once that's done, it can hand
off to our initialisation and main loop to do the bulk of the work.
Because I want to keep the distinction between GLFW and OpenGL quite strong,
I've chosen not to mix them up in this post. This section, which deals with
window setup and initialisation, uses GLFW exclusively and makes no direct GL
calls at all. Once this section is over, we won't touch GLFW again and it
will be pure GL from then on.
> main :: IO ()
> main = do
> hSetBuffering stdout LineBuffering
Not strictly necessary, but I begin here by setting `stdout` to use
LineBuffering. This means any output will be flushed on every newline, which
can be invaluable for debugging.
Next, we need to initialise GLFW.
> success ← GLFW.init
> if not success
> then void $ putStrLn "Failed to initialise GLFW"
> else do
If GLFW won't initialise we might as well give up, otherwise we can continue
on into our program.
We need to provide GLFW with some hints to tell it how to set up the window.
These will vary depending on the architecture you want to support.
> mapM_ GLFW.windowHint
> [ GLFW.WindowHint'ClientAPI
> GLFW.ClientAPI'OpenGL
> , GLFW.WindowHint'OpenGLProfile
> GLFW.OpenGLProfile'Core
> , GLFW.WindowHint'OpenGLForwardCompat True
> , GLFW.WindowHint'ContextVersionMajor 3
> , GLFW.WindowHint'ContextVersionMinor 2 ]
I've rather arbitrarily opted for OpenGL 3.2 here, which is not outrageously
out-of-date but is still widely supported. More information about the available
window hints can be found in the [GLFW documentation][glfw-winhints].
We're now ready to make the window. Again, it's possible this may fail, so
we'll just drop out with an error if that happens.
> w ← GLFW.createWindow 480 320 "Haskell GL"
> Nothing Nothing
> case w of
> Nothing → putStrLn "Failed to create window"
> Just win → do
OK, we have a window! First things first, let's associate the current GL
context with this window so that any GL calls we make from now on will apply
to it.
> GLFW.makeContextCurrent w
The next step is to hook into GLFW's callbacks. In reality, I don't think the
GLFW design of responding to callbacks fits the Haskell mindset very well as
you necessarily have to have callbacks modify some sort of global state, but
since we're using GLFW we're stuck with it. For a serious game project I
would probably just do the window handling myself and take a different
approach.
So, we start off by setting up handling of the "close" button. We create an
`IORef` to tell us whether the window has been closed, which we set to `True`
when the close button is pressed. That way we can check at any time during
our game loop whether we need to shut down. We could also close the window on
a keypress simply by setting the same `IORef` value. It's quick and dirty,
but it works:
> closed ← newIORef False
> GLFW.setWindowCloseCallback win $
> Just (const $ writeIORef closed True)
We'll also want to hook into GLFW's `WindowSizeCallback` to avoid our image
getting stretched when we resize the window. Again, we'll make use of an `IORef`
to store the calculated projection matrix so that we can access it from the
render loop. We'll cover `calculateProjectionMatrix` later; for now on just
assume it's a function which takes a tuple of `(width, height)` and returns
the projection matrix we need for that aspect ratio.
> dims ← GLFW.getWindowSize win
> projectionMatrix ← newIORef $
> calculateProjectionMatrix dims
We'll look into the details of what `resize` does later, but for now we just
tell GLFW to call it when the window is resized. Since I don't want to have
any GLFW-specific code in the main portion of this demo, I drop the
`GLFW.Window` parameter using `const` (I actually did the same for the
`WindowCloseCallback` above, too).
> GLFW.setWindowSizeCallback win $
> Just (const $ resize projectionMatrix)
Let's also make a quick helper function which swaps the draw buffers for the
current window, so we don't have to expose `win` to the rest of the program.
We also put in a call to `GLFW.pollEvents` while we're at it, so that window
events and keypresses (if there were any) are handled properly.
> let swapper = GLFW.swapBuffers win ≫ GLFW.pollEvents
That pretty much covers it for GLFW's setup -- we're now ready to initialise
and run our demo. We'll have our main function just drop out when it's done,
so we can terminate once it's complete.
> initialise ≫= runDemo closed projectionMatrix swapper
> GLFW.terminate
One last thing before I leave GLFW aside entirely -- I'll want to be able to
access the time delta within my main loop. GLFW provides us with a convenient
way to query this in a platform-agnostic manner.
> getDeltaTime :: IO GLfloat
> getDeltaTime = do
> t ← GLFW.getTime
> GLFW.setTime 0
> return $ maybe 0 (fromRational ∘ toRational) t
This very simple implementation obviously assumes we will only be querying the
delta time once per frame.
We now have a window set up and all the platform-specific stuff we might want
handled. There's just one more thing we need to get out of the way before we
can begin looking at the actual GL side of things and the `gl` package in
particular.
Constructing our cube mesh
--------------------------
We're going to construct our mesh in code to avoid having to worry about model
formats and so forth. This section has little to do with actual GL code, so
if you're keen to see the `gl` library in action you can safely skip it.
A mesh can be thought of as simply a collection of vertex data, and a set of
indices into that data. For this demo, the information we need about each vertex is:
1. Its position relative to the model
2. Its colour
3. Its texture co-ordinates (Called *UV Co-ordinates*)
4. Its normal vector
We can store these in a structure with a `Vector` for each piece of data,
along with an index `Vector`.
> data MeshSpec = MeshSpec
> { specPositions :: V.Vector (V3 GLfloat)
> , specColours :: V.Vector (V3 GLfloat)
> , specNormals :: V.Vector (V3 GLfloat)
> , specUVs :: V.Vector (V2 GLfloat)
> , specIndices :: V.Vector (GLuint, GLuint, GLuint)
> }
We're going to be defining a lot of these values all at once. Unfortunately,
this starts to look pretty ugly in Haskell because negative numbers have to be
wrapped in brackets, so that the vector $(0, -1, 0)$ is expressed `V3 0 (-1)
0`. To try and ease the pain here, let's define an alternate constructor for
*`V3`* values which takes a tuple instead of three parameters.
> v3 :: (a, a, a) → V3 a
> v3 (x, y, z) = V3 x y z
This allows us to define a function to generate a cuboid of any dimension.
The function will take the dimensions of the cuboid and fill a *`MeshSpec`*
with the required data.
> cuboid :: GLfloat → GLfloat → GLfloat → MeshSpec
> cuboid l' h' d' =
> MeshSpec positions colours normals uvs indices where
> l = l' * 0.5; d = d' * 0.5; h = h' * 0.5
I named my input parameters `l'`, `h'`, and `d'` because although I take the
length, height, and depth as input, I generally want to use these values
halved, so that I can treat them as an offset from the origin in the centre of
the cuboid. These halved values, then, I give the more accessible names of
`l`, `h`, and `d`. Here's how I use them to define a quad for each face of
the cube:
> positions =
> [v3 ( l, h, d), v3 ( l,-h, d), v3 ( l,-h,-d), v3 ( l, h,-d),
> v3 ( l, h,-d), v3 (-l, h,-d), v3 (-l, h, d), v3 ( l, h, d),
> v3 (-l, h, d), v3 (-l,-h, d), v3 ( l,-h, d), v3 ( l, h, d),
> v3 (-l, h,-d), v3 (-l,-h,-d), v3 (-l,-h, d), v3 (-l, h, d),
> v3 ( l,-h,-d), v3 ( l,-h, d), v3 (-l,-h, d), v3 (-l,-h,-d),
> v3 ( l, h,-d), v3 ( l,-h,-d), v3 (-l,-h,-d), v3 (-l, h,-d)]
Each line here is a single face: The right, top, front, left, bottom and back
faces respectively. I'm going to colour them so that the $(r, g, b)$ values
are mapped to the (normalised) $(x, y, z)$ values. So the left, bottom, back
point $(-l, -h, -d)$ is black, the right, bottom, back point $(l, -h, -d)$ is
red, the right, top, front point $(l, h, d)$ is white... and so forth. This
can be done by saying that for a particular point $(x, y, z)$ its RGB value
can be calculated thus:
$$
\left(\frac{x + l}{l'}, \frac{y + h}{h'}, \frac{z + d}{d'}\right)
$$
This can be expressed quite succinctly in Haskell.
> colours = V.map ((/ V3 l' h' d') ∘ (+ V3 l h d)) positions
For the normals, we can simply take the normal vector for each axis, and the
negations of those vectors. Since each face is composed of four vertices, and
we want to share the same normal vector across the face, we replicate each
normal four times -- one for each vertex.
> normals = V.concat ∘ map (V.replicate 4) $ ns ++ negated ns
> where ns = [V3 1 0 0, V3 0 1 0, V3 0 0 1]
The texture co-ordinates for this shape are quite simple -- they simply
stretch from $(0, 0)$ in the bottom-left corner to $(1, 1)$ in the top-right.
We want the same set of co-ordinates across each face, which again we can do
using `replicate`.
> uvs = V.concat ∘ replicate 6 $
> [V2 0 0, V2 0 1, V2 1 1, V2 1 0]
Finally we set up the indices for our shape. The quads we defined in
`positions` above follow a regular pattern: $(0, 1, 2, 3)$, $(4, 5, 6,
7)$... essentially we just make a 4-tuple of incrementing numbers from an
offset of $faceIndex \times 4$.
> indices =
> quads ∘ V.zipWith forFace [0..] ∘ V.replicate 6 $ (0, 1, 2, 3)
> where
> forFace i (a, b, c, d) = (a + i*4, b + i*4, c + i*4, d + i*4)
OpenGL doesn't work with quads, though, it uses triangles. The `quads`
function we just used takes the quads and splits them up into triangles.
> quads = V.concatMap triangulate
> triangulate (a, b, c, d) = [(a, b, c), (c, d, a)]
...and that gives us the *`MeshSpec`* for our cube!
Considering each of these sets of vertex data separately is convenient when
constructing the mesh, especially when you're hard-coding like I did here.
You'll get better performance, though, if you combine them into a single,
interleaved array (at least if you're not deforming or otherwise modifying the
mesh). This would just be a flat stream of *`GLfloat`*s, like this:
```
x1, y1, z1, r1, g1, b1, nx1, ny1, nz1, u1, v1, x2, y2, z2, r2, g2...
```
The indices are also represented as a flat list, an unpacked version of the
tuple representation we use in *`MeshSpec`* above. The following type gives a
representation closer to what we'd like to feed to GL.
> data MeshData = MeshData
> { vertexData :: V.Vector GLfloat
> , indexData :: V.Vector GLuint
> }
Unpacking the indices to fit into the above structure is reasonably simple.
> unpackIndices :: V.Vector (GLuint, GLuint, GLuint)
> → V.Vector GLuint
> unpackIndices = V.concatMap unpack
> where unpack (a, b, c) = [a, b, c]
Interleaving the vertex data isn't too much harder thanks to `zipWith4`.
> interleave :: V.Vector (V3 GLfloat)
> → V.Vector (V3 GLfloat)
> → V.Vector (V3 GLfloat)
> → V.Vector (V2 GLfloat)
> → V.Vector GLfloat
> interleave positions colours normals uvs =
> V.foldr (V.++) V.empty $
> V.zipWith4 combine positions colours normals uvs
> where combine (V3 x y z) (V3 r g b) (V3 nx ny nz) (V2 u v) =
> [x, y, z, r, g, b, nx, ny, nz, u, v]
Be careful here, as by interleaving the vertex streams into a single array of
*`GLfloats`* you are, of course, leaving type safety behind you. When writing
this post, I had a bug where my lighting looked all wrong. It turned out I
had put the normals and the *uv* co-ordinates in the wrong order in my
`combine` function -- a fact that would have been caught by the typechecker
straight away!
Now we can use these functions to convert from a *`MeshSpec`* to a
*`MeshData`*.
> fromMeshSpec :: MeshSpec → MeshData
> fromMeshSpec spec =
> MeshData (interleave (specPositions spec)
> (specColours spec)
> (specNormals spec)
> (specUVs spec))
> (unpackIndices $ specIndices spec)
This gets us from an easy-to-define "mesh specification" to the raw data that
we'd like to give to GL. Here, we've defined the mesh in code, but you could
just as well load the data from a file and read it into *`MeshData`* directly
if you wanted.
Resource loading
----------------
OK, our basic setup is complete, it's time to get down and dirty with OpenGL!
First we need to load and prepare our resources.
Our aim here is to get a textured, spinning cube on the screen using modern
OpenGL. To that end, the very least we will need is the texture, a shader
program to do the rendering, and of course the mesh itself. Let's define some
datatypes to store these in.
First, the mesh. We use a *Vertex Array Object* to do the rendering, which
contains references to all the data making up the mesh as well as settings
describing the layout of the data (which parts of the stream to bind to which
attributes in the shader).
The data itself is stored in *Vertex Buffer Objects*, which I have named `VBO`
for the vertices and `IBO` for the indices. we bind these into the Vertex
Array Object, so we don't actually need them for rendering, but we keep hold
of them for cleanup later.
More information on Vertex Buffer Objects and Vertex Array Objects can be
found on the [OpenGL wiki][glwiki-vspec].
> data Mesh = Mesh
> { meshVBO :: GLuint
> , meshIBO :: GLuint
> , meshVAO :: GLuint
> , meshIndexCount :: GLsizei
> }
For the shader, We want the ID for the shader program, and alongside that
we'll store the locations of all the constants and attributes. These will be
different per-shader, but since in this case we only have one shader we can
just assume they'll always be the same.
> data Shader = Shader
> { shaderProgram :: GLuint
> , positions :: GLuint
> , colours :: GLuint
> , normals :: GLuint
> , uvs :: GLuint
> , pvmMatrix :: GLint
> , viewModelMatrix :: GLint
> , normalMatrix :: GLint
> , diffuseColour :: GLint
> , ambientColour :: GLint
> , specularColour :: GLint
> , shininess :: GLint
> , lightDirection :: GLint
> , diffuseMap :: GLint
> }
This represents all the data we need to send to our shader. Note that
contained within this structure are not the actual *values* (the positions,
colours, matrices, etc), but the *location at which the values are stored* in
the shader program. We'll use these locations to set the values when we draw.
Finally, there's the texture, which is simple -- just the ID GL uses to refer
to the texture is enough.
> type TextureID = GLuint
Packaging these two structures together , we create a *`Resources`* type
representing all the resources this demo requires.
> data Resources = Resources
> { mesh :: Mesh
> , texture :: TextureID
> , shader :: Shader
> }
The job of our Initialise function, then, will be to load these resources and
initialise them ready for use by GL. Loading and preparing resources for
usage by GL may fail at any point, so I'm going to wrap the entire process in
the *`MaybeT`* monad transformer so it drops out early on failure.
> initialise :: IO (Maybe Resources)
> initialise = runMaybeT $ do
For a more complex application, *`EitherT`*/*`ErrorT`* might be a better
choice so that we can report *what* failed.
Loading the texture
First let's set up the texture. Here's the texture we're going to use; you can
download it if you're following along.
![](/posts/2015/03/25/getting-up-and-running-with-gl/haskell.png "The Haskell logo")
Loading the data is very simple. As it happens, I know that this image is an
`ImageRGBA8`, so that's all I'm going to handle -- in reality you may need to
handle various pixel formats depending on your input data.
> png ← liftIO $ readPng "haskell.png"
> (Image texWidth texHeight texData) ← MaybeT $ case png of
> (Right (ImageRGBA8 i)) → return $ Just i
> (Left s) → liftIO (print s) ≫ return Nothing
> _ → return Nothing
We now have the raw pixel buffer data for the texture. All that remains is to
pass it to GL. First we generate the texture name which we'll use to refer to
it (although GL calls these "names", it is just a `GLuint` ID, really).
> textureID ← liftIO ∘ alloca $ \texIDPtr → do
> glGenTextures 1 texIDPtr
> peek texIDPtr
The idiom of allocating a temporary variable, passing it to GL to be filled,
and then returning the filled value doesn't feel very "Haskelly", but it is
exactly what GL expects. It means that when following along with a GL
tutorial intended for C, you can pretty much switch the syntax and the
examples will all work.
Writing out these three lines does get old pretty fast though, so let's define
a utility function which simplifies it. The following function assumes that
the variable to be filled is the last one passed to the function.
> let fillWith f = liftIO ∘ alloca $ liftM2 (≫) f peek
Now we have the texture name we can use it to bind and set up the texture. We
use `unsafeWith` to get access to the raw pixel data from the `Vector`.
> glBindTexture GL_TEXTURE_2D textureID
> let (w, h) = (fromIntegral texWidth, fromIntegral texHeight)
> liftIO ∘ SV.unsafeWith texData $
> glTexImage2D GL_TEXTURE_2D 0 GL_RGBA w h 0
> GL_RGBA GL_UNSIGNED_BYTE ∘ castPtr
> glTexParameteri GL_TEXTURE_2D
> GL_TEXTURE_MAG_FILTER GL_LINEAR
> glTexParameteri GL_TEXTURE_2D
> GL_TEXTURE_MIN_FILTER GL_LINEAR
> glTexParameteri GL_TEXTURE_2D
> GL_TEXTURE_WRAP_S GL_CLAMP_TO_EDGE
> glTexParameteri GL_TEXTURE_2D
> GL_TEXTURE_WRAP_T GL_CLAMP_TO_EDGE
Why is `unsafeWith` unsafe? Because it gives you a pointer to the underlying
memory the *`Vector`* is pointing to. This is potentially unsafe because the
C function you pass it to could hold onto this pointer and modify it at any
time, breaking referential transparency. Stored pointers like this are also
not tracked by the garbage collector, so if you hold onto it and try to use it
after the original *`Vector`* has gone out of scope the garbage collector may
already have cleaned it up.
In this case, we know that `glTexImage2D` will upload the data to the GPU
without modifying it, meaning that neither of these issues should concern us,
so it is safe to use.
Loading the shaders
Next up are the shaders. The shader code itself is included [at the end of
this post][shaders]; For now let's just assume they are loaded into
`vertexShader.glsl` and `fragmentShader.glsl` respectively.
Loading and compiling the two shaders is basically identical, so let's create
a utility function to help us.
> let loadAndCompileShader :: GLenum → FilePath
> → IO (Maybe GLuint)
> loadAndCompileShader shaderType filename = do
First we request GL to create a shader object for us.
> shaderID ← glCreateShader shaderType
After that we load the shader file and bind its contents to our new shader
object. `glShaderSource` is looking for an array of C-style strings, or in
other words a pointer to a pointer of *`GLchar`*, expressed in C as `const
GLchar**`. This is where working at such a low level starts to get a bit
fiddly in Haskell -- you can certainly do it, but it's not quite as succinct
as it would be in C.
> shaderCode ← T.readFile filename
> T.withCStringLen shaderCode $
> \(str, len) → with str $
> \strPtr → with (fromIntegral len) $
> \lenPtr → glShaderSource shaderID 1 strPtr lenPtr
Compare the four lines starting `T.withCStringLen` with the C equivalent:
```C
glShaderSource(shaderID, 1, &shaderCode, NULL);
```
Admittedly this isn't an entirely fair comparison -- it assumes
`NULL`-terminated strings which we weren't using in Haskell, and of course the
file loading directly preceding it would have been more arduous in C. Still,
the point stands that what is a simple operator in C (`&`) requires a call to
`with` and a lambda function in Haskell.
Fortunately, Haskell offers a number of techniques for abstracting some of
this awkwardness. One of these is the *`ContT`* monad, which allows you to take
a series of nested callback functions such as the one above and transform it
into a monad, which can be expressed very readably using `do`-notation.
Here's how the above code looks using *`ContT`*:
```haskell
evalContT $ do
(str, len) ← ContT (T.withCStringLen shaderCode)
strPtr ← ContT (with str)
lenPtr ← ContT (with $ fromIntegral len)
liftIO $ glShaderSource shaderID 1 strPtr lenPtr
```
Given this, you could imagine that with a bit of effort and applicative
notation, it might be possible to get something like,
```haskell
glShaderSource shaderID 1 <$> str <*> len
```
which isn't so far from the C after all!
Anyway, now that the shader's loaded into memory, our utility function can
compile it and check that compilation succeeded.
> glCompileShader shaderID
> compileStatus ← fillWith $
> glGetShaderiv shaderID GL_COMPILE_STATUS
If compilation failed, we output the info log to tell us what happened. We
have to do a little bit of marshalling between C and Haskell datatypes to
access the log as a *`Text`* object for printing.
> when (compileStatus == GL_FALSE) $ do
> infoLogLength ← fillWith $
> glGetShaderiv shaderID GL_INFO_LOG_LENGTH
> let infoLogLength' = fromIntegral infoLogLength
> allocaBytes infoLogLength' $ \infoBuffer → do
> glGetShaderInfoLog shaderID infoLogLength
> nullPtr infoBuffer
> T.putStr =≪ T.peekCStringLen ( infoBuffer,
> infoLogLength')
Having done that, we can return the ID of our compiled shader object if
compilation was successful, or *`Nothing`* otherwise.
> return $ if compileStatus == GL_TRUE
> then Just shaderID else Nothing
Our helper function complete, let's use it to load the vertex and fragment
shaders. We wrap the calls to `loadAndCompileShader` in *`MaybeT`* so that this
function will drop out automatically if either of them fail.
> vs ← MaybeT $ loadAndCompileShader
> GL_VERTEX_SHADER "vertexShader.glsl"
> fs ← MaybeT $ loadAndCompileShader
> GL_FRAGMENT_SHADER "fragmentShader.glsl"
Now we need to generate our shader program and link the two shader objects
into it.
> programID ← glCreateProgram
> glAttachShader programID vs
> glAttachShader programID fs
> glLinkProgram programID
> linkStatus ← fillWith $
> glGetProgramiv programID GL_LINK_STATUS
Here again we output the log and drop out of the initialisation with
*`Nothing`* if `linkStatus` is `GL_FALSE`.
> when (linkStatus == GL_FALSE) ∘ MaybeT $ do
> infoLogLength ← fillWith $
> glGetProgramiv programID GL_INFO_LOG_LENGTH
> let infoLogLength' = fromIntegral infoLogLength
> allocaBytes infoLogLength' $ \infoBuffer → do
> glGetProgramInfoLog programID infoLogLength
> nullPtr infoBuffer
> T.putStr =≪ T.peekCStringLen (infoBuffer, infoLogLength')
> return Nothing
Having linked the shader program, we can throw away the individual shader
objects that went into it, which will make cleaning up later easier.
> glDeleteShader vs
> glDeleteShader fs
We now know that we have a valid, correctly linked program identified by
`programID`. We can query this for the locations of its attributes and
constants which we'll use to set their values later.
To ease the marshalling between Haskell and C I'm going to define a couple of
helper functions here. The first, `unsign`, takes the C idiom of returning
negative numbers on failure and converts it into the Haskell *`Maybe`* type.
> let unsign :: Integral a ⇒ GLint → Maybe a
> unsign x | x < 0 = Nothing
> | otherwise = Just $ fromIntegral x
The second helper function deals with marshalling strings to C. I'm going to
use *`ContT`* to reduce the reliance on callback functions. The `forString`
function will take in a function expecting a program ID and a C string, along
with a *`Text`* object with the actual string we want to use. It will
transform this into an action wrapped in *`ContT`* and *`MaybeT`*,
representing the fact that it is run as part of a sequence of callbacks, any
of which might fail, in which case they should all fail. Since we're always
going to be querying the same shader program we'll just refer to it directly
in `fromString` so that we don't have to pass it in every time. Finally we
use `unsign` to return *`Nothing`* on failure.
> let forString :: Integral a
> ⇒ (GLuint → Ptr GLchar → IO GLint)
> → T.Text → MaybeT (ContT r IO) a
> f `forString` x = do
> (str, _) ← lift $
> ContT (T.withCStringLen $ T.concat [x, "\0"])
> loc ← liftIO $ f programID str
> MaybeT ∘ return $ unsign loc
Armed with `forString`, what would have been a tedious process of marshalling
C strings through a series of callbacks can be expressed quite idiomatically:
> glShader ← MaybeT ∘ evalContT ∘ runMaybeT $
> Shader
> <$> pure programID
> <*> glGetAttribLocation `forString` "position"
> <*> glGetAttribLocation `forString` "colour"
> <*> glGetAttribLocation `forString` "normal"
> <*> glGetAttribLocation `forString` "uv"
> <*> glGetUniformLocation `forString` "pvmMatrix"
> <*> glGetUniformLocation `forString` "viewModelMatrix"
> <*> glGetUniformLocation `forString` "normalMatrix"
> <*> glGetUniformLocation `forString` "diffuseColour"
> <*> glGetUniformLocation `forString` "ambientColour"
> <*> glGetUniformLocation `forString` "specularColour"
> <*> glGetUniformLocation `forString` "shininess"
> <*> glGetUniformLocation `forString` "lightDirection"
> <*> glGetUniformLocation `forString` "diffuseMap"
It is important that the names of the attributes and uniforms above match
those that are actually used in [the shaders][shaders], otherwise they won't
be found and we'll drop out with *`Nothing`* here.
Confession: I originally tried to define `forString` with the type *`ContT r
(MaybeT IO) GLuint`* rather than the current *`MaybeT (ContT r IO) GLuint`*,
but I couldn't figure it out. Doing this would mean we could avoid unwrapping
the *`MaybeT`* with `runMaybeT` and then wrapping it up again with *`MaybeT`*
at the end, which would be a bit nicer. It does rather change the meaning of
what's being expressed though, and I think for that reason it might be
impossible.
Loading the mesh
Finally, here's the mesh. We'll initialise a *`MeshSpec`* describing a
$1\times1\times1$ cube and convert that to *`MeshData`* using the functions
described in the previous section. At that point we'll have some raw data,
such as might have been read in from a model file if we were drawing something
more complicated than a cube.
> let cube = fromMeshSpec $ cuboid 1 1 1
We need to create two buffer objects: our VBO and our IBO. We create buffer
objects using `glGenBuffers`; this in turn will give as an ID for each buffer
with which we can refer to it.
`glGenBuffers` takes an array and a length and fills the values in that array
with that many buffers. We use the facilities in *`Foreign.Marshal.Array`* to
allocate the array and pull out the values at the end.
> [vbo, ibo] ← liftIO ∘ allocaArray 2 $ \buffers → do
> glGenBuffers 2 buffers
> peekArray 2 buffers
We'll start by setting up the vertex buffer. First we need to bind the buffer
ID we just got to the `GL_ARRAY_BUFFER` target so that GL knows what we intend
to do with it. Then we fill it with data. Finally, we bind 0 to
`GL_ARRAY_BUFFER` to free it up.
> glBindBuffer GL_ARRAY_BUFFER vbo
> let vertices = vertexData cube
> vertexBufSize = sizeOf (V.head vertices) * V.length vertices
> liftIO ∘ SV.unsafeWith (SV.convert vertices) $ \vsPtr →
> glBufferData GL_ARRAY_BUFFER
> (fromIntegral vertexBufSize)
> (castPtr vsPtr)
> GL_STATIC_DRAW
> glBindBuffer GL_ARRAY_BUFFER 0
Again we use `unsafeWith` to get the data into C. We have to convert the
vector into a *Storable* vector using `Data.Vector.Storable.convert` before
we can do this.
Setting up the index buffer is similar, only this time the target is
`GL_ELEMENT_ARRAY_BUFFER`.
> glBindBuffer GL_ELEMENT_ARRAY_BUFFER ibo
> let indices = indexData cube
> indexBufSize = sizeOf (V.head indices) * V.length indices
> liftIO ∘ SV.unsafeWith (SV.convert indices) $ \isPtr →
> glBufferData GL_ELEMENT_ARRAY_BUFFER
> (fromIntegral indexBufSize)
> (castPtr isPtr)
> GL_STATIC_DRAW
> glBindBuffer GL_ELEMENT_ARRAY_BUFFER 0
Now that our buffer objects are set up for vertices and indices, we can wrap
them up together in a *Vertex Array Object*. This collects the data together
with properties about how it should be used. First we generate and bind the
vertex array object, much as we did the vertex buffer objects earlier.
> vao ← liftIO ∘ alloca $ \vaoPtr → do
> glGenVertexArrays 1 vaoPtr
> peek vaoPtr
> glBindVertexArray vao
Next we bind the buffer objects we made for the vertex and index data to this
new vertex array object.
> glBindBuffer GL_ARRAY_BUFFER vbo
> glBindBuffer GL_ELEMENT_ARRAY_BUFFER ibo
We need to enable all four of the attributes our shader uses.
> glEnableVertexAttribArray (positions glShader)
> glEnableVertexAttribArray (colours glShader)
> glEnableVertexAttribArray (normals glShader)
> glEnableVertexAttribArray (uvs glShader)
And finally, we fill in the attributes, which tells GL the actual layout of
the data within the buffer. When talking about the layout, we're mainly
talking about two things: The *offset* and the *stride*. The offset tells us
how far into the array that chunk of data begins, while the stride tells us
the difference from the start of one set of values to the start of the next.
Since we have all our data in one interleaved array, the stride will be the
same for each kind of data: `11 * sizeof(GLfloat)`.
> let offset x = wordPtrToPtr $ x * fromIntegral floatSize
> stride = fromIntegral floatSize * 11
> floatSize = sizeOf (undefined::GLfloat)
Now we can set the values for each type using `glVertexAttribPointer`.
> glVertexAttribPointer (positions glShader)
> 3 GL_FLOAT GL_FALSE stride (offset 0)
> glVertexAttribPointer (colours glShader)
> 3 GL_FLOAT GL_FALSE stride (offset 3)
> glVertexAttribPointer (normals glShader)
> 3 GL_FLOAT GL_FALSE stride (offset 6)
> glVertexAttribPointer (uvs glShader)
> 2 GL_FLOAT GL_FALSE stride (offset 9)
Our buffer objects are now set up and loaded onto the GPU ready to use. The
last thing to do is to put them in the *`Mesh`* structure ready to be added to
our *`Resources`*.
> let glMesh = Mesh vbo ibo vao (fromIntegral $ V.length indices)
Now that we have everything we need, we call `initGL` and then return the
`Resources`.
> liftIO initGL ≫ return (Resources glMesh textureID glShader)
And we're done! Our `Resources` handle should now contain all the data we
need, unless there was a problem, in which case we'll fail gracefully.
Setting up GL
-------------
That call to `initGL` at the end of `initialise` allows us to give GL its basic
settings.
> initGL :: IO ()
> initGL = do
> glClearColor 0.96 0.96 0.96 1
> glClearDepth 1
> glEnable GL_DEPTH_TEST
> glDepthFunc GL_LEQUAL
> glCullFace GL_BACK
While we're here, remember the `resize` function we gave to GLFW at the start?
Let's get the definition of that out of the way.
> resize :: IORef (M44 GLfloat) → Int → Int → IO ()
> resize projectionMatrix w h = do
`resize` takes the `IORef` we made to store the projection matrix, and the new
width and height. It has two jobs: it needs to update the viewport, so that
GL rendering can fill the window, and it needs to update the projection
matrix, so that the aspect ratio doesn't get ruined.
Setting the viewport is simple -- just pass the origin `(0, 0)`, and the full
width and height of the window. Of course. if we only wanted to draw into a
subset of the window that's what we'd pass. The projection matrix is
calculated using the same function we used in `main`:
`calculateProjectionMatrix`. It is then written to the `IORef` so that we can
access it from within our main loop.
> glViewport 0 0 (fromIntegral w) (fromIntegral h)
> writeIORef projectionMatrix $ calculateProjectionMatrix (w, h)
Here's the `calculateProjectionMatrix` function itself. We use the
`perspective` function from `linear` to do the work for us. `π/3` radians
gives us a field of view of 60°.
> calculateProjectionMatrix :: Integral a ⇒ (a, a) → M44 GLfloat
> calculateProjectionMatrix (w, h) =
> perspective (π/3) (fromIntegral w / fromIntegral h) 1 100
Handling state
--------------
This demo is very simple, so there isn't much state to deal with.
Nevertheless, the cube *does* spin, so we will need to keep track of its
angle. As well as that, I'm going to include the camera position within the
state structure even though it remains constant throughout the demo, as it's
convenient to hold the data together, and in real life you're almost certainly
going to be moving the camera at some point anyway.
Our state, then, can be represented as follows:
> data DemoState = DemoState
> { cubeRotation :: Quaternion GLfloat
> , cameraPosition :: V3 GLfloat
> }
And the default state is simply:
> defaultState :: DemoState
> defaultState = DemoState
> { cubeRotation = axisAngle (V3 0 1 0) 0
> , cameraPosition = V3 0 1 (-2)
> }
There are a number of ways to handle varying state in Haskell, and which to
use is an interesting choice which can have wide-reaching implications for
your application. For this demo, though, I'm keeping it simple, as I want to
keep the focus on use of the `gl` library. So we'll just have a simple update
function which takes the previous state and a time delta, and returns the new
state.
> update :: DemoState → GLfloat → DemoState
> update s dt =
> s { cubeRotation = cubeRotatedBy (rotationSpeed * dt) }
> where
> cubeRotatedBy θ = cubeRotation s * axisAngle (V3 0 1 0) θ
> rotationSpeed = π / 2
The main loop
-------------
Our `runDemo` function will comprise the main loop for this demo. It takes
the two `IORef`s we created at the start, a callback we can use to swap the
framebuffers, and the `Resources` we just loaded. If the resources failed to
load it just drops out straight away.
> runDemo :: IORef Bool
> → IORef (M44 GLfloat)
> → IO ()
> → Maybe Resources
> → IO ()
> runDemo _ _ _ Nothing = return ()
Otherwise it runs `loop`, which runs the frame unless the value pointed to by
`closed` is `True`. When it *is* `True`, `loop` drops out and `cleanup` gets
run.
> runDemo closed projectionMatrix swapBuffers (Just res) =
> do loop defaultState
> cleanup res where
> loop s = readIORef closed ≫= \c → unless c (runFrame s)
The frame itself is comprised of essentially two phases, the update and the
draw phase. We pass the delta time value to the update, but not the draw.
Finally we call `loop`, which again checks `closed` and runs the next frame.
> runFrame s = do
> draw res s =≪ readIORef projectionMatrix
> glFlush ≫ swapBuffers
> dt ← getDeltaTime
> loop $ update s dt
The order here might look a bit funny. The following is more usual:
1. Get delta time
2. Run update
3. Draw scene
4. Swap buffers
5. Loop
However when you look at the function above, you'll realise they're
equivalent. The reason I've done it this way is to avoid introducing a
variable `s'` for the updated state, which is easy to make mistakes with
(using `s` instead of `s'`), and makes the code just a little less clean.
Actually drawing things
-----------------------
At long last, we're ready to implement the `draw` function, which actually
renders the graphics to the screen. This function is actually surprisingly
simple. A lot of the work in graphics programming goes into efficiently
moving data between the CPU and the GPU -- that and the shader code, of
course. The actual rendering part is doing little more than passing constants
to the shader to work with.
> draw :: Resources → DemoState → M44 GLfloat → IO ()
> draw res state projectionMatrix = do
Note that we are taking the projection matrix directly here, rather than the
*`IORef`*. We let the main loop deal with the fact that this might be
modified in a callback -- as far as the `draw` function is concerned, this is
the projection matrix it is dealing with and it will not change -- not during
this frame, at least.
We have the projection matrix, but there are a number of other matrices we'll
need to calculate. The *view matrix* offsets everything based on the position
of the camera. The *model matrix* then applies the model transformations (in
this case just rotation).
> let viewMat =
> lookAt (cameraPosition state) (V3 0 0 0) (V3 0 1 0)
> modelMat =
> mkTransformation (cubeRotation state) (V3 0 0 0)
It is convenient to precalculate the products of these matrices.
> viewModelMat = viewMat !*! modelMat
> pvmMat = projectionMatrix !*! viewModelMat
Finally the *normal matrix* is used to interpolate normals across faces.
Since not all matrices have a valid inverse, I've chosen to fall back on the
identity matrix in case `inv33` returns *`Nothing`*.
> viewModelMat33 = viewModelMat ^. _xyz . column _xyz
> inverseMat = inv33 viewModelMat33
> normalMat = maybe identity distribute inverseMat
Now we have all the data we need, we can start sending it to GL. We start by
clearing both the colour and the depth buffers.
> glClear $ GL_COLOR_BUFFER_BIT
> .|. GL_DEPTH_BUFFER_BIT
Next, we bind the shader program, mesh, and texture ready for use. The
texture is bound to texture unit 0, which will become important in a minute.
> glUseProgram ∘ shaderProgram $ shader res
> glBindVertexArray ∘ meshVAO $ mesh res
> glActiveTexture GL_TEXTURE0
> glBindTexture GL_TEXTURE_2D $ texture res
We pass in the required uniforms to the shader. First the matrices, where the
*`Storable`* instance for *`M44`* helps us a lot.
> with pvmMat $
> glUniformMatrix4fv (pvmMatrix $ shader res)
> 1 GL_TRUE ∘ castPtr
> with viewModelMat $
> glUniformMatrix4fv (viewModelMatrix $ shader res)
> 1 GL_TRUE ∘ castPtr
> with normalMat $
> glUniformMatrix3fv (normalMatrix $ shader res)
> 1 GL_TRUE ∘ castPtr
And the light and texture data, which is simple. For the texture, the number
passed is the index of the texture unit that texture is bound to; we specified
`GL_TEXTURE0` a minute ago so we put `0` here.
> glUniform4f (diffuseColour $ shader res) 0.6 0.6 0.6 1
> glUniform4f (ambientColour $ shader res) 0.1 0.1 0.1 1
> glUniform4f (specularColour $ shader res) 0.7 0.7 0.7 1
> glUniform1f (shininess $ shader res) 0.4
> glUniform3f (lightDirection $ shader res) 0 0 1
> glUniform1i (diffuseMap $ shader res) 0
Finally, we're ready to draw!
> glDrawElements GL_TRIANGLES
> (meshIndexCount $ mesh res)
> GL_UNSIGNED_INT
> (wordPtrToPtr 0)
If you've got this far you should have a spinning cube on the screen! Pat
yourself on the back; you're ready to go.
Resource cleanup
----------------
OK, we've had our fun, now we need to clean up after ourselves. Actually,
since we're on our way out of the application we don't as the OS will no doubt
take care of it for us, but I'm going to anyway for the sake of completeness
if nothing else.
> cleanup :: Resources → IO ()
> cleanup (Resources m t s) = do
> with (meshVAO m) $ glDeleteVertexArrays 1
> with (meshVBO m) $ glDeleteBuffers 1
> with (meshIBO m) $ glDeleteBuffers 1
> with t $ glDeleteTextures 1
> glDeleteProgram $ shaderProgram s
The gist of this is pretty simple: for every `glCreate*` or `glGen*` function
there is a `glDelete*` equivalent which we have to call.
Final thoughts
--------------
Whew, well, that was a pretty long post! I hope that it will come in handy
for anyone who, like me, wants to fiddle about with OpenGL in Haskell but
doesn't want to spend hours getting the basic pipeline up and running.
Obviously you will want to build your own abstractions on top of this and
presumably draw something more interesting than a rubbish cube. But at least
with this as a starting point you'll be able to build it up from a program
that works.
If you liked this post, please drop me a tweet [\@danielpwright]! If it's
popular, I might explore some other libraries in a similar way. Similarly, if
you found anything lacking, please let me know.
As I mentioned, this was also my first time using the `gl` library. Having
played with it a bit now, I must say that I like it, despite the annoyance of
having to marshal data into C manually. This process is quite easy to
abstract into something easier to use, and if it's me doing the abstraction I
can be sure it will be well-suited to my application.
Apart from that, coming from a traditional games background (my day job is as
a console games programmer in C++), we tend to be quite obssessive over what
our memory is doing. Even having garbage collection feels a bit... *free and
easy*, let alone all the other high-level constructs Haskell offers! Knowing
that I'm dealing with raw GL bindings and being able to see exactly how data
is marshalled between Haskell and C gives me a reassuring sense that, at least
as far as my graphics pipeline is concerned, I am in control of my data.
There's nothing worse than getting a little way into something and then
realising that something you hadn't anticipated about the abstraction you're
working with prevents you from doing the thing you want to do.
There is probably still a place for a package like [OpenGL]. The level of
abstraction there feels much more natural for a Haskell library. But I think,
for my part, I'd rather set the level of abstraction myself, so as to best
match the needs of the project I'm working on, so I will be using the [gl]
package for any graphics projects I do from now on.
Appendix: shader code
---------------------
Here is the code for the two shaders I use in this demo. They are cobbled
together from a variety of tutorials on the internet, and aren't really very
useful for any sort of production use, given that they only allow for a single
directional light, they assume coloured vertices and alpha-blended textures,
and so on. The goal here wasn't really to explore interesting shader code or
graphics techniques, but rather to give an absolute baseline working
environment in GL.
So, I assume that once you have this up and running one of the first things
you'll want to do is throw away these shaders and replace them with something
more useful, possibly by following one of the many tutorials on the internet
for working with OpenGL in C/C++, since the code samples translate quite
naturally when using the `gl` package.
I include these two shaders, therefore, without comment.
vertexShader.glsl
```glsl
#version 330
uniform mat4 pvmMatrix;
uniform mat4 viewModelMatrix;
uniform mat3 normalMatrix;
in vec3 position;
in vec3 colour;
in vec3 normal;
in vec2 uv;
out vec3 calculatedNormal;
out vec4 calculatedEye;
out vec4 rgba;
out vec2 fragmentUV;
void main ()
{
vec4 position4 = vec4(position, 1.0);
gl_Position = pvmMatrix * position4;
calculatedNormal = normalize(normalMatrix * normal);
calculatedEye = -(viewModelMatrix * position4);
rgba = vec4(colour, 1.0);
fragmentUV = uv;
}
```
fragmentShader.glsl
```glsl
#version 330
uniform vec4 diffuseColour;
uniform vec4 ambientColour;
uniform vec4 specularColour;
uniform float shininess;
uniform vec3 lightDirection;
uniform sampler2D diffuseMap;
in vec3 calculatedNormal;
in vec4 calculatedEye;
in vec4 rgba;
in vec2 fragmentUV;
out vec4 colorOut;
void main()
{
vec4 spec = vec4(0.0);
vec3 n = normalize(calculatedNormal);
float intensity = max(dot(n,lightDirection), 0.0);
if (intensity > 0.0)
{
vec3 e = normalize(vec3(calculatedEye));
vec3 h = normalize(lightDirection + e);
float intSpec = max(dot(h,n), 0.0);
spec = specularColour * pow(intSpec,shininess);
}
vec4 texCol = texture(diffuseMap, fragmentUV);
vec4 baseCol = mix(rgba, texCol, texCol.a);
colorOut = baseCol * max(intensity * diffuseColour + spec, ambientColour);
}
```
[gl]: https://hackage.haskell.org/package/gl
[glfw-b]: https://hackage.haskell.org/package/GLFW-b
[OpenGL]: https://hackage.haskell.org/package/OpenGL
[OpenGLRaw]: https://hackage.haskell.org/package/OpenGLRaw
[gl-rant]: https://www.youtube.com/watch?v=yFXzuCFeRGM&t=1h36m55s
[linear]: https://hackage.haskell.org/package/linear
[JuicyPixels]: https://hackage.haskell.org/package/JuicyPixels
[glGetError]: https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetError.xml
[glfw-winhints]: http://www.glfw.org/docs/latest/window.html#window_hints
[glwiki-vspec]: https://www.opengl.org/wiki/Vertex_Specification
[twitter]: http://twitter.com/danielpwright
[reddit]: http://reddit.com/r/haskell
[Data.ByteString.Unsafe]: https://hackage.haskell.org/package/bytestring-0.10.2.0/docs/Data-ByteString-Unsafe.html
[\@danielpwright]: http://twitter.com/danielpwright
[shaders]: #appendix-shader-code