Add native GPU static layout export path

This commit is contained in:
wiiiii123
2026-05-03 22:43:23 +07:00
parent 816116de26
commit 9c81006e5c
62 changed files with 9819 additions and 328 deletions
+71 -13
View File
@@ -11,7 +11,7 @@ import ffmpegStatic from "ffmpeg-static";
const execFileAsync = promisify(execFile);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, "..");
const mainEntry = path.join(repoRoot, "dist-electron", "main.js");
const mainEntry = path.join(repoRoot, "dist-electron", "main.cjs");
const rendererEntry = path.join(repoRoot, "dist", "index.html");
const width = parseEvenInteger(process.env.RECORDLY_BENCH_EXPORT_WIDTH ?? "1280", "Width");
@@ -28,9 +28,13 @@ const timeoutMs = parsePositiveInteger(
const runsPerVariant = parsePositiveInteger(process.env.RECORDLY_BENCH_EXPORT_RUNS ?? "2", "Runs");
const useNativeExport = process.env.RECORDLY_BENCH_EXPORT_USE_NATIVE === "1";
const useWebcamOverlay = process.env.RECORDLY_BENCH_EXPORT_ENABLE_WEBCAM === "1";
const providedInputPath = process.env.RECORDLY_BENCH_EXPORT_INPUT ?? null;
const providedWebcamInputPath = process.env.RECORDLY_BENCH_EXPORT_WEBCAM_INPUT ?? null;
const keepTempArtifacts = process.env.RECORDLY_BENCH_EXPORT_KEEP_TEMP === "1";
const exportEncodingMode = parseExportEncodingMode(
process.env.RECORDLY_BENCH_EXPORT_ENCODING_MODE ?? null,
);
const exportQuality = parseExportQuality(process.env.RECORDLY_BENCH_EXPORT_QUALITY ?? null);
const exportShadowIntensity = parseExportShadowIntensity(
process.env.RECORDLY_BENCH_EXPORT_SHADOW_INTENSITY ?? null,
);
@@ -49,6 +53,9 @@ const webcamSize = parseExportWebcamSize(process.env.RECORDLY_BENCH_EXPORT_WEBCA
const MODERN_BACKEND_SWEEP = ["auto", "webcodecs", "breeze"];
const exportPipeline = parseExportPipeline(process.env.RECORDLY_BENCH_EXPORT_PIPELINE ?? null);
const exportBackend = parseExportBackend(process.env.RECORDLY_BENCH_EXPORT_BACKEND ?? null);
const exportRenderBackend = parseRenderBackend(
process.env.RECORDLY_BENCH_EXPORT_RENDER_BACKEND ?? null,
);
const exportBackendList = parseExportBackendList(
process.env.RECORDLY_BENCH_EXPORT_BACKENDS ?? null,
);
@@ -113,6 +120,18 @@ function parseExportBackend(rawValue) {
throw new Error("RECORDLY_BENCH_EXPORT_BACKEND must be 'auto', 'webcodecs', or 'breeze'");
}
function parseRenderBackend(rawValue) {
if (rawValue === null || rawValue === "") {
return null;
}
if (rawValue === "webgl" || rawValue === "webgpu") {
return rawValue;
}
throw new Error("RECORDLY_BENCH_EXPORT_RENDER_BACKEND must be 'webgl' or 'webgpu'");
}
function parseExportBackendList(rawValue) {
if (rawValue === null || rawValue === "") {
return null;
@@ -177,6 +196,18 @@ function parseExportEncodingMode(rawValue) {
throw new Error("RECORDLY_BENCH_EXPORT_ENCODING_MODE must be 'fast', 'balanced', or 'quality'");
}
function parseExportQuality(rawValue) {
if (rawValue === null || rawValue === "") {
return null;
}
if (rawValue === "medium" || rawValue === "good" || rawValue === "high" || rawValue === "source") {
return rawValue;
}
throw new Error("RECORDLY_BENCH_EXPORT_QUALITY must be 'medium', 'good', 'high', or 'source'");
}
function parseExportShadowIntensity(rawValue) {
if (rawValue === null || rawValue === "") {
return null;
@@ -477,8 +508,10 @@ function buildRequestedConfigRows(benchmarkRequests) {
{ key: "Runs per variant", value: runsPerVariant },
{ key: "Pipeline", value: exportPipeline ?? "default" },
{ key: "Requested backends", value: benchmarkRequests.map((request) => request.label) },
{ key: "Render backend", value: exportRenderBackend ?? "default" },
{ key: "Backend sweep", value: formatBoolean(benchmarkRequests.length > 1) },
{ key: "Encoding mode", value: exportEncodingMode ?? "default" },
{ key: "Quality", value: exportQuality ?? "default" },
{ key: "Shadow intensity", value: exportShadowIntensity ?? "default" },
{ key: "Webcam enabled", value: formatBoolean(useWebcamOverlay) },
{ key: "Experimental native override", value: formatBoolean(useNativeExport) },
@@ -665,6 +698,7 @@ async function runVariant(
...(exportEncodingMode
? { RECORDLY_SMOKE_EXPORT_ENCODING_MODE: exportEncodingMode }
: {}),
...(exportQuality ? { RECORDLY_SMOKE_EXPORT_QUALITY: exportQuality } : {}),
...(exportShadowIntensity !== null
? { RECORDLY_SMOKE_EXPORT_SHADOW_INTENSITY: String(exportShadowIntensity) }
: {}),
@@ -681,6 +715,9 @@ async function runVariant(
...(benchmarkRequest.backend
? { RECORDLY_SMOKE_EXPORT_BACKEND: benchmarkRequest.backend }
: {}),
...(exportRenderBackend
? { RECORDLY_SMOKE_EXPORT_RENDER_BACKEND: exportRenderBackend }
: {}),
...(typeof variant.maxEncodeQueue === "number"
? { RECORDLY_SMOKE_EXPORT_MAX_ENCODE_QUEUE: String(variant.maxEncodeQueue) }
: {}),
@@ -869,28 +906,45 @@ async function main() {
requestedPipeline: exportPipeline,
requestedBackend: exportBackend,
requestedBackends: benchmarkRequests.map((request) => request.label),
requestedRenderBackend: exportRenderBackend,
backendSweepEnabled: benchmarkRequests.length > 1,
requestedEncodingMode: exportEncodingMode,
requestedQuality: exportQuality,
requestedShadowIntensity: exportShadowIntensity,
webcamEnabled: useWebcamOverlay,
providedInput: providedInputPath,
providedWebcamInput: providedWebcamInputPath,
keepTempArtifacts,
requestedWebcamShadowIntensity: webcamShadowIntensity,
requestedWebcamSize: webcamSize,
}),
);
printRequestedConfigTable(benchmarkRequests);
console.log(`[benchmark-export-queues] Generating fixture video: ${inputPath}`);
await createFixtureVideo(ffmpegStatic, inputPath);
if (providedInputPath) {
console.log(`[benchmark-export-queues] Using provided input video: ${providedInputPath}`);
await fs.copyFile(providedInputPath, inputPath);
} else {
console.log(`[benchmark-export-queues] Generating fixture video: ${inputPath}`);
await createFixtureVideo(ffmpegStatic, inputPath);
}
if (webcamInputPath) {
console.log(
`[benchmark-export-queues] Generating webcam fixture video: ${webcamInputPath}`,
);
await createFixtureVideo(ffmpegStatic, webcamInputPath, {
fixtureWidth: webcamWidth,
fixtureHeight: webcamHeight,
includeAudio: false,
videoFilter: `testsrc=size=${webcamWidth}x${webcamHeight}:rate=${frameRate}`,
});
if (providedWebcamInputPath) {
console.log(
`[benchmark-export-queues] Using provided webcam video: ${providedWebcamInputPath}`,
);
await fs.copyFile(providedWebcamInputPath, webcamInputPath);
} else {
console.log(
`[benchmark-export-queues] Generating webcam fixture video: ${webcamInputPath}`,
);
await createFixtureVideo(ffmpegStatic, webcamInputPath, {
fixtureWidth: webcamWidth,
fixtureHeight: webcamHeight,
includeAudio: false,
videoFilter: `testsrc=size=${webcamWidth}x${webcamHeight}:rate=${frameRate}`,
});
}
}
const benchmarkResults = [];
@@ -968,7 +1022,11 @@ async function main() {
);
}
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
if (keepTempArtifacts) {
console.log(`[benchmark-export-queues] Preserved temp artifacts: ${tempDir}`);
} else {
await fs.rm(tempDir, { recursive: true, force: true });
}
}
}
+166
View File
@@ -0,0 +1,166 @@
import { execSync } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
import {
formatNativeHelperManifestWarning,
updateNativeHelperManifest,
verifyNativeHelperManifest,
} from "./native-helper-manifest.mjs";
const projectRoot = process.cwd();
const sourceDir = path.join(projectRoot, "electron", "native", "gpu-export-probe");
const buildDir = path.join(sourceDir, "build");
const bundledDir = path.join(
projectRoot,
"electron",
"native",
"bin",
process.arch === "arm64" ? "win32-arm64" : "win32-x64",
);
const bundledExePath = path.join(bundledDir, "recordly-gpu-export.exe");
const helperId = "recordly-gpu-export";
const generatorArch = process.arch === "arm64" ? "ARM64" : "x64";
if (process.platform !== "win32") {
console.log("[build-windows-gpu-export] Skipping Windows GPU export helper build.");
process.exit(0);
}
if (!existsSync(path.join(sourceDir, "CMakeLists.txt"))) {
console.error("[build-windows-gpu-export] CMakeLists.txt not found at", sourceDir);
process.exit(1);
}
function findCmake() {
try {
execSync("cmake --version", { stdio: "pipe" });
return "cmake";
} catch {
// Continue probing common Windows install locations.
}
const standaloneCmakePaths = [
path.join("C:", "Program Files", "CMake", "bin", "cmake.exe"),
path.join("C:", "Program Files (x86)", "CMake", "bin", "cmake.exe"),
];
for (const cmakePath of standaloneCmakePaths) {
if (existsSync(cmakePath)) {
return `"${cmakePath}"`;
}
}
const vsRoots = [
path.join("C:", "Program Files", "Microsoft Visual Studio"),
path.join("C:", "Program Files (x86)", "Microsoft Visual Studio"),
];
const vsEditions = ["Community", "Professional", "Enterprise", "BuildTools"];
const vsVersions = ["2022", "2019"];
for (const root of vsRoots) {
for (const version of vsVersions) {
for (const edition of vsEditions) {
const cmakePath = path.join(
root,
version,
edition,
"Common7",
"IDE",
"CommonExtensions",
"Microsoft",
"CMake",
"CMake",
"bin",
"cmake.exe",
);
if (existsSync(cmakePath)) {
return `"${cmakePath}"`;
}
}
}
}
return null;
}
const cmake = findCmake();
if (!cmake) {
if (existsSync(bundledExePath)) {
const verification = verifyNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "recordly-gpu-export.exe",
});
if (!verification.ok) {
console.warn(formatNativeHelperManifestWarning("build-windows-gpu-export", verification));
}
console.log(`[build-windows-gpu-export] Using bundled helper: ${bundledExePath}`);
process.exit(0);
}
console.error(
"[build-windows-gpu-export] CMake not found. Install Visual Studio with C++ CMake tools or standalone CMake.",
);
process.exit(1);
}
mkdirSync(buildDir, { recursive: true });
function clearCmakeCache() {
rmSync(path.join(buildDir, "CMakeCache.txt"), { force: true });
rmSync(path.join(buildDir, "CMakeFiles"), { recursive: true, force: true });
}
console.log("[build-windows-gpu-export] Configuring CMake...");
try {
clearCmakeCache();
execSync(`${cmake} .. -G "Visual Studio 17 2022" -A ${generatorArch}`, {
cwd: buildDir,
stdio: "inherit",
timeout: 120000,
});
} catch {
console.log("[build-windows-gpu-export] VS 2022 generator not found, trying VS 2019...");
try {
clearCmakeCache();
execSync(`${cmake} .. -G "Visual Studio 16 2019" -A ${generatorArch}`, {
cwd: buildDir,
stdio: "inherit",
timeout: 120000,
});
} catch (error) {
console.error("[build-windows-gpu-export] CMake configure failed:", error.message);
process.exit(1);
}
}
console.log("[build-windows-gpu-export] Building Windows GPU export helper...");
try {
execSync(`${cmake} --build . --config Release`, {
cwd: buildDir,
stdio: "inherit",
timeout: 300000,
});
} catch (error) {
console.error("[build-windows-gpu-export] Build failed:", error.message);
process.exit(1);
}
const exePath = path.join(buildDir, "Release", "gpu-export-probe.exe");
if (!existsSync(exePath)) {
console.error("[build-windows-gpu-export] Expected exe not found at", exePath);
process.exit(1);
}
mkdirSync(bundledDir, { recursive: true });
copyFileSync(exePath, bundledExePath);
console.log(`[build-windows-gpu-export] Staged bundled helper: ${bundledExePath}`);
const manifestPath = updateNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "recordly-gpu-export.exe",
});
console.log(`[build-windows-gpu-export] Updated helper manifest: ${manifestPath}`);
+5
View File
@@ -166,6 +166,11 @@ function getExpectedNativeHelperFiles(archTag) {
label: "Windows cursor monitor helper",
executable: true,
},
{
name: "recordly-gpu-export.exe",
label: "Windows GPU export helper",
executable: true,
},
{ name: "helpers-manifest.json", label: "Windows helper manifest" },
{ name: "whisper-cli.exe", label: "Whisper CLI runtime", executable: true },
{ name: "whisper-runtime.json", label: "Whisper runtime manifest" },