diff --git a/fsl/fsleyes/gl/gl14/glvolume_frag.prog b/fsl/fsleyes/gl/gl14/glvolume_frag.prog
index a0113aefcfb0483920d60ed0078b791781030e93..fef6ec1d5aba88ad5d8d8e207bc129b0f4d7f42c 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] = {{ param.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,8 +162,8 @@ 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
diff --git a/fsl/fsleyes/gl/gl14/glvolume_vert.prog b/fsl/fsleyes/gl/gl14/glvolume_vert.prog
index 699313d05826b50ee87460878f0db1001e28ca1e..6ff967913f7b8a7f46c0009dc03ae5f864c26a78 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
diff --git a/fsl/fsleyes/gl/shaders/__init__.py b/fsl/fsleyes/gl/shaders/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2af0f27d56753ae1ed16ef6eabd4b3bb5a10f8b8
--- /dev/null
+++ b/fsl/fsleyes/gl/shaders/__init__.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+#
+# __init__.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import glsl.parse   as glslparse
+import glsl.program as glslprogram
+import arbp.parse   as arbpparse
+import arbp.program as arbpprogram
+
+
+GLSLShader = glslprogram.GLSLShader
+ARBShaer   = arbpprogram.ARBShader
+parseGLSL  = glslparse  .parseGLSL
+parseARBP  = arbpparse  .parseARBP
diff --git a/fsl/fsleyes/gl/glsl/__init__.py b/fsl/fsleyes/gl/shaders/arbp/__init__.py
similarity index 100%
rename from fsl/fsleyes/gl/glsl/__init__.py
rename to fsl/fsleyes/gl/shaders/arbp/__init__.py
diff --git a/fsl/fsleyes/gl/shaders/arbp/parse.py b/fsl/fsleyes/gl/shaders/arbp/parse.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7a7bf84595c48ce00f2c592f14032a86e8c7f7d
--- /dev/null
+++ b/fsl/fsleyes/gl/shaders/arbp/parse.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# parse.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+
+import jinja2
+
+#
+# {{ params.paramName }} -> program.local[X]
+#
+# Or, if 'paramName' has length > 1
+#
+# {{ params.paramName }} -> { program.local[x], program.local[x + 1], ... }
+
+# {{ attrs.attName }}     -> vertex.texcoord[N]
+#
+# {{ textures.texName }} -> texture[N]
+
+
+# Maybe this too? In vertex program:
+# {{ varying.outputName }} -> result.texcoord[N]
+#
+# In fragment program:
+# {{ varying.outputName }} -> fragment.texcoord[N]
+
+
+def parseARBP(source,
+              vert,
+              paramMap=None,
+              textureMap=None,
+              attrMap=None,
+              varyingMap=None):
+
+    if paramMap   is None: paramMap   = {}
+    if textureMap is None: textureMap = {}
+    if attrMap    is None: attrMap    = {}
+    if varyingMap is None: varyingMap = {}
+
+    params   = {}
+    textures = {}
+    attrs    = {}
+    varyings = {}
+
+    for name, num in paramMap  .items(): params[  name] = _param(  num)
+    for name, num in textureMap.items(): textures[name] = _texture(num)
+    for name, num in attrMap   .items(): attrs[   name] = _attr(   num)
+    for name, num in varyingMap.items(): varyings[name] = _varying(num, vert)
+
+    template  = jinja2.Template(source)
+    parsedSrc = template.render(param=params,
+                                texture=textures,
+                                attr=attrs,
+                                varying=varyings)
+
+    return parsedSrc
+                                    
+
+def _param(number):
+
+    try:    number, length = number
+    except: number, length = number, 1
+    
+    if length == 1: 
+        return 'program.local[{}]'.format(number)
+    else:
+        bits = ['program.local[{}]'.format(n) for n in range(number,
+                                                             number + length)]
+
+        return '{{ {} }}'.format(','.join(bits))
+
+
+def _texture(number):
+    return 'texture[{}]'.format(number)
+
+
+def _attr(number):
+    return 'vertex.texcoord[{}]'.format(number)
+
+
+def _varying(number, vert):
+    if vert: return 'result.texcoord[{}]'  .format(number)
+    else:    return 'fragment.texcoord[{}]'.format(number)
diff --git a/fsl/fsleyes/gl/shaders/arbp/program.py b/fsl/fsleyes/gl/shaders/arbp/program.py
new file mode 100644
index 0000000000000000000000000000000000000000..fce3acad7ae0a5fcb7a9fe41349ce8b3c7dc1232
--- /dev/null
+++ b/fsl/fsleyes/gl/shaders/arbp/program.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+#
+# program.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+import logging
+log = logging.getLogger(__name__)
+
+
+class ARBProgram(object):
+    
+    def __init__(self,
+                 vertSrc,
+                 fragSrc,
+                 paramMap=None,
+                 textureMap=None,
+                 vertAttMap=None):
+
+        if textureMap is None: textureMap = {}
+        if paramMap   is None: paramMap   = {}
+        if vertAttMap is None: vertAttMap = {}
diff --git a/fsl/fsleyes/gl/shaders/glsl/__init__.py b/fsl/fsleyes/gl/shaders/glsl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/fsl/fsleyes/gl/glsl/parse.py b/fsl/fsleyes/gl/shaders/glsl/parse.py
similarity index 100%
rename from fsl/fsleyes/gl/glsl/parse.py
rename to fsl/fsleyes/gl/shaders/glsl/parse.py
diff --git a/fsl/fsleyes/gl/glsl/program.py b/fsl/fsleyes/gl/shaders/glsl/program.py
similarity index 95%
rename from fsl/fsleyes/gl/glsl/program.py
rename to fsl/fsleyes/gl/shaders/glsl/program.py
index 60ee3b04aeeb54a7403fbed5ba88292eccdc7480..0537fa386fbb9c1fcb092ddb353641159716e257 100644
--- a/fsl/fsleyes/gl/glsl/program.py
+++ b/fsl/fsleyes/gl/shaders/glsl/program.py
@@ -4,7 +4,7 @@
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
-"""This module provides he :class:`ShaderProgram` class, which encapsulates
+"""This module provides he :class:`GLSLShader` class, which encapsulates
 a GLSL shader program comprising a vertex shader and a fragment shader.
 """
 
@@ -35,14 +35,14 @@ corresponding GL types and sizes.
 """
 
 
-class ShaderProgram(object):
-    """The ``ShaderProgram`` class encapsulates information and logic about
+class GLSLShader(object):
+    """The ``GLSLShader`` class encapsulates information and logic about
     a GLSL 1.20 shader program, comprising a vertex shader and a fragment
     shader. It provides methods to set shader attribute and uniform values,
     to configure attributes, and to load/unload the program. Furthermore,
-    the ``ShaderProgram`` makes sure that all uniform and attribut variables
+    the ``GLSLShader`` makes sure that all uniform and attribut variables
     are converted to the appropriate type. The following methods are available
-    on a ``ShaderProgram``:
+    on a ``GLSLShader``:
 
     
     .. autosummary::
@@ -58,13 +58,13 @@ class ShaderProgram(object):
        setIndices
 
 
-    Typical usage of a ``ShaderProgram`` will look something like the
+    Typical usage of a ``GLSLShader`` will look something like the
     following::
 
         vertSrc = 'vertex shader source'
         fragSrc = 'vertex shader source'
 
-        program = ShaderProgram(vertSrc, fragSrc)
+        program = GLSLShader(vertSrc, fragSrc)
 
         # Load the program
         program.load()
@@ -97,7 +97,7 @@ class ShaderProgram(object):
     
 
     def __init__(self, vertSrc, fragSrc, indexed=False):
-        """Create a ``ShaderProgram``.
+        """Create a ``GLSLShader``.
 
         :arg vertSrc: String containing vertex shader source code.
         
@@ -166,7 +166,7 @@ class ShaderProgram(object):
 
             
     def load(self):
-        """Loads this ``ShaderProgram`` into the GL state.
+        """Loads this ``GLSLShader`` into the GL state.
         """
         gl.glUseProgram(self.program)
 
@@ -224,7 +224,7 @@ class ShaderProgram(object):
 
 
     def delete(self):
-        """Deletes all GL resources managed by this ``ShaderProgram`. """
+        """Deletes all GL resources managed by this ``GLSLShader`. """
         gl.glDeleteProgram(self.program)
 
         for buf in self.buffers.values():
@@ -283,7 +283,7 @@ class ShaderProgram(object):
 
 
     def setIndices(self, indices):
-        """If an index array is to be used by this ``ShaderProgram`` (see the
+        """If an index array is to be used by this ``GLSLShader`` (see the
         ``indexed`` argument to :meth:`__init__`), the index array may be set
         via this method.
         """