Juce/Reaper/Win10 High CPU usage for bigger GUI's

I have a JUCE plugin with a large GUI (1086 x 660), 10 knobs using a custom look and feel class, and two small images for on/off switches. When I have the GUI open, I am getting about 85% usage according to the windows task manager, and when I close the GUI it drops to around 40% (Ignore the high cpu usage for DSP processing, right now I’m only concerned with the graphics cpu usage). The project is open source so you can reference the code here:

Specifically I am concerned that I’m not handling the paint() method in “PluginEditor.cpp” or the “myLookAndFeel.cpp” class properly. All the images were .png, but I changed the background image to a jpeg in a local branch since it’s a smaller file size, and still getting the same behavior. This doesn’t seem to be as big of an issue when running on Mac, only on Windows10, Reaper DAW. I’ve seen some other questions on the JUCE forum related to OpenGL, but didn’t quite understand what could be happening.

Thank you!

First of all I have to say that I know nothing about Ios Or MacOS.
I know very little about the JUCE Gui.
I am an old school Win32 programmer and completely understand Win32 WM_PAINT and the overhead involved.

With the above said, I will point some things out from a Win32 perspective, and make some guesses. If I ‘think’ I can save some CPU cycles, I will do it.

It looks like your ::paint() is repainting your entire interface. I believe if you move the mouse over your interface this will cause a ::paint to occur. I would drop a OutputDebugString() into ::paint to see if this is true.

I will focus on your background image.
It looks like you are ‘resetting’ background image on each ::paint. The background image I would think depends upon the state of your button. I would set the background image when the button is clicked.and do a repaint() to invalidate the entire window. You will never need to care about changing the image in your ::paint.

In your ::paint() do this:
juce::Rectangle ClipRect=Graphics.getClipBounds();
This will give you the actual clipped rect that really needs to be painted.
Say this rect produces 0,0,10,10 (the upper left corner of your window)

Don’t use drawImageAt() as you are redrawing the entire image. Use Graphics::drawimage() to only repaint the the part of the image that is really ‘dirty’, (0,0,10,10). I would think this would save CPU.

You are also setting your other 6 image buttons even if they do not need to be set (they already have the correct image), Again, I would not do this in your ::paint(), but rather do it where the actual change is made (I.e., for instance wherever it is you are setting processor.amp_state)

I am not sure what JUCE does when one sets an image for a button (ampOnButton.setImages()). It probably has no knowledge of what the current image is. It no doubts invalidates the area of the button which will cause another ::paint() to occur. It may be that every ::paint() call is causing another call to ::paint().

Take everything I have said here with a grain of salt as I am not positive about any of it.

By the way did you mean that this occur only in Reaper, meaning other DAWS on Windows do not exhibit this behavior?

1 Like

You are getting a lot of images from binary data in the paint routine. For optimisation it would be best to load them all when the relevant change is made outside the paint routine. Paint should be just for painting.

That being said, there is nothing there that should be causing problems as you don’t have a timer which is constantly calling the paint function. Are you sure you’re not running it as a debug build - when measuring cpu performance always use a release build.

Side note, I’d highly recommend you move to using an AudioProcessorValueTreeState to manage your parameters.

Edit:
I don’t think you call setClickingToggleState(true) on your image buttons. I don’t use image buttons but I’d imagine with this that you would only have to set your button images once as it will toggle between the “normal” and “down” image. Set the “over” image to nullptr for it not to change when you mouse over.

1 Like

Thank you for the feedback, I’ll try moving the image loading outside the paint routine and see if that helps. I’m building in Release mode. I’ll also learn about using the AudioProcessorValueTreeState for params and setClickingToggleState for buttons. I have a bad habit of coding the first solution I can think of and not going back and doing things properly!

Not sure if Reaper is the only DAW with this behavior on Windows, I’ll try another DAW to get more data.

Thanks!

Edit: I might still be seeing the high graphics cpu usage in Mac/Linux, it’s hard to tell with the default system profiler. Probably need to find a better way to diagnose cpu usage.

@rMidi [quote=“rMidi, post:2, topic:45322”]
n’t use drawImageAt() as you are redrawing the entire image. Use Graphics::drawimage() to only repaint the the part of the image that is really ‘dirty’, (0,0,10,10). I would think this would save CPU.
[/quote]

I think I’m halfway to implementing this, but the code I have is currently drawing the entire background image into the section defined by ClipRect (so if I adjust a knob, the background image is squeezed into the knob area). I’m guessing I need to first get only the ClipRect area of the background image, then apply it to the clipped area?

    // What I have in paint() now:
    juce::Rectangle<int> ClipRect = g.getClipBounds();
    g.drawImage(background, ClipRect.toFloat());

If you have a high cpu usage it is most likely that you repaint over and over again. Try JUCE_ENABLE_REPAINT_DEBUGGING.

@baramgb Thanks, I tried putting

#define 	JUCE_ENABLE_REPAINT_DEBUGGING   1

at the top of both my PluginProcessor.h and PluginEditor.h, but didn’t see the flashing that’s supposed to occur when repainting certain sections. Forgive me for the dumb question, but how to I enable this properly?

No.
You want:
drawImage (const Image &imageToDraw, int destX, int destY, int destWidth, int destHeight, int sourceX, int sourceY, int sourceWidth, int sourceHeight, bool fillAlphaChannelWithCurrentBrush=false)

Lets start over.
Your are calling setSize (1085, 660); in your constructer. That leads me to believe your amp_clean_png, amp_off_png and amp_lead_png are all 1085x660. They are your background. Am I correct?

So:
juce::Rectangle ClipRect=Graphics.getClipBounds(); g.drawImage (backgound,ClipRect.getX(),ClipRect.getY(),ClipRect.getWidth(),ClipRect.getHeight(),ClipRect.getX(),ClipRect.getY(),ClipRect.getWidth(),ClipRect.getHeight());

That is all that should be in your ::paint().

Next, do not do a .setImage() anywhere within your ::paint().

backgound:
Take your code out of ::paint() and put it into it’s own method:
'void WaveNetVaAudioProcessorEditor::SetBackgoundImage()
{
if(current_background==1&&processor.amp_state==1&&processor.amp_lead==1)
{
background=ImageCache::getFromMemory(BinaryData::amp_clean_png, BinaryData::amp_clean_pngSize);
}
else if(current_background==1&&processor.amp_state==1&&processor.amp_lead==0)
{
background=ImageCache::getFromMemory(BinaryData::amp_lead_png, BinaryData::amp_lead_pngSize);
}
else
{
background=ImageCache::getFromMemory(BinaryData::amp_off_png, BinaryData::amp_off_pngSize);
}

}’

(Apologies, as I can never get the back ticks to work for code).

Call ::SetBackgoundIMage() from ::ampOnButtonClicked(), ::ampCleanLeadButtonClicked(), ::loadButtonClicked() (Before your repaint()).

Next, do not do a .setImages() on your buttons with in ::paint(). Set the images for your button within your ‘OnClick()’ methods.

1 Like

Your ‘Editor’ window is the parent window.
Your Buttons are child windows of the Editor Window.

When you call .setImages() on your buttons it invalidates the window region where the button is.
Look at juce_ImageButton.cpp, void ImageButton::setImages(). At the bottom you will see
repaint(). This invalidates the entire window (a child window of Editor) of your button which
causes it to call it’s own ::paint(). Since your button is a child of a parent window the region
your button occupies will also become invalidated (‘dirty’) which will cause a ::paint() to occur
for the parent window (Your Editor window). I.e., ampOnButton.setImages() will invalidate 9,495,35,105 in the parent window (see WaveNetVaAudioProcessorEditor::resized())

Every time you are calling a .setImages() within your Editor::paint() you are invalidate that
region within the parent window, which may be causing another Editor::paint() to fire.

Make sense?

1 Like

@rMidi, yes! All that makes sense. Thank you so much for taking the time to spell it out for me, those changes drastically improved cpu usage. I’m going to consider this one solved and do some more JUCE learning.

Thanks all!

Edit: For the life of me I can’t figure out where to mark an issue as resolved. Will do that once I find the button.