Commit 1fdfb85f by PLN (Algolia)

fix(slop): headless never reached the GPU — `--gl hw` was software all along

Every bake-off render failed, for two independent reasons, and the second one had
been sitting in this file as a comment asserting the opposite of the truth.

## 1. The playwright browser cache is empty now

`chromeBinary()` scans `~/.cache/ms-playwright` for a full chromium build and
returns `undefined` if it finds none, on the documented theory that this "lets
playwright pick". It does not. Playwright reaches for the exact build it shipped
against — `chromium_headless_shell-1234` — which was never downloaded, so every
render died with "Executable doesn't exist" before rendering a frame. The cache
directory is now absent entirely (it held build 1217 back in July). Falls back to
`/usr/bin/chromium`: a full build, already driven by the SoundCloud transport, and
no ~150 MB pull into PLN's shared cache behind his back.

## 2. `--gl hw` never got hardware GL

The old comment claimed swiftshader manages ~7 fps and that `--gl hw` "asks for
the real integrated GPU via EGL. Falls back by simply measuring worse." It was
not falling back — it never left software. Instead of inferring the backend from
frame rate on a machine whose load is a variable, I asked WebGL what it resolved
to:

    headless + --use-angle=swiftshader -> SwiftShader (Subzero)
    headless + --use-gl=egl            -> SwiftShader (Subzero)   <- identical
    HEADFUL                            -> Mesa Intel UHD (CML GT2)

Headless cannot reach this box's GPU whatever the flag. Which explains the
symptom exactly: landscape 1920x1080 captured 3.5 fps on swiftshader and 3.2 fps
on "hw", tripping the renderer's own under-5-fps guard both times — two numbers
that look like noise and were actually the same backend twice.

`--gl hw` now means HEADFUL. Measured after: **11.4 fps captured, page rendering
17** — 3.3x, comfortably past the guard. Note the bottleneck has moved: the page
now renders faster than the CDP screencast drains, so the next lever is the
capture path (frame encoding), not the GPU. Cost is a visible window for the
duration of each capture.

The July note warned against trusting those fps follow-ups because they were
measured at load average 12.5, and it was right to. But the fix was not a quieter
benchmark — it was asking a question that load cannot corrupt. Third time this
month a "the rig is broken" finding turned out to be the instrument.
parent c8e1aa64
...@@ -141,6 +141,12 @@ def render(p: dict, a, node: str) -> bool: ...@@ -141,6 +141,12 @@ def render(p: dict, a, node: str) -> bool:
"--fps", str(a.fps), "--fps", str(a.fps),
"--name", stem(p), "--name", stem(p),
"--playset", p["playset"], "--playset", p["playset"],
# `--gl hw` means HEADFUL, which is the only way to reach the real
# Intel GPU on this box — headless resolves to SwiftShader whatever
# the flag says, and caps at 3.5 fps, under the renderer's own
# under-5-fps guard. Headful measured 11.4 fps captured (page 17).
# Costs a visible window for the duration of each capture.
"--gl", "hw",
"--fx", json.dumps(p["fx"])] "--fx", json.dumps(p["fx"])]
if p["reactive"]: if p["reactive"]:
cmd += ["--triggerHeavy", "true"] cmd += ["--triggerHeavy", "true"]
......
...@@ -103,16 +103,25 @@ function audioDuration(file) { ...@@ -103,16 +103,25 @@ function audioDuration(file) {
function chromeBinary() { function chromeBinary() {
if (process.env.SLOP_CHROME) return process.env.SLOP_CHROME; if (process.env.SLOP_CHROME) return process.env.SLOP_CHROME;
const root = resolve(process.env.HOME ?? "/root", ".cache/ms-playwright"); const root = resolve(process.env.HOME ?? "/root", ".cache/ms-playwright");
if (!existsSync(root)) return undefined; const builds = existsSync(root)
const builds = readdirSync(root) ? readdirSync(root)
.filter((d) => /^chromium-\d+$/.test(d)) .filter((d) => /^chromium-\d+$/.test(d))
.sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1])); .sort((a, b) => Number(b.split("-")[1]) - Number(a.split("-")[1]))
: [];
for (const b of builds) { for (const b of builds) {
for (const sub of ["chrome-linux64/chrome", "chrome-linux/chrome"]) { for (const sub of ["chrome-linux64/chrome", "chrome-linux/chrome"]) {
const p = resolve(root, b, sub); const p = resolve(root, b, sub);
if (existsSync(p)) return p; if (existsSync(p)) return p;
} }
} }
// The ms-playwright cache is EMPTY on this machine as of 2026-08-17, and
// returning undefined does not "let playwright pick" gracefully — it makes
// playwright reach for the exact build it shipped against
// (chromium_headless_shell-1234), which was never downloaded, and every render
// dies with "Executable doesn't exist". The system Chromium is a full build
// and is what the SoundCloud transport already drives, so prefer it over
// pulling ~150 MB into PLN's shared cache behind his back.
if (existsSync("/usr/bin/chromium")) return "/usr/bin/chromium";
return undefined; return undefined;
} }
...@@ -265,20 +274,32 @@ try { ...@@ -265,20 +274,32 @@ try {
console.log(` fx ${Object.keys(job.fx).join(", ")}`); console.log(` fx ${Object.keys(job.fx).join(", ")}`);
// --- 2. drive the page -------------------------------------------------- // --- 2. drive the page --------------------------------------------------
// MEASURED 2026-08-17, and it overturns the previous comment here.
//
// `--gl hw` did not get hardware GL. Asked three launch modes what WebGL
// actually resolves to on this box:
//
// headless + --use-angle=swiftshader -> SwiftShader (Subzero)
// headless + --use-gl=egl -> SwiftShader (Subzero) <- same!
// HEADFUL -> Mesa Intel UHD (CML GT2)
//
// So in headless the GPU is unreachable regardless of flags, and `--gl hw`
// silently stayed on software while claiming otherwise — which is why a
// 1920x1080 landscape render captured 3.5 fps on swiftshader and 3.2 fps on
// "hw", tripping the under-5-fps guard both times. The old note said the hw
// path "falls back by simply measuring worse"; it was not falling back, it
// never left software.
//
// Hardware GL therefore REQUIRES headful, on the real display. Same
// conclusion as the SoundCloud and YouTube transports: be a real browser.
// Costs a visible window for the duration of the capture.
const headful = A.gl === "hw";
browser = await chromium.launch({ browser = await chromium.launch({
headless: true, headless: !headful,
// Playwright 1.60 wants chromium build 1223; the cache here has 1217, which
// runs these pages fine. Point at what exists rather than pulling ~150 MB
// into PLN's shared browser cache behind his back — and prefer the FULL
// chromium over chrome-headless-shell, because the shell build ships without
// the GPU/ANGLE stack this needs for WebGL.
executablePath: chromeBinary(), executablePath: chromeBinary(),
args: [ args: [
// GL backend is measurable, not a guess: swiftshader renders these scenes at ...(headful
// ~7 fps at 1080x1920, which is not a clip. `--gl hw` asks for the real ? ["--enable-gpu-rasterization"]
// integrated GPU via EGL. Falls back by simply measuring worse.
...(A.gl === "hw"
? ["--use-gl=egl", "--enable-gpu-rasterization"]
: ["--use-gl=angle", "--use-angle=swiftshader"]), : ["--use-gl=angle", "--use-angle=swiftshader"]),
"--ignore-gpu-blocklist", "--ignore-gpu-blocklist",
"--autoplay-policy=no-user-gesture-required", "--autoplay-policy=no-user-gesture-required",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment