Rendering optimization strategy

I need some advice on how to setup a rendering pipeline which can accomplish a full screen animation in the background with components being drawn on top of it. I’m building a sequencer and I want to have fun with the graphics and do a kind of paralax scrolling kind of like old school platformers when the user presses Play, but the software rendering is driving me mad with the performance. I really don’t want to scale it back, all I’m doing is drawing some primitives and moving them ever so slightly over time using repaint (set to the boundaries of only whats moving) calls triggered by a timer.

I’ve been thinking of using OpenGL since I want to take advantage of alpha blending capabilities and the quick primitive drawing and manipulations, but is this even possible if I have components on the top that are alpha blended, or would everything have to become an OpenGL component somehow?

Are there other rendering routes that I could look into to accomplish this? I was also looking at maybe trying QT and then getting my JUCE plugin integrated somehow, but I don’t even know how feasible this is.

Any advice appreciated!!

What exactly are you doing in your paint() routine?

Background is a full static image, and i’m repainting about 1/3rd of it at the bottom with triangles. filling them, and then moving them based on an offset. Total of about 20 calls to g.fillpath(…) and g.strokepath(…) per frame

Trying to achieve an effect similar to this but with fewer layers and simpler graphics: https://www.parallelcube.com/wp_install/wp-content/uploads/2017/11/parallaxsample.gif

On top of this is a step sequencer which is semi-transparent.

Are you fetching the background image from memory each time it is painted?

Code below. The image is loaded once in the constructor of the plugin.

EDIT: added the repaint section in the timer, set to 20 fps right now

        double time = (Time::getMillisecondCounterHiRes() - m_iTimer);
        double delta = time / 1000;
        delta /= (60 / BeatConnectEditorSettings::s_poTrackManager->m_poTrack->m_fBPM);
        m_iDelta = delta;
        repaint(0, getHeight() - (getHeight() / 3), getWidth(), getHeight());

Here is the main paint method

void BeatConnectAudioProcessorEditor::paint(Graphics& g)
{
    juce::Rectangle<float> rc(0, 0, getWidth(), getHeight());
    g.drawImage(m_oBackground, rc, RectanglePlacement::stretchToFit);

 //   paintStars(g);

    paintMountains(g, 3, getHeight() / 3, 0.7f);
    paintMountains(g, 5, getHeight() / 5, 0.85f);
    paintMountains(g, 8, getHeight() / 8, 1);
    
    g.setColour(BeatConnectEditorSettings::GetSequencerBackgroundColour().withAlpha(0.5f));
    juce::Rectangle<int>rc2(GetTrackComponent().getBounds());
    rc2.setX(rc2.getX() + 200);
    rc2.setRight(getWidth() - 40);
    rc2.setHeight(rc2.getHeight() - 10 - GetTrackComponent().GetViewport().getScrollBarThickness());
    g.fillRect(rc2);
 
 //   BeatConnectEditorSettings::DrawLogo(g);
}

Here is the “foreground” or animated part

void BeatConnectAudioProcessorEditor::paintMountains(Graphics& g, int amountOfMountains, int mountainHeight, float saturation)
{
    Colour colOutline = Colour::fromRGB(212, 77, 179).darker(0.5f);
    Colour colBackground = Colour::fromRGB(26, 8, 82).withSaturation(saturation);
   
    if (BeatConnectEditorSettings::s_poTrackPlaybackManager != nullptr &&
        BeatConnectEditorSettings::s_poTrackPlaybackManager->m_dblMasterDecibles > -100)
    {
        float range = 100; 
        float adj = 10 / ((-BeatConnectEditorSettings::s_poTrackPlaybackManager->m_dblMasterDecibles / range) * 100);
        colOutline = colOutline.withSaturation(adj);
    }

    Path path;
    float x1 = 0;
    float y1 = getHeight();
    float x2 = getWidth() / (amountOfMountains * 2);
    float y2 = getHeight() - mountainHeight;
    float x3 = getWidth() / amountOfMountains;
    float y3 = getHeight();

    float offset = m_iDelta * (amountOfMountains * 4);

    int i = 0;
    while (x1 <= getWidth())
    {
        x1 = (getWidth() / amountOfMountains) * i;
        x3 = (getWidth() / amountOfMountains) * (i + 1);
        x2 = (x3 + x1) / 2;
        
        x1 -= offset;
        x2 -= offset;
        x3 -= offset;
        
        if (x3 >= 0)
        {
            path.addTriangle(x1, y1, x2, y2, x3, y3);
        }
        i++;
    }

    g.setColour(colBackground);
    g.fillPath(path);
    g.setColour(colOutline);
    PathStrokeType type(PathStrokeType::curved);
    g.strokePath(path, type);
}