@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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -2
  3. package/build/angular/gl-view/index.d.ts +18 -0
  4. package/build/angular/gl-view/index.js +87 -0
  5. package/build/angular/index.d.ts +2 -0
  6. package/build/angular/index.js +3 -0
  7. package/build/core/constants.d.ts +4 -0
  8. package/build/core/constants.js +4 -0
  9. package/build/core/gl-context.d.ts +13 -0
  10. package/build/core/gl-context.js +54 -0
  11. package/build/core/gl-errors.d.ts +1 -0
  12. package/build/core/gl-errors.js +8 -0
  13. package/build/core/gl-utils.d.ts +5 -0
  14. package/build/core/gl-utils.js +84 -0
  15. package/build/core/gl-view.d.ts +16 -0
  16. package/build/core/gl-view.js +107 -0
  17. package/build/core/index.d.ts +6 -0
  18. package/build/core/index.js +5 -0
  19. package/build/core/native-module.d.ts +15 -0
  20. package/build/core/native-module.js +3 -0
  21. package/build/core/types.d.ts +75 -0
  22. package/build/core/types.js +13 -0
  23. package/build/core/worklet-context-manager.d.ts +6 -0
  24. package/build/core/worklet-context-manager.js +35 -0
  25. package/build/react/gl-view.d.ts +8 -0
  26. package/build/react/gl-view.js +6 -0
  27. package/build/react/index.d.ts +3 -0
  28. package/build/react/index.js +3 -0
  29. package/build/solid/gl-view.d.ts +13 -0
  30. package/build/solid/gl-view.js +4 -0
  31. package/build/solid/index.d.ts +2 -0
  32. package/build/solid/index.js +3 -0
  33. package/build/svelte/gl-view.svelte +19 -0
  34. package/build/svelte/gl-view.svelte.d.ts +9 -0
  35. package/build/svelte/index.d.ts +2 -0
  36. package/build/svelte/index.js +3 -0
  37. package/build/vue/gl-view.d.ts +2 -0
  38. package/build/vue/gl-view.js +4 -0
  39. package/build/vue/index.d.ts +2 -0
  40. package/build/vue/index.js +3 -0
  41. package/build-ngc/angular/gl-view/index.d.ts +21 -0
  42. package/build-ngc/angular/gl-view/index.js +46 -0
  43. package/build-ngc/angular/gl-view/index.js.map +1 -0
  44. package/build-ngc/angular/index.d.ts +2 -0
  45. package/build-ngc/angular/index.js +4 -0
  46. package/build-ngc/angular/index.js.map +1 -0
  47. package/build-ngc/core/constants.d.ts +4 -0
  48. package/build-ngc/core/constants.js +5 -0
  49. package/build-ngc/core/constants.js.map +1 -0
  50. package/build-ngc/core/gl-context.d.ts +13 -0
  51. package/build-ngc/core/gl-context.js +55 -0
  52. package/build-ngc/core/gl-context.js.map +1 -0
  53. package/build-ngc/core/gl-errors.d.ts +1 -0
  54. package/build-ngc/core/gl-errors.js +9 -0
  55. package/build-ngc/core/gl-errors.js.map +1 -0
  56. package/build-ngc/core/gl-utils.d.ts +5 -0
  57. package/build-ngc/core/gl-utils.js +85 -0
  58. package/build-ngc/core/gl-utils.js.map +1 -0
  59. package/build-ngc/core/gl-view.d.ts +16 -0
  60. package/build-ngc/core/gl-view.js +108 -0
  61. package/build-ngc/core/gl-view.js.map +1 -0
  62. package/build-ngc/core/index.d.ts +6 -0
  63. package/build-ngc/core/index.js +6 -0
  64. package/build-ngc/core/index.js.map +1 -0
  65. package/build-ngc/core/native-module.d.ts +15 -0
  66. package/build-ngc/core/native-module.js +4 -0
  67. package/build-ngc/core/native-module.js.map +1 -0
  68. package/build-ngc/core/types.d.ts +75 -0
  69. package/build-ngc/core/types.js +14 -0
  70. package/build-ngc/core/types.js.map +1 -0
  71. package/build-ngc/core/worklet-context-manager.d.ts +6 -0
  72. package/build-ngc/core/worklet-context-manager.js +36 -0
  73. package/build-ngc/core/worklet-context-manager.js.map +1 -0
  74. package/native-link.json +12 -0
  75. package/package.json +145 -3
  76. package/src/angular/gl-view/index.ts +51 -0
  77. package/src/angular/index.ts +4 -0
  78. package/src/core/constants.ts +6 -0
  79. package/src/core/gl-context.ts +79 -0
  80. package/src/core/gl-errors.ts +8 -0
  81. package/src/core/gl-utils.ts +106 -0
  82. package/src/core/gl-view.ts +160 -0
  83. package/src/core/index.ts +26 -0
  84. package/src/core/native-module.ts +22 -0
  85. package/src/core/types.ts +95 -0
  86. package/src/core/worklet-context-manager.ts +52 -0
  87. package/src/react/gl-view.ts +17 -0
  88. package/src/react/index.ts +5 -0
  89. package/src/solid/gl-view.ts +8 -0
  90. package/src/solid/index.ts +4 -0
  91. package/src/svelte/gl-view.svelte +19 -0
  92. package/src/svelte/index.ts +4 -0
  93. package/src/svelte/svelte-ambient.d.ts +3 -0
  94. package/src/svelte/svelte-compile.test-helper.ts +6 -0
  95. package/src/vue/gl-view.ts +5 -0
  96. 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
- # Temporary Holding Version
1
+ # @symbiote-native/gl
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,2 @@
1
+ export { GLView } from './gl-view';
2
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // @symbiote-native/gl/angular: a GLView that gives a WebGL context to draw into, on a shared core
2
+ export { GLView } from './gl-view/index.js';
3
+ export * from '../core/index.js';
@@ -0,0 +1,4 @@
1
+ export declare const GL_MODULE_NAME = "ExpoGL";
2
+ export declare const GL_PACKAGE_NAME = "expo-gl";
3
+ /** Where the native module keeps the contexts it installs on the runtime, by id */
4
+ export declare const GL_CONTEXTS_GLOBAL = "__EXGLContexts";
@@ -0,0 +1,4 @@
1
+ export const GL_MODULE_NAME = 'ExpoGL';
2
+ export const GL_PACKAGE_NAME = 'expo-gl';
3
+ /** Where the native module keeps the contexts it installs on the runtime, by id */
4
+ export const GL_CONTEXTS_GLOBAL = '__EXGLContexts';
@@ -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 {};