drawImage performance on Retina

Hi everyone, I know this is a recurrent topic but I haven’t found an optimal solution yet.

There is a huge difference in performance using drawImage() between Windows and MacOS with Retina. I know that maybe using OpenGL would be the best solution but for now, I’m trying to avoid this. After some research, I’ve found that it seems to be a CoreGraphics problem and not only affects JUCE. Reading through forum topics I’ve found that using Desktop::getInstance().getDisplays().getMainDisplay().scale and scaling the image using that scaling factor (double of the original resolution in the case of Retina) and then rescaling it to the original size before painting solves the performance problem. This approach seems to me a bit “hacky” and I don’t know if there exists a better solution.

First of all, the images are not painted in the message thread. These images are concurrently read (from message thread in order to paint them in the main context) and written(painted). The performance problem is not caused by concurrent accesses to the images because I’ve made them non-blocking and when I use the scaling “trick” the problem is solved.

I made a minimum working example if someone wants to check it out. It only contains the drawImage stuff but it’s enough for realizing that there is a huge difference:

Link: drawImage mwe

void MainComponent::paint(Graphics& g)
{
    auto scale = 1;
    //scale = Desktop::getInstance().getDisplays().getMainDisplay().scale;
    // This is done in other thread
    auto image = Image(Image::ARGB, getWidth() * scale, getHeight() * scale, true);
    juce::Graphics imageContext(image);
    imageContext.setColour(Colours::white);
    if (pos >= image.getWidth())
        pos = 0;
    imageContext.fillRect(Rectangle<int>(pos, image.getHeight() / 2, 100 * scale, 100 * scale));
    pos += 5 * scale; // initialized to 0 in .h

    // This is done in message thread
    g.drawImage(image, 0, 0, getWidth(), getHeight(), 0, 0, image.getWidth(), image.getHeight());
}

If scale = Desktop::getInstance()... is uncommented the problem is totally solved and I can’t see no difference between MacOS and Win (no profiling done with the mwe).

Is there any way that I’m missing that JUCE can handle this? The problem of this trick is that this scaling factor is propagated to everything that is painted in these images (i.e. text, lines, rectangles…) in case you want to mantain the sizes of these objects between MacOS and Win. And btw, why this trick works?

If scale = Desktop::getInstance()... is uncommented the problem is totally solved and I can’t see no difference between MacOS and Win (no profiling done with the mwe).

so you re saying it’s faster when you have a scale of 2 than a scale of 1 ?

When drawing an image of scale 1 into a native context of scale 2, the whole image needs to be scaled.
even pixel that you didn’t draw on.
With a scale of 2 for your image, only the fillRect is slower (but not much as probably optimized) but the whole image copy is way faster hence the sum is inferior.

Thank you for answering!

so you re saying it’s faster when you have a scale of 2 than a scale of 1 ?

Indeed, for Retina, a scale of 2 is much faster than no scaling. It makes sense that painting pixels I didn’t draw makes the whole thing slower but before painting the image in the message thread I have to rescale the image again back to the original size (no scaling). This approach makes me use this scaling factor in every object I paint in the image, is this unavoidable?

check StandardCachedComponentImage in juce own code

float scale = g.getInternalContext().getPhysicalPixelScaleFactor();
auto& lg = imageContext.getInternalContext();
lg.addTransform (AffineTransform::scale (scale));
1 Like
float scale = g.getInternalContext().getPhysicalPixelScaleFactor();
auto& lg = imageContext.getInternalContext();
lg.addTransform (AffineTransform::scale (scale));

Thanks! Going to check this out

It worked! Thanks a lot!