test(embedded-mpv): Windows frame-copy measurements close the iGPU gate

The probe now runs on Windows too (.exe helper name; setImmediate polling
because setTimeout quantizes to the ~15.6 ms system timer, which would
dominate ageMs). RESULTS.md gains the Windows section measured on the
same i7-1165G7/Iris Xe laptop as the Linux rows (dual boot): 1080p60
sustained over a 60 s run, viewport-price claim reproduced (4K HEVC in a
720p viewport = 60 fps at 1.3 ms copies), d3d11va hwdec active (5.5x
helper CPU drop vs sw decode), zero torn reads everywhere. PORTING.md
marks the Windows port done and records the machine gotchas (Smart App
Control, Basic Display Adapter binding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and 4gray committed 2026-07-15 20:46:30 +02:00
1 parent fd1bac011f
commit 9fefd70dd1
3 files changed
+100 -22

No files matched your search

+39 -17
View File
@@ -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 `<stdatomic.h>`), 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
+46 -2
View File
@@ -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).
+15 -3
View File
@@ -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;