So I have about 10 ImageButton objects in an OwnedArray, each set with only a default image and nothing else. I even removed the listeners. When a button is clicked even moderately fast, I see a significant spike in the cpu usage. The button clicks are meant to produce sound, so fast clicks are expected from the user. On Xcode the cpu usage could go up an extra 30% just from fast button clicks. That’s not normal right? I also used Time Profiler in Instruments to measure this and also noticed the rise in cpu usage.
I thought if I tried it on a release build it will be different, but even in the release build there is that significant increase. What does this mean? I’m not quite sure how to interpret this.
It actually does not seem to be limited to buttons. I just tried clicking on any visible component and I see the same increase in cpu usage. I also don’t have any mouse event handlers active on these components. So now I’m even more confused.
Some general answers to these kind of observations, not implying you did any of these wrong, I can’t know:
Don’t judge performance from a debug build.
There is quite some overhead in debug builds, like asserts, turned off optimiser, initialising values
Don’t use the CPU meter from Analyser or the IDE to judge performance.
This is where a profiler is made for, e.g. XCode “Instruments”. The CPU meter is coarse and not well defined, what it shows (e.g. it could be 30% of your program, so it looks much, but since it is mostly doing nothing, it might not be a problem)
Don’t try to fix problems before they become problematic.
You add complexity and hard to read workarounds in your code, that will hinder your development process. This advise is captured in a famous quote from Donald Knuth:
“Premature optimization is the root of all evil (or at least most of it) in programming.” (Programming as an Art, 1974)
If after all that there is still a problem, let us know with a minimum example that helps to reproduce the issue and find a fix or workaround.
From your initial post, I think you are using 30% from your program’s CPU share for minor stuff like repainting the button in pressed or whatever. But 30% of a penny is not a big deal, 30% of your bank account is.
I did test a release build. I also did use a profiler on the release build, Time Profiler and Counters from Xcode “Instruments”, that’s what my final result is based on.
I did a few more tests to see if I can pinpoint it better. As I mentioned earlier, it turns out that this issue is not limited to buttons, but components in general. So I have all my components added to the PluginEditor, and I just added an empty component as well, like with only the default set up, so I can see if the result changes when I don’t have anything on it. So that’s a total of 3 components. The results below are based on the results from “Instruments”.
I did notice a few things:
When clicking any of these 3 components, cpu usage is even higher than during sample playback, even when like 80 voices are rendering (this is a sample library plugin). It shouldn’t be that way right? Or am I misunderstanding something here?
I noticed one component in particular that uses significantly more cpu than the others when clicked on, and I think this gets us closer to the issue. This component in particular holds the ImageButtons (with images not loaded in this case), as well as a .png image in the background. The other non-empty component holds no custom images, just the default sliders and toggle buttons.
I noticed that these functions in particular have the heaviest stack trace when the clicking happens:
Also, when I start clicking on that one component that I mentioned in point 2, the paint method of that component also appears here. In that paint method I call drawImageWithin, which also appears here.
Is the image being re-rendered after each click? Does each click repaint the whole component and its children? Is that what’s causing all of this?
The problem is that I’m not entirely sure what code to show you that can help. I literally just added an empty (default) component into the editor, and the same thing happens. The difference seems to be based on paint() maybe? I didn’t add any mouse event listener to the components themselves, only Button and Slider listeners. Of course the empty component has nothing but the default stuff when a component class is added from the Projucer.
So in the constructor I just have the components added and made visible:
There’s a setRepaintsOnMouseActivity function so maybe call that with false and see what difference that makes, I suspect none though as it should be false by default.
Maybe add a break point in repaint to see what is causing the repaints?
When you say 30%… of what exactly? as Daniel put it…
30% of a penny is not a big deal, 30% of your bank account is.
Although not my bank account, certainly not at this time of year!
A couple of painting tips…
If a component has children, never implement it’s paint method! To draw a background instead make another child and add it as the first child so it’s at the back.
If the component has no transparency and it’s paint method fills the entire component, call setOpaque (true);
If you can guarantee you never draw outside the bounds of the component (so replace those fillAll calls with fillRect (getLocalBounds())) then call setPaintingIsUnclipped (true) on each component you can.
If a components paint method is more expensive to draw than an image, and it’s not constantly having to be invalidated (say it’s a background that doesn’t change for example) then call setBufferedToImage (true) on the component.
Following on from the previous point, If the component is going to be regularly invalidated because it changes a lot, such as a component drawing a waveform don’t bother with setBufferedToImage as it still has to repaint anyway. However let’s say it’s a rotary or slider, and something else keeps making it redraw (maybe it’s not opaque and it’s over the top of the component drawing the waveform) you could call setBufferedToImage (false) when interaction starts on the component and call setBufferedToImage (true) again when the interaction has finished… not something I’ve actually tried but it should help. If you were to keep it drawing to an image while invalidating it you would have to keep redrawing the image and draw the image to screen!
This line probably decompresses the png image on each paint event, which is probably not what you want. What’s the resolution of your image? If it’s quite high decompression and scaling are going to eat your CPU. Buffering the component will help with that, but it would be much better to uncompress and resize the image just once and keep it in memory uncompressed.
Could you explain why that makes a difference? In my opinion drawing the background in the parent results in exactly the same performance as drawing a full-size child component in the back.
That is not the case. The ImageCache loads the image once into a shared memory, where it fetches the Image just from the hashed pointer. It even reuses the BitmapData, so no time spent copying, so if you are writing into an image from ImageCache, you would alter the cached image.
But you make a good point about ideally having a resized copy around. The resizing is an expensive task.
calling repaint() on a non-opaque Component will cause the parent component to redraw itself, since it cannot know it’s background without painting it’s parent.
If the background is now a sibling rather than the parent, it probably wouldn’t automatically repainted. I looked at void Component::paintComponentAndChildren (Graphics& g), but to be honest, I can’t say 100% if that is the case. But if so, it would result in a different behaviour, so one needs to review, what the objective is.
Using setOpaque (true) as often as possible helps to limit the impact of a repaint.
How could it know if a component is opaque or not? Keep in mind it has to draw the most background images first so it has to know this information about a component before it has started drawing the component. When you call setOpaque (true) you’re essentially saying I promise not to make any part of my component transparent at all!
Daniel has basically already said it, but basically if a child component has to redraw then it has to transverse up the parents to cause them to redraw, even if the parent has setBufferedToImage (true) called on it the image will be invalidated forcing a full redraw. If on the other hand the drawing is done in a sibling then the same is not true! (see here for more detail CachedComponentImage invalidation logic)
This is one of those gotchas that is really hard to realise what is going wrong so my recommendation is to never draw in a component that itself contains child components, basically separate container components and drawing components, and have drawing components only ever be the end node. There will of course be exceptions to the rule but it’s served me pretty well! I even brought this up during the Optimising graphics performance workshop at ADC with Jules and Tom, in an example they showed of setBufferedToImage they had indeed made the background draw in a child component and it turned out that while writing the example Tom had been caught out by this when he originally made the component draw the background - which goes to show how easy it is to get caught out by this!
hmmm… it’s probably naive of me to think this, but it seems like they’d scan the tree first to figure out how far they have to go into the component nesting to determine the rear-most component that needs painting, and only draw from that component forwards in the tree.
Component P fills the entire background it’s the parent, Component C is the child of P and it’s a button.
You click Component C and therefore it needs to redraw, as the edges of your button are rounded and the OS needs to draw an entire rectangle to screen (the size of your button) we must also draw Component P into the rectangle first (we might not need the whole of P but we at least need it in part or we wont fill the entire rectangle once we draw P on top).
Now let’s imagine that the paint routine of C does fill the entire rectangle, without us telling this explicitly to the JUCE framework it still has to assume that it needs to draw Component P. So when we say setOpaque (true) we are saying don’t bother drawing my parents because I’m going to fill the entire rectangle anyway!
Thank you all for the great help and all the tips. It’s much better now. I’ve incorporated setOpaque() and setBufferedToImage() as suggested, and I put the background image in its own component behind the others. Not using setBufferedToImage() was definitely one of the main causes of the cpu usage spiking.
There still remains one thing that’s not clear to me. Why does a Component seem to respond to mouse clicks by repainting itself and its children even though I did not set it up to respond to any mouse click events except on buttons (Button::Listener)? Is this normal?
I have a parent Component P, which has 2 children, Components C1 and C2. C1 is a background image, where in its constructor I call setOpaque(true). When I click on C1, it repaints itself and C2, which is not what I expected. C2 is transparent and I make it appear on top of C1. It also contains other child components, buttons, each with an image that does not change. For each button, I call setBufferedToImage(true). Now when I click on the transparent section of C2 itself, not its children, it repaints itself and C1. So, for the sake of testing, I added another sibling Component C3, this time a Button, and placed it away from C2, but still on top of C1. I also set C3 as opaque, and when I click on it, still, C1 and C2 are repainted. I tried calling setRepaintsOnMouseActivity(false) to see if that changes anything, and it doesn’t. So I’m not sure why these Components are responding to these click events by repainting.
I forgot to mention something. When I add a breakpoint in paint() in C1 and C2, I don’t see anything related to a mouse click. It starts from juce::JuceNSViewClass::drawRect(objc_object*, objc_selector*, CGRect). Then after a few other calls it passes by juce::Component::paintComponentAndChildren(juce::Graphics&) until it reaches paint(Graphics&). There’s definitely something listening to these clicks but I’m unable to spot it.
That specific stack trace appears to be for the button? but I think in this case it’s triggered on the mouse down because isBroughtToFrontOnMouseClick() returns true I think it would require these two function calls to be honest…
The only place I see these set in JUCE is in the TopLevelWindow, and the default value for these should be false so are you setting these anywhere yourself?
One thing to keep in mind is when using setBufferedToImage (true) you want to separate repaints of the image to screen and repaints of the image itself - I would refer to the later as when the image has been invalidated. In this case I think the image would be invalidated because Button::focusGained() calls repaint().
I am not setting those myself. But why is repaint() even being called? I understand it’s because repaint() is called in focusGained(), but what I’m wondering is why does it even do anything to that button when it wasn’t even touched?
I just want to make sure it’s clear that the specific stack trace that I sent was after clicking on the transparent section of Component C2 itself (referring to my earlier example), and that it was not triggered by a button mouse event.
Honestly I’m not sure if I’m just not clarifying the issue well enough or if I just keep misunderstanding what to do. For the sake of even further clarification, I created a completely new plugin project, where I have the default PluginEditor and PluginProcessor, then I added 2 Components, C1 and C2. In the pluginEditor I added C1 and C2 as children. I also added a TextButton C3. C1 is at the back and covers all bounds of its parent. C2 and C3 are in front of C1, but are away from each other.
C1 is a background image with setOpaque(true) in its constructor.
C3 is only a default button, with setOpaque(true). The PluginEditor’s paint() is empty. There is absolutely nothing else that I set on these Components. Everything else, except size, is in its default state. I use DBG() in each paint method to see when they are being called.
Now, when I click on any single child Component, it repaints itself and its siblings, why? If it’s a button that’s being clicked, I understand the reasons why it repaints when it needs to, but I don’t understand why I see Components doing the same in these other cases. Am I just worrying about something that’s normal or trivial?
I should clarify something here for when I refer to Components being repainted. When I set a breakpoint on repaint() and click on C1 or C2, it only shows repaint() being called from Button::focusGained(), then again after that from Button::focusLost(). I don’t see any indication of repaint() being called on C1 or C2, but, I do know that paint() for C1 and C2 is being called since I placed DBG() in each of them to indicate when they are being called.
Also, if the button, C3, is set as opaque, why does clicking it still repaint all of its siblings? Beyond that, it’s not even touching C2 but still repaints it. setOpaque() refers only to what’s behind the Component correct? It seems like I may not fully understand how to use setOpaque().
Also for C3, if I call setOpaque(false), just entering or exiting the button with the mouse repaints C1 only, so it seems like this applies to siblings as well, but clicking it will still repaint both C1 and C2. I would imagine that setRepaintsOnMouseActivity() is true by default for buttons, but setting it to false actually didn’t make a difference so now I’m not sure.
Button::focusGained(), then again after that from Button::focusLost()
One thing I hadn’t taken in before now was you said PluginEditor. This sounds like the kind of thing you expect of some DAW doing some seemingly weird things with focus in order to steal keyboard events whenever possible. What are you testing in? have you checked on different OS’s and hosts? have you checked in stand alone mode? Maybe as a sanity check have you also tried making a basic App to see if it behaves the same?