We are building a piano roll app with some heavy vector graphics for drawing the background grids, notes, texts and other visualizations. On Linux the graphics are performing quite well but it turns a lot more sluggish when we port the app to Windows, where the release builds are about as smooth as Linux debug builds.
As a rough measure of graphical performance, I added a 60 Hz timer triggering repaints over a very small rectangle, and calculate how many Component::paint() calls are actually executed, which gives me a FPS estimate (or better interpreted as the message queue congestion). On both platforms this figure stays at 60 when idle, but when resizing the window (or anything that invalidates the whole piano roll), the Windows builds are about half as fast as Linux, a ratio that is way beyond hardware differences.
Profiling shows that most of the time is spent on JUCE EdgeTable. We also tried switching to OpenGL but it only made it even slower, as the OpenGL renderer still goes through the EdgeTable.
At this point one explanation is that perhaps MSVC isn’t good at optimizing template-heavy code, a hypothesis I raised when looking into how LowLevelGraphicsSoftwareRenderer and RenderingHelpers are implemented? I’m asking for opinions whether it’s worthy to dive into the code and build a custom software renderer based on something like Blend2D.
Previous posts about graphical performance give quite a lot of different opinions, some claiming it’s faster on Windows than macOS. It’s hard for me to make a decision, so I also wonder if there’s a 2020 update on this issue?
All I can say is Blend2D worth to use it. It’s blazing fast, the rendering quality is outstanding etc. I tried to create low level context for JUCE but it turned out that it’s not easy to do. Blend2D has its own Font management implementation that is not possible to port it to JUCE. Moreover, cross conversions between JUCE image format and that of Blend2D kill the performance. And some other incompatible things that I don’t want to mention…
All I can say, use Blend2D as a stand-alone engine. Do all work in Blend2D context and just copy the resulting image to JUCE window. Even though, there will be the final image conversion. So…
Hello, thank you for noting about the Font management issues. Very important information! I had a look at Blend2D API and at a first glace they look somewhat similar to JUCE Graphics class, but font indeed is handled quite differently. If we decide to go down this route we may need to ditch the whole thing about juce::GlyphArrangement.
You’re wasting your time even considering a software renderer if you’re trying to build a real-world product. All CPU-based rendering libraries are a dead-end for desktop and mobile devices (especially mobile).
Doesn’t matter how efficient the library is. The bottleneck of getting a rendered image from normal memory onto the GPU means that above a certain resolution, it’s just not possible.
On a desktop machine with a low-res display and unified memory architecture, you might just about get away with CPU rendering for simple stuff, (though it’s poor use the available computing power)
But something like a mobile phone where the ratio between CPU power, memory bandwidth and screen resolution is much higher, the numbers are completely stacked against you. Most mobile phones won’t even be able to refresh a blank screen at the frame rate you want using the CPU, whereas with GPUs they’re extremely powerful.
Hello Jules, thank you for the input! So it seems like we should consider a GPU renderer instead. Can you recommend a good rendering library so perhaps it will add some flexibility and save us from working directly with OpenGL (or alike)?
I’m also wondering what would be the most efficient yet non-intrusive (i.e. without modifying JUCE itself) way to implement the custom renderer? Looking into CachedComponentImage now but it seems like you still need to create a juce::Image inside paint() override and draw it through the Graphics& passed in?
I’m not sure if I’m doing it right, but I tried adding JUCE_DIRECT2D=1 as a predefined macro, and call getPeer() -> setCurrentRenderingEngine(1); on the main window. The compiler didn’t complain, although the graphics is kind of twisted…
So we shouldn’t use JUCE then? The default JUCE renderer is a software one, maybe accelerated on OSX by CoreGraphics but on Windows it’s software only. It’s kind of a confusing comment you left here. If you feel so strongly about this can you then share us the roadmap regarding the JUCE rendering engine, what are the future plans?
Well, of course you should use JUCE, and of course it has a software renderer, because that was the only option for many years on Windows and Linux, but the plan is for us to add new accelerated renderer in whatever way is most appropriate. Tom has posted a lot about this, and it’s most like going to be Metal/Vulkan, though it’s still under discussion.
My comment was aimed at the poster who was suggesting they should put some work into trying to integrate another 3rd party software renderer, and I was pointing out that that’s not a great use of anyone’s time in the long run.
Tom has posted some hints indeed, but all is still a bit vague. Having a more open discussion about this is more than welcome. We have been developing a Vulkan framework to replace our OpenGL engine, so we have quite a bit of expertise in house regarding this.
We wouldn’t mind sharing some thoughts about the direction you guys will be taking.
In my mind this is long overdue. OpenGL works fine today (though edge tables could be accelerated also), but is soon (in volume) not really cross platform available anymore. I would like the replacement, may it be vulkan/metal or whatever, to be lifted up on the prio list.