@hyperframes/engine 0.8.156 → 0.8.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/browserLeasePool.d.ts +1 -0
- package/dist/services/browserLeasePool.js +1 -0
- package/dist/services/browserManager.d.ts +7 -9
- package/dist/services/browserManager.js +33 -30
- package/dist/services/frameCapture.js +3 -5
- package/dist/services/wslHost.d.ts +1 -7
- package/dist/services/wslHost.js +6 -19
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ export type BrowserPoolState = "launching" | "ready" | "closing";
|
|
|
4
4
|
export interface BrowserLaunchFingerprint {
|
|
5
5
|
readonly args: readonly string[];
|
|
6
6
|
readonly executablePath?: string;
|
|
7
|
+
readonly withoutWaylandDisplay?: boolean;
|
|
7
8
|
readonly browserTimeoutMs: number;
|
|
8
9
|
readonly protocolTimeoutMs: number;
|
|
9
10
|
readonly requestedCaptureMode: CaptureMode;
|
|
@@ -8,6 +8,7 @@ function fingerprintKey(fingerprint) {
|
|
|
8
8
|
return JSON.stringify([
|
|
9
9
|
fingerprint.args,
|
|
10
10
|
fingerprint.executablePath ?? null,
|
|
11
|
+
fingerprint.withoutWaylandDisplay === true,
|
|
11
12
|
fingerprint.browserTimeoutMs,
|
|
12
13
|
fingerprint.protocolTimeoutMs,
|
|
13
14
|
fingerprint.requestedCaptureMode,
|
|
@@ -25,8 +25,6 @@ interface GpuProbeOptions {
|
|
|
25
25
|
platform?: NodeJS.Platform;
|
|
26
26
|
probeLifecycle?: GpuProbeLifecycle;
|
|
27
27
|
wslHost?: WslHostFacts;
|
|
28
|
-
/** Finds full Chrome for the WSL fallback; tests pass a fake. */
|
|
29
|
-
findFullChrome?: () => string | undefined;
|
|
30
28
|
}
|
|
31
29
|
export type AcquiredBrowser = BrowserLease;
|
|
32
30
|
/**
|
|
@@ -59,11 +57,8 @@ export declare const _probeBeginFrameSupportForTests: typeof probeBeginFrameSupp
|
|
|
59
57
|
export declare const _closeBrowserAfterFailedProbeForTests: typeof closeBrowserAfterFailedProbe;
|
|
60
58
|
/** Test-only: reset the cached probe result. */
|
|
61
59
|
export declare function _resetAutoBrowserGpuModeCacheForTests(): void;
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
* the GPU probe fell back to; otherwise the headless shell.
|
|
65
|
-
*/
|
|
66
|
-
export declare function resolveCaptureBrowserPath(resolvedMode: "software" | "hardware", config?: Partial<Pick<EngineConfig, "chromePath">>): Promise<string | undefined>;
|
|
60
|
+
/** True when a `resolvedMode` render must launch without WAYLAND_DISPLAY to reach the GPU. */
|
|
61
|
+
export declare function captureWithoutWaylandDisplay(resolvedMode: "software" | "hardware"): Promise<boolean>;
|
|
67
62
|
/**
|
|
68
63
|
* Resolve `browserGpuMode` to a concrete `"software" | "hardware"` answer.
|
|
69
64
|
*
|
|
@@ -91,10 +86,13 @@ export declare function resolveCaptureBrowserPath(resolvedMode: "software" | "ha
|
|
|
91
86
|
* render.
|
|
92
87
|
*/
|
|
93
88
|
export declare function resolveBrowserGpuMode(mode: EngineConfig["browserGpuMode"], options?: GpuProbeOptions): Promise<"software" | "hardware">;
|
|
94
|
-
|
|
89
|
+
type BrowserLaunchConfig = Partial<Pick<EngineConfig, "browserTimeout" | "protocolTimeout" | "chromePath" | "forceScreenshot">> & {
|
|
90
|
+
withoutWaylandDisplay?: boolean;
|
|
91
|
+
};
|
|
92
|
+
declare function createBrowserLaunchFingerprint(chromeArgs: string[], config?: BrowserLaunchConfig): BrowserLaunchFingerprint;
|
|
95
93
|
/** Test-only export for launch-argument/capture-mode agreement. */
|
|
96
94
|
export declare const _createBrowserLaunchFingerprintForTests: typeof createBrowserLaunchFingerprint;
|
|
97
|
-
export declare function acquireBrowser(chromeArgs: string[], config?: Partial<Pick<EngineConfig, "
|
|
95
|
+
export declare function acquireBrowser(chromeArgs: string[], config?: BrowserLaunchConfig & Partial<Pick<EngineConfig, "enableBrowserPool">>): Promise<AcquiredBrowser>;
|
|
98
96
|
export declare function releaseBrowser(browser: Browser, _config?: Partial<Pick<EngineConfig, "enableBrowserPool">>): Promise<void>;
|
|
99
97
|
export declare function forceReleaseBrowser(browser: Browser): void;
|
|
100
98
|
/**
|
|
@@ -13,7 +13,7 @@ import { homedir } from "os";
|
|
|
13
13
|
import { chromeMajorCeiling, exceedsChromeCeiling } from "./chromeHostCeiling.js";
|
|
14
14
|
import { DEFAULT_CONFIG } from "../config.js";
|
|
15
15
|
import { getSystemTotalMb, LOW_MEMORY_TOTAL_MB_THRESHOLD } from "./systemMemory.js";
|
|
16
|
-
import { describeWslGpuAccess, detectWsl
|
|
16
|
+
import { describeWslGpuAccess, detectWsl } from "./wslHost.js";
|
|
17
17
|
import { BrowserLeasePool, } from "./browserLeasePool.js";
|
|
18
18
|
export { BrowserLeasePool } from "./browserLeasePool.js";
|
|
19
19
|
let _puppeteer;
|
|
@@ -58,6 +58,7 @@ async function probeHardwareWebGlInfo(ppt, options) {
|
|
|
58
58
|
args: options.args,
|
|
59
59
|
defaultViewport: { width: 64, height: 64 },
|
|
60
60
|
executablePath: options.executablePath,
|
|
61
|
+
env: options.env,
|
|
61
62
|
timeout: options.browserTimeout,
|
|
62
63
|
waitForInitialPage: false,
|
|
63
64
|
};
|
|
@@ -427,39 +428,31 @@ function formatProbeFailure(err) {
|
|
|
427
428
|
return `probe failed (${err instanceof Error ? err.message : String(err)})`;
|
|
428
429
|
}
|
|
429
430
|
async function probeAutoBrowserGpuMode(options) {
|
|
430
|
-
const executablePath = options.chromePath ?? resolveHeadlessShellPath({});
|
|
431
431
|
const ppt = await getPuppeteerOrNull();
|
|
432
432
|
if (ppt === null) {
|
|
433
433
|
logResolvedBrowserGpuMode("software", "puppeteer unavailable");
|
|
434
434
|
return { mode: "software", cause: "probe-error" };
|
|
435
435
|
}
|
|
436
|
-
const probed = await probeWebGlMode(ppt,
|
|
437
|
-
if (probed.mode === "hardware"
|
|
436
|
+
const probed = await probeWebGlMode(ppt, options);
|
|
437
|
+
if (probed.mode === "hardware")
|
|
438
438
|
return probed;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
: await probeWebGlMode(ppt, fullChrome, options, "full Chrome: ");
|
|
450
|
-
if (viaFullChrome.mode === "hardware")
|
|
451
|
-
return { mode: "hardware", browserPath: fullChrome };
|
|
452
|
-
route = { state: viaFullChrome.cause ?? "no-gpu", path: fullChrome };
|
|
453
|
-
}
|
|
454
|
-
return { ...shellOutcome, wslAccess: describeWslGpuAccess(options.wslHost, route) ?? undefined };
|
|
455
|
-
}
|
|
456
|
-
async function probeWebGlMode(ppt, executablePath, options, label = "") {
|
|
439
|
+
// Chrome 150+ uses WSLg Wayland EGL, which lacks offscreen configs: https://issues.chromium.org/issues/567222741
|
|
440
|
+
if (detectWsl(options.wslHost) && process.env.WAYLAND_DISPLAY !== undefined) {
|
|
441
|
+
const env = envWithoutWaylandDisplay();
|
|
442
|
+
const retried = await probeWebGlMode(ppt, options, env, "without WAYLAND_DISPLAY: ");
|
|
443
|
+
if (retried.mode === "hardware")
|
|
444
|
+
return { mode: "hardware", withoutWaylandDisplay: true };
|
|
445
|
+
}
|
|
446
|
+
return { ...probed, ...wslAccess(options) };
|
|
447
|
+
}
|
|
448
|
+
async function probeWebGlMode(ppt, options, env, label = "") {
|
|
457
449
|
try {
|
|
458
450
|
const info = await probeHardwareWebGlInfo(ppt, {
|
|
459
451
|
args: getHardwareGpuProbeArgs(options.platform ?? process.platform),
|
|
460
452
|
browserTimeout: options.browserTimeout ?? DEFAULT_CONFIG.browserTimeout,
|
|
461
|
-
executablePath,
|
|
453
|
+
executablePath: options.chromePath ?? resolveHeadlessShellPath({}),
|
|
462
454
|
probeLifecycle: options.probeLifecycle,
|
|
455
|
+
env,
|
|
463
456
|
});
|
|
464
457
|
const resolved = resolveWebGlProbeMode(info);
|
|
465
458
|
logResolvedBrowserGpuMode(resolved, label + describeWebGlProbe(info));
|
|
@@ -470,13 +463,19 @@ async function probeWebGlMode(ppt, executablePath, options, label = "") {
|
|
|
470
463
|
return { mode: "software", cause: "probe-error" };
|
|
471
464
|
}
|
|
472
465
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
466
|
+
function envWithoutWaylandDisplay() {
|
|
467
|
+
const { WAYLAND_DISPLAY: _wslg, ...env } = process.env;
|
|
468
|
+
return env;
|
|
469
|
+
}
|
|
470
|
+
/** True when a `resolvedMode` render must launch without WAYLAND_DISPLAY to reach the GPU. */
|
|
471
|
+
export async function captureWithoutWaylandDisplay(resolvedMode) {
|
|
472
|
+
if (resolvedMode !== "hardware")
|
|
473
|
+
return false;
|
|
474
|
+
return (await _autoBrowserGpuModeCache)?.withoutWaylandDisplay === true;
|
|
475
|
+
}
|
|
476
|
+
function wslAccess(options) {
|
|
477
|
+
const lines = describeWslGpuAccess(options.wslHost);
|
|
478
|
+
return lines ? { wslAccess: lines } : {};
|
|
480
479
|
}
|
|
481
480
|
/**
|
|
482
481
|
* Resolve `browserGpuMode` to a concrete `"software" | "hardware"` answer.
|
|
@@ -620,6 +619,7 @@ function createBrowserLaunchFingerprint(chromeArgs, config) {
|
|
|
620
619
|
return {
|
|
621
620
|
args: chromeArgs,
|
|
622
621
|
executablePath: headlessShell,
|
|
622
|
+
withoutWaylandDisplay: launchConfig.withoutWaylandDisplay === true,
|
|
623
623
|
browserTimeoutMs: launchConfig.browserTimeout,
|
|
624
624
|
protocolTimeoutMs: launchConfig.protocolTimeout,
|
|
625
625
|
requestedCaptureMode,
|
|
@@ -634,6 +634,7 @@ export async function acquireBrowser(chromeArgs, config) {
|
|
|
634
634
|
// fallow-ignore-next-line complexity
|
|
635
635
|
async function launchBrowser(fingerprint) {
|
|
636
636
|
const ppt = await getPuppeteer();
|
|
637
|
+
const env = fingerprint.withoutWaylandDisplay ? envWithoutWaylandDisplay() : undefined;
|
|
637
638
|
let captureMode = fingerprint.requestedCaptureMode;
|
|
638
639
|
let browser;
|
|
639
640
|
try {
|
|
@@ -643,6 +644,7 @@ async function launchBrowser(fingerprint) {
|
|
|
643
644
|
args: [...fingerprint.args],
|
|
644
645
|
defaultViewport: null,
|
|
645
646
|
executablePath: fingerprint.executablePath,
|
|
647
|
+
env,
|
|
646
648
|
timeout: fingerprint.browserTimeoutMs,
|
|
647
649
|
protocolTimeout: fingerprint.protocolTimeoutMs,
|
|
648
650
|
});
|
|
@@ -667,6 +669,7 @@ async function launchBrowser(fingerprint) {
|
|
|
667
669
|
args: stripBeginFrameFlags([...fingerprint.args]),
|
|
668
670
|
defaultViewport: null,
|
|
669
671
|
executablePath: fingerprint.executablePath,
|
|
672
|
+
env,
|
|
670
673
|
timeout: fingerprint.browserTimeoutMs,
|
|
671
674
|
protocolTimeout: fingerprint.protocolTimeoutMs,
|
|
672
675
|
});
|
|
@@ -15,7 +15,7 @@ import { DrawElementCaptureError } from "./drawElementCaptureError.js";
|
|
|
15
15
|
import { encodePng } from "../utils/alphaBlit.js";
|
|
16
16
|
import { DEFAULT_SAMPLES_PER_FRAME, MotionBlurAccumulator, SPATIAL_TWEEN_PROPERTIES, adaptiveSampleCount, motionBlurProbeTimes, motionBlurSampleTimes, motionBlurWindowIsStatic, probeDiffMagnitude, resolveMotionBlurPlan, } from "./motionBlur.js";
|
|
17
17
|
// ── Extracted modules ───────────────────────────────────────────────────────
|
|
18
|
-
import { acquireBrowser, releaseBrowser, forceReleaseBrowser, buildChromeArgs, resolveBrowserGpuMode,
|
|
18
|
+
import { acquireBrowser, releaseBrowser, forceReleaseBrowser, buildChromeArgs, resolveBrowserGpuMode, captureWithoutWaylandDisplay, resolveHeadlessShellPath, compositionRequiresWebGpu, assertWebGpuAdapterAvailable, usesSoftwareWebGpu, } from "./browserManager.js";
|
|
19
19
|
import { beginFrameCapture, ensureRenderFrameSiblings, getCdpSession, pageContentExceedsCaptureHeight, pageScreenshotCapture, initTransparentBackground, shouldDefaultCaptureBeyondViewport, } from "./screenshotService.js";
|
|
20
20
|
import { classifyGpuRenderer, detectGpuBackend, injectDrawElementCanvas, captureDrawElementFrame, resolveDrawElementCaptureMode, instrumentAcceleratedCanvases, initDrawElementWorkerEncode, cleanupDrawElementWorkerEncode, produceDrawElementFrame, produceDrawElementFrameBatch, DE_CANVAS_NOT_INITIALIZED_CODE, } from "./drawElementService.js";
|
|
21
21
|
import { initThreeDProjection, detectCssEffectRisk } from "./threeDProjection.js";
|
|
@@ -786,10 +786,7 @@ export async function createCaptureSession(serverUrl, outputDir, options, onBefo
|
|
|
786
786
|
// boolean `forceScreenshot === false` is otherwise ambiguous between
|
|
787
787
|
// default and explicit opt-out).
|
|
788
788
|
const effectiveForceScreenshot = applyConcreteGpuScreenshotClamp(forceScreenshot, resolvedGpuMode, config);
|
|
789
|
-
const browserPath = await resolveCaptureBrowserPath(resolvedGpuMode, config);
|
|
790
|
-
const wslFullChromeHasNoBeginFrame = browserPath !== headlessShell;
|
|
791
789
|
const preMode = headlessShell &&
|
|
792
|
-
!wslFullChromeHasNoBeginFrame &&
|
|
793
790
|
isLinux &&
|
|
794
791
|
!effectiveForceScreenshot &&
|
|
795
792
|
!supersampling &&
|
|
@@ -807,7 +804,8 @@ export async function createCaptureSession(serverUrl, outputDir, options, onBefo
|
|
|
807
804
|
.catch(() => false));
|
|
808
805
|
const gpuConfig = { ...config, browserGpuMode: resolvedGpuMode };
|
|
809
806
|
const chromeArgs = buildChromeArgs({ width: options.width, height: options.height, captureMode: preMode, requiresWebGpu }, gpuConfig);
|
|
810
|
-
const
|
|
807
|
+
const withoutWaylandDisplay = await captureWithoutWaylandDisplay(resolvedGpuMode);
|
|
808
|
+
const browserLease = await acquireBrowser(chromeArgs, { ...config, withoutWaylandDisplay });
|
|
811
809
|
return constructCaptureSessionWithRollback({
|
|
812
810
|
browserLease,
|
|
813
811
|
serverUrl,
|
|
@@ -17,14 +17,8 @@ export interface WslHostFacts {
|
|
|
17
17
|
renderNodeAccess(path: string): RenderNodeAccess;
|
|
18
18
|
}
|
|
19
19
|
export declare function detectWsl(host?: WslHostFacts): boolean;
|
|
20
|
-
export type FullChromeRoute = {
|
|
21
|
-
state: "missing";
|
|
22
|
-
} | {
|
|
23
|
-
state: "no-gpu" | "probe-error";
|
|
24
|
-
path: string;
|
|
25
|
-
};
|
|
26
20
|
/**
|
|
27
21
|
* Indented lines saying what we checked on the WSL GPU path and what to try,
|
|
28
22
|
* or null outside WSL. Says nothing about the render mode: callers own that.
|
|
29
23
|
*/
|
|
30
|
-
export declare function describeWslGpuAccess(host?: WslHostFacts
|
|
24
|
+
export declare function describeWslGpuAccess(host?: WslHostFacts): string[] | null;
|
package/dist/services/wslHost.js
CHANGED
|
@@ -57,24 +57,11 @@ export function detectWsl(host = realHost) {
|
|
|
57
57
|
const RENDER_NODE = "/dev/dri/renderD128";
|
|
58
58
|
const NATIVE_WINDOWS = "render from native Windows (project copied to a Windows drive, run from PowerShell), " +
|
|
59
59
|
"where Chrome uses the GPU through Direct3D.";
|
|
60
|
-
function routeToGpu(fullChrome) {
|
|
61
|
-
const viaFullChrome = fullChrome.state === "missing"
|
|
62
|
-
? [
|
|
63
|
-
" Under WSL, Chrome's headless shell often cannot reach the GPU while full Chrome can. " +
|
|
64
|
-
"Install full Chrome with `npx @puppeteer/browsers install chrome@stable --path " +
|
|
65
|
-
"~/.cache/puppeteer` and HyperFrames will try it when the headless shell finds no GPU " +
|
|
66
|
-
"(or point HYPERFRAMES_BROWSER_PATH at a full Chrome).",
|
|
67
|
-
]
|
|
68
|
-
: fullChrome.state === "no-gpu"
|
|
69
|
-
? [` Full Chrome (${fullChrome.path}) found no hardware GPU either.`]
|
|
70
|
-
: [` Full Chrome (${fullChrome.path}) could not finish its GPU check either.`];
|
|
71
|
-
return [...viaFullChrome, ` Or ${NATIVE_WINDOWS}`];
|
|
72
|
-
}
|
|
73
60
|
/**
|
|
74
61
|
* Indented lines saying what we checked on the WSL GPU path and what to try,
|
|
75
62
|
* or null outside WSL. Says nothing about the render mode: callers own that.
|
|
76
63
|
*/
|
|
77
|
-
export function describeWslGpuAccess(host = realHost
|
|
64
|
+
export function describeWslGpuAccess(host = realHost) {
|
|
78
65
|
if (!detectWsl(host))
|
|
79
66
|
return null;
|
|
80
67
|
const access = host.renderNodeAccess(RENDER_NODE);
|
|
@@ -82,21 +69,21 @@ export function describeWslGpuAccess(host = realHost, fullChrome = { state: "mis
|
|
|
82
69
|
return [
|
|
83
70
|
` Your Linux user cannot read and write ${RENDER_NODE} and is not in its group ` +
|
|
84
71
|
`("${access.group}"), which can stop Mesa from reaching the GPU.`,
|
|
85
|
-
`
|
|
86
|
-
"Windows
|
|
87
|
-
|
|
72
|
+
` Fix: run \`sudo usermod -aG ${access.group} $USER\`, then \`wsl --shutdown\` from ` +
|
|
73
|
+
"Windows, reopen WSL and render again.",
|
|
74
|
+
` Or ${NATIVE_WINDOWS}`,
|
|
88
75
|
];
|
|
89
76
|
}
|
|
90
77
|
if (access.state === "denied") {
|
|
91
78
|
return [
|
|
92
79
|
` Your Linux user is in the "${access.group}" group but still cannot read and write ` +
|
|
93
80
|
`${RENDER_NODE}; check its permissions with \`ls -l ${RENDER_NODE}\`.`,
|
|
94
|
-
|
|
81
|
+
` Or ${NATIVE_WINDOWS}`,
|
|
95
82
|
];
|
|
96
83
|
}
|
|
97
84
|
const seen = access.state === "missing" ? "does not exist" : "is readable and writable";
|
|
98
85
|
return [
|
|
99
86
|
` ${RENDER_NODE} ${seen}, so file permissions are not the cause.`,
|
|
100
|
-
|
|
87
|
+
` To use the GPU, ${NATIVE_WINDOWS}`,
|
|
101
88
|
];
|
|
102
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.157",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"linkedom": "^0.18.12",
|
|
51
51
|
"puppeteer": "^25.8.0",
|
|
52
52
|
"puppeteer-core": "^25.8.0",
|
|
53
|
-
"@hyperframes/parsers": "^0.8.
|
|
54
|
-
"@hyperframes/core": "^0.8.
|
|
53
|
+
"@hyperframes/parsers": "^0.8.157",
|
|
54
|
+
"@hyperframes/core": "^0.8.157"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.0.10",
|