Repository navigation
Expand file tree
/
Copy pathcompositorViewClient.ts
More file actions
251 lines (231 loc) · 8.22 KB
/
Copy pathcompositorViewClient.ts
File metadata and controls
251 lines (231 loc) · 8.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/**
* Renderer-side client for the native D3D11 compositor view. Each function
* sends a `compositor` domain `NativeBridgeRequest` over the existing
* `native-bridge:invoke` channel. The compositor renders OFFSCREEN now —
* there's no native OS window to embed, so no HWND crosses the IPC boundary
* in either direction. The renderer instead pulls rendered frames via
* `readCompositorFrame` and paints them into a `<canvas>`.
*/
import type { ShaderEffectCheck } from "@/lib/ai-edition/shaderEffects";
import { requireNativeBridgeData } from "./client";
import type {
CompositorBackend,
CompositorBackendResult,
CompositorClipInput,
CompositorExportGifParams,
CompositorExportGifResult,
CompositorExportParams,
CompositorExportResult,
CompositorFramePacket,
CompositorParamValue,
CompositorSharedFrameMeta,
CompositorSharedFrameReceipt,
CompositorViewRect,
CompositorViewResult,
SegmentationSupport,
SegmentationSupportResult,
} from "./contracts";
/** Which backend the native compositor will use here.
*
* Resolves `"none"` rather than rejecting when there is no bridge or no addon — callers
* use this to decide whether to show a "running without a GPU" notice, and a missing
* native path is not a degraded GPU. Only `"cpu"` warrants a warning. */
export async function probeCompositorBackend(): Promise<CompositorBackend> {
try {
const result = await requireNativeBridgeData<CompositorBackendResult>({
domain: "compositor",
action: "probeBackend",
});
return result.backend;
} catch {
return "none";
}
}
/** Whether this machine can segment the camera. `"none"` outside Electron or without the addon.
*
* Fails closed: any error means the control should not be offered. Showing a setting that
* cannot do anything is the failure this exists to prevent, so a broken probe must not be
* read as capability. */
export async function probeSegmentationSupport(): Promise<SegmentationSupport> {
try {
const result = await requireNativeBridgeData<SegmentationSupportResult>({
domain: "compositor",
action: "probeSegmentation",
});
return result.support;
} catch {
return "none";
}
}
/** An effect's code checked by this machine's compositor, as the agent tools check it: the
* compiler's error, or the cost tier. `null` when nothing could check it (no bridge, no addon). */
export async function checkShaderEffectCode(
code: string,
params: ReadonlyArray<{ name: string; type: string }>,
): Promise<ShaderEffectCheck | null> {
try {
const { check } = await requireNativeBridgeData<{ check: ShaderEffectCheck | null }>({
domain: "compositor",
action: "checkShaderEffect",
payload: { code, params: params.map(({ name, type }) => ({ name, type })) },
});
return check;
} catch {
return null;
}
}
/** The segmentation model's input size (`segmentation::MODEL_WIDTH` x `MODEL_HEIGHT`). */
export const SEGMENTATION_WIDTH = 256;
export const SEGMENTATION_HEIGHT = 144;
/** Subject mask for one camera frame: `rgba` is SEGMENTATION_WIDTH x SEGMENTATION_HEIGHT
* RGBA8, the mask one byte per pixel, 255 = subject. `null` when the addon cannot do it. */
export async function segmentCameraFrame(rgba: Uint8Array): Promise<Uint8Array | null> {
const { mask } = await requireNativeBridgeData<{ mask: Uint8Array | null }>({
domain: "compositor",
action: "segmentFrame",
payload: { rgba },
});
return mask;
}
export function createCompositorView(
rect: CompositorViewRect,
sources?: { screenPath?: string; webcamPath?: string; cursorPath?: string },
): Promise<CompositorViewResult> {
return requireNativeBridgeData<CompositorViewResult>({
domain: "compositor",
action: "createView",
payload: { rect, ...sources },
});
}
export function setCompositorRect(id: number, rect: CompositorViewRect): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "setRect",
payload: { id, rect },
});
}
/** Polls the most recently rendered frame for `id`, but only if its generation is
* newer than `sinceGen` (the generation the caller last painted). Returns a
* {@link CompositorFramePacket} on a new frame, or `null` when the addon is absent,
* no frame is ready yet, OR the caller already holds the current generation — the
* idle path, where `null` returns without any buffer crossing IPC. Pass `sinceGen = 0`
* to force delivery of the current frame.
*
* A view on shared textures answers with a {@link CompositorSharedFrameReceipt} instead:
* its frame already went to {@link subscribeCompositorSharedFrames}, ahead of this reply. */
export function readCompositorFrame(
id: number,
sinceGen: number,
): Promise<CompositorFramePacket | CompositorSharedFrameReceipt | null> {
return requireNativeBridgeData<CompositorFramePacket | CompositorSharedFrameReceipt | null>({
domain: "compositor",
action: "readFrame",
payload: { id, sinceGen },
});
}
/** Preview frames handed over as shared GPU textures. The listener draws `frame` and closes
* it. Returns the unsubscribe; without the Electron bridge (pure web, jsdom) nothing ever
* arrives. */
export function subscribeCompositorSharedFrames(
listener: (frame: VideoFrame, meta: CompositorSharedFrameMeta) => void,
): () => void {
return window.electronAPI?.onCompositorFrame?.(listener) ?? (() => undefined);
}
/** Back to read-back frames for `id`: a shared frame reached the canvas as nothing. */
export function stopSharedCompositorFrames(id: number): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "stopSharedFrames",
payload: { id },
});
}
export function setCompositorParam(
id: number,
key: string,
value: CompositorParamValue,
): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "setParam",
payload: { id, key, value },
});
}
export function setCompositorPlaying(id: number, playing: boolean): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "setPlaying",
payload: { id, playing },
});
}
export function setCompositorTime(id: number, seconds: number): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "presentTime",
payload: { id, seconds },
});
}
export function setCompositorScene(id: number, sceneJson: string): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "setScene",
payload: { id, sceneJson },
});
}
export function setActiveClip(
id: number,
screenPath: string,
webcamPath: string,
webcamOffsetSec: number,
clipIndex: number,
sourceTimeSec: number,
): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "setActiveClip",
payload: { id, screenPath, webcamPath, webcamOffsetSec, clipIndex, sourceTimeSec },
});
}
export function destroyCompositorView(id: number): Promise<{ ok: true }> {
return requireNativeBridgeData<{ ok: true }>({
domain: "compositor",
action: "destroyView",
payload: { id },
});
}
export function exportMultiNative(
clips: CompositorClipInput[],
outPath?: string,
sceneJson?: string,
params?: CompositorExportParams,
exportId?: string,
): Promise<CompositorExportResult> {
return requireNativeBridgeData<CompositorExportResult>({
domain: "compositor",
action: "exportMulti",
payload: { clips, outPath, sceneJson, params, exportId },
});
}
/** Same clips and scene as `exportMultiNative` — the render is identical, only
* the container differs. */
export function exportGifNative(
clips: CompositorClipInput[],
outPath?: string,
sceneJson?: string,
params?: CompositorExportGifParams,
exportId?: string,
): Promise<CompositorExportGifResult> {
return requireNativeBridgeData<CompositorExportGifResult>({
domain: "compositor",
action: "exportGif",
payload: { clips, outPath, sceneJson, params, exportId },
});
}
/** Cancels the MP4 or GIF export started with `exportId`. `accepted: false` means it finishes on
* its own: it was already being published, or the addon cannot cancel it. */
export function cancelExportNative(exportId: string): Promise<{ accepted: boolean }> {
return requireNativeBridgeData<{ accepted: boolean }>({
domain: "compositor",
action: "cancelExport",
payload: { exportId },
});
}