diff --git a/fsl/fsleyes/gl/gl14/glvolume_funcs.py b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
index 3b1852abe41467c12ba9d30965c61ee2615deb6d..8fe4900c9eaacdfaa52a846189815915b18c24d2 100644
--- a/fsl/fsleyes/gl/gl14/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl14/glvolume_funcs.py
@@ -106,6 +106,8 @@ def updateShaderState(self):
     
     self.shader.unload()
 
+    return True
+
 
 def preDraw(self):
     """Prepares to draw a slice from the given :class:`.GLVolume` instance. """
diff --git a/fsl/fsleyes/gl/gl21/glvolume_funcs.py b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
index 9fabd00017ab5ba6e3eaf95ab6cb2ce60101769b..0d437e29dc847c622fb622918ea63305ad5efcf1 100644
--- a/fsl/fsleyes/gl/gl21/glvolume_funcs.py
+++ b/fsl/fsleyes/gl/gl21/glvolume_funcs.py
@@ -83,24 +83,28 @@ def updateShaderState(self):
         self.colourTexture.getCoordinateTransform())
 
     shader.load()
+
+    changed = False
     
-    shader.set('useSpline',        opts.interpolation == 'spline')
-    shader.set('imageShape',       self.image.shape[:3])
-    shader.set('clipLow',          clipLow)
-    shader.set('clipHigh',         clipHigh)
-    shader.set('texZero',          texZero)
-    shader.set('invertClip',       opts.invertClipping)
-    shader.set('useNegCmap',       opts.useNegativeCmap)
-    shader.set('imageIsClip',      opts.clipImage is None)
-    shader.set('img2CmapXform',    img2CmapXform)
-
-    shader.set('imageTexture',     0)
-    shader.set('colourTexture',    1)
-    shader.set('negColourTexture', 2)
-    shader.set('clipTexture',      3)
+    changed |= shader.set('useSpline',        opts.interpolation == 'spline')
+    changed |= shader.set('imageShape',       self.image.shape[:3])
+    changed |= shader.set('clipLow',          clipLow)
+    changed |= shader.set('clipHigh',         clipHigh)
+    changed |= shader.set('texZero',          texZero)
+    changed |= shader.set('invertClip',       opts.invertClipping)
+    changed |= shader.set('useNegCmap',       opts.useNegativeCmap)
+    changed |= shader.set('imageIsClip',      opts.clipImage is None)
+    changed |= shader.set('img2CmapXform',    img2CmapXform)
+
+    changed |= shader.set('imageTexture',     0)
+    changed |= shader.set('colourTexture',    1)
+    changed |= shader.set('negColourTexture', 2)
+    changed |= shader.set('clipTexture',      3)
 
     shader.unload()
 
+    return changed
+
 
 def preDraw(self):
     """Sets up the GL state to draw a slice from the given :class:`.GLVolume`
diff --git a/fsl/fsleyes/gl/glvolume.py b/fsl/fsleyes/gl/glvolume.py
index 5e07775dce66653aecc7c52e4ca9a74617e70362..261488e83d55658200dd5818e85cf0ad32b9af1d 100644
--- a/fsl/fsleyes/gl/glvolume.py
+++ b/fsl/fsleyes/gl/glvolume.py
@@ -265,11 +265,10 @@ class GLVolume(globject.GLImageObject):
         def update(*a):
             self.notify()
 
-
         def shaderUpdate(*a):
             if self.ready():
-                fslgl.glvolume_funcs.updateShaderState(self)
-                self.notify()
+                if fslgl.glvolume_funcs.updateShaderState(self):
+                    self.notify()
         
         def colourUpdate(*a):
             self.refreshColourTextures()
diff --git a/fsl/fsleyes/gl/shaders/glsl/program.py b/fsl/fsleyes/gl/shaders/glsl/program.py
index 49275d54e68b2d3830ad4a546af951d1b629a488..9e3c381c7533743cdea4c0b05c02d1026ca46184 100644
--- a/fsl/fsleyes/gl/shaders/glsl/program.py
+++ b/fsl/fsleyes/gl/shaders/glsl/program.py
@@ -149,6 +149,13 @@ class GLSLShader(object):
                                              self.vertUniforms,
                                              self.fragUniforms)
 
+        # We cache the most recent value for
+        # every uniform. When a call to set()
+        # is made, if the value is unchanged,
+        # we skip the GL call.
+        self.values = {n : None for n in self.positions.keys()}
+
+        # Buffers for vertex attributes
         self.buffers = {}
 
         for att in self.vertAttributes:
@@ -234,7 +241,16 @@ class GLSLShader(object):
         
         
     def set(self, name, value):
-        """Sets the value for the specified GLSL ``uniform`` variable. """
+        """Sets the value for the specified GLSL ``uniform`` variable.
+
+        The ``GLSLShader`` keeps a copy of the value of every uniform, to
+        avoid unnecessary GL calls.
+
+        :returns: ``True`` if the value was changed, ``False`` otherwise.
+        """
+
+        if self.values[name] == value:
+            return False
 
         vPos  = self.positions[name]
         vType = self.types[    name]
@@ -250,6 +266,8 @@ class GLSLShader(object):
 
         setfunc(vPos, value)
 
+        return True
+
 
     def setAtt(self, name, value, divisor=None):
         """Sets the value for the specified GLSL ``attribute`` variable.