1 | import { CodedError, Platform, UnavailabilityError } from '@unimodules/core';
|
2 | import invariant from 'invariant';
|
3 | import * as React from 'react';
|
4 | import { Dimensions } from 'react-native';
|
5 | import Canvas from './Canvas';
|
6 | function 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 | }
|
15 | function isOffscreenCanvas(element) {
|
16 | return element && typeof element.convertToBlob === 'function';
|
17 | }
|
18 | function 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 | }
|
38 | function 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 |
|
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 | }
|
51 | async 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 |
|
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 | }
|
73 | export 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 |
|
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 |
|
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 |
|
\ | No newline at end of file |