Repository: vispy/vispy

Issue:
Error when using both 2D and 3D textures
There seems to be a problem if one tries to use both a 2D and a 3D texture in a shader program. I uncovered this when trying to implement a basic volume rendering example, see https://github.com/vispy/vispy/pull/458.

The example itself is https://github.com/izaid/vispy/blob/ray_cast/examples/basics/gloo/display_volume.py. Note specifically the fragment shader program RAYS_FRAG_SHADER. As currently written, the Python program works (but does nothing). If you comment out the line "texture3D(u_data, vec3(0, 0, 0));" in RAYS_FRAG_SHADER and uncomment the lines above involving "stop" and "u_stop", you should see a square of different colors. If you now uncomment the simple 3D texture fetch "texture3D(u_data, vec3(0, 0, 0));", you get an error that says:
## 

Validation Failed: Sampler error:
  Samplers of different types use the same texture image unit.
- or -
  A sampler's texture unit is out of range (greater than max allowed or negative).
## 

I tried to sort this out myself, but failed. Is it possible that the textures are being bound to the same unit before validation, as discussed in http://www.opengl.org/discussion_boards/showthread.php/176366-Samplers-of-different-types-use-the-same-textur ?



Discussion:
I don't get this error on my Ubuntu/NVIDIA machine, which is strange. What OS / graphics card are you using? I can also try on my OSX NVIDIA machine, which seems to be pickier about GLSL datatypes.

To be clear, do you have both texture2D and texture3D uncommented?

I'm using OSX with Intel HD Graphics 4000.

Yeah, I do. I'll try my OSX laptop now...

Have you tried actually using all three? There seems to be a conflict where the variables get optimized out if they aren't actually used, and this screws up the `gloo` program's accounting.

Wait, I still get the error on my machine... still trying to track it down.

Tried using all 3, still get the error.

I agree with you @izaid that we probably do indeed need to set the `glUniform1i`'s before validating the program. @almarklein you'll probably need to tackle this one. It seems like there are two options:
1. Add into line ~248 of `gloo/program.py` something that sets the `glUniform1i` parameter, which is currently done well after the `_build` step, once all the variables are parsed it eventually gets set on line 256 of `gloo/variable.py`.
2. Delay the validation of the program until after the variables are updated. This is not a good solution because it forces us to wait until the program is drawn to find errors.

I have tried implementing (1) but have failed...

@izaid is this urgent for you or can this wait until we get to #464? 

@almarklein FWIW this should break any code that uses 2D and 3D textures in the same program, which seems kind of bad...

And whatever solution we figure out here (if we pursue it) should be useful for #464.

That's true, though I suspect its not a common use-case since we have no volume rendering yet :)

But yeah, I'll probably check this out this week :) 

It's urgent only in the sense that I can devote tomorrow and Thurs to volume rendering if we can fix it. :) 

(Otherwise I might have to cycle back to it later.)

And buying a new laptop with a less picky graphics card isn't an option? :)

I can get to it tomorrow afternoon. BTW, just got an email that you will be at the sprint this Sunday. Awesome!

Cheers, thanks Almar!

Yep, Cambridge is not too far away for me to come. Generally keen to help with VisPy's 3D features.


Resolving patch:
diff --git a/vispy/gloo/program.py b/vispy/gloo/program.py
--- a/vispy/gloo/program.py
+++ b/vispy/gloo/program.py
@@ -193,10 +193,12 @@ def _activate(self):
         
         # Check whether we need to rebuild shaders and create variables
         if any(s._need_compile for s in self.shaders):
-            self._build()
+            self._need_build = True
         
         # Stuff we need to do *before* glUse-ing the program
+        did_build = False
         if self._need_build:
+            did_build = True
             self._build()
             self._need_build = False
         
@@ -205,6 +207,15 @@ def _activate(self):
         
         # Stuff we need to do *after* glUse-ing the program
         self._activate_variables()
+        
+        # Validate. We need to validate after textures units get assigned
+        # (glUniform1i() gets called in _update() in variable.py)
+        if did_build:
+            gl.glValidateProgram(self._handle)
+            if not gl.glGetProgramParameter(self._handle, 
+                                            gl.GL_VALIDATE_STATUS):
+                print(gl.glGetProgramInfoLog(self._handle))
+                raise RuntimeError('Program validation error')
     
     def _deactivate(self):
         """Deactivate the program."""
@@ -245,12 +256,6 @@ def _build(self):
         if not gl.glGetProgramParameter(self._handle, gl.GL_LINK_STATUS):
             print(gl.glGetProgramInfoLog(self._handle))
             raise RuntimeError('Program linking error')
-
-        # Validate
-        gl.glValidateProgram(self._handle)
-        if not gl.glGetProgramParameter(self._handle, gl.GL_VALIDATE_STATUS):
-            print(gl.glGetProgramInfoLog(self._handle))
-            raise RuntimeError('Program validation error')
         
         # Now we know what variable will be used by the program
         self._enable_variables()


Test patch: