@symbiote-native/gl 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +219 -2
- package/build/angular/gl-view/index.d.ts +18 -0
- package/build/angular/gl-view/index.js +87 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +3 -0
- package/build/core/constants.d.ts +4 -0
- package/build/core/constants.js +4 -0
- package/build/core/gl-context.d.ts +13 -0
- package/build/core/gl-context.js +54 -0
- package/build/core/gl-errors.d.ts +1 -0
- package/build/core/gl-errors.js +8 -0
- package/build/core/gl-utils.d.ts +5 -0
- package/build/core/gl-utils.js +84 -0
- package/build/core/gl-view.d.ts +16 -0
- package/build/core/gl-view.js +107 -0
- package/build/core/index.d.ts +6 -0
- package/build/core/index.js +5 -0
- package/build/core/native-module.d.ts +15 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +75 -0
- package/build/core/types.js +13 -0
- package/build/core/worklet-context-manager.d.ts +6 -0
- package/build/core/worklet-context-manager.js +35 -0
- package/build/react/gl-view.d.ts +8 -0
- package/build/react/gl-view.js +6 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.js +3 -0
- package/build/solid/gl-view.d.ts +13 -0
- package/build/solid/gl-view.js +4 -0
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +3 -0
- package/build/svelte/gl-view.svelte +19 -0
- package/build/svelte/gl-view.svelte.d.ts +9 -0
- package/build/svelte/index.d.ts +2 -0
- package/build/svelte/index.js +3 -0
- package/build/vue/gl-view.d.ts +2 -0
- package/build/vue/gl-view.js +4 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +3 -0
- package/build-ngc/angular/gl-view/index.d.ts +21 -0
- package/build-ngc/angular/gl-view/index.js +46 -0
- package/build-ngc/angular/gl-view/index.js.map +1 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +4 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/constants.d.ts +4 -0
- package/build-ngc/core/constants.js +5 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/gl-context.d.ts +13 -0
- package/build-ngc/core/gl-context.js +55 -0
- package/build-ngc/core/gl-context.js.map +1 -0
- package/build-ngc/core/gl-errors.d.ts +1 -0
- package/build-ngc/core/gl-errors.js +9 -0
- package/build-ngc/core/gl-errors.js.map +1 -0
- package/build-ngc/core/gl-utils.d.ts +5 -0
- package/build-ngc/core/gl-utils.js +85 -0
- package/build-ngc/core/gl-utils.js.map +1 -0
- package/build-ngc/core/gl-view.d.ts +16 -0
- package/build-ngc/core/gl-view.js +108 -0
- package/build-ngc/core/gl-view.js.map +1 -0
- package/build-ngc/core/index.d.ts +6 -0
- package/build-ngc/core/index.js +6 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +15 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +75 -0
- package/build-ngc/core/types.js +14 -0
- package/build-ngc/core/types.js.map +1 -0
- package/build-ngc/core/worklet-context-manager.d.ts +6 -0
- package/build-ngc/core/worklet-context-manager.js +36 -0
- package/build-ngc/core/worklet-context-manager.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +145 -3
- package/src/angular/gl-view/index.ts +51 -0
- package/src/angular/index.ts +4 -0
- package/src/core/constants.ts +6 -0
- package/src/core/gl-context.ts +79 -0
- package/src/core/gl-errors.ts +8 -0
- package/src/core/gl-utils.ts +106 -0
- package/src/core/gl-view.ts +160 -0
- package/src/core/index.ts +26 -0
- package/src/core/native-module.ts +22 -0
- package/src/core/types.ts +95 -0
- package/src/core/worklet-context-manager.ts +52 -0
- package/src/react/gl-view.ts +17 -0
- package/src/react/index.ts +5 -0
- package/src/solid/gl-view.ts +8 -0
- package/src/solid/index.ts +4 -0
- package/src/svelte/gl-view.svelte +19 -0
- package/src/svelte/index.ts +4 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +6 -0
- package/src/vue/gl-view.ts +5 -0
- package/src/vue/index.ts +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,220 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @symbiote-native/gl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[`expo-gl`](https://docs.expo.dev/versions/latest/sdk/gl-view/) for **every**
|
|
4
|
+
[SymbioteNative](../../README.md) adapter: React, Vue, Svelte, Solid and Angular. A `GLView` that
|
|
5
|
+
gives a WebGL2 context to draw into, headless contexts and snapshots.
|
|
6
|
+
|
|
7
|
+
Built the same way as [`@symbiote-native/clipboard`](../clipboard)'s `ClipboardPasteButton`, an
|
|
8
|
+
`expo-modules-core` native view reached through `requireNativeViewManager` (see the
|
|
9
|
+
`symbiote-expo-native-module` project skill: why `expo-modules-core` is depended on directly and never
|
|
10
|
+
the `expo` meta-package, why the upstream JS is hand-ported into `core/`, and how autolinking finds the
|
|
11
|
+
native module). One render function in `core/` returns a descriptor and every adapter only turns it into
|
|
12
|
+
its own element.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
**New app:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @symbiote-native/cli new my-app --gl
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Existing SymbioteNative app:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @symbiote-native/cli add --gl
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Either way: installs `@symbiote-native/gl` and wires the native autolinking automatically,
|
|
29
|
+
see [`@symbiote-native/cli`](../cli).
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @symbiote-native/gl
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Depends on `expo-gl` and `expo-modules-core` directly (regular dependencies, pinned to exact
|
|
39
|
+
versions, since this package's `core/` is hand-ported against one specific native API shape).
|
|
40
|
+
Never install `expo-gl` yourself, and never add the `expo` package to this project: it bundles
|
|
41
|
+
its own Metro/Babel pipeline that conflicts with this project's own.
|
|
42
|
+
|
|
43
|
+
### Required one-time step: native autolinking wiring
|
|
44
|
+
|
|
45
|
+
`expo-gl`'s native code is discovered by `expo-modules-autolinking`, not by RN's own
|
|
46
|
+
`react-native.config.cjs` mechanism. Wire it into the native host app **once**, it covers this package
|
|
47
|
+
and every other `expo-modules-core` package with zero further changes. The steps and the
|
|
48
|
+
mechanics behind them are in the `symbiote-expo-native-module` skill and in
|
|
49
|
+
[`@symbiote-native/network`](../network)'s README; the reference app is `examples/expo-react`.
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
53
|
+
## Shape
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
src/core/ gl-view.ts - the view and its handle. gl-context.ts - createContextAsync,
|
|
57
|
+
destroyContextAsync, takeSnapshotAsync, getWorkletContext, forgetting a context.
|
|
58
|
+
worklet-context-manager.ts - the optional Reanimated side, in its own file so an
|
|
59
|
+
import of Reanimated inside a try survives Metro's inline requires. gl-utils.ts,
|
|
60
|
+
gl-errors.ts, types.ts.
|
|
61
|
+
src/react/ @symbiote-native/gl/react - GLView
|
|
62
|
+
src/vue/ @symbiote-native/gl/vue - GLView
|
|
63
|
+
src/svelte/ @symbiote-native/gl/svelte - GLView (.svelte)
|
|
64
|
+
src/solid/ @symbiote-native/gl/solid - GLView
|
|
65
|
+
src/angular/ @symbiote-native/gl/angular - GLView
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A controller may define `dispose`, which every adapter runs on unmount (a React effect cleanup, Vue
|
|
69
|
+
`onUnmounted`, Solid `onCleanup`, a Svelte `$effect` teardown, Angular's `disposeView`). `GLView` uses it to
|
|
70
|
+
forget its context. The package tsconfig adds the DOM lib for the WebGL types.
|
|
71
|
+
|
|
72
|
+
## Use it
|
|
73
|
+
|
|
74
|
+
### React
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { GLView, type IExpoWebGLRenderingContext } from '@symbiote-native/gl/react';
|
|
78
|
+
|
|
79
|
+
function onContextCreate(gl: IExpoWebGLRenderingContext) {
|
|
80
|
+
gl.clearColor(0, 1, 1, 1);
|
|
81
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
82
|
+
gl.endFrameEXP();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
<GLView style={{ flex: 1 }} onContextCreate={onContextCreate} />;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Vue
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
import { GLView, type IExpoWebGLRenderingContext } from '@symbiote-native/gl/vue';
|
|
93
|
+
|
|
94
|
+
function onContextCreate(gl: IExpoWebGLRenderingContext) {
|
|
95
|
+
gl.clearColor(0, 1, 1, 1);
|
|
96
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
97
|
+
gl.endFrameEXP();
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<template>
|
|
102
|
+
<GLView :style="{ flex: 1 }" @contextCreate="onContextCreate" />
|
|
103
|
+
</template>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Angular
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { Component } from '@angular/core';
|
|
110
|
+
import { GLView, type IExpoWebGLRenderingContext } from '@symbiote-native/gl/angular';
|
|
111
|
+
|
|
112
|
+
@Component({
|
|
113
|
+
standalone: true,
|
|
114
|
+
imports: [GLView],
|
|
115
|
+
template: `<GLView [style]="{ flex: 1 }" [onContextCreate]="onContextCreate" />`,
|
|
116
|
+
})
|
|
117
|
+
export class Surface {
|
|
118
|
+
readonly onContextCreate = (gl: IExpoWebGLRenderingContext): void => {
|
|
119
|
+
gl.clearColor(0, 1, 1, 1);
|
|
120
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
121
|
+
gl.endFrameEXP();
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Svelte
|
|
127
|
+
|
|
128
|
+
```svelte
|
|
129
|
+
<script lang="ts">
|
|
130
|
+
import { GLView, type IExpoWebGLRenderingContext } from '@symbiote-native/gl/svelte';
|
|
131
|
+
|
|
132
|
+
function onContextCreate(gl: IExpoWebGLRenderingContext) {
|
|
133
|
+
gl.clearColor(0, 1, 1, 1);
|
|
134
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
135
|
+
gl.endFrameEXP();
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<GLView style={{ flex: 1 }} {onContextCreate} />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Solid
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { GLView, type IExpoWebGLRenderingContext } from '@symbiote-native/gl/solid';
|
|
146
|
+
|
|
147
|
+
function onContextCreate(gl: IExpoWebGLRenderingContext) {
|
|
148
|
+
gl.clearColor(0, 1, 1, 1);
|
|
149
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
150
|
+
gl.endFrameEXP();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
<GLView style={{ flex: 1 }} onContextCreate={onContextCreate} />;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## API
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
<GLView onContextCreate={(gl: IExpoWebGLRenderingContext) => void} msaaSamples? enableExperimentalWorkletSupport? style? />
|
|
160
|
+
// gl is a WebGL2 context, call gl.endFrameEXP() at the end of every frame
|
|
161
|
+
// handle: exglCtxId, createCameraTextureAsync(camera), destroyObjectAsync(texture), takeSnapshotAsync(options?)
|
|
162
|
+
|
|
163
|
+
createContextAsync(): Promise<IExpoWebGLRenderingContext> // headless
|
|
164
|
+
destroyContextAsync(exgl?: IExpoWebGLRenderingContext | number): Promise<boolean>
|
|
165
|
+
takeSnapshotAsync(exgl?: IExpoWebGLRenderingContext | number, options?): Promise<IGLSnapshot>
|
|
166
|
+
getWorkletContext(contextId: number): IExpoWebGLRenderingContext | undefined // inside a worklet
|
|
167
|
+
GLLoggingOption gl.__expoSetLogging(option) // prints the GL calls with console.warn
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Ported from upstream's `GLView.tsx` and `GLView.types.ts`.
|
|
171
|
+
|
|
172
|
+
## Notes
|
|
173
|
+
|
|
174
|
+
- **Nothing reaches the screen until `endFrameEXP()`.** Call it at the end of every frame, including
|
|
175
|
+
the first. The drawing buffer is presented only then.
|
|
176
|
+
- **`onContextCreate` runs once per surface.** Start your loop in it and stop it when the view
|
|
177
|
+
unmounts. The package forgets the context of the view when it unmounts.
|
|
178
|
+
- **Texture images must be files on disk.** `gl.texImage2D` reads a file path, so load a bundled image
|
|
179
|
+
with [`@symbiote-native/asset`](/docs/packages/asset/) and pass its `localUri` after
|
|
180
|
+
`downloadAsync()` resolves.
|
|
181
|
+
- **Reanimated stays optional.** `getWorkletContext` and `enableExperimentalWorkletSupport` need
|
|
182
|
+
`react-native-reanimated` in your app, the package does not depend on it.
|
|
183
|
+
- **Logging is printed with `console.warn`.** `gl.__expoSetLogging(option)` prints every GL call, which
|
|
184
|
+
is slow, so switch it off once you have the answer.
|
|
185
|
+
- **Not ported.** The web build, the `nativeRef_EXPERIMENTAL` prop (hidden upstream) and upstream's one test, which is skipped there.
|
|
186
|
+
- **Not ported.** The web build, the hidden `nativeRef_EXPERIMENTAL` prop and upstream's one test (skipped there).
|
|
187
|
+
|
|
188
|
+
## Common questions
|
|
189
|
+
|
|
190
|
+
- **Black view.** Call `gl.endFrameEXP()` after drawing, then check
|
|
191
|
+
`gl.getShaderInfoLog(shader)` and `gl.getProgramInfoLog(program)`: a failed program draws nothing
|
|
192
|
+
without throwing.
|
|
193
|
+
- **Stutter.** One animated scene at a time (they share a GL thread), judge on a device (the iOS
|
|
194
|
+
Simulator renders in software), `msaaSamples={0}` and 30 fps for a full-screen shader, and
|
|
195
|
+
`precision highp float` in animated fragment shaders.
|
|
196
|
+
- **Crash in the background on iOS.** GL calls are forbidden once the app is inactive. Stop the loop
|
|
197
|
+
when `AppState` leaves `active`.
|
|
198
|
+
- **Blank texture in a release build.** Pass the asset's `localUri` after `await asset.downloadAsync()`
|
|
199
|
+
(`@symbiote-native/asset`), a not yet downloaded asset reports a name instead of a path.
|
|
200
|
+
- **Reanimated worklets.** Experimental: `enableExperimentalWorkletSupport` and
|
|
201
|
+
`getWorkletContext(gl.contextId)`, needs `react-native-reanimated` in the app.
|
|
202
|
+
- **Camera texture memory.** Call `destroyObjectAsync(texture)` when done.
|
|
203
|
+
|
|
204
|
+
Sources: [Expo docs: GLView](https://docs.expo.dev/versions/latest/sdk/gl-view/),
|
|
205
|
+
[expo/expo#7367](https://github.com/expo/expo/issues/7367),
|
|
206
|
+
[expo/expo#18483](https://github.com/expo/expo/issues/18483),
|
|
207
|
+
[expo/expo#2693](https://github.com/expo/expo/issues/2693),
|
|
208
|
+
[expo/expo#11267](https://github.com/expo/expo/issues/11267),
|
|
209
|
+
[expo/expo#12468](https://github.com/expo/expo/issues/12468).
|
|
210
|
+
|
|
211
|
+
## Test it
|
|
212
|
+
|
|
213
|
+
No device is needed for the logic. The core tests (`src/core/*.test.ts`) replace `expo-modules-core`
|
|
214
|
+
(`requireNativeViewManager`, `Platform`) and assert the view and the context bookkeeping. The
|
|
215
|
+
adapter tests (`src/{react,vue,solid,angular}/**/*.test.*`, `vitest`) render the real component over the
|
|
216
|
+
recording Fabric with an injected view config and assert the committed payload, and Svelte's
|
|
217
|
+
`*.smoke.test.ts` compile the `.svelte` files and mount them. Painting itself is verified on a device in
|
|
218
|
+
the six `examples/expo-*` canary apps (`examples/expo-react`, `examples/expo-vue-sfc`,
|
|
219
|
+
`examples/expo-vue-tsx`, `examples/expo-svelte`, `examples/expo-solid`, `examples/expo-angular`), see the
|
|
220
|
+
parent [README](../../README.md).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NativeViewBase } from '@symbiote-native/angular';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
import type { IGLViewProps } from '../../core';
|
|
4
|
+
declare const GL_VIEW_INPUTS: readonly ["onContextCreate", "msaaSamples", "enableExperimentalWorkletSupport"];
|
|
5
|
+
export interface GLView extends Partial<Pick<IGLViewProps, (typeof GL_VIEW_INPUTS)[number]>> {
|
|
6
|
+
}
|
|
7
|
+
/** Angular twin of `expo-gl`'s `GLView` */
|
|
8
|
+
export declare class GLView extends NativeViewBase {
|
|
9
|
+
private readonly view;
|
|
10
|
+
readonly createCameraTextureAsync: (camera: import("..").IGLNodeOrTag) => Promise<WebGLTexture>;
|
|
11
|
+
readonly destroyObjectAsync: (glObject: import("..").IGLObject) => Promise<boolean>;
|
|
12
|
+
readonly takeSnapshotAsync: (options?: import("..").IGLSnapshotOptions) => Promise<import("..").IGLSnapshot>;
|
|
13
|
+
get exglCtxId(): number | undefined;
|
|
14
|
+
protected readonly propNames: readonly ["onContextCreate", "msaaSamples", "enableExperimentalWorkletSupport"];
|
|
15
|
+
protected renderView(props: object): IDescriptor | null;
|
|
16
|
+
protected disposeView(): void;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
36
|
+
import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
|
|
37
|
+
import { createGLView } from '../../core/index.js';
|
|
38
|
+
// One list is the inputs of the component and the props it hands to the native view
|
|
39
|
+
const GL_VIEW_INPUTS = [
|
|
40
|
+
'onContextCreate',
|
|
41
|
+
'msaaSamples',
|
|
42
|
+
'enableExperimentalWorkletSupport',
|
|
43
|
+
];
|
|
44
|
+
/** Angular twin of `expo-gl`'s `GLView` */
|
|
45
|
+
let GLView = (() => {
|
|
46
|
+
let _classDecorators = [Component({
|
|
47
|
+
selector: 'GLView',
|
|
48
|
+
standalone: true,
|
|
49
|
+
imports: [DescriptorOutlet],
|
|
50
|
+
inputs: [...GL_VIEW_INPUTS],
|
|
51
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
52
|
+
template: `@if (descriptor; as node) {
|
|
53
|
+
<symbiote-descriptor-outlet [node]="node" />
|
|
54
|
+
}`,
|
|
55
|
+
})];
|
|
56
|
+
let _classDescriptor;
|
|
57
|
+
let _classExtraInitializers = [];
|
|
58
|
+
let _classThis;
|
|
59
|
+
let _classSuper = NativeViewBase;
|
|
60
|
+
var GLView = class extends _classSuper {
|
|
61
|
+
static { _classThis = this; }
|
|
62
|
+
static {
|
|
63
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
64
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
65
|
+
GLView = _classThis = _classDescriptor.value;
|
|
66
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
67
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
68
|
+
}
|
|
69
|
+
view = createGLView(() => this.hostNode());
|
|
70
|
+
// The functions of the view are members of the component, a `@ViewChild` reaches them
|
|
71
|
+
createCameraTextureAsync = this.view.handle.createCameraTextureAsync;
|
|
72
|
+
destroyObjectAsync = this.view.handle.destroyObjectAsync;
|
|
73
|
+
takeSnapshotAsync = this.view.handle.takeSnapshotAsync;
|
|
74
|
+
get exglCtxId() {
|
|
75
|
+
return this.view.handle.exglCtxId;
|
|
76
|
+
}
|
|
77
|
+
propNames = GL_VIEW_INPUTS;
|
|
78
|
+
renderView(props) {
|
|
79
|
+
return this.view.render(props);
|
|
80
|
+
}
|
|
81
|
+
disposeView() {
|
|
82
|
+
this.view.dispose();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return GLView = _classThis;
|
|
86
|
+
})();
|
|
87
|
+
export { GLView };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IExpoWebGLRenderingContext, IGLSnapshot, IGLSnapshotOptions } from './types';
|
|
2
|
+
/** The context of a worklet, `exgl` has to be created with worklet support */
|
|
3
|
+
export declare function getWorkletContext(contextId: number): IExpoWebGLRenderingContext | undefined;
|
|
4
|
+
export declare function unregisterGLContext(exglCtxId: number): void;
|
|
5
|
+
/** The GL interface of a context id the native side created */
|
|
6
|
+
export declare function getGl(exglCtxId: number): IExpoWebGLRenderingContext;
|
|
7
|
+
export declare function getContextId(exgl?: IExpoWebGLRenderingContext | number): number;
|
|
8
|
+
/** A context with no view, which does not present its framebuffer, take a snapshot to see it */
|
|
9
|
+
export declare function createContextAsync(): Promise<IExpoWebGLRenderingContext>;
|
|
10
|
+
/** For a headless context made by `createContextAsync` */
|
|
11
|
+
export declare function destroyContextAsync(exgl?: IExpoWebGLRenderingContext | number): Promise<boolean>;
|
|
12
|
+
/** Saves the framebuffer as a file in the cache directory of the app */
|
|
13
|
+
export declare function takeSnapshotAsync(exgl?: IExpoWebGLRenderingContext | number, options?: IGLSnapshotOptions): Promise<IGLSnapshot>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CodedError } from 'expo-modules-core';
|
|
2
|
+
import { GL_CONTEXTS_GLOBAL } from './constants.js';
|
|
3
|
+
import { configureLogging } from './gl-utils.js';
|
|
4
|
+
import { glNativeModule } from './native-module.js';
|
|
5
|
+
import { createWorkletContextManager } from './worklet-context-manager.js';
|
|
6
|
+
const workletContextManager = createWorkletContextManager();
|
|
7
|
+
function isContext(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null;
|
|
9
|
+
}
|
|
10
|
+
/** The context of a worklet, `exgl` has to be created with worklet support */
|
|
11
|
+
export function getWorkletContext(contextId) {
|
|
12
|
+
'worklet';
|
|
13
|
+
return workletContextManager.getContext(contextId);
|
|
14
|
+
}
|
|
15
|
+
export function unregisterGLContext(exglCtxId) {
|
|
16
|
+
const contexts = Reflect.get(globalThis, GL_CONTEXTS_GLOBAL);
|
|
17
|
+
if (contexts)
|
|
18
|
+
Reflect.deleteProperty(Object(contexts), String(exglCtxId));
|
|
19
|
+
workletContextManager.unregister?.(exglCtxId);
|
|
20
|
+
}
|
|
21
|
+
/** The GL interface of a context id the native side created */
|
|
22
|
+
export function getGl(exglCtxId) {
|
|
23
|
+
const contexts = Reflect.get(globalThis, GL_CONTEXTS_GLOBAL);
|
|
24
|
+
if (!contexts) {
|
|
25
|
+
throw new CodedError('ERR_GL_NOT_AVAILABLE', 'GL is currently not available. (Have you enabled remote debugging? GL is not available while debugging remotely.)');
|
|
26
|
+
}
|
|
27
|
+
const gl = Reflect.get(Object(contexts), String(exglCtxId));
|
|
28
|
+
if (!isContext(gl))
|
|
29
|
+
throw new Error(`There is no EXGLContext with id ${exglCtxId}`);
|
|
30
|
+
configureLogging(gl);
|
|
31
|
+
return gl;
|
|
32
|
+
}
|
|
33
|
+
export function getContextId(exgl) {
|
|
34
|
+
const exglCtxId = exgl && typeof exgl === 'object' ? exgl.contextId : exgl;
|
|
35
|
+
if (!exglCtxId || typeof exglCtxId !== 'number') {
|
|
36
|
+
throw new Error(`Invalid EXGLContext id: ${String(exglCtxId)}`);
|
|
37
|
+
}
|
|
38
|
+
return exglCtxId;
|
|
39
|
+
}
|
|
40
|
+
/** A context with no view, which does not present its framebuffer, take a snapshot to see it */
|
|
41
|
+
export async function createContextAsync() {
|
|
42
|
+
const { exglCtxId } = await glNativeModule.createContextAsync();
|
|
43
|
+
return getGl(exglCtxId);
|
|
44
|
+
}
|
|
45
|
+
/** For a headless context made by `createContextAsync` */
|
|
46
|
+
export async function destroyContextAsync(exgl) {
|
|
47
|
+
const exglCtxId = getContextId(exgl);
|
|
48
|
+
unregisterGLContext(exglCtxId);
|
|
49
|
+
return glNativeModule.destroyContextAsync(exglCtxId);
|
|
50
|
+
}
|
|
51
|
+
/** Saves the framebuffer as a file in the cache directory of the app */
|
|
52
|
+
export async function takeSnapshotAsync(exgl, options = {}) {
|
|
53
|
+
return glNativeModule.takeSnapshotAsync(getContextId(exgl), options);
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GL_ERRORS: Readonly<Record<number, string>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const GL_ERRORS = {
|
|
2
|
+
1280: 'INVALID ENUM: An unacceptable value has been specified for an enumerated argument.',
|
|
3
|
+
1281: 'INVALID_VALUE: A numeric argument is out of range.',
|
|
4
|
+
1282: 'INVALID_OPERATION: The specified command is not allowed for the current state.',
|
|
5
|
+
1285: 'OUT_OF_MEMORY: Not enough memory is left to execute the command.',
|
|
6
|
+
1286: 'INVALID_FRAMEBUFFER_OPERATION: The currently bound framebuffer is not complete when trying to render to or to read from it.',
|
|
7
|
+
37442: 'CONTEXT_LOST_WEBGL: The WebGL context is lost.',
|
|
8
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExpoWebGLRenderingContext } from './types';
|
|
2
|
+
type ILoggableGL = Pick<IExpoWebGLRenderingContext, '__expoSetLogging'>;
|
|
3
|
+
/** Adds `__expoSetLogging` to the context, for the logging options useful in debugging GL calls */
|
|
4
|
+
export declare function configureLogging(gl: ILoggableGL): void;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { GL_ERRORS } from './gl-errors.js';
|
|
2
|
+
import { GLLoggingOption } from './types.js';
|
|
3
|
+
const MAX_STRING_LENGTH = 20;
|
|
4
|
+
const SET_LOGGING_KEY = '__expoSetLogging';
|
|
5
|
+
const wrappers = new WeakSet();
|
|
6
|
+
function isGLFunction(value) {
|
|
7
|
+
return typeof value === 'function';
|
|
8
|
+
}
|
|
9
|
+
function constantNameOf(gl, value) {
|
|
10
|
+
for (const name in gl) {
|
|
11
|
+
if (Reflect.get(gl, name) === value)
|
|
12
|
+
return name;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
// Numbers get the name of a constant that holds them, which can mislead for a size, so the
|
|
17
|
+
// number stays. Strings are cut because a shader is long and logging it blocks the bridge
|
|
18
|
+
function describeArg(gl, option, arg) {
|
|
19
|
+
if (option & GLLoggingOption.RESOLVE_CONSTANTS && typeof arg === 'number') {
|
|
20
|
+
const name = constantNameOf(gl, arg);
|
|
21
|
+
if (name)
|
|
22
|
+
return `${arg} (${name})`;
|
|
23
|
+
}
|
|
24
|
+
const isLong = typeof arg === 'string' && arg.length > MAX_STRING_LENGTH;
|
|
25
|
+
if (option & GLLoggingOption.TRUNCATE_STRINGS && isLong) {
|
|
26
|
+
const lastSpace = arg.lastIndexOf(' ', MAX_STRING_LENGTH);
|
|
27
|
+
return `${arg.slice(0, lastSpace >= 0 ? lastSpace : MAX_STRING_LENGTH)}...`;
|
|
28
|
+
}
|
|
29
|
+
return String(arg);
|
|
30
|
+
}
|
|
31
|
+
// The original `getError`, a wrapped one would log itself
|
|
32
|
+
function reportError(gl) {
|
|
33
|
+
const getError = Reflect.get(Reflect.getPrototypeOf(gl) ?? {}, 'getError');
|
|
34
|
+
if (!isGLFunction(getError))
|
|
35
|
+
return;
|
|
36
|
+
const error = getError.call(gl);
|
|
37
|
+
if (typeof error !== 'number' || error === Reflect.get(gl, 'NO_ERROR'))
|
|
38
|
+
return;
|
|
39
|
+
console.warn(`\x1b[31mExpoGL: Error ${GL_ERRORS[error]}\x1b[0m`);
|
|
40
|
+
}
|
|
41
|
+
function wrapMethod(gl, key, original, getOption) {
|
|
42
|
+
const wrapper = (...args) => {
|
|
43
|
+
const option = getOption();
|
|
44
|
+
if (option & GLLoggingOption.METHOD_CALLS) {
|
|
45
|
+
const params = args.map(arg => describeArg(gl, option, arg));
|
|
46
|
+
console.warn(`ExpoGL: ${key}(${params.join(', ')})`);
|
|
47
|
+
}
|
|
48
|
+
const result = original.apply(gl, args);
|
|
49
|
+
if (option & GLLoggingOption.METHOD_CALLS)
|
|
50
|
+
console.warn(`ExpoGL: = ${String(result)}`);
|
|
51
|
+
if (option & GLLoggingOption.GET_ERRORS && key !== 'getError')
|
|
52
|
+
reportError(gl);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
wrappers.add(wrapper);
|
|
56
|
+
Reflect.set(gl, key, wrapper);
|
|
57
|
+
}
|
|
58
|
+
function wrapMethods(gl, getOption) {
|
|
59
|
+
for (const [key, original] of Object.entries(Reflect.getPrototypeOf(gl) ?? {})) {
|
|
60
|
+
if (isGLFunction(original) && key !== SET_LOGGING_KEY) {
|
|
61
|
+
wrapMethod(gl, key, original, getOption);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function unwrapMethods(gl) {
|
|
66
|
+
for (const [key, value] of Object.entries(gl)) {
|
|
67
|
+
if (isGLFunction(value) && wrappers.has(value))
|
|
68
|
+
Reflect.deleteProperty(gl, key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Adds `__expoSetLogging` to the context, for the logging options useful in debugging GL calls */
|
|
72
|
+
export function configureLogging(gl) {
|
|
73
|
+
let loggingOption = GLLoggingOption.DISABLED;
|
|
74
|
+
gl.__expoSetLogging = (option) => {
|
|
75
|
+
// Both on or both off needs no wrapping or unwrapping, only the new option
|
|
76
|
+
if (!loggingOption !== !option) {
|
|
77
|
+
if (option)
|
|
78
|
+
wrapMethods(gl, () => loggingOption);
|
|
79
|
+
else
|
|
80
|
+
unwrapMethods(gl);
|
|
81
|
+
}
|
|
82
|
+
loggingOption = option;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
2
|
+
import type { ISymbioteNode } from '@symbiote-native/engine';
|
|
3
|
+
import type { IGLViewHandle } from './types';
|
|
4
|
+
export declare const glViewName: () => string;
|
|
5
|
+
export declare const ensureGLViewRegistered: () => boolean;
|
|
6
|
+
export type IGLView = {
|
|
7
|
+
handle: IGLViewHandle;
|
|
8
|
+
/** `null` means the view cannot register and the caller renders nothing */
|
|
9
|
+
render(props: object): IDescriptor | null;
|
|
10
|
+
/** Forgets the context of the surface, called when the view unmounts */
|
|
11
|
+
dispose(): void;
|
|
12
|
+
};
|
|
13
|
+
type IGetNode = () => ISymbioteNode | null | undefined;
|
|
14
|
+
/** One per mounted view, `getNode` is kept for the shape the adapters share */
|
|
15
|
+
export declare function createGLView(_getNode: IGetNode): IGLView;
|
|
16
|
+
export {};
|