diff options
Diffstat (limited to 'src/library_gl.js')
-rw-r--r-- | src/library_gl.js | 837 |
1 files changed, 502 insertions, 335 deletions
diff --git a/src/library_gl.js b/src/library_gl.js index afd36197..cc39b048 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -209,6 +209,105 @@ var LibraryGL = { ((height - 1) * alignedRowSize + plainRowSize); }, + get: function(name_, p, type) { + var ret = undefined; + switch(name_) { // Handle a few trivial GLES values + case 0x8DFA: // GL_SHADER_COMPILER + ret = 1; + break; + case 0x8DF8: // GL_SHADER_BINARY_FORMATS + if (type === 'Integer') { + // fall through, see gles2_conformance.cpp + } else { + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGet' + type + 'v(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); +#endif + return; + } + case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS + ret = 0; + break; + case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS + // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), + // so implement it ourselves to allow C++ GLES2 code get the length. + var formats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); + ret = formats.length; + break; + case 0x8B9A: // GL_IMPLEMENTATION_COLOR_READ_TYPE + ret = 0x1401; // GL_UNSIGNED_BYTE + break; + case 0x8B9B: // GL_IMPLEMENTATION_COLOR_READ_FORMAT + ret = 0x1908; // GL_RGBA + break; + } + + if (ret === undefined) { + var result = Module.ctx.getParameter(name_); + switch (typeof(result)) { + case "number": + ret = result; + break; + case "boolean": + ret = result ? 1 : 0; + break; + case "string": + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGet' + type + 'v(' + name_ + ') on a name which returns a string!'); +#endif + return; + case "object": + if (result === null) { + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGet' + type + 'v(' + name_ + ') and it returns null!'); +#endif + return; + } else if (result instanceof Float32Array || + result instanceof Uint32Array || + result instanceof Int32Array || + result instanceof Array) { + for (var i = 0; i < result.length; ++i) { + switch (type) { + case 'Integer': {{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}}; break; + case 'Float': {{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}}; break; + case 'Boolean': {{{ makeSetValue('p', 'i', 'result[i] ? 1 : 0', 'i8') }}}; break; + default: throw 'internal glGet error, bad type: ' + type; + } + } + return; + } else if (result instanceof WebGLBuffer || + result instanceof WebGLProgram || + result instanceof WebGLFramebuffer || + result instanceof WebGLRenderbuffer || + result instanceof WebGLTexture) { + ret = result.name | 0; + } else { + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGet' + type + 'v: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; + } + break; + default: + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGet' + type + 'v(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; + } + } + + switch (type) { + case 'Integer': {{{ makeSetValue('p', '0', 'ret', 'i32') }}}; break; + case 'Float': {{{ makeSetValue('p', '0', 'ret', 'float') }}}; break; + case 'Boolean': {{{ makeSetValue('p', '0', 'ret ? 1 : 0', 'i8') }}}; break; + default: throw 'internal glGet error, bad type: ' + type; + } + }, + getTexPixelData: function(type, format, width, height, pixels, internalFormat) { var sizePerPixel; switch (type) { @@ -288,6 +387,22 @@ var LibraryGL = { } }, +#if GL_FFP_ONLY + enabledClientAttribIndices: [], + enableVertexAttribArray: function enableVertexAttribArray(index) { + if (!GL.enabledClientAttribIndices[index]) { + GL.enabledClientAttribIndices[index] = true; + Module.ctx.enableVertexAttribArray(index); + } + }, + disableVertexAttribArray: function disableVertexAttribArray(index) { + if (GL.enabledClientAttribIndices[index]) { + GL.enabledClientAttribIndices[index] = false; + Module.ctx.disableVertexAttribArray(index); + } + }, +#endif + #if FULL_ES2 calcBufLength: function calcBufLength(size, type, stride, count) { if (stride > 0) { @@ -554,214 +669,17 @@ var LibraryGL = { glGetIntegerv__sig: 'vii', glGetIntegerv: function(name_, p) { - switch(name_) { // Handle a few trivial GLES values - case 0x8DFA: // GL_SHADER_COMPILER - {{{ makeSetValue('p', '0', '1', 'i32') }}}; - return; - case 0x8DF8: // GL_SHADER_BINARY_FORMATS - case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS - {{{ makeSetValue('p', '0', '0', 'i32') }}}; - return; - case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS - // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), - // so implement it ourselves to allow C++ GLES2 code get the length. - var formats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); - {{{ makeSetValue('p', '0', 'formats.length', 'i32') }}}; - return; - } - var result = Module.ctx.getParameter(name_); - switch (typeof(result)) { - case "number": - {{{ makeSetValue('p', '0', 'result', 'i32') }}}; - break; - case "boolean": - {{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}}; - break; - case "string": - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'); -#endif - return; - case "object": - if (result === null) { - {{{ makeSetValue('p', '0', '0', 'i32') }}}; - } else if (result instanceof Float32Array || - result instanceof Uint32Array || - result instanceof Int32Array || - result instanceof Array) { - for (var i = 0; i < result.length; ++i) { - {{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}}; - } - } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; - } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; - } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; - } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; - } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; - } else { - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); -#endif - return; - } - break; - default: - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); -#endif - return; - } + return GL.get(name_, p, 'Integer'); }, glGetFloatv__sig: 'vii', glGetFloatv: function(name_, p) { - switch(name_) { - case 0x8DFA: // GL_SHADER_COMPILER - {{{ makeSetValue('p', '0', '1', 'float') }}}; - return; - case 0x8DF8: // GL_SHADER_BINARY_FORMATS - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetFloatv(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); -#endif - return; - case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS - {{{ makeSetValue('p', '0', '0', 'float') }}}; - return; - case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS - // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), - // so implement it ourselves to allow C++ GLES2 code get the length. - var formats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); - {{{ makeSetValue('p', '0', 'formats.length', 'float') }}}; - return; - } - - var result = Module.ctx.getParameter(name_); - switch (typeof(result)) { - case "number": - {{{ makeSetValue('p', '0', 'result', 'float') }}}; - break; - case "boolean": - {{{ makeSetValue('p', '0', 'result ? 1.0 : 0.0', 'float') }}}; - break; - case "string": - {{{ makeSetValue('p', '0', '0', 'float') }}}; - case "object": - if (result === null) { - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns null!'); -#endif - return; - } else if (result instanceof Float32Array || - result instanceof Uint32Array || - result instanceof Int32Array || - result instanceof Array) { - for (var i = 0; i < result.length; ++i) { - {{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}}; - } - } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; - } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; - } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; - } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; - } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; - } else { - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); -#endif - return; - } - break; - default: - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); -#endif - return; - } + return GL.get(name_, p, 'Float'); }, glGetBooleanv__sig: 'vii', glGetBooleanv: function(name_, p) { - switch(name_) { - case 0x8DFA: // GL_SHADER_COMPILER - {{{ makeSetValue('p', '0', '1', 'i8') }}}; - return; - case 0x8DF8: // GL_SHADER_BINARY_FORMATS - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetBooleanv(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); -#endif - return; - case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS - {{{ makeSetValue('p', '0', '0', 'i8') }}}; - return; - case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS - // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), - // so implement it ourselves to allow C++ GLES2 code get the length. - var hasCompressedFormats = Module.ctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/).length > 0 ? 1 : 0; - {{{ makeSetValue('p', '0', 'hasCompressedFormats', 'i8') }}}; - return; - } - - var result = Module.ctx.getParameter(name_); - switch (typeof(result)) { - case "number": - {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; - break; - case "boolean": - {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; - break; - case "string": - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'); -#endif - return; - case "object": - if (result === null) { - {{{ makeSetValue('p', '0', '0', 'i8') }}}; - } else if (result instanceof Float32Array || - result instanceof Uint32Array || - result instanceof Int32Array || - result instanceof Array) { - for (var i = 0; i < result.length; ++i) { - {{{ makeSetValue('p', 'i', 'result[i] != 0', 'i8') }}}; - } - } else if (result instanceof WebGLBuffer || - result instanceof WebGLProgram || - result instanceof WebGLFramebuffer || - result instanceof WebGLRenderbuffer || - result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1! - } else { - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); -#endif - return; - } - break; - default: - GL.recordError(0x0500/*GL_INVALID_ENUM*/); -#if GL_ASSERTIONS - Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); -#endif - return; - } + return GL.get(name_, p, 'Boolean'); }, glGenTextures__sig: 'vii', @@ -1808,7 +1726,7 @@ var LibraryGL = { // Add some emulation workarounds Module.printErr('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work.'); -#if GL_UNSAFE_OPTS == 0 +#if GL_UNSAFE_OPTS == 1 Module.printErr('WARNING: using emscripten GL emulation unsafe opts. If weirdness happens, try -s GL_UNSAFE_OPTS=0'); #endif @@ -2149,7 +2067,10 @@ var LibraryGL = { } } #endif - GL.currProgram = program; + if (GL.currProgram != program) { + GL.currentRenderer = null; // This changes the FFP emulation shader program, need to recompute that. + GL.currProgram = program; + } glUseProgram(program); } @@ -2689,32 +2610,85 @@ var LibraryGL = { GL_SRC_ALPHA ]; - this.traverseState = function CTexEnv_traverseState(keyView) { - keyView.next(this.mode); - keyView.next(this.colorCombiner); - keyView.next(this.alphaCombiner); - keyView.next(this.colorCombiner); - keyView.next(this.alphaScale); - keyView.next(this.envColor[0]); - keyView.next(this.envColor[1]); - keyView.next(this.envColor[2]); - keyView.next(this.envColor[3]); - - keyView.next(this.colorSrc[0]); - keyView.next(this.colorSrc[1]); - keyView.next(this.colorSrc[2]); - - keyView.next(this.alphaSrc[0]); - keyView.next(this.alphaSrc[1]); - keyView.next(this.alphaSrc[2]); - - keyView.next(this.colorOp[0]); - keyView.next(this.colorOp[1]); - keyView.next(this.colorOp[2]); - - keyView.next(this.alphaOp[0]); - keyView.next(this.alphaOp[1]); - keyView.next(this.alphaOp[2]); + // 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, + 0x0104 /* GL_ADD */: 2, + 0x0BE2 /* 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 + 0x0300 /* GL_SRC_COLOR */: 0, + 0x0301 /* GL_ONE_MINUS_SRC_COLOR */: 1, + 0x0302 /* GL_SRC_ALPHA */: 2, + 0x0300 /* 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; + 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. + GL.immediate.currentRenderer = null; // The currently used renderer must be re-evaluated at next render. + } + this.traverseState = function(keyView) { + if (this.key0 == -1) { + this.recomputeKey(); + } + keyView.next(this.key0); + keyView.next(this.key1); + keyView.next(this.key2); }; } @@ -3076,16 +3050,28 @@ var LibraryGL = { var cur = getCurTexUnit(); switch (cap) { case GL_TEXTURE_1D: - cur.enabled_tex1D = true; + if (!cur.enabled_tex1D) { + GL.immediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. + cur.enabled_tex1D = true; + } break; case GL_TEXTURE_2D: - cur.enabled_tex2D = true; + if (!cur.enabled_tex2D) { + GL.immediate.currentRenderer = null; + cur.enabled_tex2D = true; + } break; case GL_TEXTURE_3D: - cur.enabled_tex3D = true; + if (!cur.enabled_tex3D) { + GL.immediate.currentRenderer = null; + cur.enabled_tex3D = true; + } break; case GL_TEXTURE_CUBE_MAP: - cur.enabled_texCube = true; + if (!cur.enabled_texCube) { + GL.immediate.currentRenderer = null; + cur.enabled_texCube = true; + } break; } }, @@ -3094,16 +3080,28 @@ var LibraryGL = { var cur = getCurTexUnit(); switch (cap) { case GL_TEXTURE_1D: - cur.enabled_tex1D = false; + if (cur.enabled_tex1D) { + GL.immediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. + cur.enabled_tex1D = false; + } break; case GL_TEXTURE_2D: - cur.enabled_tex2D = false; + if (cur.enabled_tex2D) { + GL.immediate.currentRenderer = null; + cur.enabled_tex2D = false; + } break; case GL_TEXTURE_3D: - cur.enabled_tex3D = false; + if (cur.enabled_tex3D) { + GL.immediate.currentRenderer = null; + cur.enabled_tex3D = false; + } break; case GL_TEXTURE_CUBE_MAP: - cur.enabled_texCube = false; + if (cur.enabled_texCube) { + GL.immediate.currentRenderer = null; + cur.enabled_texCube = false; + } break; } }, @@ -3115,10 +3113,16 @@ var LibraryGL = { var env = getCurTexUnit().env; switch (pname) { case GL_RGB_SCALE: - env.colorScale = param; + if (env.colorScale != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.colorScale = param; + } break; case GL_ALPHA_SCALE: - env.alphaScale = param; + if (env.alphaScale != param) { + env.invalidateKey(); + env.alphaScale = param; + } break; default: @@ -3133,61 +3137,112 @@ var LibraryGL = { var env = getCurTexUnit().env; switch (pname) { case GL_TEXTURE_ENV_MODE: - env.mode = param; + if (env.mode != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.mode = param; + } break; case GL_COMBINE_RGB: - env.colorCombiner = param; + if (env.colorCombiner != param) { + env.invalidateKey(); + env.colorCombiner = param; + } break; case GL_COMBINE_ALPHA: - env.alphaCombiner = param; + if (env.alphaCombiner != param) { + env.invalidateKey(); + env.alphaCombiner = param; + } break; case GL_SRC0_RGB: - env.colorSrc[0] = param; + if (env.colorSrc[0] != param) { + env.invalidateKey(); + env.colorSrc[0] = param; + } break; case GL_SRC1_RGB: - env.colorSrc[1] = param; + if (env.colorSrc[1] != param) { + env.invalidateKey(); + env.colorSrc[1] = param; + } break; case GL_SRC2_RGB: - env.colorSrc[2] = param; + if (env.colorSrc[2] != param) { + env.invalidateKey(); + env.colorSrc[2] = param; + } break; case GL_SRC0_ALPHA: - env.alphaSrc[0] = param; + if (env.alphaSrc[0] != param) { + env.invalidateKey(); + env.alphaSrc[0] = param; + } break; case GL_SRC1_ALPHA: - env.alphaSrc[1] = param; + if (env.alphaSrc[1] != param) { + env.invalidateKey(); + env.alphaSrc[1] = param; + } break; case GL_SRC2_ALPHA: - env.alphaSrc[2] = param; + if (env.alphaSrc[2] != param) { + env.invalidateKey(); + env.alphaSrc[2] = param; + } break; case GL_OPERAND0_RGB: - env.colorOp[0] = param; + if (env.colorOp[0] != param) { + env.invalidateKey(); + env.colorOp[0] = param; + } break; case GL_OPERAND1_RGB: - env.colorOp[1] = param; + if (env.colorOp[1] != param) { + env.invalidateKey(); + env.colorOp[1] = param; + } break; case GL_OPERAND2_RGB: - env.colorOp[2] = param; + if (env.colorOp[2] != param) { + env.invalidateKey(); + env.colorOp[2] = param; + } break; case GL_OPERAND0_ALPHA: - env.alphaOp[0] = param; + if (env.alphaOp[0] != param) { + env.invalidateKey(); + env.alphaOp[0] = param; + } break; case GL_OPERAND1_ALPHA: - env.alphaOp[1] = param; + if (env.alphaOp[1] != param) { + env.invalidateKey(); + env.alphaOp[1] = param; + } break; case GL_OPERAND2_ALPHA: - env.alphaOp[2] = param; + if (env.alphaOp[2] != param) { + env.invalidateKey(); + env.alphaOp[2] = param; + } break; case GL_RGB_SCALE: - env.colorScale = param; + if (env.colorScale != param) { + env.invalidateKey(); + env.colorScale = param; + } break; case GL_ALPHA_SCALE: - env.alphaScale = param; + if (env.alphaScale != param) { + env.invalidateKey(); + env.alphaScale = param; + } break; default: @@ -3203,7 +3258,10 @@ var LibraryGL = { case GL_TEXTURE_ENV_COLOR: { for (var i = 0; i < 4; i++) { var param = {{{ makeGetValue('params', 'i*4', 'float') }}}; - env.envColor[i] = param; + if (env.envColor[i] != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.envColor[i] = param; + } } break } @@ -3243,26 +3301,21 @@ var LibraryGL = { NORMAL: 1, COLOR: 2, TEXTURE0: 3, - TEXTURE1: 4, - TEXTURE2: 5, - TEXTURE3: 6, - TEXTURE4: 7, - TEXTURE5: 8, - TEXTURE6: 9, - NUM_ATTRIBUTES: 10, // Overwritten in init(). - MAX_TEXTURES: 7, // Overwritten in init(). + 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: function(name, size, type, stride, pointer) { + setClientAttribute: function setClientAttribute(name, size, type, stride, pointer) { var attrib = this.clientAttributes[name]; if (!attrib) { for (var i = 0; i <= name; i++) { // keep flat @@ -3289,7 +3342,7 @@ var LibraryGL = { }, // Renderers - addRendererComponent: function(name, size, type) { + addRendererComponent: function addRendererComponent(name, size, type) { if (!this.rendererComponents[name]) { this.rendererComponents[name] = 1; #if ASSERTIONS @@ -3305,13 +3358,18 @@ var LibraryGL = { } }, - disableBeginEndClientAttributes: function() { + disableBeginEndClientAttributes: function disableBeginEndClientAttributes() { for (var i = 0; i < this.NUM_ATTRIBUTES; i++) { if (this.rendererComponents[i]) this.enabledClientAttributes[i] = false; } }, - getRenderer: function() { + getRenderer: function 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 (this.currentRenderer) { + return this.currentRenderer; + } // return a renderer object given the liveClientAttributes // we maintain a cache of renderers, optimized to not generate garbage var attributes = GL.immediate.liveClientAttributes; @@ -3320,10 +3378,11 @@ var LibraryGL = { var keyView = cacheMap.getStaticKeyView().reset(); // By attrib state: + var enabledAttributesKey = 0; for (var i = 0; i < attributes.length; i++) { - var attribute = attributes[i]; - keyView.next(attribute.name).next(attribute.size).next(attribute.type); + enabledAttributesKey |= 1 << attributes[i].name; } + keyView.next(enabledAttributesKey); // By fog state: var fogParam = 0; @@ -3349,18 +3408,23 @@ var LibraryGL = { } // If we don't already have it, create it. - if (!keyView.get()) { + var renderer = keyView.get(); + if (!renderer) { #if GL_DEBUG Module.printErr('generating renderer for ' + JSON.stringify(attributes)); #endif - keyView.set(this.createRenderer()); + renderer = this.createRenderer(); + this.currentRenderer = renderer; + keyView.set(renderer); + return renderer; } - return keyView.get(); + this.currentRenderer = renderer; // Cache the currently used renderer, so later lookups without state changes can get this fast. + return renderer; }, - createRenderer: function(renderer) { + createRenderer: function createRenderer(renderer) { var useCurrProgram = !!GL.currProgram; - var hasTextures = false, textureSizes = [], textureTypes = []; + var hasTextures = false; for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { var texAttribName = GL.immediate.TEXTURE0 + i; if (!GL.immediate.enabledClientAttributes[texAttribName]) @@ -3374,24 +3438,11 @@ var LibraryGL = { } #endif - textureSizes[i] = GL.immediate.clientAttributes[texAttribName].size; - textureTypes[i] = GL.immediate.clientAttributes[texAttribName].type; hasTextures = true; } - var positionSize = GL.immediate.clientAttributes[GL.immediate.VERTEX].size; - var positionType = GL.immediate.clientAttributes[GL.immediate.VERTEX].type; - var colorSize = 0, colorType; - if (GL.immediate.enabledClientAttributes[GL.immediate.COLOR]) { - colorSize = GL.immediate.clientAttributes[GL.immediate.COLOR].size; - colorType = GL.immediate.clientAttributes[GL.immediate.COLOR].type; - } - var normalSize = 0, normalType; - if (GL.immediate.enabledClientAttributes[GL.immediate.NORMAL]) { - normalSize = GL.immediate.clientAttributes[GL.immediate.NORMAL].size; - normalType = GL.immediate.clientAttributes[GL.immediate.NORMAL].type; - } + var ret = { - init: function() { + init: function init() { // For fixed-function shader generation. var uTexUnitPrefix = 'u_texUnit'; var aTexCoordPrefix = 'a_texCoord'; @@ -3524,10 +3575,25 @@ var LibraryGL = { this.program = Module.ctx.createProgram(); Module.ctx.attachShader(this.program, this.vertexShader); Module.ctx.attachShader(this.program, this.fragmentShader); - Module.ctx.bindAttribLocation(this.program, 0, 'a_position'); + + // 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. + Module.ctx.bindAttribLocation(this.program, GL.immediate.VERTEX, 'a_position'); + Module.ctx.bindAttribLocation(this.program, GL.immediate.COLOR, 'a_color'); + Module.ctx.bindAttribLocation(this.program, GL.immediate.NORMAL, 'a_normal'); + for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { + Module.ctx.bindAttribLocation(this.program, GL.immediate.TEXTURE0 + i, 'a_texCoord'+i); + Module.ctx.bindAttribLocation(this.program, GL.immediate.TEXTURE0 + i, aTexCoordPrefix+i); + } Module.ctx.linkProgram(this.program); } + // Stores a map 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 = {}; + this.positionLocation = Module.ctx.getAttribLocation(this.program, 'a_position'); this.texCoordLocations = []; @@ -3570,7 +3636,9 @@ var LibraryGL = { this.projectionLocation = Module.ctx.getUniformLocation(this.program, 'u_projection'); this.hasTextures = hasTextures; - this.hasNormal = normalSize > 0 && this.normalLocation >= 0; + this.hasNormal = GL.immediate.enabledClientAttributes[GL.immediate.NORMAL] && + GL.immediate.clientAttributes[GL.immediate.NORMAL].size > 0 && + this.normalLocation >= 0; this.hasColor = (this.colorLocation === 0) || this.colorLocation > 0; this.floatType = Module.ctx.FLOAT; // minor optimization @@ -3583,7 +3651,7 @@ var LibraryGL = { this.fogScaleLocation || this.fogDensityLocation); }, - prepare: function() { + prepare: function prepare() { // Calculate the array buffer var arrayBuffer; if (!GL.currArrayBuffer) { @@ -3598,10 +3666,10 @@ var LibraryGL = { arrayBuffer = GL.currArrayBuffer; } +#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 -#if GL_UNSAFE_OPTS var lastRenderer = GL.immediate.lastRenderer; var canSkip = this == lastRenderer && arrayBuffer == GL.immediate.lastArrayBuffer && @@ -3636,62 +3704,105 @@ var LibraryGL = { GL.immediate.fixedFunctionProgram = this.program; } - if (this.modelViewLocation) Module.ctx.uniformMatrix4fv(this.modelViewLocation, false, GL.immediate.matrix['m']); - if (this.projectionLocation) Module.ctx.uniformMatrix4fv(this.projectionLocation, false, GL.immediate.matrix['p']); + if (this.modelViewLocation && this.modelViewMatrixVersion != GL.immediate.matrixVersion['m']) { + this.modelViewMatrixVersion = GL.immediate.matrixVersion['m']; + Module.ctx.uniformMatrix4fv(this.modelViewLocation, false, GL.immediate.matrix['m']); + } + if (this.projectionLocation && this.projectionMatrixVersion != GL.immediate.matrixVersion['p']) { + this.projectionMatrixVersion = GL.immediate.matrixVersion['p']; + Module.ctx.uniformMatrix4fv(this.projectionLocation, false, GL.immediate.matrix['p']); + } var clientAttributes = GL.immediate.clientAttributes; + var posAttr = clientAttributes[GL.immediate.VERTEX]; #if GL_ASSERTIONS - GL.validateVertexAttribPointer(positionSize, positionType, GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset); + GL.validateVertexAttribPointer(posAttr.size, posAttr.type, GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset); #endif - Module.ctx.vertexAttribPointer(this.positionLocation, positionSize, positionType, false, - GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset); + +#if GL_FFP_ONLY + if (!GL.currArrayBuffer) { + Module.ctx.vertexAttribPointer(GL.immediate.VERTEX, posAttr.size, posAttr.type, false, GL.immediate.stride, posAttr.offset); + GL.enableVertexAttribArray(GL.immediate.VERTEX); + if (this.hasNormal) { + var normalAttr = clientAttributes[GL.immediate.NORMAL]; + Module.ctx.vertexAttribPointer(GL.immediate.NORMAL, normalAttr.size, normalAttr.type, true, GL.immediate.stride, normalAttr.offset); + GL.enableVertexAttribArray(GL.immediate.NORMAL); + } + } |