Skip to content

Commit 503519e

Browse files
sepion02EtienneLescot
authored andcommitted
feat(export): route GIF through the native exporter, delete the pixi path
Finishes the wiring #200 left open (the IPC route did not exist, so nothing could reach the native path even with the flag on) and removes the renderer-side GIF renderer it replaces. Wiring: exportGif takes the same clips + scene as exportMulti, gains the missing 'exportGif' dispatcher case, a client method, and resolveSceneAssetPaths (the GIF path never had it). ExportDialog now has one native branch for both formats — GIF differs only in output size preset, frame rate and loop count. Deleted, all consumer-less once GIF stopped using them: gifExporter, its documentExporter adapter, frameRenderer, the WGSL shaders it owned (composite/shadowCascade/evaluate), threeDPass, cropSchedule, frameExtract, timestampedVideoFrameQueue, and the NATIVE_GIF_EXPORT_ENABLED flag. gif.js and @types/gif.js drop out of package.json. This ends the second graphical SSOT: preview and export were both native already, but GIF still rasterised through a parallel WGSL implementation that had to be hand-synced with shaders.hlsl — and silently degraded when its 3D pass failed to init ('rotation fields will be ignored'), producing different pixels from the preview with no error. pixi.js stays: cursor rendering (pixiCursorRenderer, nativeCursor, CursorPreviewLayer) and zoomTransform still use it. That is the remaining half. The browser test suite went with it — its only test was the pixi GIF exporter, and the config existed to give Pixi software WebGL in headless CI. Test-file typecheck errors drop 80 -> 74; baseline lowered to match.
1 parent 4a24999 commit 503519e

28 files changed

Lines changed: 151 additions & 4163 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- name: Typecheck tests against a baseline
4848
shell: bash
4949
env:
50-
BASELINE: 80
50+
BASELINE: 74
5151
run: |
5252
set -uo pipefail
5353
COUNT=$(npx tsc -p tsconfig.test.json --noEmit 2>&1 | grep -c 'error TS' || true)
@@ -88,8 +88,6 @@ jobs:
8888
- uses: actions/checkout@v4
8989
- uses: ./.github/actions/setup
9090
- run: npm run test
91-
- run: npm run test:browser:install
92-
- run: npm run test:browser
9391

9492
build:
9593
name: Build

electron/ipc/nativeBridge.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,28 @@ export function registerNativeBridgeHandlers(context: NativeBridgeContext) {
413413
}
414414
return createSuccessResponse(requestId, stats);
415415
}
416+
case "exportGif": {
417+
const sender = event.sender;
418+
const stats = await compositorViewService.exportGif(
419+
request.payload.clips,
420+
request.payload.outPath,
421+
request.payload.sceneJson,
422+
request.payload.params,
423+
(frames) => {
424+
if (!sender.isDestroyed()) {
425+
sender.send("export:native-progress", frames);
426+
}
427+
},
428+
);
429+
if (!stats) {
430+
return createErrorResponse(
431+
requestId,
432+
"UNAVAILABLE",
433+
"Native compositor addon not present.",
434+
);
435+
}
436+
return createSuccessResponse(requestId, stats);
437+
}
416438
default:
417439
return createErrorResponse(
418440
requestId,

electron/native-bridge/services/compositorViewService.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -494,17 +494,17 @@ export class CompositorViewService {
494494
);
495495
}
496496

497-
/** Native single-clip GIF export (slice 1, behind `NATIVE_GIF_EXPORT_ENABLED`).
498-
* Mirrors `exportMulti`'s shape, but the slice-1 surface is deliberately small:
499-
* one screen + one webcam file, optional cursor sidecar (`<screen>.cursor.json`),
500-
* no multiclip, no app `SceneDescription` (the Player drives the compositing,
501-
* same as the live preview). Returns null when the addon is absent — the renderer
502-
* treats that as "fall back to the legacy `gif.js` path" without raising. */
497+
/** Native GIF export. Same inputs as `exportMulti` — one clip list, one scene —
498+
* because it is the same render: both drive `walk_composited_timeline` in the
499+
* compositor crate and differ only in the encoder. The scene carries background,
500+
* layout, webcam and cursor, so there is no GIF-specific input.
501+
*
502+
* Returns null when the addon is absent, which the renderer surfaces as a failed
503+
* export — there is no longer a renderer-side GIF path to fall back to. */
503504
async exportGif(
504-
screenPath: string,
505-
webcamPath: string,
506-
cursorPath?: string | null,
505+
clips: ClipInput[],
507506
outPath?: string,
507+
sceneJson?: string,
508508
params?: GifParamsInput,
509509
onProgress?: (frames: number) => void,
510510
): Promise<GifExportStats | null> {
@@ -513,6 +513,12 @@ export class CompositorViewService {
513513
return null;
514514
}
515515
const target = outPath ?? path.join(app.getPath("temp"), "openscreen-native-export.gif");
516-
return addon.exportGif(screenPath, webcamPath, cursorPath ?? null, target, params, onProgress);
516+
return addon.exportGif(
517+
clips,
518+
target,
519+
sceneJson ? resolveSceneAssetPaths(sceneJson) : undefined,
520+
params,
521+
onProgress,
522+
);
517523
}
518524
}

electron/native/compositor-view/addon.d.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,20 +148,17 @@ export interface CompositorViewAddon {
148148
params?: ExportParamsInput,
149149
onProgress?: (frames: number) => void,
150150
): Promise<ExportStats>;
151-
/** Native single-clip GIF export (slice 1, behind `NATIVE_GIF_EXPORT_ENABLED`).
152-
* Mirrors `exportMulti`'s shape, but the slice-1 PR keeps the surface small:
153-
* one screen + one webcam file (no multiclip), no app `SceneDescription` (the
154-
* Player drives the compositing, same as the live preview), and no
155-
* encoder-config codec pick — GIF is one codec. `cursorPath` follows the
156-
* sidecar convention (`<screen>.cursor.json`); `null`/missing → render
157-
* without cursor. `params` defaults to 854×480, 12 fps, infinite loop, no
158-
* dithering (`GifExportParams::default`). `onProgress(frames)` is throttled
159-
* to ~10/s like the MP4 path. */
151+
/** Native GIF export. Identical inputs to `exportMulti` — same clips, same
152+
* scene — because it is the same render: both drive `walk_composited_timeline`
153+
* in the compositor crate and differ only in the encoder. Cursor, background,
154+
* layout and webcam all come from the scene, so there is no GIF-specific
155+
* input. No codec pick: GIF is one codec. `params` defaults to 854×480,
156+
* 12 fps, infinite loop, no dithering (`GifExportParams::default`).
157+
* `onProgress(frames)` is throttled to ~10/s like the MP4 path. */
160158
exportGif(
161-
screenPath: string,
162-
webcamPath: string,
163-
cursorPath: string | null,
159+
clips: ClipInput[],
164160
outPath: string,
161+
sceneJson?: string,
165162
params?: GifParamsInput,
166163
onProgress?: (frames: number) => void,
167164
): Promise<GifExportStats>;

package-lock.json

Lines changed: 20 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@
5050
"diagnostic:run": "node scripts/diagnostic-tool/diagnostic.mjs",
5151
"diagnostic:smoke:win": "node scripts/diagnostic-tool/diagnostic.mjs --duration 3",
5252
"build-vite": "tsc && vite build",
53-
"test:browser": "vitest --config vitest.browser.config.ts --run",
54-
"test:browser:install": "playwright install --with-deps chromium-headless-shell",
5553
"test:e2e": "playwright test",
5654
"test:e2e:windows-native-checklist": "playwright test tests/e2e/windows-native-checklist.spec.ts",
5755
"prepare": "husky",
@@ -78,14 +76,12 @@
7876
"@tiptap/extension-text-style": "^3.27.1",
7977
"@tiptap/react": "^3.27.1",
8078
"@tiptap/starter-kit": "^3.27.1",
81-
"@types/gif.js": "^0.2.5",
8279
"@uiw/color-convert": "^2.10.1",
8380
"@uiw/react-color-block": "^2.10.1",
8481
"@uiw/react-color-colorful": "^2.9.2",
8582
"class-variance-authority": "^0.7.1",
8683
"clsx": "^2.1.1",
8784
"deepagents": "^1.10.5",
88-
"gif.js": "^0.2.0",
8985
"i18next": "^23.16.0",
9086
"langchain": "^1.2.39",
9187
"lucide-react": "^0.545.0",
@@ -119,8 +115,6 @@
119115
"@types/react": "^18.3.28",
120116
"@types/react-dom": "^18.3.7",
121117
"@vitejs/plugin-react": "^5.2.0",
122-
"@vitest/browser": "^4.1.4",
123-
"@vitest/browser-playwright": "^4.1.4",
124118
"autoprefixer": "^10.5.0",
125119
"electron": "^41.2.1",
126120
"electron-builder": "^26.8.1",

0 commit comments

Comments
 (0)