Repository: vispy/vispy

Issue:
Transforms broken in SceneCanvas with glfw backend
Hello. I'm using vispy 0.5.1 on Debian 8 with a PyQt5 backend. When I run my app, the SceneCanvas is stuck with an identity transform (which looks broken, obviously) until I resize the window or use a mouse key in the window. This also happens with the example https://github.com/vispy/vispy/blob/master/examples/demo/scene/oscilloscope.py when not using fullscreen.
My workaround was to call SceneCanvas' _update_transforms() right after creating the canvas.


Discussion:
@ltjax Thanks for reporting this issue. Could you try installing this repository's `master` branch and see if that fixes things for you? There have been updates recently for fixing PyQt5 compatibility. I don't remember fixing anything specifically dealing with transforms, but it would be best to make sure it hasn't been fixed already.

If this isn't fixed for you in `master` could you please provide me with a minimum working example so I can try to figure out what's going on.
I can confirm the bug still happens with the `master` version. Here's a small sample that illustrates the problem:
```
import vispy
from vispy import scene
from vispy.scene import ViewBox


class Application(object):

    def __init__(self):
        view = ViewBox(camera='panzoom', border_color=(1, 1, 1, 1))
        self.canvas = scene.SceneCanvas(keys='interactive', show=True, title="Update fix")
        # Uncomment this for workaround
        #self.canvas._update_transforms()
        self.canvas.central_widget.add_widget(view)
        self.canvas.show()

    def run(self):
        print(vispy.version_info)
        self.canvas.app.run()


if __name__ == '__main__':
    Application().run()
```
Thanks for the code. I'm testing on a macbook with a conda environment (python 3.6, pyqt5) and I'm not seeing a difference with or without the `_update_transforms` line. Are you getting exceptions/errors/warnings?

Could you take a screenshot of the difference if it is visual? When I print the `transforms` object and the `self.canvas.transforms.canvas_transform` I see non-identity transforms.
I can do that next week. I definitely saw an identity transform before the _update_transforms and non-identity after (I checked with breakpoints in the debugger/PyCharm).
Since that bug reacts to events, my guess is that where the platform dependency comes in. Are you getting resize events when just showing the window on mac maybe? Cause the resize handler is what normally called _update_transforms() and that was only called when I manually dragged the window edges.
Yes, I see a resize event before the SceneCanvas creation even returns.
Well, there's the problem then. No such event on linux. Kind of a typical cross-platform problem, unfortunately
Hello. 
So here are the promised screenshots. The first one is the same right after starting it. I changed the border_color to red to make it more visible. 
![vispy-after-start](https://user-images.githubusercontent.com/770847/38543354-aa7524a0-3ca4-11e8-8243-caad85fe0703.png)

This one is after the first resize event. As you can see, the border is correctly transformed now.
![vispy-after-move](https://user-images.githubusercontent.com/770847/38543471-e468ddc8-3ca4-11e8-8fdf-9baa747d1b6c.png)




Oh, mea culpa - I'm actually using the glfw backend, not pyqt5. Sorry about that.
Ok, so I have investigated this a bit more: When I switch to PyQt5 via vispy.use("PyQt5"), all the problems are gone. If I use vispy.use("Glfw"), it breaks again.
With the latter, it breaks again even with my initial _update_transforms() hack once I add a Text visual. Again, problem goes away after the first pan or resize.
Hope that sheds some light on the issue.
So with glfw the initial canvas is wrong, you pan/zoom/resize and it fixes itself. If you add a visual it breaks again? Is everything broken again or just the text visual?

Or, after rereading your message, if you have your update transforms hack then it is still broken when you add a visual after the update transforms? I'm not sure I even understand my question at this point.
Hehe, sorry for the confusion. First of, everything is fine with PyQt5. All other cases use glfw:

* no hack, no text visual -> initial canvas is broken
* hack, no text visual -> initial canvas is okay
* with text visual: first frame is exactly like both cases without text visual, but then it "breaks" on its own and zooms out to about 2000% - after resize or pan it corrects itself again

What do you mean by first frame? Could you update your tiny example from your previous message to behave the same way (adding a text visual, etc)?
In the past we have seen differences between backends in whether they generate an initial resize event when the window is created. I'll bet that's what happened here.
@campagnola But why would adding the visual cause the redraw to be wrong?
@djhoese I could - but you gotta have to wait another week. I'm developing on site for a customer, and I won't be there again until next week. I really just added a Text visual saying "Hello World!" though.
Here's the updated code. You can try the different cases by changing the CASE variable.

```
import vispy
from vispy import scene
from vispy.scene import ViewBox, Label

CASE = 3


class Application(object):

    def __init__(self):
        self.view = ViewBox(camera='panzoom', border_color=(1, 0, 0, 1))
        self.canvas = scene.SceneCanvas(keys='interactive', show=True, title="Update fix", size=(200, 200))
        self.grid_lines = scene.GridLines(color=(1, 1, 1, 1.0), parent=self.view.scene)
        self.canvas.central_widget.add_widget(self.view)
        if CASE != 3:
            self.view.add_widget(Label("Hello World!", color=(1, 1, 1, 1)))
        self.canvas.show()

    def run(self):
        print(vispy.version_info)
        self.canvas.app.run()


if __name__ == '__main__':
    vispy.use("PyQt5" if CASE == 1 else "Glfw")
    Application().run()

```

Case 1: (PyQt5, all good)
![vispy_case_1](https://user-images.githubusercontent.com/770847/39748092-7bf096d4-52af-11e8-822b-2aeb182bcbcd.png)
Case 2: (Glfw, with text visual)
![vispy_case_2](https://user-images.githubusercontent.com/770847/39748101-81c8b244-52af-11e8-8bb8-c1bb52e2d0b7.png)
Case 3: (Glfw, without text visual)
![vispy_case_3](https://user-images.githubusercontent.com/770847/39748109-88b414f4-52af-11e8-8db1-53baf9b25acd.png)

Note that Case 2 flickers at first and seems to render a frame that looks like Case 3
What happens if you do `show=False` in the SceneCanvas creation (I think I asked this before)?
That does not change anything (And I don't think you did)
@ltjax Any chance you could try vispy from current master and try this out again? I changed a lot with PyQt5 but also semi-recently fixed some bugs with resize events with the PyQt5 backend.
So coming back to this and running on OSX with glfw from conda-forge shows one small change, the two glfw cases (with/without text visual) are not the same result. Still wrong, but at least the same.
I'm moving this from the 0.6 to 0.7 version milestone. As far as I can tell this is only an issue with the glfw backend and I'm having trouble figuring it out. I had some success forcing a `self.canvas.size = (300, 300)` after the canvas was shown which caused a resize (because the size was different than the initial size) and caused things to show correctly. Otherwise, not much luck trying to get glfw to emit the right events.
Hello there and sorry for the delay. I tried again and glfw still does not work correctly. qt5 does not work at all (CanvasBackendDesktop has not attribute setFormat). Maybe glfw has some sort of create event at the start with the window sizes and you have to just use that to initialize the sizes?
@ltjax What version of PyQt are you running with?
I do not know. Still whatever I installed when I first worked on this last year, I guess. The program is at the customer's and I can only check next month.
So how do you know Qt5 still doesn't work? Did you or your customer update vispy?

Would it be possible for you to install PyQt5 locally and test it? I'm assuming you are running glfw locally?
What do you mean, Qt5 still doesn't work? It worked fine before I updated to the latest vispy. I just tried it out since you asked me - though I was kinda confused why you would ask me to do that after fixing stuff in the Qt5 backend.
And what do you mean by "install locally"?
I assumed you were running things on customer machines that you didn't have access to. I wanted to know if you installed vispy with PyQt5 on your local machine that's right in front of you (not a customer machine) if it worked or not. You mentioned `qt5 does not work at all (CanvasBackendDesktop has not attribute setFormat).` but I've never seen this error. So there is the glfw issue, but also a PyQt5 issue apparently.
I have all the code and setup on the customers machine, I cannot test it here.
Yea, that's right, but both issues seem to be unrelated, except that they appear on my customers machine. I say we just stay on track with the glfw thing and ignore Qt5 for now (because it _was_ working fine:-))
So I'm coming back to this because I marked it for the 0.7 milestone. As mentioned above it seems the glfw backend doesn't emit a resize event when it is first created/shown. A simple workaround seems to be:

```
diff --git a/vispy/app/backends/_glfw.py b/vispy/app/backends/_glfw.py
--- a/vispy/app/backends/_glfw.py	(revision e33aae23847e8e5f43248ba9d014cc4aea211a90)
+++ b/vispy/app/backends/_glfw.py	(date 1623253407778)
@@ -282,6 +282,7 @@
             self._vispy_set_position(*p.position)
         if p.show:
             glfw.glfwShowWindow(self._id)
+        self._on_resize(self._id, size[0], size[1])
 
         # Init
         self._initialized = True
```

My guess for why this doesn't happen on pyqt but does on glfw is that the CanvasBackend for the pyqt backend is itself a QtGLWidget. So when it gets created Qt's internals call the widget's `resizeGL` method which starts things moving in the right direction as far as vispy is concerned. For glfw we are simply wrapping glfw to create a window (`glfwCreateWindow`). We don't actually assign the callbacks for handling things like resizing until *after* the Window is created. There isn't really a way around this as assigning the callbacks requires the reference to the window.

@kmuehlbauer @ltjax @almarklein Any opinions on this?

Let me try moving the resize call after the `init` and see if that still works. Then I'll make a PR. Biggest downside could be trying to use glfw without showing the canvas and hoping for something to show up...but then again the visuals wouldn't be sized correctly without resizing anyway.
Ok, so I was on my way to make a PR for this when I thought "I should write a test for this...wait how aren't all the glfw tests failing already?". Turns out that if you put a `canvas.render()` before the `canvas.show()` call the resulting canvas/window is correct. All the tests use `.render()` so they all produce the expected output. I'm not really sure how to test this then.

Resolving patch:
diff --git a/vispy/app/backends/_glfw.py b/vispy/app/backends/_glfw.py
--- a/vispy/app/backends/_glfw.py
+++ b/vispy/app/backends/_glfw.py
@@ -289,6 +289,7 @@ def __init__(self, *args, **kwargs):
         self._next_key_text = {}
         self._vispy_canvas.set_current()
         self._vispy_canvas.events.initialize()
+        self._on_resize(self._id, size[0], size[1])
 
     def _vispy_warmup(self):
         etime = time() + 0.25


Test patch: