UNPKG

expo

Version:
337 lines (297 loc) • 11.6 kB
'use strict'; import React, { PropTypes } from 'react'; import { View, ViewPropTypes, Platform, requireNativeComponent, } from 'react-native'; import * as Constants from './Constants'; // A component that acts as an OpenGL render target. export default class GLView extends React.Component { static propTypes = { // Called when the OpenGL context is created, with the context object as a // parameter. The context object has an API mirroring WebGL's // WebGLRenderingContext. onContextCreate: PropTypes.func, // [iOS only] Number of samples for Apple's built-in multisampling. msaaSamples: PropTypes.number, ...ViewPropTypes, }; static defaultProps = { msaaSamples: 4, }; render() { // eslint-disable-next-line no-unused-vars const { onContextCreate, msaaSamples, ...viewProps } = this.props; // NOTE: Removing `backgroundColor: 'transparent'` causes a performance // regression. Not sure why yet... return ( <View {...viewProps}> <GLView.NativeView style={{ flex: 1, backgroundColor: 'transparent' }} onSurfaceCreate={this._onSurfaceCreate} msaaSamples={Platform.OS === 'ios' ? msaaSamples : undefined} /> </View> ); } _onSurfaceCreate = ({ nativeEvent: { exglCtxId } }) => { const gl = getGl(exglCtxId); if (this.props.onContextCreate) { this.props.onContextCreate(gl); } }; static NativeView = requireNativeComponent('ExponentGLView', GLView, { nativeOnly: { onSurfaceCreate: true }, }); } // JavaScript WebGL types to wrap around native objects global.WebGLRenderingContext = class WebGLRenderingContext {}; const idToObject = {}; global.WebGLObject = class WebGLObject { constructor(id) { if (idToObject[id]) { throw new Error( `WebGL object with underlying EXGLObjectId '${id}' already exists!` ); } this.id = id; // Native GL object id } toString() { return `[WebGLObject ${this.id}]`; } }; const wrapObject = (type, id) => { const found = idToObject[id]; if (found) { return found; } return (idToObject[id] = new type(id)); }; global.WebGLBuffer = class WebGLBuffer extends WebGLObject {}; global.WebGLFramebuffer = class WebGLFramebuffer extends WebGLObject {}; global.WebGLProgram = class WebGLProgram extends WebGLObject {}; global.WebGLRenderbuffer = class WebGLRenderbuffer extends WebGLObject {}; global.WebGLShader = class WebGLShader extends WebGLObject {}; global.WebGLTexture = class WebGLTexture extends WebGLObject {}; global.WebGLUniformLocation = class WebGLUniformLocation { constructor(id) { this.id = id; // Native GL object id } }; global.WebGLActiveInfo = class WebGLActiveInfo { constructor(obj) { Object.assign(this, obj); } }; global.WebGLShaderPrecisionFormat = class WebGLShaderPrecisionFormat { constructor(obj) { Object.assign(this, obj); } }; // Many functions need wrapping/unwrapping of arguments and return value. We // handle each case specifically so we can write the tightest code for // better performance. const wrapMethods = gl => { const wrap = (methodNames, wrapper) => (Array.isArray(methodNames) ? methodNames : [methodNames]).forEach( methodName => gl[methodName] = wrapper(gl[methodName]) ); // We can be slow in `gl.getParameter(...)` since it's a blocking call anyways const getParameterTypes = { [gl.ARRAY_BUFFER_BINDING]: WebGLBuffer, [gl.ELEMENT_ARRAY_BUFFER_BINDING]: WebGLBuffer, [gl.CURRENT_PROGRAM]: WebGLProgram, [gl.FRAMEBUFFER_BINDING]: WebGLFramebuffer, [gl.RENDERBUFFER_BINDING]: WebGLRenderbuffer, [gl.TEXTURE_BINDING_2D]: WebGLTexture, [gl.TEXTURE_BINDING_CUBE_MAP]: WebGLTexture, }; wrap('getParameter', orig => pname => { let ret = orig.call(gl, pname); if (pname === gl.VERSION) { // Wrap native version name ret = `WebGL 1.0 (Expo-${Platform.OS}-${Constants.expoVersion}) (${ret})`; } const type = getParameterTypes[pname]; return type ? wrapObject(type, ret) : ret; }); // Buffers wrap('bindBuffer', orig => (target, buffer) => orig.call(gl, target, buffer && buffer.id)); wrap('createBuffer', orig => () => wrapObject(WebGLBuffer, orig.call(gl))); wrap('deleteBuffer', orig => buffer => orig.call(gl, buffer && buffer.id)); wrap('isBuffer', orig => buffer => buffer instanceof WebGLBuffer && orig.call(gl, buffer.id)); // Framebuffers wrap('bindFramebuffer', orig => (target, framebuffer) => orig.call(gl, target, framebuffer && framebuffer.id)); wrap('createFramebuffer', orig => () => wrapObject(WebGLFramebuffer, orig.call(gl))); wrap('deleteFramebuffer', orig => framebuffer => orig.call(gl, framebuffer && framebuffer.id)); wrap('framebufferRenderbuffer', orig => (target, attachment, rbtarget, rb) => orig.call(gl, target, attachment, rbtarget, rb && rb.id)); wrap('framebufferTexture2D', orig => ( target, attachment, textarget, tex, level ) => orig.call(gl, target, attachment, textarget, tex && tex.id, level)); wrap('isFramebuffer', orig => framebuffer => framebuffer instanceof WebGLFramebuffer && orig.call(gl, framebuffer.id)); // Renderbuffers wrap('bindRenderbuffer', orig => (target, renderbuffer) => orig.call(gl, target, renderbuffer && renderbuffer.id)); wrap('createRenderbuffer', orig => () => wrapObject(WebGLRenderbuffer, orig.call(gl))); wrap('deleteRenderbuffer', orig => renderbuffer => orig.call(gl, renderbuffer && renderbuffer.id)); wrap('isRenderbuffer', orig => renderbuffer => renderbuffer instanceof WebGLRenderbuffer && orig.call(gl, renderbuffer.id)); // Textures wrap('bindTexture', orig => (target, texture) => orig.call(gl, target, texture && texture.id)); wrap('createTexture', orig => () => wrapObject(WebGLTexture, orig.call(gl))); wrap('deleteTexture', orig => texture => orig.call(gl, texture && texture.id)); wrap('isTexture', orig => texture => texture instanceof WebGLTexture && orig.call(gl, texture.id)); // Programs and shaders wrap('attachShader', orig => (program, shader) => orig.call(gl, program && program.id, shader && shader.id)); wrap('bindAttribLocation', orig => (program, index, name) => orig.call(gl, program && program.id, index, name)); wrap('compileShader', orig => shader => orig.call(gl, shader && shader.id)); wrap('createProgram', orig => () => wrapObject(WebGLProgram, orig.call(gl))); wrap('createShader', orig => type => wrapObject(WebGLShader, orig.call(gl, type))); wrap('deleteProgram', orig => program => orig.call(gl, program && program.id)); wrap('deleteShader', orig => shader => orig.call(gl, shader && shader.id)); wrap('detachShader', orig => (program, shader) => orig.call(gl, program && program.id, shader && shader.id)); wrap('getAttachedShaders', orig => program => orig .call(gl, program && program.id) .map(id => wrapObject(WebGLShader, id))); wrap('getProgramParameter', orig => (program, pname) => orig.call(gl, program && program.id, pname)); wrap('getProgramInfoLog', orig => program => orig.call(gl, program && program.id)); wrap('getShaderParameter', orig => (shader, pname) => orig.call(gl, shader && shader.id, pname)); wrap('getShaderPrecisionFormat', orig => (shadertype, precisiontype) => new WebGLShaderPrecisionFormat(orig.call(gl, shadertype, precisiontype))); wrap('getShaderInfoLog', orig => shader => orig.call(gl, shader && shader.id)); wrap('getShaderSource', orig => shader => orig.call(gl, shader && shader.id)); wrap('linkProgram', orig => program => orig.call(gl, program && program.id)); wrap('shaderSource', orig => (shader, source) => orig.call(gl, shader && shader.id, source)); wrap('useProgram', orig => program => orig.call(gl, program && program.id)); wrap('validateProgram', orig => program => orig.call(gl, program && program.id)); wrap('isShader', orig => shader => shader instanceof WebGLShader && orig.call(gl, shader.id)); wrap('isProgram', orig => program => program instanceof WebGLProgram && orig.call(gl, program.id)); // Uniforms and attributes wrap('getActiveAttrib', orig => (program, index) => new WebGLActiveInfo(orig.call(gl, program && program.id, index))); wrap('getActiveUniform', orig => (program, index) => new WebGLActiveInfo(orig.call(gl, program && program.id, index))); wrap('getAttribLocation', orig => (program, name) => orig.call(gl, program && program.id, name)); wrap('getUniform', orig => (program, location) => orig.call(gl, program && program.id, location && location.id)); wrap('getUniformLocation', orig => (program, name) => new WebGLUniformLocation(orig.call(gl, program && program.id, name))); wrap(['uniform1f', 'uniform1i'], orig => (loc, x) => orig.call(gl, loc && loc.id, x)); wrap(['uniform2f', 'uniform2i'], orig => (loc, x, y) => orig.call(gl, loc && loc.id, x, y)); wrap(['uniform3f', 'uniform3i'], orig => (loc, x, y, z) => orig.call(gl, loc && loc.id, x, y, z)); wrap(['uniform4f', 'uniform4i'], orig => (loc, x, y, z, w) => orig.call(gl, loc && loc.id, x, y, z, w)); wrap(['uniform1fv', 'uniform2fv', 'uniform3fv', 'uniform4fv'], orig => ( loc, val ) => orig.call(gl, loc && loc.id, new Float32Array(val))); wrap(['uniform1iv', 'uniform2iv', 'uniform3iv', 'uniform4iv'], orig => ( loc, val ) => orig.call(gl, loc && loc.id, new Int32Array(val))); wrap(['uniformMatrix2fv', 'uniformMatrix3fv', 'uniformMatrix4fv'], orig => ( loc, transpose, val ) => orig.call(gl, loc && loc.id, transpose, new Float32Array(val))); wrap( [ 'vertexAttrib1fv', 'vertexAttrib2fv', 'vertexAttrib3fv', 'vertexAttrib4fv', ], orig => (index, val) => orig.call(gl, index, new Float32Array(val)) ); }; // Get the GL interface from an EXGLContextID and do JS-side setup const getGl = exglCtxId => { const gl = global.__EXGLContexts[exglCtxId]; gl.__exglCtxId = exglCtxId; delete global.__EXGLContexts[exglCtxId]; if (Object.setPrototypeOf) { Object.setPrototypeOf(gl, global.WebGLRenderingContext.prototype); } else { gl.__proto__ = global.WebGLRenderingContext.prototype; } wrapMethods(gl); // No canvas yet... gl.canvas = null; // Drawing buffer width/height // TODO(nikki): Make this dynamic const viewport = gl.getParameter(gl.VIEWPORT); gl.drawingBufferWidth = viewport[2]; gl.drawingBufferHeight = viewport[3]; // Enable/disable logging of all GL function calls let enableLogging = false; Object.defineProperty(gl, 'enableLogging', { configurable: true, get() { return enableLogging; }, set(enable) { if (enable === enableLogging) { return; } if (enable) { Object.keys(gl).forEach(key => { if (typeof gl[key] === 'function') { const original = gl[key]; gl[key] = (...args) => { console.log(`EXGL: ${key}(${args.join(', ')})`); const r = original.apply(gl, args); console.log(`EXGL: = ${r}`); return r; }; gl[key].original = original; } }); } else { Object.keys(gl).forEach(key => { if (typeof gl[key] === 'function' && gl[key].original) { gl[key] = gl[key].original; } }); } enableLogging = enable; }, }); return gl; };