I think of it as as way to prepare data between paint calls. You can repaint using a timer and just do changes to the data in vblank callback, or just use the vblank callback to do the repaint call.
This also means you can use it to avoid paint calls if the data hasnt changed, so its like a nice way to check if its worth doing each callback.
Good question, I always think of this as as “a hook right before the next paint() call propagates down from the peer to your components.” So maybe “immediately before the next refresh” or “on next refresh” is more accurate (depending on the implementation).
Reading code again, vblank callbacks fire right right before the OS is notified about what’s dirty, it’s clearest on windows:
void onVBlank() override
{
vBlankListeners.call ([] (auto& l) { l.onVBlank(); }); // your callbacks, if any
dispatchDeferredRepaints(); // tell the os to invalidate rects
if (renderContext != nullptr)
renderContext->onVBlank(); // D2D calls peer.handlePaint()
}
So basically, it’s a “last chance” for a component to mark itself as dirty (via repaint) right before the cascade of paint calls happen.
Inside the vblank callback, I often just test if a boolean has changed (for example a parameter changed which means a visualization needs to change, or animation is running) and then call repaint(). I do treat it as guaranteed to be repainted on the next OS paint, but I haven’t “proven” this empirically.
I’m less clear on the exact mechanism on macOS, but it seems to be an AsyncUpdater calling the onVBlank, and that’s all wired up to a CVDisplayLink which is an OS notification that a paint is about to happen. This part seems like magic to me!