diff --git a/src/lib/exporter/modernFrameRenderer.test.ts b/src/lib/exporter/modernFrameRenderer.test.ts index dd820c81a..27f0275a5 100644 --- a/src/lib/exporter/modernFrameRenderer.test.ts +++ b/src/lib/exporter/modernFrameRenderer.test.ts @@ -203,6 +203,59 @@ function createRenderer() { } describe("ModernFrameRenderer Pixi lifecycle", () => { + it("defaults to webgl before webgpu when no explicit backend is requested", async () => { + pixiApplicationInstancesMock.length = 0; + pixiInitializationErrorsMock.length = 0; + vi.stubGlobal("navigator", { gpu: {} }); + + try { + const renderer = createRenderer() as unknown as { + config: { preferredRenderBackend?: "webgl" | "webgpu" }; + createPixiApplication: ( + canvas: HTMLCanvasElement, + ) => Promise<{ backend: "webgl" | "webgpu" }>; + }; + delete renderer.config.preferredRenderBackend; + + await expect( + renderer.createPixiApplication({} as HTMLCanvasElement), + ).resolves.toMatchObject({ + backend: "webgl", + }); + + expect(pixiApplicationInstancesMock).toHaveLength(1); + } finally { + vi.unstubAllGlobals(); + } + }); + + it("falls back to webgpu when webgl initialization fails and no backend was requested", async () => { + pixiApplicationInstancesMock.length = 0; + pixiInitializationErrorsMock.length = 0; + pixiInitializationErrorsMock.push(new Error("WebGL initialization failed"), undefined); + vi.stubGlobal("navigator", { gpu: {} }); + + try { + const renderer = createRenderer() as unknown as { + config: { preferredRenderBackend?: "webgl" | "webgpu" }; + createPixiApplication: ( + canvas: HTMLCanvasElement, + ) => Promise<{ backend: "webgl" | "webgpu" }>; + }; + delete renderer.config.preferredRenderBackend; + + await expect( + renderer.createPixiApplication({} as HTMLCanvasElement), + ).resolves.toMatchObject({ + backend: "webgpu", + }); + + expect(pixiApplicationInstancesMock).toHaveLength(2); + } finally { + vi.unstubAllGlobals(); + } + }); + it("continues to the next backend when failed-init cleanup would throw", async () => { pixiApplicationInstancesMock.length = 0; pixiInitializationErrorsMock.length = 0; diff --git a/src/lib/exporter/modernFrameRenderer.ts b/src/lib/exporter/modernFrameRenderer.ts index d5fb8f6d5..7e07f1a76 100644 --- a/src/lib/exporter/modernFrameRenderer.ts +++ b/src/lib/exporter/modernFrameRenderer.ts @@ -100,6 +100,7 @@ import { buildTemporalSamplePlanUs, getTemporalMotionBlurConfig } from "./tempor const TEMPORAL_ZOOM_MOTION_BLUR_ENABLED = false; +import { getDefaultLightningRenderBackend } from "./backendPolicy"; import type { ExportRenderBackend } from "./types"; interface FrameRenderConfig { @@ -639,15 +640,10 @@ export class FrameRenderer { powerPreference: "high-performance" as const, }; - const preferredRenderBackend = this.config.preferredRenderBackend; + const preferredRenderBackend = + this.config.preferredRenderBackend ?? getDefaultLightningRenderBackend(); const backendOrder: ExportRenderBackend[] = - preferredRenderBackend === "webgl" - ? ["webgl", "webgpu"] - : preferredRenderBackend === "webgpu" - ? ["webgpu", "webgl"] - : typeof navigator !== "undefined" && "gpu" in navigator - ? ["webgpu", "webgl"] - : ["webgl"]; + preferredRenderBackend === "webgpu" ? ["webgpu", "webgl"] : ["webgl", "webgpu"]; const failures: PixiRendererAttempt[] = []; for (const backend of backendOrder) { diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index 3bb58eb8f..219453544 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -53,6 +53,7 @@ import { } from "@/lib/wallpapers"; import { AudioProcessor, isAacAudioEncodingSupported } from "./audioEncoder"; import { + getDefaultLightningRenderBackend, normalizeLightningRuntimePlatform, shouldPreferNativeAutoBackend, shouldPreferNativeStaticLayoutBeforeBreeze, @@ -588,7 +589,8 @@ export class ModernVideoExporter { this.renderer = new ModernFrameRenderer({ width: this.config.width, height: this.config.height, - preferredRenderBackend: undefined, + preferredRenderBackend: + this.config.preferredRenderBackend ?? getDefaultLightningRenderBackend(), wallpaper: this.config.wallpaper, zoomRegions: this.config.zoomRegions, showShadow: this.config.showShadow,