Performance
What a visual costs, how to cap it, how low-power mode trims it, and how to measure it on your devices.
A visual is a small Rust engine computing one frame, plus the platform drawing it. Views already do the cheap things for you: they pause off screen and in the background, and draw nothing extra while paused.
Cap the frame rate
maxFps limits how often a view draws, on an exact grid (30 on a 60 Hz or 120 Hz display is exactly 30). The animation still runs on real time, so a cap lowers smoothness, never speed. Ambient visuals look fine at 30. An FX Spec can carry its own cap in performance.maxFps, and the lower cap wins.
Many small views
A chat list with an orb per row, or a grid of thumbnails, is many views at once. Views handle it:
- on the web, every view on a page shares one animation-frame loop;
- a view whose short side is under 48 px (pt, dp) draws at 30 fps by default; your
maxFps, or the spec'sperformance.maxFps, wins; - on Android, a view scrolled out of a clipping parent (a
LazyColumnrow, say) stops drawing.
Measured on the web, 50 orbs at 32 px went from 869 to 459 ms of work per second, with no dropped frames. For more than about 100 views, pass paused and show one still frame each; animate only the one that matters.
Low-power mode
When the device is in Low Power Mode (iOS) or Battery Saver (Android), views switch to low power automatically. On the web there is no reliable signal, so pass lowPower: true yourself when your app knows. In low power:
- the frame rate drops to 30 fps;
- the costly finishes are shed: glow by default, or exactly what your FX Spec lists in
performance.lowPower.disable.
{
"fxSpec": "1.8",
"object": "orb",
"pattern": "working",
"materials": {
"glow": {
"strength": 0.5,
"mode": "blur"
}
},
"performance": {
"maxFps": 60,
"lowPower": {
"maxFps": 30,
"disable": [
"glow"
]
}
}
}
On iOS and Android, lowPower follows the system by default; force it with .on / .off (SwiftUI) or ON / OFF (Compose). On the web it's a boolean you set, for example from watchLowBattery() where the Battery API exists (Chromium).
Know the cost
Every pattern and material combination has an estimated cost class (light, medium, heavy) from the number of elements it draws, how much of the canvas they cover, and how much blur they need. estimateCost(pattern, size, overrides) from @sinua/core/dev returns it (the third argument takes engine keys, as the Studio exports them), and the Studio shows it as a badge. Glow is by far the biggest multiplier.
Low power:
import { mount, watchLowBattery } from "@sinua/web";
const canvas = document.querySelector<HTMLCanvasElement>("#orb")!;
// Low power: the spec's `performance.lowPower` block if it has one, else 30 fps with
// glow off. The Web has no reliable OS signal, so the app decides:
export const fx = mount(canvas, { pattern: "speaking", lowPower: false });
// ... e.g. from the Battery API where it exists (Chromium): low below 20% and unplugged.
void watchLowBattery((low) => fx.update({ lowPower: low }));
A frame-rate cap:
import { mount } from "@sinua/web";
const canvas = document.querySelector<HTMLCanvasElement>("#orb")!;
// Cap the frame rate (the clock keeps wall time, so the motion's speed is unchanged).
export const fx = mount(canvas, {
pattern: "breathing",
maxFps: 24,
onFrame: ({ dtMs, computeMs, paintMs }) => {
if (computeMs + paintMs > 8) console.debug("slow frame", { dtMs, computeMs, paintMs });
},
});
What Sinua adds to your app
Measured on the release build of 0.1.0-beta.8:
| Platform | What ships | Size |
|---|---|---|
| Web | @sinua/core (engine inlined) | 511,877 bytes, 384,034 gzipped |
| Android (arm64) | the engine library | +1.02 MB to download, +2.42 MB installed |
| iOS | the engine library | about +1.07 MB to download, +2.52 MB installed |
| Voice (iOS, Android) | WebRTC, only with a voice package that uses it | about +5.8 MB more |
The dev tools live in @sinua/core/dev on the web and in development builds on iOS and Android, so they aren't in these numbers. The npm package is larger (about 870 KB) because it carries both engines; that's install size, not what your app ships.
Measure on your devices
The cost class is a guide, not a measurement. The bench apps run a fixed set of visuals on a real device and report frame times, dropped frames, the hitch ratio (Apple's ms-per-second measure) and CPU, at normal and low power. Run them on your slowest supported phone. The repository's docs/bench.md has the steps for iPhone, Android and the web.