Generate image THEN repaint if there's time

Hi there,

First of all, I am still learning… so patience is appreciated :slight_smile:

I have an issue where I’m working on a plugin that works great in Mac (M1), but in Windows, the more instances I open (VST3), the slower it makes the DAW until it just simply freezes 100% until I press stop.

There is a large area with a lot of paths being constantly repainted and re-coloured (24hz) and well, when a lot of instances of the VST are showing (3+) it just doesn’t work anymore.

To solve this, I wrote step() where I draw the image before painting it, so that I can simply paint the image in paint(), as below:

void visualizer_circle::step()
{
    juce::Graphics g(ringImage);
     ....draw image....
}

and in paint()


    g.drawImage(ringImage, juce::Rectangle<float>(0, 0, getWidth(), getHeight()));

so my question is:

How can I call step() in a parallel thread so that, if there was no time to draw the image, it skips the frame?
Calling it in timerCallback() doesn’t really solve it, and neither in paint()

Or - is there a way to smoothly have Windows draw these paths so that they don’t lag as bad (not using OpenGL)?

Also, I tried the ‘release’ version as I’ve read the ‘debug’ version can be laggy - but same results.

In your step function, why are you creating a juce graphics object? You should never be creating one, you should use the one passed to your editor’s paint function.

that’s not true. it can make a lot of sense to make your own graphics-objects if you want to draw things on juce::Images with the handy functions it provides.

about your question: the worst thing about painting images is resizing them. drawImage generally resizes it if you don’t actively turn it off. better use g.drawImageAt(). then you can define yourself when the image is being resized. being specific about it helps you understanding that these moments can be reduced to certain times, like resizing the window or whatever the context is. also you can use g.setResamplingQuality(juce::Graphics::lowResamplingQuality); before drawing images (if you really need to resize them a lot) to make that quicker. what that does is instead of using linear interpolation to rescale the image it then uses next-neighbor interpolation, which sometimes even looks better, because it creates sharper lines, even if they tend to look grainy ofc

1 Like

Interesting - so actually yeah I could draw my image on the constructor of the Component as the paths are static, so maybe redraw only in resize() but then, how would I go about changing the colours of the paths? That seems to be where I am 100% stuck. It’s about 100 paths and they grab colours from an array.

so if I do the juce::Graphics g(ringImage) in the constructor (and the resize() ), and then in paint I g.drawImage, where do I place the colours?

As of now I’m recoloring the paths in paint() but it simply doesn’t work at all.

The idea would be to create a function to recolour the paths - but the question is, where would I call it?

Oh sorry, I misunderstood that use case here.

If the image and paths being drawn to it are static and never change, then you can try only drawing it once in the constructor and then applying different affine transforms for scaling instead of redrawing, that may or may not help.

But if the paths change colors, then you’ll have to repost each time the colors change.

wow thanks! I’ll deffo try that.

so - the question to me is: where would I call my step() function? in paint() it just doesnt work, in timerCallback() doesnt seem to work either (i mean they work, but as I mentioned, once I have enough instances showing, the DAW cannot hold it)?

I think there are options to speed up the drawing so the buffering image is not needed.
I am drawing juce::Path objects as plot graphs of FFT or oscilloscopes at 30 FPS with no issues.

Adding the background image is a step that can make matters worse instead of solving the problem.

If you share your paint() method we can give hints where the bottleneck could be.

One idea is e.g. recycle the juce::Path objects so they don’t need to allocate each round and especially make sure they are not copied.

whatever you do, if you feel like it’s too slow you can consider outsourcing some functionality from step or paint into the constructor where you just fill a vectorjuce::Image with your desired image-states to free up performance, because then you’d only have to paint the finished images in paint. only problem is they will not be 100% continuous when changed then, which can look bad on plugin parameters.

but as daniel said: show paint and we can rethink together

2 Likes

Often such task can be divided into two parts: 1) to prepare images with some constant background to avoid its drawing again and again (of course such images can be prepared again after rescaling a plugin window) 2) to draw all details which are changing in time. Anyway, if I need to be sure that repainting is quick enough, I always measure execution time between different parts of code both in debug and release. Sometimes there are surprises.

1 Like

Thank you all for the help!

So I kinda re-thought the approach and will post as follow. First let me say what I’m after - it would be like a loudness metre in a circle, so the circle is divided in 12 “slices” (like a clock) and each slice goes from green to orange depending on the loudness of that very corner.

I created 1 component that creates the first (outer) “circle” as follows:

in my resized(); i created the paths

void visualizer_circle::resized()
{
    //resolution = 12 slices.
    segmentSize = ((1 / (float)resolution) * 2) * PI;

    auto main = getLocalBounds();

   //ring number is to prepare this component to be duplicated for its parent, so in this case, it's 0
    float thicknessTemp = 2.6 + (ringNumber/7.0f);

   for (int i = 0;i < resolution;++i)
    {
        segmentPath[i].clear();
        segmentPath[i].addCentredArc(main.getCentreX(), main.getCentreY(), (main.getWidth() / (thicknessTemp)) , (main.getHeight() / (thicknessTemp)), 0, (segmentSize * i) - (segmentSize / 2) - 0, (segmentSize * (i + 1)) - (segmentSize / 2) + 0, true);
    }


}

After, in paint, I am using a loop through all segmentPath to colour each of the slices depending on the loudness.

  for (int i = 0; i < resolution; ++i)
    {
        if (loudness[i] < 0.952278 && loudness[i] > 0)
        {
            g.setColour(juce::Colour::fromHSV(0.379 - (0.359 * loudness[i]), 0.87 - (0.25 * loudness[i]), 0.44 + (0.47 * loudness[i]), 1));
            g.strokePath(segmentPath[i], juce::PathStrokeType(10));
        }
        else if (loudness[i] >= 0.952278) {
           //if too close to 1, show red to display "clipping"
            g.setColour(juce::Colours::red);
            g.strokePath(segmentPath[i], juce::PathStrokeType(10));
        }
    }

So this seems to work GREAT! however, I want to have an effect where the ring kinda shrinks and fades in time, so for that, in my main component I just constructed this very component 10 times, and gave ringNumber 1 to 10.

The result - it works GREAT, but thinks get tricky - I need three visualisers to show simultaneously (for each axis), so now, from 120 paths, I have 360 paths being coloured 24 times per second.

This works GREAT with 1 instance showing, but the more instances I show, the worse it looks…

So basically I am trying to find a way to not call the component 10 times, but instead, see if there’s a way to “fade and shrink” the previous path before drawing a new one? or something like that :slight_smile:

I haven’t read through what you’re doing here in much detail, but I’ve used juce::SmoothedValue and a juce::ColourGradient for this before, just moving through the smoothing and getting the color along the gradient with ColourGradient::getColourAtPosition().

So I re-wrote the “paint” part to follow your suggestion, and seems that pre-filling an array with the colours to use and then using getColourAtPosition solved my problem! I’ll continue to test and update any further issues, but this might have been it! :slight_smile:

Thank you!