Skip to content

Add rendering frame-rate diagnostics and bump Nucleus to 2.5.5 - #58

Merged
kdroidFilter merged 2 commits into
mainfrom
fix/57-rendering-frame-rate-diagnostics
Aug 30, 2026
Merged

Add rendering frame-rate diagnostics and bump Nucleus to 2.5.5#58
kdroidFilter merged 2 commits into
mainfrom
fix/57-rendering-frame-rate-diagnostics

Conversation

@kdroidFilter

@kdroidFilter kdroidFilter commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Refs #57 — the issue stays open until the reporter confirms on their 120 Hz ProMotion hardware.

Rationale

#57 asks whether the Tao desktop backend renders the page offscreen, whether something caps it near 30 FPS, and whether a max_fps API could be exposed.

It does not, and there is nothing to expose: every desktop backend embeds a real native view (macOS WKWebView NSView below the Compose Metal layer with a punched hole, Linux WebKit2GTK widget reparented into Tao's content widget, Windows WebView2 as a DirectComposition visual). No backend contains frame-rate logic — grep for fps/frame/vsync/timer over webview-compose/src/jvmMain/native/** comes back empty. Pacing is entirely Nucleus Tao + the OS compositor.

Measured on macOS / M4 / 90 Hz display, embedded WebView vs. the same page in a bare WKWebView in a plain NSWindow:

Workload Embedded (Tao NativeView) Bare WKWebView
Canvas 2D rAF 90 fps 90 fps
WebGL (light) 90 fps 90 fps
WebGL, GPU-bound shader 31–34 fps 32–35 fps

Compose rendered at 90 fps in the same window at the same time. Since the answer is "nothing here caps it", this PR makes that verifiable on any machine instead of a matter of opinion — the reporter is likely on a 120 Hz ProMotion display, which I have no hardware to test.

Summary

  • e2e-shared: new Rendering group in the shared catalog
    • R01 reports the requestAnimationFrame rate (e.g. 90 fps); fails only below 10 fps, i.e. a frozen page.
    • R02 reports the WebGL renderer (e.g. Apple GPU) — a software renderer explains slow WebGL better than any fps number.
    • R01 is Skipped when the document is hidden: every engine suspends rAF for an occluded/backgrounded window, so the sample would read 0 fps and say nothing about the backend. Verified that a bare WKWebView behaves identically, so this is not a Tao artifact.
  • runMeasuredCase / SuiteSkip so a case can publish its measurement as the reported detail; suite UI and console now keep showing details on passed cases instead of ok.
  • Nucleus 2.3.1 → 2.5.5 — the library api-exports decorated-window-tao, so consumers inherit this version. Rendering behaviour is identical to 2.3.1 (bisected 2.4.0 / 2.5.0 / 2.5.2 / 2.5.3 / 2.5.4 / 2.5.5: 90 fps everywhere).
  • README: new "Rendering model & frame rate" section — embedding model per OS, absence of any throttle or max_fps knob, how to measure locally, reference numbers.

Blending cost of the Compose overlay

Measured separately, since hole-punch blending is the obvious suspect. Full-screen window (2560×1040), animated Compose overlay in the content slot, page fps / Compose fps, plus GPU utilization sampled while both run at the display rate:

Compose overlay Light page GPU-bound page GPU util (light page)
none 90 / 90 34 / 90 23.4 %
64 dp animated bar 90 / 90 34 / 90
full-window translucent scrim 90 / 90 34 / 90 22.9 %
full-window opaque surface 90 / 90 34 / 90
bare WKWebView, opaque window 90 34 27.6 %
idle (no app) 8.8 %

No measurable blending penalty on this hardware — but it is a weak stress case (2560×1040 @1x, 90 Hz, desktop M4 ≈ 2.7 Mpx/frame) versus a Retina ProMotion laptop (≈ 7.7 Mpx at 120 Hz, ~4× the compositing bandwidth), so this does not rule it out there.

Side finding, now documented: an opaque Compose overlay does not stop the WebView underneath — it keeps rendering at full speed behind it, so the GPU work is duplicated.

Not included

Compose Multiplatform 1.12.0 is blocked: its Android artifacts require compileSdk 37 and AGP ≥ 9.1, and since AGP 9.0 com.android.library / com.android.application are incompatible with the Kotlin Multiplatform plugin. Adopting it means migrating webview-compose and e2e-shared to com.android.kotlin.multiplatform.library, converting e2e-android to a non-KMP module, and updating the test task names in CI — a separate PR.

Test plan

  • ./gradlew :e2e-desktop:run (macOS / M4) — SUITE_FINISHED passed=true, R02 → Apple GPU, R01 → 90 fps with the window in front, Skipped when occluded
  • ./gradlew :webview-compose:jvmTest :webview-compose:testDebugUnitTest with the standard commonTest filters
  • :e2e-shared compiles for wasmJs, Android and iOS simulator
  • Desktop suite on Linux and Windows (CI matrix)
  • R01 value on a 120 Hz ProMotion Mac (the hardware in Offscreen rendering fps (Tao backend) #57)

Issue #57 asks whether the Tao desktop backend renders offscreen or caps
the frame rate, and whether a max_fps knob could be exposed. It does not:
every desktop backend embeds a real native view (WKWebView NSView under
the Compose Metal layer, reparented WebKit2GTK widget, WebView2 DComp
visual) and no backend contains frame-rate logic.

Measured on macOS/M4 with a 90 Hz display, embedded vs. a bare WKWebView
in a plain NSWindow: 90 vs 90 fps for canvas/WebGL, 31-34 vs 32-35 fps on
a GPU-bound shader — parity in both cases.

- e2e-shared: new Rendering group in the shared catalog. R01 reports the
  requestAnimationFrame rate, R02 the WebGL renderer (a software renderer
  explains slow WebGL better than any fps number). R01 is Skipped when
  the document is hidden: every engine suspends rAF for an occluded
  window, so the sample would read 0 fps and say nothing about the
  backend.
- e2e-shared: runMeasuredCase / SuiteSkip so a case can publish its
  measurement as the reported detail, and the suite UI + console keep
  showing details for passed cases.
- Bump Nucleus 2.3.1 -> 2.5.5 (the version this library api-exports, so
  consumers inherit it).
- README: document the rendering model, the absence of any throttle or
  max_fps knob, and how to measure it locally.
@kdroidFilter
kdroidFilter merged commit f3d85bf into main Aug 30, 2026
11 of 12 checks passed
@kdroidFilter
kdroidFilter deleted the fix/57-rendering-frame-rate-diagnostics branch August 30, 2026 04:26
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