Files
Recordly/electron/ipc/export/nativeVideo/gpuAttempt.ts
T
webadderall 088aa0bb94 Fix Recordly 2.0 part 2 review findings
Integrate validated app changes and preserve original stack history.
2026-11-07 14:48:46 +11:00

341 lines
12 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import {
getExperimentalNvidiaCudaExportSkipReason,
isExplicitNvidiaCudaExportEnabled,
isNvidiaCudaForceVideoOnlyEnabled,
isValidatedNvidiaCudaFallbackCandidate,
} from "./capabilities";
import { NVIDIA_CUDA_ALLOW_AUDIO_EXPORT_ENV } from "./constants";
import { canMuxNvidiaCudaSourceAudioInline } from "./gpuArgs";
import {
runExperimentalNvidiaCudaStaticLayoutExport,
runExperimentalWindowsGpuStaticLayoutExport,
} from "./gpuProcess";
import { hasNativeStaticLayoutTimeline } from "./layout";
import { prepareWindowsGpuWebcamInput } from "./sourceProxy";
import { removeTemporaryExportFile } from "./stream";
import {
prepareNvidiaCudaCursorAtlas,
prepareNvidiaCudaCursorTelemetry,
prepareWindowsGpuCursorAtlas,
prepareWindowsGpuCursorTelemetry,
prepareWindowsGpuZoomTelemetry,
} from "./telemetry";
import {
type NativeStaticLayoutExportMetrics,
type NativeStaticLayoutExportOptions,
type NativeStaticLayoutExportProgress,
type NativeStaticLayoutExportSession,
} from "./types";
import { validateNvidiaCudaExportSummary, validateWindowsGpuExportSummary } from "./validation";
export interface NativeStaticGpuAttemptOptions {
ffmpegPath: string;
options: NativeStaticLayoutExportOptions;
session: NativeStaticLayoutExportSession;
chunkDirectory: string;
videoOnlyPath: string;
metrics: NativeStaticLayoutExportMetrics;
onProgress?: (progress: NativeStaticLayoutExportProgress) => void;
validateRenderedVideoOutput: () => Promise<void>;
}
export async function tryNativeStaticGpuExport({
ffmpegPath,
options,
session,
chunkDirectory,
videoOnlyPath,
metrics,
onProgress,
validateRenderedVideoOutput,
}: NativeStaticGpuAttemptOptions) {
let didRenderVideo = false;
let didMuxAudioInline = false;
if (options.experimentalWindowsGpuCompositor && process.platform === "win32") {
try {
if (session.terminating) {
throw new Error("Native static layout export was cancelled");
}
let experimentalGpuOptions = options;
if (options.webcamInputPath) {
const webcamProxyPath = path.join(chunkDirectory, "webcam-proxy.mp4");
const webcamInput = await prepareWindowsGpuWebcamInput(
ffmpegPath,
options,
webcamProxyPath,
session,
);
if (webcamInput.elapsedMs > 0) {
metrics.staticAssetExecMs =
(metrics.staticAssetExecMs ?? 0) + webcamInput.elapsedMs;
}
if (webcamInput.inputPath && webcamInput.inputPath !== options.webcamInputPath) {
experimentalGpuOptions = {
...options,
webcamInputPath: webcamInput.inputPath,
};
}
}
if (options.cursorTelemetry?.length) {
const cursorTelemetryPath = await prepareWindowsGpuCursorTelemetry(
options,
path.join(chunkDirectory, "cursor-telemetry.csv"),
);
if (cursorTelemetryPath) {
experimentalGpuOptions = {
...experimentalGpuOptions,
cursorTelemetryPath,
};
}
const cursorAtlas = await prepareWindowsGpuCursorAtlas(
options,
path.join(chunkDirectory, "cursor-atlas.png"),
path.join(chunkDirectory, "cursor-atlas.csv"),
);
if (cursorAtlas) {
experimentalGpuOptions = {
...experimentalGpuOptions,
cursorAtlasPath: cursorAtlas.atlasPath,
cursorAtlasMetadataPath: cursorAtlas.metadataPath,
};
}
}
if (options.zoomTelemetry?.length) {
const zoomTelemetryPath = await prepareWindowsGpuZoomTelemetry(
options,
path.join(chunkDirectory, "zoom-telemetry.csv"),
);
if (zoomTelemetryPath) {
experimentalGpuOptions = {
...experimentalGpuOptions,
zoomTelemetryPath,
};
}
}
let experimentalNvidiaCudaOptions = experimentalGpuOptions;
const nvidiaCudaSkipReason = await getExperimentalNvidiaCudaExportSkipReason(options);
let shouldTryNvidiaCuda = nvidiaCudaSkipReason === null;
const validatedCudaFallbackCandidate = isValidatedNvidiaCudaFallbackCandidate(options);
if (
shouldTryNvidiaCuda &&
(validatedCudaFallbackCandidate || isNvidiaCudaForceVideoOnlyEnabled()) &&
(experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none") !== "none"
) {
experimentalNvidiaCudaOptions = {
...experimentalNvidiaCudaOptions,
nvidiaCudaForceVideoOnly: true,
};
console.info(
"[native-static-layout-export] NVIDIA CUDA candidate will use shared audio mux validation",
{
audioMode: experimentalNvidiaCudaOptions.audioOptions?.audioMode ?? "none",
forcedByEnv: isNvidiaCudaForceVideoOnlyEnabled(),
userOptIn: options.experimentalNvidiaCudaExport === true,
},
);
}
const shouldLogNvidiaCudaSkip =
isExplicitNvidiaCudaExportEnabled() ||
(options.experimentalNvidiaCudaExport === true &&
nvidiaCudaSkipReason !== "env-disabled");
if (
!shouldTryNvidiaCuda &&
shouldLogNvidiaCudaSkip &&
nvidiaCudaSkipReason !== "env-disabled"
) {
console.warn(
"[native-static-layout-export] Skipping NVIDIA CUDA compositor; falling back to Windows GPU compositor",
{
reason: nvidiaCudaSkipReason,
audioMode: options.audioOptions?.audioMode ?? "none",
overrideEnv: NVIDIA_CUDA_ALLOW_AUDIO_EXPORT_ENV,
userOptIn: options.experimentalNvidiaCudaExport === true,
},
);
}
if (shouldTryNvidiaCuda && options.cursorTelemetry?.length) {
const cursorTelemetryPath = await prepareNvidiaCudaCursorTelemetry(
options,
path.join(chunkDirectory, "cursor-telemetry.json"),
);
const cursorAtlas = await prepareNvidiaCudaCursorAtlas(
options,
path.join(chunkDirectory, "cursor-atlas-nvidia.png"),
path.join(chunkDirectory, "cursor-atlas-nvidia.tsv"),
);
shouldTryNvidiaCuda = Boolean(cursorTelemetryPath && cursorAtlas);
if (cursorTelemetryPath && cursorAtlas) {
experimentalNvidiaCudaOptions = {
...experimentalNvidiaCudaOptions,
cursorTelemetryPath,
cursorAtlasPath: cursorAtlas.atlasPath,
cursorAtlasMetadataPath: cursorAtlas.metadataPath,
};
}
}
if (shouldTryNvidiaCuda) {
try {
const shouldMuxAudioInline = canMuxNvidiaCudaSourceAudioInline(
experimentalNvidiaCudaOptions,
);
const cudaResult = await runExperimentalNvidiaCudaStaticLayoutExport(
ffmpegPath,
experimentalNvidiaCudaOptions,
videoOnlyPath,
chunkDirectory,
session,
onProgress,
);
const cudaValidationIssues = validateNvidiaCudaExportSummary(
cudaResult.summary,
{
durationSec: options.durationSec,
targetFrames: Math.ceil(options.durationSec * options.frameRate),
requiresTimelineSync: shouldMuxAudioInline,
},
);
if (cudaValidationIssues.length > 0) {
throw new Error(
`Experimental NVIDIA CUDA compositor produced an invalid output: ${cudaValidationIssues.join("; ")}`,
);
}
const outputStat = await fs.stat(videoOnlyPath);
if (outputStat.size <= 0) {
throw new Error(
"Experimental NVIDIA CUDA compositor produced an empty output file",
);
}
await validateRenderedVideoOutput();
console.info("[native-static-layout-export] NVIDIA CUDA compositor completed", {
elapsedMs: cudaResult.elapsedMs,
fps: cudaResult.summary.fps,
targetFrames: cudaResult.summary.targetFrames,
durationSec: cudaResult.summary.durationSec,
nativeEncodeMs: cudaResult.summary.timingsMs?.nativeEncode,
muxMs: cudaResult.summary.timingsMs?.mux,
endToEndMs: cudaResult.summary.timingsMs?.endToEnd,
nativeFps:
cudaResult.summary.nativeSummary?.measuredFps ??
cudaResult.summary.nativeSummary?.fps,
mappedDisplayFrames: cudaResult.summary.nativeSummary?.mappedDisplayFrames,
selectedDisplayFrames:
cudaResult.summary.nativeSummary?.selectedDisplayFrames,
skippedDisplayFrames:
cudaResult.summary.nativeSummary?.skippedDisplayFrames,
roiCompositeFrames: cudaResult.summary.nativeSummary?.roiCompositeFrames,
monolithicCompositeFrames:
cudaResult.summary.nativeSummary?.monolithicCompositeFrames,
copyCompositeFrames: cudaResult.summary.nativeSummary?.copyCompositeFrames,
webcamOverlay: cudaResult.summary.nativeSummary?.webcamOverlay,
cursorAtlas: cudaResult.summary.nativeSummary?.cursorAtlas,
zoomOverlay: cudaResult.summary.nativeSummary?.zoomOverlay,
zoomSamples: cudaResult.summary.nativeSummary?.zoomSamples,
});
metrics.chunkCount = 1;
metrics.chunkDurationSec = options.durationSec;
metrics.chunkExecMs += cudaResult.elapsedMs;
metrics.chunks.push({
index: 0,
startSec: 0,
durationSec: options.durationSec,
backend: "nvidia-cuda-compositor",
elapsedMs: cudaResult.elapsedMs,
outputBytes: outputStat.size,
nvidiaCudaSummary: cudaResult.summary,
});
didRenderVideo = true;
didMuxAudioInline = shouldMuxAudioInline;
} catch (error) {
if (session.terminating) {
throw error;
}
metrics.fallbackChunkCount++;
console.warn(
"[native-static-layout-export] Experimental NVIDIA CUDA compositor failed or produced invalid output; falling back to Windows GPU compositor:",
error,
);
await removeTemporaryExportFile(videoOnlyPath);
}
}
if (!didRenderVideo) {
const gpuResult = await runExperimentalWindowsGpuStaticLayoutExport(
experimentalGpuOptions,
videoOnlyPath,
session,
onProgress,
);
const gpuValidationIssues = validateWindowsGpuExportSummary(gpuResult.summary, {
durationSec: options.durationSec,
targetFrames: Math.ceil(options.durationSec * options.frameRate),
});
if (gpuValidationIssues.length > 0) {
throw new Error(
`Experimental Windows GPU compositor produced an invalid output: ${gpuValidationIssues.join("; ")}`,
);
}
await validateRenderedVideoOutput();
console.info("[native-static-layout-export] Windows GPU compositor completed", {
elapsedMs: gpuResult.elapsedMs,
width: gpuResult.summary.width,
height: gpuResult.summary.height,
fps: gpuResult.summary.fps,
frames: gpuResult.summary.frames,
realtimeMultiplier: gpuResult.summary.realtimeMultiplier,
surfacePoolSize: gpuResult.summary.surfacePoolSize,
gpuDecodeSurface: gpuResult.summary.gpuDecodeSurface,
adapterIndex: gpuResult.summary.adapterIndex,
encoderBackend: gpuResult.summary.encoderBackend,
encoderTuningApplied: gpuResult.summary.encoderTuningApplied,
readMs: gpuResult.summary.readMs,
videoProcessMs: gpuResult.summary.videoProcessMs,
writeSampleMs: gpuResult.summary.writeSampleMs,
finalizeMs: gpuResult.summary.finalizeMs,
webcamOverlay: gpuResult.summary.webcamOverlay,
cursorOverlay: gpuResult.summary.cursorOverlay,
cursorAtlas: gpuResult.summary.cursorAtlas,
zoomOverlay: gpuResult.summary.zoomOverlay,
});
const outputStat = await fs.stat(videoOnlyPath);
metrics.chunkCount = 1;
metrics.chunkDurationSec = options.durationSec;
metrics.chunkExecMs += gpuResult.elapsedMs;
metrics.chunks.push({
index: 0,
startSec: 0,
durationSec: options.durationSec,
backend: "windows-d3d11-compositor",
elapsedMs: gpuResult.elapsedMs,
outputBytes: outputStat.size,
windowsGpuSummary: gpuResult.summary,
});
didRenderVideo = true;
}
} catch (error) {
if (session.terminating) {
throw error;
}
if (hasNativeStaticLayoutTimeline(options)) {
throw error;
}
metrics.fallbackChunkCount++;
console.warn(
"[native-static-layout-export] Experimental Windows GPU compositor unavailable; falling back to FFmpeg static layout:",
error,
);
await removeTemporaryExportFile(videoOnlyPath);
if (
options.webcamInputPath ||
options.zoomTelemetry?.length ||
options.cursorTelemetry?.length
) {
throw error;
}
}
}
return { didRenderVideo, didMuxAudioInline };
}