Stage 3 — Scale-aware Rendering
Concept
Wayland configures surfaces in logical coordinates. A 32-pixel logical bar may need a 32, 48, or 77-pixel physical buffer depending on output scale. Drawing text directly at the logical size and asking the compositor to enlarge it makes glyphs blurry.
Patin requests wp_fractional_scale_v1 and wp_viewport when available. The
preferred scale is expressed in 120ths: 120 is 1× and 180 is 1.5×. Physical
dimensions are the logical dimensions multiplied by that scale and rounded
upward. The viewport destination remains the logical size and wl_surface
buffer scale stays 1, as required by the fractional-scale protocol. Without
both optional protocols, Patin uses the integer wl_output scale.
Rendering boundary
CpuRenderer is an internal backend with no Wayland knowledge. It:
- creates a physical tiny-skia pixmap;
- fills the background and draws a two-logical-pixel accent rectangle;
- asks cosmic-text to shape and rasterize a right-aligned monospace clock;
- scales font metrics and padding with the physical scale;
- converts tiny-skia RGBA storage to Wayland’s little-endian ARGB8888 canvas.
Shared-memory pool slots may contain alignment padding after the visible pixel payload. The conversion therefore writes exactly the rendered bytes into any slot large enough to hold them and leaves trailing allocation bytes untouched.
The font system and Swash glyph cache live for the process lifetime. This keeps font discovery and glyph rasterization state out of the frame loop.
Event and frame flow
A Calloop timer checks local time once per second, but requests a redraw only
when the displayed HH:MM string changes. Configure and scale changes also
request redraws. Each submitted buffer requests a Wayland frame callback; if
state changes while a frame is pending, one redraw is retained and submitted
after the callback. The SCTK slot pool can recycle shared-memory storage after
the compositor releases its buffers.
Important functions
Scale::physicalconverts logical lengths using protocol-native 120ths and ceiling division.CpuRenderer::render_baris the internal CPU renderer entry point.CpuRenderer::draw_clockowns cosmic-text layout and tiny-skia glyph compositing.Patin::request_redrawandPatin::framecoalesce updates around compositor frame callbacks.- The fractional-scale dispatch handler updates scale without depending on an output, device, or compositor name.
Verification
Verified on 29 July 2026 with Rust 1.97.1:
cargo fmt --all -- --check
cargo test --all-targets
cargo clippy --all-targets --all-features -- -D warnings
cargo run
grim /tmp/patin-stage3.png
mdbook build
git diff --check
Three unit tests passed: fractional physical-size rounding, RGBA-to-ARGB
conversion into a padded shared-memory slot, and zero-padded clock formatting.
On the active 1× output, Patin rendered a 1920x32 buffer for a 1920x32
logical bar. The screenshot confirmed that the clock was visible, right-aligned,
vertically centered, and separated from the desktop by the accent.
The timer crossed two minute boundaries during the live test and produced exactly one logged render for each new minute.
Fractional-scale verification
A nested compositor output was configured to 1.5×. Patin first submitted its 1× fallback while protocol events were arriving, then reacted to the preferred scale:
patin: rendered 853x32 buffer for 853x32 logical bar
patin: rendered 1280x48 buffer for 853x32 logical bar
The second dimensions are ceil(853 × 1.5) by 32 × 1.5. A raw screencopy of
the nested 1280x720 output confirmed the clock was sharp and correctly
right-aligned in the scaled bar.
The same source was then built natively on an aarch64 Wayland system and launched against its running compositor. It received a 2.4× preferred scale:
patin: rendered 509x32 buffer for 509x32 logical bar
patin: rendered 1222x77 buffer for 509x32 logical bar
This test exposed the valid shared-memory slot padding described above. After adding the regression test and general renderer fix, Patin stayed running at the fractional scale. Terminating Patin left both the compositor process and its Wayland socket alive.