Skip to content

feat(egui): prototype Input Viewer on Rust and egui, with ADRs for the port - #346

Merged
BernardJen merged 12 commits into
mainfrom
feat/egui-prototype
Oct 12, 2026
Merged

BernardJen merged 12 commits into
mainfrom
feat/egui-prototype

Conversation

@BernardJen

@BernardJen BernardJen commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

A Rust/egui prototype of Input Viewer, plus 52 ADRs recording the Electron app's decisions so the port can go one decision at a time. The Electron app is untouched except one line in release.yml.

What's in it

  • input_viewer_egui/: the prototype.
    • Dual/single view, pickers, legend, Settings (Inputs, Layout), and settings.json in Electron's format.
    • The Plasma screensaver, running the Electron GLSL unchanged.
    • Capture health and reopen, ported from stream-health.js.
    • The daily log file, ported from file-log.js.
    • Test flags: --mock, --no-signal, --test-pattern, --hud, --mock-fault.
  • docs/adr/: the ADRs. Each has an egui verdict and the prototype's status. The index is the port's status board. open-questions.md lists decisions to make and the Electron defects (filed as bug: low-resolution capture retry requests audio with the video device id #347-release: confirm a macOS client actually auto-updates #361).
  • Workflows.
    • release-egui.yml: egui installers via Velopack, tagged egui-v*, never marked latest, so electron-updater is unaffected.
    • ci.yml: an egui test-and-build job.
    • release.yml: git describe --match 'v[0-9]*', so egui tags never become the Electron release's baseline.

Verified

  • cargo test (71) and cargo clippy clean
  • Windows Setup.exe and macOS Setup.pkg build in CI, with distinct file names per platform
  • Two 3840x2160@60 test inputs: 60 fps shown, 0 skipped (M3 Pro)
  • Health and reopen with a dark, stalled and failing input, including with the window covered
  • The log file is written in Electron's format
  • On the wall's Windows PC with the Elgato cards: the go/no-go gate in ADR 0002
  • A real camera capture
  • Publishing a release and updating from one to the next

Before merging

The squash title starts with feat(...). The Electron release analysis isn't path-aware, so its next release would compute a minor bump. Merge under a non-bumping title, or pick the bump explicitly at the next Electron release.

🤖 Generated with Claude Code

BernardJen and others added 4 commits October 12, 2026 13:38
A native rewrite prototype in input_viewer_egui/, built to test #249's
objections to leaving Electron.

- Dual/single view, Multi-view, pickers over the wall, legend, shortcuts
  from one table, logo column and side borders, Settings (Inputs, Layout)
  writing settings.json in Electron's format and importing it once.
- Capture via nokhwa, one session per device, health and reopen,
  test-mode flags (--mock, --no-signal, --screensaver-delay).
- Plasma screensaver and its post-FX chain running the Electron GLSL
  unchanged on eframe's glow backend (shaders/extract.mjs).
- --test-pattern and --hud: two 3840x2160@60 inputs render at 60 fps with
  no skipped frames on an M3 Pro; trilinear mipmaps fix moire at 1.8x+
  downscale.
- Velopack auto-update from egui-v* releases, never marked latest.

Not tested against the wall's Elgato cards or on Windows yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- release-egui.yml: manual release of egui-v* tags with Velopack
  installers for Windows and macOS, published with make_latest false so
  electron-updater keeps resolving the Electron release. Pull requests
  touching input_viewer_egui/ get a build-only packaging check.
- release.yml: find the last Electron tag with --match 'v[0-9]*', so an
  egui-v* tag never becomes the baseline for commit analysis.
- ci.yml: cargo test and release build for input_viewer_egui on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 ADRs in docs/adr/, reconstructed from the code, issues and PRs, each
with a Porting to egui verdict and the prototype's status. The index is
the port's status board and suggested order; open-questions.md lists
product decisions to make, Electron defects and doc drift found on the
way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Windows: the checkout is CRLF, so the perl pattern anchored on a bare
  \n matched nothing, Cargo.lock kept 0.0.0 while Cargo.toml changed,
  and `cargo test --locked` refused to run. Stamping moves into
  packaging/set-version.sh: every pattern allows \r, and it checks the
  crate's own entries afterwards instead of trusting the rewrite.
- macOS: vpk rejected 0.0.0-pr.N ("must be >= 0.0.1"). A pre-release
  sorts below its release, so PR builds are now a pre-release of the
  next minor, e.g. 0.1.0-pr.346.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
E1-E15 are filed as #347-#361; E16 was already tracked in #340. Also
corrects two line numbers (E1, E8) found while verifying them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BernardJen and others added 7 commits October 12, 2026 16:04
…hidden

health.rs is now a line-for-line port of stream-health.js: the same seven
statuses, thresholds and fault/dark backoff schedules, with its unit tests
ported case for case (ADRs 0012, 0013).

- Sampled every 2 s per device from the frame counter and the luma of a
  box-averaged 384x216 thumbnail; a stream error is acted on at once.
- A reopen drops the stream to close the device and opens it again after
  a 500 ms release gap; attempts survive the reopen so the backoff backs
  off and resets after 60 s healthy.
- As in Electron, only a failed open (or a missing, disabled or forced
  input) puts the NO SIGNAL board up. A dark or stalled card is reopened
  and stays black meanwhile, instead of showing the board as before.
- Device polling, stream sync, health, screensaver timing and settings
  saves move from ui() to App::logic(), which eframe keeps calling while
  the window is hidden or covered. With them in ui(), a covered window
  stopped recovering its inputs entirely (ADR 0052).
- --mock-fault=dark|stall|fail breaks the last mock input, to watch the
  policy work: verified for all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sson

- 0012, 0013: done, with the measured --mock-fault behaviour.
- 0019: the board shows for a failed open, not for a dark card -- so
  without reference detection a wall of dark cards shows black, never the
  board or a screensaver, as Electron does today.
- 0052 (new, egui-only): work that must not stop runs in App::logic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
filelog.rs is a port of src/main/file-log.js with its tests: one file per
local day in <config>/Input Viewer (egui)/logs, 7 days kept, 20 MB/day,
600 lines/min, 2000-character lines, synchronous appends, and a log that
disables itself rather than ever failing the app (ADR 0044).

- Every line now goes to stderr and the file, with levels matching
  Electron's console.log/warn/error and its tags where an equivalent
  exists ([Health], [AutoUpdater], [TestFlags], [Settings]).
- [Health] lines use describeHealth()'s exact format, one per half that
  shows the device, so the lines labs-videowall-config documents still
  match: transitions, the reopen line, the opened line, the 10-minute
  summary, and "monitoring every 2000ms".
- A startup banner with OS uptime, a GPU line (vendor / renderer / GL
  version, in place of gpu-report.txt), and a panic hook that puts the
  panic in the file.

Adds chrono (local time; `time` cannot read the local offset safely in a
multi-threaded process on Unix) and sysinfo (OS version and uptime).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0044 records what the egui log writes, where it differs from Electron's
(the [app] source tag, the reopen line's id) and what is not ported
(fps-report.txt, the detection diagnostic, __health()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0002 and 0048 said the installers had not been built in CI; both now
build, with distinct file names per platform. Publishing and updating
remain untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the wall both Elgato 4K60 Pro MK.2 cards delivered ~13 fps to the egui
prototype, while Electron, on the same cards with nothing connected,
negotiated 3840x2160 @ 59.94 and measured 60 fps. The cards offer 4K only
as YUY2, with a ladder of frame rates down to 1 fps.

Three bugs in nokhwa's Media Foundation bindings, vendored and patched
(vendor/nokhwa-bindings-windows, PATCHED.md, wired in by [patch.crates-io]):

- fractional rates were zeroed, so 59.94 (60000/1001) never made the list;
- set_format matched a type by its range but set it as is, with its own
  MF_MT_FRAME_RATE -- possibly a low rung of the ladder;
- the current rate was read as the denominator (60000/1001 -> "1001").

Also logs the negotiated pixel format and rate, and once per 120 frames
how long a frame spends waiting for the card versus converting to RGBA,
so the next wall run shows directly whether the conversion is the next
limit.

Type-checked for x86_64-pc-windows-msvc; not yet run on the wall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wall's Elgato cards offer 3840x2160 only as YUY2. With the frame
rate fixed they negotiate 60 fps, but converting each frame to RGBA on
one CPU core took ~49 ms on the wall and still capped capture at ~12 fps
(measured 2026-10-12, "per frame 20 ms waiting for the card, 49 ms
converting").

- YUY2 frames now cross from the capture thread as the card's own bytes
  (half the size of RGBA) and are converted by a shader: uploaded as a
  half-width RGBA8 texture, one texel per pixel pair, then written into a
  full-size texture egui draws, with mipmaps (gpu_video.rs). BT.709
  limited range; yuv.rs holds the same formula on the CPU, with tests.
- The health check reads brightness straight from the Y bytes.
- --test-pattern now emits YUY2 like the cards, converted on all cores,
  so it exercises the same path: two 4K60 inputs at 60 fps on an M3 Pro,
  colours and orientation checked in a screenshot.
- Other pixel formats keep the CPU path; if the shader cannot be set up,
  YUY2 falls back to a CPU conversion so a picture still shows.

Not yet run on the wall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BernardJen
BernardJen merged commit 172e68b into main Oct 12, 2026
6 checks passed
@BernardJen
BernardJen deleted the feat/egui-prototype branch October 12, 2026 22:11
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