diff --git a/fsl/fsleyes/gl/gl14/gllabel_frag.prog b/fsl/fsleyes/gl/gl14/gllabel_frag.prog
index b5d2b3b373c3e523380c02be947043332e822c61..ffd480db9976a6154c491da774d2664586118aa3 100644
--- a/fsl/fsleyes/gl/gl14/gllabel_frag.prog
+++ b/fsl/fsleyes/gl/gl14/gllabel_frag.prog
@@ -29,27 +29,24 @@ TEMP  lutCoord;
 TEMP  invNumLabels;
 TEMP  voxValue;
 
-PARAM imageShape    = program.local[4];
-MOV   invNumLabels,   program.local[5];
-PARAM outline       = program.local[6];
+PARAM imageShape    = {{ param_imageShape   }};
+MOV   invNumLabels,   {{ param_invNumLabels }};
+PARAM outline       = {{ param_outline      }};
 
 # This matrix scales the voxel value to
 # lie in a range which is appropriate to
 # the current display range 
-PARAM voxValXform[4] = { program.local[0],
-                         program.local[1],
-                         program.local[2],
-                         program.local[3] };
+PARAM voxValXform[4] = {{ param4_voxValXform }};
 
 # retrieve the voxel coordinates,
 # bail if they are are out of bounds  
-MOV voxCoord, fragment.texcoord[1];
+MOV voxCoord, {{ varying_voxCoord }};
 
 #pragma include test_in_bounds.prog
 
 # look up image voxel value
 # from 3D image texture
-TEX voxValue, fragment.texcoord[0], texture[0], 3D;
+TEX voxValue, {{ varying_texCoord }}, {{ texture_imageTexture }}, 3D;
 
 # Scale the texture value
 # to its original voxel value
@@ -63,7 +60,7 @@ MUL lutCoord, lutCoord, invNumLabels;
 
 # look up the appropriate colour
 # in the 1D colour map texture
-TEX result.color, lutCoord.x, texture[1], 1D;
+TEX result.color, lutCoord.x, {{ texture_lutTexture }}, 1D;
 
 
 # Test whether this fragment lies 
@@ -74,7 +71,7 @@ TEMP val;
 TEMP tol;
 TEMP offsets;
 
-MOV coord, fragment.texcoord[0];
+MOV coord, {{ varying_texCoord }};
 MOV val,   voxValue;
 MOV tol,   invNumLabels.x;
 MUL tol,   tol, 0.001;
diff --git a/fsl/fsleyes/gl/gl14/gllabel_funcs.py b/fsl/fsleyes/gl/gl14/gllabel_funcs.py
index ed60037950cd92482cc4d8ba244c6821f1168eff..22b9f92aa07c3577b45aede040775c370a6c8ebe 100644
--- a/fsl/fsleyes/gl/gl14/gllabel_funcs.py
+++ b/fsl/fsleyes/gl/gl14/gllabel_funcs.py
@@ -14,24 +14,18 @@ defined in the :mod:`.gl14.glvolume_funcs` are re-used by this module.
 """
 
 
-import numpy                          as np
-
-import OpenGL.GL                      as gl
-import OpenGL.raw.GL._types           as gltypes
-import OpenGL.GL.ARB.fragment_program as arbfp
-import OpenGL.GL.ARB.vertex_program   as arbvp
+import numpy                  as np
 
 import fsl.fsleyes.gl.shaders as shaders
-
-import glvolume_funcs
+import                           glvolume_funcs
 
 
 def init(self):
     """Calls the :func:`compileShaders` and :func:`updateShaderState`
     functions.
     """
-    self.vertexProgram   = None
-    self.fragmentProgram = None
+
+    self.shader = None
     
     compileShaders(   self)
     updateShaderState(self) 
@@ -39,48 +33,39 @@ def init(self):
 
 def destroy(self):
     """Deletes handles to the vertex/fragment shader programs. """
-    arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
-    arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
+    self.shader.delete()
+    self.shader = None
 
 
 def compileShaders(self):
     """Compiles the vertex and fragment shader programs used to render
     :class:`.GLLabel` instances.
     """
-    if self.vertexProgram is not None:
-        arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
+    if self.shader is not None:
+        self.shader.delete()
         
-    if self.fragmentProgram is not None:
-        arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
-
-    vertShaderSrc = shaders.getVertexShader(  'glvolume')
-    fragShaderSrc = shaders.getFragmentShader('gllabel')
+    vertSrc  = shaders.getVertexShader(  'glvolume')
+    fragSrc  = shaders.getFragmentShader('gllabel')
+    textures = {
+        'imageTexture' : 0,
+        'lutTexture'   : 1,
+    }
 
-    vertexProgram, fragmentProgram = shaders.compilePrograms(
-        vertShaderSrc, fragShaderSrc)
-
-    self.vertexProgram   = vertexProgram
-    self.fragmentProgram = fragmentProgram 
+    self.shader = shaders.ARBPShader(vertSrc, fragSrc, textures) 
 
 
 def updateShaderState(self):
     """Updates all shader program variables. """
     
     opts = self.displayOpts
-
+    
     # enable the vertex and fragment programs
-    gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-
-    arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                           self.vertexProgram)
-    arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                           self.fragmentProgram)
+    self.shader.load()
 
     voxValXform  = self.imageTexture.voxValXform
-    shape        = list(self.image.shape[:3])
+    shape        = list(self.image.shape[:3]) + [0]
     offsets      = opts.outlineWidth / \
-                   np.array(self.image.shape[:3], dtype=np.float32)    
+                   np.array(self.image.shape[:3], dtype=np.float32)
     invNumLabels = 1.0 / (opts.lut.max() + 1)
 
     if opts.transform == 'affine':
@@ -92,14 +77,13 @@ def updateShaderState(self):
     if opts.outline: offsets = [1] + list(offsets)
     else:            offsets = [0] + list(offsets)
 
-    shaders.setVertexProgramVector(  0, shape + [0])
-    shaders.setFragmentProgramMatrix(0, voxValXform)
-    shaders.setFragmentProgramVector(4, shape + [0])
-    shaders.setFragmentProgramVector(5, [invNumLabels, 0, 0, 0])
-    shaders.setFragmentProgramVector(6, offsets)
+    self.shader.setVertParam('imageShape',   shape)
+    self.shader.setFragParam('imageShape',   shape)
+    self.shader.setFragParam('voxValXform',  voxValXform)
+    self.shader.setFragParam('invNumLabels', [invNumLabels, 0, 0, 0])
+    self.shader.setFragParam('outline',      offsets)
 
-    gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB) 
+    self.shader.unload()
 
 
 preDraw  = glvolume_funcs.preDraw
diff --git a/fsl/fsleyes/gl/gl14/glmodel_frag.prog b/fsl/fsleyes/gl/gl14/glmodel_frag.prog
index 2959110730fef376b9fec605b38cb9a193a98b40..a645f8de9c6bad363ac956a9ef61ece4f13d8747 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_frag.prog
+++ b/fsl/fsleyes/gl/gl14/glmodel_frag.prog
@@ -19,11 +19,11 @@ TEMP output;
 # edge detection tolerance is
 # 1 / 255 - a colour change of 1 bit
 PARAM tol     = 0.00392156862745098;
-PARAM offsets = program.local[0];
+PARAM offsets = {{ param_offsets }};
 
-MOV coord, fragment.texcoord[0];
+MOV coord, {{ varying_texCoord }};
 
-TEX val, coord, texture[0], 2D;
+TEX val, coord, {{ texture_renderTexture }}, 2D;
 
 #pragma include edge2D.prog
 
diff --git a/fsl/fsleyes/gl/gl14/glmodel_funcs.py b/fsl/fsleyes/gl/gl14/glmodel_funcs.py
index 5feb43e56bc4ce2c7178c8e65cf033e3953b5c6b..a57db666f8fd71e151457e48e0210d554a526e2c 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glmodel_funcs.py
@@ -9,12 +9,7 @@ class to render :class:`.Model` overlays in an OpenGL 1.4 compatible manner.
 """
 
 
-import OpenGL.GL                      as gl
-import OpenGL.raw.GL._types           as gltypes
-import OpenGL.GL.ARB.fragment_program as arbfp
-import OpenGL.GL.ARB.vertex_program   as arbvp
-
-import fsl.fsleyes.gl.shaders         as shaders
+import fsl.fsleyes.gl.shaders as shaders
 
 
 def compileShaders(self):
@@ -23,22 +18,20 @@ def compileShaders(self):
     instance.
     """
     
-    vertShaderSrc = shaders.getVertexShader(  'glmodel')
-    fragShaderSrc = shaders.getFragmentShader('glmodel')
+    vertSrc  = shaders.getVertexShader(  'glmodel')
+    fragSrc  = shaders.getFragmentShader('glmodel')
 
-    vertexProgram, fragmentProgram = shaders.compilePrograms(
-        vertShaderSrc, fragShaderSrc)
+    textures = {'renderTexture' : 0}
 
-    self.vertexProgram   = vertexProgram
-    self.fragmentProgram = fragmentProgram    
+    self.shader = shaders.ARBPShader(vertSrc, fragSrc, textures)
 
 
 def destroy(self):
     """Deletes the vertex/fragment shader programs that were compiled by
     :func:`compileShaders`.
-    """    
-    arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
-    arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
+    """
+    self.shader.delete()
+    self.shader = None
 
 
 def updateShaders(self):
@@ -48,22 +41,15 @@ def updateShaders(self):
     offsets = self.getOutlineOffsets()
     
     loadShaders(self)
-    shaders.setFragmentProgramVector(0, list(offsets) + [0, 0])
+    self.shader.setFragParam('offsets', list(offsets) + [0, 0])
     unloadShaders(self)
 
 
 def loadShaders(self):
     """Loads the :class:`.GLModel` vertex/fragment shader programs. """
-    gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-
-    arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                           self.vertexProgram)
-    arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                           self.fragmentProgram)    
-
+    self.shader.load()
 
+    
 def unloadShaders(self):
     """Un-loads the :class:`.GLModel` vertex/fragment shader programs. """
-    gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)     
+    self.shader.unload() 
diff --git a/fsl/fsleyes/gl/gl14/glmodel_vert.prog b/fsl/fsleyes/gl/gl14/glmodel_vert.prog
index 0b38e89499a62c5fde60f6b652a662a90d1dcb67..f7a6a421ccb34da3e0b8392ed800ae10d799b8b3 100644
--- a/fsl/fsleyes/gl/gl14/glmodel_vert.prog
+++ b/fsl/fsleyes/gl/gl14/glmodel_vert.prog
@@ -20,6 +20,6 @@ DP4 result.position.z, state.matrix.mvp.row[2], vertex.position;
 DP4 result.position.w, state.matrix.mvp.row[3], vertex.position;
 
 # Copy the vertex texture coordinate
-MOV result.texcoord[0], vertex.texcoord[0];
+MOV {{ varying_texCoord }}, {{ attr_texCoord }};
 
 END
diff --git a/fsl/fsleyes/gl/gl14/glvolume_frag.prog b/fsl/fsleyes/gl/gl14/glvolume_frag.prog
index 03afb3a4eaa51f6be1f9435f5abd1ed5409ba567..621d8bfc38542da4d3aa679539c4482bf1b7badf 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_frag.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_frag.prog
@@ -66,29 +66,26 @@ TEMP  useNegCmap;
 TEMP  negVoxValue;
 TEMP  negClipValue;
 
-PARAM imageShape = program.local[4];
-PARAM clipping   = program.local[5];
-PARAM negCmap    = program.local[6];
+PARAM imageShape = {{ param_imageShape }};
+PARAM clipping   = {{ param_clipping   }};
+PARAM negCmap    = {{ param_negCmap    }};
   
 # This matrix scales the voxel value to
 # lie in a range which is appropriate to
 # the current display range 
-PARAM voxValXform[4] = { program.local[0],
-                         program.local[1],
-                         program.local[2],
-                         program.local[3] };
+PARAM voxValXform[4] = {{ param4_voxValXform }};
 
 # retrieve the voxel coordinates,
 # bail if they are are out of bounds  
-MOV voxCoord, fragment.texcoord[1];
+MOV voxCoord, {{ varying_voxCoord }};
 
 #pragma include test_in_bounds.prog
 
 # look up image voxel value
 # and clipping value from 3D
 # image/clipping textures
-TEX voxValue.x,  fragment.texcoord[0], texture[0], 3D;
-TEX clipValue.x, fragment.texcoord[0], texture[3], 3D;
+TEX voxValue.x,  {{ varying_texCoord }}, {{ texture_imageTexture }}, 3D;
+TEX clipValue.x, {{ varying_texCoord }}, {{ texture_clipTexture  }}, 3D;
 
 # If the image texture is the clip
 # texture, overwrite the clip value
@@ -165,12 +162,12 @@ MAD voxValue, voxValue, voxValXform[0].x, voxValXform[3].x;
 
 # look up the appropriate colour
 # in the 1D colour map texture
-TEX posColour, voxValue.x, texture[1], 1D;
-TEX negColour, voxValue.x, texture[2], 1D;
+TEX posColour, voxValue.x, {{ texture_colourTexture    }}, 1D;
+TEX negColour, voxValue.x, {{ texture_negColourTexture }}, 1D;
 
 # useNegCmap is negative if the
 # negative colour map should be
 # used, positive otherwise.
 CMP result.color, useNegCmap.x, negColour, posColour;
 
-END
\ No newline at end of file
+END
diff --git a/fsl/fsleyes/gl/gl14/glvolume_funcs.py b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
index 98202e938eb3616363915dab15132ce2934b8a4e..af9bf226b8ccfaec74cb4acbd7fd3e331fdd55cc 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
@@ -11,11 +11,8 @@ class to render :class:`.Image` overlays in an OpenGL 1.4 compatible manner.
 
 import logging
 
-import numpy                          as np
-import OpenGL.GL                      as gl
-import OpenGL.raw.GL._types           as gltypes
-import OpenGL.GL.ARB.fragment_program as arbfp
-import OpenGL.GL.ARB.vertex_program   as arbvp
+import numpy                  as np
+import OpenGL.GL              as gl
 
 import fsl.utils.transform    as transform
 import fsl.fsleyes.gl.shaders as shaders
@@ -27,8 +24,7 @@ log = logging.getLogger(__name__)
 def init(self):
     """Calls :func:`compileShaders` and :func:`updateShaderState`."""
 
-    self.vertexProgram   = None
-    self.fragmentProgram = None
+    self.shader = None
     
     compileShaders(   self)
     updateShaderState(self)
@@ -37,8 +33,8 @@ def init(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))
+    self.shader.delete()
+    self.shader = None
 
 
 def compileShaders(self):
@@ -49,20 +45,19 @@ def compileShaders(self):
     ``framgentProgram`` respectively.
     """
 
-    if self.vertexProgram is not None:
-        arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
-        
-    if self.fragmentProgram is not None:
-        arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
-
-    vertShaderSrc = shaders.getVertexShader(  'glvolume')
-    fragShaderSrc = shaders.getFragmentShader('glvolume')
+    if self.shader is not None:
+        self.shader.delete()
 
-    vertexProgram, fragmentProgram = shaders.compilePrograms(
-        vertShaderSrc, fragShaderSrc)
+    vertSrc  = shaders.getVertexShader(  'glvolume')
+    fragSrc  = shaders.getFragmentShader('glvolume')
+    textures = {
+        'imageTexture'     : 0,
+        'colourTexture'    : 1,
+        'negColourTexture' : 2,
+        'clipTexture'      : 3
+    }
 
-    self.vertexProgram   = vertexProgram
-    self.fragmentProgram = fragmentProgram    
+    self.shader = shaders.ARBPShader(vertSrc, fragSrc, textures)
 
     
 def updateShaderState(self):
@@ -70,13 +65,7 @@ def updateShaderState(self):
     opts = self.displayOpts
 
     # enable the vertex and fragment programs
-    gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-
-    arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                           self.vertexProgram)
-    arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                           self.fragmentProgram)
+    self.shader.load()
 
     # The voxValXform transformation turns
     # an image texture value into a raw
@@ -104,16 +93,19 @@ def updateShaderState(self):
     clipLo  = opts.clippingRange[0] * clipXform[0, 0] + clipXform[3, 0]
     clipHi  = opts.clippingRange[1] * clipXform[0, 0] + clipXform[3, 0]
     texZero = 0.0                   * imgXform[ 0, 0] + imgXform[ 3, 0]
-    
-    shaders.setVertexProgramVector(  0, shape + [0])
-    
-    shaders.setFragmentProgramMatrix(0, voxValXform)
-    shaders.setFragmentProgramVector(4, shape + [0])
-    shaders.setFragmentProgramVector(5, [clipLo, clipHi, invClip, imageIsClip])
-    shaders.setFragmentProgramVector(6, [useNegCmap, texZero, 0, 0])
 
-    gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB) 
+
+    shape    = shape + [0]
+    clipping = [clipLo, clipHi, invClip, imageIsClip]
+    negCmap  = [useNegCmap, texZero, 0, 0]
+
+    self.shader.setVertParam('imageShape',  shape)
+    self.shader.setFragParam('voxValXform', voxValXform)
+    self.shader.setFragParam('imageShape',  shape)
+    self.shader.setFragParam('clipping',    clipping)
+    self.shader.setFragParam('negCmap',     negCmap)
+    
+    self.shader.unload()
 
 
 def preDraw(self):
@@ -121,34 +113,18 @@ def preDraw(self):
 
     # enable drawing from a vertex array
     gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
-
-    gl.glClientActiveTexture(gl.GL_TEXTURE0)
-    gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
-
-    # enable the vertex and fragment programs
-    gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-
-    arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                           self.vertexProgram)
-    arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                           self.fragmentProgram)
+    self.shader.load()
 
 
 def draw(self, zpos, xform=None):
     """Draws a slice of the image at the given Z location. """
     
     vertices, voxCoords, texCoords = self.generateVertices(zpos, xform)
-    
-    # Tex coords are texture 0 coords
-    # Vox coords are texture 1 coords
-    vertices  = np.array(vertices,  dtype=np.float32).ravel('C')
-    texCoords = np.array(texCoords, dtype=np.float32).ravel('C')
 
-    gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices)
+    self.shader.setAttr('texCoord', texCoords)
 
-    gl.glClientActiveTexture(gl.GL_TEXTURE0)
-    gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texCoords)
+    vertices = np.array(vertices,  dtype=np.float32).ravel('C')
+    gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices)
     
     gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
 
@@ -168,14 +144,11 @@ def drawAll(self, zposes, xforms):
         vertices[ i * 6: i * 6 + 6, :] = v
         texCoords[i * 6: i * 6 + 6, :] = tc
 
-    vertices  = vertices .ravel('C')
-    texCoords = texCoords.ravel('C')
+    self.shader.setAttr('texCoord', texCoords)
 
+    vertices = vertices.ravel('C')
+    
     gl.glVertexPointer(3, gl.GL_FLOAT, 0, vertices)
-
-    gl.glClientActiveTexture(gl.GL_TEXTURE0)
-    gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texCoords)
-
     gl.glDrawArrays(gl.GL_TRIANGLES, 0, nslices * 6) 
 
 
@@ -183,10 +156,5 @@ def postDraw(self):
     """Cleans up the GL state after drawing from the given :class:`.GLVolume`
     instance.
     """
-
     gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
-    gl.glClientActiveTexture(gl.GL_TEXTURE0)
-    gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
-
-    gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-    gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB)
+    self.shader.unload()
diff --git a/fsl/fsleyes/gl/gl14/glvolume_vert.prog b/fsl/fsleyes/gl/gl14/glvolume_vert.prog
index 4bcce0edbfda57e4c5713f4f8d350aa417402271..c79622486fc5ab3de1b01eecc83a2e359fc73f02 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_vert.prog
+++ b/fsl/fsleyes/gl/gl14/glvolume_vert.prog
@@ -6,17 +6,18 @@
 # passes the corresponding voxel and texture coordinates through to the
 # fragment program.
 #
-# Inputs:
-#    vertex.texcoord[0] - Texture coordinates
-#    program.local[0]   - image shape
-#
+# Input parameters:
+#    imageShape    - image shape
+# 
+# Input attributes:
+#    texCoord - Texture coordinates
 #
 # Outputs:
 #    result.texcoord[0] - Texture coordinates
 #    result.texcoord[1] - Voxel coordinates
 #
 
-PARAM imageShape = program.local[0];
+PARAM imageShape = {{ param_imageShape }};
 
 TEMP voxCoord;
 
@@ -26,12 +27,12 @@ DP4 result.position.y, state.matrix.mvp.row[1], vertex.position;
 DP4 result.position.z, state.matrix.mvp.row[2], vertex.position;
 DP4 result.position.w, state.matrix.mvp.row[3], vertex.position;
 
-MOV result.texcoord[0], vertex.texcoord[0];
-
-# Transform the texture coordinates into voxel coordinates
-MOV voxCoord, vertex.texcoord[0];
+# Transform the texture coordinates
+# into voxel coordinates
+MOV voxCoord, {{ attr_texCoord }};
 MUL voxCoord, voxCoord, imageShape;
 
-MOV result.texcoord[1], voxCoord;
+MOV {{ varying_texCoord }}, {{ attr_texCoord }};
+MOV {{ varying_voxCoord }}, voxCoord;
 
-END
\ No newline at end of file
+END
diff --git a/fsl/fsleyes/gl/shaders/__init__.py b/fsl/fsleyes/gl/shaders/__init__.py
index 9aa640e70e9ab7f1c24d17482e4f22017fcf232c..47dd75c03704ea571afd125c231960ac4b728b07 100644
--- a/fsl/fsleyes/gl/shaders/__init__.py
+++ b/fsl/fsleyes/gl/shaders/__init__.py
@@ -7,127 +7,19 @@
 
 import logging
 
-import os.path            as op
+import os.path        as op
 
-import fsl.fsleyes.gl     as fslgl
+import fsl.fsleyes.gl as fslgl
 
-import glsl.parse         as glslparse
-import glsl.program       as glslprogram
-import arbp.parse         as arbpparse
-import arbp.program       as arbpprogram
+import glsl.program   as glslprogram
+import arbp.program   as arbpprogram
 
 
 log = logging.getLogger(__name__)
 
 
 GLSLShader = glslprogram.GLSLShader
-ARBPShaer  = arbpprogram.ARBPShader
-parseGLSL  = glslparse  .parseGLSL
-parseARBP  = arbpparse  .parseARBP
-
-
-
-def setVertexProgramVector(index, vector):
-    """Convenience function which sets the vertex program local parameter
-    at the given index to the given 4 component vector.
-    """
-    import OpenGL.GL.ARB.vertex_program as arbvp
-    
-    arbvp.glProgramLocalParameter4fARB(
-        arbvp.GL_VERTEX_PROGRAM_ARB, index, *vector) 
-
-
-def setVertexProgramMatrix(index, matrix):
-    """Convenience function which sets four vertex program local parameters,
-    starting at the given index, to the given ``4*4`` matrix.
-    """ 
-    
-    import OpenGL.GL.ARB.vertex_program as arbvp
-    
-    for i, row in enumerate(matrix):
-        arbvp.glProgramLocalParameter4fARB(
-            arbvp.GL_VERTEX_PROGRAM_ARB, i + index,
-            row[0], row[1], row[2], row[3])    
-
-        
-def setFragmentProgramVector(index, vector):
-    """Convenience function which sets the fragment program local parameter
-    at the given index to the given 4 component vector.
-    """    
-    
-    import OpenGL.GL.ARB.fragment_program as arbfp
-    
-    arbfp.glProgramLocalParameter4fARB(
-        arbfp.GL_FRAGMENT_PROGRAM_ARB, index, *vector) 
-
-
-def setFragmentProgramMatrix(index, matrix):
-    """Convenience function which sets four fragment program local parameters,
-    starting at the given index, to the given ``4*4`` matrix.
-    """ 
-    
-    import OpenGL.GL.ARB.fragment_program as arbfp
-    
-    for i, row in enumerate(matrix):
-        arbfp.glProgramLocalParameter4fARB(
-            arbfp.GL_FRAGMENT_PROGRAM_ARB, i + index,
-            row[0], row[1], row[2], row[3])
-
-
-def compilePrograms(vertexProgramSrc, fragmentProgramSrc):
-    """Compiles the given vertex and fragment programs (written according
-    to the ``ARB_vertex_program`` and ``ARB_fragment_program`` extensions),
-    and returns references to the compiled programs.
-    """
-    
-    import OpenGL.GL                      as gl
-    import OpenGL.GL.ARB.fragment_program as arbfp
-    import OpenGL.GL.ARB.vertex_program   as arbvp
-    
-    gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
-    gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-    
-    fragmentProgram = arbfp.glGenProgramsARB(1)
-    vertexProgram   = arbvp.glGenProgramsARB(1) 
-
-    # vertex program
-    try:
-        arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                               vertexProgram)
-
-        arbvp.glProgramStringARB(arbvp.GL_VERTEX_PROGRAM_ARB,
-                                 arbvp.GL_PROGRAM_FORMAT_ASCII_ARB,
-                                 len(vertexProgramSrc),
-                                 vertexProgramSrc)
-
-    except:
-
-        position = gl.glGetIntegerv(arbvp.GL_PROGRAM_ERROR_POSITION_ARB)
-        message  = gl.glGetString(  arbvp.GL_PROGRAM_ERROR_STRING_ARB)
-
-        raise RuntimeError('Error compiling vertex program '
-                           '({}): {}'.format(position, message)) 
-
-    # fragment program
-    try:
-        arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                               fragmentProgram)
-
-        arbfp.glProgramStringARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
-                                 arbfp.GL_PROGRAM_FORMAT_ASCII_ARB,
-                                 len(fragmentProgramSrc),
-                                 fragmentProgramSrc)
-    except:
-        position = gl.glGetIntegerv(arbfp.GL_PROGRAM_ERROR_POSITION_ARB)
-        message  = gl.glGetString(  arbfp.GL_PROGRAM_ERROR_STRING_ARB)
-
-        raise RuntimeError('Error compiling fragment program '
-                           '({}): {}'.format(position, message))
-
-    gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB)
-    gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
-    
-    return vertexProgram, fragmentProgram
+ARBPShader = arbpprogram.ARBPShader
 
 
 def getVertexShader(prefix):
diff --git a/fsl/fsleyes/gl/shaders/arbp/parse.py b/fsl/fsleyes/gl/shaders/arbp/parse.py
index 7d3bb8f337d59a89bbcef7baf2b892399a075e5d..ea094e6a2186165988bf274ca14311e47f9b7497 100644
--- a/fsl/fsleyes/gl/shaders/arbp/parse.py
+++ b/fsl/fsleyes/gl/shaders/arbp/parse.py
@@ -50,31 +50,37 @@ def parseARBP(vertSrc, fragSrc):
             'varying'   : vVaryings}
 
 
-def fillARBP(vertSrc, fragSrc, vertParams, fragParams, textures, attrs):
+def fillARBP(vertSrc,
+             fragSrc,
+             vertParams,
+             vertParamLens,
+             fragParams,
+             fragParamLens,
+             textures,
+             attrs):
     
-    if vertParams   is None: vertParams = {}
-    if fragParams   is None: fragParams = {}
-    if textures     is None: textures   = {}
-    if attrs        is None: attrs      = {}
-
     vertVars = _findDeclaredVariables(vertSrc)
     fragVars = _findDeclaredVariables(fragSrc)
     
     _checkVariableValidity(
         vertVars, fragVars, vertParams, fragParams, textures, attrs)
 
-    for name, (number, length) in vertParams.items():
+    for name, number in vertParams.items():
+        
+        length = vertParamLens[name]
         
         if length == 1: name = 'param_{}'  .format(name)
         else:           name = 'param{}_{}'.format(name, length)
         
         vertParams[name] = _param(number, length)
         
-    for name, (number, length) in fragParams.items():
+    for name, number in fragParams.items():
+
+        length = fragParamLens[name]
         
         if length == 1: name = 'param_{}'  .format(name)
-        else:           name = 'param{}_{}'.format(name, length)
-        
+        else:           name = 'param{}_{}'.format(length, name)
+
         fragParams[name] = _param(number, length)
     
     textures = {'texture_{}'.format(n) : _texture(v)
@@ -195,7 +201,7 @@ def _param(number, length):
         bits = ['program.local[{}]'.format(n) for n in range(number,
                                                              number + length)]
 
-        return '{{ {} }}'.format(','.join(bits))
+        return '{{ {} }}'.format(', '.join(bits))
 
 
 def _texture(number):
diff --git a/fsl/fsleyes/gl/shaders/arbp/program.py b/fsl/fsleyes/gl/shaders/arbp/program.py
index 6c0386250299a73724374ea2f4989979b45b0f81..6d20a3abb67239453f15f1d9c90257fb40b96e6e 100644
--- a/fsl/fsleyes/gl/shaders/arbp/program.py
+++ b/fsl/fsleyes/gl/shaders/arbp/program.py
@@ -7,35 +7,175 @@
 
 import logging
 
+import numpy                          as np
+
 import OpenGL.GL                      as gl
 import OpenGL.raw.GL._types           as gltypes
 import OpenGL.GL.ARB.fragment_program as arbfp
 import OpenGL.GL.ARB.vertex_program   as arbvp
 
+import                                   parse
+
 
 log = logging.getLogger(__name__)
 
 
 class ARBPShader(object):
     
-    def __init__(self,
-                 vertSrc,
-                 fragSrc,
-                 paramMap=None,
-                 textureMap=None,
-                 vertAttMap=None):
+    def __init__(self, vertSrc, fragSrc, textureMap=None):
+
+        decs = parse.parseARBP(vertSrc, fragSrc)
+
+        vParams = decs['vertParam']
+        fParams = decs['fragParam']
+
+        if len(vParams) > 0: vParams, vLens = zip(*vParams)
+        else:                vParams, vLens = [], []
+        if len(fParams) > 0: fParams, fLens = zip(*fParams)
+        else:                fParams, fLens = [], []
+
+        vLens = {name : length for name, length in zip(vParams, vLens)}
+        fLens = {name : length for name, length in zip(fParams, fLens)}
+
+        self.vertParams    = vParams
+        self.vertParamLens = vLens
+        self.fragParams    = fParams
+        self.fragParamLens = fLens
+        self.textures      = decs['texture']
+        self.attrs         = decs['attr']
+
+        poses = self.__generatePositions(textureMap)
+        vpPoses, fpPoses, texPoses, attrPoses = poses
+
+        self.vertParamPositions = vpPoses
+        self.fragParamPositions = fpPoses
+        self.texturePositions   = texPoses
+        self.attrPositions      = attrPoses
+
+        vertSrc, fragSrc = parse.fillARBP(vertSrc,
+                                          fragSrc,
+                                          self.vertParamPositions,
+                                          self.vertParamLens,
+                                          self.fragParamPositions,
+                                          self.fragParamLens,
+                                          self.texturePositions,
+                                          self.attrPositions)
+
+        vp, fp = self.__compile(vertSrc, fragSrc)
+        
+        self.vertexProgram   = vp
+        self.fragmentProgram = fp
+        
 
-        self.__vertexProgram   = None
-        self.__fragmentProgram = None
+    def delete(self):
+        arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
+        arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram))
 
-        if textureMap is None: textureMap = {}
-        if paramMap   is None: paramMap   = {}
-        if vertAttMap is None: vertAttMap = {}
 
+    def load(self):
+        gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
+        gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
 
-    def delete(self):
-        arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.__vertexProgram))
-        arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.__fragmentProgram)) 
+        arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
+                               self.vertexProgram)
+        arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
+                               self.fragmentProgram)
+
+        for attr in self.attrs:
+            texUnit = self.__getAttrTexUnit(attr)
+
+            gl.glClientActiveTexture(texUnit)
+            gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY) 
+
+
+    def unload(self):
+        gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
+        gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB)
+
+        for attr in self.attrs:
+            texUnit = self.__getAttrTexUnit(attr)
+
+            gl.glClientActiveTexture(texUnit)
+            gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
+
+
+    def setVertParam(self, name, value):
+
+        pos   = self.vertParamPositions[name]
+        value = np.array(value, dtype=np.float32).reshape((-1, 4))
+        
+        for i, row in enumerate(value):
+            arbvp.glProgramLocalParameter4fARB(
+                arbvp.GL_VERTEX_PROGRAM_ARB, pos + i,
+                row[0], row[1], row[2], row[3]) 
+
+    
+    def setFragParam(self, name, value):
+
+        pos   = self.fragParamPositions[name]
+        value = np.array(value, dtype=np.float32).reshape((-1, 4))
+    
+        for i, row in enumerate(value):
+            arbfp.glProgramLocalParameter4fARB(
+                arbfp.GL_FRAGMENT_PROGRAM_ARB, pos + i,
+                row[0], row[1], row[2], row[3]) 
+
+
+    def setAttr(self, name, value):
+
+        texUnit = self.__getAttrTexUnit(name)
+        size    = value.shape[1]
+        value   = np.array(value, dtype=np.float32)
+        value   = value.ravel('C')
+        
+        gl.glClientActiveTexture(texUnit)
+        gl.glTexCoordPointer(size, gl.GL_FLOAT, 0, value)
+
+
+    def __getAttrTexUnit(self, attr):
+
+        pos     = self.attrPositions[attr]
+        texUnit = 'GL_TEXTURE{}'.format(pos)
+        texUnit = getattr(gl, texUnit)
+
+        return texUnit
+
+
+    def __generatePositions(self, textureMap=None):
+
+        vpPoses   = {}
+        fpPoses   = {}
+        texPoses  = {}
+        attrPoses = {}
+
+        # Vertex parameters
+        pos = 0
+        for name in self.vertParams:
+            vpPoses[name]  = pos
+            pos           += self.vertParamLens[name]
+
+        # Fragment parameters
+        pos = 0
+        for name in self.fragParams:
+            fpPoses[name]  = pos
+            pos           += self.fragParamLens[name]
+
+        # Vertex attributes
+        for i, name in enumerate(self.attrs):
+            attrPoses[name]  = i
+            
+        # Texture positions. If the caller did
+        # not provide a texture map in __init__,
+        # we'll generate some positions.
+        if textureMap is None:
+
+            names    = self.textures
+            poses    = range(len(names))
+            texPoses = {n : p for n, p in zip(names, poses)}
+        else:
+            texPoses = dict(textureMap)
+        
+        return vpPoses, fpPoses, texPoses, attrPoses
 
 
     def __compile(self, vertSrc, fragSrc):
@@ -47,7 +187,12 @@ class ARBPShader(object):
         gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
 
         fragProg = arbfp.glGenProgramsARB(1)
-        vertProg = arbvp.glGenProgramsARB(1) 
+        vertProg = arbvp.glGenProgramsARB(1)
+
+        # Make sure the source is plain
+        # ASCII - not unicode
+        vertSrc = str(vertSrc)
+        fragSrc = str(fragSrc)
 
         # vertex program
         try: