Repository: vispy/vispy

Issue:
PanZoom camera interaction becomes unusable if the up parameter is changed from "+z"
Here is a minimal working example. Try changing the `+z` to `-z` in the line `view.camera.up = "+z"` to see the difference in performance:

```
import numpy as np
import vispy.scene
from vispy.scene import visuals

canvas = vispy.scene.SceneCanvas(keys='interactive', show=True)
view = canvas.central_widget.add_view()

epsilon = 0.01

# Make Data
n_markers = 3
p1 = np.zeros((n_markers, 3))
p2 = np.zeros((n_markers, 3))
p1[:, 0] = np.linspace(0.0, 1.0, n_markers) + 0.0
p2[:, 0] = np.linspace(0.0, 1.0, n_markers) + 0.1

# Make Visuals
m1 = visuals.Markers(parent=view.scene)
m2 = visuals.Markers(parent=view.scene)
axis = visuals.XYZAxis(parent=view.scene)

p1[:, 2] += 0 * epsilon
p2[:, 2] += 1 * epsilon

m1.order = 1
m2.order = 2

# Set Data
m1.set_data(
    p1, face_color=[1.0, 0.0, 0.0, 0.5], edge_color=[1, 0, 0, 1], size=80, edge_width=15
)
m2.set_data(
    p2, face_color=[0.0, 1.0, 0.0, 0.5], edge_color=[0, 1, 0, 1], size=80, edge_width=15
)

view.camera = 'panzoom'
view.camera.up = "+z"  # <- This fixes occlusion/ordering/transparency but breaks refresh rate
# view.camera = 'arcball'

if __name__ == '__main__':
    import sys
    if sys.flags.interactive != 1:
        vispy.app.run()
```


Discussion:
The issue is in the branching logic starting around line 286 of `panzoom.py`. It results in passing a different instance of a transform into `self._set_scene_transform` which then triggers updating the full transform system of the scene. 

I have a very simple fix that I'll open a PR for.
I don't see this issue on my local machine. I was going to point to the same code you pointed to. That said, if you can remove the asymmetric behavior between +/-z handling in that method then I'm all for it.
That is a little curious that you don't see it. When I use `"-z"` panning and zooming become incredibly laggy. I'm running linux with the non-nvidia graphics driver so maybe I'm just suffering the performance hit much more than you.

Resolving patch:
diff --git a/vispy/scene/cameras/panzoom.py b/vispy/scene/cameras/panzoom.py
--- a/vispy/scene/cameras/panzoom.py
+++ b/vispy/scene/cameras/panzoom.py
@@ -49,6 +49,7 @@ def __init__(self, rect=(0, 0, 1, 1), aspect=None, **kwargs):
         super(PanZoomCamera, self).__init__(**kwargs)
 
         self.transform = STTransform()
+        self.tf_mat = MatrixTransform()
 
         # Set camera attributes
         self.aspect = aspect
@@ -151,7 +152,8 @@ def rect(self, args):
     @property
     def center(self):
         rect = self._rect
-        return 0.5 * (rect.left+rect.right), 0.5 * (rect.top+rect.bottom), 0
+        return 0.5 * (rect.left + rect.right), 0.5 * (rect.top +
+                                                      rect.bottom), 0
 
     @center.setter
     def center(self, center):
@@ -206,7 +208,7 @@ def viewbox_mouse_event(self, event):
 
         if event.type == 'mouse_wheel':
             center = self._scene_transform.imap(event.pos)
-            self.zoom((1 + self.zoom_factor) ** (-event.delta[1] * 30), center)
+            self.zoom((1 + self.zoom_factor)**(-event.delta[1] * 30), center)
             event.handled = True
 
         elif event.type == 'mouse_move':
@@ -223,14 +225,14 @@ def viewbox_mouse_event(self, event):
                 p2 = np.array(event.pos)[:2]
                 p1s = self._transform.imap(p1)
                 p2s = self._transform.imap(p2)
-                self.pan(p1s-p2s)
+                self.pan(p1s - p2s)
                 event.handled = True
             elif 2 in event.buttons and not modifiers:
                 # Zoom
                 p1c = np.array(event.last_event.pos)[:2]
                 p2c = np.array(event.pos)[:2]
-                scale = ((1 + self.zoom_factor) **
-                         ((p1c-p2c) * np.array([1, -1])))
+                scale = ((1 + self.zoom_factor)**((p1c - p2c) *
+                                                  np.array([1, -1])))
                 center = self._transform.imap(event.press_event.pos[:2])
                 self.zoom(scale, center)
                 event.handled = True
@@ -252,8 +254,8 @@ def _update_transform(self):
         # apply scale ratio constraint
         if self._aspect is not None:
             # Aspect ratio of the requested range
-            requested_aspect = (rect.width / rect.height
-                                if rect.height != 0 else 1)
+            requested_aspect = (rect.width /
+                                rect.height if rect.height != 0 else 1)
             # Aspect ratio of the viewbox
             view_aspect = vbr.width / vbr.height if vbr.height != 0 else 1
             # View aspect ratio needed to obey the scale constraint
@@ -273,7 +275,7 @@ def _update_transform(self):
         # Apply mapping between viewbox and cam view
         self.transform.set_mapping(self._real_rect, vbr, update=False)
         # Scale z, so that the clipping planes are between -alot and +alot
-        self.transform.zoom((1, 1, 1/d))
+        self.transform.zoom((1, 1, 1 / d))
 
         # We've now set self.transform, which represents our 2D
         # transform When up is +z this is all. In other cases,
@@ -281,12 +283,10 @@ def _update_transform(self):
         # for the scene we need a different (3D) mapping. When there
         # is a minus in up, we simply look at the scene from the other
         # side (as if z was flipped).
-
         if self.up == '+z':
-            thetransform = self.transform
+            self.tf_mat.matrix = self.transform.as_matrix().matrix
         else:
             rr = self._real_rect
-            tr = MatrixTransform()
             d = d if (self.up[0] == '+') else -d
             pp1 = [(vbr.left, vbr.bottom, 0), (vbr.left, vbr.top, 0),
                    (vbr.right, vbr.bottom, 0), (vbr.left, vbr.bottom, 1)]
@@ -300,9 +300,9 @@ def _update_transform(self):
             elif self.up[1] == 'x':
                 pp2 = [(0, rr.left, rr.bottom), (0, rr.left, rr.top),
                        (0, rr.right, rr.bottom), (d, rr.left, rr.bottom)]
+
             # Apply
-            tr.set_mapping(np.array(pp2), np.array(pp1))
-            thetransform = tr
+            self.tf_mat.set_mapping(np.array(pp2), np.array(pp1))
 
         # Set on viewbox
-        self._set_scene_transform(thetransform)
+        self._set_scene_transform(self.tf_mat)


Test patch: