gfx/backing.h
The backing store a window peer paints into, and the one place scale lives.
#include <sonore/gfx/backing.h>- 1type
- Guides: The native editor
On this page
Design notes
Why this exists
All three peers -- Win32, X11, Cocoa -- had the same forty lines: hold a Bitmap, resize it when the window resizes, clear the ground, paint the tree, track whether the next frame is a full one, and hand the changed rectangles back to the platform. Three copies of one algorithm, which is the shape every drift in this SDK has had.
It is pulled out here because of what had to change in it.
HiDPI
The wrappers already accept a scale from the host -- CLAP's gui.set_scale, VST3's IPlugViewContentScaleSupport, LV2's ui:scaleFactor -- and already resize the window to logical * scale DEVICE pixels. For the webview editor that is the whole job: the browser engine draws each CSS pixel as scale device pixels itself.
The native editor had no such engine, and nothing was doing the scaling. The window doubled and the component tree was laid out to the new DEVICE size, so at 200% a 620-point editor became a 1240-unit editor drawn at 1240 device pixels: the same controls at the same pixel sizes, which on that display is HALF their intended physical size, with twice as many of them on screen. The interface did not scale. It got denser and smaller, which is the exact failure a plugin developer means by "it looks wrong on my 4K laptop".
The fix is one idea consistently applied: the component tree works in LOGICAL units and knows nothing about scale, and this object is the only thing that knows the difference.
- the bitmap is
logical * scaledevice pixels - the content's bounds are the LOGICAL size
- the root paint runs under a scale transform, so every path, every stroke and every glyph is rasterised at device resolution -- the text is SHARPER at 200%, not bigger and blurrier, because the glyphs are outlines rather than a stretched bitmap
- damage is tracked in logical units and converted to device rectangles once, here, on the way out to the platform
- a pointer position arrives in device pixels and is divided on the way in
The alternative that does not work
Painting at logical resolution and stretching the bitmap by scale. That is what a plugin does when it "supports HiDPI" by not supporting it, and it is worse than doing nothing: the interface is the right physical size and visibly soft, which readers notice on text long before they notice anything else.
Types 1
A window peer's backing store: the device-pixel bitmap, the scale, and the paint of the component tree into it. See the file comment for why scale lives here and nowhere else.
The peer feeds it platform facts (setDeviceSize from a resize event, setScale from the host, pointer positions through toLogical) and calls render() when it is asked to paint; what comes back is the list of device rectangles to blit, empty when nothing changed. The content component is not owned and must outlive the Backing, or be replaced with setContent first. UI thread only, like everything that touches the tree.