/* * GL support. See https://github.com/kripken/emscripten/wiki/OpenGL-support * for current status. */ var LibraryGL = { $GL__postset: 'GL.init()', $GL: { #if GL_DEBUG debug: true, #endif counter: 1, // 0 is reserved as 'null' in gl buffers: [], programs: [], framebuffers: [], renderbuffers: [], textures: [], uniforms: [], shaders: [], #if FULL_ES2 clientBuffers: [], enabledClientBuffers: [], #endif currArrayBuffer: 0, currElementArrayBuffer: 0, byteSizeByTypeRoot: 0x1400, // GL_BYTE byteSizeByType: [ 1, // GL_BYTE 1, // GL_UNSIGNED_BYTE 2, // GL_SHORT 2, // GL_UNSIGNED_SHORT 4, // GL_INT 4, // GL_UNSIGNED_INT 4, // GL_FLOAT 2, // GL_2_BYTES 3, // GL_3_BYTES 4, // GL_4_BYTES 8 // GL_DOUBLE ], uniformTable: {}, // name => uniform ID. the uID must be identical until relinking, cannot create a new uID each call to glGetUniformLocation packAlignment: 4, // default alignment is 4 bytes unpackAlignment: 4, // default alignment is 4 bytes init: function() { Browser.moduleContextCreatedCallbacks.push(GL.initExtensions); }, // Get a new ID for a texture/buffer/etc., while keeping the table dense and fast. Creation is farely rare so it is worth optimizing lookups later. getNewId: function(table) { var ret = GL.counter++; for (var i = table.length; i < ret; i++) { table[i] = null; } return ret; }, // Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters scan: function(table, object) { for (var item in table) { if (table[item] == object) return item; } return 0; }, // Find a token in a shader source string findToken: function(source, token) { function isIdentChar(ch) { if (ch >= 48 && ch <= 57) // 0-9 return true; if (ch >= 65 && ch <= 90) // A-Z return true; if (ch >= 97 && ch <= 122) // a-z return true; return false; } var i = -1; do { i = source.indexOf(token, i + 1); if (i < 0) { break; } if (i > 0 && isIdentChar(source[i - 1])) { continue; } i += token.length; if (i < source.length - 1 && isIdentChar(source[i + 1])) { continue; } return true; } while (true); return false; }, getSource: function(shader, count, string, length) { var source = ''; for (var i = 0; i < count; ++i) { var frag; if (length) { var len = {{{ makeGetValue('length', 'i*4', 'i32') }}}; if (len < 0) { frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}}); } else { frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}}, len); } } else { frag = Pointer_stringify({{{ makeGetValue('string', 'i*4', 'i32') }}}); } source += frag; } // Let's see if we need to enable the standard derivatives extension type = Module.ctx.getShaderParameter(GL.shaders[shader], 0x8B4F /* GL_SHADER_TYPE */); if (type == 0x8B30 /* GL_FRAGMENT_SHADER */) { if (GL.findToken(source, "dFdx") || GL.findToken(source, "dFdy") || GL.findToken(source, "fwidth")) { source = "#extension GL_OES_standard_derivatives : enable\n" + source; var extension = Module.ctx.getExtension("OES_standard_derivatives"); #if GL_DEBUG if (!extension) { Module.printErr("Shader attempts to use the standard derivatives extension which is not available."); } #endif } } return source; }, computeImageSize: function(width, height, sizePerPixel, alignment) { function roundedToNextMultipleOf(x, y) { return Math.floor((x + y - 1) / y) * y } var plainRowSize = width * sizePerPixel; var alignedRowSize = roundedToNextMultipleOf(plainRowSize, alignment); return (height <= 0) ? 0 : ((height - 1) * alignedRowSize + plainRowSize); }, getTexPixelData: function(type, format, width, height, pixels, internalFormat) { var sizePerPixel; switch (type) { case 0x1401 /* GL_UNSIGNED_BYTE */: switch (format) { case 0x1906 /* GL_ALPHA */: case 0x1909 /* GL_LUMINANCE */: sizePerPixel = 1; break; case 0x1907 /* GL_RGB */: sizePerPixel = 3; break; case 0x1908 /* GL_RGBA */: sizePerPixel = 4; break; case 0x190A /* GL_LUMINANCE_ALPHA */: sizePerPixel = 2; break; default: throw 'Invalid format (' + format + ')'; } break; case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: sizePerPixel = 2; break; case 0x1406 /* GL_FLOAT */: assert(GL.floatExt, 'Must have OES_texture_float to use float textures'); switch (format) { case 0x1907 /* GL_RGB */: sizePerPixel = 3*4; break; case 0x1908 /* GL_RGBA */: sizePerPixel = 4*4; break; default: throw 'Invalid format (' + format + ')'; } internalFormat = Module.ctx.RGBA; break; default: throw 'Invalid type (' + type + ')'; } var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment); if (type == 0x1401 /* GL_UNSIGNED_BYTE */) { pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; } else if (type == 0x1406 /* GL_FLOAT */) { pixels = {{{ makeHEAPView('F32', 'pixels', 'pixels+bytes') }}}; } else { pixels = {{{ makeHEAPView('U16', 'pixels', 'pixels+bytes') }}}; } return { pixels: pixels, internalFormat: internalFormat } }, #if FULL_ES2 calcBufLength: function(size, type, stride, count) { if (stride > 0) { return count * stride; // XXXvlad this is not exactly correct I don't think } var typeSize = GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; return size * typeSize * count; }, preDrawHandleClientVertexAttribBindings: function(count) { GL.resetBufferBinding = false; for (var i = 0; i < GL.maxVertexAttribs; ++i) { if (!GL.enabledClientBuffers[i] || !GL.clientBuffers[i]) continue; GL.resetBufferBinding = true; var cb = GL.clientBuffers[i]; var buf = Module.ctx.createBuffer(); Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, buf); Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, HEAPU8.subarray(cb.ptr, cb.ptr + GL.calcBufLength(cb.size, cb.type, cb.stride, count)), Module.ctx.DYNAMIC_DRAW); Module.ctx.vertexAttribPointer(i, cb.size, cb.type, cb.normalized, cb.stride, 0); } }, postDrawHandleClientVertexAttribBindings: function() { if (GL.resetBufferBinding) { Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, GL.buffers[GL.currArrayBuffer]); } }, #endif initExtensions: function() { if (GL.initExtensions.done) return; GL.initExtensions.done = true; if (!Module.useWebGL) return; // an app might link both gl and 2d backends GL.maxVertexAttribs = Module.ctx.getParameter(Module.ctx.MAX_VERTEX_ATTRIBS); GL.compressionExt = Module.ctx.getExtension('WEBGL_compressed_texture_s3tc') || Module.ctx.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || Module.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); GL.anisotropicExt = Module.ctx.getExtension('EXT_texture_filter_anisotropic') || Module.ctx.getExtension('MOZ_EXT_texture_filter_anisotropic') || Module.ctx.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); GL.floatExt = Module.ctx.getExtension('OES_texture_float'); } }, glPixelStorei: function(pname, param) { if (pname == 0x0D05 /* GL_PACK_ALIGNMENT */) { GL.packAlignment = param; } else if (pname == 0x0cf5 /* GL_UNPACK_ALIGNMENT */) { GL.unpackAlignment = param; } Module.ctx.pixelStorei(pname, param); }, glGetString: function(name_) { switch(name_) { case 0x1F00 /* GL_VENDOR */: case 0x1F01 /* GL_RENDERER */: case 0x1F02 /* GL_VERSION */: return allocate(intArrayFromString(Module.ctx.getParameter(name_)), 'i8', ALLOC_NORMAL); case 0x1F03 /* GL_EXTENSIONS */: return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ')), 'i8', ALLOC_NORMAL); case 0x8B8C /* GL_SHADING_LANGUAGE_VERSION */: return allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL); default: throw 'Failure: Invalid glGetString value: ' + name_; } }, glGetIntegerv: function(name_, p) { switch(name_) { // Handle a few trivial GLES values case 0x8DFA: // GL_SHADER_COMPILER {{{ makeSetValue('p', '0', '1', 'i32') }}}; return; case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS {{{ makeSetValue('p', '0', '0', 'i32') }}}; return; } var result = Module.ctx.getParameter(name_); switch (typeof(result)) { case "number": {{{ makeSetValue('p', '0', 'result', 'i32') }}}; break; case "boolean": {{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}}; break; case "string": throw 'Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i32') }}}; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) { for (var i = 0; i < result.length; ++i) { {{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}}; } } else if (result instanceof WebGLBuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'i32') }}}; } else if (result instanceof WebGLProgram) { {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'i32') }}}; } else if (result instanceof WebGLFramebuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'i32') }}}; } else if (result instanceof WebGLRenderbuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'i32') }}}; } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'i32') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } break; case "undefined": throw 'Native code calling glGetIntegerv(' + name_ + ') and it returns undefined'; default: throw 'Why did we hit the default case?'; } }, glGetFloatv: function(name_, p) { var result = Module.ctx.getParameter(name_); switch (typeof(result)) { case "number": {{{ makeSetValue('p', '0', 'result', 'float') }}}; break; case "boolean": {{{ makeSetValue('p', '0', 'result ? 1.0 : 0.0', 'float') }}}; break; case "string": {{{ makeSetValue('p', '0', '0', 'float') }}}; case "object": if (result === null) { throw 'Native code calling glGetFloatv(' + name_ + ') and it returns null'; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) { for (var i = 0; i < result.length; ++i) { {{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}}; } } else if (result instanceof WebGLBuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'float') }}}; } else if (result instanceof WebGLProgram) { {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'float') }}}; } else if (result instanceof WebGLFramebuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'float') }}}; } else if (result instanceof WebGLRenderbuffer) { {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'float') }}}; } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'float') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } break; case "undefined": throw 'Native code calling glGetFloatv(' + name_ + ') and it returns undefined'; default: throw 'Why did we hit the default case?'; } }, glGetBooleanv: function(name_, p) { var result = Module.ctx.getParameter(name_); switch (typeof(result)) { case "number": {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; break; case "boolean": {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; break; case "string": throw 'Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i8') }}}; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) { for (var i = 0; i < result.length; ++i) { {{{ makeSetValue('p', 'i', 'result[i] != 0', 'i8') }}}; } } else if (result instanceof WebGLBuffer || result instanceof WebGLProgram || result instanceof WebGLFramebuffer || result instanceof WebGLRenderbuffer || result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1! } else { throw 'Unknown object returned from WebGL getParameter'; } break; case "undefined": throw 'Unknown object returned from WebGL getParameter'; default: throw 'Why did we hit the default case?'; } }, glGenTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.textures); GL.textures[id] = Module.ctx.createTexture(); {{{ makeSetValue('textures', 'i*4', 'id', 'i32') }}}; } }, glDeleteTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('textures', 'i*4', 'i32') }}}; Module.ctx.deleteTexture(GL.textures[id]); GL.textures[id] = null; } }, glCompressedTexImage2D__sig: 'viiiiiiii', glCompressedTexImage2D: function(target, level, internalFormat, width, height, border, imageSize, data) { assert(GL.compressionExt); if (data) { data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}}; } else { data = null; } Module.ctx['compressedTexImage2D'](target, level, internalFormat, width, height, border, data); }, glCompressedTexSubImage2D__sig: 'viiiiiiiii', glCompressedTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, imageSize, data) { assert(GL.compressionExt); if (data) { data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}}; } else { data = null; } Module.ctx['compressedTexSubImage2D'](target, level, xoffset, yoffset, width, height, data); }, glTexImage2D: function(target, level, internalFormat, width, height, border, format, type, pixels) { if (pixels) { var data = GL.getTexPixelData(type, format, width, height, pixels, internalFormat); pixels = data.pixels; internalFormat = data.internalFormat; } else { pixels = null; } Module.ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels); }, glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) { if (pixels) { var data = GL.getTexPixelData(type, format, width, height, pixels, -1); pixels = data.pixels; } else { pixels = null; } Module.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); }, glReadPixels: function(x, y, width, height, format, type, pixels) { assert(type == 0x1401 /* GL_UNSIGNED_BYTE */); var sizePerPixel; switch (format) { case 0x1907 /* GL_RGB */: sizePerPixel = 3; break; case 0x1908 /* GL_RGBA */: sizePerPixel = 4; break; default: throw 'unsupported glReadPixels format'; } var totalSize = width*height*sizePerPixel; Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize)); }, glBindTexture: function(target, texture) { Module.ctx.bindTexture(target, texture ? GL.textures[texture] : null); }, glGetTexParameterfv: function(target, pname, params) { {{{ makeSetValue('params', '0', 'Module.getTexParameter(target, pname)', 'float') }}}; }, glGetTexParameteriv: function(target, pname, params) { {{{ makeSetValue('params', '0', 'Module.getTexParameter(target, pname)', 'i32') }}}; }, glIsTexture: function(texture) { var texture = GL.textures[texture]; if (!texture) return 0; return Module.ctx.isTexture(texture); }, glGenBuffers__sig: 'vii', glGenBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.buffers); GL.buffers[id] = Module.ctx.createBuffer(); {{{ makeSetValue('buffers', 'i*4', 'id', 'i32') }}}; } }, glDeleteBuffers__sig: 'vii', glDeleteBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; Module.ctx.deleteBuffer(GL.buffers[id]); GL.buffers[id] = null; if (id == GL.currArrayBuffer) GL.currArrayBuffer = 0; if (id == GL.currElementArrayBuffer) GL.currElementArrayBuffer = 0; } }, glGetBufferParameteriv: function(target, value, data) { {{{ makeSetValue('data', '0', 'Module.ctx.getBufferParameter(target, value)', 'i32') }}}; }, glBufferData__sig: 'viiii', glBufferData: function(target, size, data, usage) { Module.ctx.bufferData(target, HEAPU8.subarray(data, data+size), usage); }, glBufferSubData__sig: 'viiii', glBufferSubData: function(target, offset, size, data) { Module.ctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size)); }, glIsBuffer: function(buffer) { var b = GL.buffers[buffer]; if (!b) return 0; return Module.ctx.isBuffer(b); }, glGenRenderbuffers__sig: 'vii', glGenRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.renderbuffers); GL.renderbuffers[id] = Module.ctx.createRenderbuffer(); {{{ makeSetValue('renderbuffers', 'i*4', 'id', 'i32') }}}; } }, glDeleteRenderbuffers__sig: 'vii', glDeleteRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('renderbuffers', 'i*4', 'i32') }}}; Module.ctx.deleteRenderbuffer(GL.renderbuffers[id]); GL.renderbuffers[id]; } }, glBindRenderbuffer__sig: 'vii', glBindRenderbuffer: function(target, renderbuffer) { Module.ctx.bindRenderbuffer(target, renderbuffer ? GL.renderbuffers[renderbuffer] : null); }, glGetRenderbufferParameteriv: function(target, pname, params) { {{{ makeSetValue('params', '0', 'Module.ctx.getRenderbufferParameter(target, pname)', 'i32') }}}; }, glIsRenderbuffer: function(renderbuffer) { var rb = GL.renderbuffers[renderbuffer]; if (!rb) return 0; return Module.ctx.isRenderbuffer(rb); }, glGetUniformfv: function(program, location, params) { var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number') { {{{ makeSetValue('params', '0', 'data', 'float') }}}; } else { for (var i = 0; i < data.length; i++) { {{{ makeSetValue('params', 'i', 'data[i]', 'float') }}}; } } }, glGetUniformiv: function(program, location, params) { var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number' || typeof data == 'boolean') { {{{ makeSetValue('params', '0', 'data', 'i32') }}}; } else { for (var i = 0; i < data.length; i++) { {{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}}; } } }, glGetUniformLocation__sig: 'iii', glGetUniformLocation: function(program, name) { name = Pointer_stringify(name); var ptable = GL.uniformTable[program]; if (!ptable) ptable = GL.uniformTable[program] = {}; var id = ptable[name]; if (id) return id; var loc = Module.ctx.getUniformLocation(GL.programs[program], name); if (!loc) return -1; id = GL.getNewId(GL.uniforms); GL.uniforms[id] = loc; ptable[name] = id; return id; }, glGetVertexAttribfv: function(index, pname, params) { #if FULL_ES2 if (GL.clientBuffers[index]) { Module.printErr("glGetVertexAttribfv on client-side array: not supported, bad data returned"); } #endif var data = Module.ctx.getVertexAttrib(index, pname); if (typeof data == 'number') { {{{ makeSetValue('params', '0', 'data', 'float') }}}; } else { for (var i = 0; i < data.length; i++) { {{{ makeSetValue('params', 'i', 'data[i]', 'float') }}}; } } }, glGetVertexAttribiv: function(index, pname, params) { #if FULL_ES2 if (GL.clientBuffers[index]) { Module.printErr("glGetVertexAttribiv on client-side array: not supported, bad data returned"); } #endif var data = Module.ctx.getVertexAttrib(index, pname); if (typeof data == 'number' || typeof data == 'boolean') { {{{ makeSetValue('params', '0', 'data', 'i32') }}}; } else { for (var i = 0; i < data.length; i++) { {{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}}; } } }, glGetVertexAttribPointerv: function(index, pname, pointer) { #if FULL_ES2 if (GL.clientBuffers[index]) { Module.printErr("glGetVertexAttribPointer on client-side array: not supported, bad data returned"); } #endif {{{ makeSetValue('pointer', '0', 'Module.ctx.getVertexAttribOffset(index, pname)', 'i32') }}}; }, glGetActiveUniform__sig: 'viiiiiii', glGetActiveUniform: function(program, index, bufSize, length, size, type, name) { program = GL.programs[program]; var info = Module.ctx.getActiveUniform(program, index); var infoname = info.name.slice(0, bufSize - 1); writeStringToMemory(infoname, name); if (length) { {{{ makeSetValue('length', '0', 'infoname.length', 'i32') }}}; } if (size) { {{{ makeSetValue('size', '0', 'info.size', 'i32') }}}; } if (type) { {{{ makeSetValue('type', '0', 'info.type', 'i32') }}}; } }, glUniform1f__sig: 'vid', glUniform1f: function(location, v0) { location = GL.uniforms[location]; Module.ctx.uniform1f(location, v0); }, glUniform2f__sig: 'vidd', glUniform2f: function(location, v0, v1) { location = GL.uniforms[location]; Module.ctx.uniform2f(location, v0, v1); }, glUniform3f__sig: 'viddd', glUniform3f: function(location, v0, v1, v2) { location = GL.uniforms[location]; Module.ctx.uniform3f(location, v0, v1, v2); }, glUniform4f__sig: 'vidddd', glUniform4f: function(location, v0, v1, v2, v3) { location = GL.uniforms[location]; Module.ctx.uniform4f(location, v0, v1, v2, v3); }, glUniform1i__sig: 'vii', glUniform1i: function(location, v0) { location = GL.uniforms[location]; Module.ctx.uniform1i(location, v0); }, glUniform2i__sig: 'viii', glUniform2i: function(location, v0, v1) { location = GL.uniforms[location]; Module.ctx.uniform2i(location, v0, v1); }, glUniform3i__sig: 'viiii', glUniform3i: function(location, v0, v1, v2) { location = GL.uniforms[location]; Module.ctx.uniform3i(location, v0, v1, v2); }, glUniform4i__sig: 'viiiii', glUniform4i: function(location, v0, v1, v2, v3) { location = GL.uniforms[location]; Module.ctx.uniform4i(location, v0, v1, v2, v3); }, glUniform1iv__sig: 'viii', glUniform1iv: function(location, count, value) { location = GL.uniforms[location]; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; Module.ctx.uniform1iv(location, value); }, glUniform2iv__sig: 'viii', glUniform2iv: function(location, count, value) { location = GL.uniforms[location]; count *= 2; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; Module.ctx.uniform2iv(location, value); }, glUniform3iv__sig: 'viii', glUniform3iv: function(location, count, value) { location = GL.uniforms[location]; count *= 3; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; Module.ctx.uniform3iv(location, value); }, glUniform4iv__sig: 'viii', glUniform4iv: function(location, count, value) { location = GL.uniforms[location]; count *= 4; value = {{{ makeHEAPView('32', 'value', 'value+count*4') }}}; Module.ctx.uniform4iv(location, value); }, glUniform1fv__sig: 'viii', glUniform1fv: function(location, count, value) { location = GL.uniforms[location]; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniform1fv(location, value); }, glUniform2fv__sig: 'viii', glUniform2fv: function(location, count, value) { location = GL.uniforms[location]; count *= 2; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniform2fv(location, value); }, glUniform3fv__sig: 'viii', glUniform3fv: function(location, count, value) { location = GL.uniforms[location]; count *= 3; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniform3fv(location, value); }, glUniform4fv__sig: 'viii', glUniform4fv: function(location, count, value) { location = GL.uniforms[location]; count *= 4; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniform4fv(location, value); }, glUniformMatrix2fv: function(location, count, transpose, value) { location = GL.uniforms[location]; count *= 4; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniformMatrix2fv(location, transpose, value); }, glUniformMatrix3fv: function(location, count, transpose, value) { location = GL.uniforms[location]; count *= 9; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniformMatrix3fv(location, transpose, value); }, glUniformMatrix4fv: function(location, count, transpose, value) { location = GL.uniforms[location]; count *= 16; value = {{{ makeHEAPView('F32', 'value', 'value+count*4') }}}; Module.ctx.uniformMatrix4fv(location, transpose, value); }, glBindBuffer__sig: 'vii', glBindBuffer: function(target, buffer) { if (target == Module.ctx.ARRAY_BUFFER) { GL.currArrayBuffer = buffer; } else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) { GL.currElementArrayBuffer = buffer; } Module.ctx.bindBuffer(target, buffer ? GL.buffers[buffer] : null); }, glVertexAttrib1fv: function(index, v) { v = {{{ makeHEAPView('F32', 'v', 'v+' + (1*4)) }}}; Module.ctx.vertexAttrib1fv(index, v); }, glVertexAttrib2fv: function(index, v) { v = {{{ makeHEAPView('F32', 'v', 'v+' + (2*4)) }}}; Module.ctx.vertexAttrib2fv(index, v); }, glVertexAttrib3fv: function(index, v) { v = {{{ makeHEAPView('F32', 'v', 'v+' + (3*4)) }}}; Module.ctx.vertexAttrib3fv(index, v); }, glVertexAttrib4fv: function(index, v) { v = {{{ makeHEAPView('F32', 'v', 'v+' + (4*4)) }}}; Module.ctx.vertexAttrib4fv(index, v); }, glGetAttribLocation: function(program, name) { program = GL.programs[program]; name = Pointer_stringify(name); return Module.ctx.getAttribLocation(program, name); }, glGetActiveAttrib: function(program, index, bufSize, length, size, type, name) { program = GL.programs[program]; var info = Module.ctx.getActiveAttrib(program, index); var infoname = info.name.slice(0, bufSize - 1); writeStringToMemory(infoname, name); if (length) { {{{ makeSetValue('length', '0', 'infoname.length', 'i32') }}}; } if (size) { {{{ makeSetValue('size', '0', 'info.size', 'i32') }}}; } if (type) { {{{ makeSetValue('type', '0', 'info.type', 'i32') }}}; } }, glCreateShader__sig: 'ii', glCreateShader: function(shaderType) { var id = GL.getNewId(GL.shaders); GL.shaders[id] = Module.ctx.createShader(shaderType); return id; }, glDeleteShader: function(shader) { Module.ctx.deleteShader(GL.shaders[shader]); GL.shaders[shader] = null; }, glDetachShader: function(program, shader) { Module.ctx.detachShader(GL.programs[program], GL.shaders[shader]); }, glGetAttachedShaders: function(program, maxCount, count, shaders) { var result = Module.ctx.getAttachedShaders(GL.programs[program]); var len = result.length; if (len > maxCount) { len = maxCount; } {{{ makeSetValue('count', '0', 'len', 'i32') }}}; for (var i = 0; i < len; ++i) { {{{ makeSetValue('shaders', 'i*4', 'GL.shaders[result[i]]', 'i32') }}}; } }, glShaderSource__sig: 'viiii', glShaderSource: function(shader, count, string, length) { var source = GL.getSource(shader, count, string, length); Module.ctx.shaderSource(GL.shaders[shader], source); }, glGetShaderSource: function(shader, bufSize, length, source) { var result = Module.ctx.getShaderSource(GL.shaders[shader]); result.slice(0, bufSize - 1); writeStringToMemory(result, source); if (length) { {{{ makeSetValue('length', '0', 'result.length', 'i32') }}}; } }, glCompileShader__sig: 'vi', glCompileShader: function(shader) { Module.ctx.compileShader(GL.shaders[shader]); }, glGetShaderInfoLog: function(shader, maxLength, length, infoLog) { var log = Module.ctx.getShaderInfoLog(GL.shaders[shader]); // Work around a bug in Chromium which causes getShaderInfoLog to return null if (!log) { log = ""; } log = log.substr(0, maxLength - 1); writeStringToMemory(log, infoLog); if (length) { {{{ makeSetValue('length', '0', 'log.length', 'i32') }}} } }, glGetShaderiv : function(shader, pname, p) { if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[shader]).length + 1', 'i32') }}}; } else { {{{ makeSetValue('p', '0', 'Module.ctx.getShaderParameter(GL.shaders[shader], pname)', 'i32') }}}; } }, glGetProgramiv__sig: 'viii', glGetProgramiv : function(program, pname, p) { if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getProgramInfoLog(GL.programs[program]).length + 1', 'i32') }}}; } else { {{{ makeSetValue('p', '0', 'Module.ctx.getProgramParameter(GL.programs[program], pname)', 'i32') }}}; } }, glIsShader: function(shader) { var s = GL.shaders[shader]; if (!s) return 0; return Module.ctx.isShader(s); }, glCreateProgram__sig: 'i', glCreateProgram: function() { var id = GL.getNewId(GL.programs); GL.programs[id] = Module.ctx.createProgram(); return id; }, glDeleteProgram: function(program) { Module.ctx.deleteProgram(GL.programs[program]); GL.programs[program] = null; GL.uniformTable[program] = null; }, glAttachShader__sig: 'vii', glAttachShader: function(program, shader) { Module.ctx.attachShader(GL.programs[program], GL.shaders[shader]); }, glGetShaderPrecisionFormat: function(shaderType, precisionType, range, precision) { var result = Module.ctx.getShaderPrecisionFormat(shaderType, precisionType); {{{ makeSetValue('range', '0', 'result.rangeMin', 'i32') }}}; {{{ makeSetValue('range', '4', 'result.rangeMax', 'i32') }}}; {{{ makeSetValue('precision', '0', 'result.precision', 'i32') }}}; }, glLinkProgram__sig: 'vi', glLinkProgram: function(program) { Module.ctx.linkProgram(GL.programs[program]); GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking }, glGetProgramInfoLog: function(program, maxLength, length, infoLog) { var log = Module.ctx.getProgramInfoLog(GL.programs[program]); // Work around a bug in Chromium which causes getProgramInfoLog to return null if (!log) { log = ""; } log = log.substr(0, maxLength - 1); writeStringToMemory(log, infoLog); if (length) { {{{ makeSetValue('length', '0', 'log.length', 'i32') }}} } }, glUseProgram__sig: 'vi', glUseProgram: function(program) { Module.ctx.useProgram(program ? GL.programs[program] : null); }, glValidateProgram: function(program) { Module.ctx.validateProgram(GL.programs[program]); }, glIsProgram: function(program) { var program = GL.programs[program]; if (!program) return 0; return Module.ctx.isProgram(program); }, glBindAttribLocation__sig: 'viii', glBindAttribLocation: function(program, index, name) { name = Pointer_stringify(name); Module.ctx.bindAttribLocation(GL.programs[program], index, name); }, glBindFramebuffer__sig: 'vii', glBindFramebuffer: function(target, framebuffer) { Module.ctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : null); }, glGenFramebuffers__sig: 'vii', glGenFramebuffers: function(n, ids) { for (var i = 0; i < n; ++i) { var id = GL.getNewId(GL.framebuffers); GL.framebuffers[id] = Module.ctx.createFramebuffer(); {{{ makeSetValue('ids', 'i*4', 'id', 'i32') }}}; } }, glDeleteFramebuffers__sig: 'vii', glDeleteFramebuffers: function(n, framebuffers) { for (var i = 0; i < n; ++i) { var id = {{{ makeGetValue('framebuffers', 'i*4', 'i32') }}}; Module.ctx.deleteFramebuffer(GL.framebuffers[id]); GL.framebuffers[id] = null; } }, glFramebufferRenderbuffer__sig: 'viiii', glFramebufferRenderbuffer: function(target, attachment, renderbuffertarget, renderbuffer) { Module.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, GL.renderbuffers[renderbuffer]); }, glFramebufferTexture2D__sig: 'viiiii', glFramebufferTexture2D: function(target, attachment, textarget, texture, level) { Module.ctx.framebufferTexture2D(target, attachment, textarget, GL.textures[texture], level); }, glGetFramebufferAttachmentParameteriv__sig: 'viiii', glGetFramebufferAttachmentParameteriv: function(target, attachment, pname, params) { var result = Module.ctx.getFramebufferAttachmentParameter(target, attachment, pname); {{{ makeSetValue('params', '0', 'params', 'i32') }}}; }, glIsFramebuffer__sig: 'ii', glIsFramebuffer: function(framebuffer) { var fb = GL.framebuffers[framebuffer]; if (!fb) return 0; return Module.ctx.isFramebuffer(fb); }, // GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL $GLEmulation__postset: 'GLEmulation.init();', $GLEmulation: { // Fog support. Partial, we assume shaders are used that implement fog. We just pass them uniforms fogStart: 0, fogEnd: 1, fogDensity: 1.0, fogColor: null, fogMode: 0x0800, // GL_EXP fogEnabled: false, // VAO support vaos: [], currentVao: null, enabledVertexAttribArrays: {}, // helps with vao cleanups init: function() { GLEmulation.fogColor = new Float32Array(4); // Add some emulation workarounds Module.printErr('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work'); // XXX some of the capabilities we don't support may lead to incorrect rendering, if we do not emulate them in shaders var validCapabilities = { 0x0B44: 1, // GL_CULL_FACE 0x0BE2: 1, // GL_BLEND 0x0BD0: 1, // GL_DITHER, 0x0B90: 1, // GL_STENCIL_TEST 0x0B71: 1, // GL_DEPTH_TEST 0x0C11: 1, // GL_SCISSOR_TEST 0x8037: 1, // GL_POLYGON_OFFSET_FILL 0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE 0x80A0: 1 // GL_SAMPLE_COVERAGE }; _glEnable = function(cap) { // Clean up the renderer on any change to the rendering state. The optimization of // skipping renderer setup is aimed at the case of multiple glDraw* right after each other if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); if (cap == 0x0B60 /* GL_FOG */) { GLEmulation.fogEnabled = true; return; } else if (cap == 0x0de1 /* GL_TEXTURE_2D */) { // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support // it by forwarding to glEnableClientState _glEnableClientState(cap); return; } else if (!(cap in validCapabilities)) { return; } Module.ctx.enable(cap); }; _glDisable = function(cap) { if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); if (cap == 0x0B60 /* GL_FOG */) { GLEmulation.fogEnabled = false; return; } else if (cap == 0x0de1 /* GL_TEXTURE_2D */) { // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support // it by forwarding to glDisableClientState _glDisableClientState(cap); return; } else if (!(cap in validCapabilities)) { return; } Module.ctx.disable(cap); }; _glIsEnabled = function(cap) { if (cap == 0x0B60 /* GL_FOG */) { return GLEmulation.fogEnabled ? 1 : 0; } else if (!(cap in validCapabilities)) { return 0; } return Module.ctx.isEnabled(cap); }; var glGetBooleanv = _glGetBooleanv; _glGetBooleanv = function(pname, p) { var attrib = GLEmulation.getAttributeFromCapability(pname); if (attrib !== null) { var result = GL.immediate.enabledClientAttributes[attrib]; {{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}}; return; } glGetBooleanv(pname, p); }; var glGetIntegerv = _glGetIntegerv; _glGetIntegerv = function(pname, params) { switch (pname) { case 0x84E2: pname = Module.ctx.MAX_TEXTURE_IMAGE_UNITS /* fake it */; break; // GL_MAX_TEXTURE_UNITS case 0x8B4A: { // GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB var result = Module.ctx.getParameter(Module.ctx.MAX_VERTEX_UNIFORM_VECTORS); {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply return; } case 0x8B49: { // GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB var result = Module.ctx.getParameter(Module.ctx.MAX_FRAGMENT_UNIFORM_VECTORS); {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply return; } case 0x8B4B: { // GL_MAX_VARYING_FLOATS_ARB var result = Module.ctx.getParameter(Module.ctx.MAX_VARYING_VECTORS); {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply return; } case 0x8871: pname = Module.ctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS /* close enough */; break; // GL_MAX_TEXTURE_COORDS case 0x807A: { // GL_VERTEX_ARRAY_SIZE var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; return; } case 0x807B: { // GL_VERTEX_ARRAY_TYPE var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; return; } case 0x807C: { // GL_VERTEX_ARRAY_STRIDE var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; return; } case 0x8081: { // GL_COLOR_ARRAY_SIZE var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; return; } case 0x8082: { // GL_COLOR_ARRAY_TYPE var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; return; } case 0x8083: { // GL_COLOR_ARRAY_STRIDE var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; return; } case 0x8088: { // GL_TEXTURE_COORD_ARRAY_SIZE var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; return; } case 0x8089: { // GL_TEXTURE_COORD_ARRAY_TYPE var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; return; } case 0x808A: { // GL_TEXTURE_COORD_ARRAY_STRIDE var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; return; } } glGetIntegerv(pname, params); }; var glGetString = _glGetString; _glGetString = function(name_) { switch(name_) { case 0x1F03 /* GL_EXTENSIONS */: // Add various extensions that we can support return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ') + ' GL_EXT_texture_env_combine GL_ARB_texture_env_crossbar GL_ATI_texture_env_combine3 GL_NV_texture_env_combine4 GL_EXT_texture_env_dot3 GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_framebuffer_object GL_ARB_vertex_program GL_ARB_fragment_program GL_ARB_shading_language_100 GL_ARB_shader_objects GL_ARB_vertex_shader GL_ARB_fragment_shader GL_ARB_texture_cube_map GL_EXT_draw_range_elements' + (GL.compressionExt ? ' GL_ARB_texture_compression GL_EXT_texture_compression_s3tc' : '') + (GL.anisotropicExt ? ' GL_EXT_texture_filter_anisotropic' : '') ), 'i8', ALLOC_NORMAL); } return glGetString(name_); }; // Do some automatic rewriting to work around GLSL differences. Note that this must be done in // tandem with the rest of the program, by itself it cannot suffice. // Note that we need to remember shader types for this rewriting, saving sources makes it easier to debug. GL.shaderInfos = {}; #if GL_DEBUG GL.shaderSources = {}; GL.shaderOriginalSources = {}; #endif var glCreateShader = _glCreateShader; _glCreateShader = function(shaderType) { var id = glCreateShader(shaderType); GL.shaderInfos[id] = { type: shaderType, ftransform: false }; return id; }; var glShaderSource = _glShaderSource; _glShaderSource = function(shader, count, string, length) { var source = GL.getSource(shader, count, string, length); #if GL_DEBUG GL.shaderOriginalSources[shader] = source; #endif // XXX We add attributes and uniforms to shaders. The program can ask for the # of them, and see the // ones we generated, potentially confusing it? Perhaps we should hide them. if (GL.shaderInfos[shader].type == Module.ctx.VERTEX_SHADER) { // Replace ftransform() with explicit project/modelview transforms, and add position and matrix info. var has_pm = source.search(/u_projection/) >= 0; var has_mm = source.search(/u_modelView/) >= 0; var has_pv = source.search(/a_position/) >= 0; var need_pm = 0, need_mm = 0, need_pv = 0; var old = source; source = source.replace(/ftransform\(\)/g, '(u_projection * u_modelView * a_position)'); if (old != source) need_pm = need_mm = need_pv = 1; old = source; source = source.replace(/gl_ProjectionMatrix/g, 'u_projection'); if (old != source) need_pm = 1; old = source; source = source.replace(/gl_ModelViewMatrixTranspose\[2\]/g, 'vec4(u_modelView[0][2], u_modelView[1][2], u_modelView[2][2], u_modelView[3][2])'); // XXX extremely inefficient if (old != source) need_mm = 1; old = source; source = source.replace(/gl_ModelViewMatrix/g, 'u_modelView'); if (old != source) need_mm = 1; old = source; source = source.replace(/gl_Vertex/g, 'a_position'); if (old != source) need_pv = 1; old = source; source = source.replace(/gl_ModelViewProjectionMatrix/g, '(u_projection * u_modelView)'); if (old != source) need_pm = need_mm = 1; if (need_pv && !has_pv) source = 'attribute vec4 a_position; \n' + source; if (need_mm && !has_mm) source = 'uniform mat4 u_modelView; \n' + source; if (need_pm && !has_pm) source = 'uniform mat4 u_projection; \n' + source; GL.shaderInfos[shader].ftransform = need_pm || need_mm || need_pv; // we will need to provide the fixed function stuff as attributes and uniforms for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { // XXX To handle both regular texture mapping and cube mapping, we use vec4 for tex coordinates. var old = source; var need_vtc = source.search('v_texCoord' + i) == -1; source = source.replace(new RegExp('gl_TexCoord\\[' + i + '\\]', 'g'), 'v_texCoord' + i) .replace(new RegExp('gl_MultiTexCoord' + i, 'g'), 'a_texCoord' + i); if (source != old) { source = 'attribute vec4 a_texCoord' + i + '; \n' + source; if (need_vtc) { source = 'varying vec4 v_texCoord' + i + '; \n' + source; } } old = source; source = source.replace(new RegExp('gl_TextureMatrix\\[' + i + '\\]', 'g'), 'u_textureMatrix' + i); if (source != old) { source = 'uniform mat4 u_textureMatrix' + i + '; \n' + source; } } if (source.indexOf('gl_FrontColor') >= 0) { source = 'varying vec4 v_color; \n' + source.replace(/gl_FrontColor/g, 'v_color'); } if (source.indexOf('gl_Color') >= 0) { source = 'attribute vec4 a_color; \n' + 'uniform vec4 u_color; \n' + 'uniform int u_hasColorAttrib; \n' + source.replace(/gl_Color/g, '(u_hasColorAttrib > 0 ? a_color : u_color)'); } if (source.indexOf('gl_Normal') >= 0) { source = 'attribute vec3 a_normal; \n' + source.replace(/gl_Normal/g, 'a_normal'); } // fog if (source.indexOf('gl_FogFragCoord') >= 0) { source = 'varying float v_fogFragCoord; \n' + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); } } else { // Fragment shader for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { var old = source; source = source.replace(new RegExp('gl_TexCoord\\[' + i + '\\]', 'g'), 'v_texCoord' + i); if (source != old) { source = 'varying vec4 v_texCoord' + i + '; \n' + source; } } if (source.indexOf('gl_Color') >= 0) { source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color'); } if (source.indexOf('gl_Fog.color') >= 0) { source = 'uniform vec4 u_fogColor; \n' + source.replace(/gl_Fog.color/g, 'u_fogColor'); } if (source.indexOf('gl_Fog.end') >= 0) { source = 'uniform float u_fogEnd; \n' + source.replace(/gl_Fog.end/g, 'u_fogEnd'); } if (source.indexOf('gl_Fog.scale') >= 0) { source = 'uniform float u_fogScale; \n' + source.replace(/gl_Fog.scale/g, 'u_fogScale'); } if (source.indexOf('gl_Fog.density') >= 0) { source = 'uniform float u_fogDensity; \n' + source.replace(/gl_Fog.density/g, 'u_fogDensity'); } if (source.indexOf('gl_FogFragCoord') >= 0) { source = 'varying float v_fogFragCoord; \n' + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); } source = 'precision mediump float;\n' + source; } #if GL_DEBUG GL.shaderSources[shader] = source; #endif Module.ctx.shaderSource(GL.shaders[shader], source); }; var glCompileShader = _glCompileShader; _glCompileShader = function(shader) { Module.ctx.compileShader(GL.shaders[shader]); if (!Module.ctx.getShaderParameter(GL.shaders[shader], Module.ctx.COMPILE_STATUS)) { Module.printErr('Failed to compile shader: ' + Module.ctx.getShaderInfoLog(GL.shaders[shader])); Module.printErr('Info: ' + JSON.stringify(GL.shaderInfos[shader])); #if GL_DEBUG Module.printErr('Original source: ' + GL.shaderOriginalSources[shader]); Module.printErr('Source: ' + GL.shaderSources[shader]); throw 'Shader compilation halt'; #else Module.printErr('Enable GL_DEBUG to see shader source'); #endif } }; GL.programShaders = {}; var glAttachShader = _glAttachShader; _glAttachShader = function(program, shader) { if (!GL.programShaders[program]) GL.programShaders[program] = []; GL.programShaders[program].push(shader); glAttachShader(program, shader); }; var glUseProgram = _glUseProgram; _glUseProgram = function(program) { #if GL_DEBUG if (GL.debug) { Module.printErr('[using program with shaders]'); if (program) { GL.programShaders[program].forEach(function(shader) { Module.printErr(' shader ' + shader + ', original source: ' + GL.shaderOriginalSources[shader]); Module.printErr(' Source: ' + GL.shaderSources[shader]); }); } } #endif GL.currProgram = program; glUseProgram(program); } var glDeleteProgram = _glDeleteProgram; _glDeleteProgram = function(program) { glDeleteProgram(program); if (program == GL.currProgram) GL.currProgram = 0; }; // If attribute 0 was not bound, bind it to 0 for WebGL performance reasons. Track if 0 is free for that. var zeroUsedPrograms = {}; var glBindAttribLocation = _glBindAttribLocation; _glBindAttribLocation = function(program, index, name) { if (index == 0) zeroUsedPrograms[program] = true; glBindAttribLocation(program, index, name); }; var glLinkProgram = _glLinkProgram; _glLinkProgram = function(program) { if (!(program in zeroUsedPrograms)) { Module.ctx.bindAttribLocation(GL.programs[program], 0, 'a_position'); } glLinkProgram(program); }; var glBindBuffer = _glBindBuffer; _glBindBuffer = function(target, buffer) { glBindBuffer(target, buffer); if (target == Module.ctx.ARRAY_BUFFER) { if (GLEmulation.currentVao) { assert(GLEmulation.currentVao.arrayBuffer == buffer || GLEmulation.currentVao.arrayBuffer == 0 || buffer == 0, 'TODO: support for multiple array buffers in vao'); GLEmulation.currentVao.arrayBuffer = buffer; } } else if (target == Module.ctx.ELEMENT_ARRAY_BUFFER) { if (GLEmulation.currentVao) GLEmulation.currentVao.elementArrayBuffer = buffer; } }; var glGetFloatv = _glGetFloatv; _glGetFloatv = function(pname, params) { if (pname == 0x0BA6) { // GL_MODELVIEW_MATRIX HEAPF32.set(GL.immediate.matrix['m'], params >> 2); } else if (pname == 0x0BA7) { // GL_PROJECTION_MATRIX HEAPF32.set(GL.immediate.matrix['p'], params >> 2); } else if (pname == 0x0BA8) { // GL_TEXTURE_MATRIX HEAPF32.set(GL.immediate.matrix['t' + GL.immediate.clientActiveTexture], params >> 2); } else if (pname == 0x0B66) { // GL_FOG_COLOR HEAPF32.set(GLEmulation.fogColor, params >> 2); } else if (pname == 0x0B63) { // GL_FOG_START {{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}}; } else if (pname == 0x0B64) { // GL_FOG_END {{{ makeSetValue('params', '0', 'GLEmulation.fogEnd', 'float') }}}; } else if (pname == 0x0B62) { // GL_FOG_DENSITY {{{ makeSetValue('params', '0', 'GLEmulation.fogDensity', 'float') }}}; } else if (pname == 0x0B65) { // GL_FOG_MODE {{{ makeSetValue('params', '0', 'GLEmulation.fogMode', 'float') }}}; } else { glGetFloatv(pname, params); } }; var glHint = _glHint; _glHint = function(target, mode) { if (target == 0x84EF) { // GL_TEXTURE_COMPRESSION_HINT return; } glHint(target, mode); }; var glEnableVertexAttribArray = _glEnableVertexAttribArray; _glEnableVertexAttribArray = function(index) { glEnableVertexAttribArray(index); GLEmulation.enabledVertexAttribArrays[index] = 1; if (GLEmulation.currentVao) GLEmulation.currentVao.enabledVertexAttribArrays[index] = 1; }; var glDisableVertexAttribArray = _glDisableVertexAttribArray; _glDisableVertexAttribArray = function(index) { glDisableVertexAttribArray(index); delete GLEmulation.enabledVertexAttribArrays[index]; if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledVertexAttribArrays[index]; }; var glVertexAttribPointer = _glVertexAttribPointer; _glVertexAttribPointer = function(index, size, type, normalized, stride, pointer) { glVertexAttribPointer(index, size, type, normalized, stride, pointer); if (GLEmulation.currentVao) { // TODO: avoid object creation here? likely not hot though GLEmulation.currentVao.vertexAttribPointers[index] = [index, size, type, normalized, stride, pointer]; } }; }, getAttributeFromCapability: function(cap) { var attrib = null; switch (cap) { case 0x8078: // GL_TEXTURE_COORD_ARRAY case 0x0de1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break; case 0x8074: // GL_VERTEX_ARRAY attrib = GL.immediate.VERTEX; break; case 0x8075: // GL_NORMAL_ARRAY attrib = GL.immediate.NORMAL; break; case 0x8076: // GL_COLOR_ARRAY attrib = GL.immediate.COLOR; break; } return attrib; }, getProcAddress: function(name) { name = name.replace('EXT', '').replace('ARB', ''); // Do the translation carefully because of closure var ret = 0; switch (name) { case 'glCreateShaderObject': case 'glCreateShader': ret = {{{ Functions.getIndex('_glCreateShader', true) }}}; break; case 'glCreateProgramObject': case 'glCreateProgram': ret = {{{ Functions.getIndex('_glCreateProgram', true) }}}; break; case 'glAttachObject': case 'glAttachShader': ret = {{{ Functions.getIndex('_glAttachShader', true) }}}; break; case 'glUseProgramObject': case 'glUseProgram': ret = {{{ Functions.getIndex('_glUseProgram', true) }}}; break; case 'glDeleteObject': ret = {{{ Functions.getIndex('_glDeleteObject', true) }}}; break; case 'glGetObjectParameteriv': ret = {{{ Functions.getIndex('_glGetObjectParameteriv', true) }}}; break; case 'glGetInfoLog': ret = {{{ Functions.getIndex('_glGetInfoLog', true) }}}; break; case 'glBindProgram': ret = {{{ Functions.getIndex('_glBindProgram', true) }}}; break; case 'glDrawRangeElements': ret = {{{ Functions.getIndex('_glDrawRangeElements', true) }}}; break; case 'glShaderSource': ret = {{{ Functions.getIndex('_glShaderSource', true) }}}; break; case 'glCompileShader': ret = {{{ Functions.getIndex('_glCompileShader', true) }}}; break; case 'glLinkProgram': ret = {{{ Functions.getIndex('_glLinkProgram', true) }}}; break; case 'glGetUniformLocation': ret = {{{ Functions.getIndex('_glGetUniformLocation', true) }}}; break; case 'glUniform1f': ret = {{{ Functions.getIndex('_glUniform1f', true) }}}; break; case 'glUniform2f': ret = {{{ Functions.getIndex('_glUniform2f', true) }}}; break; case 'glUniform3f': ret = {{{ Functions.getIndex('_glUniform3f', true) }}}; break; case 'glUniform4f': ret = {{{ Functions.getIndex('_glUniform4f', true) }}}; break; case 'glUniform1fv': ret = {{{ Functions.getIndex('_glUniform1fv', true) }}}; break; case 'glUniform2fv': ret = {{{ Functions.getIndex('_glUniform2fv', true) }}}; break; case 'glUniform3fv': ret = {{{ Functions.getIndex('_glUniform3fv', true) }}}; break; case 'glUniform4fv': ret = {{{ Functions.getIndex('_glUniform4fv', true) }}}; break; case 'glUniform1i': ret = {{{ Functions.getIndex('_glUniform1i', true) }}}; break; case 'glUniform2i': ret = {{{ Functions.getIndex('_glUniform2i', true) }}}; break; case 'glUniform3i': ret = {{{ Functions.getIndex('_glUniform3i', true) }}}; break; case 'glUniform4i': ret = {{{ Functions.getIndex('_glUniform4i', true) }}}; break; case 'glUniform1iv': ret = {{{ Functions.getIndex('_glUniform1iv', true) }}}; break; case 'glUniform2iv': ret = {{{ Functions.getIndex('_glUniform2iv', true) }}}; break; case 'glUniform3iv': ret = {{{ Functions.getIndex('_glUniform3iv', true) }}}; break; case 'glUniform4iv': ret = {{{ Functions.getIndex('_glUniform4iv', true) }}}; break; case 'glBindAttribLocation': ret = {{{ Functions.getIndex('_glBindAttribLocation', true) }}}; break; case 'glGetActiveUniform': ret = {{{ Functions.getIndex('_glGetActiveUniform', true) }}}; break; case 'glGenBuffers': ret = {{{ Functions.getIndex('_glGenBuffers', true) }}}; break; case 'glBindBuffer': ret = {{{ Functions.getIndex('_glBindBuffer', true) }}}; break; case 'glBufferData': ret = {{{ Functions.getIndex('_glBufferData', true) }}}; break; case 'glBufferSubData': ret = {{{ Functions.getIndex('_glBufferSubData', true) }}}; break; case 'glDeleteBuffers': ret = {{{ Functions.getIndex('_glDeleteBuffers', true) }}}; break; case 'glActiveTexture': ret = {{{ Functions.getIndex('_glActiveTexture', true) }}}; break; case 'glClientActiveTexture': ret = {{{ Functions.getIndex('_glClientActiveTexture', true) }}}; break; case 'glGetProgramiv': ret = {{{ Functions.getIndex('_glGetProgramiv', true) }}}; break; case 'glEnableVertexAttribArray': ret = {{{ Functions.getIndex('_glEnableVertexAttribArray', true) }}}; break; case 'glDisableVertexAttribArray': ret = {{{ Functions.getIndex('_glDisableVertexAttribArray', true) }}}; break; case 'glVertexAttribPointer': ret = {{{ Functions.getIndex('_glVertexAttribPointer', true) }}}; break; case 'glBindRenderbuffer': ret = {{{ Functions.getIndex('_glBindRenderbuffer', true) }}}; break; case 'glDeleteRenderbuffers': ret = {{{ Functions.getIndex('_glDeleteRenderbuffers', true) }}}; break; case 'glGenRenderbuffers': ret = {{{ Functions.getIndex('_glGenRenderbuffers', true) }}}; break; case 'glCompressedTexImage2D': ret = {{{ Functions.getIndex('_glCompressedTexImage2D', true) }}}; break; case 'glCompressedTexSubImage2D': ret = {{{ Functions.getIndex('_glCompressedTexSubImage2D', true) }}}; break; case 'glBindFramebuffer': ret = {{{ Functions.getIndex('_glBindFramebuffer', true) }}}; break; case 'glGenFramebuffers': ret = {{{ Functions.getIndex('_glGenFramebuffers', true) }}}; break; case 'glDeleteFramebuffers': ret = {{{ Functions.getIndex('_glDeleteFramebuffers', true) }}}; break; case 'glFramebufferRenderbuffer': ret = {{{ Functions.getIndex('_glFramebufferRenderbuffer', true) }}}; break; case 'glFramebufferTexture2D': ret = {{{ Functions.getIndex('_glFramebufferTexture2D', true) }}}; break; case 'glGetFramebufferAttachmentParameteriv': ret = {{{ Functions.getIndex('_glGetFramebufferAttachmentParameteriv', true) }}}; break; case 'glIsFramebuffer': ret = {{{ Functions.getIndex('_glIsFramebuffer', true) }}}; break; case 'glCheckFramebufferStatus': ret = {{{ Functions.getIndex('_glCheckFramebufferStatus', true) }}}; break; case 'glRenderbufferStorage': ret = {{{ Functions.getIndex('_glRenderbufferStorage', true) }}}; break; case 'glGenVertexArrays': ret = {{{ Functions.getIndex('_glGenVertexArrays', true) }}}; break; case 'glDeleteVertexArrays': ret = {{{ Functions.getIndex('_glDeleteVertexArrays', true) }}}; break; case 'glBindVertexArray': ret = {{{ Functions.getIndex('_glBindVertexArray', true) }}}; break; } if (!ret) Module.printErr('WARNING: getProcAddress failed for ' + name); return ret; } }, glDeleteObject__sig: 'vi', glDeleteObject: function(id) { if (GL.programs[id]) { _glDeleteProgram(id); } else if (GL.shaders[id]) { _glDeleteShader(id); } else { Module.printErr('WARNING: deleteObject received invalid id: ' + id); } }, glGetObjectParameteriv__sig: 'viii', glGetObjectParameteriv: function(id, type, result) { if (GL.programs[id]) { if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB {{{ makeSetValue('result', '0', 'Module.ctx.getProgramInfoLog(GL.programs[id]).length', 'i32') }}}; return; } _glGetProgramiv(id, type, result); } else if (GL.shaders[id]) { if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB {{{ makeSetValue('result', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[id]).length', 'i32') }}}; return; } _glGetShaderiv(id, type, result); } else { Module.printErr('WARNING: getObjectParameteriv received invalid id: ' + id); } }, glGetInfoLog__sig: 'viiii', glGetInfoLog: function(id, maxLength, length, infoLog) { if (GL.programs[id]) { _glGetProgramInfoLog(id, maxLength, length, infoLog); } else if (GL.shaders[id]) { _glGetShaderInfoLog(id, maxLength, length, infoLog); } else { Module.printErr('WARNING: getObjectParameteriv received invalid id: ' + id); } }, glBindProgram__sig: 'vii', glBindProgram: function(type, id) { assert(id == 0); }, glGetPointerv: function(name, p) { var attribute; switch(name) { case 0x808E: // GL_VERTEX_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; break; case 0x8090: // GL_COLOR_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0]; break; default: throw 'TODO: glGetPointerv for ' + name; } {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; }, // GL Immediate mode $GLImmediate__postset: 'GL.immediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(function() { GL.immediate.init() });', $GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'], $GLImmediate: { MAX_TEXTURES: 7, // Vertex and index data vertexData: null, // current vertex data. either tempData (glBegin etc.) or a view into the heap (gl*Pointer). Default view is F32 vertexDataU8: null, // U8 view tempData: null, indexData: null, vertexCounter: 0, mode: -1, rendererCache: null, rendererCacheItemTemplate: [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null], // 16 nulls rendererComponents: [], // small cache for calls inside glBegin/end. counts how many times the element was seen rendererComponentPointer: 0, // next place to start a glBegin/end component lastRenderer: null, // used to avoid cleaning up and re-preparing the same renderer lastArrayBuffer: null, // used in conjunction with lastRenderer lastProgram: null, // "" // The following data structures are used for OpenGL Immediate Mode matrix routines. matrix: {}, matrixStack: {}, currentMatrix: 'm', // default is modelview tempMatrix: null, matricesModified: false, // Clientside attributes VERTEX: 0, NORMAL: 1, COLOR: 2, TEXTURE0: 3, TEXTURE1: 4, TEXTURE2: 5, TEXTURE3: 6, TEXTURE4: 7, TEXTURE5: 8, TEXTURE6: 9, NUM_ATTRIBUTES: 10, NUM_TEXTURES: 7, totalEnabledClientAttributes: 0, enabledClientAttributes: [0, 0], clientAttributes: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], // raw data, including possible unneeded ones liveClientAttributes: [], // the ones actually alive in the current computation, sorted modifiedClientAttributes: false, clientActiveTexture: 0, clientColor: null, setClientAttribute: function(name, size, type, stride, pointer) { var attrib = this.clientAttributes[name]; attrib.name = name; attrib.size = size; attrib.type = type; attrib.stride = stride; attrib.pointer = pointer; this.modifiedClientAttributes = true; }, // Temporary buffers MAX_TEMP_BUFFER_SIZE: {{{ GL_MAX_TEMP_BUFFER_SIZE }}}, tempBufferIndexLookup: null, tempVertexBuffers: null, tempIndexBuffers: null, tempQuadIndexBuffer: null, generateTempBuffers: function() { this.tempBufferIndexLookup = new Uint8Array(this.MAX_TEMP_BUFFER_SIZE+1); this.tempVertexBuffers = []; this.tempIndexBuffers = []; var last = -1, curr = -1; var size = 1; for (var i = 0; i <= this.MAX_TEMP_BUFFER_SIZE; i++) { if (i > size) { size <<= 1; } if (size != last) { curr++; this.tempVertexBuffers[curr] = Module.ctx.createBuffer(); Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, this.tempVertexBuffers[curr]); Module.ctx.bufferData(Module.ctx.ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); this.tempIndexBuffers[curr] = Module.ctx.createBuffer(); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempIndexBuffers[curr]); Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, size, Module.ctx.DYNAMIC_DRAW); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); last = size; } this.tempBufferIndexLookup[i] = curr; } // GL_QUAD indexes can be precalculated this.tempQuadIndexBuffer = Module.ctx.createBuffer(); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); var numIndexes = this.MAX_TEMP_BUFFER_SIZE >> 1; var quadIndexes = new Uint16Array(numIndexes); var i = 0, v = 0; while (1) { quadIndexes[i++] = v; if (i >= numIndexes) break; quadIndexes[i++] = v+1; if (i >= numIndexes) break; quadIndexes[i++] = v+2; if (i >= numIndexes) break; quadIndexes[i++] = v; if (i >= numIndexes) break; quadIndexes[i++] = v+2; if (i >= numIndexes) break; quadIndexes[i++] = v+3; if (i >= numIndexes) break; v += 4; } Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, quadIndexes, Module.ctx.STATIC_DRAW); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); }, // Renderers addRendererComponent: function(name, size, type) { if (!this.rendererComponents[name]) { this.rendererComponents[name] = 1; #if ASSERTIONS assert(!this.enabledClientAttributes[name]); // cannot get mixed up with this, for example we will disable this later #endif this.enabledClientAttributes[name] = true; this.setClientAttribute(name, size, type, 0, this.rendererComponentPointer); this.rendererComponentPointer += size * GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; } else { this.rendererComponents[name]++; } }, disableBeginEndClientAttributes: function() { for (var i = 0; i < this.NUM_ATTRIBUTES; i++) { if (this.rendererComponents[i]) this.enabledClientAttributes[i] = false; } }, getRenderer: function() { // return a renderer object given the liveClientAttributes // we maintain a cache of renderers, optimized to not generate garbage var attributes = GL.immediate.liveClientAttributes; var cacheItem = GL.immediate.rendererCache; var temp; for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; temp = cacheItem[attribute.name]; cacheItem = temp ? temp : (cacheItem[attribute.name] = GL.immediate.rendererCacheItemTemplate.slice()); temp = cacheItem[attribute.size]; cacheItem = temp ? temp : (cacheItem[attribute.size] = GL.immediate.rendererCacheItemTemplate.slice()); var typeIndex = attribute.type - GL.byteSizeByTypeRoot; // ensure it starts at 0 to keep the cache items dense temp = cacheItem[typeIndex]; cacheItem = temp ? temp : (cacheItem[typeIndex] = GL.immediate.rendererCacheItemTemplate.slice()); } var fogParam; if (GLEmulation.fogEnabled) { switch (GLEmulation.fogMode) { case 0x0801: // GL_EXP2 fogParam = 1; break; case 0x2601: // GL_LINEAR fogParam = 2; break; default: // default to GL_EXP fogParam = 3; break; } } else { fogParam = 0; } temp = cacheItem[fogParam]; cacheItem = temp ? temp : (cacheItem[fogParam] = GL.immediate.rendererCacheItemTemplate.slice()); if (GL.currProgram) { // Note the order here; this one is last, and optional. Note that we cannot ensure it is dense, sadly temp = cacheItem[GL.currProgram]; cacheItem = temp ? temp : (cacheItem[GL.currProgram] = GL.immediate.rendererCacheItemTemplate.slice()); } if (!cacheItem.renderer) { #if GL_DEBUG Module.printErr('generating renderer for ' + JSON.stringify(attributes)); #endif cacheItem.renderer = this.createRenderer(); } return cacheItem.renderer; }, createRenderer: function(renderer) { var useCurrProgram = !!GL.currProgram; var hasTextures = false, textureSizes = [], textureTypes = [], textureOffsets = []; for (var i = 0; i < GL.immediate.NUM_TEXTURES; i++) { if (GL.immediate.enabledClientAttributes[GL.immediate.TEXTURE0 + i]) { textureSizes[i] = GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + i].size; textureTypes[i] = GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + i].type; textureOffsets[i] = GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + i].offset; hasTextures = true; } } var stride = GL.immediate.stride; var positionSize = GL.immediate.clientAttributes[GL.immediate.VERTEX].size; var positionType = GL.immediate.clientAttributes[GL.immediate.VERTEX].type; var positionOffset = GL.immediate.clientAttributes[GL.immediate.VERTEX].offset; var colorSize = 0, colorType, colorOffset; if (GL.immediate.enabledClientAttributes[GL.immediate.COLOR]) { colorSize = GL.immediate.clientAttributes[GL.immediate.COLOR].size; colorType = GL.immediate.clientAttributes[GL.immediate.COLOR].type; colorOffset = GL.immediate.clientAttributes[GL.immediate.COLOR].offset; } var normalSize = 0, normalType, normalOffset; if (GL.immediate.enabledClientAttributes[GL.immediate.NORMAL]) { normalSize = GL.immediate.clientAttributes[GL.immediate.NORMAL].size; normalType = GL.immediate.clientAttributes[GL.immediate.NORMAL].type; normalOffset = GL.immediate.clientAttributes[GL.immediate.NORMAL].offset; } var ret = { init: function() { if (useCurrProgram) { if (GL.shaderInfos[GL.programShaders[GL.currProgram][0]].type == Module.ctx.VERTEX_SHADER) { this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][0]]; this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][1]]; } else { this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][1]]; this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][0]]; } this.program = GL.programs[GL.currProgram]; } else { // IMPORTANT NOTE: If you parameterize the shader source based on any runtime values // in order to create the least expensive shader possible based on the features being // used, you should also update the code in the beginning of getRenderer to make sure // that you cache the renderer based on the said parameters. this.vertexShader = Module.ctx.createShader(Module.ctx.VERTEX_SHADER); var zero = positionSize == 2 ? '0, ' : ''; if (GLEmulation.fogEnabled) { switch (GLEmulation.fogMode) { case 0x0801: // GL_EXP2 // fog = exp(-(gl_Fog.density * gl_FogFragCoord)^2) var fogFormula = ' float fog = exp(-u_fogDensity * u_fogDensity * ecDistance * ecDistance); \n'; break; case 0x2601: // GL_LINEAR // fog = (gl_Fog.end - gl_FogFragCoord) * gl_fog.scale var fogFormula = ' float fog = (u_fogEnd - ecDistance) * u_fogScale; \n'; break; default: // default to GL_EXP // fog = exp(-gl_Fog.density * gl_FogFragCoord) var fogFormula = ' float fog = exp(-u_fogDensity * ecDistance); \n'; break; } } Module.ctx.shaderSource(this.vertexShader, 'attribute vec' + positionSize + ' a_position; \n' + 'attribute vec2 a_texCoord0; \n' + (hasTextures ? 'varying vec2 v_texCoord; \n' : '') + 'varying vec4 v_color; \n' + (colorSize ? 'attribute vec4 a_color; \n': 'uniform vec4 u_color; \n') + (GLEmulation.fogEnabled ? 'varying float v_fogFragCoord; \n' : '') + 'uniform mat4 u_modelView; \n' + 'uniform mat4 u_projection; \n' + 'void main() \n' + '{ \n' + ' vec4 ecPosition = (u_modelView * vec4(a_position, ' + zero + '1.0)); \n' + // eye-coordinate position ' gl_Position = u_projection * ecPosition; \n' + (hasTextures ? 'v_texCoord = a_texCoord0; \n' : '') + (colorSize ? 'v_color = a_color; \n' : 'v_color = u_color; \n') + (GLEmulation.fogEnabled ? 'v_fogFragCoord = abs(ecPosition.z);\n' : '') + '} \n'); Module.ctx.compileShader(this.vertexShader); this.fragmentShader = Module.ctx.createShader(Module.ctx.FRAGMENT_SHADER); Module.ctx.shaderSource(this.fragmentShader, 'precision mediump float; \n' + 'varying vec2 v_texCoord; \n' + 'uniform sampler2D u_texture; \n' + 'varying vec4 v_color; \n' + (GLEmulation.fogEnabled ? ( 'varying float v_fogFragCoord; \n' + 'uniform vec4 u_fogColor; \n' + 'uniform float u_fogEnd; \n' + 'uniform float u_fogScale; \n' + 'uniform float u_fogDensity; \n' + 'float ffog(in float ecDistance) { \n' + fogFormula + ' fog = clamp(fog, 0.0, 1.0); \n' + ' return fog; \n' + '} \n' ) : '') + 'void main() \n' + '{ \n' + (hasTextures ? 'gl_FragColor = v_color * texture2D( u_texture, v_texCoord );\n' : 'gl_FragColor = v_color;\n') + (GLEmulation.fogEnabled ? 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a); \n' : '') + '} \n'); Module.ctx.compileShader(this.fragmentShader); this.program = Module.ctx.createProgram(); Module.ctx.attachShader(this.program, this.vertexShader); Module.ctx.attachShader(this.program, this.fragmentShader); Module.ctx.bindAttribLocation(this.program, 0, 'a_position'); Module.ctx.linkProgram(this.program); } this.positionLocation = Module.ctx.getAttribLocation(this.program, 'a_position'); this.texCoordLocations = []; for (var i = 0; i < textureSizes.length; i++) { if (textureSizes[i]) { this.texCoordLocations[i] = Module.ctx.getAttribLocation(this.program, 'a_texCoord' + i); } } this.textureMatrixLocations = []; for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { this.textureMatrixLocations[i] = Module.ctx.getUniformLocation(this.program, 'u_textureMatrix' + i); } this.colorLocation = Module.ctx.getAttribLocation(this.program, 'a_color'); this.normalLocation = Module.ctx.getAttribLocation(this.program, 'a_normal'); this.textureLocation = Module.ctx.getUniformLocation(this.program, 'u_texture'); // only for immediate mode with no shaders, so only one is enough this.modelViewLocation = Module.ctx.getUniformLocation(this.program, 'u_modelView'); this.projectionLocation = Module.ctx.getUniformLocation(this.program, 'u_projection'); this.hasColorAttribLocation = Module.ctx.getUniformLocation(this.program, 'u_hasColorAttrib'); this.colorUniformLocation = Module.ctx.getUniformLocation(this.program, 'u_color'); this.hasTextures = hasTextures; this.hasColorAttrib = colorSize > 0 && this.colorLocation >= 0; this.hasColorUniform = !!this.colorUniformLocation; this.hasNormal = normalSize > 0 && this.normalLocation >= 0; this.floatType = Module.ctx.FLOAT; // minor optimization this.fogColorLocation = Module.ctx.getUniformLocation(this.program, 'u_fogColor'); this.fogEndLocation = Module.ctx.getUniformLocation(this.program, 'u_fogEnd'); this.fogScaleLocation = Module.ctx.getUniformLocation(this.program, 'u_fogScale'); this.fogDensityLocation = Module.ctx.getUniformLocation(this.program, 'u_fogDensity'); this.hasFog = !!(this.fogColorLocation || this.fogEndLocation || this.fogScaleLocation || this.fogDensityLocation); }, prepare: function() { // Calculate the array buffer var arrayBuffer; if (!GL.currArrayBuffer) { var start = GL.immediate.firstVertex*GL.immediate.stride; var end = GL.immediate.lastVertex*GL.immediate.stride; assert(end <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); arrayBuffer = GL.immediate.tempVertexBuffers[GL.immediate.tempBufferIndexLookup[end]]; // TODO: consider using the last buffer we bound, if it was larger. downside is larger buffer, but we might avoid rebinding and preparing } else { arrayBuffer = GL.currArrayBuffer; } // If the array buffer is unchanged and the renderer as well, then we can avoid all the work here // XXX We use some heuristics here, and this may not work in all cases. Try disabling GL_UNSAFE_OPTS if you // have odd glitches #if GL_UNSAFE_OPTS var lastRenderer = GL.immediate.lastRenderer; var canSkip = this == lastRenderer && arrayBuffer == GL.immediate.lastArrayBuffer && (GL.currProgram || this.program) == GL.immediate.lastProgram && !GL.immediate.matricesModified; if (!canSkip && lastRenderer) lastRenderer.cleanup(); #endif if (!GL.currArrayBuffer) { // Bind the array buffer and upload data after cleaning up the previous renderer #if GL_UNSAFE_OPTS // Potentially unsafe, since lastArrayBuffer might not reflect the true array buffer in code that mixes immediate/non-immediate if (arrayBuffer != GL.immediate.lastArrayBuffer) { #endif Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, arrayBuffer); #if GL_UNSAFE_OPTS } #endif Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, GL.immediate.vertexData.subarray(start >> 2, end >> 2)); } #if GL_UNSAFE_OPTS if (canSkip) return; GL.immediate.lastRenderer = this; GL.immediate.lastArrayBuffer = arrayBuffer; GL.immediate.lastProgram = GL.currProgram || this.program; GL.immediate.matricesModified = false; #endif if (!GL.currProgram) { Module.ctx.useProgram(this.program); } if (this.modelViewLocation) Module.ctx.uniformMatrix4fv(this.modelViewLocation, false, GL.immediate.matrix['m']); if (this.projectionLocation) Module.ctx.uniformMatrix4fv(this.projectionLocation, false, GL.immediate.matrix['p']); Module.ctx.vertexAttribPointer(this.positionLocation, positionSize, positionType, false, stride, positionOffset); Module.ctx.enableVertexAttribArray(this.positionLocation); if (this.hasTextures) { for (var i = 0; i < textureSizes.length; i++) { if (textureSizes[i] && this.texCoordLocations[i] >= 0) { Module.ctx.vertexAttribPointer(this.texCoordLocations[i], textureSizes[i], textureTypes[i], false, stride, textureOffsets[i]); Module.ctx.enableVertexAttribArray(this.texCoordLocations[i]); } } for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { if (this.textureMatrixLocations[i]) { // XXX might we need this even without the condition we are currently in? Module.ctx.uniformMatrix4fv(this.textureMatrixLocations[i], false, GL.immediate.matrix['t' + i]); } } } if (this.hasColorAttrib) { Module.ctx.vertexAttribPointer(this.colorLocation, colorSize, colorType, true, stride, colorOffset); Module.ctx.enableVertexAttribArray(this.colorLocation); Module.ctx.uniform1i(this.hasColorAttribLocation, 1); } else if (this.hasColorUniform) { Module.ctx.uniform1i(this.hasColorAttribLocation, 0); Module.ctx.uniform4fv(this.colorUniformLocation, GL.immediate.clientColor); } if (this.hasNormal) { Module.ctx.vertexAttribPointer(this.normalLocation, normalSize, normalType, true, stride, normalOffset); Module.ctx.enableVertexAttribArray(this.normalLocation); } if (!useCurrProgram) { // otherwise, the user program will set the sampler2D binding and uniform itself var texture = Module.ctx.getParameter(Module.ctx.TEXTURE_BINDING_2D); Module.ctx.activeTexture(Module.ctx.TEXTURE0); Module.ctx.bindTexture(Module.ctx.TEXTURE_2D, texture); Module.ctx.uniform1i(this.textureLocation, 0); } if (this.hasFog) { if (this.fogColorLocation) Module.ctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor); if (this.fogEndLocation) Module.ctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd); if (this.fogScaleLocation) Module.ctx.uniform1f(this.fogScaleLocation, 1/(GLEmulation.fogEnd - GLEmulation.fogStart)); if (this.fogDensityLocation) Module.ctx.uniform1f(this.fogDensityLocation, GLEmulation.fogDensity); } }, cleanup: function() { Module.ctx.disableVertexAttribArray(this.positionLocation); if (this.hasTextures) { for (var i = 0; i < textureSizes.length; i++) { if (textureSizes[i] && this.texCoordLocations[i] >= 0) { Module.ctx.disableVertexAttribArray(this.texCoordLocations[i]); } } } if (this.hasColorAttrib) { Module.ctx.disableVertexAttribArray(this.colorLocation); } if (this.hasNormal) { Module.ctx.disableVertexAttribArray(this.normalLocation); } if (!GL.currProgram) { Module.ctx.useProgram(null); } if (!GL.currArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); } #if GL_UNSAFE_OPTS GL.immediate.lastRenderer = null; GL.immediate.lastArrayBuffer = null; GL.immediate.lastProgram = null; #endif GL.immediate.matricesModified = true; } }; ret.init(); return ret; }, setupFuncs: function() { // Replace some functions with immediate-mode aware versions. If there are no client // attributes enabled, and we use webgl-friendly modes (no GL_QUADS), then no need // for emulation _glDrawArrays = function(mode, first, count) { if (GL.immediate.totalEnabledClientAttributes == 0 && mode <= 6) { Module.ctx.drawArrays(mode, first, count); return; } GL.immediate.prepareClientAttributes(count, false); GL.immediate.mode = mode; if (!GL.currArrayBuffer) { GL.immediate.vertexData = {{{ makeHEAPView('F32', 'GL.immediate.vertexPointer', 'GL.immediate.vertexPointer + (first+count)*GL.immediate.stride') }}}; // XXX assuming float GL.immediate.firstVertex = first; GL.immediate.lastVertex = first + count; } GL.immediate.flush(null, first); GL.immediate.mode = -1; }; _glDrawElements = function(mode, count, type, indices, start, end) { // start, end are given if we come from glDrawRangeElements if (GL.immediate.totalEnabledClientAttributes == 0 && mode <= 6 && GL.currElementArrayBuffer) { Module.ctx.drawElements(mode, count, type, indices); return; } if (!GL.currElementArrayBuffer) { assert(type == Module.ctx.UNSIGNED_SHORT); // We can only emulate buffers of this kind, for now } GL.immediate.prepareClientAttributes(count, false); GL.immediate.mode = mode; if (!GL.currArrayBuffer) { GL.immediate.firstVertex = end ? start : TOTAL_MEMORY; // if we don't know the start, set an invalid value and we will calculate it later from the indices GL.immediate.lastVertex = end ? end+1 : 0; GL.immediate.vertexData = {{{ makeHEAPView('F32', 'GL.immediate.vertexPointer', '(end ? GL.immediate.vertexPointer + (end+1)*GL.immediate.stride : TOTAL_MEMORY)') }}}; // XXX assuming float } GL.immediate.flush(count, 0, indices); GL.immediate.mode = -1; }; }, // Main functions initted: false, init: function() { Module.printErr('WARNING: using emscripten GL immediate mode emulation. This is very limited in what it supports'); GL.immediate.initted = true; if (!Module.useWebGL) return; // a 2D canvas may be currently used TODO: make sure we are actually called in that case this.matrixStack['m'] = []; this.matrixStack['p'] = []; for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { this.matrixStack['t' + i] = []; } // Initialize matrix library GL.immediate.matrix['m'] = GL.immediate.matrix.lib.mat4.create(); GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['m']); GL.immediate.matrix['p'] = GL.immediate.matrix.lib.mat4.create(); GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['p']); for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { GL.immediate.matrix['t' + i] = GL.immediate.matrix.lib.mat4.create(); } // Renderer cache this.rendererCache = this.rendererCacheItemTemplate.slice(); // Buffers for data this.tempData = new Float32Array(this.MAX_TEMP_BUFFER_SIZE >> 2); this.indexData = new Uint16Array(this.MAX_TEMP_BUFFER_SIZE >> 1); this.vertexDataU8 = new Uint8Array(this.tempData.buffer); this.generateTempBuffers(); this.clientColor = new Float32Array([1, 1, 1, 1]); }, // Prepares and analyzes client attributes. // Modifies liveClientAttributes, stride, vertexPointer, vertexCounter // count: number of elements we will draw // beginEnd: whether we are drawing the results of a begin/end block prepareClientAttributes: function(count, beginEnd) { // If no client attributes were modified since we were last called, do nothing. Note that this // does not work for glBegin/End, where we generate renderer components dynamically and then // disable them ourselves, but it does help with glDrawElements/Arrays. if (!this.modifiedClientAttributes) { return; } this.modifiedClientAttributes = false; var stride = 0, start; var attributes = GL.immediate.liveClientAttributes; attributes.length = 0; for (var i = 0; i < GL.immediate.NUM_ATTRIBUTES; i++) { if (GL.immediate.enabledClientAttributes[i]) attributes.push(GL.immediate.clientAttributes[i]); } attributes.sort(function(x, y) { return !x ? (!y ? 0 : 1) : (!y ? -1 : (x.pointer - y.pointer)) }); start = attributes[0].pointer; for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; if (!attribute) break; #if ASSERTIONS assert(stride == 0 || stride == attribute.stride); // must all be in the same buffer #endif if (attribute.stride) stride = attribute.stride; } var bytes = 0; // total size in bytes if (!stride && !beginEnd) { // beginEnd can not have stride in the attributes, that is fine. otherwise, // no stride means that all attributes are in fact packed. to keep the rest of // our emulation code simple, we perform unpacking/restriding here. this adds overhead, so // it is a good idea to not hit this! #if ASSERTIONS Runtime.warnOnce('Unpacking/restriding attributes, this is not fast'); #endif if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.immediate.MAX_TEMP_BUFFER_SIZE); start = GL.immediate.restrideBuffer; #if ASSERTIONS assert(start % 4 == 0); #endif // calculate restrided offsets and total size for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; if (!attribute) break; var size = attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot]; if (size % 4 != 0) size += 4 - (size % 4); // align everything attribute.offset = bytes; bytes += size; } #if ASSERTIONS assert(count*bytes <= GL.immediate.MAX_TEMP_BUFFER_SIZE); #endif // copy out the data (we need to know the stride for that, and define attribute.pointer for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; if (!attribute) break; var size4 = Math.floor((attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot])/4); for (var j = 0; j < count; j++) { for (var k = 0; k < size4; k++) { // copy in chunks of 4 bytes, our alignment makes this possible HEAP32[((start + attribute.offset + bytes*j)>>2) + k] = HEAP32[(attribute.pointer>>2) + j*size4 + k]; } } attribute.pointer = start + attribute.offset; } } else { // normal situation, everything is strided and in the same buffer for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; if (!attribute) break; attribute.offset = attribute.pointer - start; if (attribute.offset > bytes) { // ensure we start where we should assert((attribute.offset - bytes)%4 == 0); // XXX assuming 4-alignment bytes += attribute.offset - bytes; } bytes += attribute.size * GL.byteSizeByType[attribute.type - GL.byteSizeByTypeRoot]; if (bytes % 4 != 0) bytes += 4 - (bytes % 4); // XXX assuming 4-alignment } assert(beginEnd || bytes <= stride); // if not begin-end, explicit stride should make sense with total byte size if (bytes < stride) { // ensure the size is that of the stride bytes = stride; } } GL.immediate.stride = bytes; if (!beginEnd) { bytes *= count; if (!GL.currArrayBuffer) { GL.immediate.vertexPointer = start; } GL.immediate.vertexCounter = bytes / 4; // XXX assuming float } }, flush: function(numProvidedIndexes, startIndex, ptr) { #if ASSERTIONS assert(numProvidedIndexes >= 0 || !numProvidedIndexes); #endif startIndex = startIndex || 0; ptr = ptr || 0; var renderer = this.getRenderer(); // Generate index data in a format suitable for GLES 2.0/WebGL var numVertexes = 4 * this.vertexCounter / GL.immediate.stride; // XXX assuming float assert(numVertexes % 1 == 0); var emulatedElementArrayBuffer = false; var numIndexes = 0; if (numProvidedIndexes) { numIndexes = numProvidedIndexes; if (!GL.currArrayBuffer && GL.immediate.firstVertex > GL.immediate.lastVertex) { // Figure out the first and last vertex from the index data assert(!GL.currElementArrayBuffer); // If we are going to upload array buffer data, we need to find which range to // upload based on the indices. If they are in a buffer on the GPU, that is very // inconvenient! So if you do not have an array buffer, you should also not have // an element array buffer. But best is to use both buffers! for (var i = 0; i < numProvidedIndexes; i++) { var currIndex = {{{ makeGetValue('ptr', 'i*2', 'i16', null, 1) }}}; GL.immediate.firstVertex = Math.min(GL.immediate.firstVertex, currIndex); GL.immediate.lastVertex = Math.max(GL.immediate.lastVertex, currIndex+1); } } if (!GL.currElementArrayBuffer) { // If no element array buffer is bound, then indices is a literal pointer to clientside data assert(numProvidedIndexes << 1 <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); var indexBuffer = GL.immediate.tempIndexBuffers[GL.immediate.tempBufferIndexLookup[numProvidedIndexes << 1]]; Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, indexBuffer); Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, 0, {{{ makeHEAPView('U16', 'ptr', 'ptr + (numProvidedIndexes << 1)') }}}); ptr = 0; emulatedElementArrayBuffer = true; } } else if (GL.immediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes if (GL.immediate.mode != 7) throw 'unsupported immediate mode ' + GL.immediate.mode; // GL_QUADS // GL.immediate.firstVertex is the first vertex we want. Quad indexes are in the pattern // 0 1 2, 0 2 3, 4 5 6, 4 6 7, so we need to look at index firstVertex * 1.5 to see it. // Then since indexes are 2 bytes each, that means 3 assert(GL.immediate.firstVertex % 4 == 0); ptr = GL.immediate.firstVertex*3; var numQuads = numVertexes / 4; numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern assert(ptr + (numIndexes << 1) <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); emulatedElementArrayBuffer = true; } renderer.prepare(); if (numIndexes) { Module.ctx.drawElements(Module.ctx.TRIANGLES, numIndexes, Module.ctx.UNSIGNED_SHORT, ptr); } else { Module.ctx.drawArrays(GL.immediate.mode, startIndex, numVertexes); } if (emulatedElementArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null); } #if GL_UNSAFE_OPTS == 0 renderer.cleanup(); #endif } }, $GLImmediateSetup__deps: ['$GLImmediate', function() { return 'GL.immediate = GLImmediate; GL.immediate.matrix.lib = ' + read('gl-matrix.js') + ';\n' }], $GLImmediateSetup: {}, glBegin__deps: ['$GLImmediateSetup'], glBegin: function(mode) { GL.immediate.mode = mode; GL.immediate.vertexCounter = 0; var components = GL.immediate.rendererComponents = []; for (var i = 0; i < GL.immediate.NUM_ATTRIBUTES; i++) { components[i] = 0; } GL.immediate.rendererComponentPointer = 0; GL.immediate.vertexData = GL.immediate.tempData; }, glEnd: function() { GL.immediate.prepareClientAttributes(GL.immediate.rendererComponents[GL.immediate.VERTEX], true); GL.immediate.firstVertex = 0; GL.immediate.lastVertex = GL.immediate.vertexCounter / (GL.immediate.stride >> 2); GL.immediate.flush(); GL.immediate.disableBeginEndClientAttributes(); GL.immediate.mode = -1; }, glVertex3f: function(x, y, z) { #if ASSERTIONS assert(GL.immediate.mode >= 0); // must be in begin/end #endif GL.immediate.vertexData[GL.immediate.vertexCounter++] = x; GL.immediate.vertexData[GL.immediate.vertexCounter++] = y; GL.immediate.vertexData[GL.immediate.vertexCounter++] = z || 0; #if ASSERTIONS assert(GL.immediate.vertexCounter << 2 < GL.immediate.MAX_TEMP_BUFFER_SIZE); #endif GL.immediate.addRendererComponent(GL.immediate.VERTEX, 3, Module.ctx.FLOAT); }, glVertex2f: 'glVertex3f', glVertex3fv__deps: ['glVertex3f'], glVertex3fv: function(p) { _glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}); }, glVertex2fv__deps: ['glVertex3f'], glVertex2fv: function(p) { _glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, 0); }, glVertex3i: 'glVertex3f', glVertex2i: 'glVertex3f', glTexCoord2i: function(u, v) { #if ASSERTIONS assert(GL.immediate.mode >= 0); // must be in begin/end #endif GL.immediate.vertexData[GL.immediate.vertexCounter++] = u; GL.immediate.vertexData[GL.immediate.vertexCounter++] = v; GL.immediate.addRendererComponent(GL.immediate.TEXTURE0, 2, Module.ctx.FLOAT); }, glTexCoord2f: 'glTexCoord2i', glTexCoord2fv__deps: ['glTexCoord2i'], glTexCoord2fv: function(v) { _glTexCoord2i({{{ makeGetValue('v', '0', 'float') }}}, {{{ makeGetValue('v', '4', 'float') }}}); }, glTexCoord4f: function() { throw 'glTexCoord4f: TODO' }, glColor4f: function(r, g, b, a) { r = Math.max(Math.min(r, 1), 0); g = Math.max(Math.min(g, 1), 0); b = Math.max(Math.min(b, 1), 0); a = Math.max(Math.min(a, 1), 0); // TODO: make ub the default, not f, save a few mathops if (GL.immediate.mode >= 0) { var start = GL.immediate.vertexCounter << 2; GL.immediate.vertexDataU8[start + 0] = r * 255; GL.immediate.vertexDataU8[start + 1] = g * 255; GL.immediate.vertexDataU8[start + 2] = b * 255; GL.immediate.vertexDataU8[start + 3] = a * 255; GL.immediate.vertexCounter++; GL.immediate.addRendererComponent(GL.immediate.COLOR, 4, Module.ctx.UNSIGNED_BYTE); } else { GL.immediate.clientColor[0] = r; GL.immediate.clientColor[1] = g; GL.immediate.clientColor[2] = b; GL.immediate.clientColor[3] = a; } }, glColor4d: 'glColor4f', glColor4ub__deps: ['glColor4f'], glColor4ub: function(r, g, b, a) { _glColor4f((r&255)/255, (g&255)/255, (b&255)/255, (a&255)/255); }, glColor4us__deps: ['glColor4f'], glColor4us: function(r, g, b, a) { _glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535); }, glColor4ui__deps: ['glColor4f'], glColor4ui: function(r, g, b, a) { _glColor4f((r>>>0)/4294967295, (g>>>0)/4294967295, (b>>>0)/4294967295, (a>>>0)/4294967295); }, glColor3f__deps: ['glColor4f'], glColor3f: function(r, g, b) { _glColor4f(r, g, b, 1); }, glColor3d: 'glColor3f', glColor3ub__deps: ['glColor4ub'], glColor3ub: function(r, g, b) { _glColor4ub(r, g, b, 255); }, glColor3us__deps: ['glColor4us'], glColor3us: function(r, g, b) { _glColor4us(r, g, b, 65535); }, glColor3ui__deps: ['glColor4ui'], glColor3ui: function(r, g, b) { _glColor4ui(r, g, b, 4294967295); }, glColor3ubv__deps: ['glColor3ub'], glColor3ubv: function(p) { _glColor3ub({{{ makeGetValue('p', '0', 'i8') }}}, {{{ makeGetValue('p', '1', 'i8') }}}, {{{ makeGetValue('p', '2', 'i8') }}}); }, glColor3usv__deps: ['glColor3us'], glColor3usv: function(p) { _glColor3us({{{ makeGetValue('p', '0', 'i16') }}}, {{{ makeGetValue('p', '2', 'i16') }}}, {{{ makeGetValue('p', '4', 'i16') }}}); }, glColor3uiv__deps: ['glColor3ui'], glColor3uiv: function(p) { _glColor3ui({{{ makeGetValue('p', '0', 'i32') }}}, {{{ makeGetValue('p', '4', 'i32') }}}, {{{ makeGetValue('p', '8', 'i32') }}}); }, glColor3fv__deps: ['glColor3f'], glColor3fv: function(p) { _glColor3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}); }, glColor4fv__deps: ['glColor4f'], glColor4fv: function(p) { _glColor4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}}); }, glColor4ubv: function() { throw 'glColor4ubv not implemented' }, glFogf: function(pname, param) { // partial support, TODO switch(pname) { case 0x0B63: // GL_FOG_START GLEmulation.fogStart = param; break; case 0x0B64: // GL_FOG_END GLEmulation.fogEnd = param; break; case 0x0B62: // GL_FOG_DENSITY GLEmulation.fogDensity = param; break; case 0x0B65: // GL_FOG_MODE switch (param) { case 0x0801: // GL_EXP2 case 0x2601: // GL_LINEAR GLEmulation.fogMode = param; break; default: // default to GL_EXP GLEmulation.fogMode = 0x0800 /* GL_EXP */; break; } break; } }, glFogi__deps: ['glFogf'], glFogi: function(pname, param) { return _glFogf(pname, param); }, glFogfv__deps: ['glFogf'], glFogfv: function(pname, param) { // partial support, TODO switch(pname) { case 0x0B66: // GL_FOG_COLOR GLEmulation.fogColor[0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.fogColor[1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.fogColor[2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.fogColor[3] = {{{ makeGetValue('param', '12', 'float') }}}; break; case 0x0B63: // GL_FOG_START case 0x0B64: // GL_FOG_END _glFogf(pname, {{{ makeGetValue('param', '0', 'float') }}}); break; } }, glFogiv__deps: ['glFogf'], glFogiv: function(pname, param) { switch(pname) { case 0x0B66: // GL_FOG_COLOR GLEmulation.fogColor[0] = ({{{ makeGetValue('param', '0', 'i32') }}}/2147483647)/2.0+0.5; GLEmulation.fogColor[1] = ({{{ makeGetValue('param', '4', 'i32') }}}/2147483647)/2.0+0.5; GLEmulation.fogColor[2] = ({{{ makeGetValue('param', '8', 'i32') }}}/2147483647)/2.0+0.5; GLEmulation.fogColor[3] = ({{{ makeGetValue('param', '12', 'i32') }}}/2147483647)/2.0+0.5; break; default: _glFogf(pname, {{{ makeGetValue('param', '0', 'i32') }}}); break; } }, glFogx: 'glFogi', glFogxv: 'glFogiv', glPolygonMode: function(){}, // TODO glAlphaFunc: function(){}, // TODO glNormal3f: function(){}, // TODO // Additional non-GLES rendering calls glDrawRangeElements__sig: 'viiiiii', glDrawRangeElements: function(mode, start, end, count, type, indices) { _glDrawElements(mode, count, type, indices, start, end); }, // ClientState/gl*Pointer glEnableClientState: function(cap, disable) { var attrib = GLEmulation.getAttributeFromCapability(cap); if (attrib === null) { #if ASSERTIONS Module.printErr('WARNING: unhandled clientstate: ' + cap); #endif return; } if (disable && GL.immediate.enabledClientAttributes[attrib]) { GL.immediate.enabledClientAttributes[attrib] = false; GL.immediate.totalEnabledClientAttributes--; if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledClientStates[cap]; } else if (!disable && !GL.immediate.enabledClientAttributes[attrib]) { GL.immediate.enabledClientAttributes[attrib] = true; GL.immediate.totalEnabledClientAttributes++; if (GLEmulation.currentVao) GLEmulation.currentVao.enabledClientStates[cap] = 1; } GL.immediate.modifiedClientAttributes = true; }, glDisableClientState: function(cap) { _glEnableClientState(cap, 1); }, glVertexPointer__deps: ['$GLEmulation'], // if any pointers are used, glVertexPointer must be, and if it is, then we need emulation glVertexPointer: function(size, type, stride, pointer) { GL.immediate.setClientAttribute(GL.immediate.VERTEX, size, type, stride, pointer); }, glTexCoordPointer: function(size, type, stride, pointer) { GL.immediate.setClientAttribute(GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture, size, type, stride, pointer); }, glNormalPointer: function(type, stride, pointer) { GL.immediate.setClientAttribute(GL.immediate.NORMAL, 3, type, stride, pointer); }, glColorPointer: function(size, type, stride, pointer) { GL.immediate.setClientAttribute(GL.immediate.COLOR, size, type, stride, pointer); }, glClientActiveTexture__sig: 'vi', glClientActiveTexture: function(texture) { GL.immediate.clientActiveTexture = texture - 0x84C0; // GL_TEXTURE0 }, // Vertex array object (VAO) support. TODO: when the WebGL extension is popular, use that and remove this code and GL.vaos glGenVertexArrays__deps: ['$GLEMulation'], glGenVertexArrays__sig: ['vii'], glGenVertexArrays: function(n, vaos) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GLEmulation.vaos); GLEmulation.vaos[id] = { id: id, arrayBuffer: 0, elementArrayBuffer: 0, enabledVertexAttribArrays: {}, vertexAttribPointers: {}, enabledClientStates: {}, }; {{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}}; } }, glDeleteVertexArrays__sig: ['vii'], glDeleteVertexArrays: function(n, vaos) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}}; GLEmulation.vaos[id] = null; if (GLEmulation.currentVao && GLEmulation.currentVao.id == id) GLEmulation.currentVao = null; } }, glBindVertexArray__sig: ['vi'], glBindVertexArray: function(vao) { // undo vao-related things, wipe the slate clean, both for vao of 0 or an actual vao GLEmulation.currentVao = null; // make sure the commands we run here are not recorded if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); _glBindBuffer(Module.ctx.ARRAY_BUFFER, 0); // XXX if one was there before we were bound? _glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, 0); for (var vaa in GLEmulation.enabledVertexAttribArrays) { Module.ctx.disableVertexAttribArray(vaa); } GLEmulation.enabledVertexAttribArrays = {}; GL.immediate.enabledClientAttributes = [0, 0]; GL.immediate.totalEnabledClientAttributes = 0; GL.immediate.modifiedClientAttributes = true; if (vao) { // replay vao var info = GLEmulation.vaos[vao]; _glBindBuffer(Module.ctx.ARRAY_BUFFER, info.arrayBuffer); // XXX overwrite current binding? _glBindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, info.elementArrayBuffer); for (var vaa in info.enabledVertexAttribArrays) { _glEnableVertexAttribArray(vaa); } for (var vaa in info.vertexAttribPointers) { _glVertexAttribPointer.apply(null, info.vertexAttribPointers[vaa]); } for (var attrib in info.enabledClientStates) { _glEnableClientState(attrib|0); } GLEmulation.currentVao = info; // set currentVao last, so the commands we ran here were not recorded } }, // OpenGL Immediate Mode matrix routines. // Note that in the future we might make these available only in certain modes. glMatrixMode__deps: ['$GL', '$GLImmediateSetup', '$GLEmulation'], // emulation is not strictly needed, this is a workaround glMatrixMode: function(mode) { if (mode == 0x1700 /* GL_MODELVIEW */) { GL.immediate.currentMatrix = 'm'; } else if (mode == 0x1701 /* GL_PROJECTION */) { GL.immediate.currentMatrix = 'p'; } else if (mode == 0x1702) { // GL_TEXTURE GL.immediate.currentMatrix = 't' + GL.immediate.clientActiveTexture; } else { throw "Wrong mode " + mode + " passed to glMatrixMode"; } }, glPushMatrix: function() { GL.immediate.matricesModified = true; GL.immediate.matrixStack[GL.immediate.currentMatrix].push( Array.prototype.slice.call(GL.immediate.matrix[GL.immediate.currentMatrix])); }, glPopMatrix: function() { GL.immediate.matricesModified = true; GL.immediate.matrix[GL.immediate.currentMatrix] = GL.immediate.matrixStack[GL.immediate.currentMatrix].pop(); }, glLoadIdentity__deps: ['$GL', '$GLImmediateSetup'], glLoadIdentity: function() { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadMatrixd: function(matrix) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadMatrixf: function(matrix) { #if GL_DEBUG if (GL.debug) Module.printErr('glLoadMatrixf receiving: ' + Array.prototype.slice.call(HEAPF32.subarray(matrix >> 2, (matrix >> 2) + 16))); #endif GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadTransposeMatrixd: function(matrix) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadTransposeMatrixf: function(matrix) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glMultMatrixd: function(matrix) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], {{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}); }, glMultMatrixf: function(matrix) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], {{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}); }, glMultTransposeMatrixd: function(matrix) { GL.immediate.matricesModified = true; var colMajor = GL.immediate.matrix.lib.mat4.create(); GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, colMajor); GL.immediate.matrix.lib.mat4.transpose(colMajor); GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], colMajor); }, glMultTransposeMatrixf: function(matrix) { GL.immediate.matricesModified = true; var colMajor = GL.immediate.matrix.lib.mat4.create(); GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, colMajor); GL.immediate.matrix.lib.mat4.transpose(colMajor); GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], colMajor); }, glFrustum: function(left, right, bottom, top_, nearVal, farVal) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], GL.immediate.matrix.lib.mat4.frustum(left, right, bottom, top_, nearVal, farVal)); }, glFrustumf: 'glFrustum', glOrtho: function(left, right, bottom, top_, nearVal, farVal) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], GL.immediate.matrix.lib.mat4.ortho(left, right, bottom, top_, nearVal, farVal)); }, glOrthof: 'glOrtho', glScaled: function(x, y, z) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.scale(GL.immediate.matrix[GL.immediate.currentMatrix], [x, y, z]); }, glScalef: 'glScaled', glTranslated: function(x, y, z) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.translate(GL.immediate.matrix[GL.immediate.currentMatrix], [x, y, z]); }, glTranslatef: 'glTranslated', glRotated: function(angle, x, y, z) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.rotate(GL.immediate.matrix[GL.immediate.currentMatrix], angle*Math.PI/180, [x, y, z]); }, glRotatef: 'glRotated', // GLU gluPerspective: function(fov, aspect, near, far) { GL.immediate.matricesModified = true; GL.immediate.matrix[GL.immediate.currentMatrix] = GL.immediate.matrix.lib.mat4.perspective(fov, aspect, near, far, GL.immediate.matrix[GL.immediate.currentMatrix]); }, gluLookAt: function(ex, ey, ez, cx, cy, cz, ux, uy, uz) { GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.lookAt(GL.immediate.matrix[GL.immediate.currentMatrix], [ex, ey, ez], [cx, cy, cz], [ux, uy, uz]); }, gluProject: function(objX, objY, objZ, model, proj, view, winX, winY, winZ) { // The algorithm for this functions comes from Mesa var inVec = new Float32Array(4); var outVec = new Float32Array(4); GL.immediate.matrix.lib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}}, [objX, objY, objZ, 1.0], outVec); GL.immediate.matrix.lib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}}, outVec, inVec); if (inVec[3] == 0.0) { return 0 /* GL_FALSE */; } inVec[0] /= inVec[3]; inVec[1] /= inVec[3]; inVec[2] /= inVec[3]; // Map x, y and z to range 0-1 */ inVec[0] = inVec[0] * 0.5 + 0.5; inVec[1] = inVec[1] * 0.5 + 0.5; inVec[2] = inVec[2] * 0.5 + 0.5; // Map x, y to viewport inVec[0] = inVec[0] * {{{ makeGetValue('view', 2*4, 'i32') }}} + {{{ makeGetValue('view', 0*4, 'i32') }}}; inVec[1] = inVec[1] * {{{ makeGetValue('view', 3*4, 'i32') }}} + {{{ makeGetValue('view', 1*4, 'i32') }}}; {{{ makeSetValue('winX', '0', 'inVec[0]', 'double') }}}; {{{ makeSetValue('winY', '0', 'inVec[1]', 'double') }}}; {{{ makeSetValue('winZ', '0', 'inVec[2]', 'double') }}}; return 1 /* GL_TRUE */; }, gluUnProject: function(winX, winY, winZ, model, proj, view, objX, objY, objZ) { var result = GL.immediate.matrix.lib.mat4.unproject([winX, winY, winZ], {{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}}, {{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}}, {{{ makeHEAPView('32', 'view', 'view+' + (4*4)) }}}); if (result === null) { return 0 /* GL_FALSE */; } {{{ makeSetValue('objX', '0', 'result[0]', 'double') }}}; {{{ makeSetValue('objY', '0', 'result[1]', 'double') }}}; {{{ makeSetValue('objZ', '0', 'result[2]', 'double') }}}; return 1 /* GL_TRUE */; }, gluOrtho2D: function(left, right, bottom, top) { _glOrtho(left, right, bottom, top, -1, 1); }, glDrawBuffer: function() { throw 'glDrawBuffer: TODO' }, glReadBuffer: function() { throw 'glReadBuffer: TODO' }, glLightfv: function() { throw 'glLightfv: TODO' }, glLightModelfv: function() { throw 'glLightModelfv: TODO' }, glMaterialfv: function() { throw 'glMaterialfv: TODO' }, glTexGeni: function() { throw 'glTexGeni: TODO' }, glTexGenfv: function() { throw 'glTexGenfv: TODO' }, glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') }, glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') }, glTexImage1D: function() { throw 'glTexImage1D: TODO' }, glTexCoord3f: function() { throw 'glTexCoord3f: TODO' }, glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' }, glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') }, glVertexAttribPointer__sig: 'viiiiii', glVertexAttribPointer: function(index, size, type, normalized, stride, ptr) { #if FULL_ES2 if (!GL.currArrayBuffer) { GL.clientBuffers[index] = { size: size, type: type, normalized: normalized, stride: stride, ptr: ptr }; return; } GL.clientBuffers[index] = null; #endif Module.ctx.vertexAttribPointer(index, size, type, normalized, stride, ptr); }, glEnableVertexAttribArray__sig: 'vi', glEnableVertexAttribArray: function(index) { #if FULL_ES2 GL.enabledClientBuffers[index] = true; #endif Module.ctx.enableVertexAttribArray(index); }, glDisableVertexAttribArray__sig: 'vi', glDisableVertexAttribArray: function(index) { #if FULL_ES2 GL.enabledClientBuffers[index] = false; #endif Module.ctx.disableVertexAttribArray(index); }, glDrawArrays: function(mode, first, count) { #if FULL_ES2 // bind any client-side buffers GL.preDrawHandleClientVertexAttribBindings(count); #endif Module.ctx.drawArrays(mode, first, count); #if FULL_ES2 GL.postDrawHandleClientVertexAttribBindings(); #endif }, glDrawElements: function(mode, count, type, indices) { #if FULL_ES2 var buf; if (!GL.currElementArrayBuffer) { buf = Module.ctx.createBuffer(); Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, buf); Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, HEAPU8.subarray(indices, indices + GL.calcBufLength(1, type, 0, count)), Module.ctx.DYNAMIC_DRAW); // the index is now 0 indices = 0; } // bind any client-side buffers GL.preDrawHandleClientVertexAttribBindings(count); #endif Module.ctx.drawElements(mode, count, type, indices); #if FULL_ES2 GL.postDrawHandleClientVertexAttribBindings(count); if (!GL.currElementArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); Module.ctx.deleteBuffer(buf); } #endif }, // signatures of simple pass-through functions, see later glActiveTexture__sig: 'vi', glCheckFramebufferStatus__sig: 'ii', glRenderbufferStorage__sig: 'viiii', // Open GLES1.1 compatibility glGenFramebuffersOES : 'glGenFramebuffers', glGenRenderbuffersOES : 'glGenRenderbuffers', glBindFramebufferOES : 'glBindFramebuffer', glBindRenderbufferOES : 'glBindRenderbuffer', glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv', glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer', glRenderbufferStorageOES : 'glRenderbufferStorage', glCheckFramebufferStatusOES : 'glCheckFramebufferStatus', glDeleteFramebuffersOES : 'glDeleteFramebuffers', glDeleteRenderbuffersOES : 'glDeleteRenderbuffers', glGenVertexArraysOES: 'glGenVertexArrays', glDeleteVertexArraysOES: 'glDeleteVertexArrays', glBindVertexArrayOES: 'glBindVertexArray', glFramebufferTexture2DOES: 'glFramebufferTexture2D' }; // Simple pass-through functions. Starred ones have return values. [X] ones have X in the C name but not in the JS name [[0, 'getError* finish flush'], [1, 'clearDepth clearDepth[f] depthFunc enable disable frontFace cullFace clear lineWidth clearStencil depthMask stencilMask checkFramebufferStatus* generateMipmap activeTexture blendEquation sampleCoverage isEnabled*'], [2, 'blendFunc blendEquationSeparate depthRange depthRange[f] stencilMaskSeparate hint polygonOffset'], [3, 'texParameteri texParameterf vertexAttrib2f stencilFunc stencilOp'], [4, 'viewport clearColor scissor vertexAttrib3f colorMask renderbufferStorage blendFuncSeparate blendColor stencilFuncSeparate stencilOpSeparate'], [5, 'vertexAttrib4f'], [8, 'copyTexImage2D copyTexSubImage2D']].forEach(function(data) { var num = data[0]; var names = data[1]; var args = range(num).map(function(i) { return 'x' + i }).join(', '); var plainStub = '(function(' + args + ') { Module.ctx.NAME(' + args + ') })'; var returnStub = '(function(' + args + ') { return Module.ctx.NAME(' + args + ') })'; names.split(' ').forEach(function(name) { var stub = plainStub; if (name[name.length-1] == '*') { name = name.substr(0, name.length-1); stub = returnStub; } var cName = name; if (name.indexOf('[') >= 0) { cName = name.replace('[', '').replace(']', ''); name = cName.substr(0, cName.length-1); } var cName = 'gl' + cName[0].toUpperCase() + cName.substr(1); assert(!(cName in LibraryGL), "Cannot reimplement the existing function " + cName); LibraryGL[cName] = eval(stub.replace('NAME', name)); }); }); autoAddDeps(LibraryGL, '$GL'); // Emulation requires everything else, potentially LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared var glFuncs = []; for (var item in LibraryGL) { if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') { glFuncs.push(item); } } LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs); LibraryGL.$GLEmulation__deps.push(function() { for (var func in Functions.getIndex.tentative) Functions.getIndex(func); }); if (FORCE_GL_EMULATION) { LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation'); LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation'); } mergeInto(LibraryManager.library, LibraryGL);