xref: /expo/packages/expo-gl/build/GLView.js (revision b3a89280)
1import { NativeModulesProxy, UnavailabilityError, requireNativeModule, requireNativeViewManager, CodedError, } from 'expo-modules-core';
2import * as React from 'react';
3import { Platform, View, findNodeHandle } from 'react-native';
4import { configureLogging } from './GLUtils';
5import { createWorkletContextManager } from './GLWorkletContextManager';
6const ExponentGLObjectManager = requireNativeModule('ExponentGLObjectManager');
7const { ExponentGLViewManager } = NativeModulesProxy;
8const NativeView = requireNativeViewManager('ExponentGLView');
9const workletContextManager = createWorkletContextManager();
10// @needsAudit
11/**
12 * A View that acts as an OpenGL ES render target. On mounting, an OpenGL ES context is created.
13 * Its drawing buffer is presented as the contents of the View every frame.
14 */
15export class GLView extends React.Component {
16    static NativeView;
17    static defaultProps = {
18        msaaSamples: 4,
19        enableExperimentalWorkletSupport: false,
20    };
21    /**
22     * Imperative API that creates headless context which is devoid of underlying view.
23     * It's useful for headless rendering or in case you want to keep just one context per application and share it between multiple components.
24     * It is slightly faster than usual context as it doesn't swap framebuffers and doesn't present them on the canvas,
25     * however it may require you to take a snapshot in order to present its results.
26     * Also, keep in mind that you need to set up a viewport and create your own framebuffer and texture that you will be drawing to, before you take a snapshot.
27     * @return A promise that resolves to WebGL context object. See [WebGL API](#webgl-api) for more details.
28     */
29    static async createContextAsync() {
30        const { exglCtxId } = await ExponentGLObjectManager.createContextAsync();
31        return getGl(exglCtxId);
32    }
33    /**
34     * Destroys given context.
35     * @param exgl WebGL context to destroy.
36     * @return A promise that resolves to boolean value that is `true` if given context existed and has been destroyed successfully.
37     */
38    static async destroyContextAsync(exgl) {
39        const exglCtxId = getContextId(exgl);
40        unregisterGLContext(exglCtxId);
41        return ExponentGLObjectManager.destroyContextAsync(exglCtxId);
42    }
43    /**
44     * Takes a snapshot of the framebuffer and saves it as a file to app's cache directory.
45     * @param exgl WebGL context to take a snapshot from.
46     * @param options
47     * @return A promise that resolves to `GLSnapshot` object.
48     */
49    static async takeSnapshotAsync(exgl, options = {}) {
50        const exglCtxId = getContextId(exgl);
51        return ExponentGLObjectManager.takeSnapshotAsync(exglCtxId, options);
52    }
53    static getWorkletContext = workletContextManager.getContext;
54    nativeRef = null;
55    exglCtxId;
56    render() {
57        const { onContextCreate, // eslint-disable-line no-unused-vars
58        msaaSamples, enableExperimentalWorkletSupport, ...viewProps } = this.props;
59        return (React.createElement(View, { ...viewProps },
60            React.createElement(NativeView, { ref: this._setNativeRef, style: {
61                    flex: 1,
62                    ...(Platform.OS === 'ios'
63                        ? {
64                            backgroundColor: 'transparent',
65                        }
66                        : {}),
67                }, onSurfaceCreate: this._onSurfaceCreate, enableExperimentalWorkletSupport: enableExperimentalWorkletSupport, msaaSamples: Platform.OS === 'ios' ? msaaSamples : undefined })));
68    }
69    _setNativeRef = (nativeRef) => {
70        if (this.props.nativeRef_EXPERIMENTAL) {
71            this.props.nativeRef_EXPERIMENTAL(nativeRef);
72        }
73        this.nativeRef = nativeRef;
74    };
75    _onSurfaceCreate = ({ nativeEvent: { exglCtxId } }) => {
76        const gl = getGl(exglCtxId);
77        this.exglCtxId = exglCtxId;
78        if (this.props.onContextCreate) {
79            this.props.onContextCreate(gl);
80        }
81    };
82    componentWillUnmount() {
83        if (this.exglCtxId) {
84            unregisterGLContext(this.exglCtxId);
85        }
86    }
87    componentDidUpdate(prevProps) {
88        if (this.props.enableExperimentalWorkletSupport !== prevProps.enableExperimentalWorkletSupport) {
89            console.warn('Updating prop enableExperimentalWorkletSupport is not supported');
90        }
91    }
92    // @docsMissing
93    async startARSessionAsync() {
94        if (!ExponentGLViewManager.startARSessionAsync) {
95            throw new UnavailabilityError('expo-gl', 'startARSessionAsync');
96        }
97        return await ExponentGLViewManager.startARSessionAsync(findNodeHandle(this.nativeRef));
98    }
99    // @docsMissing
100    async createCameraTextureAsync(cameraRefOrHandle) {
101        if (!ExponentGLObjectManager.createCameraTextureAsync) {
102            throw new UnavailabilityError('expo-gl', 'createCameraTextureAsync');
103        }
104        const { exglCtxId } = this;
105        if (!exglCtxId) {
106            throw new Error("GLView's surface is not created yet!");
107        }
108        const cameraTag = findNodeHandle(cameraRefOrHandle);
109        const { exglObjId } = await ExponentGLObjectManager.createCameraTextureAsync(exglCtxId, cameraTag);
110        return { id: exglObjId };
111    }
112    // @docsMissing
113    async destroyObjectAsync(glObject) {
114        if (!ExponentGLObjectManager.destroyObjectAsync) {
115            throw new UnavailabilityError('expo-gl', 'destroyObjectAsync');
116        }
117        return await ExponentGLObjectManager.destroyObjectAsync(glObject.id);
118    }
119    /**
120     * Same as static [`takeSnapshotAsync()`](#glviewtakesnapshotasyncgl-options),
121     * but uses WebGL context that is associated with the view on which the method is called.
122     * @param options
123     */
124    async takeSnapshotAsync(options = {}) {
125        if (!GLView.takeSnapshotAsync) {
126            throw new UnavailabilityError('expo-gl', 'takeSnapshotAsync');
127        }
128        const { exglCtxId } = this;
129        return await GLView.takeSnapshotAsync(exglCtxId, options);
130    }
131}
132GLView.NativeView = NativeView;
133function unregisterGLContext(exglCtxId) {
134    if (global.__EXGLContexts) {
135        delete global.__EXGLContexts[String(exglCtxId)];
136    }
137    workletContextManager.unregister?.(exglCtxId);
138}
139// Get the GL interface from an EXGLContextId
140const getGl = (exglCtxId) => {
141    if (!global.__EXGLContexts) {
142        throw new CodedError('ERR_GL_NOT_AVAILABLE', 'GL is currently not available. (Have you enabled remote debugging? GL is not available while debugging remotely.)');
143    }
144    const gl = global.__EXGLContexts[String(exglCtxId)];
145    configureLogging(gl);
146    return gl;
147};
148const getContextId = (exgl) => {
149    const exglCtxId = exgl && typeof exgl === 'object' ? exgl.contextId : exgl;
150    if (!exglCtxId || typeof exglCtxId !== 'number') {
151        throw new Error(`Invalid EXGLContext id: ${String(exglCtxId)}`);
152    }
153    return exglCtxId;
154};
155//# sourceMappingURL=GLView.js.map