denise-macos
An embeddable Cocoa view backend for Denise, a direct-rendering UI toolkit in Rust for embedded Linux and systems without a desktop environment.
Not a way to ship Denise on a Mac.
denise-winit already previews on one, and
a desktop application should use a desktop toolkit. This exists for the same reason
the Win32 control does: an existing Cocoa application that wants a Denise panel
inside it, next to its own views, with the host owning the window and the run
loop.
#
#
DeniseView is the NSView subclass, ViewDelegate is what the host implements to
drive it, and ViewSurface is the denise::Surface behind a layer-backed context.
What is different from the bare-metal backends
- The host owns the run loop. There is no
runfunction here. AppKit decides when to draw and Denise answers — the opposite of the DRM backend, where Denise decides and the display follows. - Damage is real.
setNeedsDisplayInRect:genuinely limits what gets composited, unlike a page flip where the whole buffer goes regardless. So the rectangles the tree produces are worth passing on rather than rounding up to the whole view. - Points are not pixels. A Retina view is 2 physical pixels per point. Denise lays out in physical pixels throughout — the conversion happens once, at this edge, and nothing above it needs the scale factor to hit-test.
- There is already a cursor. The host's window system draws one, so the
composited sprite stays off:
Ui::show_cursor(false), a decision that sticks rather than one the next mouse move overrides.
How a frame reaches the screen
Two IOSurfaces, shown alternately. The rasteriser draws into whichever one the
compositor is not showing, and present swaps them; the view hands the new one
to its CALayer as contents, where CoreAnimation reads it in place.
Nothing is copied between the tree and the screen.
The pair is not only about tearing. Assigning the same object to contents
tells CoreAnimation nothing — the property has not changed, so it has no reason
to look at the buffer again, and the window shows its first frame for ever while
the application draws happily into memory nobody reads. Two surfaces means every
present assigns a different object, which is a change it cannot miss.
Consequences worth knowing:
- The buffer is two frames old, so
acquirereportsBufferAge::Frames(2)andDamageTrackerwidens the repaint to match. That is the case it exists for. presentis what publishes. The view calls it after the delegate returns, so a delegate that only paints keeps working — but a host driving aViewSurfaceitself must present, or nothing reaches the screen.
The alternative, a CGImage snapshot per frame, is copied whole on every commit
however little of it changed. On a 1040×720 view with one spinner: 9.2% of a
core against 0.6%.
Platform
macOS only; elsewhere the crate compiles to almost nothing. Built on objc2, so
the Objective-C bridging is checked rather than hand-rolled. unsafe is necessarily
permitted here; every block carries a // SAFETY: comment.
examples/embed.rs is a complete host — a window, a view and the run loop:
cargo run -p denise-macos --example embed
Where this sits
Wraps denise-ui. The Windows equivalents are
denise-win32 and
denise-activex.
Status
M5 complete, run on real hardware. Part of Denise — see the repository README for the whole picture.
MIT licensed.