VBlankAttachment

This class in theory sounded like a magic bullet, however I’m having a lot of issues with it.

In Reaper on Windows 10, it seem reapers UI will trigger vblanks as it needs them. Moving a fader in reaper will cause a lot of callbacks to trigger in the VBlankAttachment, much faster than what the rate was just following along with the screen.

This breaks the use case of using this class to time animations, as in real world use, the rate of the callback is going to be randomly jumping around - resulting in something less reliable than a timer.

Anyone have anything to contribute to this? I was pretty excited by this class, but now I’m feeling like going back to openGL again…

Why don’t/can’t you do your animations based on actual elapsed time between the callbacks?

I can’t find a stable enough reference point, impossible to workout what the framerate actually is, or which vblanks are in time with that target rate, how to accurately get elapsed time… etc.

Ultimately I just want to shift pixels at a regular time interval to create fluid motion, but outside of spinning a thread as the openGL class does, I can’t seem to nail this down with any JUCE classes.

I can’t get an average time between vblanks, as these are inconsistent. If I chose an arbitrary movement speed for my pixels, there’s a pretty good chance that the timing will vary as the relationship between that movement rate and the refresh rate will be out of sync (so movement might look like 2 frames, 2 frames, 3 frames, 2 frames, 2 frames, 3 frames, etc == stutter).

@bgporter3 just added VBlank support for his friz animation library, which I use. I believe it tracks time elapsed and on vblank tells the animation to update to the appropriate place. This allows animations to be fixed to time, see: animator/controller.cpp at master · bgporter/animator · GitHub

I haven’t checked out my usage of it in Repear — is the implication that sometimes you are getting a very slow frame rate as well?

I really like 60fps as a “smooth” default for animations. My only reservation with VBlank is any faster than that and it feels like I’m burning CPU making unnecessary frames. I guess some frame-throttling would be a possibility on faster screens…

Yes, I have also found that one still needs to measure the elapsed time from the last VBlank callback and adjust the animations accordingly in order for them to be smooth.

I also limit the refresh rate. If the time difference between a callback and the previous one is under a certain threshold I skip that callback.

I can confirm I am observing the same behavior even in the friz library. Make a long animation, click to start it, wobble a reaper fader and watch it wig out.

wobble a reaper fader and watch it wig out.

What’s the observed behavior? It speeds up when you move a fader? Is this on friz 2.1 using the constructor that takes a friz::DisplaySyncController?

Yes, using that constructor.

speeds up, slows down, extreme stutter. I think this is because under the hood it is establishing framerate by averaging recent history to the update calls (which are triggered via the vBlank), however these triggers are random once you move a fader in reaper.

If I didn’t know any better, I’d say the vBlank code in JUCE is being triggered when a new frame is called for, rather than when it’s actually getting executed, but this is all currently way over my head.

1 Like

Do you know if your paint calls execute fast enough to handle the potential frequency of vblanking? Not sure what your display fps is, but if it’s 60Hz, then if any paint call took longer than 16ms, you’d see some stuttering/slow down.

I’ll check out my friz stuff on Reaper soon!

It’s nothing to do with the paint, it’s the vBlank triggering itself. As a barebones example, try the following:

//.h
	juce::VBlankAttachment vba;

	void update()
	{
		auto now = juce::Time::getMillisecondCounterHiRes();
		auto delta = now - last;
		last = now;
		DBG(delta);
	}

	double last = 0.0;
//.cpp
MyComp::MyComp() : vba(this, [&]{update(); })  
{

}

In my case, my monitor is running at 144hz, so there is around 7ms between calls. If I move a fader in reaper, this number jumps all over the place while I am interacting with that fader. No paint calls here at all, just monitoring the response to the vBlank call.

EDIT: unless reaper itself is causing this overrun on paint time?

EDIT2: just checked, moving the fader causes the times to be both slower and faster (in the example above it varied from 2ms to 20ms when interacting with the host)

If I move a fader in reaper, this number jumps all over the place while I am interacting with that fader.

This doesn’t sound…wrong to me…

Unless you have a timer calling repaint, are explicitly calling repaint in the vblank callback, or using friz and explicitly calling repaint on an animation onUpdate, my understanding is that the frequency would be dictated by what else on the screen needs to be repainted (aka moving a slider).

So far, my VBlank stuff in Repear seems happy, but I’m using friz 2.0, haven’t updated to 2.1 yet.

It could very well be me misunderstanding things.

I had assumed that the vblank time would have been locked to the system refresh rate, and something trying to paint to screen would be delayed until the next vBlank. This is not the case here, as the vBlank appears to be called between actual frames on my monitor.

Were you calling repaint() on your animated component in the friz onUpdate or the vblank callback? That’s what works for me.

Looking at Peer implementations, I also don’t fully understand when onVBlank is called. My understanding was that it is modulated by modern OSes (VRR on windows, adaptive sync mac). I was under the impression that in those cases, to achieve fast rates, you’ll want to inform the OS that your app actually needs to be repainted. But I could be oversimplifying, and would be happy to be corrected…

Do you have any examples of your work I can play with? Builds are fine, I don’t need source code.

My standard is something like the histogram in fabfilter products. I can achieve this with OpenGL, but I’ve never been able to crack it on the cpu alone.