Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
DrawnUI for Rust
A UI engine for Rust that draws everything with Skia: layouts, controls you can subclass and paint, gestures, animations, caching, shaders and accessibility, on the desktop, on phones and in the browser from one source. Same family, controls and contract as DrawnUI for .NET and DrawnUI for React.
- Crate:
drawnui· API docs: https://docs.rs/drawnui - Live demo (the DrawnUI Hello app in the browser): https://hellorust.drawnui.net
- Release notes:
CHANGELOG.md· Accessibility:ACCESSIBILITY.md· What is ported from DrawnUI for .NET:PARITY.md
use *;
Platforms
| Platform | Drawing | State |
|---|---|---|
| Windows | Skia on OpenGL (winit + glutin) | done |
Browser (wasm32-unknown-emscripten) |
Skia on WebGL2 | done, live demo |
| macOS | Skia on Metal, paced by the display | done (Apple silicon) |
| iOS | Skia on Metal, 120 Hz on ProMotion screens | done on the simulator and an iPhone |
| Android | Skia on Vulkan, OpenGL ES as the fallback; winit GameActivity | done on emulators and a phone |
| Linux | Skia on OpenGL (X11 and Wayland) | built and run under WSL 2; a desktop with a GPU not checked yet |
What the engine has
- A control tree written with fluent builders; properties that invalidate only what they change; measure / arrange / paint, render transforms, opacity, clipping, z-index.
- Every DrawnUI cache type: Operations, OperationsFull, Image, GPU, ImageDoubleBuffered (bitmaps made on background threads on the desktop), ImageComposite. A lost GPU context is recreated.
- Layouts: Absolute, Column, Row, Wrap, Grid, decorated grids; templated lists with recycling and virtualisation.
- Controls: shapes, labels and rich text, buttons, an editor with IME, scroll with scroll bars and refresh, carousels (also with shader transitions), drawer, shell (pages, tabs, popups, modals, toasts), toggles, switch, checkbox, radio buttons, slider, progress, backdrop blur.
- Media: images (decoded off the frame thread), image tiles, SVG, GIF, Lottie, sprites, SkSL shader effects and transitions, SkMesh (custom vertex and fragment programs).
- Input: tap, pan, fling, long press, hover, context menu, mouse wheel and touchpad, keyboard and focus.
- Accessibility: screen readers on every platform (UI Automation, VoiceOver on macOS and iOS,
TalkBack, AT-SPI / Orca, an ARIA overlay in the browser), keyboard navigation with a focus ring,
selectable text, values a screen reader reads and adjusts. See
ACCESSIBILITY.md. - Animators: value, range, spring, ping-pong, pendulum, ripple, timers.
- About 700 headless tests, many of them ports of the .NET engine's tests with the same numbers.
Crates
| Crate | What it is | Version |
|---|---|---|
drawnui |
the engine: controls, layouts, caches, gestures, animations, accessibility, the desktop, mobile and browser hosts | 0.1.0-preview.2 |
drawnui-skia-safe |
Skia for Rust: rust-skia's skia-safe 0.153.3 plus SkMesh; drawnui re-exports it as drawnui::skia |
0.153.4 |
drawnui-skia-bindings |
the native Skia and its C bindings under drawnui-skia-safe; downloads prebuilt Skia from DrawnUi/rust-skia | 0.153.4 |
An app depends on drawnui only; the two Skia crates come with it. Skia's API is drawnui::skia
(the same as skia_safe, SkMesh included): never add skia-safe or skia-bindings to an app, a
program can link one Skia only.
Getting started
A new app: copy templates/app (an empty app with an icon, assets, a web page
and build scripts for the desktop, the browser and Android; it takes drawnui from crates.io), rename
myapp, build. Or by hand, in a new project (cargo new myapp):
Cargo.toml:
[]
= "0.1.0-preview.2"
[]
= "thin"
= "abort"
Skia needs no setup. The first build downloads it prebuilt for your target (about 11 to 25 MB)
from DrawnUi/rust-skia's releases: Windows x64,
Linux x64, macOS and iOS (Apple silicon, simulator), Android arm64 / armv7 / x86_64 and the browser.
Other targets or features (32-bit x86 Android, Linux / Windows arm64, Intel Macs) compile Skia from
source once: LLVM, Python and Ninja. Do not set FORCE_SKIA_BINARIES_DOWNLOAD: inside a crate it
makes skia-bindings compile Skia from source. SKIA_BINARIES_URL points the download at a mirror.
.cargo/config.toml, for the browser's link flags and Android's 16 KB pages:
[]
= "em++"
= [
"-C", "link-arg=-sMAX_WEBGL_VERSION=2",
"-C", "link-arg=-sMODULARIZE=1",
"-C", "link-arg=-sEXPORT_NAME=createDrawnUi",
"-C", "link-arg=-sEXPORTED_RUNTIME_METHODS=GL,HEAPU8,UTF8ToString",
"-C", "link-arg=-sALLOW_MEMORY_GROWTH=1",
# 1 MB stack and the overflow check: keep both (see "The wasm stack" below).
"-C", "link-arg=-sSTACK_SIZE=1048576",
"-C", "link-arg=-sSTACK_OVERFLOW_CHECK=1",
"-C", "link-arg=-sERROR_ON_UNDEFINED_SYMBOLS=0",
]
[]
= ["-C", "link-arg=-Wl,-z,max-page-size=16384"]
The web page loads the build and drawnui_host.js, which must come from the same drawnui version
(the template's web.ps1 / web.sh copy it from the dependency):
Per platform:
- Windows, Linux, macOS:
cargo run --release. Assets go next to the exe (the template'sbuild.rs),icon.icois the window and taskbar icon. Linux needs the X11 / Wayland and GL development packages. On Linux, rustc 1.94.1 can crash while printing the warnings of some dependencies (a compiler bug);RUSTFLAGS=-Awarningsavoids it. - Browser:
rustup target add wasm32-unknown-emscripten, emsdk 6 with its tools on PATH, then the template'sweb.ps1/web.sh; servedist/over http. - Android: cargo-ndk, NDK 26;
cargo ndk ... rustc --lib --release --crate-type cdylibmakes the activity's library (android_mainin the template'ssrc/lib.rs), packed by Gradle with a GameActivity. Until two fixes are released upstream, add the patched winit and android-activity (dev/winit-android,dev/android-activity-null-text) with[patch.crates-io]in the app's ownCargo.toml. - iOS:
dev/iosbuilds an.appbundle for the simulator or a signed one for a device.
The wasm stack (read before changing the web link flags)
The browser build runs on a 1 MB main-thread stack (-sSTACK_SIZE=1048576), with emscripten's
stack cookie check (-sSTACK_OVERFLOW_CHECK=1). Keep both.
Emscripten's default stack is 64 KB. Layout and paint walk the control tree recursively, so a deep page needs more. A wasm stack overflow is not caught by default: the stack runs into the memory below it and corrupts it silently. What that looks like, with the desktop build of the same app fine:
memory allocation of N bytes failed,memory access out of bounds,Aborted(),RuntimeError: unreachablein the browser console, often inside asset delivery or a font load;- errors that move: a page that works alone fails after another page was opened, because the memory was already damaged.
Diagnosis: link once with -sSTACK_OVERFLOW_CHECK=2 (checks every stack pointer move; slow, for
diagnosis only). An overflow then aborts at once with stack overflow (Attempt to set SP to ..., with stack limits [...]). If a page needs more than 1 MB, raise STACK_SIZE, or make the deep walk
iterative; never remove the check.
Benchmarks
Same draw list, N rounded rectangles per frame, 1609x1163 canvas, Chrome on Windows, WebGL2:
| Draw calls | Rust + Skia in one wasm module | CanvasKit 0.42 driven from JS |
|---|---|---|
| 2,000 | 59.9 FPS, 2.8 ms CPU | 60.0 FPS, 3.7 ms CPU |
| 5,000 | 60.0 FPS, 5.1 ms CPU | 59.8 FPS, 7.4 ms CPU |
| 20,000 | 59.7 FPS, 12.5 ms CPU | 47.8 FPS, 20.6 ms CPU |
Windows desktop (OpenGL): 20,000 draw calls at 60 FPS, 10 to 12 ms CPU. macOS (MacBook Air M1, Metal): 20,000 draw calls at a steady 60 FPS, 10.3 ms CPU; 2,000 SkMesh waves 6.1 ms CPU.
SkMesh waves (a triangle strip each, a vertex program with a time uniform, one draw call each), Windows desktop, 1250x875: 200 waves 60 FPS at 1.1 ms CPU; 2,000 waves 60 FPS at 6.4 ms CPU.
Payload of the bench (it links only the Skia code it uses):
| Build | raw | brotli |
|---|---|---|
gl |
3.93 MB | 1.15 MB |
gl + text layout (ICU, HarfBuzz) + SVG |
7.39 MB | 2.27 MB |
| CanvasKit 0.42 wasm, for scale | 7.32 MB | 2.28 MB |
Android: Vulkan vs OpenGL ES
A phone with a Mali-G57 MC2 (Vulkan 1.1), 1080x2408, a 90 Hz panel. The same release build, the API
switched with adb shell setprop debug.drawnui.gpu gl|vulkan; frame stats from logcat.
On heavy scenes Vulkan draws 21 to 35 % more frames; where both keep up with the 90 Hz panel, Vulkan needs 26 to 59 % less CPU per frame.
| Scene | Vulkan | OpenGL ES | Vulkan better by |
|---|---|---|---|
| 20,000 draw calls | 40.2 FPS, 24.8 ms a frame | 33.3 FPS, 30.0 ms | +21 % FPS |
| 2,000 SkMesh waves | 57.2 FPS, 17.5 ms | 42.4 FPS, 23.6 ms | +35 % FPS |
| 2,000 animated shapes | 91.6 FPS, CPU 5.4 ms | 91.6 FPS, CPU 7.3 ms | -26 % CPU |
| HelloRust: Recycled cells, flings | 91.6 FPS, CPU 1.45 ms | 91.6 FPS, CPU 2.71 ms | -46 % CPU |
| HelloRust: Uneven cells, flings | 91.6 FPS, CPU 0.94 ms | 91.6 FPS, CPU 2.32 ms | -59 % CPU |
| HelloRust: Lottie & GIF | 91.6 FPS, CPU 1.94 ms | 91.6 FPS, CPU 3.67 ms | -47 % CPU |
| HelloRust: Shaders | 91.6 FPS, CPU 1.60 ms | 91.6 FPS, CPU 2.75 ms | -42 % CPU |
Vulkan is the default on Android; OpenGL ES stays as the fallback and an option
(Ui::gpu_backend).
Building this repository
cargo test -p drawnui -p hellorust # headless tests, no window
cargo run --release -p hellorust # the demo
cd examples/bench && cargo run --release -- 20000 g0 # 20,000 draw calls
cd examples/bench && cargo run --release -- 2000 mesh # 2,000 animated SkMesh waves
dev/build.ps1 -Example hellorust -WebOnly # the browser build into target/web/hellorust
The workspace builds the Skia crates from a checkout of
DrawnUi/rust-skia (branch drawnui-crates) next to this
repository (../rust-skia). Skia is compiled from source once per target and exported as a
binaries archive that every later build unpacks (dev/skia-binaries.ps1; -Web for the browser,
-Android <triple> for Android). LLVM, Python and Ninja are needed for that; on Windows keep the
target directory on a short path, Skia's build fails on long ones.
Every platform problem we hit and its fix, for anyone porting a Rust + Skia library:
solved-win.md, solved-wasm.md,
solved-mac.md, solved-ios.md,
solved-android.md, solved-linux.md.
For AI agents
skills/drawnui-rust/SKILL.md teaches an agent to build apps with
this crate (adding drawnui, the browser build and its wasm stack, the web host page, Android, icons,
cache types, accessibility). It is also served at
https://hellorust.drawnui.net/skills/drawnui-rust/SKILL.md, with llms.txt / llms-full.txt at
the site root. Pair it with the DrawnUI framework skill from https://drawnui.net/llms.txt.
Repository layout
drawnui/the engine crate;drawnui/web/drawnui_host.jsthe browser host.examples/hello/a counter;examples/hellorust/the DrawnUI Hello app (20 pages);examples/bench/the benchmark scene;examples/images/a photo feed;examples/dungeon/Dungeon Run, a first-person runner drawn with SkMesh.templates/app/the starter app.dev/build, release and platform scripts.
License
MIT, see LICENSE.