diff --git a/spikes/mpv-frame-copy/PORTING.md b/spikes/mpv-frame-copy/PORTING.md index c84eb5fec..ef1f8b484 100644 --- a/spikes/mpv-frame-copy/PORTING.md +++ b/spikes/mpv-frame-copy/PORTING.md @@ -19,12 +19,28 @@ (Wayland session) with the xtream mock portal. Dev-build-only on Linux: the helper links system libmpv and `electron-after-pack.cjs` strips it from packages until milestone 4 (Linux bundled-libmpv runtime) — remove - that strip when milestone 4 lands. The Windows implementation lives in the - follow-up PR #1175; until that PR lands, `master` still has no WGL/named-shm - helper and the frame-copy flag falls back to the native engine on Windows. + that strip when milestone 4 lands. +- **The Windows port below landed through PR #1175** — WGL GlContext twin, + QPC clock + `Local\` named-file-mapping + shm twins (the protocol keeps POSIX-style `/impv-*` names; the native + sides derive the mapping name), reader compiled as C++ on `_WIN32` + (MSVC has no C11 ``), helper `.exe` binding.gyp target + linking the vendored import lib + opengl32 (mpv DLL resolved from the + exe's own directory), TS gates + `.exe` helper discovery, i18n, + packaging/CI guards (win32 packages must ship helper + reader; MSVC + intermediates and import libs excluded from dist). **The open iGPU perf + gate is CLOSED**: RESULTS.md rows on the same i7-1165G7/Iris Xe laptop + as the Linux section (dual boot) — 1080p60 sustained (clean 60 s run), + the viewport-price claim reproduces, d3d11va hwdec active (5.5× CPU + drop), torn=0 everywhere. Machine gotchas for future sessions: Windows + 11 Smart App Control must be OFF to run locally-built unsigned helpers, + and a fresh Windows install can leave the iGPU on the Basic Display + Adapter — bind the real Intel driver (`pnputil /remove-device` + + `/scan-devices` once the driver is in the store) or WGL has no 3.2 core + context and no d3d11va. - PR #1169 credits larsemig's idea (#1154 comment 4932807350). Shared-player - controls are reviewed as a separate integration after this platform stack; - do not conflate that UI layer with the frame-copy transport ports. + controls remain a separate integration after this platform stack; do not + conflate that UI layer with the frame-copy transport ports. ## What "porting" means @@ -33,22 +49,24 @@ The stdio protocol, shm layout, TS adapter, main-process service, preload pump, and Angular UI are shared and already shipped. ``` -apps/electron-backend/native/helper/ # state after the Linux port: +apps/electron-backend/native/helper/ # state after all three ports: ├── mpv_frame_helper.cpp # portable: protocol, mpv session, snapshots ├── frame_helper_io.h # portable: TSV-in/JSON-out, percent-encoding -├── frame_shm.h # portable layout + shared CLOCK_MONOTONIC clock -│ # (POSIX shm calls still need a Windows twin) -├── frame_helper_render.h # portable: FBO/PBO readback + shm publish -└── frame_helper_gl.h # PLATFORM SEAM: GlContext — CGL (macOS) and - # EGL (Linux); Windows adds its WGL twin HERE +├── frame_shm.h # portable layout + shared clock (POSIX +│ # CLOCK_MONOTONIC / Windows QPC) + the Windows +│ # Local\ mapping-name derivation +├── frame_helper_render.h # portable render + ShmRing (POSIX shm_open / +│ # Windows CreateFileMapping twins) +└── frame_helper_gl.h # PLATFORM SEAM: GlContext — CGL (macOS), + # EGL (Linux) and WGL (Windows) apps/electron-backend/native/src/embedded_mpv_frame_reader.c - # real impl on __APPLE__ + __linux__, stub - # elsewhere (Windows needs shm-open/clock twins) + # real impl on __APPLE__ + __linux__ + _WIN32 + # (compiled as C++ there), stub elsewhere ``` -Porting Windows = give `frame_helper_gl.h` a WGL GlContext twin, give the -shm create/open (+ `frame_shm_now_ns`) Windows twins, flip the TS gate in -`embedded-mpv-frame-copy-platform.util.ts`, extend packaging. +All three ports have landed; new platforms follow the same seams: a +GlContext twin in `frame_helper_gl.h`, shm create/open + `frame_shm_now_ns` +twins, the TS gate in `embedded-mpv-frame-copy-platform.util.ts`, packaging. ## Branching & merge strategy @@ -95,7 +113,11 @@ shm create/open (+ `frame_shm_now_ns`) Windows twins, flip the TS gate in the Linux support matrix in `docs/architecture/embedded-mpv-native.md` when this lands. -### Windows +### Windows (DONE 2026-07-12 — see the update in "State" above) + +One deviation from the checklist below: the WGL context uses a hidden +regular window, not a message-only one — SetPixelFormat needs a +display-capable DC. 1. **Render backend**: WGL headless — create a hidden message-only window + dummy pixel format, `wglCreateContextAttribsARB` 3.2 core, then the same diff --git a/spikes/mpv-frame-copy/RESULTS.md b/spikes/mpv-frame-copy/RESULTS.md index 40bb2f9f2..fb83fd340 100644 --- a/spikes/mpv-frame-copy/RESULTS.md +++ b/spikes/mpv-frame-copy/RESULTS.md @@ -143,6 +143,50 @@ Readings: - EGL display tier used: Mesa surfaceless platform (first tier; no display server needed). -## Windows mid-range laptop (iGPU) — PENDING +## Windows mid-range laptop (iGPU) — Windows 11 Home 26200, i7-1165G7 / Iris Xe, x64 — 2026-07-12 -Blocked on the Windows helper port (WGL or D3D11 readback path). +Source: Windows port branch (WGL `frame_helper_gl.h` backend), vendored +libmpv from zhongfly/mpv-winbuild 2026-06-14 (`git-7d245fd100`, +`libmpv-2.dll`), Intel driver 30.0.101.1340. **Same physical laptop as the +Linux section above** (TUXEDO Book XP14 Gen12, dual boot), so the two +sections compare OS/driver stacks on identical hardware. Measured with the +production helper + `embedded_mpv_frame_reader.node` through +`linux-frame-probe.mjs` (now cross-platform; on Windows it polls via +setImmediate because setTimeout quantizes to the ~15.6 ms system timer, +which would dominate *age*), so *age* is produce→reader-copy and excludes +the renderer texture upload — same semantics as the Linux rows. Unlike the +Linux rows, hwdec IS available here: mpv's `hwdec=auto` engages d3d11va +(verified by helper CPU: 0.51 core-s/s vs 2.82 core-s/s with `--hwdec no` +on the 4K row — a 5.5× CPU drop). Test clip generated with `hevc_qsv` +(the Intel encoder), so decode complexity is not byte-identical to the +videotoolbox/x265 clips of the other sections. + +| Scenario | New fps | copy ms avg/p95 | age ms avg/p95 | torn | +| --- | --- | --- | --- | --- | +| 1080p60 testsrc2, sw | 60.0 | 1.80 / 2.10 | 1.83 / 2.14 | 0 | +| 4K60 testsrc2, sw | 56.0 | 8.81 / 9.88 | 8.83 / 9.86 | 0 | +| 4K60 HEVC 25 Mbit, hwdec=d3d11va | 41.1 | 8.57 / 10.4 | 8.71 / 10.4 | 0 | +| 4K60 HEVC 25 Mbit, sw decode | 45.7 | 10.3 / 15.1 | 10.4 / 15.4 | 0 | +| 4K60 HEVC 25 Mbit in a 1280×720 viewport, hwdec | 60.2 | 1.29 / 1.87 | 1.27 / 1.82 | 0 | + +Readings: + +- 1080p60 — the realistic viewport class for this laptop's 1920×1200 + screen — holds a clean 60 fps with ~1.8 ms copies. A 60-second sustained + run kept 60.0 fps over 3601 frames (copy 1.57 / 1.84 ms, torn 0). +- The 4K rows are stress rows, as on Linux: at a full-4K viewport the + Iris Xe is saturated by mpv render + readback (56 fps ceiling with no + decode at all), so d3d11va decode — which shares the same iGPU — buys + CPU headroom (5.5×), not fps; sw decode trades ~2.3 cores for ~4 fps. +- The viewport-size claim reproduces on Windows: the same 4K60 HEVC clip + in a 720p viewport runs 60 fps with 1.3 ms copies and hardware decode + active. +- torn=0 across every run; the aspect-fit generation bump was verified + (4:3 960×720 source in a 1280×720 viewport → `-g2` at 960×720). +- WGL context: `gl renderer: Intel(R) Iris(R) Xe Graphics` (hardware, + 3.2 core via wglCreateContextAttribsARB). Machine prerequisite hit + during bring-up: Windows 11 Smart App Control blocks locally-built + unsigned executables (the helper) until turned off, and a fresh Windows + install may run the iGPU on the Basic Display Adapter — the frame-copy + engine needs the real Intel driver bound (WGL on the basic adapter has + no 3.2 core context, and there is no d3d11va). diff --git a/spikes/mpv-frame-copy/linux-frame-probe.mjs b/spikes/mpv-frame-copy/linux-frame-probe.mjs index 2707675a8..8783cc199 100644 --- a/spikes/mpv-frame-copy/linux-frame-probe.mjs +++ b/spikes/mpv-frame-copy/linux-frame-probe.mjs @@ -1,5 +1,5 @@ /* - * Linux frame-copy probe: spawns iptvnator_mpv_helper, attaches the + * Linux/Windows frame-copy probe: spawns iptvnator_mpv_helper, attaches the * embedded_mpv_frame_reader addon to the announced shm generation, and * reports producer fps, copy latency (ageMs), copy wall time, torn reads * and pixel spread. Usage: @@ -22,7 +22,12 @@ const hwdecIdx = rest.indexOf('--hwdec'); const hwdec = hwdecIdx >= 0 ? rest[hwdecIdx + 1] : null; const reader = require(path.join(releaseDir, 'embedded_mpv_frame_reader.node')); -const helperPath = path.join(releaseDir, 'iptvnator_mpv_helper'); +const helperPath = path.join( + releaseDir, + process.platform === 'win32' + ? 'iptvnator_mpv_helper.exe' + : 'iptvnator_mpv_helper' +); const shmBase = `/impv-probe-${process.pid}`; const args = ['--shm-base', shmBase, '--width', width, '--height', height]; @@ -56,6 +61,13 @@ child.stdout.on('data', (chunk) => { }); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +/* Poll yield between latestSeq checks: Windows quantizes setTimeout to the + * ~15.6 ms system timer, which would dominate ageMs; setImmediate keeps the + * poll sub-ms there at the cost of one busy event loop. */ +const pollYield = + process.platform === 'win32' + ? () => new Promise((r) => setImmediate(r)) + : () => sleep(2); await sleep(300); child.stdin.write(`load\turl=${url.replace(/%/g, '%25')}\n`); @@ -102,7 +114,7 @@ while (Date.now() - start < durationMs) { if (result.torn) torn += 1; } } - await sleep(2); + await pollYield(); } const elapsed = (Date.now() - start) / 1000;