Acceptable CPU load of GUI component with Timer (repaint())? [SOLVED-ish]

Hello,

So I am about to enter the world of GUI and graphics programming, and I am looking at the best way to incorporate procedural graphics into JUCE.

If I configure the most basic component, let’s say a standard hello world component and run it, I couldn’t be happier with the CPU load. It barely registers.

But, If I add a timer to the component, even with nothing occurring in the paint function, and call repaint() at 60 hz, immediately the load jumps to ~40%. (according to htop).

Comment out repaint() and we’re down to an acceptable 1%.

Bring back in repaint(), have a paint function that draws an ellipse, and now my CPU is grumbling at ~60%

Am I doing something wrong, is this acceptable/standard? Should I be looking at drawing with OpenGL (or other methodology) if I wish to be doing any kind of animation at all?

Here’s the code, as simple as it gets:

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class MainComponent   : public Component, private Timer
{
private:
    int totalUpdates = 0;
    int posX = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
public:
    MainComponent()
    {
        setSize (600, 400);
        startTimerHz(60);
    }

    ~MainComponent() {}

    void update()
    {
        posX = totalUpdates % getWidth();
    }

    void timerCallback() override
    {
        ++totalUpdates;
        update();
        repaint();
    }

    //==============================================================================
    void paint(Graphics& g) override
    {
      // (Our component is opaque, so we must completely fill the background with a solid colour)
        // g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
        // g.setColour (Colours::white);
        // g.drawEllipse (posX, 0, 50, 50, 2);
    }

    void resized() override
    {
        // This is called when the MainComponent is resized.
        // If you add any child components, this is where you should
        // update their positions.
    }
};

Good news, adding more Ellipses to the paint fuction (100 to be precise) only adds about 10% to the CPU load.

for (int i = 0; i < 100; ++i)
            g.drawEllipse (posX + (i * 10), i * 10, 50, 50, 2);

But that baseline of 40 - 60% caused (i think) by repaint() doesn’t appear good, especially since I want to port this to android, and I’m gonna need that CPU for the DSP

Did you test with a release build?

1 Like

Also you might want to look into enabling OpenGL if you want to do that kind of graphics that continuously repaints itself with a timer. I think the generally recommended approach with Juce if you don’t use OpenGL is to repaint only when really necessary. (That can be a real pain to actually implement, though…And if I’ve myself often just resorted to using timers that do the repaint call. But I usually use a more conservative time interval for the timer.)

Okay, wow. I compiled with make CONFIG=Release and that brought the CPU load on a an empty paint function with repaint called at 60Hz down to about 6%.

The below = 13%

void update()
{
    posXtop = totalUpdates % getWidth();
    posXbottom = abs((totalUpdates % getWidth()) - getWidth());
}

void paint(Graphics& g) override
{
        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
        g.setColour (Colours::white);
        g.drawEllipse (posXtop, 0, 50, 50, 2);
        g.drawEllipse (posXbottom-50, getHeight()-50, 50, 50, 2);
        g.drawEllipse (mouseX-25, mouseY-25, 50, 50, 2);
}

And the 100 drawEllipses = about 23%

I’m not in a position to say what is acceptable. But that is certainly workable for now.

I’m going to build my idea this way for now, then maybe in the future look into OpenGL.

…Or maybe also look at SDL and Cinder.

You can quite easily enable OpenGL to be used for any Component in Juce. The paint code doesn’t even need to change. But since using OpenGL that way is quite high level, it might not always work optimally. You will just have to test it out to see if it works and helps. edit2 : now that I’ve investigated this a bit further, it seems it’s not helpful at all for regular paint() code. You really do need to write actual OpenGL code to get any benefits.

edit : I am now testing the Juce OpenGLContext in my plugin. Enabling it actually increases the CPU usage, while also taking GPU resources… :sweat_smile: So clearly these things are not so simple.

Yeah one issue with OpenGL in JUCE is the renderer still performs the edge-table rasterization of geometry, using the OpenGLContext simply caches the results to GL textures. Makes re-using the caches super quick but the rasterization process doesn’t change from the software renderer. If you have components that are calling repaint often you can still see a pretty decent CPU load for that reason

2 Likes