From aeeaac983ebc9f4d17f130705d47e1f4fe51b8cd Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Wed, 20 Jan 2016 17:29:54 +0000
Subject: [PATCH] async.wait related updates to other GL types.

---
 fsl/fsleyes/gl/gllabel.py      | 42 ++++++++-------
 fsl/fsleyes/gl/gllinevector.py |  9 ++--
 fsl/fsleyes/gl/glmask.py       | 28 +++++-----
 fsl/fsleyes/gl/glrgbvector.py  | 20 +++----
 fsl/fsleyes/gl/gltensor.py     | 10 ++--
 fsl/fsleyes/gl/glvector.py     | 95 ++++++++++++++++++++++------------
 fsl/fsleyes/gl/glvolume.py     | 34 ++++++------
 7 files changed, 138 insertions(+), 100 deletions(-)

diff --git a/fsl/fsleyes/gl/gllabel.py b/fsl/fsleyes/gl/gllabel.py
index c224819a8..4af047079 100644
--- a/fsl/fsleyes/gl/gllabel.py
+++ b/fsl/fsleyes/gl/gllabel.py
@@ -9,12 +9,13 @@ functionality to render an :class:`.Image` overlay as a label/atlas image.
 """
 
 
-import OpenGL.GL      as gl
+import OpenGL.GL       as gl
 
-import fsl.fsleyes.gl as fslgl
-import resources      as glresources
-import                   globject
-import                   textures
+import fsl.fsleyes.gl  as fslgl
+import fsl.utils.async as async
+import resources       as glresources
+import                    globject
+import                    textures
 
 
 class GLLabel(globject.GLImageObject):
@@ -99,18 +100,17 @@ class GLLabel(globject.GLImageObject):
             self.notify()
 
         def shaderUpdate(*a):
-            fslgl.gllabel_funcs.updateShaderState(self)
-            self.notify()
+            if self.ready():
+                fslgl.gllabel_funcs.updateShaderState(self)
+                self.notify()
             
         def shaderCompile(*a):
             fslgl.gllabel_funcs.compileShaders(self)
-            fslgl.gllabel_funcs.updateShaderState(self)
-            self.notify() 
+            shaderUpdate()
 
         def lutUpdate(*a):
             self.refreshLutTexture()
-            fslgl.gllabel_funcs.updateShaderState(self)
-            self.notify()
+            shaderUpdate()
 
         def lutChanged(*a):
             if self.__lut is not None:
@@ -124,14 +124,13 @@ class GLLabel(globject.GLImageObject):
             lutUpdate()
 
         def imageRefresh(*a):
-            self.refreshImageTexture()
-            fslgl.gllabel_funcs.updateShaderState(self)
+            async.wait([self.refreshImageTexture()], shaderUpdate)
             
         def imageUpdate(*a):
             self.imageTexture.set(volume=opts.volume,
                                   resolution=opts.resolution)
-            
-            fslgl.gllabel_funcs.updateShaderState(self)
+
+            async.wait([self.imageTexture.refreshThread()], shaderUpdate)
 
         self.__lut = opts.lut
 
@@ -184,13 +183,13 @@ class GLLabel(globject.GLImageObject):
             opts.removeSyncChangeListener('resolution', name)
 
 
-        
     def setAxes(self, xax, yax):
         """Overrides :meth:`.GLImageObject.setAxes`. Updates the shader
-        program state,
+        program state.
         """
         globject.GLImageObject.setAxes(self, xax, yax)
-        fslgl.gllabel_funcs.updateShaderState(self)
+        if self.ready():
+            fslgl.gllabel_funcs.updateShaderState(self)
 
 
     def refreshImageTexture(self):
@@ -211,7 +210,7 @@ class GLLabel(globject.GLImageObject):
         if self.imageTexture is not None:
             
             if self.imageTexture.getTextureName() == texName:
-                return 
+                return None
             
             self.imageTexture.deregister(self.name)
             glresources.delete(self.imageTexture.getTextureName())
@@ -220,10 +219,13 @@ class GLLabel(globject.GLImageObject):
             texName, 
             textures.ImageTexture,
             texName,
-            self.image)
+            self.image,
+            notify=False)
         
         self.imageTexture.register(self.name, self.__imageTextureChanged)
 
+        return self.imageTexture.refreshThread()
+
 
     def refreshLutTexture(self, *a):
         """Refreshes the :class:`.LookupTableTexture` which stores the
diff --git a/fsl/fsleyes/gl/gllinevector.py b/fsl/fsleyes/gl/gllinevector.py
index d27f39161..4507d96c7 100644
--- a/fsl/fsleyes/gl/gllinevector.py
+++ b/fsl/fsleyes/gl/gllinevector.py
@@ -67,9 +67,12 @@ class GLLineVector(glvector.GLVector):
         if isinstance(image, tensorimage.TensorImage): vecImage = image.V1()
         else:                                          vecImage = image
 
-        glvector.GLVector.__init__(self, image, display, vectorImage=vecImage)
-        
-        fslgl.gllinevector_funcs.init(self)
+        glvector.GLVector.__init__(self,
+                                   image,
+                                   display,
+                                   vectorImage=vecImage,
+                                   init=lambda: fslgl.gllinevector_funcs.init(
+                                       self))
 
         def update(*a):
             self.notify()
diff --git a/fsl/fsleyes/gl/glmask.py b/fsl/fsleyes/gl/glmask.py
index 9596c1202..b6436d26b 100644
--- a/fsl/fsleyes/gl/glmask.py
+++ b/fsl/fsleyes/gl/glmask.py
@@ -14,6 +14,7 @@ import numpy                  as np
 
 import fsl.fsleyes.gl         as fslgl
 import fsl.fsleyes.colourmaps as colourmaps
+import fsl.utils.async        as async
 import                           glvolume
 
 
@@ -49,34 +50,29 @@ class GLMask(glvolume.GLVolume):
 
         def update(*a):
             self.notify()
-
-        def shaderCompile(*a):
-            fslgl.glvolume_funcs.compileShaders(   self)
-            fslgl.glvolume_funcs.updateShaderState(self)
-            self.notify() 
         
         def shaderUpdate(*a):
-            fslgl.glvolume_funcs.updateShaderState(self)
-            self.notify() 
-            
+            if self.ready():
+                fslgl.glvolume_funcs.updateShaderState(self)
+                self.notify() 
+
+        def shaderCompile(*a):
+            fslgl.glvolume_funcs.compileShaders(self)
+            shaderUpdate()
+
         def colourUpdate(*a):
             self.refreshColourTextures()
-            fslgl.glvolume_funcs.updateShaderState(self)
-            self.notify()
+            shaderUpdate()
 
         def imageRefresh(*a):
-            self.refreshImageTexture()
-            fslgl.glvolume_funcs.updateShaderState(self)
-            self.notify()
+            async.wait([self.refreshImageTexture()], shaderUpdate)
 
         def imageUpdate(*a):
             volume     = opts.volume
             resolution = opts.resolution
 
             self.imageTexture.set(volume=volume, resolution=resolution)
-            
-            fslgl.glvolume_funcs.updateShaderState(self) 
-            self.notify()
+            async.wait([self.refreshThread()], shaderUpdate)
 
         display.addListener('alpha',         name, colourUpdate,  weak=False)
         display.addListener('brightness',    name, colourUpdate,  weak=False)
diff --git a/fsl/fsleyes/gl/glrgbvector.py b/fsl/fsleyes/gl/glrgbvector.py
index 86ef4bbc9..8d8968dce 100644
--- a/fsl/fsleyes/gl/glrgbvector.py
+++ b/fsl/fsleyes/gl/glrgbvector.py
@@ -13,6 +13,7 @@ import numpy                   as np
 import OpenGL.GL               as gl
 
 import fsl.data.tensorimage    as tensorimage
+import fsl.utils.async         as async
 import fsl.fsleyes.gl          as fslgl
 import fsl.fsleyes.gl.glvector as glvector
 
@@ -75,9 +76,9 @@ class GLRGBVector(glvector.GLVector):
                                    image,
                                    display,
                                    prefilter=np.abs,
-                                   vectorImage=vecImage)
-        
-        fslgl.glrgbvector_funcs.init(self)
+                                   vectorImage=vecImage,
+                                   init=lambda: fslgl.glrgbvector_funcs.init(
+                                       self))
 
         self.displayOpts.addListener('interpolation',
                                      self.name,
@@ -107,7 +108,7 @@ class GLRGBVector(glvector.GLVector):
         if opts.interpolation == 'none': interp = gl.GL_NEAREST
         else:                            interp = gl.GL_LINEAR 
         
-        glvector.GLVector.refreshImageTexture(self, interp)
+        return glvector.GLVector.refreshImageTexture(self, interp)
 
 
     def __dataChanged(self, *a):
@@ -126,12 +127,13 @@ class GLRGBVector(glvector.GLVector):
         if opts.interpolation == 'none': interp = gl.GL_NEAREST
         else:                            interp = gl.GL_LINEAR 
         
-        texChange = self.imageTexture.set(interp=interp)
-        self.updateShaderState()
+        self.imageTexture.set(interp=interp)
+
+        def onRefresh():
+            self.updateShaderState()
+            self.notify() 
 
-        # See comments in GLVolume.addDisplayListeners about this
-        if not texChange:
-            self.notify()
+        async.wait([self.imageTexture.refreshThread()], onRefresh)
 
 
     def compileShaders(self):
diff --git a/fsl/fsleyes/gl/gltensor.py b/fsl/fsleyes/gl/gltensor.py
index f2f9c45b4..513c51f9e 100644
--- a/fsl/fsleyes/gl/gltensor.py
+++ b/fsl/fsleyes/gl/gltensor.py
@@ -41,8 +41,9 @@ class GLTensor(glvector.GLVector):
                                    image,
                                    display,
                                    prefilter=np.abs,
-                                   vectorImage=image.V1())
-        fslgl.gltensor_funcs.init(self)
+                                   vectorImage=image.V1(),
+                                   init=lambda: fslgl.gltensor_funcs.init(
+                                       self))
 
 
     def destroy(self):
@@ -65,8 +66,9 @@ class GLTensor(glvector.GLVector):
         opts = self.displayOpts
 
         def shaderUpdate(*a):
-            self.updateShaderState()
-            self.notify()
+            if self.ready():
+                self.updateShaderState()
+                self.notify()
 
         opts.addListener('lighting',         name, shaderUpdate, weak=False)
         opts.addListener('tensorResolution', name, shaderUpdate, weak=False)
diff --git a/fsl/fsleyes/gl/glvector.py b/fsl/fsleyes/gl/glvector.py
index 54b5aa032..73b6dfc54 100644
--- a/fsl/fsleyes/gl/glvector.py
+++ b/fsl/fsleyes/gl/glvector.py
@@ -14,6 +14,7 @@ import numpy                    as np
 import OpenGL.GL                as gl
 
 import fsl.data.image           as fslimage
+import fsl.utils.async          as async
 import fsl.fsleyes.colourmaps   as fslcm
 import resources                as glresources
 import                             textures
@@ -92,7 +93,12 @@ class GLVector(globject.GLImageObject):
     """
 
     
-    def __init__(self, image, display, prefilter=None, vectorImage=None):
+    def __init__(self,
+                 image,
+                 display,
+                 prefilter=None,
+                 vectorImage=None,
+                 init=None):
         """Create a ``GLVector`` object bound to the given image and display.
 
         Initialises the OpenGL data required to render the given image.
@@ -122,6 +128,11 @@ class GLVector(globject.GLImageObject):
                           ``vectorImage`` parameter can be used to specify
                           an ``Image`` instance which does contain the
                           vector data.
+
+        :arg init:        An optional function to be called when all of the
+                          :class:`.ImageTexture` instances associated with
+                          this ``GLVector`` have been initialised.
+        
         """
 
         if vectorImage is None: vectorImage = image
@@ -140,7 +151,8 @@ class GLVector(globject.GLImageObject):
         self.yColourTexture  = textures.ColourMapTexture('{}_y' .format(name))
         self.zColourTexture  = textures.ColourMapTexture('{}_z' .format(name))
         self.cmapTexture     = textures.ColourMapTexture('{}_cm'.format(name))
-        
+
+        self.shader          = None
         self.modulateImage   = None
         self.clipImage       = None
         self.colourImage     = None
@@ -162,12 +174,19 @@ class GLVector(globject.GLImageObject):
         if opts.clipImage     is not None: self.registerAuxImage('clip') 
 
         self.addListeners()
-        self.refreshImageTexture()
-        self.refreshAuxTexture('modulate')
-        self.refreshAuxTexture('clip')
-        self.refreshAuxTexture('colour')
         self.refreshColourTextures()
 
+        def texRefresh():
+            if init is not None:
+                init()
+            self.notify()
+
+        async.wait([self.refreshImageTexture(),
+                    self.refreshAuxTexture('modulate'),
+                    self.refreshAuxTexture('clip'),
+                    self.refreshAuxTexture('colour')],
+                   texRefresh)
+
         
     def destroy(self):
         """Must be called when this ``GLVector`` is no longer needed. Deletes
@@ -210,7 +229,8 @@ class GLVector(globject.GLImageObject):
         """Returns ``True`` if this ``GLVector`` is ready to be drawn,
         ``False`` otherwise.
         """
-        return all((self.imageTexture    is not None,
+        return all((self.shader          is not None,
+                    self.imageTexture    is not None,
                     self.modulateTexture is not None,
                     self.clipTexture     is not None,
                     self.colourTexture   is not None,
@@ -232,50 +252,47 @@ class GLVector(globject.GLImageObject):
 
         def update(*a):
             self.notify()
+            
+        def shaderUpdate(*a):
+            if self.ready():
+                self.updateShaderState()
+                self.notify() 
         
         def modUpdate( *a):
             self.deregisterAuxImage('modulate')
-            self.registerAuxImage(  'modulate') 
-            self.refreshAuxTexture( 'modulate')
-            self.updateShaderState()
+            self.registerAuxImage(  'modulate')
+            async.wait([self.refreshAuxTexture( 'modulate')], shaderUpdate)
 
         def clipUpdate( *a):
             self.deregisterAuxImage('clip')
             self.registerAuxImage(  'clip')
-            self.refreshAuxTexture( 'clip')
-            self.updateShaderState()
+            async.wait([self.refreshAuxTexture( 'clip')], shaderUpdate)
 
         def colourUpdate( *a):
             self.deregisterAuxImage('colour')
             self.registerAuxImage(  'colour')
-            self.refreshAuxTexture( 'colour')
+
+            def onRefresh():
+                self.compileShaders()
+                self.refreshColourTextures()
+                shaderUpdate()
+                
+            async.wait([self.refreshAuxTexture( 'colour')], onRefresh)
             
-            self.compileShaders()
-            self.refreshColourTextures()
-            self.updateShaderState()
- 
         def cmapUpdate(*a):
             self.refreshColourTextures()
-            self.updateShaderState()
-            self.notify()
-            
-        def shaderUpdate(*a):
-            self.updateShaderState()
-            self.notify() 
+            shaderUpdate()
 
         def shaderCompile(*a):
             self.compileShaders()
-            self.updateShaderState()
-            self.notify()
+            shaderUpdate()
 
         def imageRefresh(*a):
-            self.refreshImageTexture()
-            self.updateShaderState()
+            async.wait([self.refreshImageTexture()], shaderUpdate)
             
         def imageUpdate(*a):
-
             self.imageTexture.set(resolution=opts.resolution)
-            self.updateShaderState()
+            async.wait([self.imageTexture.refreshThread()], shaderUpdate)
 
         display.addListener('alpha',         name, cmapUpdate,   weak=False)
         display.addListener('brightness',    name, cmapUpdate,   weak=False)
@@ -373,10 +390,13 @@ class GLVector(globject.GLImageObject):
             nvals=3,
             interp=interp,
             normalise=True,
-            prefilter=realPrefilter)
+            prefilter=realPrefilter,
+            notify=False)
         
         self.imageTexture.register(self.name, self.__textureChanged)
 
+        return self.imageTexture.refreshThread()
+
     
     def compileShaders(self):
         """This method must be provided by subclasses (the
@@ -408,8 +428,10 @@ class GLVector(globject.GLImageObject):
 
         imageAttr = '{}Image'  .format(which)
         optsAttr  = '{}Opts'   .format(which)
+        texAttr   = '{}Texture'.format(which)
         
         image = getattr(self.displayOpts, imageAttr)
+        tex   = getattr(self,             texAttr)
 
         if image is None or image == 'none':
             image = None
@@ -426,8 +448,12 @@ class GLVector(globject.GLImageObject):
 
         def volumeChange(*a):
             
-            self.refreshAuxTexture(which)
-            self.notify()
+            def onRefresh():
+                self.updateShaderState()
+                self.notify()
+                
+            tex.set(volume=opts.volume)
+            async.wait([tex.refreshThread()], onRefresh)
 
         # We set overwrite=True, because
         # the modulate/clip/colour images
@@ -508,12 +534,15 @@ class GLVector(globject.GLImageObject):
             textures.ImageTexture,
             texName,
             image,
-            normalise=norm)
+            normalise=norm,
+            notify=False)
         
         tex.register(self.name, self.__textureChanged)
         
         setattr(self, texAttr, tex)
 
+        return tex.refreshThread()
+
 
     def refreshColourTextures(self, colourRes=256):
         """Called when the component colour maps need to be updated, when one
diff --git a/fsl/fsleyes/gl/glvolume.py b/fsl/fsleyes/gl/glvolume.py
index 1b93a440c..5e07775dc 100644
--- a/fsl/fsleyes/gl/glvolume.py
+++ b/fsl/fsleyes/gl/glvolume.py
@@ -264,25 +264,20 @@ class GLVolume(globject.GLImageObject):
 
         def update(*a):
             self.notify()
-        
-        def colourUpdate(*a):
-            self.refreshColourTextures()
-            if self.ready():
-                fslgl.glvolume_funcs.updateShaderState(self)
-                self.notify()
+
 
         def shaderUpdate(*a):
             if self.ready():
                 fslgl.glvolume_funcs.updateShaderState(self)
                 self.notify()
-
-        def onTextureRefresh():
+        
+        def colourUpdate(*a):
+            self.refreshColourTextures()
             if self.ready():
-                fslgl.glvolume_funcs.updateShaderState(self)
-                self.notify() 
+                shaderUpdate()
 
         def imageRefresh(*a):
-            async.wait([self.refreshImageTexture()], onTextureRefresh)
+            async.wait([self.refreshImageTexture()], shaderUpdate)
 
         def imageUpdate(*a):
             volume     = opts.volume
@@ -304,12 +299,12 @@ class GLVolume(globject.GLImageObject):
                                      notify=False)
                 waitfor.append(self.clipTexture.refreshThread())
 
-            async.wait(waitfor, onTextureRefresh)
+            async.wait(waitfor, shaderUpdate)
 
         def clipUpdate(*a):
             self.deregisterClipImage()
             self.registerClipImage()
-            async.wait([self.refreshClipTexture()], onTextureRefresh)
+            async.wait([self.refreshClipTexture()], shaderUpdate)
 
         display.addListener('alpha',          lName, colourUpdate,  weak=False)
         opts   .addListener('displayRange',   lName, colourUpdate,  weak=False)
@@ -397,6 +392,11 @@ class GLVolume(globject.GLImageObject):
         :class:`.Image` data. This is performed through the :mod:`.resources`
         module, so the image texture can be shared between multiple
         ``GLVolume`` instances.
+
+        :returns: A reference to the ``Thread`` instance which is
+                  asynchronously updating the :class:`.ImageTexture`,
+                  or ``None`` if the texture is updated - see the
+                  :meth:`.ImageTexture.refreshThread` method.
         """
 
         opts     = self.displayOpts
@@ -447,9 +447,13 @@ class GLVolume(globject.GLImageObject):
         self.clipOpts  = clipOpts
         
         def updateClipTexture(*a):
+
+            def onRefresh():
+                fslgl.glvolume_funcs.updateShaderState(self)
+                self.notify()
+            
             self.clipTexture.set(volume=clipOpts.volume)
-            async.wait([self.clipTexture.refreshThread()],
-                       fslgl.glvolume_funcs.updateShaderState, self)
+            async.wait([self.clipTexture.refreshThread()], onRefresh)
 
         clipOpts.addListener('volume',
                              self.name,
-- 
GitLab