diff --git a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
index 00d52f972f268c1b8a2e4b4b7c0abf38db3242fc..3f7a72d5cb3cb83e9c87773f2e588ad004a67050 100644
--- a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
@@ -16,11 +16,8 @@ by this module.
 """
 
 
-import OpenGL.GL            as gl
-import OpenGL.raw.GL._types as gltypes
-
-import                         glvolume_funcs
-import                         glvector_funcs
+import glvolume_funcs
+import glvector_funcs
 
 
 def init(self):
@@ -29,20 +26,18 @@ def init(self):
     information.
     """
 
-    self.shaders = None
+    self.shader = None
 
     compileShaders(   self)
     updateShaderState(self)
 
-    self.vertexAttrBuffer = gl.glGenBuffers(1)
-
 
 def destroy(self):
     """Destroys the vertex buffer and vertex/fragment shaders created
     in :func:`init`.
     """
-    gl.glDeleteBuffers(1, gltypes.GLuint(self.vertexAttrBuffer))
-    gl.glDeleteProgram(self.shaders)
+    self.shader.delete()
+    self.shader = None
 
     
 def compileShaders(self):
@@ -50,23 +45,18 @@ def compileShaders(self):
     :class:`.GLRGBVector` instances. Stores references to the shader
     programs, and to all shader variables on the ``GLRGBVector`` instance.
     """
-
-    vertUniforms = []
-    vertAtts     = ['vertex', 'voxCoord', 'texCoord']
-
-    glvector_funcs.compileShaders(self, vertAtts, vertUniforms)
+    self.shader = glvector_funcs.compileShaders(self)
 
 
 def updateShaderState(self):
     """Updates all shader program variables. """
 
-    opts = self.displayOpts
-
+    opts      = self.displayOpts
     useSpline = opts.interpolation == 'spline'
 
-    gl.glUseProgram(self.shaders)
+    self.shader.load()
     glvector_funcs.updateFragmentShaderState(self, useSpline=useSpline)
-    gl.glUseProgram(0) 
+    self.shader.unload()
 
 
 preDraw  = glvolume_funcs.preDraw
diff --git a/fsl/fsleyes/gl/gl21/glvector_funcs.py b/fsl/fsleyes/gl/gl21/glvector_funcs.py
index 30a4d1524e9f4efe62488f2d29c7412db045cf5f..1885af218cbf6d3abfa99ba09d2dd12c7d840a0d 100644
--- a/fsl/fsleyes/gl/gl21/glvector_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glvector_funcs.py
@@ -6,128 +6,89 @@
 #
 
 
-import OpenGL.GL as gl
-import numpy     as np
+import fsl.fsleyes.gl.shaders      as shaders
+import fsl.fsleyes.gl.glsl.program as glslprogram
+import fsl.utils.transform         as transform
 
-import fsl.fsleyes.gl.shaders as shaders
-import fsl.utils.transform    as transform
 
+def compileShaders(self):
 
-def compileShaders(self, vertAtts, vertUniforms):
-
-    if self.shaders is not None:
-        gl.glDeleteProgram(self.shaders) 
+    if self.shader is not None:
+        self.shader.delete()
 
     opts                = self.displayOpts
     useVolumeFragShader = opts.colourImage is not None
 
-    if useVolumeFragShader:
-        fragShader   = 'GLVolume'
-        fragUniforms = ['imageTexture',     'clipTexture', 'colourTexture',
-                        'negColourTexture', 'imageIsClip', 'useNegCmap',
-                        'imageShape',       'useSpline',   'img2CmapXform',
-                        'clipLow',          'clipHigh',    'texZero',
-                        'invertClip'] 
-    else:
-        fragShader   = self
-        fragUniforms = ['imageTexture',   'modulateTexture', 'clipTexture',
-                        'clipLow',        'clipHigh',        'xColourTexture',
-                        'yColourTexture', 'zColourTexture',  'voxValXform',
-                        'cmapXform',      'imageShape',      'useSpline'] 
-
-    vertShaderSrc = shaders.getVertexShader(  self)
-    fragShaderSrc = shaders.getFragmentShader(fragShader)
+    if useVolumeFragShader: fragShader = 'GLVolume'
+    else:                   fragShader = self
+
+    vertSrc = shaders.getVertexShader(  self)
+    fragSrc = shaders.getFragmentShader(fragShader)
     
-    self.shaders    = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
-    self.shaderVars = shaders.getShaderVars(self.shaders,
-                                            vertAtts,
-                                            vertUniforms,
-                                            fragUniforms) 
+    return glslprogram.ShaderProgram(vertSrc, fragSrc)
 
     
-def updateFragmentShaderState(
-        self,
-        useSpline=False):
+def updateFragmentShaderState(self, useSpline=False):
     """
     """
     opts                = self.displayOpts
-    svars               = self.shaderVars
+    shader              = self.shader
     useVolumeFragShader = opts.colourImage is not None
 
+    invClipValXform = self.clipTexture  .invVoxValXform
+    clippingRange   = opts.clippingRange
+    imageShape      = self.vectorImage.shape[:3]
+
+    # Transform the clip threshold into
+    # the texture value range, so the
+    # fragment shader can compare texture
+    # values directly to it.    
+    if opts.clipImage is not None:
+        clipLow  = clippingRange[0] * \
+            invClipValXform[0, 0] + invClipValXform[3, 0]
+        clipHigh = clippingRange[1] * \
+            invClipValXform[0, 0] + invClipValXform[3, 0]
+    else:
+        clipLow  = 0
+        clipHigh = 1
+
     if useVolumeFragShader:
 
         voxValXform     = self.colourTexture.voxValXform
         invVoxValXform  = self.colourTexture.invVoxValXform
-        invClipValXform = self.clipTexture  .invVoxValXform
-        clippingRange   = opts.clippingRange
-        imageShape      = self.vectorImage.shape[:3]
-        imageShape      = np.array(imageShape, dtype=np.float32)
         texZero         = 0.0 * invVoxValXform[0, 0] + invVoxValXform[3, 0]
-
-        img2CmapXform = transform.concat(
+        img2CmapXform   = transform.concat(
             voxValXform,
             self.cmapTexture.getCoordinateTransform())
-        img2CmapXform = np.array(img2CmapXform, dtype=np.float32).ravel('C') 
-        
-        if opts.clipImage is not None:
-            clipLow  = clippingRange[0] * \
-                invClipValXform[0, 0] + invClipValXform[3, 0]
-            clipHigh = clippingRange[1] * \
-                invClipValXform[0, 0] + invClipValXform[3, 0]
-        else:
-            clipLow  = 0
-            clipHigh = 1
- 
-        gl.glUniform1i(svars['imageTexture'],      3)
-        gl.glUniform1i(svars['clipTexture'],       2)
-        gl.glUniform1i(svars['colourTexture'],     7)
-        gl.glUniform1i(svars['negColourTexture'],  7)
-        
-        gl.glUniformMatrix4fv(svars['img2CmapXform'], 1, False, img2CmapXform)
-        gl.glUniform3fv(      svars['imageShape'],  1,          imageShape)
-        
-        gl.glUniform1i(svars['imageIsClip'], False)
-        gl.glUniform1i(svars['useNegCmap'],  False)
-        gl.glUniform1i(svars['useSpline'],   useSpline)
-        gl.glUniform1f(svars['clipLow'],     clipLow)
-        gl.glUniform1f(svars['clipHigh'],    clipHigh)
-        gl.glUniform1f(svars['texZero'],     texZero)
-        gl.glUniform1i(svars['invertClip'],  False)
+
+        shader.set('imageTexture',     3)
+        shader.set('clipTexture',      2)
+        shader.set('colourTexture',    7)
+        shader.set('negColourTexture', 7)
+        shader.set('img2CmapXform',    img2CmapXform)
+        shader.set('imageShape',       imageShape)
+        shader.set('imageIsClip',      False)
+        shader.set('useNegCmap',       False)
+        shader.set('useSpline',        useSpline)
+        shader.set('clipLow',          clipLow)
+        shader.set('clipHigh',         clipHigh)
+        shader.set('texZero',          texZero)
+        shader.set('invertClip',       False)
     
     else:
 
-        # The coordinate transformation matrices for 
-        # each of the three colour textures are identical
-        voxValXform     = self.imageTexture.voxValXform
-        invClipValXform = self.clipTexture .invVoxValXform
-        cmapXform       = self.xColourTexture.getCoordinateTransform()
-        imageShape      = np.array(self.vectorImage.shape, dtype=np.float32)
-        clippingRange   = opts.clippingRange
-
-        # Transform the clip threshold into
-        # the texture value range, so the
-        # fragment shader can compare texture
-        # values directly to it.
-        if opts.clipImage is not None:
-            clipLow  = clippingRange[0] * \
-                invClipValXform[0, 0] + invClipValXform[3, 0]
-            clipHigh = clippingRange[1] * \
-                invClipValXform[0, 0] + invClipValXform[3, 0]
-        else:
-            clipLow  = 0
-            clipHigh = 1
-
-        gl.glUniform1i(svars['imageTexture'],    0)
-        gl.glUniform1i(svars['modulateTexture'], 1)
-        gl.glUniform1i(svars['clipTexture'],     2)
-        gl.glUniform1i(svars['xColourTexture'],  4)
-        gl.glUniform1i(svars['yColourTexture'],  5)
-        gl.glUniform1i(svars['zColourTexture'],  6)
-        
-        gl.glUniformMatrix4fv(svars['voxValXform'], 1, False, voxValXform)
-        gl.glUniformMatrix4fv(svars['cmapXform'],   1, False, cmapXform)
-        
-        gl.glUniform3fv(svars['imageShape'], 1, imageShape)
-        gl.glUniform1f( svars['clipLow'],       clipLow)
-        gl.glUniform1f( svars['clipHigh'],      clipHigh) 
-        gl.glUniform1f( svars['useSpline'],     useSpline)
+        voxValXform = self.imageTexture.voxValXform
+        cmapXform   = self.xColourTexture.getCoordinateTransform()
+
+        shader.set('imageTexture',    0)
+        shader.set('modulateTexture', 1)
+        shader.set('clipTexture',     2)
+        shader.set('xColourTexture',  4)
+        shader.set('yColourTexture',  5)
+        shader.set('zColourTexture',  6)
+        shader.set('voxValXform',     voxValXform)
+        shader.set('cmapXform',       cmapXform)
+        shader.set('imageShape',      imageShape)
+        shader.set('clipLow',         clipLow)
+        shader.set('clipHigh',        clipHigh) 
+        shader.set('useSpline',       useSpline)