1import { CodedError, Platform, UnavailabilityError } from 'expo-modules-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.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 canvas; 75 gl; 76 static async createContextAsync() { 77 if (!Platform.isDOMAvailable) { 78 return null; 79 } 80 const canvas = document.createElement('canvas'); 81 const { width, height, scale } = Dimensions.get('window'); 82 canvas.width = width * scale; 83 canvas.height = height * scale; 84 return ensureContext(canvas); 85 } 86 static async destroyContextAsync(exgl) { 87 // Do nothing 88 return true; 89 } 90 static async takeSnapshotAsync(gl, options = {}) { 91 const { blob, width, height } = await getBlobFromWebGLRenderingContext(gl, options); 92 if (!blob) { 93 throw new CodedError('ERR_GL_SNAPSHOT', 'Failed to save the GL context'); 94 } 95 return { 96 uri: blob, 97 localUri: '', 98 width, 99 height, 100 }; 101 } 102 componentWillUnmount() { 103 if (this.gl) { 104 const loseContextExt = this.gl.getExtension('WEBGL_lose_context'); 105 if (loseContextExt) { 106 loseContextExt.loseContext(); 107 } 108 this.gl = undefined; 109 } 110 if (this.canvas) { 111 this.canvas.removeEventListener('webglcontextlost', this.onContextLost); 112 this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored); 113 } 114 } 115 render() { 116 const { onContextCreate, onContextRestored, onContextLost, webglContextAttributes, msaaSamples, nativeRef_EXPERIMENTAL, 117 // @ts-ignore: ref does not exist 118 ref, ...domProps } = this.props; 119 return React.createElement(Canvas, { ...domProps, canvasRef: this.setCanvasRef }); 120 } 121 componentDidUpdate(prevProps) { 122 const { webglContextAttributes } = this.props; 123 if (this.canvas && webglContextAttributes !== prevProps.webglContextAttributes) { 124 this.onContextLost(null); 125 this.onContextRestored(); 126 } 127 } 128 getGLContextOrReject() { 129 const gl = this.getGLContext(); 130 if (!gl) { 131 throw new CodedError('ERR_GL_INVALID', 'Attempting to use the GL context before it has been created.'); 132 } 133 return gl; 134 } 135 onContextLost = (event) => { 136 if (event && event.preventDefault) { 137 event.preventDefault(); 138 } 139 this.gl = undefined; 140 if (typeof this.props.onContextLost === 'function') { 141 this.props.onContextLost(); 142 } 143 }; 144 onContextRestored = () => { 145 this.gl = undefined; 146 if (this.getGLContext() == null) { 147 throw new CodedError('ERR_GL_INVALID', 'Failed to restore GL context.'); 148 } 149 }; 150 getGLContext() { 151 if (this.gl) 152 return this.gl; 153 if (this.canvas) { 154 this.gl = ensureContext(this.canvas, this.props.webglContextAttributes); 155 if (typeof this.props.onContextCreate === 'function') { 156 this.props.onContextCreate(this.gl); 157 } 158 return this.gl; 159 } 160 return null; 161 } 162 setCanvasRef = (canvas) => { 163 this.canvas = canvas; 164 if (typeof this.props.nativeRef_EXPERIMENTAL === 'function') { 165 this.props.nativeRef_EXPERIMENTAL(canvas); 166 } 167 if (this.canvas) { 168 this.canvas.addEventListener('webglcontextlost', this.onContextLost); 169 this.canvas.addEventListener('webglcontextrestored', this.onContextRestored); 170 this.getGLContext(); 171 } 172 }; 173 async takeSnapshotAsync(options = {}) { 174 if (!GLView.takeSnapshotAsync) { 175 throw new UnavailabilityError('expo-gl', 'takeSnapshotAsync'); 176 } 177 const gl = this.getGLContextOrReject(); 178 return await GLView.takeSnapshotAsync(gl, options); 179 } 180 async startARSessionAsync() { 181 throw new UnavailabilityError('GLView', 'startARSessionAsync'); 182 } 183 async createCameraTextureAsync() { 184 throw new UnavailabilityError('GLView', 'createCameraTextureAsync'); 185 } 186 async destroyObjectAsync(glObject) { 187 throw new UnavailabilityError('GLView', 'destroyObjectAsync'); 188 } 189} 190//# sourceMappingURL=GLView.web.js.map