Bad performance of drawing an Image in paint()

Hi all,

I’ve created the following very simple test case simulating what would happen if I draw a meter. It is basically drawing one part of the window with one colour and the other part with another colour. The timerCallback() is updating the “level” and repainting the window.

#pragma once

#include <JuceHeader.h>


class MainComponent  : public juce::Component, private juce::Timer
{
public:
    //==============================================================================
    MainComponent();
    ~MainComponent() override;

    //==============================================================================
    void paint (juce::Graphics&) override;
    void resized() override;
    
    void timerCallback() override;

private:
    
    // optional
    // juce::OpenGLContext openGLContext;
    
    juce::Image onImage, offImage;
    int level {0};

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};



//==============================================================================
MainComponent::MainComponent()
{
    // optional
    // openGLContext.attachTo(*this);
    
    setOpaque(true);
    setSize (600, 400);
    
    startTimerHz(60);
}

MainComponent::~MainComponent()
{
    // optional
    // openGLContext.detach();
    stopTimer();
}

//==============================================================================
void MainComponent::paint (juce::Graphics& g)
{
    auto w = getWidth();
    auto onImageHeight = getHeight() - level;

    // VERSION 1:
//    g.drawImage(offImage, 0, 0, w, level, 0, 0, w, level);
//    g.drawImage(onImage, 0, level, w, onImageHeight, 0, level, w, onImageHeight);

    // =======================================================

    // VERSION 2:
    g.setColour(juce::Colours::sandybrown);
    g.fillRect(0, 0, w, level);

    g.setColour(juce::Colours::darkgrey);
    g.fillRect(0, level, w, onImageHeight);

    
    
}

void MainComponent::resized()
{
    level = 0;
    
    onImage = juce::Image(juce::Image::ARGB, getWidth(), getHeight(), true, juce::OpenGLImageType());
    juce::Graphics gOn(onImage);
    gOn.fillAll(juce::Colours::sandybrown);
    
    offImage = juce::Image(juce::Image::ARGB, getWidth(), getHeight(), true, juce::OpenGLImageType());
    juce::Graphics gOff(offImage);
    gOff.fillAll(juce::Colours::darkgrey);
    
}

void MainComponent::timerCallback() {
    
    if (++level > getHeight())
        level = 0;
    
    repaint();
}

  1. Why is Version1 (drawing an Image created in resized()) significantly slower than Version2 (directly drawing in paint()). Sure, in that example, it doesn’t make any difference which solution to use. But what if I have a more complex Image to draw which would be expensive to render in every paint() call?

  2. using openGLContext.attachTo(*this) brings CPU usage down by a lot in Version2 but does nothing in Version1. Is there something special to consider when using an OpenGLContext with an Image?

Thanks
Stefan

want to add:

tested on macOS Big Sur (Intel) in release mode.

I believe the reason images are that costly it’s because the renderer samples the images, then draws them into the graphic context instead of just drawing the pixels it wants directly like in version 2. I believe it’s the same thing when you attach it to the open gl context.

But wouldn’t that mean setBufferedToImage() being useless?

Drawing images is much slower because it involves copying a lot of data. To draw an image, you have to iterate through every row and every column and copy each pixel over to whatever context you’re rendering to. Drawing a primitive shape like a rectangle is much faster since you can simply tell the context what range of pixels to fill, and what colour to fill them.

Assuming you didn’t change anything else, the images were still generated on the CPU in your resized() method - all OpenGL is doing at that point is copying a bunch of pixels to the GPU. With the version that does the drawing in paint(), the underlying OpenGL context will only be sending a few coordinates and some colour info to the GPU which will then run the shaders to actually draw the pixels. Which is what GPUs are optimised for.

Always do a benchmark test to see if setBufferedToImage(true) is beneficial - if you’re using an OpenGL context then it likely won’t be. If you’re drawing on the CPU and your paint() call takes longer than it would to draw an image, that’s when setBufferedToImage(true) comes into play.

1 Like

Okay thanks. That makes sense if copying is involved at refresh rate.
the above was a only simple example. The painting I am doing is more complex than just painting some colored rectangles. My initial idea was to paint them once on a ON- and OFF-Image at resized() and only repaint the changed region from those images. I’ll have to compare if my paint routine is still faster than cashing it to an Image and paint that.