-
-
Notifications
You must be signed in to change notification settings - Fork 7.8k
qt backend has more draws than necessary #4943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The example in #4947 is sufficient to show the extra draws (as something was masking the restore_region failure). I suspect that what we want to do here is to defer the call to attn @pwuertz |
I don't really understand what this change does, maybe because I don't have any in-deep knowledge about the Agg renderer. For me it looks like the agg image is drawn exactly once in the lifetime of the canvas, and after self.renderer exists it is never updated again.. at least not in paintEvent. However, paintEvent is the only routine where the agg draw should be happening. |
@tacaswell While paintEvent needs to draw the latest figure-image onto the widget surface, its true that the agg canvas might not need a redraw each time. I don't see a lot of scenarios for this though, other than the use of the rubberband tool. Who else but draw_idle() is calling the widgets update() routine? |
I ran the example from #4947 on current master. On my system everything seems to work as expected. There are no duplicate red ellipses and the red ellipse is removed correctly after the second break. Note that the draw_artist/restore_region calls are not required at the moment since blit->redraw->paintEvent will redraw the figure anyways. The ion() mode however causes lots of extra draws. For instance, there is a looped draw_idle() call that is caused from within the figure.draw(). Backtrace:
I don't see a reason why figure.draw should be emitting another draw_idle call. |
@jrevans The agg.draw in paintEvent is not intended as a cover up for missing canvas.draw calls. It is actually the other way around. The superfluous agg.draw call is the one in canvas.draw, but we cannot get rid of it since other code might rely on the figure being drawn immediately for having some figure elements initialized in the process. |
The stale callbacks causing draw_idle loops are currently under investigation? Is this related to #4738? |
@pwuertz You are missing the blit workflow. We have 3 layers of state: scene graph -> renderer buffer -> Qt screen buffer and any changes on the left need to propagate right. The standard way is: canvas.draw() # moves state change from scene graph -> renderer buffer
Qt.update() -> canvas.paintEvent() # moves state from Agg buffer -> Qt screen buffer What @pwuertz change did was to merge the canvas.draw()
canvas.draw()
canvas.draw()
Qt.update() -> canvas.paintEvent() where the first two draws are just wasted cycles as only the last draw matters. The problem with this is that the blit work flow (which is to avoid draw calls) is canvas.restore_region(bg)
canvas.draw_artist(art)
Qt.update() -> canvas.paintEvent() If The cascading That is all I have time to type as I need to catch a train to work 😄 . |
@tacaswell I didn't know that there was something like a general blit/draw_artist/restore workflow, and if I wrap my head around it I'm not really sure if it is such a useful thing to do. It seems like a workaround for not having a scene graph on the backend-side. For the current situation I added a flag for determining if paintEvent needs to update the agg buffer or not. The buffer is updated when draw_idle triggers the paintEvent and the buffer is considered valid after using draw and when using blit. |
Another feature that is important to keep track of here is that artists can have an
This property should probably be rename from 'animated' -> 'blitted' On current master if you set |
I should have made the #4943 PR and #4944 PR the same one, I saw them as two separate issues at the time, but now I see that they are really closely tied. It seems that some clarification needs to be given. The current design of matplotlib (up to this point) has been that when matplotlib deems a figure needs to be drawn (to the screen, to a file, whatever) a call to 'FigureCanvas.draw' is made. This is where the magic happens. Since the internal design of matplotlib is really geared towards an Agg-like renderer, this is typically where the Agg buffers get rendered to. In the case of Qt it will draw a widget and its contents to the screen in a 'paintEvent'. This can happen for any number of reasons. Reasons can include showing/hiding the widget, resizing the widget (which happens at least one time during widget creation), etc. GUIs (like Qt) typically only want to draw the least amount possible when they can for speed purposes, so ideally they will only redraw the areas of a window that need it. This is where Qt can decide when it calls 'paintEvent' based on their own internal decision making processes. If you are using Qt, you are typically using it because it has these types of optimizations. When rendering any matplotlib figure to the screen, the slowest part is the rendering of the artists (to the Agg buffers). This is not something that you want happening every time Qt issues a 'paintEvent'. The original designed behavior relies on the blitting functionality of transferring the contents of the Agg buffers to the widget. A process which is fairly fast and efficient. This is using the same idea behind what makes the animation system, lassoing, label dragging, etc work. By updating the Agg buffers during every 'paintEvent' will not only make the Qt GUIs very slow, but will make the above mentioned features not work. We should not short-circuit the matplotlib design of the artists being rendered when matplotlib deems necessary, which it enacts by calling 'FigureCanvas.draw' The changes introduced in #4612 appear to be addressing the Qt bug identified in #4897. Those changes, when applied do not redraw the canvas properly when dragging during a pan when there is alot of artists present on the canvas until after the mouse is released. The reason for the timer in draw_idle, is in fact to delay a teeny bit longer so that matplotlib can finish processing its events before the redraw actually happens, otherwise when you select a region for zooming or use the lasso and release the mouse button, they will not disappear. Ideally an optimized mechanism for matplotlib signal processing, where you could 'flush' the signals might be an alternative solution, but the timer delay was much easier and less invasive of a fix. I also need to mention that in our use cases we can have some plots that have thousands of data points and potentially thousands of artists on a canvas at a time. When you are dealing with the more complicated plots, then little things can sometime turn into bigger things. |
@pwuertz That bilt/draw/restore workflow has been is place for longer than I have been working on the project and is absolutely essential to making animations/complex figures go fast. I consider this ability to be one of the core features of mpl. |
@tacasawell, it has been around for awhile, but it was implemented in the Some of the blitting bugs have been fixed in recent weeks, so maybe I'll On Wed, Aug 19, 2015 at 1:53 PM, Thomas A Caswell notifications@github.com
|
I never claimed it was easy, just that it was very important where it is used 😜 To get blitting to work correctly with pan/zoom you need to hook up a callback to the Axes/Figure @blink1073 has a 'blit manager' which can take care of much of this (and the animation framework does take care of this for the most part). I have also seen demos from @joferkington that are amazingly responsive, I think that glueviz relies on blitting (and obviously the jpl folks rely on it). On my internal to-do list is to make using blitting much easier. I have notions that we can use this blitting + stale information to do auto-blitting |
@tacaswell The problems concerning the blitting solution listed by @WeatherGod are what I was thinking about. Efficient segmentation of the scene graph, setting up and maintaining multiple agg buffers and figuring out the order in which to blend them is a tough job. For such tasks there are already well optimized solutions like QtQuick. But I see the point in not having to completely redesign the matplotlib scenegraph-to-backend framework, despite the idea of letting the backend do all drawing optimizations - possibly using multiple threads and hardware acceleration. |
After sending us off topic, I would like to keep this discussion on the On Thu, Aug 20, 2015 at 9:45 AM Peter Würtz notifications@github.com
|
Keep in mind, one of the main reasons why we don't normally delegate much As for hardware accelerations, at the SciPy 2015 conference, there were On Thu, Aug 20, 2015 at 9:45 AM, Peter Würtz notifications@github.com
|
The near term plan for this is that this two PRs clearly fix a problem so I am going to merge them so master is less broken, we can then work on improving that state in #4962 |
FIX: qt backend has more draws than necessary
Sometimes (such as at widget creation), Qt tries to paint itself before matplotlib has called 'draw'. It seems like someone the Agg draw here to solve this problem. We cannot and should not call this every time Qt wants to paint itself. That costs a huge hit in performance because then matplotlib would be refreshing the Agg backend twice for every call to 'draw'. This fixes it so that if Qt tries to paint itself before the Agg buffers have been updated, then it will cause Agg to update itself first.
This addresses an issue in #4897.