Hi, this smells like a classic problem with an easily solution but I cannot seem to figure it out nor find help here.
I have a slider on top of a visualization component (animated, repainted on a timer). The slider bounds is strictly contained in visualization bounds. When I move the slider, it gets immediately repainted and repaints the part of the visualization that’s underneath, but not the rest of the visualization, which gets repainted at the next timer tick. This leaves unpleasant transient glitches at the boundaries of the slider.
If I understand correctly I need the slider’s paint to be synchronized with the visualization’s paint. Am I missing an obvious way to do this?
We have a somewhat recent addition for this purpose, the VBlankAttachment class.
You can use this instead of a Timer and its callbacks will be synchronised with the current monitor’s refresh rate, so there is a good chance this would solve your problem.
You can add a VBlankAttachment member to the visualisation Component and have it call your Component’s update function, something like this.
class MyVisualisation : public Component
{
public:
// [...]
private:
void update()
{
const auto needsToRepaint = updateState();
if (needsToRepaint)
repaint();
}
// [...]
VBlankAttachment vBlankAttachment { this, [this] { update(); } };
};
Is there also a way to get the refresh rate? We want to keep the number of repaints under a certain threshold. There are monitors out there with 500Hz refresh rates. Getting our update function called 500 per second seems like a giant waste. Even at “just” 140 Hz, we would like to skip every second update call.
Getting the refresh rate would definitely be great, but failing that you can always compare time between calls and work it out, then if a minimum time has not elapsed, don’t repaint.
Edit: it may even be beneficial to do it this way. Then if frames are missed for whatever reason, you can keep your animation in sync.
Unfortunately, getting the current time and comparing it depends too much on the OS. On Windows, we observed that sometimes the millisecond counter only refreshes once every 20ms.
We currently use a timer at 60Hz, but that is a bit janky and stutters sometimes. Using the VBlankAttachment sounds great, but then we need to know the current monitor refresh-rate so that we can add our own skipper. Or maybe the VBlankAttachment class could get a parameter of the “ideal” refresh-rate and then skip update calls that happen “too soon.”
I’ve just examined the current VBlankAttachment code. The Windows implementation starts a high-priority thread that sleeps for 1ms (which probably won’t sleep that short, but rather a bit longer, depending on the current processes timer resolution, which the host controls), and then check if the vblank has happened. I think this implementation is a bit optimistic. It wants to wake up 1,000 times per second, check the vblank state, and then call the listeners.
Is there some demo program we can compile and run that demonstrates this? I have little hope that it works as intended.
You can set the resolution of the timer in windows, which by default is 15ms. timeBeginPeriod(1) is the call to look into. I’m not sure what the implications are of doing this in a plugin, as I believe this is a per process command, and in older versions of windows it may even be system wide. But to my understanding, this controls the resolution of all timer and thread wait style calls going on.
perhaps, although it should be relatively safe as this just controls how frequently windows ‘pings’ the things that are waiting on it if the time they originally asked for has been met or exceeded. I’m not sure it would be dangerous, just less room for extra time spent waiting on the times that were requested. I guess if there’s code using wait(1) when they really wanted wait(15), as the result was the same under default settings, you may see a large increase in CPU usage.
Sorry, but for our products, such a risk is unacceptable. We can’t just change the process’s timer resolution and hope for the best. We would cause unexplainable behavior in other plugins and possibly the DAW itself.
Depending on your particular overall layout, you can often get away with manually painting the overlapping component from within the paint method of the component underneath. So, don’t paint the slider from its own paint method but leave it where it is. Then inside the paint of the component underneath, grab the bounds of the component and paint it using the parent’s graphics object.
The easiest way to solve this is in your timer callback, copy the visualization data from your processor into a buffer and then call repaint. Then in your paint function, only access the cached data, never go back and access the ‘live’ data.
Even if you find a way to fix this slider issue, the OS can repaint any part of your component at any time. You need to make sure all intermediate paint calls are using the same data until your next timer interval.
The AnimationAppDemo uses the VBlankAttachment through the AnimatedAppComponent.
There is no way to obtain the current refresh rate through the attachment currently. Also, if you move the Component between monitors with different refresh rates, this frequency will change, so ideally, you would want to be notified of this event.
The best workaround right now would be using a counter and checking % 2 or % 3 etc, to figure out if you want to do an update. Measuring the time difference between callbacks would not be an unreasonable way to estimate what kind of framerate divisor you want to use.
Thank you very much to everybody for the pointers and advices!
^ This got me 99% there, and eliminated most of the glitchy frames. I think the 1% remaining is due to the time between the call to Visualization::repaint() (which caches the live data) and its actual paint(): in between the two, Slider::paint() can display new data under the slider while the rest of the visualization is still painted with old data. But it’s probably good enough for now.
I had a quick try (I can’t update to 7.0.3 just now). Using VBlankAttachment instead of my timer seems to solve it completely (at the cost of lots of additional paints, obviously), but I’m not exactly sure why.
Provided we’d have the exact framerate info, would I get the same result with a free-running timer set to the framerate? Or is the phase of the “synced timer” in VBlankAttachment important? In its callback I am calling repaint(); what about the time between repaint and paint?
The phase is important, using a Timer you could still miss your update window every now and then.
The paint calls are also synchronised to the VBlank events and they occur after the VBlankAttachments have been notified. So when you call repaint() synced to one given frame it’s guaranteed that paint() will be called for that same frame.
How about measuring it yourself using a highres timer and then applying an integer frameskip? I’d probably add only one dummy component for taking care of this repaint sync, implement my own listener scheme, and attach my components to that instead of replicating this all over the place. That might work for getting into the general area of whatever FPS you target, e.g. 30fps.
Now that I’m writing this I seriously wonder how all of this behaves in combination with variable refresh screens though…