diff --git a/fsl/data/strings.py b/fsl/data/strings.py
index 67099b53b232a5b21702c3b8b972ba6142a81655..04bdea3a82524d24a984ccfd836c76b73f010698 100644
--- a/fsl/data/strings.py
+++ b/fsl/data/strings.py
@@ -539,8 +539,7 @@ choices = TypeDict({
     'SceneOpts.performance' : {1 : 'Fastest',
                                2 : 'Faster',
                                3 : 'Good looking',
-                               4 : 'Better looking',
-                               5 : 'Best looking'},
+                               4 : 'Best looking'},
 
     'CanvasOpts.zax' : {0 : 'X axis',
                         1 : 'Y axis',
diff --git a/fsl/fsleyes/controls/overlaydisplaypanel.py b/fsl/fsleyes/controls/overlaydisplaypanel.py
index 94bedcaed4d8b70811df9dafce3a3043274ba628..ff0cf0d2fb256cff2171dcd1272a4420bd1932ef 100644
--- a/fsl/fsleyes/controls/overlaydisplaypanel.py
+++ b/fsl/fsleyes/controls/overlaydisplaypanel.py
@@ -287,9 +287,7 @@ _DISPLAY_PROPS = td.TypeDict({
                      labels=strings.choices['VolumeOpts.interpolation']),
         props.Widget('cmap'),
         props.Widget('invert'),
-        props.Widget('invertClipping',
-                     enabledWhen=lambda o, sw: not sw,
-                     dependencies=[(lambda o: o.display, 'softwareMode')]),
+        props.Widget('invertClipping'),
         props.Widget('displayRange',
                      showLimits=False,
                      slider=True,
@@ -347,13 +345,8 @@ _DISPLAY_PROPS = td.TypeDict({
 
     'LabelOpts' : [
         props.Widget('lut', labels=lambda l: l.name),
-        props.Widget('outline',
-                     enabledWhen=lambda o, sw: not sw,
-                     dependencies=[(lambda o: o.display, 'softwareMode')]),
-        props.Widget('outlineWidth',
-                     showLimits=False,
-                     enabledWhen=lambda o, sw: not sw,
-                     dependencies=[(lambda o: o.display, 'softwareMode')]),
+        props.Widget('outline'),
+        props.Widget('outlineWidth', showLimits=False),
         # props.Widget('showNames'),
         props.Widget('resolution',   showLimits=False),
         props.Widget('volume',
diff --git a/fsl/fsleyes/controls/overlaydisplaytoolbar.py b/fsl/fsleyes/controls/overlaydisplaytoolbar.py
index 28e678899a00a7e25c47547140bf30b0f6b44bfc..a0d57cd921b060099e60d20caf0640744d782ea9 100644
--- a/fsl/fsleyes/controls/overlaydisplaytoolbar.py
+++ b/fsl/fsleyes/controls/overlaydisplaytoolbar.py
@@ -530,15 +530,11 @@ _TOOLBAR_PROPS = td.TypeDict({
             tooltip=_TOOLTIPS['LabelOpts.outline'],
             icon=[icons.findImageFile('outline24'),
                   icons.findImageFile('filled24')],
-            toggle=True,
-            enabledWhen=lambda i, sw: not sw,
-            dependencies=[(lambda o: o.display, 'softwareMode')]),
+            toggle=True),
         
         'outlineWidth' : props.Widget(
             'outlineWidth',
             tooltip=_TOOLTIPS['LabelOpts.outlineWidth'],
-            enabledWhen=lambda i, sw: not sw,
-            dependencies=[(lambda o: o.display, 'softwareMode')],
             showLimits=False,
             spin=False)},
 
diff --git a/fsl/fsleyes/displaycontext/canvasopts.py b/fsl/fsleyes/displaycontext/canvasopts.py
index 0266cae7abea8483514a837521273044a0425a5c..94a99d17c0d0364c58edebf791fc218cd623d0d4 100644
--- a/fsl/fsleyes/displaycontext/canvasopts.py
+++ b/fsl/fsleyes/displaycontext/canvasopts.py
@@ -91,13 +91,6 @@ class SliceCanvasOpts(props.HasProperties):
     """
     
     
-    softwareMode = props.Boolean(default=False)
-    """If ``True``, the :attr:`.Display.softwareMode` property for every
-    displayed overlay is set to ``True``. This will result in better
-    performance for slow systems (e.g. software based OpenGL renderers).
-    """
-
-    
     resolutionLimit = props.Real(default=0, minval=0, maxval=5, clamped=True)
     """The minimum resolution at which overlays should be drawn. A higher
     value will result in better performance.
diff --git a/fsl/fsleyes/displaycontext/display.py b/fsl/fsleyes/displaycontext/display.py
index cd21c97086fb8f56e151debdab24e76a4becd330..35d418ef70adf6e7ab0fe9a46acc13d709bd5df9 100644
--- a/fsl/fsleyes/displaycontext/display.py
+++ b/fsl/fsleyes/displaycontext/display.py
@@ -61,12 +61,6 @@ class Display(props.SyncableHasProperties):
     """Contrast - 50% is normal contrast."""
 
 
-    softwareMode = props.Boolean(default=False)
-    """If possible, optimise for a low-performance rendering environment
-    (e.g. a software-based OpenGL renderer).
-    """
-
-    
     def __init__(self,
                  overlay,
                  overlayList,
@@ -127,7 +121,7 @@ class Display(props.SyncableHasProperties):
 
             # These properties cannot be unbound, as
             # they affect the OpenGL representation 
-            nounbind=['softwareMode', 'overlayType'],
+            nounbind=['overlayType'],
 
             # Initial sync state between this
             # Display and the parent Display
diff --git a/fsl/fsleyes/displaycontext/sceneopts.py b/fsl/fsleyes/displaycontext/sceneopts.py
index 06527340afb04b367a504233f0b83c0db210d9e6..6ef975fe5c220dc66bdda2d1bdd0958e5aed990f 100644
--- a/fsl/fsleyes/displaycontext/sceneopts.py
+++ b/fsl/fsleyes/displaycontext/sceneopts.py
@@ -36,7 +36,6 @@ class SceneOpts(props.HasProperties):
     cursorColour    = copy.copy(canvasopts.SliceCanvasOpts.cursorColour)
     resolutionLimit = copy.copy(canvasopts.SliceCanvasOpts.resolutionLimit)
     renderMode      = copy.copy(canvasopts.SliceCanvasOpts.renderMode)
-    softwareMode    = copy.copy(canvasopts.SliceCanvasOpts.softwareMode)
 
     
     showColourBar = props.Boolean(default=False)
@@ -57,13 +56,13 @@ class SceneOpts(props.HasProperties):
     """ 
 
     
-    performance = props.Choice((1, 2, 3, 4, 5), default=5)
+    performance = props.Choice((1, 2, 3, 4), default=4)
     """User controllable performance setting.
 
-    This property is linked to the :attr:`renderMode`,
-    :attr:`resolutionLimit`, and :attr:`softwareMode` properties. Setting this
-    property to a low value will result in faster rendering time, at the cost
-    of reduced features, and poorer rendering quality.
+    This property is linked to the :attr:`renderMode` and
+    :attr:`resolutionLimit` properties. Setting this property to a low value
+    will result in faster rendering time, at the cost of increased memory
+    usage and poorer rendering quality.
 
     See the :meth:`__onPerformanceChange` method.
     """
@@ -73,8 +72,7 @@ class SceneOpts(props.HasProperties):
         """Create a ``SceneOpts`` instance.
 
         This method simply links the :attr:`performance` property to the
-        :attr:`renderMode`, :attr:`softwareMode`,  and :attr:`resolutionLimit`
-        properties.
+        :attr:`renderMode` and :attr:`resolutionLimit` properties.
         """
         
         name = '{}_{}'.format(type(self).__name__, id(self))
@@ -86,40 +84,29 @@ class SceneOpts(props.HasProperties):
     def __onPerformanceChange(self, *a):
         """Called when the :attr:`performance` property changes.
 
-        Changes the values of the :attr:`renderMode`, :attr:`softwareMode`
-        and :attr:`resolutionLimit` properties accoridng to the performance
+        Changes the values of the :attr:`renderMode` and and
+        :attr:`resolutionLimit` properties accoridng to the performance
         setting.
         """
 
-        if   self.performance == 5:
+        if   self.performance == 4:
             self.renderMode      = 'onscreen'
-            self.softwareMode    = False
-            self.resolutionLimit = 0
-            
-        elif self.performance == 4:
-            self.renderMode      = 'onscreen'
-            self.softwareMode    = True
             self.resolutionLimit = 0
 
         elif self.performance == 3:
             self.renderMode      = 'offscreen'
-            self.softwareMode    = True
             self.resolutionLimit = 0 
             
         elif self.performance == 2:
             self.renderMode      = 'prerender'
-            self.softwareMode    = True
             self.resolutionLimit = 0
 
         elif self.performance == 1:
             self.renderMode      = 'prerender'
-            self.softwareMode    = True
             self.resolutionLimit = 1
 
         log.debug('Performance settings changed: '
                   'renderMode={}, '
-                  'softwareMode={}, '
                   'resolutionLimit={}'.format(
                       self.renderMode,
-                      self.softwareMode,
                       self.resolutionLimit))
diff --git a/fsl/fsleyes/gl/gl14/gllabel_funcs.py b/fsl/fsleyes/gl/gl14/gllabel_funcs.py
index 60dbd45c01c82b22f4ab37d468aeeb10173658b3..d883bb00d3b31ec456d1bf299e886ecf8ddeb055 100644
--- a/fsl/fsleyes/gl/gl14/gllabel_funcs.py
+++ b/fsl/fsleyes/gl/gl14/gllabel_funcs.py
@@ -53,10 +53,8 @@ def compileShaders(self):
     if self.fragmentProgram is not None:
         arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
 
     vertexProgram, fragmentProgram = shaders.compilePrograms(
         vertShaderSrc, fragShaderSrc)
diff --git a/fsl/fsleyes/gl/gl14/gllinevector_funcs.py b/fsl/fsleyes/gl/gl14/gllinevector_funcs.py
index 8b274fcf21447ad3e190c510578550c258d997ed..1bb585acdc335ee0100263a0476aee5ccb5ed0be 100644
--- a/fsl/fsleyes/gl/gl14/gllinevector_funcs.py
+++ b/fsl/fsleyes/gl/gl14/gllinevector_funcs.py
@@ -95,10 +95,8 @@ def compileShaders(self):
     if self.fragmentProgram is not None:
         arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
 
     vertexProgram, fragmentProgram = shaders.compilePrograms(
         vertShaderSrc, fragShaderSrc)
@@ -173,9 +171,6 @@ def preDraw(self):
 
     gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
 
-    if self.display.softwareMode:
-        gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
-
     arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB,
                            self.vertexProgram)
     arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB,
@@ -187,18 +182,12 @@ def draw(self, zpos, xform=None):
     at the specified Z location.
     """
 
-    display             = self.display
     opts                = self.displayOpts
     vertices, texCoords = self.lineVertices.getVertices(zpos, self)
 
     if vertices.size == 0:
         return
 
-    if display.softwareMode:
-        texCoords = texCoords.ravel('C')
-        gl.glClientActiveTexture(gl.GL_TEXTURE0)
-        gl.glTexCoordPointer(3, gl.GL_FLOAT, 0, texCoords)
-
     vertices = vertices.ravel('C')
     v2d      = opts.getTransform('voxel', 'display')
 
@@ -228,6 +217,3 @@ def postDraw(self):
     gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
     
     gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
-
-    if self.display.softwareMode:
-        gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
diff --git a/fsl/fsleyes/gl/gl14/glrgbvector_funcs.py b/fsl/fsleyes/gl/gl14/glrgbvector_funcs.py
index 23142710680097e6e6f40ce10b398dec874f18a4..b420a1c31e6caca39dca56e07ea425d0f0fad26b 100644
--- a/fsl/fsleyes/gl/gl14/glrgbvector_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glrgbvector_funcs.py
@@ -55,10 +55,8 @@ def compileShaders(self):
     if self.fragmentProgram is not None:
         arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
 
     vertexProgram, fragmentProgram = shaders.compilePrograms(
         vertShaderSrc, fragShaderSrc)
diff --git a/fsl/fsleyes/gl/gl14/glvolume_funcs.py b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
index c6b789dcb087e9651bc348c21b2b3504b1a948b3..ea444b7099b9868b2f6c802a5481763e6454d2c4 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
@@ -55,10 +55,8 @@ def compileShaders(self):
     if self.fragmentProgram is not None:
         arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) 
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
 
     vertexProgram, fragmentProgram = shaders.compilePrograms(
         vertShaderSrc, fragShaderSrc)
diff --git a/fsl/fsleyes/gl/gl21/gllabel_funcs.py b/fsl/fsleyes/gl/gl21/gllabel_funcs.py
index 595906617d113da744cab04c63be81ce15c4ef64..086b0c0fa13e272c7a6eef41f0be854f8284da71 100644
--- a/fsl/fsleyes/gl/gl21/gllabel_funcs.py
+++ b/fsl/fsleyes/gl/gl21/gllabel_funcs.py
@@ -48,10 +48,8 @@ def compileShaders(self):
     if self.shaders is not None:
         gl.glDeleteProgram(self.shaders)
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
     self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
 
     self.vertexPos         = gl.glGetAttribLocation( self.shaders,
diff --git a/fsl/fsleyes/gl/gl21/gllinevector_funcs.py b/fsl/fsleyes/gl/gl21/gllinevector_funcs.py
index b2c026b48cb8aa41dd8e755a3d60aaaad5c351d4..b788a27dffc8991ad0593fa41e04624c2c14c16c 100644
--- a/fsl/fsleyes/gl/gl21/gllinevector_funcs.py
+++ b/fsl/fsleyes/gl/gl21/gllinevector_funcs.py
@@ -9,22 +9,14 @@ class to render :class:`.Image` overlays as line vector images in an OpenGL 2.1
 compatible manner.
 
 
-This module uses two different techniques to render a ``GLLineVector``. If
-the :attr:`.Display.softwareMode` (a.k.a. low performance) mode is enabled,
-a :class:`.GLLineVertices` instance is used to generate line vertices and
-texture coordinates for each voxel in the image. This is the same approach
-used by the :mod:`.gl14.gllinevector_funcs` module.
+This module uses two different techniques to render a ``GLLineVector``. The
+voxel coordinates for every vector are passed directly to a vertex shader
+program which calculates the position of the corresponding line vertices.
 
 
-If :attr:`.Display.softwareMode` is disabled, a ``GLLineVertices`` instance is
-not used. Instead, the voxel coordinates for every vector are passed directly
-to a vertex shader program which calculates the position of the corresponding
-line vertices.
-
-
-For both of the above techniques, a fragment shader (the same as that used by
-the :class:`.GLRGBVector` class) is used to colour each line according to the
-orientation of the underlying vector.
+A fragment shader (the same as that used by the :class:`.GLRGBVector` class)
+is used to colour each line according to the orientation of the underlying
+vector.
 """
 
 
@@ -35,9 +27,7 @@ import OpenGL.GL                   as gl
 import OpenGL.raw.GL._types        as gltypes
 
 import fsl.utils.transform         as transform
-import fsl.fsleyes.gl.resources    as glresources
 import fsl.fsleyes.gl.routines     as glroutines
-import fsl.fsleyes.gl.gllinevector as gllinevector
 import fsl.fsleyes.gl.shaders      as shaders
 
 
@@ -59,10 +49,6 @@ def init(self):
     self.vertexIDBuffer     = gl.glGenBuffers(1)
     self.lineVertices       = None
 
-    # False -> hardware shaders are in use
-    # True  -> software shaders are in use
-    self.swShadersInUse = False
-
     self._vertexResourceName = '{}_{}_vertices'.format(
         type(self).__name__, id(self.image))
 
@@ -70,7 +56,6 @@ def init(self):
 
     def vertexUpdate(*a):
         
-        updateVertices(self)
         self.updateShaderState()
         self.onUpdate()
 
@@ -99,28 +84,20 @@ def destroy(self):
     self.displayOpts.removeListener('resolution', name)
     self.displayOpts.removeListener('directed',   name)
 
-    if self.display.softwareMode:
-        glresources.delete(self._vertexResourceName)
-
 
 def compileShaders(self):
     """Compiles the vertex/fragment shaders, and stores references to all
-    shader variables as attributes of the :class:`.GLLineVector`. This
-    also results in a call to :func:`updateVertices`.
+    shader variables as attributes of the :class:`.GLLineVector`.
     """
     
     if self.shaders is not None:
         gl.glDeleteProgram(self.shaders)
     
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
     
     self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
 
-    self.swShadersInUse     = self.display.softwareMode
-
     self.vertexPos          = gl.glGetAttribLocation( self.shaders,
                                                       'vertex')
     self.vertexIDPos        = gl.glGetAttribLocation( self.shaders,
@@ -157,15 +134,12 @@ def compileShaders(self):
                                                       'voxelOffset') 
     self.cmapXformPos       = gl.glGetUniformLocation(self.shaders,
                                                       'cmapXform')
-    
-    updateVertices(self)
 
     
 def updateShaderState(self):
     """Updates all variables used by the vertex/fragment shaders. """
     
-    display = self.display
-    opts    = self.displayOpts
+    opts = self.displayOpts
 
     # The coordinate transformation matrices for 
     # each of the three colour textures are identical,
@@ -194,62 +168,30 @@ def updateShaderState(self):
     gl.glUniform1i(self.yColourTexturePos, 3)
     gl.glUniform1i(self.zColourTexturePos, 4)
 
-    if not display.softwareMode:
-        
-        directed  = opts.directed
-        imageDims = self.image.pixdim[:3]
-        d2vMat    = opts.getTransform('display', 'voxel')
-        v2dMat    = opts.getTransform('voxel',   'display')
-
-        # The shader adds these offsets to
-        # transformed voxel coordinates, so
-        # it can floor them to get integer
-        # voxel coordinates
-        offset    = [0.5, 0.5, 0.5]
-        
-        offset    = np.array(offset,    dtype=np.float32)
-        imageDims = np.array(imageDims, dtype=np.float32)
-        d2vMat    = np.array(d2vMat,    dtype=np.float32).ravel('C')
-        v2dMat    = np.array(v2dMat,    dtype=np.float32).ravel('C')
-
-        gl.glUniform1f( self.directedPos,       directed)
-        gl.glUniform3fv(self.imageDimsPos,   1, imageDims)
-        gl.glUniform3fv(self.voxelOffsetPos, 1, offset)
-        
-        gl.glUniformMatrix4fv(self.displayToVoxMatPos, 1, False, d2vMat)
-        gl.glUniformMatrix4fv(self.voxToDisplayMatPos, 1, False, v2dMat) 
+    directed  = opts.directed
+    imageDims = self.image.pixdim[:3]
+    d2vMat    = opts.getTransform('display', 'voxel')
+    v2dMat    = opts.getTransform('voxel',   'display')
 
-    gl.glUseProgram(0) 
+    # The shader adds these offsets to
+    # transformed voxel coordinates, so
+    # it can floor them to get integer
+    # voxel coordinates
+    offset    = [0.5, 0.5, 0.5]
 
+    offset    = np.array(offset,    dtype=np.float32)
+    imageDims = np.array(imageDims, dtype=np.float32)
+    d2vMat    = np.array(d2vMat,    dtype=np.float32).ravel('C')
+    v2dMat    = np.array(v2dMat,    dtype=np.float32).ravel('C')
 
-def updateVertices(self):
-    """If :attr:`.Display.softwareMode` is enabled, a :class:`.GLLineVertices`
-    instance is created/refreshed. Otherwise, this function does nothing.
-    """
+    gl.glUniform1f( self.directedPos,       directed)
+    gl.glUniform3fv(self.imageDimsPos,   1, imageDims)
+    gl.glUniform3fv(self.voxelOffsetPos, 1, offset)
 
-    image   = self.image
-    display = self.display
+    gl.glUniformMatrix4fv(self.displayToVoxMatPos, 1, False, d2vMat)
+    gl.glUniformMatrix4fv(self.voxToDisplayMatPos, 1, False, v2dMat) 
 
-    if not display.softwareMode:
-
-        self.lineVertices = None
-        
-        if glresources.exists(self._vertexResourceName):
-            log.debug('Clearing any cached line vertices for {}'.format(image))
-            glresources.delete(self._vertexResourceName)
-        return
-
-    if self.lineVertices is None:
-        self.lineVertices = glresources.get(
-            self._vertexResourceName, gllinevector.GLLineVertices, self)
-
-    if hash(self.lineVertices) != self.lineVertices.calculateHash(self):
-
-        log.debug('Re-generating line vertices for {}'.format(image))
-        self.lineVertices.refresh(self)
-        glresources.set(self._vertexResourceName,
-                        self.lineVertices,
-                        overwrite=True)
+    gl.glUseProgram(0) 
 
 
 def preDraw(self):
@@ -260,74 +202,11 @@ def preDraw(self):
 
 
 def draw(self, zpos, xform=None):
-    """Draws the line vectors at a plane at the specified Z location.
-    This is performed using either :func:`softwareDraw` or
-    :func:`hardwareDraw`, depending upon the value of
-    :attr:`.Display.softwareMode`.
-    """
-    if self.display.softwareMode: softwareDraw(self, zpos, xform)
-    else:                         hardwareDraw(self, zpos, xform)
-
-
-def softwareDraw(self, zpos, xform=None):
-    """Draws the line vectors at a plane at the specified Z location, using
-    a :class:`.GLLineVertices` instance.
-    """
-
-    # Software shaders have not yet been compiled - 
-    # we can't draw until they're updated
-    if not self.swShadersInUse:
-        return
-
-    opts                = self.displayOpts
-    vertices, texCoords = self.lineVertices.getVertices(zpos, self)
-
-    if vertices.size == 0:
-        return
-    
-    vertices  = vertices .ravel('C')
-    texCoords = texCoords.ravel('C')
-
-    v2d = opts.getTransform('voxel', 'display')
-
-    if xform is None: xform = v2d
-    else:             xform = transform.concat(v2d, xform)
- 
-    gl.glPushMatrix()
-    gl.glMultMatrixf(np.array(xform, dtype=np.float32).ravel('C'))
-
-    # upload the vertices
-    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexBuffer)
-    gl.glBufferData(
-        gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_STATIC_DRAW)
-    gl.glVertexAttribPointer(
-        self.vertexPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None)
-    gl.glEnableVertexAttribArray(self.vertexPos)
-
-    # and the texture coordinates
-    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.texCoordBuffer)
-    gl.glBufferData(
-        gl.GL_ARRAY_BUFFER, texCoords.nbytes, texCoords, gl.GL_STATIC_DRAW)
-    gl.glVertexAttribPointer(
-        self.texCoordPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None)
-    gl.glEnableVertexAttribArray(self.texCoordPos) 
-        
-    gl.glLineWidth(opts.lineWidth)
-    gl.glDrawArrays(gl.GL_LINES, 0, vertices.size / 3)
-
-    gl.glPopMatrix()
-    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
-    gl.glDisableVertexAttribArray(self.vertexPos)
-
-
-def hardwareDraw(self, zpos, xform=None):
     """Draws the line vectors at a plane at the specified Z location.
     Voxel coordinates are passed to the vertex shader, which calculates
     the corresponding line vertex locations.
     """ 
 
-    if self.swShadersInUse:
-        return
 
     image      = self.image
     opts       = self.displayOpts
diff --git a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
index bfee14303215b1a78ca49b12a04658b9600c4eba..de9009af559d77fcd6934293de8c4d39516e13e9 100644
--- a/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glrgbvector_funcs.py
@@ -55,10 +55,8 @@ def compileShaders(self):
     if self.shaders is not None:
         gl.glDeleteProgram(self.shaders) 
     
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
     
     self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
 
diff --git a/fsl/fsleyes/gl/gl21/glvolume_funcs.py b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
index bc9c35bbf0ecd7f9fa7c0aa1d10bbbed8e4363f2..009ad30bcee83fafb86dfbe7b8f3e0983d3d7210 100644
--- a/fsl/fsleyes/gl/gl21/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
@@ -53,10 +53,8 @@ def compileShaders(self):
     if self.shaders is not None:
         gl.glDeleteProgram(self.shaders)
 
-    vertShaderSrc = shaders.getVertexShader(  self,
-                                              sw=self.display.softwareMode)
-    fragShaderSrc = shaders.getFragmentShader(self,
-                                              sw=self.display.softwareMode)
+    vertShaderSrc = shaders.getVertexShader(  self)
+    fragShaderSrc = shaders.getFragmentShader(self)
     self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc)
 
     # indices of all vertex/fragment shader parameters
@@ -164,16 +162,9 @@ def _prepareVertexAttributes(self, vertices, voxCoords, texCoords):
         verPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 36, None)
     gl.glVertexAttribPointer(
         texPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 36, ctypes.c_void_p(24))
-
-    # The sw shader does not use voxel coordinates
-    # so attempting to binding would raise an error.
-    # So we only attempt to bind if softwareMode is
-    # false, and there is a shader uniform position
-    # for the voxel coordinates available.
-    if not self.display.softwareMode and voxPos != -1:
-        gl.glVertexAttribPointer(
-            voxPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 36, ctypes.c_void_p(12))
-        gl.glEnableVertexAttribArray(self.voxCoordPos)
+    gl.glVertexAttribPointer(
+        voxPos, 3, gl.GL_FLOAT, gl.GL_FALSE, 36, ctypes.c_void_p(12))
+    gl.glEnableVertexAttribArray(self.voxCoordPos)
 
     gl.glEnableVertexAttribArray(self.vertexPos)
     gl.glEnableVertexAttribArray(self.texCoordPos) 
@@ -223,11 +214,7 @@ def postDraw(self):
 
     gl.glDisableVertexAttribArray(self.vertexPos)
     gl.glDisableVertexAttribArray(self.texCoordPos)
-
-    # See comments in _prepareVertexAttributes
-    # about softwareMode/voxel coordinates
-    if not self.display.softwareMode and self.voxCoordPos != -1:
-        gl.glDisableVertexAttribArray(self.voxCoordPos)
+    gl.glDisableVertexAttribArray(self.voxCoordPos)
     
     gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
     gl.glUseProgram(0)
diff --git a/fsl/fsleyes/gl/gllabel.py b/fsl/fsleyes/gl/gllabel.py
index 7b67bef964d8b379bf015a21af969df398e0749f..e95017bac69f8efd075a51534e7fc31094caee0c 100644
--- a/fsl/fsleyes/gl/gllabel.py
+++ b/fsl/fsleyes/gl/gllabel.py
@@ -128,7 +128,6 @@ class GLLabel(globject.GLImageObject):
         display .addListener('alpha',        name, lutUpdate,     weak=False)
         display .addListener('brightness',   name, lutUpdate,     weak=False)
         display .addListener('contrast',     name, lutUpdate,     weak=False)
-        display .addListener('softwareMode', name, shaderCompile, weak=False)
         opts    .addListener('outline',      name, shaderUpdate,  weak=False)
         opts    .addListener('outlineWidth', name, shaderUpdate,  weak=False)
         opts    .addListener('lut',          name, lutChanged,    weak=False)
@@ -154,7 +153,6 @@ class GLLabel(globject.GLImageObject):
         display .removeListener(          'alpha',        name)
         display .removeListener(          'brightness',   name)
         display .removeListener(          'contrast',     name)
-        display .removeListener(          'softwareMode', name)
         opts    .removeListener(          'outline',      name)
         opts    .removeListener(          'outlineWidth', name)
         opts    .removeListener(          'lut',          name)
diff --git a/fsl/fsleyes/gl/gllinevector.py b/fsl/fsleyes/gl/gllinevector.py
index 06856ad85a8da44db97f0801a53610fff0ea1dc9..2a6e9b361ec375f270008abb6eefa7ac30be720d 100644
--- a/fsl/fsleyes/gl/gllinevector.py
+++ b/fsl/fsleyes/gl/gllinevector.py
@@ -8,11 +8,10 @@
 vector :class:`.Image` overlays in line mode.
 
 
-The :class:`.GLLineVertices` class is also defined in this module, and is
-used in certain rendering situations - when running in OpenGL 1.4, and
-when running in *software* (a.k.a. low performance) mode, in OpenGL 2.1. See
-the :mod:`.gl14.gllinevector_funcs` and :mod:`.gl21.gllinevector_funcs`
-modules for more details.
+The :class:`.GLLineVertices` class is also defined in this module, and is used
+in certain rendering situations - specifically, when running in OpenGL
+1.4. See the :mod:`.gl14.gllinevector_funcs` and
+:mod:`.gl21.gllinevector_funcs` modules for more details.
 """
 
 import logging
diff --git a/fsl/fsleyes/gl/glmask.py b/fsl/fsleyes/gl/glmask.py
index 6674298351897a9109dd12a64715b71c747ba4d9..ac8547e4ee532a61b9099f72b5bd8fbde1e87c8d 100644
--- a/fsl/fsleyes/gl/glmask.py
+++ b/fsl/fsleyes/gl/glmask.py
@@ -75,7 +75,6 @@ class GLMask(glvolume.GLVolume):
             fslgl.glvolume_funcs.updateShaderState(self) 
             self.onUpdate()
 
-        display.addListener('softwareMode',  name, shaderCompile, weak=False)
         display.addListener('alpha',         name, colourUpdate,  weak=False)
         display.addListener('brightness',    name, colourUpdate,  weak=False)
         display.addListener('contrast',      name, colourUpdate,  weak=False)
@@ -102,7 +101,6 @@ class GLMask(glvolume.GLVolume):
         opts    = self.displayOpts
         name    = self.name
         
-        display.removeListener(          'softwareMode',  name)
         display.removeListener(          'alpha',         name)
         display.removeListener(          'brightness',    name)
         display.removeListener(          'contrast',      name)
diff --git a/fsl/fsleyes/gl/glvector.py b/fsl/fsleyes/gl/glvector.py
index bb6564abbedf2112d78b0ecfcd9f07d4f4b29964..a6e361f3dad03dc12eb2e39288d49426274c5eb6 100644
--- a/fsl/fsleyes/gl/glvector.py
+++ b/fsl/fsleyes/gl/glvector.py
@@ -158,7 +158,6 @@ class GLVector(globject.GLImageObject):
             self.updateShaderState()
             self.onUpdate()
 
-        display.addListener('softwareMode',  name, shaderCompile, weak=False)
         display.addListener('alpha',         name, cmapUpdate,    weak=False)
         display.addListener('brightness',    name, cmapUpdate,    weak=False)
         display.addListener('contrast',      name, cmapUpdate,    weak=False)
@@ -186,7 +185,6 @@ class GLVector(globject.GLImageObject):
         opts    = self.displayOpts
         name    = self.name
 
-        display.removeListener('softwareMode', name)
         display.removeListener('alpha',        name)
         display.removeListener('brightness',   name)
         display.removeListener('contrast',     name)
diff --git a/fsl/fsleyes/gl/glvolume.py b/fsl/fsleyes/gl/glvolume.py
index b678b5534b458382ae7cd5ba9f66681b03acdd28..2608e926a05b55b6d61459a0c6e690f1cb9a9494 100644
--- a/fsl/fsleyes/gl/glvolume.py
+++ b/fsl/fsleyes/gl/glvolume.py
@@ -200,7 +200,6 @@ class GLVolume(globject.GLImageObject):
             fslgl.glvolume_funcs.updateShaderState(self)
             self.onUpdate()
 
-        display.addListener('softwareMode',   lName, shaderCompile, weak=False)
         display.addListener('alpha',          lName, colourUpdate,  weak=False)
         opts   .addListener('displayRange',   lName, colourUpdate,  weak=False)
         opts   .addListener('clippingRange',  lName, shaderUpdate,  weak=False)
@@ -231,7 +230,6 @@ class GLVolume(globject.GLImageObject):
 
         lName = self.name
         
-        display.removeListener(          'softwareMode',   lName)
         display.removeListener(          'alpha',          lName)
         opts   .removeListener(          'displayRange',   lName)
         opts   .removeListener(          'clippingRange',  lName)
diff --git a/fsl/fsleyes/gl/slicecanvas.py b/fsl/fsleyes/gl/slicecanvas.py
index 6d062a8ef4eb4fa582826bf814a219d2ae596a47..634288cc017d5681e4bff8be116ec98d83e0916b 100644
--- a/fsl/fsleyes/gl/slicecanvas.py
+++ b/fsl/fsleyes/gl/slicecanvas.py
@@ -71,11 +71,11 @@ class SliceCanvas(props.HasProperties):
     **Performance optimisations**
 
     
-    The :attr:`renderMode`, :attr:`softwareMode`, and :attr:`resolutionLimit`
-    properties control various ``SliceCanvas`` performance settings, which can
-    be useful when running in a low performance environment (e.g. when only a
-    software based GL driver is available). See also the
-    :attr:`.SceneOpts.performance` setting.
+    The :attr:`renderMode` and :attr:`resolutionLimit` properties control
+    various ``SliceCanvas`` performance settings, which can be useful when
+    running in a low performance environment (e.g. when only a software based
+    GL driver is available). See also the :attr:`.SceneOpts.performance`
+    setting.
 
     
     The :attr:`resolutionLimit` property controls the highest resolution at
@@ -85,13 +85,6 @@ class SliceCanvas(props.HasProperties):
     :class:`.Image` overlay is updated.
 
 
-    The :attr:`softwareMode` property controls the OpenGL shader program that
-    is used to render overlays - several :class:`.GLObject` types have shader
-    programs which are optimised for low-performance environments (at the cost
-    of a reduced feature set). This property is linked to the
-    :attr:`.Display.softwareMode` property.
-
-
     The :attr:`renderMode` property controls the way in which the
     ``SliceCanvas`` renders :class:`.GLObject` instances. It has three
     settings:
@@ -155,7 +148,6 @@ class SliceCanvas(props.HasProperties):
     cursorColour    = copy.copy(canvasopts.SliceCanvasOpts.cursorColour)
     bgColour        = copy.copy(canvasopts.SliceCanvasOpts.bgColour)
     renderMode      = copy.copy(canvasopts.SliceCanvasOpts.renderMode)
-    softwareMode    = copy.copy(canvasopts.SliceCanvasOpts.softwareMode)
     resolutionLimit = copy.copy(canvasopts.SliceCanvasOpts.resolutionLimit)
     
         
@@ -259,7 +251,6 @@ class SliceCanvas(props.HasProperties):
 
             disp.removeListener('overlayType',  self.name)
             disp.removeListener('enabled',      self.name)
-            disp.unbindProps(   'softwareMode', self)
 
             globj.destroy()
 
@@ -708,9 +699,6 @@ class SliceCanvas(props.HasProperties):
         if updateRenderTextures:
             self._updateRenderTextures() 
 
-        if not display.isBound('softwareMode', self):
-            display.bindProps('softwareMode', self)
-
         display.addListener('overlayType',
                             self.name,
                             self.__overlayTypeChanged,
diff --git a/fsl/fsleyes/views/lightboxpanel.py b/fsl/fsleyes/views/lightboxpanel.py
index 7ac448133b5a65dfbbb221ac93bab5c4ddb434fb..ebb620f3c9c0930af2abc94b31ea0f4a52fb5d78 100644
--- a/fsl/fsleyes/views/lightboxpanel.py
+++ b/fsl/fsleyes/views/lightboxpanel.py
@@ -101,7 +101,6 @@ class LightBoxPanel(canvaspanel.CanvasPanel):
         self.__lbCanvas.bindProps('showGridLines',   sceneOpts)
         self.__lbCanvas.bindProps('highlightSlice',  sceneOpts)
         self.__lbCanvas.bindProps('renderMode',      sceneOpts)
-        self.__lbCanvas.bindProps('softwareMode',    sceneOpts)
         self.__lbCanvas.bindProps('resolutionLimit', sceneOpts)
 
         # Bind these properties the other way around,
diff --git a/fsl/fsleyes/views/orthopanel.py b/fsl/fsleyes/views/orthopanel.py
index 25ca142f550b4effa1afd5c67b9266b4161f80eb..6d5aae39485b13f1d6f4437cc70032a413d38760 100644
--- a/fsl/fsleyes/views/orthopanel.py
+++ b/fsl/fsleyes/views/orthopanel.py
@@ -202,10 +202,6 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         self.__ycanvas.bindProps('renderMode',      sceneOpts)
         self.__zcanvas.bindProps('renderMode',      sceneOpts)
 
-        self.__xcanvas.bindProps('softwareMode',    sceneOpts)
-        self.__ycanvas.bindProps('softwareMode',    sceneOpts)
-        self.__zcanvas.bindProps('softwareMode',    sceneOpts)
-
         self.__xcanvas.bindProps('resolutionLimit', sceneOpts)
         self.__ycanvas.bindProps('resolutionLimit', sceneOpts)
         self.__zcanvas.bindProps('resolutionLimit', sceneOpts)