// SPDX-License-Identifier: AGPL-3.0-or-later import {createRequire} from 'node:module'; import os from 'node:os'; import {stopWindowsScreenCaptureGuard} from '@electron/main/WindowsScreenCaptureGuard'; import {reportWindowsCaptureItemReuse} from '@electron/main/WindowsWindowCaptureItemReuse'; import {app, powerSaveBlocker} from 'electron'; import log from 'electron-log'; const STREAMING_PRIORITY = os.constants?.priority?.PRIORITY_ABOVE_NORMAL ?? -7; const CAN_ELEVATE_PROCESS_PRIORITY = process.platform === 'win32'; const GPU_PRIORITY_REFRESH_INTERVAL_MS = 20000; const GPU_SCHEDULING_PRIORITY_ENV = 'FLUXER_STREAMING_GPU_SCHEDULING_PRIORITY'; const MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS = 32; const requireModule = createRequire(import.meta.url); type WindowsGpuSchedulingPriority = 'high' | 'realtime'; type WindowsGpuPriorityTargetReason = | 'browser-main' | 'renderer' | 'tracked-renderer' | 'chromium-gpu' | 'chromium-video-capture' | 'chromium-video-encode' | 'chromium-media-utility'; const PROCESS_PRIORITY_TARGET_REASONS: ReadonlySet = new Set([ 'browser-main', 'renderer', 'tracked-renderer', ]); type GpuPriorityModuleStatus = 'disabled' | 'unsupported-platform' | 'not-loaded' | 'loaded' | 'unavailable'; type GpuPriorityAttemptStatus = | 'disabled' | 'unsupported-platform' | 'native-module-unavailable' | 'no-targets' | 'succeeded' | 'partial' | 'failed' | 'no-active-priority'; type WindowsGpuPriorityModule = { elevateGpuSchedulingPriority?: (processId?: number, priorityClass?: WindowsGpuSchedulingPriority) => boolean; restoreGpuSchedulingPriority?: (processId?: number) => boolean; loadError?: Error | null; }; interface GpuPriorityTarget { processId: number; reasons: Array; } interface GpuPriorityFailure { processId: number; reason: string; } interface GpuPriorityAcquireDiagnostics { status: GpuPriorityAttemptStatus; priorityClass: WindowsGpuSchedulingPriority | null; targets: Array; elevatedProcessIds: Array; skippedProcessIds: Array; failedProcessIds: Array; detail?: string; } interface GpuPriorityRestoreDiagnostics { status: GpuPriorityAttemptStatus; processIds: Array; restoredProcessIds: Array; failedProcessIds: Array; detail?: string; } export interface StreamingPriorityDiagnostics { refCount: number; powerSaveBlocker: { active: boolean; id: number | null; }; processPriority: { supported: boolean; streamingPriority: number; savedPriority: number | null; elevated: boolean; elevatedProcesses: Array<{processId: number; savedPriority: number}>; }; gpuScheduling: { supported: boolean; priorityClass: WindowsGpuSchedulingPriority | null; env: string; nativeModuleStatus: GpuPriorityModuleStatus; nativeModuleLoadErrorDetail: string | null; refreshActive: boolean; refreshIntervalMs: number; trackedWebContents: number; elevatedProcesses: Array<{processId: number; priorityClass: WindowsGpuSchedulingPriority}>; lastAcquire: GpuPriorityAcquireDiagnostics | null; lastRestore: GpuPriorityRestoreDiagnostics | null; }; } let refCount = 0; let powerBlockerId: number | null = null; const savedProcessPriorities = new Map(); let gpuPriorityModule: WindowsGpuPriorityModule | null | undefined; let gpuPriorityModuleLoadErrorDetail: string | null = null; const gpuPriorityElevatedProcessIds = new Map(); const streamingPriorityWebContents = new Set(); const streamingPriorityWebContentsCleanup = new Map void>(); const backgroundThrottlingBypassedWebContents = new Set(); let gpuPriorityRefreshTimer: NodeJS.Timeout | null = null; let lastGpuPriorityAcquire: GpuPriorityAcquireDiagnostics | null = null; let lastGpuPriorityRestore: GpuPriorityRestoreDiagnostics | null = null; function formatErrorDetail(error: unknown): string { if (error instanceof Error) return error.message; if ( typeof error === 'object' && error !== null && 'message' in error && typeof (error as {message?: unknown}).message === 'string' ) { return (error as {message: string}).message; } return String(error); } const GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL = 'Windows GPU priority native API unavailable'; function gpuPriorityModuleUnavailableReason(): string { if (gpuPriorityModuleLoadErrorDetail === null) return GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL; const lineBreakIndex = gpuPriorityModuleLoadErrorDetail.indexOf('\n'); if (lineBreakIndex === -1) return gpuPriorityModuleLoadErrorDetail; return gpuPriorityModuleLoadErrorDetail.slice(0, lineBreakIndex).trimEnd(); } function resolveGpuSchedulingPriority(): WindowsGpuSchedulingPriority | null { const raw = process.env[GPU_SCHEDULING_PRIORITY_ENV]; if (raw == null || raw.trim() === '') return 'high'; const normalized = raw.trim().toLowerCase(); if (normalized === 'realtime' || normalized === 'real-time') return 'realtime'; if (normalized === 'high') return 'high'; if (normalized === 'off' || normalized === 'disabled' || normalized === 'none') return null; log.warn('[StreamingPriority] Ignoring invalid GPU scheduling priority override', { env: GPU_SCHEDULING_PRIORITY_ENV, value: raw, }); return 'high'; } const GPU_SCHEDULING_PRIORITY = resolveGpuSchedulingPriority(); function startPowerSaveBlocker(): void { try { powerBlockerId = powerSaveBlocker.start('prevent-display-sleep'); } catch (error) { log.warn('[StreamingPriority] Failed to start power save blocker', {error}); } } function stopPowerSaveBlocker(): void { if (powerBlockerId === null) return; try { powerSaveBlocker.stop(powerBlockerId); } catch (error) { log.warn('[StreamingPriority] Failed to stop power save blocker', {error}); } powerBlockerId = null; } function elevateProcessPriority(processId: number): void { if (!CAN_ELEVATE_PROCESS_PRIORITY || savedProcessPriorities.has(processId)) return; try { const currentPriority = os.getPriority(processId); if (currentPriority <= STREAMING_PRIORITY) return; os.setPriority(processId, STREAMING_PRIORITY); savedProcessPriorities.set(processId, currentPriority); } catch (error) { log.debug('[StreamingPriority] Failed to elevate process priority', {processId, error}); } } function elevateProcessPriorityTargets(targets: Array): void { if (!CAN_ELEVATE_PROCESS_PRIORITY) return; for (const target of targets) { if (!target.reasons.some((reason) => PROCESS_PRIORITY_TARGET_REASONS.has(reason))) continue; elevateProcessPriority(target.processId); } } function restoreProcessPriority(): void { if (savedProcessPriorities.size === 0) return; const entries = [...savedProcessPriorities.entries()]; savedProcessPriorities.clear(); for (const [processId, priority] of entries) { try { os.setPriority(processId, priority); } catch (error) { log.debug('[StreamingPriority] Failed to restore process priority', {processId, error}); } } } function loadWindowsGpuPriorityModule(): WindowsGpuPriorityModule | null { if (process.platform !== 'win32') return null; if (gpuPriorityModule !== undefined) return gpuPriorityModule; try { const addon = requireModule('@fluxer/win-game-capture') as WindowsGpuPriorityModule; if (addon.loadError) { gpuPriorityModuleLoadErrorDetail = formatErrorDetail(addon.loadError); gpuPriorityModule = null; log.debug('[StreamingPriority] Windows GPU priority module reported load error', { detail: gpuPriorityModuleLoadErrorDetail, }); } else { gpuPriorityModuleLoadErrorDetail = null; gpuPriorityModule = addon; } return gpuPriorityModule; } catch (error) { gpuPriorityModuleLoadErrorDetail = formatErrorDetail(error); gpuPriorityModule = null; log.debug('[StreamingPriority] Failed to load Windows GPU priority module', { detail: gpuPriorityModuleLoadErrorDetail, }); return null; } } function addGpuPriorityTarget( targets: Map>, processId: number | undefined, reason: WindowsGpuPriorityTargetReason, ): void { if (typeof processId !== 'number' || !Number.isInteger(processId) || processId <= 0) return; const reasons = targets.get(processId); if (reasons) { reasons.add(reason); } else { targets.set(processId, new Set([reason])); } } function getRendererProcessId(webContents?: Electron.WebContents): number | undefined { if (!webContents || webContents.isDestroyed()) return undefined; try { return webContents.getOSProcessId(); } catch { return undefined; } } function setBackgroundThrottlingBypass(webContents: Electron.WebContents, bypass: boolean): void { if (webContents.isDestroyed()) return; try { webContents.setBackgroundThrottling(!bypass); if (bypass) { backgroundThrottlingBypassedWebContents.add(webContents); } else { backgroundThrottlingBypassedWebContents.delete(webContents); } } catch (error) { log.debug('[StreamingPriority] Failed to update renderer background throttling', {bypass, error}); } } function restoreBackgroundThrottling(): void { for (const webContents of backgroundThrottlingBypassedWebContents) { setBackgroundThrottlingBypass(webContents, false); } backgroundThrottlingBypassedWebContents.clear(); } function rememberStreamingPriorityWebContents(webContents?: Electron.WebContents): void { if (!webContents || webContents.isDestroyed() || streamingPriorityWebContents.has(webContents)) return; streamingPriorityWebContents.add(webContents); const processId = getRendererProcessId(webContents); const forgetProcessPriority = (): void => { if (processId === undefined) return; savedProcessPriorities.delete(processId); gpuPriorityElevatedProcessIds.delete(processId); }; const cleanup = (): void => { streamingPriorityWebContents.delete(webContents); streamingPriorityWebContentsCleanup.delete(webContents); backgroundThrottlingBypassedWebContents.delete(webContents); forgetProcessPriority(); }; streamingPriorityWebContentsCleanup.set(webContents, () => { webContents.removeListener('destroyed', cleanup); webContents.removeListener('render-process-gone', forgetProcessPriority); }); webContents.once('destroyed', cleanup); webContents.once('render-process-gone', forgetProcessPriority); } function clearStreamingPriorityWebContents(): void { for (const [webContents, detachListeners] of streamingPriorityWebContentsCleanup) { if (webContents && !webContents.isDestroyed()) { detachListeners(); } } streamingPriorityWebContents.clear(); streamingPriorityWebContentsCleanup.clear(); } function getChromiumProcessTargetReasons(metric: Electron.ProcessMetric): Array { const type = metric.type.toLowerCase(); if (type === 'gpu') return ['chromium-gpu']; const processName = `${metric.name ?? ''} ${metric.serviceName ?? ''}`.toLowerCase(); const reasons: Array = []; if (processName.includes('video capture') || processName.includes('video_capture')) { reasons.push('chromium-video-capture'); } if (processName.includes('video encode') || processName.includes('video_encode')) { reasons.push('chromium-video-encode'); } if (type !== 'utility' || reasons.length > 0) return reasons; if ( processName.includes('capture') || processName.includes('encoder') || processName.includes('media') || processName.includes('video') || processName.includes('webrtc') ) { reasons.push('chromium-media-utility'); } return reasons; } function collectGpuSchedulingPriorityTargets(webContents?: Electron.WebContents): Array { const targets = new Map>(); addGpuPriorityTarget(targets, process.pid, 'browser-main'); const rendererProcessId = getRendererProcessId(webContents); addGpuPriorityTarget(targets, rendererProcessId, 'renderer'); for (const trackedWebContents of streamingPriorityWebContents) { const trackedRendererProcessId = getRendererProcessId(trackedWebContents); if (trackedRendererProcessId !== rendererProcessId) { addGpuPriorityTarget(targets, trackedRendererProcessId, 'tracked-renderer'); } } try { for (const metric of app.getAppMetrics()) { for (const reason of getChromiumProcessTargetReasons(metric)) { addGpuPriorityTarget(targets, metric.pid, reason); } } } catch (error) { log.debug('[StreamingPriority] Failed to collect Chromium process metrics', {error}); } return [...targets.entries()].map(([processId, reasons]) => ({processId, reasons: [...reasons]})); } function limitGpuPriorityTargets(targets: Array): Array { return targets .slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS) .map((target) => ({processId: target.processId, reasons: [...target.reasons]})); } function getGpuPriorityModuleStatus(): GpuPriorityModuleStatus { if (GPU_SCHEDULING_PRIORITY === null) return 'disabled'; if (process.platform !== 'win32') return 'unsupported-platform'; if (gpuPriorityModule === undefined) return 'not-loaded'; return gpuPriorityModule === null ? 'unavailable' : 'loaded'; } function cloneGpuPriorityAcquireDiagnostics( diagnostics: GpuPriorityAcquireDiagnostics | null, ): GpuPriorityAcquireDiagnostics | null { if (!diagnostics) return null; return { ...diagnostics, targets: diagnostics.targets.map((target) => ({processId: target.processId, reasons: [...target.reasons]})), elevatedProcessIds: [...diagnostics.elevatedProcessIds], skippedProcessIds: [...diagnostics.skippedProcessIds], failedProcessIds: diagnostics.failedProcessIds.map((failure) => ({...failure})), }; } function cloneGpuPriorityRestoreDiagnostics( diagnostics: GpuPriorityRestoreDiagnostics | null, ): GpuPriorityRestoreDiagnostics | null { if (!diagnostics) return null; return { ...diagnostics, processIds: [...diagnostics.processIds], restoredProcessIds: [...diagnostics.restoredProcessIds], failedProcessIds: diagnostics.failedProcessIds.map((failure) => ({...failure})), }; } function elevateGpuSchedulingPriority(targets: Array): void { if (GPU_SCHEDULING_PRIORITY === null) { lastGpuPriorityAcquire = { status: 'disabled', priorityClass: null, targets: [], elevatedProcessIds: [], skippedProcessIds: [], failedProcessIds: [], detail: `${GPU_SCHEDULING_PRIORITY_ENV} disabled GPU scheduling priority`, }; return; } if (process.platform !== 'win32') { lastGpuPriorityAcquire = { status: 'unsupported-platform', priorityClass: GPU_SCHEDULING_PRIORITY, targets: limitGpuPriorityTargets(targets), elevatedProcessIds: [], skippedProcessIds: [], failedProcessIds: [], detail: `GPU scheduling priority is Windows-only; current platform is ${process.platform}`, }; return; } const addon = loadWindowsGpuPriorityModule(); if (!addon?.elevateGpuSchedulingPriority) { lastGpuPriorityAcquire = { status: 'native-module-unavailable', priorityClass: GPU_SCHEDULING_PRIORITY, targets: limitGpuPriorityTargets(targets), elevatedProcessIds: [], skippedProcessIds: [], failedProcessIds: targets.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS).map((target) => ({ processId: target.processId, reason: gpuPriorityModuleUnavailableReason(), })), detail: gpuPriorityModuleLoadErrorDetail ?? GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL, }; log.debug('[StreamingPriority] Cannot elevate GPU scheduling priority; native module unavailable', { priorityClass: GPU_SCHEDULING_PRIORITY, targets: lastGpuPriorityAcquire.targets, detail: lastGpuPriorityAcquire.detail, }); return; } if (targets.length === 0) { lastGpuPriorityAcquire = { status: 'no-targets', priorityClass: GPU_SCHEDULING_PRIORITY, targets: [], elevatedProcessIds: [], skippedProcessIds: [], failedProcessIds: [], }; return; } const elevatedProcessIds: Array = []; const skippedProcessIds: Array = []; const failedProcessIds: Array = []; for (const {processId} of targets) { if (gpuPriorityElevatedProcessIds.get(processId) === GPU_SCHEDULING_PRIORITY) { skippedProcessIds.push(processId); continue; } try { if (addon.elevateGpuSchedulingPriority(processId, GPU_SCHEDULING_PRIORITY)) { gpuPriorityElevatedProcessIds.set(processId, GPU_SCHEDULING_PRIORITY); elevatedProcessIds.push(processId); } else { failedProcessIds.push({processId, reason: 'native API returned false'}); log.debug('[StreamingPriority] Failed to elevate GPU scheduling priority', {processId}); } } catch (error) { failedProcessIds.push({processId, reason: formatErrorDetail(error)}); log.debug('[StreamingPriority] Failed to elevate GPU scheduling priority', {processId, error}); } } lastGpuPriorityAcquire = { status: failedProcessIds.length === 0 ? 'succeeded' : elevatedProcessIds.length > 0 ? 'partial' : 'failed', priorityClass: GPU_SCHEDULING_PRIORITY, targets: limitGpuPriorityTargets(targets), elevatedProcessIds: elevatedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS), skippedProcessIds: skippedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS), failedProcessIds: failedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS), }; if (elevatedProcessIds.length > 0 || failedProcessIds.length > 0) { log.info('[StreamingPriority] Elevated GPU scheduling priority', { priorityClass: GPU_SCHEDULING_PRIORITY, targets: lastGpuPriorityAcquire.targets, elevatedProcessIds: lastGpuPriorityAcquire.elevatedProcessIds, skippedProcessIds: lastGpuPriorityAcquire.skippedProcessIds, failedProcessIds: lastGpuPriorityAcquire.failedProcessIds, }); } } function refreshGpuSchedulingPriority(): void { if (refCount <= 0) return; const targets = collectGpuSchedulingPriorityTargets(); elevateProcessPriorityTargets(targets); elevateGpuSchedulingPriority(targets); } function startGpuPriorityRefresh(): void { if (process.platform !== 'win32' || GPU_SCHEDULING_PRIORITY === null || gpuPriorityRefreshTimer !== null) return; gpuPriorityRefreshTimer = setInterval(refreshGpuSchedulingPriority, GPU_PRIORITY_REFRESH_INTERVAL_MS); gpuPriorityRefreshTimer.unref?.(); } function stopGpuPriorityRefresh(): void { if (gpuPriorityRefreshTimer === null) return; clearInterval(gpuPriorityRefreshTimer); gpuPriorityRefreshTimer = null; } function restoreGpuSchedulingPriority(): void { if (gpuPriorityElevatedProcessIds.size === 0) { lastGpuPriorityRestore = { status: 'no-active-priority', processIds: [], restoredProcessIds: [], failedProcessIds: [], }; return; } const processIds = [...gpuPriorityElevatedProcessIds.keys()]; gpuPriorityElevatedProcessIds.clear(); const addon = loadWindowsGpuPriorityModule(); if (!addon?.restoreGpuSchedulingPriority) { lastGpuPriorityRestore = { status: 'native-module-unavailable', processIds, restoredProcessIds: [], failedProcessIds: processIds.map((processId) => ({ processId, reason: gpuPriorityModuleUnavailableReason(), })), detail: gpuPriorityModuleLoadErrorDetail ?? GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL, }; log.debug('[StreamingPriority] Cannot restore GPU scheduling priority; native module unavailable', { processIds, detail: lastGpuPriorityRestore.detail, }); return; } const restoredProcessIds: Array = []; const failedProcessIds: Array = []; for (const processId of processIds) { try { if (addon.restoreGpuSchedulingPriority(processId)) { restoredProcessIds.push(processId); } else { failedProcessIds.push({processId, reason: 'native API returned false'}); } } catch (error) { failedProcessIds.push({processId, reason: formatErrorDetail(error)}); log.debug('[StreamingPriority] Failed to restore GPU scheduling priority', {processId, error}); } } lastGpuPriorityRestore = { status: failedProcessIds.length === 0 ? 'succeeded' : restoredProcessIds.length > 0 ? 'partial' : 'failed', processIds, restoredProcessIds, failedProcessIds, }; if (failedProcessIds.length > 0) { log.debug('[StreamingPriority] Failed to restore GPU scheduling priority for some processes', { processIds: failedProcessIds, }); } else { log.info('[StreamingPriority] Restored GPU scheduling priority', {processIds: restoredProcessIds}); } } export function getStreamingPriorityDiagnostics(): StreamingPriorityDiagnostics { return { refCount, powerSaveBlocker: { active: powerBlockerId !== null, id: powerBlockerId, }, processPriority: { supported: CAN_ELEVATE_PROCESS_PRIORITY, streamingPriority: STREAMING_PRIORITY, savedPriority: savedProcessPriorities.get(process.pid) ?? null, elevated: savedProcessPriorities.size > 0, elevatedProcesses: [...savedProcessPriorities.entries()].map(([processId, savedPriority]) => ({ processId, savedPriority, })), }, gpuScheduling: { supported: process.platform === 'win32' && GPU_SCHEDULING_PRIORITY !== null, priorityClass: GPU_SCHEDULING_PRIORITY, env: GPU_SCHEDULING_PRIORITY_ENV, nativeModuleStatus: getGpuPriorityModuleStatus(), nativeModuleLoadErrorDetail: gpuPriorityModuleLoadErrorDetail, refreshActive: gpuPriorityRefreshTimer !== null, refreshIntervalMs: GPU_PRIORITY_REFRESH_INTERVAL_MS, trackedWebContents: streamingPriorityWebContents.size, elevatedProcesses: [...gpuPriorityElevatedProcessIds.entries()].map(([processId, priorityClass]) => ({ processId, priorityClass, })), lastAcquire: cloneGpuPriorityAcquireDiagnostics(lastGpuPriorityAcquire), lastRestore: cloneGpuPriorityRestoreDiagnostics(lastGpuPriorityRestore), }, }; } export function acquireStreamingPriority(webContents?: Electron.WebContents): void { rememberStreamingPriorityWebContents(webContents); if (webContents && !webContents.isDestroyed()) { setBackgroundThrottlingBypass(webContents, true); } refCount++; if (refCount === 1) { startPowerSaveBlocker(); startGpuPriorityRefresh(); log.info('[StreamingPriority] Acquired', {refCount}); } const targets = collectGpuSchedulingPriorityTargets(webContents); elevateProcessPriorityTargets(targets); elevateGpuSchedulingPriority(targets); } export function releaseStreamingPriority(): void { if (refCount <= 0) return; refCount--; if (refCount === 0) { stopGpuPriorityRefresh(); restoreBackgroundThrottling(); clearStreamingPriorityWebContents(); stopPowerSaveBlocker(); restoreProcessPriority(); restoreGpuSchedulingPriority(); stopWindowsScreenCaptureGuard('streaming-priority-release'); reportWindowsCaptureItemReuse('streaming-priority-release'); log.info('[StreamingPriority] Released'); } } export function resetStreamingPriority(): void { if (refCount === 0) return; refCount = 0; stopGpuPriorityRefresh(); restoreBackgroundThrottling(); clearStreamingPriorityWebContents(); stopPowerSaveBlocker(); restoreProcessPriority(); restoreGpuSchedulingPriority(); stopWindowsScreenCaptureGuard('streaming-priority-reset'); reportWindowsCaptureItemReuse('streaming-priority-reset'); log.info('[StreamingPriority] Reset'); }