UNPKG

12.9 kBJavaScriptView Raw
1// @flow
2
3import PropTypes from 'prop-types';
4import * as React from 'react';
5import {
6 NativeModules,
7 Platform,
8 View,
9 ViewPropTypes,
10 findNodeHandle,
11 requireNativeComponent,
12} from 'react-native';
13
14import Constants from './Constants';
15
16type Props = {
17 /**
18 * Called when the OpenGL context is created, with the context object as a parameter. The context
19 * object has an API mirroring WebGL's WebGLRenderingContext.
20 */
21 onContextCreate?: (gl: *) => void,
22
23 /**
24 * [iOS only] Number of samples for Apple's built-in multisampling.
25 */
26 msaaSamples: number,
27
28 /**
29 * A ref callback for the native GLView
30 */
31 nativeRef_EXPERIMENTAL: React.Ref<typeof GLView.NativeView>,
32} & React.ElementProps<typeof View>;
33
34type SurfaceCreateEvent = {
35 nativeEvent: {
36 exglCtxId: number,
37 },
38};
39
40/**
41 * A component that acts as an OpenGL render target
42 */
43export default class GLView extends React.Component<Props> {
44 static propTypes = {
45 onContextCreate: PropTypes.func,
46 msaaSamples: PropTypes.number,
47 nativeRef_EXPERIMENTAL: PropTypes.func,
48 ...ViewPropTypes,
49 };
50
51 static defaultProps = {
52 msaaSamples: 4,
53 };
54
55 nativeRef: ?GLView.NativeView;
56
57 render() {
58 const {
59 onContextCreate, // eslint-disable-line no-unused-vars
60 msaaSamples,
61 ...viewProps
62 } = this.props;
63
64 return (
65 <View {...viewProps}>
66 <GLView.NativeView
67 ref={this._setNativeRef}
68 style={{
69 flex: 1,
70 ...(Platform.OS === 'ios'
71 ? {
72 backgroundColor: 'transparent',
73 }
74 : {}),
75 }}
76 onSurfaceCreate={this._onSurfaceCreate}
77 msaaSamples={Platform.OS === 'ios' ? msaaSamples : undefined}
78 />
79 </View>
80 );
81 }
82
83 _setNativeRef = (nativeRef: GLView.NativeView) => {
84 if (this.props.nativeRef_EXPERIMENTAL) {
85 this.props.nativeRef_EXPERIMENTAL(nativeRef);
86 }
87 this.nativeRef = nativeRef;
88 };
89
90 _onSurfaceCreate = ({ nativeEvent: { exglCtxId } }: SurfaceCreateEvent) => {
91 const gl = getGl(exglCtxId);
92 if (this.props.onContextCreate) {
93 this.props.onContextCreate(gl);
94 }
95 };
96
97 static NativeView = requireNativeComponent('ExponentGLView', GLView, {
98 nativeOnly: { onSurfaceCreate: true },
99 });
100
101 startARSessionAsync() {
102 return NativeModules.ExponentGLViewManager.startARSessionAsync(findNodeHandle(this.nativeRef));
103 }
104}
105
106// JavaScript WebGL types to wrap around native objects
107
108class WebGLRenderingContext {}
109
110type WebGLObjectId = any;
111
112const idToObject = {};
113
114class WebGLObject {
115 id: WebGLObjectId;
116
117 constructor(id: WebGLObjectId) {
118 if (idToObject[id]) {
119 throw new Error(`WebGL object with underlying EXGLObjectId '${id}' already exists!`);
120 }
121 this.id = id; // Native GL object id
122 }
123 toString() {
124 return `[WebGLObject ${this.id}]`;
125 }
126}
127
128const wrapObject = (type, id: WebGLObjectId) => {
129 const found = idToObject[id];
130 if (found) {
131 return found;
132 }
133 return (idToObject[id] = new type(id));
134};
135
136class WebGLBuffer extends WebGLObject {}
137
138class WebGLFramebuffer extends WebGLObject {}
139
140class WebGLProgram extends WebGLObject {}
141
142class WebGLRenderbuffer extends WebGLObject {}
143
144class WebGLShader extends WebGLObject {}
145
146class WebGLTexture extends WebGLObject {}
147
148class WebGLUniformLocation {
149 id: WebGLObjectId;
150
151 constructor(id: WebGLObjectId) {
152 this.id = id; // Native GL object id
153 }
154}
155
156class WebGLActiveInfo {
157 constructor(obj) {
158 Object.assign(this, obj);
159 }
160}
161
162class WebGLShaderPrecisionFormat {
163 constructor(obj) {
164 Object.assign(this, obj);
165 }
166}
167
168// Many functions need wrapping/unwrapping of arguments and return value. We handle each case
169// specifically so we can write the tightest code for better performance.
170const wrapMethods = gl => {
171 const wrap = (methodNames, wrapper) =>
172 (Array.isArray(methodNames) ? methodNames : [methodNames]).forEach(
173 methodName => (gl[methodName] = wrapper(gl[methodName]))
174 );
175
176 // We can be slow in `gl.getParameter(...)` since it's a blocking call anyways
177 const getParameterTypes = {
178 [gl.ARRAY_BUFFER_BINDING]: WebGLBuffer,
179 [gl.ELEMENT_ARRAY_BUFFER_BINDING]: WebGLBuffer,
180 [gl.CURRENT_PROGRAM]: WebGLProgram,
181 [gl.FRAMEBUFFER_BINDING]: WebGLFramebuffer,
182 [gl.RENDERBUFFER_BINDING]: WebGLRenderbuffer,
183 [gl.TEXTURE_BINDING_2D]: WebGLTexture,
184 [gl.TEXTURE_BINDING_CUBE_MAP]: WebGLTexture,
185 };
186 wrap('getParameter', orig => pname => {
187 let ret = orig.call(gl, pname);
188 if (pname === gl.VERSION) {
189 // Wrap native version name
190 ret = `WebGL 1.0 (Expo-${Platform.OS}-${Constants.expoVersion}) (${ret})`;
191 }
192 const type = getParameterTypes[pname];
193 return type ? wrapObject(type, ret) : ret;
194 });
195
196 // Buffers
197 wrap('bindBuffer', orig => (target, buffer) => orig.call(gl, target, buffer && buffer.id));
198 wrap('createBuffer', orig => () => wrapObject(WebGLBuffer, orig.call(gl)));
199 wrap('deleteBuffer', orig => buffer => orig.call(gl, buffer && buffer.id));
200 wrap('isBuffer', orig => buffer => buffer instanceof WebGLBuffer && orig.call(gl, buffer.id));
201
202 // Framebuffers
203 wrap('bindFramebuffer', orig => (target, framebuffer) =>
204 orig.call(gl, target, framebuffer && framebuffer.id)
205 );
206 wrap('createFramebuffer', orig => () => wrapObject(WebGLFramebuffer, orig.call(gl)));
207 wrap('deleteFramebuffer', orig => framebuffer => orig.call(gl, framebuffer && framebuffer.id));
208 wrap('framebufferRenderbuffer', orig => (target, attachment, rbtarget, rb) =>
209 orig.call(gl, target, attachment, rbtarget, rb && rb.id)
210 );
211 wrap('framebufferTexture2D', orig => (target, attachment, textarget, tex, level) =>
212 orig.call(gl, target, attachment, textarget, tex && tex.id, level)
213 );
214 wrap('isFramebuffer', orig => framebuffer =>
215 framebuffer instanceof WebGLFramebuffer && orig.call(gl, framebuffer.id)
216 );
217
218 // Renderbuffers
219 wrap('bindRenderbuffer', orig => (target, renderbuffer) =>
220 orig.call(gl, target, renderbuffer && renderbuffer.id)
221 );
222 wrap('createRenderbuffer', orig => () => wrapObject(WebGLRenderbuffer, orig.call(gl)));
223 wrap('deleteRenderbuffer', orig => renderbuffer =>
224 orig.call(gl, renderbuffer && renderbuffer.id)
225 );
226 wrap('isRenderbuffer', orig => renderbuffer =>
227 renderbuffer instanceof WebGLRenderbuffer && orig.call(gl, renderbuffer.id)
228 );
229
230 // Textures
231 wrap('bindTexture', orig => (target, texture) => orig.call(gl, target, texture && texture.id));
232 wrap('createTexture', orig => () => wrapObject(WebGLTexture, orig.call(gl)));
233 wrap('deleteTexture', orig => texture => orig.call(gl, texture && texture.id));
234 wrap('isTexture', orig => texture =>
235 texture instanceof WebGLTexture && orig.call(gl, texture.id)
236 );
237
238 // Programs and shaders
239 wrap('attachShader', orig => (program, shader) =>
240 orig.call(gl, program && program.id, shader && shader.id)
241 );
242 wrap('bindAttribLocation', orig => (program, index, name) =>
243 orig.call(gl, program && program.id, index, name)
244 );
245 wrap('compileShader', orig => shader => orig.call(gl, shader && shader.id));
246 wrap('createProgram', orig => () => wrapObject(WebGLProgram, orig.call(gl)));
247 wrap('createShader', orig => type => wrapObject(WebGLShader, orig.call(gl, type)));
248 wrap('deleteProgram', orig => program => orig.call(gl, program && program.id));
249 wrap('deleteShader', orig => shader => orig.call(gl, shader && shader.id));
250 wrap('detachShader', orig => (program, shader) =>
251 orig.call(gl, program && program.id, shader && shader.id)
252 );
253 wrap('getAttachedShaders', orig => program =>
254 orig.call(gl, program && program.id).map(id => wrapObject(WebGLShader, id))
255 );
256 wrap('getProgramParameter', orig => (program, pname) =>
257 orig.call(gl, program && program.id, pname)
258 );
259 wrap('getProgramInfoLog', orig => program => orig.call(gl, program && program.id));
260 wrap('getShaderParameter', orig => (shader, pname) => orig.call(gl, shader && shader.id, pname));
261 wrap('getShaderPrecisionFormat', orig => (shadertype, precisiontype) =>
262 new WebGLShaderPrecisionFormat(orig.call(gl, shadertype, precisiontype))
263 );
264 wrap('getShaderInfoLog', orig => shader => orig.call(gl, shader && shader.id));
265 wrap('getShaderSource', orig => shader => orig.call(gl, shader && shader.id));
266 wrap('linkProgram', orig => program => orig.call(gl, program && program.id));
267 wrap('shaderSource', orig => (shader, source) => orig.call(gl, shader && shader.id, source));
268 wrap('useProgram', orig => program => orig.call(gl, program && program.id));
269 wrap('validateProgram', orig => program => orig.call(gl, program && program.id));
270 wrap('isShader', orig => shader => shader instanceof WebGLShader && orig.call(gl, shader.id));
271 wrap('isProgram', orig => program =>
272 program instanceof WebGLProgram && orig.call(gl, program.id)
273 );
274
275 // Uniforms and attributes
276 wrap('getActiveAttrib', orig => (program, index) =>
277 new WebGLActiveInfo(orig.call(gl, program && program.id, index))
278 );
279 wrap('getActiveUniform', orig => (program, index) =>
280 new WebGLActiveInfo(orig.call(gl, program && program.id, index))
281 );
282 wrap('getAttribLocation', orig => (program, name) => orig.call(gl, program && program.id, name));
283 wrap('getUniform', orig => (program, location) =>
284 orig.call(gl, program && program.id, location && location.id)
285 );
286 wrap('getUniformLocation', orig => (program, name) =>
287 new WebGLUniformLocation(orig.call(gl, program && program.id, name))
288 );
289 wrap(['uniform1f', 'uniform1i'], orig => (loc, x) => orig.call(gl, loc && loc.id, x));
290 wrap(['uniform2f', 'uniform2i'], orig => (loc, x, y) => orig.call(gl, loc && loc.id, x, y));
291 wrap(['uniform3f', 'uniform3i'], orig => (loc, x, y, z) => orig.call(gl, loc && loc.id, x, y, z));
292 wrap(['uniform4f', 'uniform4i'], orig => (loc, x, y, z, w) =>
293 orig.call(gl, loc && loc.id, x, y, z, w)
294 );
295 wrap(['uniform1fv', 'uniform2fv', 'uniform3fv', 'uniform4fv'], orig => (loc, val) =>
296 orig.call(gl, loc && loc.id, new Float32Array(val))
297 );
298 wrap(['uniform1iv', 'uniform2iv', 'uniform3iv', 'uniform4iv'], orig => (loc, val) =>
299 orig.call(gl, loc && loc.id, new Int32Array(val))
300 );
301 wrap(
302 ['uniformMatrix2fv', 'uniformMatrix3fv', 'uniformMatrix4fv'],
303 orig => (loc, transpose, val) => orig.call(gl, loc && loc.id, transpose, new Float32Array(val))
304 );
305 wrap(
306 ['vertexAttrib1fv', 'vertexAttrib2fv', 'vertexAttrib3fv', 'vertexAttrib4fv'],
307 orig => (index, val) => orig.call(gl, index, new Float32Array(val))
308 );
309};
310
311// Get the GL interface from an EXGLContextID and do JS-side setup
312const getGl = exglCtxId => {
313 const gl = global.__EXGLContexts[exglCtxId];
314 gl.__exglCtxId = exglCtxId;
315 delete global.__EXGLContexts[exglCtxId];
316 if (Object.setPrototypeOf) {
317 Object.setPrototypeOf(gl, global.WebGLRenderingContext.prototype);
318 } else {
319 // Delete this path when we are competely sure we're using modern JSC on Android. iOS 9+
320 // supports Object.setPrototypeOf.
321 gl.__proto__ = global.WebGLRenderingContext.prototype; // eslint-disable-line no-proto
322 }
323
324 wrapMethods(gl);
325
326 // No canvas yet...
327 gl.canvas = null;
328
329 // Drawing buffer width/height
330 // TODO(nikki): Make this dynamic
331 const viewport = gl.getParameter(gl.VIEWPORT);
332 gl.drawingBufferWidth = viewport[2];
333 gl.drawingBufferHeight = viewport[3];
334
335 // Enable/disable logging of all GL function calls
336 let enableLogging = false;
337
338 // $FlowIssue: Flow wants a "value" field
339 Object.defineProperty(gl, 'enableLogging', {
340 configurable: true,
341 get(): boolean {
342 return enableLogging;
343 },
344 set(enable: boolean): void {
345 if (enable === enableLogging) {
346 return;
347 }
348 if (enable) {
349 Object.keys(gl).forEach(key => {
350 if (typeof gl[key] === 'function') {
351 const original = gl[key];
352 gl[key] = (...args) => {
353 console.log(`EXGL: ${key}(${args.join(', ')})`);
354 const r = original.apply(gl, args);
355 console.log(`EXGL: = ${r}`);
356 return r;
357 };
358 gl[key].original = original;
359 }
360 });
361 } else {
362 Object.keys(gl).forEach(key => {
363 if (typeof gl[key] === 'function' && gl[key].original) {
364 gl[key] = gl[key].original;
365 }
366 });
367 }
368 enableLogging = enable;
369 },
370 });
371
372 return gl;
373};
374
375global.WebGLRenderingContext = WebGLRenderingContext;
376global.WebGLObject = WebGLObject;
377global.WebGLBuffer = WebGLBuffer;
378global.WebGLFramebuffer = WebGLFramebuffer;
379global.WebGLProgram = WebGLProgram;
380global.WebGLRenderbuffer = WebGLRenderbuffer;
381global.WebGLShader = WebGLShader;
382global.WebGLTexture = WebGLTexture;
383global.WebGLUniformLocation = WebGLUniformLocation;
384global.WebGLActiveInfo = WebGLActiveInfo;
385global.WebGLShaderPrecisionFormat = WebGLShaderPrecisionFormat;