@fishjam-cloud/react-native-custom-video-source 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/module/index.js +22 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/internal/stopStreamTracks.js +9 -0
- package/dist/module/internal/stopStreamTracks.js.map +1 -0
- package/dist/module/internal/toError.js +5 -0
- package/dist/module/internal/toError.js.map +1 -0
- package/dist/module/internal/useManagedForwardTrack.js +63 -0
- package/dist/module/internal/useManagedForwardTrack.js.map +1 -0
- package/dist/module/internal/useManagedPooledTrack.js +120 -0
- package/dist/module/internal/useManagedPooledTrack.js.map +1 -0
- package/dist/module/webgpu/cameraPassthroughPipeline.js +237 -0
- package/dist/module/webgpu/cameraPassthroughPipeline.js.map +1 -0
- package/dist/module/webgpu/cameraShaderBindings.js +126 -0
- package/dist/module/webgpu/cameraShaderBindings.js.map +1 -0
- package/dist/module/webgpu/cameraTextureResolver.js +52 -0
- package/dist/module/webgpu/cameraTextureResolver.js.map +1 -0
- package/dist/module/webgpu/cropUtilities.js +121 -0
- package/dist/module/webgpu/cropUtilities.js.map +1 -0
- package/dist/module/webgpu/frameRenderContext.js +2 -0
- package/dist/module/webgpu/frameRenderContext.js.map +1 -0
- package/dist/module/webgpu/requiredFeatures.js +46 -0
- package/dist/module/webgpu/requiredFeatures.js.map +1 -0
- package/dist/module/webgpu/useCameraWebGpuDevice.js +114 -0
- package/dist/module/webgpu/useCameraWebGpuDevice.js.map +1 -0
- package/dist/module/webgpu/webGpuRuntime.js +25 -0
- package/dist/module/webgpu/webGpuRuntime.js.map +1 -0
- package/dist/module/webgpu.js +25 -0
- package/dist/module/webgpu.js.map +1 -0
- package/dist/typescript/index.d.ts +21 -0
- package/dist/typescript/index.d.ts.map +1 -0
- package/dist/typescript/internal/stopStreamTracks.d.ts +4 -0
- package/dist/typescript/internal/stopStreamTracks.d.ts.map +1 -0
- package/dist/typescript/internal/toError.d.ts +3 -0
- package/dist/typescript/internal/toError.d.ts.map +1 -0
- package/dist/typescript/internal/useManagedForwardTrack.d.ts +17 -0
- package/dist/typescript/internal/useManagedForwardTrack.d.ts.map +1 -0
- package/dist/typescript/internal/useManagedPooledTrack.d.ts +26 -0
- package/dist/typescript/internal/useManagedPooledTrack.d.ts.map +1 -0
- package/dist/typescript/webgpu/cameraPassthroughPipeline.d.ts +46 -0
- package/dist/typescript/webgpu/cameraPassthroughPipeline.d.ts.map +1 -0
- package/dist/typescript/webgpu/cameraShaderBindings.d.ts +69 -0
- package/dist/typescript/webgpu/cameraShaderBindings.d.ts.map +1 -0
- package/dist/typescript/webgpu/cameraTextureResolver.d.ts +39 -0
- package/dist/typescript/webgpu/cameraTextureResolver.d.ts.map +1 -0
- package/dist/typescript/webgpu/cropUtilities.d.ts +67 -0
- package/dist/typescript/webgpu/cropUtilities.d.ts.map +1 -0
- package/dist/typescript/webgpu/frameRenderContext.d.ts +61 -0
- package/dist/typescript/webgpu/frameRenderContext.d.ts.map +1 -0
- package/dist/typescript/webgpu/requiredFeatures.d.ts +25 -0
- package/dist/typescript/webgpu/requiredFeatures.d.ts.map +1 -0
- package/dist/typescript/webgpu/useCameraWebGpuDevice.d.ts +27 -0
- package/dist/typescript/webgpu/useCameraWebGpuDevice.d.ts.map +1 -0
- package/dist/typescript/webgpu/webGpuRuntime.d.ts +14 -0
- package/dist/typescript/webgpu/webGpuRuntime.d.ts.map +1 -0
- package/dist/typescript/webgpu.d.ts +24 -0
- package/dist/typescript/webgpu.d.ts.map +1 -0
- package/package.json +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# `@fishjam-cloud/react-native-custom-video-source`
|
|
2
|
+
|
|
3
|
+
Publish your own video frames to Fishjam from **any** frame source. This is the generic layer
|
|
4
|
+
under [`@fishjam-cloud/react-native-vision-camera-source`](../react-native-vision-camera-source) —
|
|
5
|
+
use that package if your source is [VisionCamera](https://react-native-vision-camera.com); use this
|
|
6
|
+
one directly for any other source (a native ML pipeline, a compositor, your own renderer).
|
|
7
|
+
|
|
8
|
+
There are two modes, picked by **how you produce frames**:
|
|
9
|
+
|
|
10
|
+
- **Forwarding** — you already have finished native buffers. `useManagedForwardTrack()` creates and
|
|
11
|
+
owns the track; hand each buffer pointer to `forwardFrame` from `@fishjam-cloud/react-native-webrtc`.
|
|
12
|
+
- **Render-target (pooled)** — you render the frames yourself. `useManagedPooledTrack()` allocates a
|
|
13
|
+
surface pool; draw into it and hand each frame back with `pushFrame`. The
|
|
14
|
+
[`/webgpu`](#webgpu-camera-toolkit) entry point provides a WebGPU camera-rendering toolkit for this
|
|
15
|
+
mode.
|
|
16
|
+
|
|
17
|
+
Each hook owns the track's async lifecycle — creation, the published `stream`, and teardown — so you
|
|
18
|
+
only feed frames. Must be used under `FishjamProvider` from `@fishjam-cloud/react-native-client`.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
|
|
22
|
+
- `@fishjam-cloud/react-native-webrtc` and `@fishjam-cloud/react-native-client` (with your app wrapped
|
|
23
|
+
in `FishjamProvider`)
|
|
24
|
+
- New Architecture (custom video tracks require it)
|
|
25
|
+
- For the `/webgpu` entry only: `react-native-webgpu` ≥ 0.5.15, plus `unplugin-typegpu` in your app's
|
|
26
|
+
Babel config (the shaders are authored in [TypeGPU](https://docs.swmansion.com/TypeGPU/)), and
|
|
27
|
+
**iOS 17+** for the Metal external-texture camera-import path.
|
|
28
|
+
|
|
29
|
+
## Forward finished buffers
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useManagedForwardTrack } from '@fishjam-cloud/react-native-custom-video-source';
|
|
33
|
+
import { forwardFrame } from '@fishjam-cloud/react-native-webrtc';
|
|
34
|
+
import { useCustomSource } from '@fishjam-cloud/react-native-client';
|
|
35
|
+
|
|
36
|
+
const { track, stream } = useManagedForwardTrack(/* enabled */ true);
|
|
37
|
+
// publish `stream` (e.g. via useCustomSource), then per frame:
|
|
38
|
+
forwardFrame(track, { nativeBuffer /* bigint CVPixelBufferRef / AHardwareBuffer* */ });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Render your own frames
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useManagedPooledTrack } from '@fishjam-cloud/react-native-custom-video-source';
|
|
45
|
+
import { pushFrame } from '@fishjam-cloud/react-native-webrtc';
|
|
46
|
+
|
|
47
|
+
const { track, bufferDescriptors } = useManagedPooledTrack(true, 720, 1280, /* poolSize */ 3);
|
|
48
|
+
// import bufferDescriptors[i].surfaceHandle into your GPU, render into slot i, then:
|
|
49
|
+
pushFrame(track, { bufferIndex: i, timestampNs });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## WebGPU camera toolkit
|
|
53
|
+
|
|
54
|
+
`@fishjam-cloud/react-native-custom-video-source/webgpu` samples the live camera and helps you render
|
|
55
|
+
into the pooled surfaces:
|
|
56
|
+
|
|
57
|
+
- `createCameraShaderBindings` / `sampleCamera` — sample the camera from your own shaders, YUV decode
|
|
58
|
+
handled per platform
|
|
59
|
+
- `createCameraPassthroughPipeline` / `encodeCameraPassthrough` — a ready-made camera→output pass
|
|
60
|
+
- `useCameraWebGpuDevice` — the shared, camera-import-capable `GPUDevice`
|
|
61
|
+
- `computeAspectFillCrop` / `computeSquareCrop` / `packFrameCropParams` — crop + orientation helpers
|
|
62
|
+
|
|
63
|
+
> Verify camera + WebGPU behavior on **physical devices** — the iOS Simulator cannot import the
|
|
64
|
+
> camera's YUV textures.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
Part of the [web-client-sdk](https://github.com/fishjam-cloud/web-client-sdk) monorepo. `yarn build`
|
|
69
|
+
compiles `src/` to `dist/` with `react-native-builder-bob` (Babel + `unplugin-typegpu` for the TGSL
|
|
70
|
+
shaders, `tsc` for type definitions).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish your own video frames to Fishjam.
|
|
3
|
+
*
|
|
4
|
+
* Source-lifecycle hooks that create a custom video track, publish it, and clean it up — you
|
|
5
|
+
* supply the frames from any source. Two modes, picked by how you produce frames:
|
|
6
|
+
*
|
|
7
|
+
* - {@link useManagedForwardTrack} — you already have finished native buffers (a camera, a native
|
|
8
|
+
* ML pipeline, a compositor); forward each buffer pointer with `forwardFrame` from
|
|
9
|
+
* `@fishjam-cloud/react-native-webrtc`.
|
|
10
|
+
* - {@link useManagedPooledTrack} — you render the frames yourself; allocate a surface pool, draw
|
|
11
|
+
* into it, and hand each frame back with `pushFrame`. The `@fishjam-cloud/react-native-custom-video-source/webgpu`
|
|
12
|
+
* entry point provides a WebGPU camera-rendering toolkit for this mode.
|
|
13
|
+
*
|
|
14
|
+
* For a ready-made VisionCamera integration on top of this, use
|
|
15
|
+
* `@fishjam-cloud/react-native-vision-camera-source`.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export { useManagedForwardTrack } from './internal/useManagedForwardTrack';
|
|
21
|
+
export { useManagedPooledTrack } from './internal/useManagedPooledTrack';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useManagedForwardTrack","useManagedPooledTrack"],"sources":["index.ts"],"sourcesContent":["/**\n * Publish your own video frames to Fishjam.\n *\n * Source-lifecycle hooks that create a custom video track, publish it, and clean it up — you\n * supply the frames from any source. Two modes, picked by how you produce frames:\n *\n * - {@link useManagedForwardTrack} — you already have finished native buffers (a camera, a native\n * ML pipeline, a compositor); forward each buffer pointer with `forwardFrame` from\n * `@fishjam-cloud/react-native-webrtc`.\n * - {@link useManagedPooledTrack} — you render the frames yourself; allocate a surface pool, draw\n * into it, and hand each frame back with `pushFrame`. The `@fishjam-cloud/react-native-custom-video-source/webgpu`\n * entry point provides a WebGPU camera-rendering toolkit for this mode.\n *\n * For a ready-made VisionCamera integration on top of this, use\n * `@fishjam-cloud/react-native-vision-camera-source`.\n *\n * @packageDocumentation\n */\n\nexport { type ManagedForwardTrack, useManagedForwardTrack } from './internal/useManagedForwardTrack';\nexport {\n type ManagedPooledTrack,\n useManagedPooledTrack,\n type WorkletBufferDescriptor,\n} from './internal/useManagedPooledTrack';\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAAmCA,sBAAsB,QAAQ,mCAAmC;AACpG,SAEEC,qBAAqB,QAEhB,kCAAkC","ignoreList":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Stops every track of `stream`, downgrading failures to a warning tagged with `ownerLabel`. */
|
|
2
|
+
export function stopStreamTracks(stream, ownerLabel) {
|
|
3
|
+
try {
|
|
4
|
+
stream.getTracks().forEach(mediaTrack => mediaTrack.stop());
|
|
5
|
+
} catch (cause) {
|
|
6
|
+
console.warn(`${ownerLabel}: stopping tracks failed`, cause);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=stopStreamTracks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["stopStreamTracks","stream","ownerLabel","getTracks","forEach","mediaTrack","stop","cause","console","warn"],"sources":["stopStreamTracks.ts"],"sourcesContent":["import type { MediaStream } from '@fishjam-cloud/react-native-webrtc';\n\n/** Stops every track of `stream`, downgrading failures to a warning tagged with `ownerLabel`. */\nexport function stopStreamTracks(stream: MediaStream, ownerLabel: string): void {\n try {\n stream.getTracks().forEach((mediaTrack) => mediaTrack.stop());\n } catch (cause) {\n console.warn(`${ownerLabel}: stopping tracks failed`, cause);\n }\n}\n"],"mappings":"AAEA;AACA,OAAO,SAASA,gBAAgBA,CAACC,MAAmB,EAAEC,UAAkB,EAAQ;EAC9E,IAAI;IACFD,MAAM,CAACE,SAAS,CAAC,CAAC,CAACC,OAAO,CAAEC,UAAU,IAAKA,UAAU,CAACC,IAAI,CAAC,CAAC,CAAC;EAC/D,CAAC,CAAC,OAAOC,KAAK,EAAE;IACdC,OAAO,CAACC,IAAI,CAAC,GAAGP,UAAU,0BAA0B,EAAEK,KAAK,CAAC;EAC9D;AACF","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["toError","cause","Error","String"],"sources":["toError.ts"],"sourcesContent":["/** Coerce an unknown thrown value into an Error, leaving real Errors untouched. */\nexport function toError(cause: unknown): Error {\n return cause instanceof Error ? cause : new Error(String(cause));\n}\n"],"mappings":"AAAA;AACA,OAAO,SAASA,OAAOA,CAACC,KAAc,EAAS;EAC7C,OAAOA,KAAK,YAAYC,KAAK,GAAGD,KAAK,GAAG,IAAIC,KAAK,CAACC,MAAM,CAACF,KAAK,CAAC,CAAC;AAClE","ignoreList":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createCustomVideoTrack } from '@fishjam-cloud/react-native-webrtc';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { stopStreamTracks } from './stopStreamTracks';
|
|
4
|
+
import { toError } from './toError';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* State of a forwarding custom video track managed by {@link useManagedForwardTrack}.
|
|
8
|
+
* While the track is being created (or after an error) `track` and `stream` are `null`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const INITIAL_STATE = {
|
|
12
|
+
track: null,
|
|
13
|
+
stream: null,
|
|
14
|
+
error: null
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Owns the async lifecycle of a forwarding custom video track: creates it while `enabled`,
|
|
19
|
+
* exposes the handle + stream once ready, and stops the tracks on disable/unmount (also when
|
|
20
|
+
* creation resolves after the owner already unmounted).
|
|
21
|
+
*/
|
|
22
|
+
export function useManagedForwardTrack(enabled) {
|
|
23
|
+
const [managedTrack, setManagedTrack] = useState(INITIAL_STATE);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// The disabled case needs no work: the previous run's cleanup already reset the state.
|
|
26
|
+
if (!enabled) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Creation is async, so the effect may be torn down before it resolves. `disposed` records
|
|
30
|
+
// that; `created` holds the result once it exists so cleanup can stop it exactly once.
|
|
31
|
+
let disposed = false;
|
|
32
|
+
let created = null;
|
|
33
|
+
createCustomVideoTrack().then(result => {
|
|
34
|
+
if (disposed) {
|
|
35
|
+
// Torn down while creating — throw the just-built track away.
|
|
36
|
+
stopStreamTracks(result.stream, 'useManagedForwardTrack');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
created = result;
|
|
40
|
+
setManagedTrack({
|
|
41
|
+
track: result.track,
|
|
42
|
+
stream: result.stream,
|
|
43
|
+
error: null
|
|
44
|
+
});
|
|
45
|
+
}).catch(cause => {
|
|
46
|
+
if (!disposed) {
|
|
47
|
+
setManagedTrack({
|
|
48
|
+
...INITIAL_STATE,
|
|
49
|
+
error: toError(cause)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
disposed = true;
|
|
55
|
+
if (created != null) {
|
|
56
|
+
stopStreamTracks(created.stream, 'useManagedForwardTrack');
|
|
57
|
+
}
|
|
58
|
+
setManagedTrack(INITIAL_STATE);
|
|
59
|
+
};
|
|
60
|
+
}, [enabled]);
|
|
61
|
+
return managedTrack;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=useManagedForwardTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["createCustomVideoTrack","useEffect","useState","stopStreamTracks","toError","INITIAL_STATE","track","stream","error","useManagedForwardTrack","enabled","managedTrack","setManagedTrack","disposed","created","then","result","catch","cause"],"sources":["useManagedForwardTrack.ts"],"sourcesContent":["import {\n createCustomVideoTrack,\n type CustomVideoTrackResult,\n type ForwardTrack,\n type MediaStream,\n} from '@fishjam-cloud/react-native-webrtc';\nimport { useEffect, useState } from 'react';\n\nimport { stopStreamTracks } from './stopStreamTracks';\nimport { toError } from './toError';\n\n/**\n * State of a forwarding custom video track managed by {@link useManagedForwardTrack}.\n * While the track is being created (or after an error) `track` and `stream` are `null`.\n */\nexport interface ManagedForwardTrack {\n track: ForwardTrack | null;\n stream: MediaStream | null;\n error: Error | null;\n}\n\nconst INITIAL_STATE: ManagedForwardTrack = { track: null, stream: null, error: null };\n\n/**\n * Owns the async lifecycle of a forwarding custom video track: creates it while `enabled`,\n * exposes the handle + stream once ready, and stops the tracks on disable/unmount (also when\n * creation resolves after the owner already unmounted).\n */\nexport function useManagedForwardTrack(enabled: boolean): ManagedForwardTrack {\n const [managedTrack, setManagedTrack] = useState<ManagedForwardTrack>(INITIAL_STATE);\n\n useEffect(() => {\n // The disabled case needs no work: the previous run's cleanup already reset the state.\n if (!enabled) {\n return;\n }\n // Creation is async, so the effect may be torn down before it resolves. `disposed` records\n // that; `created` holds the result once it exists so cleanup can stop it exactly once.\n let disposed = false;\n let created: CustomVideoTrackResult<ForwardTrack> | null = null;\n\n createCustomVideoTrack()\n .then((result) => {\n if (disposed) {\n // Torn down while creating — throw the just-built track away.\n stopStreamTracks(result.stream, 'useManagedForwardTrack');\n return;\n }\n created = result;\n setManagedTrack({ track: result.track, stream: result.stream, error: null });\n })\n .catch((cause: unknown) => {\n if (!disposed) {\n setManagedTrack({ ...INITIAL_STATE, error: toError(cause) });\n }\n });\n\n return () => {\n disposed = true;\n if (created != null) {\n stopStreamTracks(created.stream, 'useManagedForwardTrack');\n }\n setManagedTrack(INITIAL_STATE);\n };\n }, [enabled]);\n\n return managedTrack;\n}\n"],"mappings":"AAAA,SACEA,sBAAsB,QAIjB,oCAAoC;AAC3C,SAASC,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAE3C,SAASC,gBAAgB,QAAQ,oBAAoB;AACrD,SAASC,OAAO,QAAQ,WAAW;;AAEnC;AACA;AACA;AACA;;AAOA,MAAMC,aAAkC,GAAG;EAAEC,KAAK,EAAE,IAAI;EAAEC,MAAM,EAAE,IAAI;EAAEC,KAAK,EAAE;AAAK,CAAC;;AAErF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,sBAAsBA,CAACC,OAAgB,EAAuB;EAC5E,MAAM,CAACC,YAAY,EAAEC,eAAe,CAAC,GAAGV,QAAQ,CAAsBG,aAAa,CAAC;EAEpFJ,SAAS,CAAC,MAAM;IACd;IACA,IAAI,CAACS,OAAO,EAAE;MACZ;IACF;IACA;IACA;IACA,IAAIG,QAAQ,GAAG,KAAK;IACpB,IAAIC,OAAoD,GAAG,IAAI;IAE/Dd,sBAAsB,CAAC,CAAC,CACrBe,IAAI,CAAEC,MAAM,IAAK;MAChB,IAAIH,QAAQ,EAAE;QACZ;QACAV,gBAAgB,CAACa,MAAM,CAACT,MAAM,EAAE,wBAAwB,CAAC;QACzD;MACF;MACAO,OAAO,GAAGE,MAAM;MAChBJ,eAAe,CAAC;QAAEN,KAAK,EAAEU,MAAM,CAACV,KAAK;QAAEC,MAAM,EAAES,MAAM,CAACT,MAAM;QAAEC,KAAK,EAAE;MAAK,CAAC,CAAC;IAC9E,CAAC,CAAC,CACDS,KAAK,CAAEC,KAAc,IAAK;MACzB,IAAI,CAACL,QAAQ,EAAE;QACbD,eAAe,CAAC;UAAE,GAAGP,aAAa;UAAEG,KAAK,EAAEJ,OAAO,CAACc,KAAK;QAAE,CAAC,CAAC;MAC9D;IACF,CAAC,CAAC;IAEJ,OAAO,MAAM;MACXL,QAAQ,GAAG,IAAI;MACf,IAAIC,OAAO,IAAI,IAAI,EAAE;QACnBX,gBAAgB,CAACW,OAAO,CAACP,MAAM,EAAE,wBAAwB,CAAC;MAC5D;MACAK,eAAe,CAACP,aAAa,CAAC;IAChC,CAAC;EACH,CAAC,EAAE,CAACK,OAAO,CAAC,CAAC;EAEb,OAAOC,YAAY;AACrB","ignoreList":[]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createCustomVideoBufferPool, createCustomVideoTrack } from '@fishjam-cloud/react-native-webrtc';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { stopStreamTracks } from './stopStreamTracks';
|
|
4
|
+
import { toError } from './toError';
|
|
5
|
+
|
|
6
|
+
/** One pooled output surface as plain values the frame worklet can capture and import itself. */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* State of a pooled custom video track managed by {@link useManagedPooledTrack}.
|
|
10
|
+
* While the pool and track are being created (or after an error) all fields are `null`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const INITIAL_STATE = {
|
|
14
|
+
track: null,
|
|
15
|
+
stream: null,
|
|
16
|
+
bufferDescriptors: null,
|
|
17
|
+
error: null
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** A fully-allocated pool + its track, owned and torn down as a single unit. */
|
|
21
|
+
|
|
22
|
+
function toWorkletBufferDescriptor(buffer) {
|
|
23
|
+
return {
|
|
24
|
+
index: buffer.index,
|
|
25
|
+
surfaceHandle: buffer.surfaceHandle,
|
|
26
|
+
width: buffer.width,
|
|
27
|
+
height: buffer.height
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function disposePool(pool) {
|
|
31
|
+
void pool.dispose().catch(cause => {
|
|
32
|
+
console.warn('useManagedPooledTrack: disposing the buffer pool failed', cause);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Tears an allocation down in the required order: stop the track's frames, then free its pool. */
|
|
37
|
+
function disposeAllocation({
|
|
38
|
+
pool,
|
|
39
|
+
stream
|
|
40
|
+
}) {
|
|
41
|
+
stopStreamTracks(stream, 'useManagedPooledTrack');
|
|
42
|
+
disposePool(pool);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Allocates the surface pool and a track bound to it. If the track fails, frees the orphan pool. */
|
|
46
|
+
async function allocatePooledTrack(width, height, poolSize) {
|
|
47
|
+
const pool = await createCustomVideoBufferPool({
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
poolSize
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
const {
|
|
54
|
+
track,
|
|
55
|
+
stream
|
|
56
|
+
} = await createCustomVideoTrack({
|
|
57
|
+
pool
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
pool,
|
|
61
|
+
track,
|
|
62
|
+
stream,
|
|
63
|
+
bufferDescriptors: pool.buffers.map(toWorkletBufferDescriptor)
|
|
64
|
+
};
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
disposePool(pool);
|
|
67
|
+
throw cause;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Owns the async lifecycle of a surface pool + pooled custom video track: allocates both while
|
|
73
|
+
* `enabled` (re-allocates when the dimensions change), exposes worklet-ready descriptors, and
|
|
74
|
+
* tears down in the correct order (stop tracks, then dispose the pool) on disable/unmount.
|
|
75
|
+
*/
|
|
76
|
+
export function useManagedPooledTrack(enabled, width, height, poolSize) {
|
|
77
|
+
const [managedTrack, setManagedTrack] = useState(INITIAL_STATE);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
// The disabled case needs no work: the previous run's cleanup already reset the state (so
|
|
80
|
+
// consumers never hold descriptors of a disposed pool).
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Allocation is async, so the effect may be torn down before it resolves. `disposed` records
|
|
86
|
+
// that; `allocation` holds the result once it exists so cleanup can free it exactly once.
|
|
87
|
+
let disposed = false;
|
|
88
|
+
let allocation = null;
|
|
89
|
+
allocatePooledTrack(width, height, poolSize).then(result => {
|
|
90
|
+
if (disposed) {
|
|
91
|
+
// Torn down while allocating — throw the just-built resources away.
|
|
92
|
+
disposeAllocation(result);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
allocation = result;
|
|
96
|
+
setManagedTrack({
|
|
97
|
+
track: result.track,
|
|
98
|
+
stream: result.stream,
|
|
99
|
+
bufferDescriptors: result.bufferDescriptors,
|
|
100
|
+
error: null
|
|
101
|
+
});
|
|
102
|
+
}).catch(cause => {
|
|
103
|
+
if (!disposed) {
|
|
104
|
+
setManagedTrack({
|
|
105
|
+
...INITIAL_STATE,
|
|
106
|
+
error: toError(cause)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
disposed = true;
|
|
112
|
+
if (allocation) {
|
|
113
|
+
disposeAllocation(allocation);
|
|
114
|
+
}
|
|
115
|
+
setManagedTrack(INITIAL_STATE);
|
|
116
|
+
};
|
|
117
|
+
}, [enabled, width, height, poolSize]);
|
|
118
|
+
return managedTrack;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=useManagedPooledTrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["createCustomVideoBufferPool","createCustomVideoTrack","useEffect","useState","stopStreamTracks","toError","INITIAL_STATE","track","stream","bufferDescriptors","error","toWorkletBufferDescriptor","buffer","index","surfaceHandle","width","height","disposePool","pool","dispose","catch","cause","console","warn","disposeAllocation","allocatePooledTrack","poolSize","buffers","map","useManagedPooledTrack","enabled","managedTrack","setManagedTrack","disposed","allocation","then","result"],"sources":["useManagedPooledTrack.ts"],"sourcesContent":["import {\n createCustomVideoBufferPool,\n createCustomVideoTrack,\n type CustomVideoBuffer,\n type CustomVideoBufferPool,\n type MediaStream,\n type PooledTrack,\n} from '@fishjam-cloud/react-native-webrtc';\nimport { useEffect, useState } from 'react';\n\nimport { stopStreamTracks } from './stopStreamTracks';\nimport { toError } from './toError';\n\n/** One pooled output surface as plain values the frame worklet can capture and import itself. */\nexport interface WorkletBufferDescriptor {\n index: number;\n surfaceHandle: bigint;\n width: number;\n height: number;\n}\n\n/**\n * State of a pooled custom video track managed by {@link useManagedPooledTrack}.\n * While the pool and track are being created (or after an error) all fields are `null`.\n */\nexport interface ManagedPooledTrack {\n track: PooledTrack | null;\n stream: MediaStream | null;\n /** Plain per-surface descriptors (the pool object itself is not worklet-serializable). */\n bufferDescriptors: WorkletBufferDescriptor[] | null;\n error: Error | null;\n}\n\nconst INITIAL_STATE: ManagedPooledTrack = { track: null, stream: null, bufferDescriptors: null, error: null };\n\n/** A fully-allocated pool + its track, owned and torn down as a single unit. */\ninterface PooledTrackAllocation {\n pool: CustomVideoBufferPool;\n track: PooledTrack;\n stream: MediaStream;\n bufferDescriptors: WorkletBufferDescriptor[];\n}\n\nfunction toWorkletBufferDescriptor(buffer: CustomVideoBuffer): WorkletBufferDescriptor {\n return { index: buffer.index, surfaceHandle: buffer.surfaceHandle, width: buffer.width, height: buffer.height };\n}\n\nfunction disposePool(pool: CustomVideoBufferPool): void {\n void pool.dispose().catch((cause: unknown) => {\n console.warn('useManagedPooledTrack: disposing the buffer pool failed', cause);\n });\n}\n\n/** Tears an allocation down in the required order: stop the track's frames, then free its pool. */\nfunction disposeAllocation({ pool, stream }: PooledTrackAllocation): void {\n stopStreamTracks(stream, 'useManagedPooledTrack');\n disposePool(pool);\n}\n\n/** Allocates the surface pool and a track bound to it. If the track fails, frees the orphan pool. */\nasync function allocatePooledTrack(width: number, height: number, poolSize: number): Promise<PooledTrackAllocation> {\n const pool = await createCustomVideoBufferPool({ width, height, poolSize });\n try {\n const { track, stream } = await createCustomVideoTrack({ pool });\n return { pool, track, stream, bufferDescriptors: pool.buffers.map(toWorkletBufferDescriptor) };\n } catch (cause) {\n disposePool(pool);\n throw cause;\n }\n}\n\n/**\n * Owns the async lifecycle of a surface pool + pooled custom video track: allocates both while\n * `enabled` (re-allocates when the dimensions change), exposes worklet-ready descriptors, and\n * tears down in the correct order (stop tracks, then dispose the pool) on disable/unmount.\n */\nexport function useManagedPooledTrack(\n enabled: boolean,\n width: number,\n height: number,\n poolSize: number,\n): ManagedPooledTrack {\n const [managedTrack, setManagedTrack] = useState<ManagedPooledTrack>(INITIAL_STATE);\n\n useEffect(() => {\n // The disabled case needs no work: the previous run's cleanup already reset the state (so\n // consumers never hold descriptors of a disposed pool).\n if (!enabled) {\n return;\n }\n\n // Allocation is async, so the effect may be torn down before it resolves. `disposed` records\n // that; `allocation` holds the result once it exists so cleanup can free it exactly once.\n let disposed = false;\n let allocation: PooledTrackAllocation | null = null;\n\n allocatePooledTrack(width, height, poolSize)\n .then((result) => {\n if (disposed) {\n // Torn down while allocating — throw the just-built resources away.\n disposeAllocation(result);\n return;\n }\n allocation = result;\n setManagedTrack({\n track: result.track,\n stream: result.stream,\n bufferDescriptors: result.bufferDescriptors,\n error: null,\n });\n })\n .catch((cause: unknown) => {\n if (!disposed) {\n setManagedTrack({ ...INITIAL_STATE, error: toError(cause) });\n }\n });\n\n return () => {\n disposed = true;\n if (allocation) {\n disposeAllocation(allocation);\n }\n setManagedTrack(INITIAL_STATE);\n };\n }, [enabled, width, height, poolSize]);\n\n return managedTrack;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { GPUBufferUsage, GPUShaderStage } from 'react-native-webgpu';
|
|
2
|
+
import tgpu from 'typegpu';
|
|
3
|
+
import * as d from 'typegpu/data';
|
|
4
|
+
import { add, div, mul, sub } from 'typegpu/std';
|
|
5
|
+
import { createCameraBindGroup, createCameraShaderBindings, sampleCamera } from './cameraShaderBindings';
|
|
6
|
+
import { FrameCropParams, packFrameCropParams } from './cropUtilities';
|
|
7
|
+
import { getOutputSurfaceFormat } from './requiredFeatures';
|
|
8
|
+
|
|
9
|
+
// Crop is a plain uniform, so it is authored in TGSL and TypeGPU assigns it @group(0). The camera
|
|
10
|
+
// is a `texture_external`, which TypeGPU cannot resolve in a shader, so its bindings are raw WGSL
|
|
11
|
+
// and go at @group(1) (see cameraShaderBindings). This is a group swap from the pre-TGSL shader
|
|
12
|
+
// (which had camera at 0, crop at 1) but is otherwise identical.
|
|
13
|
+
const CROP_BIND_GROUP_INDEX = 0;
|
|
14
|
+
const CAMERA_BIND_GROUP_INDEX = 1;
|
|
15
|
+
const FRAME_CROP_BUFFER_BYTES = d.sizeOf(FrameCropParams);
|
|
16
|
+
|
|
17
|
+
// TypeGPU bind group layout for the crop uniform — the source of truth for the WGSL declaration
|
|
18
|
+
// and for the fragment's typed access to `cropParams`.
|
|
19
|
+
const cropLayout = /*#__PURE__*/(globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu.bindGroupLayout({
|
|
20
|
+
cropParams: {
|
|
21
|
+
uniform: FrameCropParams
|
|
22
|
+
}
|
|
23
|
+
}), "cropLayout");
|
|
24
|
+
|
|
25
|
+
// One oversized triangle covering the viewport; uv spans [0,1] top-left origin over the visible
|
|
26
|
+
// area.
|
|
27
|
+
const vertexMain = tgpu.vertexFn({
|
|
28
|
+
in: {
|
|
29
|
+
vertexIndex: d.builtin.vertexIndex
|
|
30
|
+
},
|
|
31
|
+
out: {
|
|
32
|
+
position: d.builtin.position,
|
|
33
|
+
uv: d.location(0, d.vec2f)
|
|
34
|
+
}
|
|
35
|
+
})(/*#__PURE__*/($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = input => {
|
|
36
|
+
const positions = [d.vec2f(-1, -1), d.vec2f(3, -1), d.vec2f(-1, 3)];
|
|
37
|
+
const position = positions[input.vertexIndex];
|
|
38
|
+
return {
|
|
39
|
+
position: d.vec4f(position.x, position.y, 0, 1),
|
|
40
|
+
uv: d.vec2f((position.x + 1) * 0.5, 1 - (position.y + 1) * 0.5)
|
|
41
|
+
};
|
|
42
|
+
}, {
|
|
43
|
+
v: 1,
|
|
44
|
+
name: undefined,
|
|
45
|
+
ast: {
|
|
46
|
+
params: [{
|
|
47
|
+
type: "i",
|
|
48
|
+
name: "input"
|
|
49
|
+
}],
|
|
50
|
+
body: [0, [[13, "positions", [100, [[6, [7, "d", "vec2f"], [[4, "-", [5, "1"]], [4, "-", [5, "1"]]]], [6, [7, "d", "vec2f"], [[5, "3"], [4, "-", [5, "1"]]]], [6, [7, "d", "vec2f"], [[4, "-", [5, "1"]], [5, "3"]]]]]], [13, "position", [8, "positions", [7, "input", "vertexIndex"]]], [10, [104, {
|
|
51
|
+
position: [6, [7, "d", "vec4f"], [[7, "position", "x"], [7, "position", "y"], [5, "0"], [5, "1"]]],
|
|
52
|
+
uv: [6, [7, "d", "vec2f"], [[1, [1, [7, "position", "x"], "+", [5, "1"]], "*", [5, "0.5"]], [1, [5, "1"], "-", [1, [1, [7, "position", "y"], "+", [5, "1"]], "*", [5, "0.5"]]]]]
|
|
53
|
+
}]]]],
|
|
54
|
+
externalNames: ["d"]
|
|
55
|
+
},
|
|
56
|
+
externals: () => {
|
|
57
|
+
return {
|
|
58
|
+
d
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}) && $.f)({})).$name('vertexMain');
|
|
62
|
+
|
|
63
|
+
// The fragment applies the FrameCropParams crop + orientation transform and samples the camera via
|
|
64
|
+
// sampleCamera(). Mirroring is folded into a build-time sign/offset on uv.x (mirror → 1 - x) so the
|
|
65
|
+
// shader stays branch-free.
|
|
66
|
+
function makeFragmentMain(mirror) {
|
|
67
|
+
const mirrorSign = mirror ? -1 : 1;
|
|
68
|
+
const mirrorOffset = mirror ? 1 : 0;
|
|
69
|
+
return tgpu.fragmentFn({
|
|
70
|
+
in: {
|
|
71
|
+
screenUv: d.location(0, d.vec2f)
|
|
72
|
+
},
|
|
73
|
+
out: d.vec4f
|
|
74
|
+
})(/*#__PURE__*/($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = input => {
|
|
75
|
+
// cropUv = (mirrorSign * screenUv.x + mirrorOffset, screenUv.y), expressed with vector ops
|
|
76
|
+
// so mirrorSign/mirrorOffset fold to literals and the WGSL stays branch-free.
|
|
77
|
+
const cropUv = add(mul(input.screenUv, d.vec2f(mirrorSign, 1)), d.vec2f(mirrorOffset, 0));
|
|
78
|
+
const cp = cropLayout.$.cropParams;
|
|
79
|
+
const sourcePixel = add(cp.cropOrigin, mul(cropUv, cp.cropSize));
|
|
80
|
+
const sourceUv = div(sourcePixel, d.vec2f(cp.sourceSize));
|
|
81
|
+
const cameraUv = add(mul(cp.uvTransform, sub(sourceUv, d.vec2f(0.5))), d.vec2f(0.5));
|
|
82
|
+
return sampleCamera(cameraUv);
|
|
83
|
+
}, {
|
|
84
|
+
v: 1,
|
|
85
|
+
name: undefined,
|
|
86
|
+
ast: {
|
|
87
|
+
params: [{
|
|
88
|
+
type: "i",
|
|
89
|
+
name: "input"
|
|
90
|
+
}],
|
|
91
|
+
body: [0, [[13, "cropUv", [6, "add", [[6, "mul", [[7, "input", "screenUv"], [6, [7, "d", "vec2f"], ["mirrorSign", [5, "1"]]]]], [6, [7, "d", "vec2f"], ["mirrorOffset", [5, "0"]]]]]], [13, "cp", [7, [7, "cropLayout", "$"], "cropParams"]], [13, "sourcePixel", [6, "add", [[7, "cp", "cropOrigin"], [6, "mul", ["cropUv", [7, "cp", "cropSize"]]]]]], [13, "sourceUv", [6, "div", ["sourcePixel", [6, [7, "d", "vec2f"], [[7, "cp", "sourceSize"]]]]]], [13, "cameraUv", [6, "add", [[6, "mul", [[7, "cp", "uvTransform"], [6, "sub", ["sourceUv", [6, [7, "d", "vec2f"], [[5, "0.5"]]]]]]], [6, [7, "d", "vec2f"], [[5, "0.5"]]]]]], [10, [6, "sampleCamera", ["cameraUv"]]]]],
|
|
92
|
+
externalNames: ["add", "mul", "d", "mirrorSign", "mirrorOffset", "cropLayout", "div", "sub", "sampleCamera"]
|
|
93
|
+
},
|
|
94
|
+
externals: () => {
|
|
95
|
+
return {
|
|
96
|
+
add,
|
|
97
|
+
mul,
|
|
98
|
+
d,
|
|
99
|
+
mirrorSign,
|
|
100
|
+
mirrorOffset,
|
|
101
|
+
cropLayout,
|
|
102
|
+
div,
|
|
103
|
+
sub,
|
|
104
|
+
sampleCamera
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}) && $.f)({})).$name('fragmentMain');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Options for {@link createCameraPassthroughPipeline}. */
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A ready-made full-screen camera→target render pipeline. Build once at setup with
|
|
114
|
+
* {@link createCameraPassthroughPipeline}; every field is safe to capture into the frame worklet.
|
|
115
|
+
*
|
|
116
|
+
* @group WebGPU
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolves the passthrough shader to WGSL: the raw camera-texture declarations (which TypeGPU
|
|
121
|
+
* cannot emit) followed by the TGSL-authored vertex + fragment functions, `sampleCamera`, and the
|
|
122
|
+
* `FrameCropParams` struct + crop uniform.
|
|
123
|
+
*/
|
|
124
|
+
function buildPassthroughShaderCode(cameraShaderBindings, mirror) {
|
|
125
|
+
const resolved = tgpu.resolve({
|
|
126
|
+
externals: {
|
|
127
|
+
vertexMain,
|
|
128
|
+
fragmentMain: makeFragmentMain(mirror)
|
|
129
|
+
},
|
|
130
|
+
names: 'strict'
|
|
131
|
+
});
|
|
132
|
+
return `${cameraShaderBindings.bindingDeclarations}\n${resolved}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Builds the full-screen camera passthrough pipeline: crop/orientation via {@link FrameCrop},
|
|
137
|
+
* platform-correct camera sampling, one triangle. Use it to publish the camera through the WebGPU
|
|
138
|
+
* tier with zero WGSL of your own, or as the base pass under your overlay passes.
|
|
139
|
+
*
|
|
140
|
+
* @group WebGPU
|
|
141
|
+
*/
|
|
142
|
+
export function createCameraPassthroughPipeline(device, options = {}) {
|
|
143
|
+
const outputFormat = options.outputFormat ?? getOutputSurfaceFormat();
|
|
144
|
+
const cameraShaderBindings = createCameraShaderBindings(device, {
|
|
145
|
+
bindGroupIndex: CAMERA_BIND_GROUP_INDEX
|
|
146
|
+
});
|
|
147
|
+
const cropBindGroupLayout = device.createBindGroupLayout({
|
|
148
|
+
label: 'fishjam-camera-passthrough-crop',
|
|
149
|
+
entries: [{
|
|
150
|
+
binding: 0,
|
|
151
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
152
|
+
buffer: {
|
|
153
|
+
type: 'uniform'
|
|
154
|
+
}
|
|
155
|
+
}]
|
|
156
|
+
});
|
|
157
|
+
const cropParamsBuffer = /*#__PURE__*/(globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(device.createBuffer({
|
|
158
|
+
label: 'fishjam-camera-passthrough-crop-params',
|
|
159
|
+
size: FRAME_CROP_BUFFER_BYTES,
|
|
160
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
161
|
+
}), "cropParamsBuffer");
|
|
162
|
+
const cropBindGroup = device.createBindGroup({
|
|
163
|
+
layout: cropBindGroupLayout,
|
|
164
|
+
entries: [{
|
|
165
|
+
binding: 0,
|
|
166
|
+
resource: {
|
|
167
|
+
buffer: cropParamsBuffer
|
|
168
|
+
}
|
|
169
|
+
}]
|
|
170
|
+
});
|
|
171
|
+
const shaderModule = device.createShaderModule({
|
|
172
|
+
label: 'fishjam-camera-passthrough',
|
|
173
|
+
code: buildPassthroughShaderCode(cameraShaderBindings, options.mirror ?? false)
|
|
174
|
+
});
|
|
175
|
+
const bindGroupLayouts = [];
|
|
176
|
+
bindGroupLayouts[CROP_BIND_GROUP_INDEX] = cropBindGroupLayout;
|
|
177
|
+
bindGroupLayouts[CAMERA_BIND_GROUP_INDEX] = cameraShaderBindings.bindGroupLayout;
|
|
178
|
+
const pipeline = /*#__PURE__*/(globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(device.createRenderPipeline({
|
|
179
|
+
label: 'fishjam-camera-passthrough',
|
|
180
|
+
layout: device.createPipelineLayout({
|
|
181
|
+
bindGroupLayouts
|
|
182
|
+
}),
|
|
183
|
+
vertex: {
|
|
184
|
+
module: shaderModule,
|
|
185
|
+
entryPoint: 'vertexMain'
|
|
186
|
+
},
|
|
187
|
+
fragment: {
|
|
188
|
+
module: shaderModule,
|
|
189
|
+
entryPoint: 'fragmentMain',
|
|
190
|
+
targets: [{
|
|
191
|
+
format: outputFormat
|
|
192
|
+
}]
|
|
193
|
+
},
|
|
194
|
+
primitive: {
|
|
195
|
+
topology: 'triangle-list'
|
|
196
|
+
}
|
|
197
|
+
}), "pipeline");
|
|
198
|
+
return {
|
|
199
|
+
pipeline,
|
|
200
|
+
cameraShaderBindings,
|
|
201
|
+
cropParamsBuffer,
|
|
202
|
+
cropBindGroup
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Encodes one full-screen pass drawing the camera into `outputView`, cropped per `crop`.
|
|
208
|
+
* Worklet-safe; call it inside your render callback, and encode any overlay passes after it on
|
|
209
|
+
* the same command encoder (with `loadOp: 'load'` so they draw on top).
|
|
210
|
+
*
|
|
211
|
+
* At most one call per pipeline instance per frame: the crop lives in a single uniform buffer
|
|
212
|
+
* written via `queue.writeBuffer`, which lands before the submitted command buffer executes — a
|
|
213
|
+
* second call in the same frame makes both draws use the second crop. Encode to multiple targets
|
|
214
|
+
* with different crops by building one pipeline per target.
|
|
215
|
+
*
|
|
216
|
+
* @group WebGPU
|
|
217
|
+
*/
|
|
218
|
+
export function encodeCameraPassthrough(device, passthrough, cameraTexture, outputView, commandEncoder, crop) {
|
|
219
|
+
'worklet';
|
|
220
|
+
|
|
221
|
+
device.queue.writeBuffer(passthrough.cropParamsBuffer, 0, packFrameCropParams(crop));
|
|
222
|
+
const cameraBindGroup = createCameraBindGroup(device, passthrough.cameraShaderBindings, cameraTexture);
|
|
223
|
+
const pass = commandEncoder.beginRenderPass({
|
|
224
|
+
colorAttachments: [{
|
|
225
|
+
view: outputView,
|
|
226
|
+
loadOp: 'clear',
|
|
227
|
+
storeOp: 'store',
|
|
228
|
+
clearValue: [0, 0, 0, 1]
|
|
229
|
+
}]
|
|
230
|
+
});
|
|
231
|
+
pass.setPipeline(passthrough.pipeline);
|
|
232
|
+
pass.setBindGroup(CROP_BIND_GROUP_INDEX, passthrough.cropBindGroup);
|
|
233
|
+
pass.setBindGroup(CAMERA_BIND_GROUP_INDEX, cameraBindGroup);
|
|
234
|
+
pass.draw(3);
|
|
235
|
+
pass.end();
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=cameraPassthroughPipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["GPUBufferUsage","GPUShaderStage","tgpu","d","add","div","mul","sub","createCameraBindGroup","createCameraShaderBindings","sampleCamera","FrameCropParams","packFrameCropParams","getOutputSurfaceFormat","CROP_BIND_GROUP_INDEX","CAMERA_BIND_GROUP_INDEX","FRAME_CROP_BUFFER_BYTES","sizeOf","cropLayout","globalThis","__TYPEGPU_AUTONAME__","a","bindGroupLayout","cropParams","uniform","vertexMain","vertexFn","in","vertexIndex","builtin","out","position","uv","location","vec2f","$","__TYPEGPU_META__","WeakMap","set","f","input","positions","vec4f","x","y","v","name","undefined","ast","params","type","body","externalNames","externals","$name","makeFragmentMain","mirror","mirrorSign","mirrorOffset","fragmentFn","screenUv","cropUv","cp","sourcePixel","cropOrigin","cropSize","sourceUv","sourceSize","cameraUv","uvTransform","buildPassthroughShaderCode","cameraShaderBindings","resolved","resolve","fragmentMain","names","bindingDeclarations","createCameraPassthroughPipeline","device","options","outputFormat","bindGroupIndex","cropBindGroupLayout","createBindGroupLayout","label","entries","binding","visibility","FRAGMENT","buffer","cropParamsBuffer","createBuffer","size","usage","UNIFORM","COPY_DST","cropBindGroup","createBindGroup","layout","resource","shaderModule","createShaderModule","code","bindGroupLayouts","pipeline","createRenderPipeline","createPipelineLayout","vertex","module","entryPoint","fragment","targets","format","primitive","topology","encodeCameraPassthrough","passthrough","cameraTexture","outputView","commandEncoder","crop","queue","writeBuffer","cameraBindGroup","pass","beginRenderPass","colorAttachments","view","loadOp","storeOp","clearValue","setPipeline","setBindGroup","draw","end"],"sources":["cameraPassthroughPipeline.ts"],"sourcesContent":["import { GPUBufferUsage, GPUShaderStage } from 'react-native-webgpu';\nimport tgpu from 'typegpu';\nimport * as d from 'typegpu/data';\nimport { add, div, mul, sub } from 'typegpu/std';\n\nimport {\n type CameraShaderBindings,\n createCameraBindGroup,\n createCameraShaderBindings,\n sampleCamera,\n} from './cameraShaderBindings';\nimport { type FrameCrop, FrameCropParams, packFrameCropParams } from './cropUtilities';\nimport { getOutputSurfaceFormat } from './requiredFeatures';\n\n// Crop is a plain uniform, so it is authored in TGSL and TypeGPU assigns it @group(0). The camera\n// is a `texture_external`, which TypeGPU cannot resolve in a shader, so its bindings are raw WGSL\n// and go at @group(1) (see cameraShaderBindings). This is a group swap from the pre-TGSL shader\n// (which had camera at 0, crop at 1) but is otherwise identical.\nconst CROP_BIND_GROUP_INDEX = 0;\nconst CAMERA_BIND_GROUP_INDEX = 1;\n\nconst FRAME_CROP_BUFFER_BYTES = d.sizeOf(FrameCropParams);\n\n// TypeGPU bind group layout for the crop uniform — the source of truth for the WGSL declaration\n// and for the fragment's typed access to `cropParams`.\nconst cropLayout = tgpu.bindGroupLayout({ cropParams: { uniform: FrameCropParams } });\n\n// One oversized triangle covering the viewport; uv spans [0,1] top-left origin over the visible\n// area.\nconst vertexMain = tgpu\n .vertexFn({\n in: { vertexIndex: d.builtin.vertexIndex },\n out: { position: d.builtin.position, uv: d.location(0, d.vec2f) },\n })((input) => {\n const positions = [d.vec2f(-1, -1), d.vec2f(3, -1), d.vec2f(-1, 3)];\n const position = positions[input.vertexIndex];\n return {\n position: d.vec4f(position.x, position.y, 0, 1),\n uv: d.vec2f((position.x + 1) * 0.5, 1 - (position.y + 1) * 0.5),\n };\n })\n .$name('vertexMain');\n\n// The fragment applies the FrameCropParams crop + orientation transform and samples the camera via\n// sampleCamera(). Mirroring is folded into a build-time sign/offset on uv.x (mirror → 1 - x) so the\n// shader stays branch-free.\nfunction makeFragmentMain(mirror: boolean) {\n const mirrorSign = mirror ? -1 : 1;\n const mirrorOffset = mirror ? 1 : 0;\n return tgpu\n .fragmentFn({ in: { screenUv: d.location(0, d.vec2f) }, out: d.vec4f })((input) => {\n // cropUv = (mirrorSign * screenUv.x + mirrorOffset, screenUv.y), expressed with vector ops\n // so mirrorSign/mirrorOffset fold to literals and the WGSL stays branch-free.\n const cropUv = add(mul(input.screenUv, d.vec2f(mirrorSign, 1)), d.vec2f(mirrorOffset, 0));\n const cp = cropLayout.$.cropParams;\n const sourcePixel = add(cp.cropOrigin, mul(cropUv, cp.cropSize));\n const sourceUv = div(sourcePixel, d.vec2f(cp.sourceSize));\n const cameraUv = add(mul(cp.uvTransform, sub(sourceUv, d.vec2f(0.5))), d.vec2f(0.5));\n return sampleCamera(cameraUv);\n })\n .$name('fragmentMain');\n}\n\n/** Options for {@link createCameraPassthroughPipeline}. */\nexport interface CameraPassthroughPipelineOptions {\n /** Render-target format. Defaults to {@link getOutputSurfaceFormat} (the Fishjam output surface). */\n outputFormat?: GPUTextureFormat;\n /** Mirror the camera horizontally (the usual selfie self-view convention). Defaults to `false`. */\n mirror?: boolean;\n}\n\n/**\n * A ready-made full-screen camera→target render pipeline. Build once at setup with\n * {@link createCameraPassthroughPipeline}; every field is safe to capture into the frame worklet.\n *\n * @group WebGPU\n */\nexport interface CameraPassthroughPipeline {\n readonly pipeline: GPURenderPipeline;\n /** The camera shader bindings the pipeline samples through. */\n readonly cameraShaderBindings: CameraShaderBindings;\n /** Uniform buffer holding the packed FrameCropParams; written by {@link encodeCameraPassthrough}. */\n readonly cropParamsBuffer: GPUBuffer;\n /** Static bind group with the crop uniform. */\n readonly cropBindGroup: GPUBindGroup;\n}\n\n/**\n * Resolves the passthrough shader to WGSL: the raw camera-texture declarations (which TypeGPU\n * cannot emit) followed by the TGSL-authored vertex + fragment functions, `sampleCamera`, and the\n * `FrameCropParams` struct + crop uniform.\n */\nfunction buildPassthroughShaderCode(cameraShaderBindings: CameraShaderBindings, mirror: boolean): string {\n const resolved = tgpu.resolve({\n externals: { vertexMain, fragmentMain: makeFragmentMain(mirror) },\n names: 'strict',\n });\n return `${cameraShaderBindings.bindingDeclarations}\\n${resolved}`;\n}\n\n/**\n * Builds the full-screen camera passthrough pipeline: crop/orientation via {@link FrameCrop},\n * platform-correct camera sampling, one triangle. Use it to publish the camera through the WebGPU\n * tier with zero WGSL of your own, or as the base pass under your overlay passes.\n *\n * @group WebGPU\n */\nexport function createCameraPassthroughPipeline(\n device: GPUDevice,\n options: CameraPassthroughPipelineOptions = {},\n): CameraPassthroughPipeline {\n const outputFormat = options.outputFormat ?? getOutputSurfaceFormat();\n const cameraShaderBindings = createCameraShaderBindings(device, { bindGroupIndex: CAMERA_BIND_GROUP_INDEX });\n\n const cropBindGroupLayout = device.createBindGroupLayout({\n label: 'fishjam-camera-passthrough-crop',\n entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } }],\n });\n const cropParamsBuffer = device.createBuffer({\n label: 'fishjam-camera-passthrough-crop-params',\n size: FRAME_CROP_BUFFER_BYTES,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n const cropBindGroup = device.createBindGroup({\n layout: cropBindGroupLayout,\n entries: [{ binding: 0, resource: { buffer: cropParamsBuffer } }],\n });\n\n const shaderModule = device.createShaderModule({\n label: 'fishjam-camera-passthrough',\n code: buildPassthroughShaderCode(cameraShaderBindings, options.mirror ?? false),\n });\n const bindGroupLayouts: GPUBindGroupLayout[] = [];\n bindGroupLayouts[CROP_BIND_GROUP_INDEX] = cropBindGroupLayout;\n bindGroupLayouts[CAMERA_BIND_GROUP_INDEX] = cameraShaderBindings.bindGroupLayout;\n const pipeline = device.createRenderPipeline({\n label: 'fishjam-camera-passthrough',\n layout: device.createPipelineLayout({ bindGroupLayouts }),\n vertex: { module: shaderModule, entryPoint: 'vertexMain' },\n fragment: {\n module: shaderModule,\n entryPoint: 'fragmentMain',\n targets: [{ format: outputFormat }],\n },\n primitive: { topology: 'triangle-list' },\n });\n\n return { pipeline, cameraShaderBindings, cropParamsBuffer, cropBindGroup };\n}\n\n/**\n * Encodes one full-screen pass drawing the camera into `outputView`, cropped per `crop`.\n * Worklet-safe; call it inside your render callback, and encode any overlay passes after it on\n * the same command encoder (with `loadOp: 'load'` so they draw on top).\n *\n * At most one call per pipeline instance per frame: the crop lives in a single uniform buffer\n * written via `queue.writeBuffer`, which lands before the submitted command buffer executes — a\n * second call in the same frame makes both draws use the second crop. Encode to multiple targets\n * with different crops by building one pipeline per target.\n *\n * @group WebGPU\n */\nexport function encodeCameraPassthrough(\n device: GPUDevice,\n passthrough: CameraPassthroughPipeline,\n cameraTexture: GPUExternalTexture,\n outputView: GPUTextureView,\n commandEncoder: GPUCommandEncoder,\n crop: FrameCrop,\n): void {\n 'worklet';\n device.queue.writeBuffer(passthrough.cropParamsBuffer, 0, packFrameCropParams(crop));\n const cameraBindGroup = createCameraBindGroup(device, passthrough.cameraShaderBindings, cameraTexture);\n const pass = commandEncoder.beginRenderPass({\n colorAttachments: [{ view: outputView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1] }],\n });\n pass.setPipeline(passthrough.pipeline);\n pass.setBindGroup(CROP_BIND_GROUP_INDEX, passthrough.cropBindGroup);\n pass.setBindGroup(CAMERA_BIND_GROUP_INDEX, cameraBindGroup);\n pass.draw(3);\n pass.end();\n}\n"],"mappings":"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","ignoreList":[]}
|