napari: Very slow rendering when using `autoswap=True` in the VisPy Canvas
đ Bug
I was really puzzled why the brush was not so responsive as it should have even on low-resolution images. I measured FPS and discovered that it never exceeded 10-15 when you use the brush even on very small images (e.g. examples\add_labels.py
).
Initially, I thought that it was because of some very inefficient code in the Label layer, but after long hours with a debugger and profiler I eliminated this hypothesis. There is certainly room for improvement, but there is no such code that can limit FPS to 15 even on 100x100 images.
Finally, I found the culprit of this issue, it was because of using autoswap=True
in the VisPy Canvas. Setting autoswap
to False
in the following code immediately increases the FPS from 13 to 50 in examples\add_labels.py
on my laptop:
https://github.com/napari/napari/blob/cf55aa005230a89b906be2447ce900be2c1f200a/napari/_qt/qt_viewer.py#L431-L436
Calling Canvas.swap_buffers
at each update below blocks the OpenGL engine at least for 50-60 ms on my laptop not depending on the resolution of an image causing a significant drop in the rendering performance:
https://github.com/vispy/vispy/blob/5449e3402b35da4d6a8215d8d72e19dcf05917d7/vispy/app/canvas.py#L236-L237
which leads to the execution of the following code:
https://github.com/vispy/vispy/blob/5449e3402b35da4d6a8215d8d72e19dcf05917d7/vispy/app/backends/_qt.py#L869
I didnât dig any deeper into VisPy as disabling autoswap
solves the issue without any noticeable bugs so far for me.
Environment
I tested it on a laptop with Windows and integrated graphics:
napari: 0.5.0a2.dev55+g8a793b9f.d20230406
Platform: Windows-10-10.0.22621-SP0
Python: 3.9.1 (default, Dec 11 2020, 09:29:25) [MSC v.1916 64 bit (AMD64)]
Qt: 5.12.9
PyQt5: 5.12.3
NumPy: 1.22.4
SciPy: 1.9.1
Dask: 2023.3.2
VisPy: 0.12.2
magicgui: 0.7.2
superqt: unknown
in-n-out: 0.1.7
app-model: 0.1.2
npe2: 0.6.2
OpenGL:
- GL version: 4.6.13596 Compatibility Profile Context 20.10.22.14 27.20.11022.14001
- MAX_TEXTURE_SIZE: 16384
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 44 (36 by maintainers)
Commits related to this issue
- Disable buffer swapping (#5741) # Fixes/Closes Closes #5734. # Description This change drastically improves performance on some hardware by sacrificing some quality (tearing is more likely). Ho... — committed to napari/napari by brisvag a year ago
- Disable buffer swapping (#5741) # Fixes/Closes Closes #5734. # Description This change drastically improves performance on some hardware by sacrificing some quality (tearing is more likely). Ho... — committed to napari/napari by brisvag a year ago
- Disable buffer swapping (#5741) # Fixes/Closes Closes #5734. # Description This change drastically improves performance on some hardware by sacrificing some quality (tearing is more likely). Ho... — committed to napari/napari by brisvag a year ago
- Disable buffer swapping (#5741) # Fixes/Closes Closes #5734. # Description This change drastically improves performance on some hardware by sacrificing some quality (tearing is more likely). Ho... — committed to napari/napari by brisvag a year ago
- Disable buffer swapping (#5741) # Fixes/Closes Closes #5734. # Description This change drastically improves performance on some hardware by sacrificing some quality (tearing is more likely). Ho... — committed to napari/napari by brisvag a year ago
Looks like you went already deep down the rabbithole đ I think this should be brought up over at Vispy for sure. @ksofiyuk since you have a good picture of the problem, do you mind opening an issue at vispy (linking here as well)? Otherwise let me know and I can take care of it.
In the meantime, even heavy stuff with fast camera movements looks exactly the same for me with
autoswap=False
, so Iâm in favor of doing that in napari until we figure it out on the vispy side.looking on this wiki https://www.khronos.org/opengl/wiki/Swap_Interval it looks like selection of synchronized and non synchronized swap buffer should be possible.
I see few references to glFinish in vispy. Maybe this could be a good way to research?
I think it has almost nothing to do with napari as all the OpenGL stuff is happening inside VisPy. VisPy should just have another thread for OpenGL stuff.
The simplest solution is to put only
SwapBuffers
in a separate thread, it works at least for me and completely resolves the issue, using something like this (this code is just for illustrative purposes):instead of just
I think it should be implemented somewhere there: https://github.com/vispy/vispy/blob/5449e3402b35da4d6a8215d8d72e19dcf05917d7/vispy/app/canvas.py#L415
I remember multiple problems on Xubuntu, and I finally switched to Gnome Shell. As I read the team who manages XFCE is very limited and they do not have enough resources to fix all problems.
Based on the accepted response to linked StackOverflow. Could you check if #5710 solves your problem?
@Czaki @alisterburt Iâm pretty sure that
swapBuffers
is just not used in a proper way in VisPy causing performance issues on some machines. It is just like here PyQt5 OpenGL swapBuffers very slow, where the issue was caused by inappropriate use ofswapBuffers
.I could blame my system if all OpenGL applications were laggy, but I donât have problems with other 3D applications, even with other PyQt apps that use OpenGL.
thanks for reporting back @ksofiyuk !
I ran the same test as you @ksofiyuk and enabling
autoswap
makes no discernible difference in FPS on my system.It does not feel like the double buffering makes a big difference during 2D/3D rendering on my system, there is a slight difference when rotating the 3D camera extremely quickly but nothing perceptible when doing things âreasonablyâ. In this context, I think it would be safe to default to
autoswap=False
on the canvas to solve this problem@psobolewskiPhD Iâve just updated it all to
Qt: 5.15.2 PyQt5: 5.15.9
. The performance issue withautoswap=True
remains exactly the same.There is no any specific reason for
PyQt5: 5.12.3
. I just havenât updated it for a long time đ .Ok, then it looks like it is indeed a platform specific bug.
@ksofiyuk yep - just asserting that the callback is indeed called and the example runs as expected on my machine on main
Very cool find @ksofiyuk and thanks for the ping @jni
From some Googling it seems âdouble bufferingâ is used to prevent flickering and improve the smoothness
An OpenGL canvas maintains two buffers for drawing:
The front buffer is the buffer that is currently visible on the screen. It contains the latest completed frame. The back buffer is the buffer that is not visible on the screen. It is where the application draws the next frame while the front buffer is being displayed.
âswap buffersâ swaps the roles of the front and back buffers. The back buffer, which contains the newly drawn frame, becomes the front buffer and is displayed on the screen, while the previous front buffer becomes the back buffer and is now used for drawing the next frame.
Itâs possible that turning this off will lead to some âtearingâ where partially rendered frames are visible sometimes - I donât have any experience with this so I vote we run the experiment, get a few people on different systems to test and see what happens!