Repository: vispy/vispy

Issue:
Having problems with TextVisuals and InstancedMesh
Hello,
I am having problems with TextVisuals displaying when using InstancedMeshes.
It seems that the TextVisual doesn't display when using an instancedMesh, but does with a standard mesh.
I am having this problem in a larger program, with many other visuals and meshes, both instanced and otherwise.
The below example demonstrates this behaviour.

- Both the TextVisual and the single mesh are originally drawn
- Pressing 'p'
  -  the InstancedMesh is now rendered
  -  the TextVisual disappears
- Unsetting the parent of InstancedMesh does not bring the TextVisual back.

The instantiation order of the visuals does not change the behaviour.

My vispy and python versions are
 -  python: 3.10.12
 - vispy: 0.14.1

Any help with something I'm doing wrong, or the right direction (I don't have any GL experience) to try and debug further would be greatly appreciated.

```python
import numpy as np
from scipy.spatial.transform import Rotation
from vispy import app, scene, use
from vispy.io import imread, load_data_file, read_mesh
from vispy.scene import visuals as vVisuals

# needed for instanced rendering to work
use(gl='gl+')

mesh_path = load_data_file('spot/spot.obj.gz')
texture_path = load_data_file('spot/spot.png')
vertices, faces, normals, texcoords = read_mesh(mesh_path)
texture = np.flipud(imread(texture_path))

canvas = scene.SceneCanvas(keys='interactive', bgcolor='white', show=True)
view = canvas.central_widget.add_view()

view.camera = 'arcball'
view.camera.depth_value = 10 * (vertices.max() - vertices.min())

# Instanced Mesh
n_instances = 2
instance_colors = np.array([[1,0,0],
							[0,1,0]])
instance_positions = ((np.random.rand(n_instances, 3) - 0.5) * 2).astype(np.float32)
instance_transforms = Rotation.random(n_instances).as_matrix().astype(np.float32)
instanced_mesh = vVisuals.InstancedMesh(
	vertices,
	faces,
	instance_colors=instance_colors,
	instance_positions=instance_positions,
	instance_transforms=instance_transforms,
	parent=None,
)

# Single Mesh
mesh = vVisuals.Mesh(vertices, 
					 	faces, 
						color=(.5, .7, .5, 1), 
						parent=None)

# instanced_mesh.parent = view.scene
mesh.parent = view.scene

# Text Visual
t_visual = scene.visuals.Text('Test Default Text',
							color=(0,0,0),
							anchor_x='left',
							anchor_y='top',
							parent=view,
							font_size=40,
							pos=(100,100))

@canvas.events.key_press.connect
def on_key_press(event):
	if event.key == 'p':
		if instanced_mesh.parent is None:
			print("setting parent")
			instanced_mesh.parent = view.scene
		else:
			print("unsetting parent")
			instanced_mesh.parent = None

if __name__ == "__main__":
	app.run()
```


Discussion:
Hey @rzmearns, thanks for the issue and for the reproducer! It's pretty weird, yeah. I also get the same problem on my machine, so this is very likely a bug.

I tried playing with the parents manually, but I can't seem to recover the visibility of the text once it disappears. I'm not sure where to begin here :/

It could be something at the GLIR level getting "stuck" in instanced mode, but that doesn't explain why the normal `Mesh` works.

Otherwise it could be like a blending problem, but even that seems completely unrelated code-wise.

I'm a bit lost...
Hi @brisvag, thanks for taking a look.
I've been doing a bit more digging, (kind of blindly at this point), but a little progress.
In `vispy/visuals/instanced_mesh.py` in the definition of the `_VERTEX_SHADER` which overwrites the basic Mesh version.
Changing the `gl_position` in the GL program to ignore the instance transforms,
i.e. `gl_position = $transform($to_vec4($position));` 
The TextVisual remains when setting the `InstancedMesh.parent` (need to move the basic mesh out of the way to see it though).

So I'm going to do a bit of research about how these programs might be interacting.
Thanks again.

Interesting, nice find! So really it's probably not something fundamentally broken with the instancing draw call, which is a good first step ^^'
@rzmearns can you please try this patch? My hypothesis here is that the `attr_handle` is reused between the text and instanced mesh visuals. The single mesh does not interfere because it does not use more than handle 0, which is okay because the divisor is the same for this attribute (`a_position`).

Here is some debug output from this call (which is in `GlirProgram._pre_draw`) showing the attribute name, handle, and divisor value. You can see the overlapping values from the `Text` and `InstancedMesh` visuals.

```
drawing <Text at 0x1340d6dd0>
a_color 4 None
a_position 1 None
a_texcoord 0 None
a_rotation 2 None
a_pos 3 None

drawing <Mesh at 0x1340e5310>
a_position 0 None

drawing <InstancedMesh at 0x1340e5410>
a_position 0 None
u_base_color 1 1
transform_x 5 1
transform_y 4 1
transform_z 2 1
shift 3 1
```

```diff
diff --git a/vispy/gloo/glir.py b/vispy/gloo/glir.py
index 3b7cc4c4..988e5cd7 100644
--- a/vispy/gloo/glir.py
+++ b/vispy/gloo/glir.py
@@ -1283,8 +1283,7 @@ class GlirProgram(GlirObject):
                 gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo_handle)
                 gl.glEnableVertexAttribArray(attr_handle)
                 func(attr_handle, *args)
-                if divisor is not None:
-                    gl.glVertexAttribDivisor(attr_handle, divisor)
+                gl.glVertexAttribDivisor(attr_handle, divisor or 0)
             else:
                 gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
                 gl.glDisableVertexAttribArray(attr_handle)
```

If this works I can make a PR for more testing of the change.
Hi @aganders3,
That patch seems to have fixed the problem. The textVisual remains as the instancedMesh parent is set and unset.

I have to say I don't really understand the underlying reasoning though (mainly due to my ignorance about GLSL and the glir layer)
Are the handle numbers not still overlapping in the below debug output (after the patch).
`print(f"{name} {attr_handle} {divisor}")`
```
drawing <Mesh at 0x7f88a2301840>
drawing <GlirProgram 11 at 0x7f88a232e920>
a_position 0 None

drawing <InstancedMesh at 0x7f88a2301690>
drawing <GlirProgram 13 at 0x7f88a2356cb0>
a_position 0 None
u_base_color 1 1
transform_x 3 1
transform_y 4 1
transform_z 5 1
shift 2 1

drawing <Text at 0x7f88a277c670>
drawing <GlirProgram 9 at 0x7f88a2302a40>
a_color 0 None
a_position 2 None
a_texcoord 3 None
a_rotation 1 None
a_pos 4 None
```


Thanks 


Great!

Yes, they will still overlap - sorry I was not clear in my description.

Currently the divisor is set to 1 by the InstancedMesh, but never set back to 0 because the call to (re)set it is skipped if `None`. With my patch it will set the divisor to 0 (the default) when `None`. 

This means the overlap does not cause problems as the divisor is always set correctly for each buffer for the current command.
Right, thanks for clarifying that, I think I understand the underlying problem now.
Thanks @aganders3 and @brisvag for your help.
What would the next step be?


I will let @brisvag weigh in, but I can open a PR tomorrow morning (I'm on US East Coast time).
Aaaaah! Great detective work @aganders3! I also ended up on that line and tried to add an `else` statement, but I was working a bit blindly and I guess I didn't stumble on the right solution xD That makes perfect sense now :) Happy to review and merge the PR!

Resolving patch:
diff --git a/vispy/gloo/glir.py b/vispy/gloo/glir.py
--- a/vispy/gloo/glir.py
+++ b/vispy/gloo/glir.py
@@ -1283,8 +1283,13 @@ def _pre_draw(self):
                 gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo_handle)
                 gl.glEnableVertexAttribArray(attr_handle)
                 func(attr_handle, *args)
-                if divisor is not None:
-                    gl.glVertexAttribDivisor(attr_handle, divisor)
+                if hasattr(gl, "glVertexAttribDivisor"):
+                    gl.glVertexAttribDivisor(attr_handle, divisor or 0)
+                elif divisor is not None:
+                    logger.warning(
+                        'Instanced rendering is not supported by the current'
+                        f'backend ("{gl.current_backend.__name__}")'
+                    )
             else:
                 gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
                 gl.glDisableVertexAttribArray(attr_handle)


Test patch: