diff --git a/fsl/fsleyes/gl/gl14/glvolume_frag.prog b/fsl/fsleyes/gl/gl14/glvolume_frag.prog
index 99d0e41ad6cca5a289173141308dc9771ba86885..7fe020ebf69b7e459ee7b13ac22187c7ae572836 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_frag.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_frag.prog
@@ -38,7 +38,6 @@
 #                          the range). Clipping values are assumed to be
 #                          normalised to the image texture value range.
 #
-#
 # Outputs:
 #
 #   result.color         - The fragment colour
diff --git a/fsl/fsleyes/gl/gl14/glvolume_funcs.py b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
index 4ffbe4aa8a3e3ab1ea5996b4ad5a0e8cd3659a41..4546faaab674409e895b412190cd47068d7f73ab 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
@@ -1,24 +1,14 @@
 #!/usr/bin/env python
 #
-# glvolume_funcs.py - Functions used by the GLVolume class to render 3D
-#                     images in an OpenGL 1.4 compatible manner.
+# glvolume_funcs.py - OpenGL 1.4 functions used by the GLVolume class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
-"""Provides functions which are used by the :class:`.GLVolume` class to render
-3D images in an OpenGL 1.4 compatible manner.
-
-This module depends upon two OpenGL ARB extensions, ARB_vertex_program and
-ARB_fragment_program which, being ancient (2002) technology, should be
-available on pretty much any graphics card in the wild today.
-
-See the :mod:`.gl21.glvolume_funcs` module for more details.
-
-This PDF is quite useful:
- - http://www.renderguild.com/gpuguide.pdf
-
+"""This module provides functions which are used by the :class:`.GLVolume`
+class to render :class:`.Image` overlays in an OpenGL 1.4 compatible manner.
 """
 
+
 import logging
 
 import numpy                          as np
@@ -34,7 +24,30 @@ import fsl.fsleyes.gl.shaders as shaders
 log = logging.getLogger(__name__)
 
 
+def init(self):
+    """Calls :func:`compleShaders` and :func:`updateShaderState`."""
+
+    self.vertexProgram   = None
+    self.fragmentProgram = None
+    
+    compileShaders(   self)
+    updateShaderState(self)
+
+    
+def destroy(self):
+    """Deletes handles to the vertex/fragment programs."""
+
+    arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
+    arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram))
+
+
 def compileShaders(self):
+    """Compiles the vertex and fragment programs used for rendering
+    :class:`.GLVolume` instances. This is performed using the :mod:`.shaders`
+    module. The compiled vertex and shader programs are stored as attributes
+    on the ``GLVolume`` instance, called ``vertexProgram`` and
+    ``framgentProgram`` respectively.
+    """
 
     if self.vertexProgram is not None:
         arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
@@ -53,25 +66,9 @@ def compileShaders(self):
     self.vertexProgram   = vertexProgram
     self.fragmentProgram = fragmentProgram    
 
-
-def init(self):
-    """Compiles the vertex and fragment programs used for rendering."""
-
-    self.vertexProgram   = None
-    self.fragmentProgram = None
-    
-    compileShaders(   self)
-    updateShaderState(self)
-
     
-def destroy(self):
-    """Deletes handles to the vertex/fragment programs."""
-
-    arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
-    arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram))
-
-
 def updateShaderState(self):
+    """Sets all variables required by the vertex and fragment programs. """
     opts = self.displayOpts
 
     # enable the vertex and fragment programs
@@ -117,8 +114,7 @@ def updateShaderState(self):
 
 
 def preDraw(self):
-    """Prepares to draw a slice from the given :class:`.GLVolume` instance.
-    """
+    """Prepares to draw a slice from the given :class:`.GLVolume` instance. """
 
     # enable drawing from a vertex array
     gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
@@ -138,7 +134,6 @@ def preDraw(self):
 
 def draw(self, zpos, xform=None):
     """Draws a slice of the image at the given Z location. """
-
     
     vertices, voxCoords, texCoords = self.generateVertices(zpos, xform)
     
diff --git a/fsl/fsleyes/gl/gl14/glvolume_sw_frag.prog b/fsl/fsleyes/gl/gl14/glvolume_sw_frag.prog
index f7c28841ec5b2f153793d6d775b05c311a3a0d63..9276d56d820ab2891c4da2abb171cc8ac36bec53 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_sw_frag.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_sw_frag.prog
@@ -1,20 +1,10 @@
 !!ARBfp1.0
 #
-# Fragment program used for rendering GLVolume instances.
-#
-# This fragment program does the following:
-# 
-#  - Retrieves the display space/voxel coordinates corresponding to the
-#    fragment
-# 
-#  - Uses those voxel coordinates to look up the corresponding voxel
-#    value in the 3D image texture.
-# 
-#  - Uses that voxel value to look up the corresponding colour in the
-#    1D colour map texture.
-# 
-#  - Sets the fragment colour.
-#
+# Fragment program used for rendering GLVolume instances. This is a faster
+# (and more limited) version of the standard glvolume_frag.prog shader, which
+# does not perform bounds checking, and does not allow the clipping range to
+# be inverted.
+
 # Required inputs:
 #
 #   fragment.texcoord[0] - Fragment texture coordinates
@@ -30,7 +20,6 @@
 #                          high threshold (y) will not be shown. Assumed to
 #                          be normalised to the image texture value range.
 #
-#
 # Outputs:
 #
 #   result.color         - The fragment colour
diff --git a/fsl/fsleyes/gl/gl14/glvolume_sw_vert.prog b/fsl/fsleyes/gl/gl14/glvolume_sw_vert.prog
index c5040438129255d30ff815c9d0d98b565eab04c9..5e01a841b0c49cde5ed00bcd8bbfb054eef5863a 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_sw_vert.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_sw_vert.prog
@@ -2,14 +2,16 @@
 #
 # Vertex program for rendering GLVolume instances.
 #
+# This version is slightly faster than the standard glvolume_vert.prog shader
+# The only difference is that this version only copies texture coordinates
+# through to the fragment program, not voxel coordinates,
+#
 # Inputs:
-#    state.matrix.mvp
-#    vertex.position
-#    vertex.texcoord[0]
+#    vertex.texcoord[0] - Texture coordinates.
 #
 #
 # Outputs:
-#    result.position
+#    result.texcoord[0] - Texture coordinates.
 #
 
 # Transform the vertex position into display coordinates
diff --git a/fsl/fsleyes/gl/gl14/glvolume_vert.prog b/fsl/fsleyes/gl/gl14/glvolume_vert.prog
index 91fa583fc3174431174b13ffd8cd345c3fd924cb..f03921e2a95c29e08270d0721010120d10754c64 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_vert.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_vert.prog
@@ -1,18 +1,19 @@
 !!ARBvp1.0
 #
-# Vertex program for rendering GLVolume instances.
+# Vertex program for rendering GLVolume instances. 
+#
+# Performs a standard transformation of the vertex coordinates, and
+# passes the corresponding voxel and texture coordinates through to the
+# fragment program.
 #
 # Inputs:
-#    state.matrix.mvp
-#    vertex.position
-#    vertex.texcoord[0]
-#    program.local[0] - image shape
+#    vertex.texcoord[0] - Texture coordinates
+#    program.local[0]   - image shape
 #
 #
 # Outputs:
-#    result.position
-#    result.texcoord[0]
-#    result.texcoord[1]
+#    result.texcoord[0] - Texture coordinates
+#    result.texcoord[1] - Voxel coordinates
 #
 
 PARAM imageShape = program.local[0];
diff --git a/fsl/fsleyes/gl/gl14/test_in_bounds.prog b/fsl/fsleyes/gl/gl14/test_in_bounds.prog
index b334d4af96a2cb03bd89ad3b71062b5e29605014..4e19852bde2d907349e37deb7b56c72f9f61a602 100644
--- a/fsl/fsleyes/gl/gl14/test_in_bounds.prog
+++ b/fsl/fsleyes/gl/gl14/test_in_bounds.prog
@@ -6,7 +6,7 @@
 #   voxCoord   - 3D voxel coordinates
 #   imageShape - Shape of the image
 #
-# Author: Paul McCarthy<pauldmccarthy@gmail.com>
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
 
 TEMP workspace;
diff --git a/fsl/fsleyes/gl/gl21/glvolume_frag.glsl b/fsl/fsleyes/gl/gl21/glvolume_frag.glsl
index 98e952ca940d7374fb5cf819e6d656e344d07791..006509b51051ee001a4e2a870901d3dca23c6066 100644
--- a/fsl/fsleyes/gl/gl21/glvolume_frag.glsl
+++ b/fsl/fsleyes/gl/gl21/glvolume_frag.glsl
@@ -1,5 +1,5 @@
 /*
- * OpenGL fragment shader used by fsl/fslview/gl/gl21/glvolume_funcs.py.
+ * OpenGL fragment shader used for rendering GLVolume instances.
  *
  * Author: Paul McCarthy <pauldmccarthy@gmail.com>
  */
@@ -57,9 +57,8 @@ uniform bool invertClip;
  */
 varying vec3 fragVoxCoord;
 
-
 /*
- * Corresponding texture coordinates.
+ * Corresponding image texture coordinates.
  */
 varying vec3 fragTexCoord;
 
diff --git a/fsl/fsleyes/gl/gl21/glvolume_funcs.py b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
index 588140f5ac3c0af3e43c21273f5baaed9e1a7154..cb59ceee93cd122962e38ad65985215329438a54 100644
--- a/fsl/fsleyes/gl/gl21/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
@@ -1,36 +1,14 @@
 #!/usr/bin/env python
 #
-# glvolume_funcs.py - Functions used by the GLVolume class to render 3D images
-#                     in an OpenGL 2.1 compatible manner.
+# glvolume_funcs.py - OpenGL 2.1 functions used by the GLVolume class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
-"""A GLVolume object encapsulates the OpenGL information necessary
-to render 2D slices of a 3D image, in an OpenGL 2.1 compatible manner.
-
-This module is extremely tightly coupled to the fragment shader
-program ``glvolume_frag.glsl``.
-
-This module provides the following functions:
-
- - :func:`init`:              Initialises GL objects and memory buffers.
-
- - :func:`compileShaders`:    Compiles vertex/fragment shaders.
-
- - :func:`updateShaderState`: Refreshes the parameters used by the shader
-                              programs, controlling clipping and interpolation.
-
- - :func:`preDraw`:           Prepares the GL state for drawing.
-
- - :func:`draw`:              Draws the scene.
-
- - :func:`drawAll`:           Draws multiple scenes.
-
- - :func:`postDraw`:          Resets the GL state after drawing.
-
- - :func:`destroy`:           Deletes the vertex and texture coordinate VBOs.
+"""This module provides functions which are used by the :class:`.GLVolume`
+class to render :class:`.Image` overlays in an OpenGL 2.1 compatible manner.
 """
 
+
 import logging
 import ctypes
 
@@ -45,10 +23,31 @@ import fsl.utils.transform     as transform
 log = logging.getLogger(__name__)
 
 
+def init(self):
+    """Calls :func:`compleShaders` and :func:`updateShaderState`,
+    and creates a GL buffer which will be used to store vertex data.
+    """
+
+    self.shaders = None
+    
+    compileShaders(   self)
+    updateShaderState(self)
+    
+    self.vertexAttrBuffer = gl.glGenBuffers(1)
+                    
+
+def destroy(self):
+    """Cleans up the vertex buffer handle and shader programs."""
+
+    gl.glDeleteBuffers(1, gltypes.GLuint(self.vertexAttrBuffer))
+    gl.glDeleteProgram(self.shaders)
+
+
 def compileShaders(self):
     """Compiles and links the OpenGL GLSL vertex and fragment shader
     programs, and attaches a reference to the resulting program, and
-    all GLSL variables, to the given :class:`.GLVolume` object. 
+    all GLSL variables, as attributes on the given :class:`.GLVolume`
+    object. 
     """
 
     if self.shaders is not None:
@@ -85,25 +84,6 @@ def compileShaders(self):
                                                       'invertClip')
 
 
-def init(self):
-    """Compiles the vertex and fragment shaders used to render image slices.
-    """
-
-    self.shaders = None
-    
-    compileShaders(   self)
-    updateShaderState(self)
-    
-    self.vertexAttrBuffer = gl.glGenBuffers(1)
-                    
-
-def destroy(self):
-    """Cleans up the vertex buffer handle and shader programs."""
-
-    gl.glDeleteBuffers(1, gltypes.GLuint(self.vertexAttrBuffer))
-    gl.glDeleteProgram(self.shaders)
-
-
 def updateShaderState(self):
     """Updates the parameters used by the shader programs, reflecting the
     current display properties.
@@ -218,8 +198,7 @@ def draw(self, zpos, xform=None):
 
 
 def drawAll(self, zposes, xforms):
-    """Delegates to the default implementation in :meth:`.GLObject.drawAll`.
-    """
+    """Draws all of the specified slices. """
 
     nslices   = len(zposes)
     vertices  = np.zeros((nslices * 6, 3), dtype=np.float32)
diff --git a/fsl/fsleyes/gl/gl21/glvolume_sw_frag.glsl b/fsl/fsleyes/gl/gl21/glvolume_sw_frag.glsl
index 441aa999b3272a22eadd334c1cac812c0836aedc..22c2da2839f19d2107c88a09a55e7b400bf0d2e6 100644
--- a/fsl/fsleyes/gl/gl21/glvolume_sw_frag.glsl
+++ b/fsl/fsleyes/gl/gl21/glvolume_sw_frag.glsl
@@ -1,14 +1,11 @@
 /*
- * Fast but limited OpenGL fragment shader used by
- * fsl/fslview/gl/gl21/glvolume_funcs.py.
+ * Fast but limited OpenGL fragment shader used for rendering GLVolume
+ * instances.
  *
  * Author: Paul McCarthy <pauldmccarthy@gmail.com>
  */
 #version 120
 
-#pragma include spline_interp.glsl
-#pragma include test_in_bounds.glsl
-
 /*
  * image data texture.
  */
@@ -19,7 +16,6 @@ uniform sampler3D imageTexture;
  */
 uniform sampler1D colourTexture;
 
-
 /*
  * Shape of the imageTexture.
  */
@@ -42,9 +38,8 @@ uniform float clipLow;
  */
 uniform float clipHigh;
 
-
 /*
- * Corresponding texture coordinates.
+ * Image texture coordinates.
  */
 varying vec3 fragTexCoord;
 
diff --git a/fsl/fsleyes/gl/glmask.py b/fsl/fsleyes/gl/glmask.py
index 5dfa7a7b290a4563d6588b7243dc456216f2f4c6..6674298351897a9109dd12a64715b71c747ba4d9 100644
--- a/fsl/fsleyes/gl/glmask.py
+++ b/fsl/fsleyes/gl/glmask.py
@@ -1,19 +1,11 @@
 #!/usr/bin/env python
 #
-# glmask.py - OpenGL rendering of a binary mask image.
+# glmask.py - The GLMask class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
-"""This module defines the :class:`GLMask` class, which provides functionality
-for OpenGL rendering of a 3D volume as a binary mask.
-
-When created, a :class:`GLMask` instance assumes that the provided
-:class:`.Image` instance has an ``overlayType`` of ``mask``, and that its
-associated :class:`.Display` instance contains a :class:`.MaskOpts` instance,
-containing mask-specific display properties.
-
-The :class:`GLMask` class uses the functionality of the :class:`.GLVolume`
-class through inheritance.
+"""This module provides the :class:`GLMask` class, which implements
+functionality for rendering an :class:`.Image` overlay as a binary mask.
 """
 
 import logging
@@ -29,12 +21,17 @@ log = logging.getLogger(__name__)
 
 
 class GLMask(glvolume.GLVolume):
-    """The :class:`GLMask` class encapsulates logic to render 2D slices of a
+    """The ``GLMask`` class encapsulates logic to render 2D slices of a
     :class:`.Image` instance as a binary mask in OpenGL.
 
-    ``GLMask`` is a subclass of the :class:`.GLVolume class. It overrides a
-    few key methods of ``GLVolume``, but most of the logic is provided by
-    ``GLVolume``.
+    When created, a ``GLMask`` instance assumes that the provided
+    :class:`.Image` instance has a :attr:`.Display.overlayType` of ``mask``,
+    and that its associated :class:`.Display` instance contains a
+    :class:`.MaskOpts` instance, containing mask-specific display properties.
+
+    The ``GLMask`` class derives from the :class:`.GLVolume` class.  It
+    overrides a few key methods of ``GLVolume``, but most of the logic is
+    provided by ``GLVolume``.
     """
 
 
diff --git a/fsl/fsleyes/gl/glvolume.py b/fsl/fsleyes/gl/glvolume.py
index f1470633e78e19bc3f2016c846280a6eca09d998..461bc098a4063f2310f678e9ad9e7c9f199b4d49 100644
--- a/fsl/fsleyes/gl/glvolume.py
+++ b/fsl/fsleyes/gl/glvolume.py
@@ -1,76 +1,109 @@
 #!/usr/bin/env python
 #
-# glvolume.py - OpenGL vertex/texture creation for 2D slice rendering of a 3D
-#               image.
+# glvolume.py - The GLVolume class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
-"""Defines the :class:`GLVolume` class, which creates and encapsulates the
-data and logic required to render 2D slice of a 3D image. The
-:class:`GLVolume` class provides the interface defined in the
-:class:`.GLObject` class.
+"""This module provides the :class:`GLVolume` class, which creates and
+encapsulates the data and logic required to render 2D slice of an
+:class:`.Image` instance.
+"""
 
-A :class:`GLVolume` instance may be used to render an :class:`.Image` instance
-which has an ``overlayType`` of ``volume``. It is assumed that this ``Image``
-instance is associated with a :class:`.Display` instance which, in turn,
-contains a :class:`.VolumeOpts` instance, containing display options specific
-to volume rendering.
 
-The :class:`GLVolume` class makes use of the functions defined in the
-:mod:`.gl14.glvolume_funcs` or the :mod:`.gl21.glvolume_funcs` modules, which
-provide OpenGL version specific details for creation/storage of vertex data,
-and for rendering.
+import logging
 
-These version dependent modules must provide the following functions:
+import OpenGL.GL      as gl
 
-  - ``init(GLVolume)``: Perform any necessary initialisation.
+import fsl.fsleyes.gl as fslgl
+import                   textures
+import                   globject
+import resources      as glresources
 
-  - ``destroy(GLVolume)``: Perform any necessary clean up.
 
-  - ``compileShaders(GLVolume)``: (Re-)Compile the shader programs.
+log = logging.getLogger(__name__)
 
-  - ``updateShaderState(GLVolume)``: Updates the shader program states
-    when display parameters are changed.
 
-  - ``preDraw(GLVolume)``: Initialise the GL state, ready for drawing.
+class GLVolume(globject.GLImageObject):
+    """The ``GLVolume`` class is a :class:`.GLImageObject` which encapsulates
+    the data and logic required to render 2D slices of an :class:`.Image`
+    overlay.
 
-  - ``draw(GLVolume, zpos, xform=None)``: Draw a slice of the image at the
-    given Z position. If xform is not None, it must be applied as a 
-    transformation on the vertex coordinates.
+    
+    A ``GLVolume`` instance may be used to render an :class:`.Image` instance
+    which has a :attr:`.Display.overlayType` equal to ``volume``. It is
+    assumed that this ``Image`` instance is associated with a
+    :class:`.Display` instance which, in turn, contains a :class:`.VolumeOpts`
+    instance, containing display options specific to volume rendering.
 
-  - ``drawAll(Glvolume, zposes, xforms)`` - Draws slices at each of the
-    specified ``zposes``, applying the corresponding ``xforms`` to each.
 
-  - ``postDraw(GLVolume)``: Clear the GL state after drawing.
+    **Version dependent modules**
 
-Images are rendered in essentially the same way, regardless of which OpenGL
-version-specific module is used.  The image data itself is stored on the GPU
-as a 3D texture, and the current colour map as a 1D texture. A slice through
-the texture is rendered using six vertices, located at the respective corners
-of the image bounds.
-"""
+    
+    The ``GLVolume`` class makes use of the functions defined in the
+    :mod:`.gl14.glvolume_funcs` or the :mod:`.gl21.glvolume_funcs` modules,
+    which provide OpenGL version specific details for creation/storage of
+    vertex data, and for rendering.
 
-import logging
-log = logging.getLogger(__name__)
 
-import OpenGL.GL      as gl
+    These version dependent modules must provide the following functions:
 
-import fsl.fsleyes.gl as fslgl
-import                   textures
-import                   globject
-import resources      as glresources
+    ===================================== =====================================
+    ``init(GLVolume)``                    Perform any necessary initialisation.
 
+    ``destroy(GLVolume)``                 Perform any necessary clean up.
 
-class GLVolume(globject.GLImageObject):
-    """The :class:`GLVolume` class encapsulates the data and logic required to
-    render 2D slices of a 3D image.
-    """
+    ``compileShaders(GLVolume)``          (Re-)Compile the shader programs.
+
+    ``updateShaderState(GLVolume)``       Updates the shader program states
+                                          when display parameters are changed.
+
+    ``preDraw(GLVolume)``                 Initialise the GL state, ready for
+                                          drawing.
+
+    ``draw(GLVolume, zpos, xform=None)``  Draw a slice of the image at the
+                                          given ``zpos``. If ``xform`` is not
+                                          ``None``, it must be applied as a
+                                          transformation on the vertex
+                                          coordinates.
  
-    def __init__(self, image, display):
-        """Creates a GLVolume object bound to the given image, and associated
-        image display.
+    ``drawAll(Glvolume, zposes, xforms)`` Draws slices at each of the
+                                          specified ``zposes``, applying the
+                                          corresponding ``xforms`` to each.
+
+    ``postDraw(GLVolume)``                Clear the GL state after drawing.
+    ===================================== =====================================
+
 
-        Initialises the OpenGL data required to render the given image.
+    **Rendering**
+
+    
+    Images are rendered in essentially the same way, regardless of which
+    OpenGL version-specific module is used.  The image data itself is stored
+    as an :class:`.ImageTexture`. This ``ImageTexture`` is managed by the
+    :mod:`.resources` module, so may be shared by many ``GLVolume`` instances.
+    The current colour map (defined by the :class:`.VolumeOpts.cmap` property)
+    is stored as a :class:`.ColourMapTexture`.  A slice through the texture is
+    rendered using six vertices, located at the respective corners of the
+    image bounds. 
+
+
+    **Attributes**
+
+
+    The following attributes are available on a ``GLVolume`` instance:
+
+    ================= =======================================================
+    ``imageTexture``  The :class:`.ImageTexture` which stores the image data.
+    ``colourTexture`` The :class:`.ColourMapTexture` used to store the
+                      colour map.
+    ``texName``       A name used for the ``imageTexture`` and
+                      ``colourTexture``.
+    ================= =======================================================
+    """
+
+    
+    def __init__(self, image, display):
+        """Create a ``GLVolume`` object.
 
         :arg image:   An :class:`.Image` object.
         
@@ -87,8 +120,8 @@ class GLVolume(globject.GLImageObject):
         # Create an image texture and a colour map texture
         self.texName = '{}_{}'.format(type(self).__name__, id(self.image))
 
-        self.imageTexture  = None
         self.colourTexture = textures.ColourMapTexture(self.texName)
+        self.imageTexture  = None
  
         self.refreshImageTexture()
         self.refreshColourTexture()
@@ -97,9 +130,10 @@ class GLVolume(globject.GLImageObject):
 
 
     def destroy(self):
-        """This should be called when this :class:`GLVolume` object is no
+        """This must be called when this :class:`GLVolume` object is no
         longer needed. It performs any needed clean up of OpenGL data (e.g.
-        deleting texture handles).
+        deleting texture handles), calls :meth:`removeDisplayListeners`,
+        and calls :meth:`.GLImageObject.destroy`.
         """
 
         glresources.delete(self.imageTexture.getTextureName())
@@ -115,66 +149,8 @@ class GLVolume(globject.GLImageObject):
         globject.GLImageObject.destroy(self)
 
         
-    def testUnsynced(self):
-        """Used by the :meth:`refreshImageTexture` method.
-        
-        Returns ``True`` if certain critical :class:`VolumeOpts` properties
-        have been unsynced from the parent instance, meaning that this
-        :class:`GLVolume` instance needs to create its own image texture;
-        returns ``False`` otherwise.
-        """
-        return (self.displayOpts.getParent() is None                or
-                not self.displayOpts.isSyncedToParent('volume')     or
-                not self.displayOpts.isSyncedToParent('resolution') or
-                not self.displayOpts.isSyncedToParent('interpolation'))
-        
-
-    def refreshImageTexture(self):
-
-        opts     = self.displayOpts
-        texName  = self.texName
-        unsynced = self.testUnsynced()
-
-        if unsynced:
-            texName = '{}_unsync_{}'.format(texName, id(opts))
-
-        if self.imageTexture is not None:
-            glresources.delete(self.imageTexture.getTextureName())
-
-        if opts.interpolation == 'none': interp = gl.GL_NEAREST
-        else:                            interp = gl.GL_LINEAR
-
-        # The image texture may be used elsewhere,
-        # so we'll use the resource management
-        # module rather than creating one directly
-        self.imageTexture = glresources.get(
-            texName, 
-            textures.ImageTexture,
-            texName,
-            self.image,
-            interp=interp) 
-
-    
-    def refreshColourTexture(self):
-        """Configures the colour texture used to colour image voxels."""
-
-        display = self.display
-        opts    = self.displayOpts
-
-        alpha  = display.alpha / 100.0
-        cmap   = opts.cmap
-        invert = opts.invert
-        dmin   = opts.displayRange[0]
-        dmax   = opts.displayRange[1]
-
-        self.colourTexture.set(cmap=cmap,
-                               invert=invert,
-                               alpha=alpha,
-                               displayRange=(dmin, dmax))
-
-        
     def addDisplayListeners(self):
-        """Called by :meth:`init`.
+        """Called by :meth:`__init__`.
 
         Adds a bunch of listeners to the :class:`.Display` object, and the
         associated :class:`.VolumeOpts` instance, which define how the image
@@ -267,9 +243,71 @@ class GLVolume(globject.GLImageObject):
             opts.removeSyncChangeListener('interpolation', lName)
 
         
+    def testUnsynced(self):
+        """Used by the :meth:`refreshImageTexture` method.
+        
+        Returns ``True`` if certain critical :class:`VolumeOpts` properties
+        have been unsynced from the parent instance, meaning that this
+        ``GLVolume`` instance needs to create its own image texture;
+        returns ``False`` otherwise.
+        """
+        return (self.displayOpts.getParent() is None                or
+                not self.displayOpts.isSyncedToParent('volume')     or
+                not self.displayOpts.isSyncedToParent('resolution') or
+                not self.displayOpts.isSyncedToParent('interpolation'))
+        
+
+    def refreshImageTexture(self):
+        """Refreshes the :class:`.ImageTexture` used to store the
+        :class:`.Image` data. This is performed through the :mod:`.resources`
+        module, so the image texture can be shared between multiple
+        ``GLVolume`` instances.
+        """
+
+        opts     = self.displayOpts
+        texName  = self.texName
+        unsynced = self.testUnsynced()
+
+        if unsynced:
+            texName = '{}_unsync_{}'.format(texName, id(opts))
+
+        if self.imageTexture is not None:
+            glresources.delete(self.imageTexture.getTextureName())
+
+        if opts.interpolation == 'none': interp = gl.GL_NEAREST
+        else:                            interp = gl.GL_LINEAR
+
+        self.imageTexture = glresources.get(
+            texName, 
+            textures.ImageTexture,
+            texName,
+            self.image,
+            interp=interp) 
+
+    
+    def refreshColourTexture(self):
+        """Refreshes the :class:`.ColourMapTexture` used to colour image
+        voxels.
+        """
+
+        display = self.display
+        opts    = self.displayOpts
+        alpha   = display.alpha / 100.0
+        cmap    = opts.cmap
+        invert  = opts.invert
+        dmin    = opts.displayRange[0]
+        dmax    = opts.displayRange[1]
+
+        self.colourTexture.set(cmap=cmap,
+                               invert=invert,
+                               alpha=alpha,
+                               displayRange=(dmin, dmax))
+
+        
     def preDraw(self):
-        """Sets up the GL state to draw a slice from this :class:`GLVolume`
-        instance.
+        """Binds the :class:`.ImageTexture` to ``GL_TEXTURE0`` and the
+        :class:`.ColourMapTexture` to ``GL_TEXTURE1, and calls the
+        version-dependent ``preDraw`` function.
         """
         
         # Set up the image and colour textures
@@ -280,30 +318,32 @@ class GLVolume(globject.GLImageObject):
 
         
     def draw(self, zpos, xform=None):
-        """Draws a 2D slice of the image at the given real world Z location.
-        This is performed via a call to the OpenGL version-dependent `draw`
-        function, contained in one of the :mod:`.gl14` or :mod:`.gl21`
-        packages.
+        """Draws a 2D slice of the image at the given Z location in the
+        display coordinate system.
+        
+     .  This is performed via a call to the OpenGL version-dependent ``draw``
+        function, contained in one of the :mod:`.gl14.glvolume_funcs` or
+        :mod:`.gl21.glvolume_funcs` modules.
 
-        If `xform` is not None, it is applied as an affine transformation to
-        the vertex coordinates of the rendered image data.
+        If ``xform`` is not ``None``, it is applied as an affine
+        transformation to the vertex coordinates of the rendered image data.
 
-        Note: Calls to this method must be preceded by a call to
-        :meth:`preDraw`, and followed by a call to :meth:`postDraw`.
+        .. note:: Calls to this method must be preceded by a call to
+                  :meth:`preDraw`, and followed by a call to :meth:`postDraw`.
         """
         
         fslgl.glvolume_funcs.draw(self, zpos, xform)
 
         
     def drawAll(self, zposes, xforms):
-        """Calls the module-specific ``drawAll`` function. """
+        """Calls the version dependent ``drawAll`` function. """
         
         fslgl.glvolume_funcs.drawAll(self, zposes, xforms)
 
         
     def postDraw(self):
-        """Clears the GL state after drawing from this :class:`GLVolume`
-        instance.
+        """Unbinds the ``ImageTexture`` and ``ColourMapTexture``, and calls the
+        version-dependent ``postDraw`` function.
         """
 
         self.imageTexture .unbindTexture()
diff --git a/fsl/fsleyes/gl/shaders.py b/fsl/fsleyes/gl/shaders.py
index 256f795b84da97e175d940f01328d57875895f71..9bf0c72356496c5323bb3c383e6a7d93d10b63fc 100644
--- a/fsl/fsleyes/gl/shaders.py
+++ b/fsl/fsleyes/gl/shaders.py
@@ -50,6 +50,15 @@ the OpenGL version specific packages, i.e. :mod:`.gl14`
 When a shader file is loaded, a simple preprocessor is applied to the source -
 any lines of the form '#pragma include filename', will be replaced with the
 contents of the specified file.
+
+
+**Resources**
+
+
+ - http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_program.txt
+ - http://oss.sgi.com/projects/ogl-sample/registry/ARB/fragment_program.txt
+ - http://www.renderguild.com/gpuguide.pdf
+ - https://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf
 """