UNPKG

7.25 kBJavaScriptView Raw
1import { CodedError, Platform, UnavailabilityError } from '@unimodules/core';
2import invariant from 'invariant';
3import * as React from 'react';
4import { Dimensions } from 'react-native';
5import Canvas from './Canvas';
6function getImageForAsset(asset) {
7 if (asset != null && typeof asset === 'object' && asset !== null && asset.downloadAsync) {
8 const dataURI = asset.localUri || asset.uri || '';
9 const image = new Image();
10 image.src = dataURI;
11 return image;
12 }
13 return asset;
14}
15function isOffscreenCanvas(element) {
16 return element && typeof element.convertToBlob === 'function';
17}
18function asExpoContext(gl) {
19 gl.endFrameEXP = function glEndFrameEXP() { };
20 if (!gl['_expo_texImage2D']) {
21 gl['_expo_texImage2D'] = gl.texImage2D;
22 gl.texImage2D = (...props) => {
23 const nextProps = [...props];
24 nextProps.push(getImageForAsset(nextProps.pop()));
25 return gl['_expo_texImage2D'](...nextProps);
26 };
27 }
28 if (!gl['_expo_texSubImage2D']) {
29 gl['_expo_texSubImage2D'] = gl.texSubImage2D;
30 gl.texSubImage2D = (...props) => {
31 const nextProps = [...props];
32 nextProps.push(getImageForAsset(nextProps.pop()));
33 return gl['_expo_texSubImage2D'](...nextProps);
34 };
35 }
36 return gl;
37}
38function ensureContext(canvas, contextAttributes) {
39 if (!canvas) {
40 throw new CodedError('ERR_GL_INVALID', 'Attempting to use the GL context before it has been created.');
41 }
42 // Apple disables WebGL 2.0 and doesn't provide any way to detect if it's disabled.
43 const isIOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
44 const context = (!isIOS && canvas.getContext('webgl2', contextAttributes)) ||
45 canvas.getContext('webgl', contextAttributes) ||
46 canvas.getContext('webgl-experimental', contextAttributes) ||
47 canvas.getContext('experimental-webgl', contextAttributes);
48 invariant(context, 'Browser does not support WebGL');
49 return asExpoContext(context);
50}
51async function getBlobFromWebGLRenderingContext(gl, options = {}) {
52 invariant(gl, 'getBlobFromWebGLRenderingContext(): WebGL Rendering Context is not defined');
53 const { canvas } = gl;
54 let blob = null;
55 if (typeof canvas.msToBlob === 'function') {
56 // @ts-ignore: polyfill: https://stackoverflow.com/a/29815058/4047926
57 blob = await canvas.msToBlob();
58 }
59 else if (isOffscreenCanvas(canvas)) {
60 blob = await canvas.convertToBlob({ quality: options.compress, type: options.format });
61 }
62 else {
63 blob = await new Promise(resolve => {
64 canvas.toBlob((blob) => resolve(blob), options.format, options.compress);
65 });
66 }
67 return {
68 blob,
69 width: canvas.width,
70 height: canvas.height,
71 };
72}
73export class GLView extends React.Component {
74 constructor() {
75 super(...arguments);
76 this.onContextLost = (event) => {
77 if (event && event.preventDefault) {
78 event.preventDefault();
79 }
80 this.gl = undefined;
81 if (typeof this.props.onContextLost === 'function') {
82 this.props.onContextLost();
83 }
84 };
85 this.onContextRestored = () => {
86 this.gl = undefined;
87 if (this.getGLContext() == null) {
88 throw new CodedError('ERR_GL_INVALID', 'Failed to restore GL context.');
89 }
90 };
91 this.setCanvasRef = (canvas) => {
92 this.canvas = canvas;
93 if (typeof this.props.nativeRef_EXPERIMENTAL === 'function') {
94 this.props.nativeRef_EXPERIMENTAL(canvas);
95 }
96 if (this.canvas) {
97 this.canvas.addEventListener('webglcontextlost', this.onContextLost);
98 this.canvas.addEventListener('webglcontextrestored', this.onContextRestored);
99 this.getGLContext();
100 }
101 };
102 }
103 static async createContextAsync() {
104 if (!Platform.isDOMAvailable) {
105 return null;
106 }
107 const canvas = document.createElement('canvas');
108 const { width, height, scale } = Dimensions.get('window');
109 canvas.width = width * scale;
110 canvas.height = height * scale;
111 return ensureContext(canvas);
112 }
113 static async destroyContextAsync(exgl) {
114 // Do nothing
115 return true;
116 }
117 static async takeSnapshotAsync(gl, options = {}) {
118 const { blob, width, height } = await getBlobFromWebGLRenderingContext(gl, options);
119 if (!blob) {
120 throw new CodedError('ERR_GL_SNAPSHOT', 'Failed to save the GL context');
121 }
122 return {
123 uri: blob,
124 localUri: '',
125 width,
126 height,
127 };
128 }
129 componentWillUnmount() {
130 if (this.gl) {
131 const loseContextExt = this.gl.getExtension('WEBGL_lose_context');
132 if (loseContextExt) {
133 loseContextExt.loseContext();
134 }
135 this.gl = undefined;
136 }
137 if (this.canvas) {
138 this.canvas.removeEventListener('webglcontextlost', this.onContextLost);
139 this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored);
140 }
141 }
142 render() {
143 const { onContextCreate, onContextRestored, onContextLost, webglContextAttributes, msaaSamples, nativeRef_EXPERIMENTAL,
144 // @ts-ignore: ref does not exist
145 ref, ...domProps } = this.props;
146 return React.createElement(Canvas, Object.assign({}, domProps, { canvasRef: this.setCanvasRef }));
147 }
148 componentDidUpdate(prevProps) {
149 const { webglContextAttributes } = this.props;
150 if (this.canvas && webglContextAttributes !== prevProps.webglContextAttributes) {
151 this.onContextLost(null);
152 this.onContextRestored();
153 }
154 }
155 getGLContextOrReject() {
156 const gl = this.getGLContext();
157 if (!gl) {
158 throw new CodedError('ERR_GL_INVALID', 'Attempting to use the GL context before it has been created.');
159 }
160 return gl;
161 }
162 getGLContext() {
163 if (this.gl)
164 return this.gl;
165 if (this.canvas) {
166 this.gl = ensureContext(this.canvas, this.props.webglContextAttributes);
167 if (typeof this.props.onContextCreate === 'function') {
168 this.props.onContextCreate(this.gl);
169 }
170 return this.gl;
171 }
172 return null;
173 }
174 async takeSnapshotAsync(options = {}) {
175 if (!GLView.takeSnapshotAsync) {
176 throw new UnavailabilityError('expo-gl', 'takeSnapshotAsync');
177 }
178 const gl = this.getGLContextOrReject();
179 return await GLView.takeSnapshotAsync(gl, options);
180 }
181 async startARSessionAsync() {
182 throw new UnavailabilityError('GLView', 'startARSessionAsync');
183 }
184 async createCameraTextureAsync() {
185 throw new UnavailabilityError('GLView', 'createCameraTextureAsync');
186 }
187 async destroyObjectAsync(glObject) {
188 throw new UnavailabilityError('GLView', 'destroyObjectAsync');
189 }
190}
191//# sourceMappingURL=GLView.web.js.map
\No newline at end of file