Juce constantly freezes and is not suitable for animations (both native OpenGL and software renderer)

The animation we are testing here is a looping one, so it’s pretty easy to see the inconsistencies. I take the time from Time::getMillisecondCounterHiRes when the app is constructed and then do this in the paint() method :

void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll(juce::Colours::black);
    g.setColour(juce::Colours::white);
    double t = juce::Time::getMillisecondCounterHiRes();
    double diff = t - mAnimStartTime;
    float xcor = fmod(diff*0.5, 1000.0)*0.5;
    g.drawRect(10.0+xcor, 10.0f, 100.0f, 100.0f);
    
}

If there’s some error in that approach, please do tell.

1 Like

No it’s hard to argue with that :grinning_face_with_smiling_eyes:

I would suggest running a profiler to see what might be going on.

I guess the next thing to test would be to see if it’s the Timer callback that isn’t being called consistently…

1 Like

I’ve never used this before but I wonder if you could call this on every frame then if there is a particularly long wait between two frames hopefully you can see what happened in between?

I put an assert treshold at 40Hz according to the diff between 2 consecutive frames and it was ok, so it seems there is no long wait.

1 Like

I wonder if the problem is getting the thing to screen, i.e. all the callbacks are occurring such that if they were to draw that regularly it would be fine but somewhere along the line the OS is dropping the frame?

The timer callbacks seem to be OK. The chart shows the time differences between the Juce timer callbacks at 60 Hz. (For about 700 data points.) The expected interval is at about 16 milliseconds and Windows/Juce seems to handle that fine.

edit : Those were taken from the AnimatedApp update() method. I now also tried the paint() method and got similar results. This would seem to indicate the problem is related to when Juce/Windows renders/puts the image on the screen. Apparently that operation is occasionally interrupted/cancelled by something or there’s some random factor involved that sometimes makes that take more time than expected.

2 Likes

Perhaps it could be a trick due to the double buffering design of Juce, but i’m not an expert in this. Because after looking again the artefacts look like flickering.

Have you retried this with OpenGL? I get very different results

  1. using the software renderer and calling repaint from a Timer
  2. like (1) but with an OpenGL context doing component painting
  3. like (2) but with a HighResolutionTimer

For (1) I get a very obvious, regular stutter. For (2) I get an occasional, slighter, irregular stutter. For (3) I get an even slighter stutter, which I’d blame to the interval of 17 instead of 16.66. It may be that it’s just implausible to get the message thread synchronized with the screen refresh when they’re at the same rate.

in iPlug2 using VSync with D3DKMTWaitForVerticalBlankEvent was the key for us to get good frame rates, WM_TIMER was useless

1 Like

Yes I tried with OpenGLContext and I got the same result as you meaning (2) was better than (1).
With IPlug2, animations are much smoother it could be great if the Juce team could invest a bit in this VSync with D3DKMTWaitForVerticalBlankEvent feature pointed by Oli.

Right, even if the timer callbacks seem to be coming to the Juce code at a pretty consistent rate based on my tests, that of course doesn’t mean those are synced well to the display monitor refresh rate!

And it’s worse with the software renderer, because you have the imprecision of the timer callback plus the imprecision of the call to paint(). I guess it would work better if the OpenGL loop called paint() unconditionally at the swap rate -I expected setContinuousRepainting to have that effect but it doesn’t seem so.

edit: What did work was taking out the timers, not setting continuous repainting, and calling repaint() at the end of paint(). It doesn’t seem right to me, but it happens that the painting job polls needsUpdate even for continuous repainting, so the loop keeps running but it does nothing unless the cachedImage is invalidated. Calling getCachedComponentImage()->invalidateAll() also works -I’m not sure which one should be used.

With openGL when setting setContinuousRepainting(true) or manually calling repaint() at the end of the paint() function my cpu is full with a call to the deactivateCurrentContext() function taking 90% of the resources when profiling. It’s another pb but it’s quite pbmatic as well.

Yep, the context is acquired and released per frame -I don’t know if that’s evitable or not. It’s a different problem indeed, the efficiency of the OpenGL implementation in general. I think the stutter we were seeing here was not about efficiency but synchronization. I don’t see another way to ensure continuous synchronization to the screen refresh rate than making the process advance directly on the rendering thread. If you trigger each frame from the message thread, there will be hiccups. I still find the semantics of setContinuousRepainting rather unintuitive -it just means the thread is not suspended, but it doesn’t really continuously repaint.

1 Like

Yep, the context is acquired and released per frame -I don’t know if that’s evitable or not

I’m pretty sure it is. This issue is not happening everytime, for example when I enable the power saving mode in Windows it disappears. The pb is that for now I can’t see anyway to use properly openGL without having my fans running at 100%. And this issue is not happening only for me, as I mentionned in the previous post. So for now I prefer to stay with the software renderer but it would be great if the Juce team could take a look about this issue too.

1 Like