mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
Add native GPU static layout export path
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user