Skip to content

mobile: an idle phone presents 1 frame/s; transition hitches split, open-app and glass fixed - #27

Closed
ymote wants to merge 7 commits into
perf/mobile-shellfrom
perf/android-idle-repaint
Closed

ymote wants to merge 7 commits into
perf/mobile-shellfrom
perf/android-idle-repaint

Conversation

@ymote

@ymote ymote commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

The phone shell on the OnePlus 6T: an idle screen stops presenting at display rate, and the transition hitches are split into their causes, with fixes where the cause was found.

Based on perf/mobile-shell (#26). It bumps the makepad pin to OctoSense-org/makepad#6 (fix/android-idle-repaint @ f1db9eb) and adds the shell-side fixes. docs/perf-mobile-shell.md gets a third column.

Idle: the display now gets ~1 frame/s

Root cause (makepad fork).

  • GL advances its completed frame serial only when the completion fence is polled, and the GL render path never polled it.
  • Released retained-upload allocations therefore stayed in the ledger forever, and render_view set demo_time_repaint on every render (platform/src/os/linux/opengl.rs:409 at ad8f372). Every live pass repainted at display rate.
  • Diagnostic build on the phone: submitted=953 completed=0 records=598 terms=allocations.
  • The fix polls the fence, and on Android serves the debt on an idle vsync beat without painting. Details in makepad#6.

Shell: the status clock no longer forks date. The sampler thread forked date twice a second, which alone cost 11 % of a core.

idle screen perf (#26) this PR
Home 56.2 presents/s, 43 % CPU 1.0 presents/s, 3 % CPU
hosted-AppCard screen 18.6 presents/s (every frame ≥ 33 ms), 34 % CPU 1.0 presents/s, 3 % CPU
Recents 33.7 presents/s 1.2 presents/s
group window 44.1 presents/s 1.0 presents/s

The idle App screen's 66 ms frames were not the AppCard module. They were the forced repaint re-encoding the window gauss pyramid the module's glass had requested.

Frosted backdrop right side up

The fork's gauss helper still asked for a V flip on Android, although GL render targets have been top-left since d1a0eb1cb. It is fixed in the fork, and the shell's partial DrawGaussScene override is removed.

Verified on the phone: the clock behind the shade and Recents reads upright.

Transitions

What the frames are made of.

  • makepad#6 adds draw, wait (eglSwapBuffers) and gc channels to Android's frame monitor.
  • The burst sampler also reads SurfaceFlinger's acquire-fence time as GPU-ready latency.
  • The frames are GPU-bound. On the idle-fix build, open-app spent 25.5 ms/frame in eglSwapBuffers (worst 124 ms) against 9.8 ms of encode, with GPU-ready p50 at 94 ms.

Fixes.

  • A hosted module's glass blurs its own capture. WindowFrame gets a CaptureGauss, as the fork's WM has. Before, the AppCard kit's glass requested the whole WM window's pyramid on every frame that recorded the module.
  • The opening app zooms its last capture. It is re-recorded once openness settles, instead of a full-screen module draw on every animation frame.
  • The wallpaper shader computes one style per pixel.

Results. Burst stats are over intervals under 250 ms; the full tables are in the doc.

scenario before (#26): fps · p95 ms this PR: presents/s · burst fps · burst p95 ms · >33
idle-home 56.2 · 33.1 1.0 · — · — · —
open-app(tap AppCard icon) 27.3 · 66.6 6.0 · 31.5 · 49.9 · 5/22
idle-app-screen 18.6 · 66.9 1.0 · — · — · —
swipe-up-home 40.6 · 66.4 7.5 · 40.1 · 33.3 · 1/26
shade-pull+close 41.1 · 50.0 10.2 · 33.7 · 49.9 · 8/46
page-swipe(right then back) 55.3 · 16.8 7.0 · 50.1 · 16.8 · 1/30
recents(swipe-up-hold) 39.9 · 33.6 15.6 · 36.0 · 33.3 · 1/73
recents-idle 33.7 · 49.9 1.2 · 60.1 · 16.6 · 0/1
recents-to-home 48.6 · 33.5 6.2 · 34.1 · 33.3 · 1/21
island-demo(triple-tap clock) 52.9 · 33.3 5.5 · 55.7 · 33.2 · 0/25
group-open(tap Work) 47.3 · 33.5 26.8 · 52.6 · 33.3 · 2/105
group-idle 44.1 · 49.9 1.0 · — · — · —
group-close(tap scrim) 53.7 · 33.3 6.0 · 37.6 · 83.2 · 2/15

What still blocks 55 fps. A temporary A/B build switched parts of the frame off from adb (not committed): the wallpaper shader, the whole backdrop compositor (gauss scene + pyramid), or the home page.

  • None of them moves the shade, the Recents hold or Recents → Home beyond noise. Full-screen GPU content is not the bottleneck.
  • With the monitor off, the Recents hold is already 52–56 fps. The 36 fps above was measured with PerfGraph on.
  • The remaining misses are on the main thread.
    • The shade's finger phase runs at 33/33/50 ms.
    • The swipe home and Recents → Home alternate 16.7/33 ms.
    • A transition's first frame takes 67–150 ms.
    • Monitor per frame: event 6–13 ms (of which the shell's own channels explain ~2), draw 4–6 ms, wait (eglSwapBuffers) 8–15 ms, which together exceed the 16.7 ms budget.
  • Next step: a simpleperf CPU profile of the scene recording, which needs a debuggable build.

Tests

  • cargo test: 264 passed.
  • cargo test --features mobile-only: 261 passed.
  • The macOS desktop app draws with the new pin, checked with an in-app frame capture.
  • Fork: makepad-platform 252 passed. makepad-widgets has 5 failures that also fail on ad8f372.

Phone

The run set stayon and screen_off_timeout 1800000, and restored both to 30000 and stayon false afterwards. OctoSense is left on Home with this build.

🤖 Generated with Claude Code

ymote and others added 7 commits September 14, 2026 23:14
The status sampler thread forked `date` twice every second. On the phone
that fork+exec out of a 250 MB app process cost 11% of a core on its own,
more than half of an idle screen's CPU. `localtime_r` + `strftime` read the
same tz data with the same formats in microseconds.

Idle home on the OnePlus 6T: 19.2% -> 7.6% of one core.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…argets

`octosense/android_rendering.rs` overrode `DrawGaussScene` to ignore the V
flip the pinned makepad asked for on Android. The fork now returns no flip
for any backend (OctoSense-org/makepad fix/android-idle-repaint), which also
fixes what the override could not reach: the glass shaders sampled the
pyramid with the flip, so the frosted backdrop under the shade and Recents
was upside down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…window

`WindowFrame` scoped a module's overlays to its capture pass but not its gauss
requests, so the AppCard kit's glass asked for the WM WINDOW's pyramid: every
frame that recorded a module re-routed the whole window through a full-screen
gauss scene pass, a copy and a six-level pyramid (the whole open animation and
every idle App-screen tick; `gauss_mip_*` on the window in the census hot
list). The frame now carries a `CaptureGauss`, as the fork's WM `WindowFrame`
does: the pyramid is built from the capture while something inside asks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ettles

The foreground app's full capture was re-recorded on every scene frame while
`screen == App`, which is true from the first frame of the open animation: a
full-screen module draw and its capture pass under a quad that only moves. On
the OnePlus 6T the open animation's frames were GPU-bound (eglSwapBuffers
25 ms mean, GPU-ready p50 94 ms). The capture now refreshes every frame only
once openness and overview have settled; while zooming, the last capture (or
the first one, recorded on arrival or when stale) is the animation's source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The phone wallpaper computed both the iOS ribbons and the Android petals for
every pixel and mixed one away at weight zero: roughly ten exp/pow/sin terms
plus atan2 and length, full screen (1080x2340), on every Home and transition
frame and again in the compositor's gauss scene when glass is up. It now
branches on the style and returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f1db9eb: the GL completion fence is polled and Android serves retirement
debt without painting (an idle phone presents ~1 frame/s instead of ~56),
glass samples GL render targets right side up, a parallel-compile driver
reads the program cache, and Android's frame monitor gets draw/wait/gc.

`octos-app` (Octoscript-AppCard @ 4f1ac61) still pins makepad ad8f372 in its
own manifest. A patch cannot point a git source at another rev of itself, so
every crate of the OctoSense-org/makepad URL resolves through the www host
alias of the same repository at this pin, with exact versions: one makepad
lineage (no ad8f372 left in Cargo.lock, one makepad-widgets). The alias can go
once Octoscript-AppCard moves to this rev.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Root cause of the ~56 presents/s on an idle phone (the unpolled GL completion
fence), the forked `date` behind the idle CPU, the upside-down backdrop, the
Android draw/wait/gc split of the transition frames, the per-scenario tables
(burst-aware SurfaceFlinger stats with GPU-ready latency, monitor channels),
and the A/B evidence of what still blocks 55 fps on the shade and swipes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ymote

ymote commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded: every commit in this stack (#22–#28) is already on OctoSense-ROM main, where the phone shell continued. Per ADR 0001 the phone layer moves into the shared crates/shell from that copy when OctoSense-Desktop and OctoSense-ROM are merged into one OctoSense repository, so nothing here is lost. #25's octos-app pin is obsolete: AppCard now comes from OctoSense-System-Apps on octos main. The branches are kept.

🤖 Generated with Claude Code

@ymote ymote closed this Sep 27, 2026
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
Adopt Apache-2.0 as the project license ahead of the Agentic App
hackathon. Adds/updates LICENSE and NOTICE and sets Cargo license
fields for the project's own crates. Third-party code keeps its
original license, as recorded in NOTICE.

Co-authored-by: ymote <151983+ymote@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
…opt-in

Make AppCard strictly opt-in (--features app-appcard)
chrislearn pushed a commit to chrislearn/OctoSense that referenced this pull request Sep 29, 2026
OctoSense-System-Apps c0f2199c and App Hub 3e993d4c (pinned by OctoSense-org#28) carry
the AI providers app, octosense-llm-service and octosense-llm-config, and
the `llm` capability; the Mail and llm services name Home's App Hub rev, so
the graph keeps one App Hub. Home links the llm crates under `app-hub`, and
system-apps.json selects ai-providers.

src/llm_host.rs registers `llm` at startup with the AppCard kernel's octos
home as the core dir and, on Android, a camera QR scanner over
cx.show_qr_scanner() (NativeQrScanned / NativeQrCancelled routed from
Home's events; a scan the service did not ask for, such as AppCard's
composer's, is left alone) and an image picker: QrImagePickActivity, an
invisible activity in a task of its own that runs the system picker and
hands the bytes over in a private cache file on the qr.image.result
packet. With AppCard linked (--features app-appcard, opt-in since OctoSense-org#27), a
change to the provider set restarts the AppCard core; without it no change
hook is registered.

System-Apps' AppCard now takes octos from octos main, which no longer
vendors nix; Home's nix patch stays at the 18fcd3f1 copy octos-app itself
patches in, and the comment and READMEs say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant