Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion technical-documentation/engineering/rendering-performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,28 @@ All three in one afternoon, with the editor open in the background (~40 % of a c

**Reading the GPU on this machine:** ACO's statistics come out only when a pipeline is compiled, and Mesa's disk cache skips compilation for a shader it has seen — set `MESA_SHADER_CACHE_DISABLE=true` or the shader you are measuring prints nothing. Per-pass timestamps read back synchronously every frame disturb the clocks and the overlap with decode; trust the span of a frame and repeated wall-clock runs over a single draw's number.

### The same fix on Windows — 2026-10-03

The same laptop under Windows 11 (26200): D3D11 on the Radeon iGPU, `h264_amf` encode, the same benchmark scenario, three builds in one session. Each ran from the app tree of its CI-built installer, side by side in one directory. 2.0.0-rc.13 is the build for the fix: its tag has the same `crates/`, `electron/`, `src/` and `scripts/` as `main` at `0624c148`, and its export path differs from rc.12's by `compositor_view.node` alone (the ffmpeg DLLs are hash-identical).

| | 1.11.0-rc.1 | 2.0.0-rc.12 | 2.0.0-rc.13 (= `main`) |
|---|---:|---:|---:|
| benchmark median | 34.18 s ± 0.73 | 116.79 s ± 2.36 | 35.05 s ± 0.46 |
| local `h264_amf` floor | 19.83 s | 24.42 s | 20.54 s |
| × its local floor | 1.72 | 4.78 | 1.71 |
| foreign load, export / floor | 88 % / 61 % | 154 % / 206 % | 72 % / 72 % |

**The regression was worse here than on Linux**, 4.78× against 2.43×: the export ran at half real time. The fix takes it back to 1.11 exactly. rc.12's row ran under the heaviest foreign load of the three, and its floor spread from 20.8 to 30.4 s, which inflates its cost somewhat. Its raw seconds, 3.3× rc.13's, do not depend on the floor. The load came from a Claude client, FocuSee, Steam, Discord and Slack idling; there was no remote session (Parsec's last guest left on 2026-09-30). The benchmark's `calibration.json` was the M1's, the same for all three rows, so it moves nothing between them.

**The pixels did not change.** rc.13's three scoring exports are byte-identical to rc.12's (md5 `e8ce4b86…`). Its warm-up export, the build's first run on the machine, differed in the first GOP only: 23–39 pixels off by ±2 in frames 1–29, gone at the next I-frame. A fresh copy of the build in a new directory exported `e8ce4b86…` straight away, so it did not reproduce. Twelve-second variants exported by both builds:

- **Byte-identical:** the 3D cursor with click impact and the laptop frame with its shadow; the window frame (`sd_screen_under_bar`); a solid colour with Aurora.
- **Different, invisibly:** a gradient with 70 % background blur, and an image with Waves, 40 % blur and the phone frame. Each build is deterministic on its own. Between them, PSNR is at least 76 and 67 dB, the differences are macroblock-shaped in the screen and camera content, and side-by-side crops cannot be told apart. The blurred background is now drawn once and copied, and a one-level difference upstream gets redistributed by the encoder's rate control.

In the editor (a dev build of `main` with rc.13's native directory), the preview followed every change while the static background is cached: image → colour → colour → gradient → image, blur 100 → 0 → 50, and a window resize from 1508×794 to 1028×650. Aurora kept the background moving between two programme times (mean difference 3.47 on the border), while None left it unchanged (0.01).

No register counts: Radeon GPU Analyzer was not run, so the VGPR explanation under fxc is inferred from the Linux measurement and from this outcome, not measured.

## How we got here — the WebCodecs trail

> **This section is history.** It records the measurements that killed the browser-based export pipeline and motivated the native one. The code it describes is **gone**: `src/lib/exporter/videoExporter.ts`, `src/bench/runBench.ts` and the `npm run bench:export` script were deleted with the web MP4 pipeline. It is kept because it is the evidence for [why the compositor, not the encoder, was the wall](#the-wall-is-the-compositor) — which is the entire reason `crates/compositor/` exists — and because the [measurement hazards](#measurement-hazards) it uncovered still apply to any new benchmark here.
Expand Down Expand Up @@ -821,7 +843,7 @@ the bench runs on the reference machine.

## Known gaps

- **The D3D11 and Metal layer shaders were split like the WGSL one without a measurement on either.** Whether an AMD or Intel iGPU under Windows paid the same occupancy for the models is unknown, and so is what the split buys there; the M1 showed no regression on 2.0, which says nothing about fxc's register allocation. [The Linux section](#the-linux-export-path--2026-10-02) has the method — the register counts first, then the export. The static-background cache (keyed by the shared `BackgroundKey`) and the scissored cursor-trail composite were ported to both, unmeasured as well.
- **The Metal layer shader was split like the WGSL one without a measurement, and D3D11 only on an AMD iGPU.** On the Ryzen 5 7520U under Windows, the split, the static-background cache and the scissored trail together took the export from 4.78× to 1.71× its floor, level with 1.11.0-rc.1's 1.72×. The benchmark's scoring exports were byte-identical, and so were the model and frame variants; only the blurred-background variants differed, invisibly ([the Windows A/B](#the-same-fix-on-windows--2026-10-03)). Still owed: an Intel iGPU under Windows, fxc's register counts (Radeon GPU Analyzer, `ps_main` against `ps_main_models`), and an A/B on the M1, where the published figures are 1.04× for 1.11.0-rc.1 and 1.11× for 2.0.0-rc.12. [The Linux section](#the-linux-export-path--2026-10-02) has the method: register counts first, then the export.
- **macOS export startup can cost 4 s, and nobody has reproduced it on demand.** Measured repeatedly at 4208–4502 ms between the CLI's `started` event and the first composed frame — 18 % of a 60 s export, 71 % of a 5 s one — then gone, on the same shipped binary, hours later (481 ms). It is not the compositor (init is 2.4 ms, runtime MSL compilation included), not the `<video>` metadata probes (13 ms and 6 ms), not the CLI prologue (24 ms total), and not the renderer entry point (measured at −0.1 %). It correlates with memory pressure on an 8 GiB machine — `387M unused / 2613M compressor` while it reproduced, `564M unused / 1837M compressor` after — which would fit faulting ~1.8 MB of module chunks out of a 274 MB `app.asar` while the compressor thrashes: seconds of wall clock, no CPU in either process, cost independent of the media. Untested. Recreating the pressure deliberately and watching it return is what would settle it, and then whether asar size is the lever.
- **10-bit and HEVC decode on macOS are unmeasured.** The export's decode predicate is `codec_id == H264 && format == YUV420P`, so both keep VideoToolbox untested. HEVC is the case most likely to invert the result, since its software decoder is materially more expensive. 10-bit needs work beyond the predicate first: `mac_frames::CpuFrames` converts to 8-bit NV12, so routing 10-bit through the software path would silently truncate — the predicate is currently what prevents that.
- **The macOS preview's decode backend has never been measured.** `DecodeIntent` splits preview from export precisely so the preview could keep the old arbitration; the export won on throughput, but the preview scrubs, where seek latency after `avcodec_flush_buffers` may matter more, and it shares the machine with the editor UI. Changing it without measuring it would be the same mistake the export change corrects.
Expand Down
1 change: 1 addition & 0 deletions technical-documentation/testing/manual-e2e-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,4 @@ On macOS 15.2+ it opens at launch, until it has been closed once, while one of i
| 2026-09-30 | installed `v2.0.0-rc.1` (CI artifact, sha512 matched `latest.yml`), installed over 1.10.0 | Windows 11 26200, 1920×1080 @ 125 %, AMD Radeon iGPU | Partial — 2 defects | Launch/HUD, source selector (window and full screen), record with pause/resume, stop→editor ~0.65 s, transport, seek, zoom/speed/trim/full-camera regions with undo/redo, autosave, imported audio track (mixed at the right place and gain), voiceover take, Rec mode, MP4 export (duration = source video stream − trim − speed gain, exact), GIF export (ffmpeg and Pillow decode byte-identical). **Defects:** HUD walks up on a vertical→horizontal flip (#951, fixed in rc.5 by #953); transcript word times VAD-compressed, ~−14 s (fixed for rc.3, #948). GIF export ~3 frames/s vs MP4 ~84 fps (#952). **Not defects, re-triaged:** the Whisper Vulkan boot crash came from a helper staged from an unrelated branch (409cf4d7); "HUD not topmost" is computer-use's own doing (a `screenshot` clears `WS_EX_TOPMOST` on the HUD); the 98 % progress stop was the Windows audio tail (#942). Not covered: installer UI (ran `/S`), updater, tray, cancel/restart, notes, reopen from library, AI chat, macOS/Linux. |
| 2026-10-01 | installed `v2.0.0-rc.6` (CI artifact, sha512 matched `latest.yml`), installed over rc.1 | Windows 11 26200, 1920×1080 @ 125 %, AMD Radeon iGPU | Partial — 1 defect (transcript) | Targeted at the Windows changes since rc.1. **#951:** two vertical↔horizontal round trips with real clicks, state read after each, HUD back at the same bounds (`302,149`). **Recording** (full screen + mic + system audio + webcam, instant pause/resume mid-take): screen and webcam H.264 **High**, BT.709 primaries/transfer/matrix, TV range; audio runs 28 ms past video (rc.1: 0.87 s); stop→editor ~1 s. Screen at 57.2 fps average — 181 missed 60 Hz ticks over 52 s, timestamps stay on the clock (video 52.17 s vs audio 52.20 s) — known, #945. **STT:** helper boots on Vulkan, VAD on CPU, no crash; on a ground-truth clip (12.12 s silence then speech), first word at 12.10 s and every sentence boundary within 0.12 s; the "en" CTC aligner downloads in the background and only applies from the next transcription. **Defect:** the last word before one pause ("happened.") lands after it as a 20 ms word, its audio given to the previous word; without the aligner the display order also inverts ("nothing The happened."). Reported to #948. **Export:** 3-clip MP4 720p30, progress reaches 100 % (4200/4203), 4200 frames, video 139.97 s / audio 140.00 s, each clip's speech at the expected offset, decodes clean. **Mixer (#911, instant resume):** a second take with system audio only (mic off) while a 26 s TTS WAV played at 76 % volume (output peak meter 0.985 during the take). No punched hole anywhere in the voice: an alignment-free scan for 2–12 ms dips below 5 % between loud neighbours found none, and the same scan finds 10 ms holes injected into the take. Instant pause/resume (0.32 s button to button) skips 0.330 s of the source, so no voice before the pause is lost (the bug was 80–90 ms). Take is +3 dB over the source, peak −0.1 dBFS, 0 full-scale samples. The first take of this pass had the speakers muted at 0 %, which silences WASAPI loopback, so its audio was not usable. **Not covered:** preview limiter; MCP server; keep-awake; tray; GIF; macOS/Linux. |
| 2026-10-01 | installed `v2.0.0-rc.8` (build run 36883700385, tag `33a16fe1`, helper from build-whisper-stt run 36830983393; NSIS sha512 matched `latest.yml`), installed over rc.5 for all users, About reports `2.0.0-rc.8` (`win32 x64 · nsis`, Electron 41.2.1) | Windows 11 Home 26200, 1920×1080 @ 125 %, Logitech StreamCam (sub-4K, MF) | **Partial — 2 defects** | **Webcam:** torn and mis-exposed frames in the webcam file (some frames split horizontally into two pictures, others blue-dark or washed out). The maintainer attributes them to the camera itself, not OpenScreen; the same frames are in the rc.1 take of 2026-09-30. **Defect 1 (#960): captions in the preview are stale after switching projects.** Two clips of one asset, captions Bold top-left: after opening another project and reopening this one, clip 1 shows no caption or the wrong line (`0:06` showed "we are recording the screen…", `0:12` and `1:05` nothing) until *Show captions* is toggled; saved data is intact. **Defect 2 (#961): after a working session, closing the editor leaves OpenScreen running with no window** (6 processes, tray only), seen twice: once after takes (HUD, Record mode, restart, cancel), once after only the project dialog, *Edit clip* and a chat turn. An instance that only opened the editor and closed it quit as `window-all-closed` intends, so some hidden window outlives those surfaces. **Loudness in spec:** MP4 export at the Audio facet's default output level (never changed in this project), −15.8 LUFS integrated, −1.4 dBFS sample and −1.4 dBTP true peak; the check asked for a sample peak under −1.5 dBFS, which the AAC overshoot past the −1.5 dBFS limiter cannot meet, so its wording is corrected in this change. **Passed:** HUD layout toggle both ways, tooltips idle and recording (with continuous real moves; a teleported cursor shows the previous control's tooltip, a driver artefact), language menu, *Device settings* (devices, live preview, *Camera quality* 1080p/1440p/4K, 4K default, 1080p persisted across quit/relaunch as `camQuality`, hand-edited `720p` reads back as 4K, device switch persisted), *Hide recording bar* → tray click brings the HUD back, *Quit OpenScreen* (0 processes), second launch exits 0 with one HUD. Source selector (Screens/Windows, Cancel), record with no source opens it and starts after the pick, countdown, pause (span absent from the file) / resume, restart (new helper process, timer reset), cancel (no editor, no file), gear locked during a take. Stop → editor, 53.0 s take with 3 automatic zooms at the clicks, desktop icons hidden in the second take for its whole length. Files: screen and webcam `profile=High`, `has_b_frames=0`, `tv/bt709×3`, AAC 48 kHz, `encoder-selection` hardware. Auto-transcription after the take; *Regenerate as* English. Transcript: clip order, silence spans with durations, monotonic word seeks (4.0 → 11.2 → 16.4 → 21.3 → 21.8 → 27.2 → 56.9 s), live word highlight, Backspace marks skipped + restore, silence-span trim and restore, **#959 checked: sentence-final "time." cut 11.91–12.51 against a speech offset of 12.44 (silencedetect −40 dB) and next speech at 16.46**; typing between words does nothing (packaged gate). Transport, Left/Right 1/60 s, stop at end, navigator narrow/pan. Regions by T/S/A/C, trim edges, *Bring this part back*, undo/redo; zoom levels, custom 1.25 kept, 9 → "Zoom goes from 1× to 2.86×", 3D Orbit/left/right; speed presets, custom 3×, 20 → "Speed can't go higher than 16×"; annotation text/size/plate; Full Camera. Clip reorder carries every modifier; Edit clip grip (0:10.7–0:53.0) and 1:1 crop, Cancel leaves it. Export dialog v2 layout and sizes (1224×720 / 1838×1080 / 1910×1122); MP4 1838×1080 30 fps, video 92.033 s vs audio 92.066 s (1 frame), 2762 frames vs 2760.7 computed (105.941 − 10.312 trim − 5.41 × 2/3 for 3×), frames match the preview at 16.5/28/40 s; GIF cancel (no partial file) then full GIF 816×480, 1383 frames at 15 fps, loop, ffmpeg and GDI+ agree. Imported audio: −18 dB, 1 s fades, ramps at the track's own edges in the export (3.0→4.0 s, 22.0→23.0 s). Captions toggle, Bold, top/left anchors and *Distance from left*, captions unscaled under a zoom. Camera layout presets with Auto reshaping, shape, mirror, Cutout. Cursor facet style and *Show cursor*. Wordmark menu, About (version, Copy to clipboard), light/dark theme, French and back, shortcut rebind (Q adds a zoom) and reset, Ctrl+S toast, Ctrl+O, an older project opens without prompt, reopen keeps regions/settings/audio track. Editor bounds and maximized state restored. Chat (deepseek-v4-flash): "Add a 2x zoom from 0:07 to 0:09" applied as 2.20× with a reason, one undo/redo unit. **Not run:** HUD drag (the HUD window now ends ~45 px above the bar, any longer move lands on the click-tier desktop; short in-window moves did not move it, inconclusive); `Esc` (Claude Desktop swallows it); Ctrl/Shift+wheel (the scroll tool holds no modifier); tray right-click menu (Stop Recording, Save Diagnostics, Update Settings, About from tray) and the Alt-held Help menu; level meter and mic-only/system-only takes, music ducking (nobody spoke); "No camera found"; 4K-camera, no-30-fps, DirectShow, covered-lens and sharpness checks (no such camera / setup); window source and 24H2 context menu; DPI and multi-display (single display, scaling not changed); voice-over recording; region copy/paste and merge/repel, clip duplicate/delete; translation; most AI-settings and chat-history checks; camera background export of Cutout/Custom; caption plate measurement; legacy-project migration checks beyond opening one; macOS and Linux. Also seen: an English text read by the French SAPI voice auto-transcribed as French (detection, not app). |
| 2026-10-03 | `v2.0.0-rc.13` CI artifact (build run 37072118778, tag `018d22ab`, same `crates/` `electron/` `src/` `scripts/` as `main` at `0624c148`) against `v2.0.0-rc.12` and `v1.11.0-rc.1`, each run from its installer's extracted app tree; editor checks on a dev build of `main` with rc.13's whole `bin/win32-x64` | Windows 11 Home 26200, Ryzen 5 7520U, AMD Radeon iGPU (D3D11, `h264_amf`) | Partial — no defect | **Export-path slice for #982/#983/#984.** `cargo test -p openscreen-compositor --lib --tests` on the real GPU: 456 passed, including the D3D11 render tests for the device frame, cursor model, click impact and cursor tap. Benchmark A/B in one session: rc.12 4.78× its floor, rc.13 1.71×, 1.11.0-rc.1 1.72× (numbers in `rendering-performance.md`, "The same fix on Windows"). rc.13's scoring exports are byte-identical to rc.12's. Twelve-second variants exported by both builds: the 3D cursor with click impact, the laptop frame **with its shadow**, the window frame, and a colour with Aurora are byte-identical; the two blurred-background variants differ invisibly (PSNR ≥ 67 dB). Frames extracted with ffmpeg show the device frame, 3D cursor, window chrome and rounded corners. Preview while the background is cached: image/colour/gradient switches, blur 100→0→50 and a window resize are all followed, Aurora keeps moving, None stays still, and playback frames show no leftover trail rectangle. **Not run:** real capture and HUD (computer-use screenshots came back uniformly grey, so the editor was driven over CDP, with injected input rather than OS input), Radeon GPU Analyzer register counts, Intel iGPU. |
Loading