/** * @license * Copyright 2010 The Emscripten Authors * SPDX-License-Identifier: MIT */ assert(LEGACY_GL_EMULATION, 'libglemu.js should only be included with LEGACY_GL_EMULATION set') assert(!FULL_ES2, 'cannot emulate both ES2 and legacy GL'); assert(!FULL_ES3, 'cannot emulate both ES3 and legacy GL'); {{{ const copySigs = (func) => { if (!MAIN_MODULE) return ''; return ` _${func}.sig = _emscripten_${func}.sig = orig_${func}.sig;`; }; const fromPtr = (arg) => { if (CAN_ADDRESS_2GB) { return `${arg} >>>= 0`; } else if (MEMORY64) { return `${arg} = Number(${arg})`; } return ''; }; }}} var LibraryGLEmulation = { // GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL $GLEmulation__deps: ['$GLImmediateSetup', 'glEnable', 'glDisable', 'glIsEnabled', 'glGetBooleanv', 'glGetIntegerv', 'glGetString', 'glCreateShader', 'glShaderSource', 'glCompileShader', 'glAttachShader', 'glDetachShader', 'glUseProgram', 'glDeleteProgram', 'glBindAttribLocation', 'glLinkProgram', 'glBindBuffer', 'glGetFloatv', 'glHint', 'glEnableVertexAttribArray', 'glDisableVertexAttribArray', 'glVertexAttribPointer', 'glActiveTexture', '$stringToNewUTF8', '$ptrToString', '$getEmscriptenSupportedExtensions', ], $GLEmulation__postset: ` // Forward declare GL functions that are overridden by GLEmulation. /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDrawArrays; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDrawElements; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glActiveTexture; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glEnable; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDisable; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvf; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvi; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvfv; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetIntegerv; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glIsEnabled; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetBooleanv; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetString; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glCreateShader; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glShaderSource; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glCompileShader; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glAttachShader; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDetachShader; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glUseProgram; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDeleteProgram; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glBindAttribLocation; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glLinkProgram; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glBindBuffer; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetFloatv; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glHint; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glEnableVertexAttribArray; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDisableVertexAttribArray; /**@suppress {duplicate, undefinedVars}*/var _emscripten_glVertexAttribPointer; /**@suppress {duplicate, undefinedVars}*/var _glTexEnvf; /**@suppress {duplicate, undefinedVars}*/var _glTexEnvi; /**@suppress {duplicate, undefinedVars}*/var _glTexEnvfv; /**@suppress {duplicate, undefinedVars}*/var _glGetTexEnviv; /**@suppress {duplicate, undefinedVars}*/var _glGetTexEnvfv; 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: 0x800, // GL_EXP fogEnabled: false, // GL_CLIP_PLANE support MAX_CLIP_PLANES: 6, clipPlaneEnabled: [false, false, false, false, false, false], clipPlaneEquation: [], // GL_LIGHTING support lightingEnabled: false, lightModelAmbient: null, lightModelLocalViewer: false, lightModelTwoSide: false, materialAmbient: null, materialDiffuse: null, materialSpecular: null, materialShininess: null, materialEmission: null, MAX_LIGHTS: 8, lightEnabled: [false, false, false, false, false, false, false, false], lightAmbient: [], lightDiffuse: [], lightSpecular: [], lightPosition: [], // TODO attenuation modes of lights // GL_ALPHA_TEST support alphaTestEnabled: false, alphaTestFunc: 0x207, // GL_ALWAYS alphaTestRef: 0.0, // GL_POINTS support. pointSize: 1.0, // VAO support vaos: [], currentVao: null, enabledVertexAttribArrays: {}, // helps with vao cleanups hasRunInit: false, // Find a token in a shader source string findToken(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; }, init() { // Do not activate immediate/emulation code (e.g. replace glDrawElements) // when in FULL_ES2 mode. We do not need full emulation, we instead // emulate client-side arrays etc. in FULL_ES2 code in a straightforward // manner, and avoid not having a bound buffer be ambiguous between es2 // emulation code and legacy gl emulation code. #if FULL_ES2 return; #endif if (GLEmulation.hasRunInit) { return; } GLEmulation.hasRunInit = true; GLEmulation.fogColor = new Float32Array(4); for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { GLEmulation.clipPlaneEquation[clipPlaneId] = new Float32Array(4); } // set defaults for GL_LIGHTING GLEmulation.lightModelAmbient = new Float32Array([0.2, 0.2, 0.2, 1.0]); GLEmulation.materialAmbient = new Float32Array([0.2, 0.2, 0.2, 1.0]); GLEmulation.materialDiffuse = new Float32Array([0.8, 0.8, 0.8, 1.0]); GLEmulation.materialSpecular = new Float32Array([0.0, 0.0, 0.0, 1.0]); GLEmulation.materialShininess = new Float32Array([0.0]); GLEmulation.materialEmission = new Float32Array([0.0, 0.0, 0.0, 1.0]); for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { GLEmulation.lightAmbient[lightId] = new Float32Array([0.0, 0.0, 0.0, 1.0]); GLEmulation.lightDiffuse[lightId] = lightId ? new Float32Array([0.0, 0.0, 0.0, 1.0]) : new Float32Array([1.0, 1.0, 1.0, 1.0]); GLEmulation.lightSpecular[lightId] = lightId ? new Float32Array([0.0, 0.0, 0.0, 1.0]) : new Float32Array([1.0, 1.0, 1.0, 1.0]); GLEmulation.lightPosition[lightId] = new Float32Array([0.0, 0.0, 1.0, 0.0]); } // Add some emulation workarounds err('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work.'); #if GL_UNSAFE_OPTS == 1 err('WARNING: using emscripten GL emulation unsafe opts. If weirdness happens, try -sGL_UNSAFE_OPTS=0'); #endif // XXX some of the capabilities we don't support may lead to incorrect rendering, if we do not emulate them in shaders var validCapabilities = { 0xB44: 1, // GL_CULL_FACE 0xBE2: 1, // GL_BLEND 0xBD0: 1, // GL_DITHER, 0xB90: 1, // GL_STENCIL_TEST 0xB71: 1, // GL_DEPTH_TEST 0xC11: 1, // GL_SCISSOR_TEST 0x8037: 1, // GL_POLYGON_OFFSET_FILL 0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE 0x80A0: 1 // GL_SAMPLE_COVERAGE }; var orig_glEnable = _glEnable; _glEnable = _emscripten_glEnable = (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 GLImmediate.lastRenderer?.cleanup(); if (cap == 0xB60 /* GL_FOG */) { if (GLEmulation.fogEnabled != true) { GLImmediate.currentRenderer = null; // Fog parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.fogEnabled = true; } return; } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { var clipPlaneId = cap - 0x3000; if (GLEmulation.clipPlaneEnabled[clipPlaneId] != true) { GLImmediate.currentRenderer = null; // clip plane parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.clipPlaneEnabled[clipPlaneId] = true; } return; } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { var lightId = cap - 0x4000; if (GLEmulation.lightEnabled[lightId] != true) { GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.lightEnabled[lightId] = true; } return; } else if (cap == 0xB50 /* GL_LIGHTING */) { if (GLEmulation.lightingEnabled != true) { GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.lightingEnabled = true; } return; } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { if (GLEmulation.alphaTestEnabled != true) { GLImmediate.currentRenderer = null; // alpha testing is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.alphaTestEnabled = true; } return; } else if (cap == 0xDE1 /* 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 /* Actually, let's not, for now. (This sounds exceedingly broken) * This is in gl_ps_workaround2.c. _glEnableClientState(cap); */ return; } else if (!(cap in validCapabilities)) { return; } orig_glEnable(cap); }; {{{ copySigs('glEnable') }}} var orig_glDisable = _glDisable; _glDisable = _emscripten_glDisable = (cap) => { GLImmediate.lastRenderer?.cleanup(); if (cap == 0xB60 /* GL_FOG */) { if (GLEmulation.fogEnabled != false) { GLImmediate.currentRenderer = null; // Fog parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.fogEnabled = false; } return; } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { var clipPlaneId = cap - 0x3000; if (GLEmulation.clipPlaneEnabled[clipPlaneId] != false) { GLImmediate.currentRenderer = null; // clip plane parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.clipPlaneEnabled[clipPlaneId] = false; } return; } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { var lightId = cap - 0x4000; if (GLEmulation.lightEnabled[lightId] != false) { GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.lightEnabled[lightId] = false; } return; } else if (cap == 0xB50 /* GL_LIGHTING */) { if (GLEmulation.lightingEnabled != false) { GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.lightingEnabled = false; } return; } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { if (GLEmulation.alphaTestEnabled != false) { GLImmediate.currentRenderer = null; // alpha testing is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.alphaTestEnabled = false; } return; } else if (cap == 0xDE1 /* 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 /* Actually, let's not, for now. (This sounds exceedingly broken) * This is in gl_ps_workaround2.c. _glDisableClientState(cap); */ return; } else if (!(cap in validCapabilities)) { return; } orig_glDisable(cap); }; {{{ copySigs('glDisable') }}} var orig_glIsEnabled = _glIsEnabled; _glIsEnabled = _emscripten_glIsEnabled = (cap) => { if (cap == 0xB60 /* GL_FOG */) { return GLEmulation.fogEnabled ? 1 : 0; } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { var clipPlaneId = cap - 0x3000; return GLEmulation.clipPlaneEnabled[clipPlaneId] ? 1 : 0; } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { var lightId = cap - 0x4000; return GLEmulation.lightEnabled[lightId] ? 1 : 0; } else if (cap == 0xB50 /* GL_LIGHTING */) { return GLEmulation.lightingEnabled ? 1 : 0; } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { return GLEmulation.alphaTestEnabled ? 1 : 0; } else if (!(cap in validCapabilities)) { return 0; } return GLctx.isEnabled(cap); }; {{{ copySigs('glIsEnabled') }}} var orig_glGetBooleanv = _glGetBooleanv; _glGetBooleanv = _emscripten_glGetBooleanv = (pname, p) => { var attrib = GLEmulation.getAttributeFromCapability(pname); if (attrib !== null) { {{{ fromPtr('p') }}} var result = GLImmediate.enabledClientAttributes[attrib]; {{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}}; return; } orig_glGetBooleanv(pname, p); }; {{{ copySigs('glGetBooleanv') }}} var orig_glGetIntegerv = _glGetIntegerv; _glGetIntegerv = _emscripten_glGetIntegerv = (pname, params) => { {{{ fromPtr('params') }}} switch (pname) { case 0x84E2: pname = GLctx.MAX_TEXTURE_IMAGE_UNITS /* fake it */; break; // GL_MAX_TEXTURE_UNITS case 0x8B4A: { // GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB var result = GLctx.getParameter(GLctx.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 = GLctx.getParameter(GLctx.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 = GLctx.getParameter(GLctx.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 = GLctx.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 + GLImmediate.clientActiveTexture]; {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; return; } case 0x8089: { // GL_TEXTURE_COORD_ARRAY_TYPE var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; return; } case 0x808A: { // GL_TEXTURE_COORD_ARRAY_STRIDE var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; return; } case 0x0D32: { // GL_MAX_CLIP_PLANES {{{ makeSetValue('params', '0', 'GLEmulation.MAX_CLIP_PLANES', 'i32') }}}; // all implementations need to support at least 6 return; } case 0x0BA0: { // GL_MATRIX_MODE {{{ makeSetValue('params', '0', 'GLImmediate.currentMatrix + 0x1700', 'i32') }}}; return; } case 0x0BC1: { // GL_ALPHA_TEST_FUNC {{{ makeSetValue('params', '0', 'GLEmulation.alphaTestFunc', 'i32') }}}; return; } } orig_glGetIntegerv(pname, params); }; {{{ copySigs('glGetIntegerv') }}} var orig_glGetString = _glGetString; _glGetString = _emscripten_glGetString = (name_) => { if (GL.stringCache[name_]) return GL.stringCache[name_]; switch (name_) { case 0x1F03 /* GL_EXTENSIONS */: // Add various extensions that we can support var ret = stringToNewUTF8(getEmscriptenSupportedExtensions(GLctx).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.currentContext.compressionExt ? ' GL_ARB_texture_compression GL_EXT_texture_compression_s3tc' : '') + (GL.currentContext.anisotropicExt ? ' GL_EXT_texture_filter_anisotropic' : '') ); return GL.stringCache[name_] = {{{ to64('ret') }}}; } return orig_glGetString(name_); }; {{{ copySigs('glGetString') }}} // 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 orig_glCreateShader = _glCreateShader; _glCreateShader = _emscripten_glCreateShader = (shaderType) => { var id = orig_glCreateShader(shaderType); GL.shaderInfos[id] = { type: shaderType, ftransform: false }; return id; }; {{{ copySigs('glCreateShader') }}} function ensurePrecision(source) { if (!/precision +(low|medium|high)p +float *;/.test(source)) { source = '#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n' + source; } return source; } var orig_glShaderSource = _glShaderSource; _glShaderSource = _emscripten_glShaderSource = (shader, count, string, length) => { {{{ fromPtr('string') }}} {{{ fromPtr('length') }}} var source = GL.getSource(shader, count, string, length); #if GL_DEBUG dbg("glShaderSource: Input: \n" + source); 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 == GLctx.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 < GLImmediate.MAX_TEXTURES; i++) { // XXX To handle both regular texture mapping and cube mapping, we use vec4 for tex coordinates. 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.includes('gl_FrontColor')) { source = 'varying vec4 v_color; \n' + source.replace(/gl_FrontColor/g, 'v_color'); } if (source.includes('gl_Color')) { source = 'attribute vec4 a_color; \n' + source.replace(/gl_Color/g, 'a_color'); } if (source.includes('gl_Normal')) { source = 'attribute vec3 a_normal; \n' + source.replace(/gl_Normal/g, 'a_normal'); } // fog if (source.includes('gl_FogFragCoord')) { source = 'varying float v_fogFragCoord; \n' + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); } } else { // Fragment shader for (i = 0; i < GLImmediate.MAX_TEXTURES; i++) { 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.includes('gl_Color')) { source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color'); } if (source.includes('gl_Fog.color')) { source = 'uniform vec4 u_fogColor; \n' + source.replace(/gl_Fog.color/g, 'u_fogColor'); } if (source.includes('gl_Fog.end')) { source = 'uniform float u_fogEnd; \n' + source.replace(/gl_Fog.end/g, 'u_fogEnd'); } if (source.includes('gl_Fog.scale')) { source = 'uniform float u_fogScale; \n' + source.replace(/gl_Fog.scale/g, 'u_fogScale'); } if (source.includes('gl_Fog.density')) { source = 'uniform float u_fogDensity; \n' + source.replace(/gl_Fog.density/g, 'u_fogDensity'); } if (source.includes('gl_FogFragCoord')) { source = 'varying float v_fogFragCoord; \n' + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); } source = ensurePrecision(source); } #if GL_DEBUG GL.shaderSources[shader] = source; dbg("glShaderSource: Output: \n" + source); #endif GLctx.shaderSource(GL.shaders[shader], source); }; {{{ copySigs('glShaderSource') }}} var orig_glCompileShader = _glCompileShader; _glCompileShader = _emscripten_glCompileShader = (shader) => { GLctx.compileShader(GL.shaders[shader]); #if GL_DEBUG if (!GLctx.getShaderParameter(GL.shaders[shader], GLctx.COMPILE_STATUS)) { dbg(`Failed to compile shader: ${GLctx.getShaderInfoLog(GL.shaders[shader])}`); dbg(`Info: ${JSON.stringify(GL.shaderInfos[shader])}`); dbg(`Original source: ${GL.shaderOriginalSources[shader]}`); dbg(`Source: ${GL.shaderSources[shader]}`); abort('Shader compilation halt'); } #endif }; {{{ copySigs('glCompileShader') }}} GL.programShaders = {}; var orig_glAttachShader = _glAttachShader; _glAttachShader = _emscripten_glAttachShader = (program, shader) => { GL.programShaders[program] ||= []; GL.programShaders[program].push(shader); orig_glAttachShader(program, shader); }; {{{ copySigs('glAttachShader') }}} var orig_glDetachShader = _glDetachShader; _glDetachShader = _emscripten_glDetachShader = (program, shader) => { var programShader = GL.programShaders[program]; if (!programShader) { err(`WARNING: _glDetachShader received invalid program: ${program}`); return; } var index = programShader.indexOf(shader); programShader.splice(index, 1); orig_glDetachShader(program, shader); }; {{{ copySigs('glDetachShader') }}} var orig_glUseProgram = _glUseProgram; _glUseProgram = _emscripten_glUseProgram = (program) => { #if GL_DEBUG if (GL.debug) { dbg('[using program with shaders]'); if (program) { for (var shader of GL.programShaders[program]) { dbg(` shader ${shader}, original source: ${GL.shaderOriginalSources[shader]}`); dbg(` Source: ${GL.shaderSources[shader]}`); } } } #endif if (GL.currProgram != program) { GLImmediate.currentRenderer = null; // This changes the FFP emulation shader program, need to recompute that. GL.currProgram = program; GLImmediate.fixedFunctionProgram = 0; orig_glUseProgram(program); } } {{{ copySigs('glUseProgram') }}} var orig_glDeleteProgram = _glDeleteProgram; _glDeleteProgram = _emscripten_glDeleteProgram = (program) => { orig_glDeleteProgram(program); if (program == GL.currProgram) { GLImmediate.currentRenderer = null; // This changes the FFP emulation shader program, need to recompute that. GL.currProgram = 0; } }; {{{ copySigs('glDeleteProgram') }}} // If attribute 0 was not bound, bind it to 0 for WebGL performance reasons. Track if 0 is free for that. var zeroUsedPrograms = {}; var orig_glBindAttribLocation = _glBindAttribLocation; _glBindAttribLocation = _emscripten_glBindAttribLocation = (program, index, name) => { if (index == 0) zeroUsedPrograms[program] = true; orig_glBindAttribLocation(program, index, name); }; {{{ copySigs('glBindAttribLocation') }}} var orig_glLinkProgram = _glLinkProgram; _glLinkProgram = _emscripten_glLinkProgram = (program) => { if (!(program in zeroUsedPrograms)) { GLctx.bindAttribLocation(GL.programs[program], 0, 'a_position'); } orig_glLinkProgram(program); }; {{{ copySigs('glLinkProgram') }}} var orig_glBindBuffer = _glBindBuffer; _glBindBuffer = _emscripten_glBindBuffer = (target, buffer) => { orig_glBindBuffer(target, buffer); if (target == GLctx.ARRAY_BUFFER) { if (GLEmulation.currentVao) { #if ASSERTIONS assert(GLEmulation.currentVao.arrayBuffer == buffer || GLEmulation.currentVao.arrayBuffer == 0 || buffer == 0, 'TODO: support for multiple array buffers in vao'); #endif GLEmulation.currentVao.arrayBuffer = buffer; } } else if (target == GLctx.ELEMENT_ARRAY_BUFFER) { if (GLEmulation.currentVao) GLEmulation.currentVao.elementArrayBuffer = buffer; } }; {{{ copySigs('glBindBuffer') }}} var orig_glGetFloatv = _glGetFloatv; _glGetFloatv = _emscripten_glGetFloatv = (pname, params) => { {{{ fromPtr('params') }}} if (pname == 0xBA6) { // GL_MODELVIEW_MATRIX HEAPF32.set(GLImmediate.matrix[0/*m*/], {{{ getHeapOffset('params', 'float') }}}); } else if (pname == 0xBA7) { // GL_PROJECTION_MATRIX HEAPF32.set(GLImmediate.matrix[1/*p*/], {{{ getHeapOffset('params', 'float') }}}); } else if (pname == 0xBA8) { // GL_TEXTURE_MATRIX HEAPF32.set(GLImmediate.matrix[2/*t*/ + GLImmediate.clientActiveTexture], {{{ getHeapOffset('params', 'float') }}}); } else if (pname == 0xB66) { // GL_FOG_COLOR HEAPF32.set(GLEmulation.fogColor, {{{ getHeapOffset('params', 'float') }}}); } else if (pname == 0xB63) { // GL_FOG_START {{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}}; } else if (pname == 0xB64) { // GL_FOG_END {{{ makeSetValue('params', '0', 'GLEmulation.fogEnd', 'float') }}}; } else if (pname == 0xB62) { // GL_FOG_DENSITY {{{ makeSetValue('params', '0', 'GLEmulation.fogDensity', 'float') }}}; } else if (pname == 0xB65) { // GL_FOG_MODE {{{ makeSetValue('params', '0', 'GLEmulation.fogMode', 'float') }}}; } else if (pname == 0xB53) { // GL_LIGHT_MODEL_AMBIENT {{{ makeSetValue('params', '0', 'GLEmulation.lightModelAmbient[0]', 'float') }}}; {{{ makeSetValue('params', '4', 'GLEmulation.lightModelAmbient[1]', 'float') }}}; {{{ makeSetValue('params', '8', 'GLEmulation.lightModelAmbient[2]', 'float') }}}; {{{ makeSetValue('params', '12', 'GLEmulation.lightModelAmbient[3]', 'float') }}}; } else if (pname == 0xBC2) { // GL_ALPHA_TEST_REF {{{ makeSetValue('params', '0', 'GLEmulation.alphaTestRef', 'float') }}}; } else { orig_glGetFloatv(pname, params); } }; {{{ copySigs('glGetFloatv') }}} var orig_glHint = _glHint; _glHint = _emscripten_glHint = (target, mode) => { if (target == 0x84EF) { // GL_TEXTURE_COMPRESSION_HINT return; } orig_glHint(target, mode); }; {{{ copySigs('glHint') }}} var orig_glEnableVertexAttribArray = _glEnableVertexAttribArray; _glEnableVertexAttribArray = _emscripten_glEnableVertexAttribArray = (index) => { orig_glEnableVertexAttribArray(index); GLEmulation.enabledVertexAttribArrays[index] = 1; if (GLEmulation.currentVao) GLEmulation.currentVao.enabledVertexAttribArrays[index] = 1; }; {{{ copySigs('glEnableVertexAttribArray') }}} var orig_glDisableVertexAttribArray = _glDisableVertexAttribArray; _glDisableVertexAttribArray = _emscripten_glDisableVertexAttribArray = (index) => { orig_glDisableVertexAttribArray(index); delete GLEmulation.enabledVertexAttribArrays[index]; if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledVertexAttribArrays[index]; }; {{{ copySigs('glDisableVertexAttribArray') }}} var orig_glVertexAttribPointer = _glVertexAttribPointer; _glVertexAttribPointer = _emscripten_glVertexAttribPointer = (index, size, type, normalized, stride, pointer) => { orig_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]; } }; {{{ copySigs('glVertexAttribPointer') }}} }, getAttributeFromCapability(cap) { var attrib = null; switch (cap) { case 0xDE1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it #if ASSERTIONS abort("GL_TEXTURE_2D is not a spec-defined capability for gl{Enable,Disable}ClientState."); #endif // Fall through: case 0x8078: // GL_TEXTURE_COORD_ARRAY attrib = GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture; break; case 0x8074: // GL_VERTEX_ARRAY attrib = GLImmediate.VERTEX; break; case 0x8075: // GL_NORMAL_ARRAY attrib = GLImmediate.NORMAL; break; case 0x8076: // GL_COLOR_ARRAY attrib = GLImmediate.COLOR; break; } return attrib; }, }, glDeleteObject__deps: ['glDeleteProgram', 'glDeleteShader'], glDeleteObject: (id) => { if (GL.programs[id]) { _glDeleteProgram(id); } else if (GL.shaders[id]) { _glDeleteShader(id); } else { err(`WARNING: deleteObject received invalid id: ${id}`); } }, glDeleteObjectARB: 'glDeleteObject', glGetObjectParameteriv__deps: ['glGetProgramiv', 'glGetShaderiv'], glGetObjectParameteriv: (id, type, result) => { if (GL.programs[id]) { if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB var log = GLctx.getProgramInfoLog(GL.programs[id]); if (log === null) log = '(unknown error)'; {{{ makeSetValue('result', '0', 'log.length', 'i32') }}}; return; } _glGetProgramiv(id, type, result); } else if (GL.shaders[id]) { if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB var log = GLctx.getShaderInfoLog(GL.shaders[id]); if (log === null) log = '(unknown error)'; {{{ makeSetValue('result', '0', 'log.length', 'i32') }}}; return; } else if (type == 0x8B88) { // GL_OBJECT_SHADER_SOURCE_LENGTH_ARB var source = GLctx.getShaderSource(GL.shaders[id]); if (source === null) return; // If an error occurs, nothing will be written to result {{{ makeSetValue('result', '0', 'source.length', 'i32') }}}; return; } _glGetShaderiv(id, type, result); } else { err(`WARNING: getObjectParameteriv received invalid id: ${id}`); } }, glGetObjectParameterivARB: 'glGetObjectParameteriv', glGetInfoLog__deps: ['glGetProgramInfoLog', 'glGetShaderInfoLog'], glGetInfoLog: (id, maxLength, length, infoLog) => { if (GL.programs[id]) { _glGetProgramInfoLog(id, maxLength, length, infoLog); } else if (GL.shaders[id]) { _glGetShaderInfoLog(id, maxLength, length, infoLog); } else { err(`WARNING: glGetInfoLog received invalid id: ${id}`); } }, glGetInfoLogARB: 'glGetInfoLog', glBindProgram: (type, id) => { #if ASSERTIONS assert(id == 0); #endif }, glBindProgramARB: 'glBindProgram', glGetPointerv: (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 + GLImmediate.clientActiveTexture]; break; default: GL.recordError(0x500/*GL_INVALID_ENUM*/); #if GL_ASSERTIONS err(`GL_INVALID_ENUM in glGetPointerv: Unsupported name ${name}!`); #endif return; } {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; }, // GL Immediate mode // See comment in GLEmulation.init() #if !FULL_ES2 $GLImmediate__postset: 'GLImmediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(() => GLImmediate.init());', #endif $GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'], $GLImmediate: { MapTreeLib: null, spawnMapTreeLib: () => { /** * A naive implementation of a map backed by an array, and accessed by * naive iteration along the array. (hashmap with only one bucket) * @constructor */ function CNaiveListMap() { var list = []; this.insert = function CNaiveListMap_insert(key, val) { if (this.contains(key|0)) return false; list.push([key, val]); return true; }; var __contains_i; this.contains = function CNaiveListMap_contains(key) { for (__contains_i = 0; __contains_i < list.length; ++__contains_i) { if (list[__contains_i][0] === key) return true; } return false; }; var __get_i; this.get = function CNaiveListMap_get(key) { for (__get_i = 0; __get_i < list.length; ++__get_i) { if (list[__get_i][0] === key) return list[__get_i][1]; } return undefined; }; }; /** * A tree of map nodes. * Uses `KeyView`s to allow descending the tree without garbage. * Example: { * // Create our map object. * var map = new ObjTreeMap(); * * // Grab the static keyView for the map. * var keyView = map.GetStaticKeyView(); * * // Let's make a map for: * // root: * // 1: * // 2: * // 5: "Three, sir!" * // 3: "Three!" * * // Note how we can chain together `Reset` and `Next` to * // easily descend based on multiple key fragments. * keyView.Reset().Next(1).Next(2).Next(5).Set("Three, sir!"); * keyView.Reset().Next(1).Next(2).Next(3).Set("Three!"); * } * @constructor */ function CMapTree() { /** @constructor */ function CNLNode() { var map = new CNaiveListMap(); this.child = function CNLNode_child(keyFrag) { if (!map.contains(keyFrag|0)) { map.insert(keyFrag|0, new CNLNode()); } return map.get(keyFrag|0); }; this.value = undefined; this.get = function CNLNode_get() { return this.value; }; this.set = function CNLNode_set(val) { this.value = val; }; } /** @constructor */ function CKeyView(root) { var cur; this.reset = function CKeyView_reset() { cur = root; return this; }; this.reset(); this.next = function CKeyView_next(keyFrag) { cur = cur.child(keyFrag); return this; }; this.get = function CKeyView_get() { return cur.get(); }; this.set = function CKeyView_set(val) { cur.set(val); }; }; var root; var staticKeyView; this.createKeyView = function CNLNode_createKeyView() { return new CKeyView(root); } this.clear = function CNLNode_clear() { root = new CNLNode(); staticKeyView = this.createKeyView(); }; this.clear(); this.getStaticKeyView = function CNLNode_getStaticKeyView() { staticKeyView.reset(); return staticKeyView; }; }; // Exports: return { create: () => new CMapTree(), }; }, TexEnvJIT: null, spawnTexEnvJIT: () => { // GL defs: var GL_TEXTURE0 = 0x84C0; var GL_TEXTURE_1D = 0xDE0; var GL_TEXTURE_2D = 0xDE1; var GL_TEXTURE_3D = 0x806f; var GL_TEXTURE_CUBE_MAP = 0x8513; var GL_TEXTURE_ENV = 0x2300; var GL_TEXTURE_ENV_MODE = 0x2200; var GL_TEXTURE_ENV_COLOR = 0x2201; var GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; var GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; var GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; var GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; var GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; var GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; var GL_SRC0_RGB = 0x8580; var GL_SRC1_RGB = 0x8581; var GL_SRC2_RGB = 0x8582; var GL_SRC0_ALPHA = 0x8588; var GL_SRC1_ALPHA = 0x8589; var GL_SRC2_ALPHA = 0x858A; var GL_OPERAND0_RGB = 0x8590; var GL_OPERAND1_RGB = 0x8591; var GL_OPERAND2_RGB = 0x8592; var GL_OPERAND0_ALPHA = 0x8598; var GL_OPERAND1_ALPHA = 0x8599; var GL_OPERAND2_ALPHA = 0x859A; var GL_COMBINE_RGB = 0x8571; var GL_COMBINE_ALPHA = 0x8572; var GL_RGB_SCALE = 0x8573; var GL_ALPHA_SCALE = 0xD1C; // env.mode var GL_ADD = 0x104; var GL_BLEND = 0xBE2; var GL_REPLACE = 0x1E01; var GL_MODULATE = 0x2100; var GL_DECAL = 0x2101; var GL_COMBINE = 0x8570; // env.color/alphaCombiner //var GL_ADD = 0x104; //var GL_REPLACE = 0x1E01; //var GL_MODULATE = 0x2100; var GL_SUBTRACT = 0x84E7; var GL_INTERPOLATE = 0x8575; // env.color/alphaSrc var GL_TEXTURE = 0x1702; var GL_CONSTANT = 0x8576; var GL_PRIMARY_COLOR = 0x8577; var GL_PREVIOUS = 0x8578; // env.color/alphaOp var GL_SRC_COLOR = 0x300; var GL_ONE_MINUS_SRC_COLOR = 0x301; var GL_SRC_ALPHA = 0x302; var GL_ONE_MINUS_SRC_ALPHA = 0x303; var GL_RGB = 0x1907; var GL_RGBA = 0x1908; // Our defs: var TEXENVJIT_NAMESPACE_PREFIX = "tej_"; // Not actually constant, as they can be changed between JIT passes: var TEX_UNIT_UNIFORM_PREFIX = "uTexUnit"; var TEX_COORD_VARYING_PREFIX = "vTexCoord"; var PRIM_COLOR_VARYING = "vPrimColor"; var TEX_MATRIX_UNIFORM_PREFIX = "uTexMatrix"; // Static vars: var s_texUnits = null; //[]; var s_activeTexture = 0; var s_requiredTexUnitsForPass = []; // Static funcs: function abort_noSupport(info) { abort("[TexEnvJIT] ABORT: No support: " + info); } function abort_sanity(info) { abort("[TexEnvJIT] ABORT: Sanity failure: " + info); } function genTexUnitSampleExpr(texUnitID) { var texUnit = s_texUnits[texUnitID]; var texType = texUnit.getTexType(); var func = null; switch (texType) { case GL_TEXTURE_1D: func = "texture2D"; break; case GL_TEXTURE_2D: func = "texture2D"; break; case GL_TEXTURE_3D: return abort_noSupport("No support for 3D textures."); case GL_TEXTURE_CUBE_MAP: func = "textureCube"; break; default: return abort_sanity(`Unknown texType: ${ptrToString(texType)}`); } var texCoordExpr = TEX_COORD_VARYING_PREFIX + texUnitID; if (TEX_MATRIX_UNIFORM_PREFIX != null) { texCoordExpr = `(${TEX_MATRIX_UNIFORM_PREFIX}${texUnitID} * ${texCoordExpr})`; } return `${func}(${TEX_UNIT_UNIFORM_PREFIX}${texUnitID}, ${texCoordExpr}.xy)`; } function getTypeFromCombineOp(op) { switch (op) { case GL_SRC_COLOR: case GL_ONE_MINUS_SRC_COLOR: return "vec3"; case GL_SRC_ALPHA: case GL_ONE_MINUS_SRC_ALPHA: return "float"; } return abort_noSupport("Unsupported combiner op: " + ptrToString(op)); } function getCurTexUnit() { return s_texUnits[s_activeTexture]; } function genCombinerSourceExpr(texUnitID, constantExpr, previousVar, src, op) { var srcExpr = null; switch (src) { case GL_TEXTURE: srcExpr = genTexUnitSampleExpr(texUnitID); break; case GL_CONSTANT: srcExpr = constantExpr; break; case GL_PRIMARY_COLOR: srcExpr = PRIM_COLOR_VARYING; break; case GL_PREVIOUS: srcExpr = previousVar; break; default: return abort_noSupport("Unsupported combiner src: " + ptrToString(src)); } var expr = null; switch (op) { case GL_SRC_COLOR: expr = srcExpr + ".rgb"; break; case GL_ONE_MINUS_SRC_COLOR: expr = "(vec3(1.0) - " + srcExpr + ".rgb)"; break; case GL_SRC_ALPHA: expr = srcExpr + ".a"; break; case GL_ONE_MINUS_SRC_ALPHA: expr = "(1.0 - " + srcExpr + ".a)"; break; default: return abort_noSupport("Unsupported combiner op: " + ptrToString(op)); } return expr; } function valToFloatLiteral(val) { if (val == Math.round(val)) return val + '.0'; return val; } // Classes: /** @constructor */ function CTexEnv() { this.mode = GL_MODULATE; this.colorCombiner = GL_MODULATE; this.alphaCombiner = GL_MODULATE; this.colorScale = 1; this.alphaScale = 1; this.envColor = [0, 0, 0, 0]; this.colorSrc = [ GL_TEXTURE, GL_PREVIOUS, GL_CONSTANT ]; this.alphaSrc = [ GL_TEXTURE, GL_PREVIOUS, GL_CONSTANT ]; this.colorOp = [ GL_SRC_COLOR, GL_SRC_COLOR, GL_SRC_ALPHA ]; this.alphaOp = [ GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA ]; // Map GLenums to small values to efficiently pack the enums to bits for tighter access. this.traverseKey = { // mode 0x1E01 /* GL_REPLACE */: 0, 0x2100 /* GL_MODULATE */: 1, 0x104 /* GL_ADD */: 2, 0xBE2 /* GL_BLEND */: 3, 0x2101 /* GL_DECAL */: 4, 0x8570 /* GL_COMBINE */: 5, // additional color and alpha combiners 0x84E7 /* GL_SUBTRACT */: 3, 0x8575 /* GL_INTERPOLATE */: 4, // color and alpha src 0x1702 /* GL_TEXTURE */: 0, 0x8576 /* GL_CONSTANT */: 1, 0x8577 /* GL_PRIMARY_COLOR */: 2, 0x8578 /* GL_PREVIOUS */: 3, // color and alpha op 0x300 /* GL_SRC_COLOR */: 0, 0x301 /* GL_ONE_MINUS_SRC_COLOR */: 1, 0x302 /* GL_SRC_ALPHA */: 2, 0x303 /* GL_ONE_MINUS_SRC_ALPHA */: 3 }; // The tuple (key0,key1,key2) uniquely identifies the state of the variables in CTexEnv. // -1 on key0 denotes 'the whole cached key is dirty' this.key0 = -1; this.key1 = 0; this.key2 = 0; this.computeKey0 = function() { var k = this.traverseKey; var key = k[this.mode] * 1638400; // 6 distinct values. key += k[this.colorCombiner] * 327680; // 5 distinct values. key += k[this.alphaCombiner] * 65536; // 5 distinct values. // The above three fields have 6*5*5=150 distinct values -> 8 bits. key += (this.colorScale-1) * 16384; // 10 bits used. key += (this.alphaScale-1) * 4096; // 12 bits used. key += k[this.colorSrc[0]] * 1024; // 14 key += k[this.colorSrc[1]] * 256; // 16 key += k[this.colorSrc[2]] * 64; // 18 key += k[this.alphaSrc[0]] * 16; // 20 key += k[this.alphaSrc[1]] * 4; // 22 key += k[this.alphaSrc[2]]; // 24 bits used total. return key; } this.computeKey1 = function() { var k = this.traverseKey; var key = k[this.colorOp[0]] * 4096; key += k[this.colorOp[1]] * 1024; key += k[this.colorOp[2]] * 256; key += k[this.alphaOp[0]] * 16; key += k[this.alphaOp[1]] * 4; key += k[this.alphaOp[2]]; return key; } // TODO: remove this. The color should not be part of the key! this.computeKey2 = function() { return this.envColor[0] * 16777216 + this.envColor[1] * 65536 + this.envColor[2] * 256 + 1 + this.envColor[3]; } this.recomputeKey = function() { this.key0 = this.computeKey0(); this.key1 = this.computeKey1(); this.key2 = this.computeKey2(); } this.invalidateKey = function() { this.key0 = -1; // The key of this texture unit must be recomputed when rendering the next time. GLImmediate.currentRenderer = null; // The currently used renderer must be re-evaluated at next render. } } /** @constructor */ function CTexUnit() { this.env = new CTexEnv(); this.enabled_tex1D = false; this.enabled_tex2D = false; this.enabled_tex3D = false; this.enabled_texCube = false; this.texTypesEnabled = 0; // A bitfield combination of the four flags above, used for fast access to operations. this.traverseState = function CTexUnit_traverseState(keyView) { if (this.texTypesEnabled) { if (this.env.key0 == -1) { this.env.recomputeKey(); } keyView.next(this.texTypesEnabled | (this.env.key0 << 4)); keyView.next(this.env.key1); keyView.next(this.env.key2); } else { // For correctness, must traverse a zero value, theoretically a subsequent integer key could collide with this value otherwise. keyView.next(0); } }; }; // Class impls: CTexUnit.prototype.enabled = function CTexUnit_enabled() { return this.texTypesEnabled; } CTexUnit.prototype.genPassLines = function CTexUnit_genPassLines(passOutputVar, passInputVar, texUnitID) { if (!this.enabled()) { return ["vec4 " + passOutputVar + " = " + passInputVar + ";"]; } var lines = this.env.genPassLines(passOutputVar, passInputVar, texUnitID).join('\n'); var texLoadLines = ''; var texLoadRegex = /(texture.*?\(.*?\))/g; var loadCounter = 0; var load; // As an optimization, merge duplicate identical texture loads to one var. while (load = texLoadRegex.exec(lines)) { var texLoadExpr = load[1]; var secondOccurrence = lines.slice(load.index+1).indexOf(texLoadExpr); if (secondOccurrence != -1) { // And also has a second occurrence of same load expression.. // Create new var to store the common load. var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; var texLoadVar = prefix + 'texload' + loadCounter++; var texLoadLine = 'vec4 ' + texLoadVar + ' = ' + texLoadExpr + ';\n'; texLoadLines += texLoadLine + '\n'; // Store the generated texture load statements in a temp string to not confuse regex search in progress. lines = lines.split(texLoadExpr).join(texLoadVar); // Reset regex search, since we modified the string. texLoadRegex = /(texture.*\(.*\))/g; } } return [texLoadLines + lines]; } CTexUnit.prototype.getTexType = function CTexUnit_getTexType() { if (this.enabled_texCube) { return GL_TEXTURE_CUBE_MAP; } else if (this.enabled_tex3D) { return GL_TEXTURE_3D; } else if (this.enabled_tex2D) { return GL_TEXTURE_2D; } else if (this.enabled_tex1D) { return GL_TEXTURE_1D; } return 0; } CTexEnv.prototype.genPassLines = function CTexEnv_genPassLines(passOutputVar, passInputVar, texUnitID) { switch (this.mode) { case GL_REPLACE: { /* RGB: * Cv = Cs * Av = Ap // Note how this is different, and that we'll * need to track the bound texture internalFormat * to get this right. * * RGBA: * Cv = Cs * Av = As */ return [ "vec4 " + passOutputVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", ]; } case GL_ADD: { /* RGBA: * Cv = Cp + Cs * Av = ApAs */ var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; var texVar = prefix + "tex"; var colorVar = prefix + "color"; var alphaVar = prefix + "alpha"; return [ "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", "vec3 " + colorVar + " = " + passInputVar + ".rgb + " + texVar + ".rgb;", "float " + alphaVar + " = " + passInputVar + ".a * " + texVar + ".a;", "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", ]; } case GL_MODULATE: { /* RGBA: * Cv = CpCs * Av = ApAs */ var line = [ "vec4 " + passOutputVar, " = ", passInputVar, " * ", genTexUnitSampleExpr(texUnitID), ";", ]; return [line.join("")]; } case GL_DECAL: { /* RGBA: * Cv = Cp(1 - As) + CsAs * Av = Ap */ var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; var texVar = prefix + "tex"; var colorVar = prefix + "color"; var alphaVar = prefix + "alpha"; return [ "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", [ "vec3 " + colorVar + " = ", passInputVar + ".rgb * (1.0 - " + texVar + ".a)", " + ", texVar + ".rgb * " + texVar + ".a", ";" ].join(""), "float " + alphaVar + " = " + passInputVar + ".a;", "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", ]; } case GL_BLEND: { /* RGBA: * Cv = Cp(1 - Cs) + CcCs * Av = As */ var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; var texVar = prefix + "tex"; var colorVar = prefix + "color"; var alphaVar = prefix + "alpha"; return [ "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", [ "vec3 " + colorVar + " = ", passInputVar + ".rgb * (1.0 - " + texVar + ".rgb)", " + ", PRIM_COLOR_VARYING + ".rgb * " + texVar + ".rgb", ";" ].join(""), "float " + alphaVar + " = " + texVar + ".a;", "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", ]; } case GL_COMBINE: { var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; var colorVar = prefix + "color"; var alphaVar = prefix + "alpha"; var colorLines = this.genCombinerLines(true, colorVar, passInputVar, texUnitID, this.colorCombiner, this.colorSrc, this.colorOp); var alphaLines = this.genCombinerLines(false, alphaVar, passInputVar, texUnitID, this.alphaCombiner, this.alphaSrc, this.alphaOp); // Generate scale, but avoid generating an identity op that multiplies by one. var scaledColor = (this.colorScale == 1) ? colorVar : (colorVar + " * " + valToFloatLiteral(this.colorScale)); var scaledAlpha = (this.alphaScale == 1) ? alphaVar : (alphaVar + " * " + valToFloatLiteral(this.alphaScale)); var line = [ "vec4 " + passOutputVar, " = ", "vec4(", scaledColor, ", ", scaledAlpha, ")", ";", ].join(""); return [].concat(colorLines, alphaLines, [line]); } } return abort_noSupport("Unsupported TexEnv mode: " + ptrToString(this.mode)); } CTexEnv.prototype.genCombinerLines = function CTexEnv_getCombinerLines(isColor, outputVar, passInputVar, texUnitID, combiner, srcArr, opArr) { var argsNeeded = null; switch (combiner) { case GL_REPLACE: argsNeeded = 1; break; case GL_MODULATE: case GL_ADD: case GL_SUBTRACT: argsNeeded = 2; break; case GL_INTERPOLATE: argsNeeded = 3; break; default: return abort_noSupport("Unsupported combiner: " + ptrToString(combiner)); } var constantExpr = [ "vec4(", valToFloatLiteral(this.envColor[0]), ", ", valToFloatLiteral(this.envColor[1]), ", ", valToFloatLiteral(this.envColor[2]), ", ", valToFloatLiteral(this.envColor[3]), ")", ].join(""); var src0Expr = (argsNeeded >= 1) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[0], opArr[0]) : null; var src1Expr = (argsNeeded >= 2) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[1], opArr[1]) : null; var src2Expr = (argsNeeded >= 3) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[2], opArr[2]) : null; var outputType = isColor ? "vec3" : "float"; var lines = null; switch (combiner) { case GL_REPLACE: { lines = [`${outputType} ${outputVar} = ${src0Expr};`] break; } case GL_MODULATE: { lines = [`${outputType} ${outputVar} = ${src0Expr} * ${src1Expr};`]; break; } case GL_ADD: { lines = [`${outputType} ${outputVar} = ${src0Expr} + ${src1Expr};`] break; } case GL_SUBTRACT: { lines = [`${outputType} ${outputVar} = ${src0Expr} - ${src1Expr};`] break; } case GL_INTERPOLATE: { var prefix = `${TEXENVJIT_NAMESPACE_PREFIX}env${texUnitID}_`; var arg2Var = `${prefix}colorSrc2`; var arg2Type = getTypeFromCombineOp(this.colorOp[2]); lines = [ `${arg2Type} ${arg2Var} = ${src2Expr};`, `${outputType} ${outputVar} = ${src0Expr} * ${arg2Var} + ${src1Expr} * (1.0 - ${arg2Var});`, ]; break; } default: return abort_sanity("Unmatched TexEnv.colorCombiner?"); } return lines; } return { // Exports: init: (gl, specifiedMaxTextureImageUnits) => { var maxTexUnits = 0; if (specifiedMaxTextureImageUnits) { maxTexUnits = specifiedMaxTextureImageUnits; } else if (gl) { maxTexUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); } #if ASSERTIONS assert(maxTexUnits > 0); #endif s_texUnits = []; for (var i = 0; i < maxTexUnits; i++) { s_texUnits.push(new CTexUnit()); } }, setGLSLVars: (uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix) => { TEX_UNIT_UNIFORM_PREFIX = uTexUnitPrefix; TEX_COORD_VARYING_PREFIX = vTexCoordPrefix; PRIM_COLOR_VARYING = vPrimColor; TEX_MATRIX_UNIFORM_PREFIX = uTexMatrixPrefix; }, genAllPassLines: (resultDest, indentSize = 0) => { s_requiredTexUnitsForPass.length = 0; // Clear the list. var lines = []; var lastPassVar = PRIM_COLOR_VARYING; for (var i = 0; i < s_texUnits.length; i++) { if (!s_texUnits[i].enabled()) continue; s_requiredTexUnitsForPass.push(i); var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + i + "_"; var passOutputVar = prefix + "result"; var newLines = s_texUnits[i].genPassLines(passOutputVar, lastPassVar, i); lines = lines.concat(newLines, [""]); lastPassVar = passOutputVar; } lines.push(resultDest + " = " + lastPassVar + ";"); var indent = ""; for (var i = 0; i < indentSize; i++) indent += " "; var output = indent + lines.join("\n" + indent); return output; }, getUsedTexUnitList: () => s_requiredTexUnitsForPass, getActiveTexture: () => s_activeTexture, traverseState: (keyView) => { for (var texUnit of s_texUnits) { texUnit.traverseState(keyView); } }, getTexUnitType: (texUnitID) => { #if ASSERTIONS assert(texUnitID >= 0 && texUnitID < s_texUnits.length); #endif return s_texUnits[texUnitID].getTexType(); }, // Hooks: hook_activeTexture: (texture) => { s_activeTexture = texture - GL_TEXTURE0; // Check if the current matrix mode is GL_TEXTURE. if (GLImmediate.currentMatrix >= 2) { // Switch to the corresponding texture matrix stack. GLImmediate.currentMatrix = 2 + s_activeTexture; } }, hook_enable: (cap) => { var cur = getCurTexUnit(); switch (cap) { case GL_TEXTURE_1D: if (!cur.enabled_tex1D) { GLImmediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. cur.enabled_tex1D = true; cur.texTypesEnabled |= 1; } break; case GL_TEXTURE_2D: if (!cur.enabled_tex2D) { GLImmediate.currentRenderer = null; cur.enabled_tex2D = true; cur.texTypesEnabled |= 2; } break; case GL_TEXTURE_3D: if (!cur.enabled_tex3D) { GLImmediate.currentRenderer = null; cur.enabled_tex3D = true; cur.texTypesEnabled |= 4; } break; case GL_TEXTURE_CUBE_MAP: if (!cur.enabled_texCube) { GLImmediate.currentRenderer = null; cur.enabled_texCube = true; cur.texTypesEnabled |= 8; } break; } }, hook_disable: (cap) => { var cur = getCurTexUnit(); switch (cap) { case GL_TEXTURE_1D: if (cur.enabled_tex1D) { GLImmediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. cur.enabled_tex1D = false; cur.texTypesEnabled &= ~1; } break; case GL_TEXTURE_2D: if (cur.enabled_tex2D) { GLImmediate.currentRenderer = null; cur.enabled_tex2D = false; cur.texTypesEnabled &= ~2; } break; case GL_TEXTURE_3D: if (cur.enabled_tex3D) { GLImmediate.currentRenderer = null; cur.enabled_tex3D = false; cur.texTypesEnabled &= ~4; } break; case GL_TEXTURE_CUBE_MAP: if (cur.enabled_texCube) { GLImmediate.currentRenderer = null; cur.enabled_texCube = false; cur.texTypesEnabled &= ~8; } break; } }, hook_texEnvf(target, pname, param) { if (target != GL_TEXTURE_ENV) return; var env = getCurTexUnit().env; switch (pname) { case GL_RGB_SCALE: if (env.colorScale != param) { env.invalidateKey(); // We changed FFP emulation renderer state. env.colorScale = param; } break; case GL_ALPHA_SCALE: if (env.alphaScale != param) { env.invalidateKey(); env.alphaScale = param; } break; default: err('WARNING: Unhandled `pname` in call to `glTexEnvf`.'); } }, hook_texEnvi(target, pname, param) { if (target != GL_TEXTURE_ENV) return; var env = getCurTexUnit().env; switch (pname) { case GL_TEXTURE_ENV_MODE: if (env.mode != param) { env.invalidateKey(); // We changed FFP emulation renderer state. env.mode = param; } break; case GL_COMBINE_RGB: if (env.colorCombiner != param) { env.invalidateKey(); env.colorCombiner = param; } break; case GL_COMBINE_ALPHA: if (env.alphaCombiner != param) { env.invalidateKey(); env.alphaCombiner = param; } break; case GL_SRC0_RGB: if (env.colorSrc[0] != param) { env.invalidateKey(); env.colorSrc[0] = param; } break; case GL_SRC1_RGB: if (env.colorSrc[1] != param) { env.invalidateKey(); env.colorSrc[1] = param; } break; case GL_SRC2_RGB: if (env.colorSrc[2] != param) { env.invalidateKey(); env.colorSrc[2] = param; } break; case GL_SRC0_ALPHA: if (env.alphaSrc[0] != param) { env.invalidateKey(); env.alphaSrc[0] = param; } break; case GL_SRC1_ALPHA: if (env.alphaSrc[1] != param) { env.invalidateKey(); env.alphaSrc[1] = param; } break; case GL_SRC2_ALPHA: if (env.alphaSrc[2] != param) { env.invalidateKey(); env.alphaSrc[2] = param; } break; case GL_OPERAND0_RGB: if (env.colorOp[0] != param) { env.invalidateKey(); env.colorOp[0] = param; } break; case GL_OPERAND1_RGB: if (env.colorOp[1] != param) { env.invalidateKey(); env.colorOp[1] = param; } break; case GL_OPERAND2_RGB: if (env.colorOp[2] != param) { env.invalidateKey(); env.colorOp[2] = param; } break; case GL_OPERAND0_ALPHA: if (env.alphaOp[0] != param) { env.invalidateKey(); env.alphaOp[0] = param; } break; case GL_OPERAND1_ALPHA: if (env.alphaOp[1] != param) { env.invalidateKey(); env.alphaOp[1] = param; } break; case GL_OPERAND2_ALPHA: if (env.alphaOp[2] != param) { env.invalidateKey(); env.alphaOp[2] = param; } break; case GL_RGB_SCALE: if (env.colorScale != param) { env.invalidateKey(); env.colorScale = param; } break; case GL_ALPHA_SCALE: if (env.alphaScale != param) { env.invalidateKey(); env.alphaScale = param; } break; default: err('WARNING: Unhandled `pname` in call to `glTexEnvi`.'); } }, hook_texEnvfv(target, pname, params) { if (target != GL_TEXTURE_ENV) return; var env = getCurTexUnit().env; switch (pname) { case GL_TEXTURE_ENV_COLOR: { for (var i = 0; i < 4; i++) { var param = {{{ makeGetValue('params', 'i*4', 'float') }}}; if (env.envColor[i] != param) { env.invalidateKey(); // We changed FFP emulation renderer state. env.envColor[i] = param; } } break } default: err('WARNING: Unhandled `pname` in call to `glTexEnvfv`.'); } }, hook_getTexEnviv(target, pname, param) { if (target != GL_TEXTURE_ENV) return; var env = getCurTexUnit().env; switch (pname) { case GL_TEXTURE_ENV_MODE: {{{ makeSetValue('param', '0', 'env.mode', 'i32') }}}; return; case GL_TEXTURE_ENV_COLOR: {{{ makeSetValue('param', '0', 'Math.max(Math.min(env.envColor[0]*255, 255, -255))', 'i32') }}}; {{{ makeSetValue('param', '1', 'Math.max(Math.min(env.envColor[1]*255, 255, -255))', 'i32') }}}; {{{ makeSetValue('param', '2', 'Math.max(Math.min(env.envColor[2]*255, 255, -255))', 'i32') }}}; {{{ makeSetValue('param', '3', 'Math.max(Math.min(env.envColor[3]*255, 255, -255))', 'i32') }}}; return; case GL_COMBINE_RGB: {{{ makeSetValue('param', '0', 'env.colorCombiner', 'i32') }}}; return; case GL_COMBINE_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaCombiner', 'i32') }}}; return; case GL_SRC0_RGB: {{{ makeSetValue('param', '0', 'env.colorSrc[0]', 'i32') }}}; return; case GL_SRC1_RGB: {{{ makeSetValue('param', '0', 'env.colorSrc[1]', 'i32') }}}; return; case GL_SRC2_RGB: {{{ makeSetValue('param', '0', 'env.colorSrc[2]', 'i32') }}}; return; case GL_SRC0_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaSrc[0]', 'i32') }}}; return; case GL_SRC1_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaSrc[1]', 'i32') }}}; return; case GL_SRC2_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaSrc[2]', 'i32') }}}; return; case GL_OPERAND0_RGB: {{{ makeSetValue('param', '0', 'env.colorOp[0]', 'i32') }}}; return; case GL_OPERAND1_RGB: {{{ makeSetValue('param', '0', 'env.colorOp[1]', 'i32') }}}; return; case GL_OPERAND2_RGB: {{{ makeSetValue('param', '0', 'env.colorOp[2]', 'i32') }}}; return; case GL_OPERAND0_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaOp[0]', 'i32') }}}; return; case GL_OPERAND1_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaOp[1]', 'i32') }}}; return; case GL_OPERAND2_ALPHA: {{{ makeSetValue('param', '0', 'env.alphaOp[2]', 'i32') }}}; return; case GL_RGB_SCALE: {{{ makeSetValue('param', '0', 'env.colorScale', 'i32') }}}; return; case GL_ALPHA_SCALE: {{{ makeSetValue('param', '0', 'env.alphaScale', 'i32') }}}; return; default: err('WARNING: Unhandled `pname` in call to `glGetTexEnvi`.'); } }, hook_getTexEnvfv: (target, pname, param) => { if (target != GL_TEXTURE_ENV) return; var env = getCurTexUnit().env; switch (pname) { case GL_TEXTURE_ENV_COLOR: {{{ makeSetValue('param', '0', 'env.envColor[0]', 'float') }}}; {{{ makeSetValue('param', '4', 'env.envColor[1]', 'float') }}}; {{{ makeSetValue('param', '8', 'env.envColor[2]', 'float') }}}; {{{ makeSetValue('param', '12', 'env.envColor[3]', 'float') }}}; return; } } }; }, // 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, 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, // "" lastStride: -1, // "" // The following data structures are used for OpenGL Immediate Mode matrix routines. matrix: [], matrixStack: [], currentMatrix: 0, // 0: modelview, 1: projection, 2+i, texture matrix i. tempMatrix: null, matricesModified: false, useTextureMatrix: false, // Clientside attributes VERTEX: 0, NORMAL: 1, COLOR: 2, TEXTURE0: 3, NUM_ATTRIBUTES: -1, // Initialized in GL emulation init(). MAX_TEXTURES: -1, // Initialized in GL emulation init(). totalEnabledClientAttributes: 0, enabledClientAttributes: [0, 0], clientAttributes: [], // raw data, including possible unneeded ones liveClientAttributes: [], // the ones actually alive in the current computation, sorted currentRenderer: null, // Caches the currently active FFP emulation renderer, so that it does not have to be re-looked up unless relevant state changes. modifiedClientAttributes: false, clientActiveTexture: 0, clientColor: null, usedTexUnitList: [], fixedFunctionProgram: null, setClientAttribute(name, size, type, stride, pointer) { var attrib = GLImmediate.clientAttributes[name]; if (!attrib) { for (var i = 0; i <= name; i++) { // keep flat GLImmediate.clientAttributes[i] ||= { name, size, type, stride, pointer, offset: 0 }; } } else { attrib.name = name; attrib.size = size; attrib.type = type; attrib.stride = stride; attrib.pointer = pointer; attrib.offset = 0; } GLImmediate.modifiedClientAttributes = true; }, // Renderers addRendererComponent(name, size, type) { if (!GLImmediate.rendererComponents[name]) { GLImmediate.rendererComponents[name] = 1; #if ASSERTIONS if (GLImmediate.enabledClientAttributes[name]) { warnOnce("Warning: glTexCoord used after EnableClientState for TEXTURE_COORD_ARRAY for TEXTURE0. Disabling TEXTURE_COORD_ARRAY..."); } #endif GLImmediate.enabledClientAttributes[name] = true; GLImmediate.setClientAttribute(name, size, type, 0, GLImmediate.rendererComponentPointer); GLImmediate.rendererComponentPointer += size * GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; #if GL_FFP_ONLY // We can enable the correct attribute stream index immediately here, since the same attribute in each shader // will be bound to this same index. GL.enableVertexAttribArray(name); #endif } else { GLImmediate.rendererComponents[name]++; } }, disableBeginEndClientAttributes() { for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { if (GLImmediate.rendererComponents[i]) GLImmediate.enabledClientAttributes[i] = false; } }, getRenderer() { // If no FFP state has changed that would have forced to re-evaluate which FFP emulation shader to use, // we have the currently used renderer in cache, and can immediately return that. if (GLImmediate.currentRenderer) { return GLImmediate.currentRenderer; } // return a renderer object given the liveClientAttributes // we maintain a cache of renderers, optimized to not generate garbage var attributes = GLImmediate.liveClientAttributes; var cacheMap = GLImmediate.rendererCache; var keyView = cacheMap.getStaticKeyView().reset(); // By attrib state: var enabledAttributesKey = 0; for (var attr of attributes) { enabledAttributesKey |= 1 << attr.name; } // To prevent using more than 31 bits add another level to the maptree // and reset the enabledAttributesKey for the next glemulation state bits keyView.next(enabledAttributesKey); enabledAttributesKey = 0; // By fog state: var fogParam = 0; if (GLEmulation.fogEnabled) { switch (GLEmulation.fogMode) { case 0x801: // GL_EXP2 fogParam = 1; break; case 0x2601: // GL_LINEAR fogParam = 2; break; default: // default to GL_EXP fogParam = 3; break; } } enabledAttributesKey = (enabledAttributesKey << 2) | fogParam; // By clip plane mode for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { enabledAttributesKey = (enabledAttributesKey << 1) | GLEmulation.clipPlaneEnabled[clipPlaneId]; } // By lighting mode and enabled lights enabledAttributesKey = (enabledAttributesKey << 1) | GLEmulation.lightingEnabled; for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { enabledAttributesKey = (enabledAttributesKey << 1) | (GLEmulation.lightingEnabled ? GLEmulation.lightEnabled[lightId] : 0); } // By alpha testing mode enabledAttributesKey = (enabledAttributesKey << 3) | (GLEmulation.alphaTestEnabled ? (GLEmulation.alphaTestFunc - 0x200) : 0x7); // By drawing mode: enabledAttributesKey = (enabledAttributesKey << 1) | (GLImmediate.mode == GLctx.POINTS ? 1 : 0); keyView.next(enabledAttributesKey); #if !GL_FFP_ONLY // By cur program: keyView.next(GL.currProgram); if (!GL.currProgram) { #endif GLImmediate.TexEnvJIT.traverseState(keyView); #if !GL_FFP_ONLY } #endif // If we don't already have it, create it. var renderer = keyView.get(); if (!renderer) { #if GL_DEBUG dbg(`generating renderer for ${JSON.stringify(attributes)}`); #endif renderer = GLImmediate.createRenderer(); GLImmediate.currentRenderer = renderer; keyView.set(renderer); return renderer; } GLImmediate.currentRenderer = renderer; // Cache the currently used renderer, so later lookups without state changes can get this fast. return renderer; }, createRenderer(renderer) { var useCurrProgram = !!GL.currProgram; var hasTextures = false; for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { var texAttribName = GLImmediate.TEXTURE0 + i; if (!GLImmediate.enabledClientAttributes[texAttribName]) continue; #if ASSERTIONS if (!useCurrProgram) { if (GLImmediate.TexEnvJIT.getTexUnitType(i) == 0) { warnOnce("GL_TEXTURE" + i + " coords are supplied, but that texture unit is disabled in the fixed-function pipeline."); } } #endif hasTextures = true; } /** @constructor */ function Renderer() { this.init = function() { // For fixed-function shader generation. var uTexUnitPrefix = 'u_texUnit'; var aTexCoordPrefix = 'a_texCoord'; var vTexCoordPrefix = 'v_texCoord'; var vPrimColor = 'v_color'; var uTexMatrixPrefix = GLImmediate.useTextureMatrix ? 'u_textureMatrix' : null; if (useCurrProgram) { if (GL.shaderInfos[GL.programShaders[GL.currProgram][0]].type == GLctx.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]; this.usedTexUnitList = []; } 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. if (GLEmulation.fogEnabled) { switch (GLEmulation.fogMode) { case 0x801: // 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; } } GLImmediate.TexEnvJIT.setGLSLVars(uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix); var fsTexEnvPass = GLImmediate.TexEnvJIT.genAllPassLines('gl_FragColor', 2); var texUnitAttribList = ''; var texUnitVaryingList = ''; var texUnitUniformList = ''; var vsTexCoordInits = ''; this.usedTexUnitList = GLImmediate.TexEnvJIT.getUsedTexUnitList(); for (var texUnit of this.usedTexUnitList) { texUnitAttribList += 'attribute vec4 ' + aTexCoordPrefix + texUnit + ';\n'; texUnitVaryingList += 'varying vec4 ' + vTexCoordPrefix + texUnit + ';\n'; texUnitUniformList += 'uniform sampler2D ' + uTexUnitPrefix + texUnit + ';\n'; vsTexCoordInits += ' ' + vTexCoordPrefix + texUnit + ' = ' + aTexCoordPrefix + texUnit + ';\n'; if (GLImmediate.useTextureMatrix) { texUnitUniformList += 'uniform mat4 ' + uTexMatrixPrefix + texUnit + ';\n'; } } var vsFogVaryingInit = null; if (GLEmulation.fogEnabled) { vsFogVaryingInit = ' v_fogFragCoord = abs(ecPosition.z);\n'; } var vsPointSizeDefs = null; var vsPointSizeInit = null; if (GLImmediate.mode == GLctx.POINTS) { vsPointSizeDefs = 'uniform float u_pointSize;\n'; vsPointSizeInit = ' gl_PointSize = u_pointSize;\n'; } var vsClipPlaneDefs = ''; var vsClipPlaneInit = ''; var fsClipPlaneDefs = ''; var fsClipPlanePass = ''; for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { if (GLEmulation.clipPlaneEnabled[clipPlaneId]) { vsClipPlaneDefs += 'uniform vec4 u_clipPlaneEquation' + clipPlaneId + ';'; vsClipPlaneDefs += 'varying float v_clipDistance' + clipPlaneId + ';'; vsClipPlaneInit += ' v_clipDistance' + clipPlaneId + ' = dot(ecPosition, u_clipPlaneEquation' + clipPlaneId + ');'; fsClipPlaneDefs += 'varying float v_clipDistance' + clipPlaneId + ';'; fsClipPlanePass += ' if (v_clipDistance' + clipPlaneId + ' < 0.0) discard;'; } } var vsLightingDefs = ''; var vsLightingPass = ''; if (GLEmulation.lightingEnabled) { vsLightingDefs += 'attribute vec3 a_normal;'; vsLightingDefs += 'uniform mat3 u_normalMatrix;'; vsLightingDefs += 'uniform vec4 u_lightModelAmbient;'; vsLightingDefs += 'uniform vec4 u_materialAmbient;'; vsLightingDefs += 'uniform vec4 u_materialDiffuse;'; vsLightingDefs += 'uniform vec4 u_materialSpecular;'; vsLightingDefs += 'uniform float u_materialShininess;'; vsLightingDefs += 'uniform vec4 u_materialEmission;'; vsLightingPass += ' vec3 ecNormal = normalize(u_normalMatrix * a_normal);'; vsLightingPass += ' v_color.w = u_materialDiffuse.w;'; vsLightingPass += ' v_color.xyz = u_materialEmission.xyz;'; vsLightingPass += ' v_color.xyz += u_lightModelAmbient.xyz * u_materialAmbient.xyz;'; for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { if (GLEmulation.lightEnabled[lightId]) { vsLightingDefs += 'uniform vec4 u_lightAmbient' + lightId + ';'; vsLightingDefs += 'uniform vec4 u_lightDiffuse' + lightId + ';'; vsLightingDefs += 'uniform vec4 u_lightSpecular' + lightId + ';'; vsLightingDefs += 'uniform vec4 u_lightPosition' + lightId + ';'; vsLightingPass += ' {'; vsLightingPass += ' vec3 lightDirection = normalize(u_lightPosition' + lightId + ').xyz;'; vsLightingPass += ' vec3 halfVector = normalize(lightDirection + vec3(0,0,1));'; vsLightingPass += ' vec3 ambient = u_lightAmbient' + lightId + '.xyz * u_materialAmbient.xyz;'; vsLightingPass += ' float diffuseI = max(dot(ecNormal, lightDirection), 0.0);'; vsLightingPass += ' float specularI = max(dot(ecNormal, halfVector), 0.0);'; vsLightingPass += ' vec3 diffuse = diffuseI * u_lightDiffuse' + lightId + '.xyz * u_materialDiffuse.xyz;'; vsLightingPass += ' specularI = (diffuseI > 0.0 && specularI > 0.0) ? exp(u_materialShininess * log(specularI)) : 0.0;'; vsLightingPass += ' vec3 specular = specularI * u_lightSpecular' + lightId + '.xyz * u_materialSpecular.xyz;'; vsLightingPass += ' v_color.xyz += ambient + diffuse + specular;'; vsLightingPass += ' }'; } } vsLightingPass += ' v_color = clamp(v_color, 0.0, 1.0);'; } var vsSource = [ 'attribute vec4 a_position;', 'attribute vec4 a_color;', 'varying vec4 v_color;', texUnitAttribList, texUnitVaryingList, (GLEmulation.fogEnabled ? 'varying float v_fogFragCoord;' : null), 'uniform mat4 u_modelView;', 'uniform mat4 u_projection;', vsPointSizeDefs, vsClipPlaneDefs, vsLightingDefs, 'void main()', '{', ' vec4 ecPosition = u_modelView * a_position;', // eye-coordinate position ' gl_Position = u_projection * ecPosition;', ' v_color = a_color;', vsTexCoordInits, vsFogVaryingInit, vsPointSizeInit, vsClipPlaneInit, vsLightingPass, '}', '' ].join('\n').replace(/\n\n+/g, '\n'); this.vertexShader = GLctx.createShader(GLctx.VERTEX_SHADER); GLctx.shaderSource(this.vertexShader, vsSource); GLctx.compileShader(this.vertexShader); var fogHeaderIfNeeded = null; if (GLEmulation.fogEnabled) { fogHeaderIfNeeded = [ '', 'varying float v_fogFragCoord; ', 'uniform vec4 u_fogColor; ', 'uniform float u_fogEnd; ', 'uniform float u_fogScale; ', 'uniform float u_fogDensity; ', 'float ffog(in float ecDistance) { ', fogFormula, ' fog = clamp(fog, 0.0, 1.0); ', ' return fog; ', '}', '', ].join("\n"); } var fogPass = null; if (GLEmulation.fogEnabled) { fogPass = 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a);\n'; } var fsAlphaTestDefs = ''; var fsAlphaTestPass = ''; if (GLEmulation.alphaTestEnabled) { fsAlphaTestDefs = 'uniform float u_alphaTestRef;'; switch (GLEmulation.alphaTestFunc) { case 0x200: // GL_NEVER fsAlphaTestPass = 'discard;'; break; case 0x201: // GL_LESS fsAlphaTestPass = 'if (!(gl_FragColor.a < u_alphaTestRef)) { discard; }'; break; case 0x202: // GL_EQUAL fsAlphaTestPass = 'if (!(gl_FragColor.a == u_alphaTestRef)) { discard; }'; break; case 0x203: // GL_LEQUAL fsAlphaTestPass = 'if (!(gl_FragColor.a <= u_alphaTestRef)) { discard; }'; break; case 0x204: // GL_GREATER fsAlphaTestPass = 'if (!(gl_FragColor.a > u_alphaTestRef)) { discard; }'; break; case 0x205: // GL_NOTEQUAL fsAlphaTestPass = 'if (!(gl_FragColor.a != u_alphaTestRef)) { discard; }'; break; case 0x206: // GL_GEQUAL fsAlphaTestPass = 'if (!(gl_FragColor.a >= u_alphaTestRef)) { discard; }'; break; case 0x207: // GL_ALWAYS fsAlphaTestPass = ''; break; } } var fsSource = [ 'precision mediump float;', texUnitVaryingList, texUnitUniformList, 'varying vec4 v_color;', fogHeaderIfNeeded, fsClipPlaneDefs, fsAlphaTestDefs, 'void main()', '{', fsClipPlanePass, fsTexEnvPass, fogPass, fsAlphaTestPass, '}', '' ].join("\n").replace(/\n\n+/g, '\n'); this.fragmentShader = GLctx.createShader(GLctx.FRAGMENT_SHADER); GLctx.shaderSource(this.fragmentShader, fsSource); GLctx.compileShader(this.fragmentShader); this.program = GLctx.createProgram(); GLctx.attachShader(this.program, this.vertexShader); GLctx.attachShader(this.program, this.fragmentShader); // As optimization, bind all attributes to prespecified locations, so that the FFP emulation // code can submit attributes to any generated FFP shader without having to examine each shader in turn. // These prespecified locations are only assumed if GL_FFP_ONLY is specified, since user could also create their // own shaders that didn't have attributes in the same locations. GLctx.bindAttribLocation(this.program, GLImmediate.VERTEX, 'a_position'); GLctx.bindAttribLocation(this.program, GLImmediate.COLOR, 'a_color'); GLctx.bindAttribLocation(this.program, GLImmediate.NORMAL, 'a_normal'); var maxVertexAttribs = GLctx.getParameter(GLctx.MAX_VERTEX_ATTRIBS); for (var i = 0; i < GLImmediate.MAX_TEXTURES && GLImmediate.TEXTURE0 + i < maxVertexAttribs; i++) { GLctx.bindAttribLocation(this.program, GLImmediate.TEXTURE0 + i, 'a_texCoord'+i); GLctx.bindAttribLocation(this.program, GLImmediate.TEXTURE0 + i, aTexCoordPrefix+i); } GLctx.linkProgram(this.program); } // Stores an array that remembers which matrix uniforms are up-to-date in this FFP renderer, so they don't need to be resubmitted // each time we render with this program. this.textureMatrixVersion = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; this.positionLocation = GLctx.getAttribLocation(this.program, 'a_position'); this.texCoordLocations = []; for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { if (!GLImmediate.enabledClientAttributes[GLImmediate.TEXTURE0 + i]) { this.texCoordLocations[i] = -1; continue; } if (useCurrProgram) { this.texCoordLocations[i] = GLctx.getAttribLocation(this.program, `a_texCoord${i}`); } else { this.texCoordLocations[i] = GLctx.getAttribLocation(this.program, aTexCoordPrefix + i); } } this.colorLocation = GLctx.getAttribLocation(this.program, 'a_color'); if (!useCurrProgram) { // Temporarily switch to the program so we can set our sampler uniforms early. var prevBoundProg = GLctx.getParameter(GLctx.CURRENT_PROGRAM); GLctx.useProgram(this.program); { for (var i = 0; i < this.usedTexUnitList.length; i++) { var texUnitID = this.usedTexUnitList[i]; var texSamplerLoc = GLctx.getUniformLocation(this.program, uTexUnitPrefix + texUnitID); GLctx.uniform1i(texSamplerLoc, texUnitID); } } // The default color attribute value is not the same as the default for all other attribute streams (0,0,0,1) but (1,1,1,1), // so explicitly set it right at start. GLctx.vertexAttrib4fv(this.colorLocation, [1,1,1,1]); GLctx.useProgram(prevBoundProg); } this.textureMatrixLocations = []; for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { this.textureMatrixLocations[i] = GLctx.getUniformLocation(this.program, `u_textureMatrix${i}`); } this.normalLocation = GLctx.getAttribLocation(this.program, 'a_normal'); this.modelViewLocation = GLctx.getUniformLocation(this.program, 'u_modelView'); this.projectionLocation = GLctx.getUniformLocation(this.program, 'u_projection'); this.normalMatrixLocation = GLctx.getUniformLocation(this.program, 'u_normalMatrix'); this.hasTextures = hasTextures; this.hasNormal = GLImmediate.enabledClientAttributes[GLImmediate.NORMAL] && GLImmediate.clientAttributes[GLImmediate.NORMAL].size > 0 && this.normalLocation >= 0; this.hasColor = (this.colorLocation === 0) || this.colorLocation > 0; this.floatType = GLctx.FLOAT; // minor optimization this.fogColorLocation = GLctx.getUniformLocation(this.program, 'u_fogColor'); this.fogEndLocation = GLctx.getUniformLocation(this.program, 'u_fogEnd'); this.fogScaleLocation = GLctx.getUniformLocation(this.program, 'u_fogScale'); this.fogDensityLocation = GLctx.getUniformLocation(this.program, 'u_fogDensity'); this.hasFog = !!(this.fogColorLocation || this.fogEndLocation || this.fogScaleLocation || this.fogDensityLocation); this.pointSizeLocation = GLctx.getUniformLocation(this.program, 'u_pointSize'); this.hasClipPlane = false; this.clipPlaneEquationLocation = []; for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { this.clipPlaneEquationLocation[clipPlaneId] = GLctx.getUniformLocation(this.program, `u_clipPlaneEquation${clipPlaneId}`); this.hasClipPlane = (this.hasClipPlane || this.clipPlaneEquationLocation[clipPlaneId]); } this.hasLighting = GLEmulation.lightingEnabled; this.lightModelAmbientLocation = GLctx.getUniformLocation(this.program, 'u_lightModelAmbient'); this.materialAmbientLocation = GLctx.getUniformLocation(this.program, 'u_materialAmbient'); this.materialDiffuseLocation = GLctx.getUniformLocation(this.program, 'u_materialDiffuse'); this.materialSpecularLocation = GLctx.getUniformLocation(this.program, 'u_materialSpecular'); this.materialShininessLocation = GLctx.getUniformLocation(this.program, 'u_materialShininess'); this.materialEmissionLocation = GLctx.getUniformLocation(this.program, 'u_materialEmission'); this.lightAmbientLocation = [] this.lightDiffuseLocation = [] this.lightSpecularLocation = [] this.lightPositionLocation = [] for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { this.lightAmbientLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightAmbient${lightId}`); this.lightDiffuseLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightDiffuse${lightId}`); this.lightSpecularLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightSpecular${lightId}`); this.lightPositionLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightPosition${lightId}`); } this.hasAlphaTest = GLEmulation.alphaTestEnabled; this.alphaTestRefLocation = GLctx.getUniformLocation(this.program, 'u_alphaTestRef'); }; this.prepare = function() { // Calculate the array buffer var arrayBuffer; if (!GLctx.currentArrayBufferBinding) { var start = GLImmediate.firstVertex*GLImmediate.stride; var end = GLImmediate.lastVertex*GLImmediate.stride; #if ASSERTIONS assert(end <= GL.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); #endif arrayBuffer = GL.getTempVertexBuffer(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 = GLctx.currentArrayBufferBinding; } #if GL_UNSAFE_OPTS // 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 var lastRenderer = GLImmediate.lastRenderer; var canSkip = this == lastRenderer && arrayBuffer == GLImmediate.lastArrayBuffer && (GL.currProgram || this.program) == GLImmediate.lastProgram && GLImmediate.stride == GLImmediate.lastStride && !GLImmediate.matricesModified; if (!canSkip && lastRenderer) lastRenderer.cleanup(); #endif if (!GLctx.currentArrayBufferBinding) { // Bind the array buffer and upload data after cleaning up the previous renderer if (arrayBuffer != GLImmediate.lastArrayBuffer) { GLctx.bindBuffer(GLctx.ARRAY_BUFFER, arrayBuffer); GLImmediate.lastArrayBuffer = arrayBuffer; } GLctx.bufferSubData(GLctx.ARRAY_BUFFER, start, GLImmediate.vertexData.subarray(start >> 2, end >> 2)); } #if GL_UNSAFE_OPTS if (canSkip) return; GLImmediate.lastRenderer = this; GLImmediate.lastProgram = GL.currProgram || this.program; GLImmediate.lastStride = GLImmediate.stride; GLImmediate.matricesModified = false; #endif if (!GL.currProgram) { if (GLImmediate.fixedFunctionProgram != this.program) { GLctx.useProgram(this.program); GLImmediate.fixedFunctionProgram = this.program; } } if (this.modelViewLocation && this.modelViewMatrixVersion != GLImmediate.matrixVersion[0/*m*/]) { this.modelViewMatrixVersion = GLImmediate.matrixVersion[0/*m*/]; GLctx.uniformMatrix4fv(this.modelViewLocation, false, GLImmediate.matrix[0/*m*/]); // set normal matrix to the upper 3x3 of the inverse transposed current modelview matrix if (GLEmulation.lightEnabled) { var tmpMVinv = GLImmediate.matrixLib.mat4.create(GLImmediate.matrix[0]); GLImmediate.matrixLib.mat4.inverse(tmpMVinv); GLImmediate.matrixLib.mat4.transpose(tmpMVinv); GLctx.uniformMatrix3fv(this.normalMatrixLocation, false, GLImmediate.matrixLib.mat4.toMat3(tmpMVinv)); } } if (this.projectionLocation && this.projectionMatrixVersion != GLImmediate.matrixVersion[1/*p*/]) { this.projectionMatrixVersion = GLImmediate.matrixVersion[1/*p*/]; GLctx.uniformMatrix4fv(this.projectionLocation, false, GLImmediate.matrix[1/*p*/]); } var clientAttributes = GLImmediate.clientAttributes; var posAttr = clientAttributes[GLImmediate.VERTEX]; #if GL_ASSERTIONS GL.validateVertexAttribPointer(posAttr.size, posAttr.type, GLImmediate.stride, clientAttributes[GLImmediate.VERTEX].offset); #endif #if GL_FFP_ONLY if (!GLctx.currentArrayBufferBinding) { GLctx.vertexAttribPointer(GLImmediate.VERTEX, posAttr.size, posAttr.type, false, GLImmediate.stride, posAttr.offset); if (this.hasNormal) { var normalAttr = clientAttributes[GLImmediate.NORMAL]; GLctx.vertexAttribPointer(GLImmediate.NORMAL, normalAttr.size, normalAttr.type, true, GLImmediate.stride, normalAttr.offset); } } #else GLctx.vertexAttribPointer(this.positionLocation, posAttr.size, posAttr.type, false, GLImmediate.stride, posAttr.offset); GLctx.enableVertexAttribArray(this.positionLocation); if (this.hasNormal) { var normalAttr = clientAttributes[GLImmediate.NORMAL]; #if GL_ASSERTIONS GL.validateVertexAttribPointer(normalAttr.size, normalAttr.type, GLImmediate.stride, normalAttr.offset); #endif GLctx.vertexAttribPointer(this.normalLocation, normalAttr.size, normalAttr.type, true, GLImmediate.stride, normalAttr.offset); GLctx.enableVertexAttribArray(this.normalLocation); } #endif if (this.hasTextures) { for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { #if GL_FFP_ONLY if (!GLctx.currentArrayBufferBinding) { var attribLoc = GLImmediate.TEXTURE0+i; var texAttr = clientAttributes[attribLoc]; if (texAttr.size) { GLctx.vertexAttribPointer(attribLoc, texAttr.size, texAttr.type, false, GLImmediate.stride, texAttr.offset); } else { // These two might be dangerous, but let's try them. GLctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1); } } #else var attribLoc = this.texCoordLocations[i]; if (attribLoc === undefined || attribLoc < 0) continue; var texAttr = clientAttributes[GLImmediate.TEXTURE0+i]; if (texAttr.size) { #if GL_ASSERTIONS GL.validateVertexAttribPointer(texAttr.size, texAttr.type, GLImmediate.stride, texAttr.offset); #endif GLctx.vertexAttribPointer(attribLoc, texAttr.size, texAttr.type, false, GLImmediate.stride, texAttr.offset); GLctx.enableVertexAttribArray(attribLoc); } else { // These two might be dangerous, but let's try them. GLctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1); GLctx.disableVertexAttribArray(attribLoc); } #endif var t = 2/*t*/+i; if (this.textureMatrixLocations[i] && this.textureMatrixVersion[t] != GLImmediate.matrixVersion[t]) { // XXX might we need this even without the condition we are currently in? this.textureMatrixVersion[t] = GLImmediate.matrixVersion[t]; GLctx.uniformMatrix4fv(this.textureMatrixLocations[i], false, GLImmediate.matrix[t]); } } } if (GLImmediate.enabledClientAttributes[GLImmediate.COLOR]) { var colorAttr = clientAttributes[GLImmediate.COLOR]; #if GL_ASSERTIONS GL.validateVertexAttribPointer(colorAttr.size, colorAttr.type, GLImmediate.stride, colorAttr.offset); #endif #if GL_FFP_ONLY if (!GLctx.currentArrayBufferBinding) { GLctx.vertexAttribPointer(GLImmediate.COLOR, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); } #else GLctx.vertexAttribPointer(this.colorLocation, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); GLctx.enableVertexAttribArray(this.colorLocation); #endif } #if !GL_FFP_ONLY else if (this.hasColor) { GLctx.disableVertexAttribArray(this.colorLocation); GLctx.vertexAttrib4fv(this.colorLocation, GLImmediate.clientColor); } #endif if (this.hasFog) { if (this.fogColorLocation) GLctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor); if (this.fogEndLocation) GLctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd); if (this.fogScaleLocation) GLctx.uniform1f(this.fogScaleLocation, 1/(GLEmulation.fogEnd - GLEmulation.fogStart)); if (this.fogDensityLocation) GLctx.uniform1f(this.fogDensityLocation, GLEmulation.fogDensity); } if (this.hasClipPlane) { for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { if (this.clipPlaneEquationLocation[clipPlaneId]) GLctx.uniform4fv(this.clipPlaneEquationLocation[clipPlaneId], GLEmulation.clipPlaneEquation[clipPlaneId]); } } if (this.hasLighting) { if (this.lightModelAmbientLocation) GLctx.uniform4fv(this.lightModelAmbientLocation, GLEmulation.lightModelAmbient); if (this.materialAmbientLocation) GLctx.uniform4fv(this.materialAmbientLocation, GLEmulation.materialAmbient); if (this.materialDiffuseLocation) GLctx.uniform4fv(this.materialDiffuseLocation, GLEmulation.materialDiffuse); if (this.materialSpecularLocation) GLctx.uniform4fv(this.materialSpecularLocation, GLEmulation.materialSpecular); if (this.materialShininessLocation) GLctx.uniform1f(this.materialShininessLocation, GLEmulation.materialShininess[0]); if (this.materialEmissionLocation) GLctx.uniform4fv(this.materialEmissionLocation, GLEmulation.materialEmission); for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { if (this.lightAmbientLocation[lightId]) GLctx.uniform4fv(this.lightAmbientLocation[lightId], GLEmulation.lightAmbient[lightId]); if (this.lightDiffuseLocation[lightId]) GLctx.uniform4fv(this.lightDiffuseLocation[lightId], GLEmulation.lightDiffuse[lightId]); if (this.lightSpecularLocation[lightId]) GLctx.uniform4fv(this.lightSpecularLocation[lightId], GLEmulation.lightSpecular[lightId]); if (this.lightPositionLocation[lightId]) GLctx.uniform4fv(this.lightPositionLocation[lightId], GLEmulation.lightPosition[lightId]); } } if (this.hasAlphaTest) { if (this.alphaTestRefLocation) GLctx.uniform1f(this.alphaTestRefLocation, GLEmulation.alphaTestRef); } if (GLImmediate.mode == GLctx.POINTS) { if (this.pointSizeLocation) { GLctx.uniform1f(this.pointSizeLocation, GLEmulation.pointSize); } } }; this.cleanup = function() { #if !GL_FFP_ONLY GLctx.disableVertexAttribArray(this.positionLocation); if (this.hasTextures) { for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { if (GLImmediate.enabledClientAttributes[GLImmediate.TEXTURE0+i] && this.texCoordLocations[i] >= 0) { GLctx.disableVertexAttribArray(this.texCoordLocations[i]); } } } if (this.hasColor) { GLctx.disableVertexAttribArray(this.colorLocation); } if (this.hasNormal) { GLctx.disableVertexAttribArray(this.normalLocation); } if (!GL.currProgram) { GLctx.useProgram(null); GLImmediate.fixedFunctionProgram = 0; } if (!GLctx.currentArrayBufferBinding) { GLctx.bindBuffer(GLctx.ARRAY_BUFFER, null); GLImmediate.lastArrayBuffer = null; } #if GL_UNSAFE_OPTS GLImmediate.lastRenderer = null; GLImmediate.lastProgram = null; #endif GLImmediate.matricesModified = true; #endif } this.init(); } return new Renderer(); }, setupFuncs() { // TexEnv stuff needs to be prepared early, so do it here. // init() is too late for -O2, since it freezes the GL functions // by that point. GLImmediate.MapTreeLib = GLImmediate.spawnMapTreeLib(); GLImmediate.spawnMapTreeLib = null; GLImmediate.TexEnvJIT = GLImmediate.spawnTexEnvJIT(); GLImmediate.spawnTexEnvJIT = null; GLImmediate.setupHooks(); }, setupHooks() { if (!GLEmulation.hasRunInit) { GLEmulation.init(); } var glActiveTexture = _glActiveTexture; _glActiveTexture = _emscripten_glActiveTexture = (texture) => { GLImmediate.TexEnvJIT.hook_activeTexture(texture); glActiveTexture(texture); }; var glEnable = _glEnable; _glEnable = _emscripten_glEnable = (cap) => { GLImmediate.TexEnvJIT.hook_enable(cap); glEnable(cap); }; var glDisable = _glDisable; _glDisable = _emscripten_glDisable = (cap) => { GLImmediate.TexEnvJIT.hook_disable(cap); glDisable(cap); }; var glTexEnvf = (typeof _glTexEnvf != 'undefined') ? _glTexEnvf : () => {}; /** @suppress {checkTypes} */ _glTexEnvf = _emscripten_glTexEnvf = (target, pname, param) => { GLImmediate.TexEnvJIT.hook_texEnvf(target, pname, param); // Don't call old func, since we are the implementor. //glTexEnvf(target, pname, param); }; var glTexEnvi = (typeof _glTexEnvi != 'undefined') ? _glTexEnvi : () => {}; /** @suppress {checkTypes} */ _glTexEnvi = _emscripten_glTexEnvi = (target, pname, param) => { {{{ fromPtr('param') }}} GLImmediate.TexEnvJIT.hook_texEnvi(target, pname, param); // Don't call old func, since we are the implementor. //glTexEnvi(target, pname, param); }; var glTexEnvfv = (typeof _glTexEnvfv != 'undefined') ? _glTexEnvfv : () => {}; /** @suppress {checkTypes} */ _glTexEnvfv = _emscripten_glTexEnvfv = (target, pname, param) => { {{{ fromPtr('param') }}} GLImmediate.TexEnvJIT.hook_texEnvfv(target, pname, param); // Don't call old func, since we are the implementor. //glTexEnvfv(target, pname, param); }; _glGetTexEnviv = (target, pname, param) => { {{{ fromPtr('param') }}} GLImmediate.TexEnvJIT.hook_getTexEnviv(target, pname, param); }; _glGetTexEnvfv = (target, pname, param) => { {{{ fromPtr('param') }}} GLImmediate.TexEnvJIT.hook_getTexEnvfv(target, pname, param); }; var glGetIntegerv = _glGetIntegerv; _glGetIntegerv = _emscripten_glGetIntegerv = (pname, params) => { switch (pname) { case 0x8B8D: { // GL_CURRENT_PROGRAM // Just query directly so we're working with WebGL objects. var cur = GLctx.getParameter(GLctx.CURRENT_PROGRAM); if (cur == GLImmediate.fixedFunctionProgram) { // Pretend we're not using a program. {{{ makeSetValue('params', '0', '0', 'i32') }}}; return; } break; } } glGetIntegerv(pname, params); }; }, // Main functions initted: false, init() { err('WARNING: using emscripten GL immediate mode emulation. This is very limited in what it supports'); GLImmediate.initted = true; if (!Browser.useWebGL) return; // a 2D canvas may be currently used TODO: make sure we are actually called in that case // User can override the maximum number of texture units that we emulate. Using fewer texture units increases runtime performance // slightly, so it is advantageous to choose as small value as needed. // Limit to a maximum of 28 to not overflow the state bits used for renderer caching (31 bits = 3 attributes + 28 texture units). GLImmediate.MAX_TEXTURES = Math.min(Module['GL_MAX_TEXTURE_IMAGE_UNITS'] || GLctx.getParameter(GLctx.MAX_TEXTURE_IMAGE_UNITS), 28); GLImmediate.TexEnvJIT.init(GLctx, GLImmediate.MAX_TEXTURES); GLImmediate.NUM_ATTRIBUTES = 3 /*pos+normal+color attributes*/ + GLImmediate.MAX_TEXTURES; GLImmediate.clientAttributes = []; GLEmulation.enabledClientAttribIndices = []; for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { GLImmediate.clientAttributes.push({}); GLEmulation.enabledClientAttribIndices.push(false); } // Initialize matrix library // When user sets a matrix, increment a 'version number' on the new data, and when rendering, submit // the matrices to the shader program only if they have an old version of the data. GLImmediate.matrix = []; GLImmediate.matrixStack = []; GLImmediate.matrixVersion = []; for (var i = 0; i < 2 + GLImmediate.MAX_TEXTURES; i++) { // Modelview, Projection, plus one matrix for each texture coordinate. GLImmediate.matrixStack.push([]); GLImmediate.matrixVersion.push(0); GLImmediate.matrix.push(GLImmediate.matrixLib.mat4.create()); GLImmediate.matrixLib.mat4.identity(GLImmediate.matrix[i]); } // Renderer cache GLImmediate.rendererCache = GLImmediate.MapTreeLib.create(); // Buffers for data GLImmediate.tempData = new Float32Array(GL.MAX_TEMP_BUFFER_SIZE >> 2); GLImmediate.indexData = new Uint16Array(GL.MAX_TEMP_BUFFER_SIZE >> 1); GLImmediate.vertexDataU8 = new Uint8Array(GLImmediate.tempData.buffer); GL.generateTempBuffers(true, GL.currentContext); GLImmediate.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(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 (!GLImmediate.modifiedClientAttributes) { #if GL_ASSERTIONS if ((GLImmediate.stride & 3) != 0) { warnOnce(`Warning: Rendering from client side vertex arrays where stride (${GLImmediate.stride}) is not a multiple of four! This is not currently supported!`); } #endif GLImmediate.vertexCounter = (GLImmediate.stride * count) / 4; // XXX assuming float return; } GLImmediate.modifiedClientAttributes = false; // The role of prepareClientAttributes is to examine the set of // client-side vertex attribute buffers that user code has submitted, and // to prepare them to be uploaded to a VBO in GPU memory (since WebGL does // not support client-side rendering, i.e. rendering from vertex data in // CPU memory). User can submit vertex data generally in three different // configurations: // 1. Fully planar: all attributes are in their own separate // tightly-packed arrays in CPU memory. // 2. Fully interleaved: all attributes share a single array where data is // interleaved something like (pos,uv,normal), // (pos,uv,normal), ... // 3. Complex hybrid: Multiple separate arrays that either are sparsely // strided, and/or partially interleaves vertex // attributes. // For simplicity, we support the case (2) as the fast case. For (1) and // (3), we do a memory copy of the vertex data here to prepare a // relayouted buffer that is of the structure in case (2). The reason // for this is that it allows the emulation code to get away with using // just one VBO buffer for rendering, and not have to maintain multiple // ones. Therefore cases (1) and (3) will be very slow, and case (2) is // fast. // Detect which case we are in by using a quick heuristic by examining the // strides of the buffers. If all the buffers have identical stride, we // assume we have case (2), otherwise we have something more complex. var clientStartPointer = {{{ POINTER_MAX }}}; var bytes = 0; // Total number of bytes taken up by a single vertex. var minStride = {{{ POINTER_MAX }}}; var maxStride = 0; var attributes = GLImmediate.liveClientAttributes; attributes.length = 0; for (var i = 0; i < 3+GLImmediate.MAX_TEXTURES; i++) { if (GLImmediate.enabledClientAttributes[i]) { var attr = GLImmediate.clientAttributes[i]; attributes.push(attr); clientStartPointer = Math.min(clientStartPointer, attr.pointer); attr.sizeBytes = attr.size * GL.byteSizeByType[attr.type - GL.byteSizeByTypeRoot]; bytes += attr.sizeBytes; minStride = Math.min(minStride, attr.stride); maxStride = Math.max(maxStride, attr.stride); } } if ((minStride != maxStride || maxStride < bytes) && !beginEnd) { // We are in cases (1) or (3): slow path, shuffle the data around into a // single interleaved vertex buffer. // The immediate-mode glBegin()/glEnd() vertex submission gets // automatically generated in appropriate layout, so never need to come // down this path if that was used. #if GL_ASSERTIONS warnOnce('Rendering from planar client-side vertex arrays. This is a very slow emulation path! Use interleaved vertex arrays for best performance.'); #endif GLImmediate.restrideBuffer ||= _malloc(GL.MAX_TEMP_BUFFER_SIZE); var start = GLImmediate.restrideBuffer; bytes = 0; // calculate restrided offsets and total size for (var attr of attributes) { var size = attr.sizeBytes; if (size % 4 != 0) size += 4 - (size % 4); // align everything attr.offset = bytes; bytes += size; } // copy out the data (we need to know the stride for that, and define attr.pointer) for (var attr of attributes) { var srcStride = Math.max(attr.sizeBytes, attr.stride); if ((srcStride & 3) == 0 && (attr.sizeBytes & 3) == 0) { for (var j = 0; j < count; j++) { for (var k = 0; k < attr.sizeBytes; k+=4) { // copy in chunks of 4 bytes, our alignment makes this possible var val = {{{ makeGetValue('attr.pointer', 'j*srcStride + k', 'i32') }}}; {{{ makeSetValue('start + attr.offset', 'bytes*j + k', 'val', 'i32') }}}; } } } else { for (var j = 0; j < count; j++) { for (var k = 0; k < attr.sizeBytes; k++) { // source data was not aligned to multiples of 4, must copy byte by byte. HEAP8[start + attr.offset + bytes*j + k] = HEAP8[attr.pointer + j*srcStride + k]; } } } attr.pointer = start + attr.offset; } GLImmediate.stride = bytes; GLImmediate.vertexPointer = start; } else { // case (2): fast path, all data is interleaved to a single vertex array so we can get away with a single VBO upload. if (GLctx.currentArrayBufferBinding) { GLImmediate.vertexPointer = 0; } else { GLImmediate.vertexPointer = clientStartPointer; } for (var attr of attributes) { attr.offset = attr.pointer - GLImmediate.vertexPointer; // Compute what will be the offset of this attribute in the VBO after we upload. } GLImmediate.stride = Math.max(maxStride, bytes); } if (!beginEnd) { #if GL_ASSERTIONS if ((GLImmediate.stride & 3) != 0) { warnOnce(`Warning: Rendering from client side vertex arrays where stride (${GLImmediate.stride}) is not a multiple of four! This is not currently supported!`); } #endif GLImmediate.vertexCounter = (GLImmediate.stride * count) / 4; // XXX assuming float } }, flush(numProvidedIndexes, startIndex = 0, ptr = 0) { #if ASSERTIONS assert(numProvidedIndexes >= 0 || !numProvidedIndexes); #endif var renderer = GLImmediate.getRenderer(); // Generate index data in a format suitable for GLES 2.0/WebGL var numVertices = 4 * GLImmediate.vertexCounter / GLImmediate.stride; if (!numVertices) return; #if ASSERTIONS assert(numVertices % 1 == 0, "`numVertices` must be an integer."); #endif var emulatedElementArrayBuffer = false; var numIndexes = 0; if (numProvidedIndexes) { numIndexes = numProvidedIndexes; if (!GLctx.currentArrayBufferBinding && GLImmediate.firstVertex > GLImmediate.lastVertex) { // Figure out the first and last vertex from the index data #if ASSERTIONS // 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! assert(!GLctx.currentElementArrayBufferBinding, 'must use array buffers when using element buffer'); #endif for (var i = 0; i < numProvidedIndexes; i++) { var currIndex = {{{ makeGetValue('ptr', 'i*2', 'u16') }}}; GLImmediate.firstVertex = Math.min(GLImmediate.firstVertex, currIndex); GLImmediate.lastVertex = Math.max(GLImmediate.lastVertex, currIndex+1); } } if (!GLctx.currentElementArrayBufferBinding) { // If no element array buffer is bound, then indices is a literal pointer to clientside data #if ASSERTIONS assert(numProvidedIndexes << 1 <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); #endif var indexBuffer = GL.getTempIndexBuffer(numProvidedIndexes << 1); GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, indexBuffer); GLctx.bufferSubData(GLctx.ELEMENT_ARRAY_BUFFER, 0, {{{ makeHEAPView('U16', 'ptr', 'ptr + (numProvidedIndexes << 1)') }}}); ptr = 0; emulatedElementArrayBuffer = true; } } else if (GLImmediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes if (GLImmediate.mode != 7) abort('unsupported immediate mode ' + GLImmediate.mode); // GL_QUADS // GLImmediate.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 #if ASSERTIONS assert(GLImmediate.firstVertex % 4 == 0); #endif ptr = GLImmediate.firstVertex * 3; var numQuads = numVertices / 4; numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern #if ASSERTIONS assert(ptr + (numIndexes << 1) <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); #endif GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.currentContext.tempQuadIndexBuffer); emulatedElementArrayBuffer = true; GLImmediate.mode = GLctx.TRIANGLES; } renderer.prepare(); if (numIndexes) { GLctx.drawElements(GLImmediate.mode, numIndexes, GLctx.UNSIGNED_SHORT, ptr); } else { GLctx.drawArrays(GLImmediate.mode, startIndex, numVertices); } if (emulatedElementArrayBuffer) { GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GLctx.currentElementArrayBufferBinding] || null); } #if !GL_UNSAFE_OPTS #if !GL_FFP_ONLY renderer.cleanup(); #endif #endif } }, $GLImmediateSetup__deps: ['$GLImmediate', () => 'GLImmediate.matrixLib = ' + read('gl-matrix.js') + ';\n'], $GLImmediateSetup: {}, glBegin__deps: ['$GLImmediateSetup'], glBegin: (mode) => { // Push the old state: GLImmediate.enabledClientAttributes_preBegin = GLImmediate.enabledClientAttributes; GLImmediate.enabledClientAttributes = []; GLImmediate.clientAttributes_preBegin = GLImmediate.clientAttributes; GLImmediate.clientAttributes = [] for (var i = 0; i < GLImmediate.clientAttributes_preBegin.length; i++) { GLImmediate.clientAttributes.push({}); } GLImmediate.mode = mode; GLImmediate.vertexCounter = 0; var components = GLImmediate.rendererComponents = []; for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { components[i] = 0; } GLImmediate.rendererComponentPointer = 0; GLImmediate.vertexData = GLImmediate.tempData; }, glEnd: () => { GLImmediate.prepareClientAttributes(GLImmediate.rendererComponents[GLImmediate.VERTEX], true); GLImmediate.firstVertex = 0; GLImmediate.lastVertex = GLImmediate.vertexCounter / (GLImmediate.stride >> 2); GLImmediate.flush(); GLImmediate.disableBeginEndClientAttributes(); GLImmediate.mode = -1; // Pop the old state: GLImmediate.enabledClientAttributes = GLImmediate.enabledClientAttributes_preBegin; GLImmediate.clientAttributes = GLImmediate.clientAttributes_preBegin; GLImmediate.currentRenderer = null; // The set of active client attributes changed, we must re-lookup the renderer to use. GLImmediate.modifiedClientAttributes = true; }, glVertex2f: (x, y) => { #if ASSERTIONS assert(GLImmediate.mode >= 0); // must be in begin/end #endif GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; GLImmediate.vertexData[GLImmediate.vertexCounter++] = 0; GLImmediate.vertexData[GLImmediate.vertexCounter++] = 1; #if ASSERTIONS assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); #endif GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); }, glVertex3f: (x, y, z) => { #if ASSERTIONS assert(GLImmediate.mode >= 0); // must be in begin/end #endif GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; GLImmediate.vertexData[GLImmediate.vertexCounter++] = 1; #if ASSERTIONS assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); #endif GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); }, glVertex4f: (x, y, z, w) => { #if ASSERTIONS assert(GLImmediate.mode >= 0); // must be in begin/end #endif GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; GLImmediate.vertexData[GLImmediate.vertexCounter++] = w; #if ASSERTIONS assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); #endif GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); }, glVertex2fv__deps: ['glVertex2f'], glVertex2fv: (p) => _glVertex2f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}), glVertex3fv__deps: ['glVertex3f'], glVertex3fv: (p) => _glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}), glVertex4fv__deps: ['glVertex4f'], glVertex4fv: (p) => _glVertex4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}}), glVertex2i: 'glVertex2f', glVertex3i: 'glVertex3f', glVertex4i: 'glVertex4f', glTexCoord2i: (u, v) => { #if ASSERTIONS assert(GLImmediate.mode >= 0); // must be in begin/end #endif GLImmediate.vertexData[GLImmediate.vertexCounter++] = u; GLImmediate.vertexData[GLImmediate.vertexCounter++] = v; GLImmediate.addRendererComponent(GLImmediate.TEXTURE0, 2, GLctx.FLOAT); }, glTexCoord2f: 'glTexCoord2i', glTexCoord2fv__deps: ['glTexCoord2i'], glTexCoord2fv: (v) => _glTexCoord2i({{{ makeGetValue('v', '0', 'float') }}}, {{{ makeGetValue('v', '4', 'float') }}}), glTexCoord4f: () => { abort('glTexCoord4f: TODO') }, glColor4f: (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 (GLImmediate.mode >= 0) { var start = GLImmediate.vertexCounter << 2; GLImmediate.vertexDataU8[start + 0] = r * 255; GLImmediate.vertexDataU8[start + 1] = g * 255; GLImmediate.vertexDataU8[start + 2] = b * 255; GLImmediate.vertexDataU8[start + 3] = a * 255; GLImmediate.vertexCounter++; GLImmediate.addRendererComponent(GLImmediate.COLOR, 4, GLctx.UNSIGNED_BYTE); } else { GLImmediate.clientColor[0] = r; GLImmediate.clientColor[1] = g; GLImmediate.clientColor[2] = b; GLImmediate.clientColor[3] = a; #if GL_FFP_ONLY GLctx.vertexAttrib4fv(GLImmediate.COLOR, GLImmediate.clientColor); #endif } }, glColor4d: 'glColor4f', glColor4ub__deps: ['glColor4f'], glColor4ub: (r, g, b, a) => _glColor4f((r&255)/255, (g&255)/255, (b&255)/255, (a&255)/255), glColor4us__deps: ['glColor4f'], glColor4us: (r, g, b, a) => _glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535), glColor4ui__deps: ['glColor4f'], glColor4ui: (r, g, b, a) => _glColor4f((r>>>0)/4294967295, (g>>>0)/4294967295, (b>>>0)/4294967295, (a>>>0)/4294967295), glColor3f__deps: ['glColor4f'], glColor3f: (r, g, b) => _glColor4f(r, g, b, 1), glColor3d: 'glColor3f', glColor3ub__deps: ['glColor4ub'], glColor3ub: (r, g, b) => _glColor4ub(r, g, b, 255), glColor3us__deps: ['glColor4us'], glColor3us: (r, g, b) => _glColor4us(r, g, b, 65535), glColor3ui__deps: ['glColor4ui'], glColor3ui: (r, g, b) => _glColor4ui(r, g, b, 4294967295), glColor3ubv__deps: ['glColor3ub'], glColor3ubv: (p) => _glColor3ub({{{ makeGetValue('p', '0', 'i8') }}}, {{{ makeGetValue('p', '1', 'i8') }}}, {{{ makeGetValue('p', '2', 'i8') }}}), glColor3usv__deps: ['glColor3us'], glColor3usv: (p) => _glColor3us({{{ makeGetValue('p', '0', 'i16') }}}, {{{ makeGetValue('p', '2', 'i16') }}}, {{{ makeGetValue('p', '4', 'i16') }}}), glColor3uiv__deps: ['glColor3ui'], glColor3uiv: (p) => _glColor3ui({{{ makeGetValue('p', '0', 'i32') }}}, {{{ makeGetValue('p', '4', 'i32') }}}, {{{ makeGetValue('p', '8', 'i32') }}}), glColor3fv__deps: ['glColor3f'], glColor3fv: (p) => _glColor3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}), glColor4fv__deps: ['glColor4f'], glColor4fv: (p) => _glColor4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}}), glColor4ubv__deps: ['glColor4ub'], glColor4ubv: (p) => _glColor4ub({{{ makeGetValue('p', '0', 'i8') }}}, {{{ makeGetValue('p', '1', 'i8') }}}, {{{ makeGetValue('p', '2', 'i8') }}}, {{{ makeGetValue('p', '3', 'i8') }}}), glFogf: (pname, param) => { // partial support, TODO switch (pname) { case 0xB63: // GL_FOG_START GLEmulation.fogStart = param; break; case 0xB64: // GL_FOG_END GLEmulation.fogEnd = param; break; case 0xB62: // GL_FOG_DENSITY GLEmulation.fogDensity = param; break; case 0xB65: // GL_FOG_MODE switch (param) { case 0x801: // GL_EXP2 case 0x2601: // GL_LINEAR if (GLEmulation.fogMode != param) { GLImmediate.currentRenderer = null; // Fog mode is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.fogMode = param; } break; default: // default to GL_EXP if (GLEmulation.fogMode != 0x800 /* GL_EXP */) { GLImmediate.currentRenderer = null; // Fog mode is part of the FFP shader state, we must re-lookup the renderer to use. GLEmulation.fogMode = 0x800 /* GL_EXP */; } break; } break; } }, glFogi__deps: ['glFogf'], glFogi: (pname, param) => { return _glFogf(pname, param); }, glFogfv__deps: ['glFogf'], glFogfv: (pname, param) => { // partial support, TODO switch (pname) { case 0xB66: // 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 0xB63: // GL_FOG_START case 0xB64: // GL_FOG_END _glFogf(pname, {{{ makeGetValue('param', '0', 'float') }}}); break; } }, glFogiv__deps: ['glFogf'], glFogiv: (pname, param) => { switch (pname) { case 0xB66: // 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', glPointSize: (size) => { GLEmulation.pointSize = size; }, glPolygonMode: () => {}, // TODO glAlphaFunc: (func, ref) => { switch(func) { case 0x200: // GL_NEVER case 0x201: // GL_LESS case 0x202: // GL_EQUAL case 0x203: // GL_LEQUAL case 0x204: // GL_GREATER case 0x205: // GL_NOTEQUAL case 0x206: // GL_GEQUAL case 0x207: // GL_ALWAYS GLEmulation.alphaTestRef = ref; if (GLEmulation.alphaTestFunc != func) { GLEmulation.alphaTestFunc = func; GLImmediate.currentRenderer = null; // alpha test mode is part of the FFP shader state, we must re-lookup the renderer to use. } break; default: // invalid value provided #if GL_ASSERTIONS err(`glAlphaFunc: Invalid alpha comparison function ${ptrToString(func)}!`); #endif break; } }, glNormal3f: (x, y, z) => { #if ASSERTIONS assert(GLImmediate.mode >= 0); // must be in begin/end #endif GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; #if ASSERTIONS assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); #endif GLImmediate.addRendererComponent(GLImmediate.NORMAL, 3, GLctx.FLOAT); }, glNormal3fv__deps: ['glNormal3f'], glNormal3fv: (p) => { _glNormal3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}); }, // Additional non-GLES rendering calls glDrawRangeElements__deps: ['glDrawElements'], glDrawRangeElements: (mode, start, end, count, type, indices) => { _glDrawElements(mode, count, type, indices, start, end); }, // ClientState/gl*Pointer glEnableClientState: (cap) => { var attrib = GLEmulation.getAttributeFromCapability(cap); if (attrib === null) { #if ASSERTIONS err(`WARNING: unhandled clientstate: ${cap}`); #endif return; } if (!GLImmediate.enabledClientAttributes[attrib]) { GLImmediate.enabledClientAttributes[attrib] = true; GLImmediate.totalEnabledClientAttributes++; GLImmediate.currentRenderer = null; // Will need to change current renderer, since the set of active vertex pointers changed. #if GL_FFP_ONLY // In GL_FFP_ONLY mode, attributes are bound to the same index in each FFP emulation shader, so we can immediately apply the change here. GL.enableVertexAttribArray(attrib); #endif if (GLEmulation.currentVao) GLEmulation.currentVao.enabledClientStates[cap] = 1; GLImmediate.modifiedClientAttributes = true; } }, glDisableClientState: (cap) => { var attrib = GLEmulation.getAttributeFromCapability(cap); if (attrib === null) { #if ASSERTIONS err(`WARNING: unhandled clientstate: ${cap}`); #endif return; } if (GLImmediate.enabledClientAttributes[attrib]) { GLImmediate.enabledClientAttributes[attrib] = false; GLImmediate.totalEnabledClientAttributes--; GLImmediate.currentRenderer = null; // Will need to change current renderer, since the set of active vertex pointers changed. #if GL_FFP_ONLY // In GL_FFP_ONLY mode, attributes are bound to the same index in each FFP emulation shader, so we can immediately apply the change here. GL.disableVertexAttribArray(attrib); #endif if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledClientStates[cap]; GLImmediate.modifiedClientAttributes = true; } }, glVertexPointer: (size, type, stride, pointer) => { GLImmediate.setClientAttribute(GLImmediate.VERTEX, size, type, stride, pointer); #if GL_FFP_ONLY if (GLctx.currentArrayBufferBinding) { GLctx.vertexAttribPointer(GLImmediate.VERTEX, size, type, false, stride, pointer); } #endif }, glTexCoordPointer: (size, type, stride, pointer) => { GLImmediate.setClientAttribute(GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture, size, type, stride, pointer); #if GL_FFP_ONLY if (GLctx.currentArrayBufferBinding) { var loc = GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture; GLctx.vertexAttribPointer(loc, size, type, false, stride, pointer); } #endif }, glNormalPointer: (type, stride, pointer) => { GLImmediate.setClientAttribute(GLImmediate.NORMAL, 3, type, stride, pointer); #if GL_FFP_ONLY if (GLctx.currentArrayBufferBinding) { GLctx.vertexAttribPointer(GLImmediate.NORMAL, 3, type, true, stride, pointer); } #endif }, glColorPointer: (size, type, stride, pointer) => { GLImmediate.setClientAttribute(GLImmediate.COLOR, size, type, stride, pointer); #if GL_FFP_ONLY if (GLctx.currentArrayBufferBinding) { GLctx.vertexAttribPointer(GLImmediate.COLOR, size, type, true, stride, pointer); } #endif }, glClientActiveTexture: (texture) => { GLImmediate.clientActiveTexture = texture - 0x84C0; // GL_TEXTURE0 }, // 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: (mode, first, count) => { if (GLImmediate.totalEnabledClientAttributes == 0 && mode <= 6) { GLctx.drawArrays(mode, first, count); return; } GLImmediate.prepareClientAttributes(count, false); GLImmediate.mode = mode; if (!GLctx.currentArrayBufferBinding) { GLImmediate.vertexData = {{{ makeHEAPView('F32', 'GLImmediate.vertexPointer', 'GLImmediate.vertexPointer + (first+count)*GLImmediate.stride') }}}; // XXX assuming float GLImmediate.firstVertex = first; GLImmediate.lastVertex = first + count; } GLImmediate.flush(null, first); GLImmediate.mode = -1; }, // start, end are given if we come from glDrawRangeElements glDrawElements: (mode, count, type, indices, start, end) => { if (GLImmediate.totalEnabledClientAttributes == 0 && mode <= 6 && GLctx.currentElementArrayBufferBinding) { GLctx.drawElements(mode, count, type, indices); return; } #if ASSERTIONS if (!GLctx.currentElementArrayBufferBinding) { assert(type == GLctx.UNSIGNED_SHORT); // We can only emulate buffers of this kind, for now } warnOnce("DrawElements doesn't actually prepareClientAttributes properly."); #endif GLImmediate.prepareClientAttributes(count, false); GLImmediate.mode = mode; if (!GLctx.currentArrayBufferBinding) { GLImmediate.firstVertex = end ? start : HEAP8.length; // if we don't know the start, set an invalid value and we will calculate it later from the indices GLImmediate.lastVertex = end ? end + 1 : 0; start = GLImmediate.vertexPointer; // TODO(sbc): Combine these two subarray calls back into a single one if // we ever fix https://github.com/emscripten-core/emscripten/issues/21250. if (end) { end = GLImmediate.vertexPointer + (end +1 ) * GLImmediate.stride; GLImmediate.vertexData = HEAPF32.subarray({{{ getHeapOffset('start', 'float') }}}, {{{ getHeapOffset('end', 'float') }}}); } else { GLImmediate.vertexData = HEAPF32.subarray({{{ getHeapOffset('start', 'float') }}}); } } GLImmediate.flush(count, 0, indices); GLImmediate.mode = -1; }, // Vertex array object (VAO) support. TODO: when the WebGL extension is // popular, use that and remove this code and GL.vaos $emulGlGenVertexArrays__deps: ['$GLEmulation'], $emulGlGenVertexArrays: (n, vaos) => { for (var i = 0; i < n; i++) { var id = GL.getNewId(GLEmulation.vaos); GLEmulation.vaos[id] = { id, arrayBuffer: 0, elementArrayBuffer: 0, enabledVertexAttribArrays: {}, vertexAttribPointers: {}, enabledClientStates: {}, }; {{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}}; } }, $emulGlDeleteVertexArrays: (n, vaos) => { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}}; GLEmulation.vaos[id] = null; if (GLEmulation.currentVao?.id == id) GLEmulation.currentVao = null; } }, $emulGlIsVertexArray: (array) => { var vao = GLEmulation.vaos[array]; if (!vao) return 0; return 1; }, $emulGlBindVertexArray__deps: ['glBindBuffer', 'glEnableVertexAttribArray', 'glVertexAttribPointer', 'glEnableClientState'], $emulGlBindVertexArray: (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 GLImmediate.lastRenderer?.cleanup(); _glBindBuffer(GLctx.ARRAY_BUFFER, 0); // XXX if one was there before we were bound? _glBindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, 0); for (var vaa in GLEmulation.enabledVertexAttribArrays) { GLctx.disableVertexAttribArray(vaa); } GLEmulation.enabledVertexAttribArrays = {}; GLImmediate.enabledClientAttributes = [0, 0]; GLImmediate.totalEnabledClientAttributes = 0; GLImmediate.modifiedClientAttributes = true; if (vao) { // replay vao var info = GLEmulation.vaos[vao]; _glBindBuffer(GLctx.ARRAY_BUFFER, info.arrayBuffer); // XXX overwrite current binding? _glBindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, info.elementArrayBuffer); for (var vaa in info.enabledVertexAttribArrays) { _glEnableVertexAttribArray(vaa); } for (var vaa in info.vertexAttribPointers) { _glVertexAttribPointer(...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'], glMatrixMode: (mode) => { if (mode == 0x1700 /* GL_MODELVIEW */) { GLImmediate.currentMatrix = 0/*m*/; } else if (mode == 0x1701 /* GL_PROJECTION */) { GLImmediate.currentMatrix = 1/*p*/; } else if (mode == 0x1702) { // GL_TEXTURE GLImmediate.useTextureMatrix = true; GLImmediate.currentMatrix = 2/*t*/ + GLImmediate.TexEnvJIT.getActiveTexture(); } else { throw `Wrong mode ${mode} passed to glMatrixMode`; } }, glPushMatrix: () => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixStack[GLImmediate.currentMatrix].push( Array.prototype.slice.call(GLImmediate.matrix[GLImmediate.currentMatrix])); }, glPopMatrix: () => { if (GLImmediate.matrixStack[GLImmediate.currentMatrix].length == 0) { GL.recordError(0x504/*GL_STACK_UNDERFLOW*/); return; } GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrix[GLImmediate.currentMatrix] = GLImmediate.matrixStack[GLImmediate.currentMatrix].pop(); }, glLoadIdentity__deps: ['$GL', '$GLImmediateSetup'], glLoadIdentity: () => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.identity(GLImmediate.matrix[GLImmediate.currentMatrix]); }, glLoadMatrixd: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); }, glLoadMatrixf: (matrix) => { #if GL_DEBUG if (GL.debug) dbg('glLoadMatrixf receiving: ' + Array.prototype.slice.call(HEAPF32.subarray(matrix >> 2, (matrix >> 2) + 16))); #endif GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); }, glLoadTransposeMatrixd: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); GLImmediate.matrixLib.mat4.transpose(GLImmediate.matrix[GLImmediate.currentMatrix]); }, glLoadTransposeMatrixf: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); GLImmediate.matrixLib.mat4.transpose(GLImmediate.matrix[GLImmediate.currentMatrix]); }, glMultMatrixd: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], {{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}); }, glMultMatrixf: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], {{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}); }, glMultTransposeMatrixd: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; var colMajor = GLImmediate.matrixLib.mat4.create(); GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, colMajor); GLImmediate.matrixLib.mat4.transpose(colMajor); GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], colMajor); }, glMultTransposeMatrixf: (matrix) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; var colMajor = GLImmediate.matrixLib.mat4.create(); GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, colMajor); GLImmediate.matrixLib.mat4.transpose(colMajor); GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], colMajor); }, glFrustum: (left, right, bottom, top_, nearVal, farVal) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], GLImmediate.matrixLib.mat4.frustum(left, right, bottom, top_, nearVal, farVal)); }, glFrustumf: 'glFrustum', glOrtho: (left, right, bottom, top_, nearVal, farVal) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], GLImmediate.matrixLib.mat4.ortho(left, right, bottom, top_, nearVal, farVal)); }, glOrthof: 'glOrtho', glScaled: (x, y, z) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.scale(GLImmediate.matrix[GLImmediate.currentMatrix], [x, y, z]); }, glScalef: 'glScaled', glTranslated: (x, y, z) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.translate(GLImmediate.matrix[GLImmediate.currentMatrix], [x, y, z]); }, glTranslatef: 'glTranslated', glRotated: (angle, x, y, z) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.rotate(GLImmediate.matrix[GLImmediate.currentMatrix], angle*Math.PI/180, [x, y, z]); }, glRotatef: 'glRotated', glDrawBuffer: () => { abort('glDrawBuffer: TODO') }, #if MAX_WEBGL_VERSION < 2 glReadBuffer: () => { abort('glReadBuffer: TODO') }, #endif glClipPlane: (pname, param) => { if ((pname >= 0x3000) && (pname < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { var clipPlaneId = pname - 0x3000; GLEmulation.clipPlaneEquation[clipPlaneId][0] = {{{ makeGetValue('param', '0', 'double') }}}; GLEmulation.clipPlaneEquation[clipPlaneId][1] = {{{ makeGetValue('param', '8', 'double') }}}; GLEmulation.clipPlaneEquation[clipPlaneId][2] = {{{ makeGetValue('param', '16', 'double') }}}; GLEmulation.clipPlaneEquation[clipPlaneId][3] = {{{ makeGetValue('param', '24', 'double') }}}; // apply inverse transposed current modelview matrix when setting clip plane var tmpMV = GLImmediate.matrixLib.mat4.create(GLImmediate.matrix[0]); GLImmediate.matrixLib.mat4.inverse(tmpMV); GLImmediate.matrixLib.mat4.transpose(tmpMV); GLImmediate.matrixLib.mat4.multiplyVec4(tmpMV, GLEmulation.clipPlaneEquation[clipPlaneId]); } }, glLightfv: (light, pname, param) => { if ((light >= 0x4000) && (light < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { var lightId = light - 0x4000; if (pname == 0x1200) { // GL_AMBIENT GLEmulation.lightAmbient[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.lightAmbient[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.lightAmbient[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.lightAmbient[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1201) { // GL_DIFFUSE GLEmulation.lightDiffuse[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.lightDiffuse[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.lightDiffuse[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.lightDiffuse[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1202) { // GL_SPECULAR GLEmulation.lightSpecular[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.lightSpecular[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.lightSpecular[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.lightSpecular[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1203) { // GL_POSITION GLEmulation.lightPosition[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.lightPosition[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.lightPosition[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.lightPosition[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; // multiply position with current modelviewmatrix GLImmediate.matrixLib.mat4.multiplyVec4(GLImmediate.matrix[0], GLEmulation.lightPosition[lightId]); } else { abort('glLightfv: TODO: ' + pname); } } }, glLightModelf: (pname, param) => { if (pname == 0x0B52) { // GL_LIGHT_MODEL_TWO_SIDE GLEmulation.lightModelTwoSide = (param != 0) ? true : false; } else { abort('glLightModelf: TODO: ' + pname); } }, glLightModelfv: (pname, param) => { // TODO: GL_LIGHT_MODEL_LOCAL_VIEWER if (pname == 0x0B53) { // GL_LIGHT_MODEL_AMBIENT GLEmulation.lightModelAmbient[0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.lightModelAmbient[1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.lightModelAmbient[2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.lightModelAmbient[3] = {{{ makeGetValue('param', '12', 'float') }}}; } else { abort('glLightModelfv: TODO: ' + pname); } }, glMaterialfv: (face, pname, param) => { if ((face != 0x0404) && (face != 0x0408)) { abort('glMaterialfv: TODO' + face); } // only GL_FRONT and GL_FRONT_AND_BACK supported if (pname == 0x1200) { // GL_AMBIENT GLEmulation.materialAmbient[0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.materialAmbient[1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.materialAmbient[2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.materialAmbient[3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1201) { // GL_DIFFUSE GLEmulation.materialDiffuse[0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.materialDiffuse[1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.materialDiffuse[2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.materialDiffuse[3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1202) { // GL_SPECULAR GLEmulation.materialSpecular[0] = {{{ makeGetValue('param', '0', 'float') }}}; GLEmulation.materialSpecular[1] = {{{ makeGetValue('param', '4', 'float') }}}; GLEmulation.materialSpecular[2] = {{{ makeGetValue('param', '8', 'float') }}}; GLEmulation.materialSpecular[3] = {{{ makeGetValue('param', '12', 'float') }}}; } else if (pname == 0x1601) { // GL_SHININESS GLEmulation.materialShininess[0] = {{{ makeGetValue('param', '0', 'float') }}}; } else { abort('glMaterialfv: TODO: ' + pname); } }, glTexGeni: (coord, pname, param) => abort('glTexGeni: TODO'), glTexGenfv: (coord, pname, param) => abort('glTexGenfv: TODO'), glTexEnvi: (target, pname, params) => warnOnce('glTexEnvi: TODO'), glTexEnvf: (target, pname, params) => warnOnce('glTexEnvf: TODO'), glTexEnvfv: (target, pname, params) => warnOnce('glTexEnvfv: TODO'), glGetTexEnviv: (target, pname, param) => abort('GL emulation not initialized!'), glGetTexEnvfv: (target, pname, param) => abort('GL emulation not initialized!'), glTexImage1D: (target, level, internalformat, width, border, format, type, data) => abort('glTexImage1D: TODO'), glTexCoord3f: (target, level, internalformat, width, border, format, type, data) => abort('glTexCoord3f: TODO'), glGetTexLevelParameteriv: (target, level, pname, params) => abort('glGetTexLevelParameteriv: TODO'), glShadeModel: () => warnOnce('TODO: glShadeModel'), // Open GLES1.1 compatibility glGenFramebuffersOES: 'glGenFramebuffers', glGenRenderbuffersOES: 'glGenRenderbuffers', glBindFramebufferOES: 'glBindFramebuffer', glBindRenderbufferOES: 'glBindRenderbuffer', glGetRenderbufferParameterivOES: 'glGetRenderbufferParameteriv', glFramebufferRenderbufferOES: 'glFramebufferRenderbuffer', glRenderbufferStorageOES: 'glRenderbufferStorage', glCheckFramebufferStatusOES: 'glCheckFramebufferStatus', glDeleteFramebuffersOES: 'glDeleteFramebuffers', glDeleteRenderbuffersOES: 'glDeleteRenderbuffers', glFramebufferTexture2DOES: 'glFramebufferTexture2D', // GLU gluPerspective: (fov, aspect, near, far) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrix[GLImmediate.currentMatrix] = GLImmediate.matrixLib.mat4.perspective(fov, aspect, near, far, GLImmediate.matrix[GLImmediate.currentMatrix]); }, gluLookAt: (ex, ey, ez, cx, cy, cz, ux, uy, uz) => { GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrixLib.mat4.lookAt(GLImmediate.matrix[GLImmediate.currentMatrix], [ex, ey, ez], [cx, cy, cz], [ux, uy, uz]); }, gluProject: (objX, objY, objZ, model, proj, view, winX, winY, winZ) => { // The algorithm for this function comes from Mesa var inVec = new Float32Array(4); var outVec = new Float32Array(4); GLImmediate.matrixLib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}}, [objX, objY, objZ, 1.0], outVec); GLImmediate.matrixLib.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: (winX, winY, winZ, model, proj, view, objX, objY, objZ) => { var result = GLImmediate.matrixLib.vec3.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__deps: ['glOrtho'], gluOrtho2D: (left, right, bottom, top) => _glOrtho(left, right, bottom, top, -1, 1), }; extraLibraryFuncs.push('$GLEmulation'); recordGLProcAddressGet(LibraryGLEmulation); addToLibrary(LibraryGLEmulation);