Buckets:
| /** | |
| * @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: <undefined> | |
| * // 1: <undefined> | |
| * // 2: <undefined> | |
| * // 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); | |
Xet Storage Details
- Size:
- 167 kB
- Xet hash:
- fbe565aa3be6d6582279e28c46da629467253452ebe0fe2cb237204292a7c267
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.