Use WriteableBitmap to improve performance of WPF rendering#1388
Use WriteableBitmap to improve performance of WPF rendering#1388swharden merged 6 commits intoScottPlot:masterfrom
Conversation
- Added use of a WriteableBitmap to avoid unneeded allocations and rebinding the image element source when the backend bitmap is only updated (not changed)
- Locking is not required to call WritePixels (locking is done by the call) - Fixed typo in comment
|
OK I cleaned it up - feel free to squash the commits before merge or let me know if you want me to. |
We don't normally squash PRs into one commit, so I think that's ok. |
|
Also, it's worth noting that this enhancement could likely be made to WinForms and Avalonia. Non-Windows platforms don't have If you're interested that could become part of this or a later PR, or we can open an issue for someone to do that later. |
I don't want to reinvent the wheel here... but is this something we could implement ourselves? The BMP file format isn't that bad, and an obligate writer could be more straightforward to implement than a reader that would have to support multiple/nuanced file structures and header types. Resources:
I'm definitely interested in exploring this idea for the other two controls! A separate PR/issue sounds like a good idea because I see the warning signs of a rabbit hole 😉 |
|
I didn't want to affect the backend API since my issue was only on WPF and the changes I envision to the backend are quite a bit more work. The architecture could be changed to optimize things quite a bit even with the inefficiencies that come with blitting bitmaps rather than a pipeline utilizing the GPU to rasterize directly to a frame (the nice thing about blitting bitmaps is the API is simple and highly portable, so I'm not arguing against the way it's done now since it has advantages). Right now the backend does many large memory allocations (the bitmap buffers are very large when scaled up on a high resolution screen) during real time interaction which puts a lot of pressure on the garbage collector. It should be possible to reuse all the bitmap buffers involved until a resize event occurs and the bitmap buffer must be increased or decreased (although even in this case it's possible to only resize if the bitmap is made larger and just use part of the buffer at the cost of retaining more memory when things are made smaller). There are a few common architectures for this type of things but I like the idea of a double swap type of architecture so the consumer is never blocked waiting for the renderer to create a frame and no allocations or copies are made. The backend renderer has two bitmap buffers (one for actively writing to called A, one for swapping with called B), the consumer has one bitmap buffer it uses (called C). The backend renderer writes to A to render then locks and swaps with B then notifies the consumer of a new frame. When the consumer receives the notification it then locks and swaps C with B. The idea here is that the only time the consumer or renderer is blocked by a lock is for a simple pointer swap. No copies or memory allocations occur through this whole process unless a resize occurs (or is required by the consuming platform UI API). Resize events are handled by having the renderer always look at A's size before writing to it and allocating a new buffer for A if required, everything else proceeds the same (as the renderer writes to each buffer they will each be resized as needed). |
|
I'm going to test this now and hopefully merge it in tonight... Thanks @jbuckmccready for this PR, and @bclehmann for your input! 🚀
I should have clarified #1388 (comment) was directed toward @bclehmann, and implementing an in-memory bitmap writer could be a goal for another issue/PR. This PR is looking great, and it certainly makes sense to use
I'm inclined away from this because I want to minimize the dependencies (software and hardware) this project takes on. If we went into GPU land there would be some interesting mathematical operations we could optimize too. I don't intend to go there any time soon (beyond Skia/OpenGL support for hardware-accelerated 2D drawing, #1036).
ScottPlot actually does this under the hood (re-using a
Double-buffering is an excellent concept to consider! I added it to the triage list #1028 to revisit in the future. I did some early experiments with this long ago, but I think it's definitely worth reconsidering in the future. Note that the primary |
Ah looking at it again I misread the code involving the
Thanks for the context. The Microsoft.Maui.Graphics progression looks awesome. |
|
This PR made me take a second look at this function: ScottPlot/src/controls/ScottPlot.WPF/WpfPlot.xaml.cs Lines 208 to 222 in 041a1a4 I wonder what advantage/disadvantage there is encoding as Png vs Bmp? I'm guessing Png would require less space in memory, but perhaps Bmp would be faster requiring less processing to encode/decode? It may be worth benchmarking both ways to see if one is meaningfully favorable. |
Added use of a WriteableBitmap to avoid unneeded allocations and rebinding the image element source when the backend bitmap is only updated (not changed)
Purpose:
Improves performance of WPF rendering and fixes #1387.