gfx/vblank.h
VBlankAttachment: a callback once per display refresh, on the UI thread.
#include <sonore/gfx/vblank.h>- 1type
- Guides: The native editor
On this page
Design notes
Why a plugin wants the refresh, when it has a 33 ms tick
The editor's own tick is the right clock for following automation and for meters that do not need to be smoother than the eye can track a number. It is the wrong clock for MOTION: a scope trace, a spectrum, a momentum scroll, anything that animates continuously. 30 Hz against a 60 Hz display shows every frame twice, and a tick that is not locked to the display drifts against it and judders -- a frame shown three times, then once. Drawing once per refresh, when the refresh happens, is the only way motion looks smooth.
How each platform hears the refresh
Windows- A worker thread waits on the display -- IDXGIOutput::WaitForVBlank on the primary output, or DwmFlush where DXGI is unavailable, or a sleep at the monitor's rate as a last resort -- and posts ONE message to a message-only window on the UI thread. One: a flag stays set until the UI thread has handled it, so a UI thread that falls behind sees one late frame rather than a queue of them. dxgi.dll and dwmapi.dll are loaded at run time, never linked.
macOS- CVDisplayLink, whose callback runs on its own thread, hops to the main queue with dispatch_async_f under the same one-pending rule. CoreVideo and libdispatch are looked up at run time. Compiled against the shim, never run.
X11- No display link. A timer at the monitor's refresh rate (XRandR's current rate, 60 Hz if it cannot be read), checked each time the X11 peer pumps its events -- so the callback rate is the lower of the monitor's and however often the host pumps the window. Said plainly because it is a real limit: an X11 plugin has no thread of its own on which to run a timer that may touch the UI.
What an attachment is attached to
A component, as in the reference: the callback runs only while that component is in a window (routerFor() finds one), so an editor that is closed but not yet destroyed is not animated for nobody. A null component means "always".
Types 1
Call callback once per display refresh, on the UI thread, for as long as this object lives and component is in a window.
The argument is the refresh's timestamp in seconds -- the same clock for every attachment, so two animations driven by it agree about the time.