diff options
Diffstat (limited to 'src/library_gl.js')
-rw-r--r-- | src/library_gl.js | 554 |
1 files changed, 417 insertions, 137 deletions
diff --git a/src/library_gl.js b/src/library_gl.js index 9f88260a..f3cea384 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -4,6 +4,7 @@ */ var LibraryGL = { + $GL__postset: 'GL.init()', $GL: { #if GL_DEBUG debug: true, @@ -21,6 +22,10 @@ var LibraryGL = { packAlignment: 4, // default alignment is 4 bytes unpackAlignment: 4, // default alignment is 4 bytes + init: function() { + Browser.moduleContextCreatedCallbacks.push(GL.initExtensions); + }, + // Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters scan: function(table, object) { for (var item in table) { @@ -100,6 +105,79 @@ var LibraryGL = { var alignedRowSize = roundedToNextMultipleOf(plainRowSize, alignment); return (height <= 0) ? 0 : ((height - 1) * alignedRowSize + plainRowSize); + }, + + getTexPixelData: function(type, format, width, height, pixels, internalFormat) { + var sizePerPixel; + switch (type) { + case 0x1401 /* GL_UNSIGNED_BYTE */: + switch (format) { + case 0x1906 /* GL_ALPHA */: + case 0x1909 /* GL_LUMINANCE */: + sizePerPixel = 1; + break; + case 0x1907 /* GL_RGB */: + sizePerPixel = 3; + break; + case 0x1908 /* GL_RGBA */: + sizePerPixel = 4; + break; + case 0x190A /* GL_LUMINANCE_ALPHA */: + sizePerPixel = 2; + break; + default: + throw 'Invalid format (' + format + ')'; + } + break; + case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: + case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: + case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: + sizePerPixel = 2; + break; + case 0x1406 /* GL_FLOAT */: + assert(GL.floatExt, 'Must have OES_texture_float to use float textures'); + switch (format) { + case 0x1907 /* GL_RGB */: + sizePerPixel = 3*4; + break; + case 0x1908 /* GL_RGBA */: + sizePerPixel = 4*4; + break; + default: + throw 'Invalid format (' + format + ')'; + } + internalFormat = Module.ctx.RGBA; + break; + default: + throw 'Invalid type (' + type + ')'; + } + var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment); + if (type == 0x1401 /* GL_UNSIGNED_BYTE */) { + pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; + } else if (type == 0x1406 /* GL_FLOAT */) { + pixels = {{{ makeHEAPView('F32', 'pixels', 'pixels+bytes') }}}; + } else { + pixels = {{{ makeHEAPView('U16', 'pixels', 'pixels+bytes') }}}; + } + return { + pixels: pixels, + internalFormat: internalFormat + } + }, + + initExtensions: function() { + if (GL.initExtensions.done) return; + GL.initExtensions.done = true; + + GL.compressionExt = Module.ctx.getExtension('WEBGL_compressed_texture_s3tc') || + Module.ctx.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || + Module.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); + + GL.anisotropicExt = Module.ctx.getExtension('EXT_texture_filter_anisotropic') || + Module.ctx.getExtension('MOZ_EXT_texture_filter_anisotropic') || + Module.ctx.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); + + GL.floatExt = Module.ctx.getExtension('OES_texture_float'); } }, @@ -273,96 +351,41 @@ var LibraryGL = { } }, - glCompressedTexImage2D: function(target, level, internalformat, width, height, border, imageSize, data) { + glCompressedTexImage2D: function(target, level, internalFormat, width, height, border, imageSize, data) { + assert(GL.compressionExt); if (data) { data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}}; } else { data = null; } - Module.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, data); + Module.ctx['compressedTexImage2D'](target, level, internalFormat, width, height, border, data); }, glCompressedTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, imageSize, data) { + assert(GL.compressionExt); if (data) { data = {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}}; } else { data = null; } - Module.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, data); + Module.ctx['compressedTexSubImage2D'](target, level, xoffset, yoffset, width, height, data); }, - glTexImage2D: function(target, level, internalformat, width, height, border, format, type, pixels) { + glTexImage2D: function(target, level, internalFormat, width, height, border, format, type, pixels) { if (pixels) { - var sizePerPixel; - switch (type) { - case 0x1401 /* GL_UNSIGNED_BYTE */: - switch (format) { - case 0x1906 /* GL_ALPHA */: - case 0x1909 /* GL_LUMINANCE */: - sizePerPixel = 1; - break; - case 0x1907 /* GL_RGB */: - sizePerPixel = 3; - break; - case 0x1908 /* GL_RGBA */: - sizePerPixel = 4; - break; - case 0x190A /* GL_LUMINANCE_ALPHA */: - sizePerPixel = 2; - break; - default: - throw 'Invalid format (' + format + ') passed to glTexImage2D'; - } - break; - case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: - case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: - case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: - sizePerPixel = 2; - break; - default: - throw 'Invalid type (' + type + ') passed to glTexImage2D'; - } - var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment); - pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; + var data = GL.getTexPixelData(type, format, width, height, pixels, internalFormat); + pixels = data.pixels; + internalFormat = data.internalFormat; } else { pixels = null; } - Module.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, pixels); + Module.ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels); }, glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) { if (pixels) { - var sizePerPixel; - switch (type) { - case 0x1401 /* GL_UNSIGNED_BYTE */: - switch (format) { - case 0x1906 /* GL_ALPHA */: - case 0x1909 /* GL_LUMINANCE */: - sizePerPixel = 1; - break; - case 0x1907 /* GL_RGB */: - sizePerPixel = 3; - break; - case 0x1908 /* GL_RGBA */: - sizePerPixel = 4; - break; - case 0x190A /* GL_LUMINANCE_ALPHA */: - sizePerPixel = 2; - break; - default: - throw 'Invalid format (' + format + ') passed to glTexSubImage2D'; - } - break; - case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: - case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: - case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: - sizePerPixel = 2; - break; - default: - throw 'Invalid type (' + type + ') passed to glTexSubImage2D'; - } - var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment); - pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; + var data = GL.getTexPixelData(type, format, width, height, pixels, -1); + pixels = data.pixels; } else { pixels = null; } @@ -891,28 +914,62 @@ var LibraryGL = { $GLEmulation__postset: 'GLEmulation.init();', $GLEmulation: { + // Fog support. Partial, we assume shaders are used that implement fog. We just pass them uniforms + fogStart: 0, + fogEnd: 1, + fogDensity: 1.0, + fogColor: null, + fogMode: 0x0800, // GL_EXP + fogEnabled: false, + init: function() { + GLEmulation.fogColor = new Float32Array(4); + // Add some emulation workarounds Module.printErr('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work'); - // XXX some of these ignored capabilities may lead to incorrect rendering, if we do not emulate them in shaders - var ignoredCapabilities = { - 0x0DE1: 1, // GL_TEXTURE_2D - 0x0B20: 1, // GL_LINE_SMOOTH - 0x0B60: 1, // GL_FOG - 0x8513: 1, // GL_TEXTURE_CUBE_MAP - 0x0BA1: 1, // GL_NORMALIZE - 0x0C60: 1, // GL_TEXTURE_GEN_S - 0x0C61: 1 // GL_TEXTURE_GEN_T + // XXX some of the capabilities we don't support may lead to incorrect rendering, if we do not emulate them in shaders + var validCapabilities = { + 0x0B44: 1, // GL_CULL_FACE + 0x0BE2: 1, // GL_BLEND + 0x0BD0: 1, // GL_DITHER, + 0x0B90: 1, // GL_STENCIL_TEST + 0x0B71: 1, // GL_DEPTH_TEST + 0x0C11: 1, // GL_SCISSOR_TEST + 0x8037: 1, // GL_POLYGON_OFFSET_FILL + 0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE + 0x80A0: 1 // GL_SAMPLE_COVERAGE }; _glEnable = function(cap) { - if (cap in ignoredCapabilities) return; + // Clean up the renderer on any change to the rendering state. The optimization of + // skipping renderer setup is aimed at the case of multiple glDraw* right after each other + if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); + if (cap == 0x0B60 /* GL_FOG */) { + GLEmulation.fogEnabled = true; + return; + } else if (!(cap in validCapabilities)) { + return; + } Module.ctx.enable(cap); }; _glDisable = function(cap) { - if (cap in ignoredCapabilities) return; + if (GL.immediate.lastRenderer) GL.immediate.lastRenderer.cleanup(); + if (cap == 0x0B60 /* GL_FOG */) { + GLEmulation.fogEnabled = false; + return; + } else if (!(cap in validCapabilities)) { + return; + } Module.ctx.disable(cap); }; + _glIsEnabled = function(cap) { + if (cap == 0x0B60 /* GL_FOG */) { + return GLEmulation.fogEnabled ? 1 : 0; + } else if (!(cap in validCapabilities)) { + return 0; + } + return Module.ctx.isEnabled(cap); + }; var glGetIntegerv = _glGetIntegerv; _glGetIntegerv = function(pname, params) { @@ -942,7 +999,11 @@ var LibraryGL = { _glGetString = function(name_) { switch(name_) { case 0x1F03 /* GL_EXTENSIONS */: // Add various extensions that we can support - return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ') + ' GL_EXT_texture_env_combine GL_ARB_texture_env_crossbar GL_ATI_texture_env_combine3 GL_NV_texture_env_combine4 GL_EXT_texture_env_dot3 GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_framebuffer_object GL_ARB_vertex_program GL_ARB_fragment_program GL_ARB_shading_language_100 GL_ARB_shader_objects GL_ARB_vertex_shader GL_ARB_fragment_shader GL_ARB_texture_cube_map GL_EXT_draw_range_elements'), 'i8', ALLOC_NORMAL); + return allocate(intArrayFromString(Module.ctx.getSupportedExtensions().join(' ') + + ' GL_EXT_texture_env_combine GL_ARB_texture_env_crossbar GL_ATI_texture_env_combine3 GL_NV_texture_env_combine4 GL_EXT_texture_env_dot3 GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_framebuffer_object GL_ARB_vertex_program GL_ARB_fragment_program GL_ARB_shading_language_100 GL_ARB_shader_objects GL_ARB_vertex_shader GL_ARB_fragment_shader GL_ARB_texture_cube_map GL_EXT_draw_range_elements' + + (GL.compressionExt ? ' GL_ARB_texture_compression GL_EXT_texture_compression_s3tc' : '') + + (GL.anisotropicExt ? ' GL_EXT_texture_filter_anisotropic' : '') + ), 'i8', ALLOC_NORMAL); } return glGetString(name_); }; @@ -986,7 +1047,7 @@ var LibraryGL = { 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][0], u_modelView[1][0], u_modelView[2][0], u_modelView[3][0])'); // XXX extremely inefficient + 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'); @@ -1034,9 +1095,10 @@ var LibraryGL = { source = 'attribute vec3 a_normal; \n' + source.replace(/gl_Normal/g, 'a_normal'); } + // fog if (source.indexOf('gl_FogFragCoord') >= 0) { - source = 'varying float v_fogCoord; \n' + - source.replace(/gl_FogFragCoord/g, 'v_fogCoord'); + source = 'varying float v_fogFragCoord; \n' + + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); } } else { // Fragment shader for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { @@ -1049,7 +1111,26 @@ var LibraryGL = { if (source.indexOf('gl_Color') >= 0) { source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color'); } - source = source.replace(/gl_Fog.color/g, 'vec4(0.0)'); // XXX TODO + if (source.indexOf('gl_Fog.color') >= 0) { + source = 'uniform vec4 u_fogColor; \n' + + source.replace(/gl_Fog.color/g, 'u_fogColor'); + } + if (source.indexOf('gl_Fog.end') >= 0) { + source = 'uniform float u_fogEnd; \n' + + source.replace(/gl_Fog.end/g, 'u_fogEnd'); + } + if (source.indexOf('gl_Fog.scale') >= 0) { + source = 'uniform float u_fogScale; \n' + + source.replace(/gl_Fog.scale/g, 'u_fogScale'); + } + if (source.indexOf('gl_Fog.density') >= 0) { + source = 'uniform float u_fogDensity; \n' + + source.replace(/gl_Fog.density/g, 'u_fogDensity'); + } + if (source.indexOf('gl_FogFragCoord') >= 0) { + source = 'varying float v_fogFragCoord; \n' + + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); + } source = 'precision mediump float;\n' + source; } #if GL_DEBUG @@ -1105,6 +1186,21 @@ var LibraryGL = { if (program == GL.currProgram) GL.currProgram = 0; }; + // If attribute 0 was not bound, bind it to 0 for WebGL performance reasons. Track if 0 is free for that. + var zeroUsedPrograms = {}; + var glBindAttribLocation = _glBindAttribLocation; + _glBindAttribLocation = function(program, index, name) { + if (index == 0) zeroUsedPrograms[program] = true; + glBindAttribLocation(program, index, name); + }; + var glLinkProgram = _glLinkProgram; + _glLinkProgram = function(program) { + if (!(program in zeroUsedPrograms)) { + Module.ctx.bindAttribLocation(GL.programs[program], 0, 'a_position'); + } + glLinkProgram(program); + }; + var glBindBuffer = _glBindBuffer; _glBindBuffer = function(target, buffer) { glBindBuffer(target, buffer); @@ -1134,15 +1230,27 @@ var LibraryGL = { } else if (pname == 0x0BA8) { // GL_TEXTURE_MATRIX HEAPF32.set(GL.immediate.matrix['t' + GL.immediate.clientActiveTexture], params >> 2); } else if (pname == 0x0B66) { // GL_FOG_COLOR - {{{ makeSetValue('params', '0', '0', 'float') }}}; + HEAPF32.set(GLEmulation.fogColor, params >> 2); } else if (pname == 0x0B63) { // GL_FOG_START - {{{ makeSetValue('params', '0', '0', 'float') }}}; + {{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}}; } else if (pname == 0x0B64) { // GL_FOG_END - {{{ makeSetValue('params', '0', '0', 'float') }}}; + {{{ makeSetValue('params', '0', 'GLEmulation.fogEnd', 'float') }}}; + } else if (pname == 0x0B62) { // GL_FOG_DENSITY + {{{ makeSetValue('params', '0', 'GLEmulation.fogDensity', 'float') }}}; + } else if (pname == 0x0B65) { // GL_FOG_MODE + {{{ makeSetValue('params', '0', 'GLEmulation.fogMode', 'float') }}}; } else { glGetFloatv(pname, params); } }; + + var glHint = _glHint; + _glHint = function(target, mode) { + if (target == 0x84EF) { // GL_TEXTURE_COMPRESSION_HINT + return; + } + glHint(target, mode); + }; }, getProcAddress: function(name) { @@ -1269,12 +1377,16 @@ var LibraryGL = { rendererCache: {}, rendererComponents: {}, // small cache for calls inside glBegin/end. counts how many times the element was seen rendererComponentPointer: 0, // next place to start a glBegin/end component + lastRenderer: null, // used to avoid cleaning up and re-preparing the same renderer + lastArrayBuffer: null, // used in conjunction with lastRenderer + lastProgram: null, // "" // The following data structures are used for OpenGL Immediate Mode matrix routines. matrix: {}, matrixStack: {}, currentMatrix: 'm', // default is modelview tempMatrix: null, + matricesModified: false, // Clientside attributes VERTEX: 0, @@ -1324,6 +1436,7 @@ var LibraryGL = { tempBufferIndexLookup: null, tempVertexBuffers: null, tempIndexBuffers: null, + tempQuadIndexBuffer: null, generateTempBuffers: function() { function ceilPower2(x) { @@ -1357,6 +1470,30 @@ var LibraryGL = { last = size; } } + + // GL_QUAD indexes can be precalculated + this.tempQuadIndexBuffer = Module.ctx.createBuffer(); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); + var numIndexes = this.MAX_TEMP_BUFFER_SIZE >> 1; + var quadIndexes = new Uint16Array(numIndexes); + var i = 0, v = 0; + while (1) { + quadIndexes[i++] = v; + if (i >= numIndexes) break; + quadIndexes[i++] = v+1; + if (i >= numIndexes) break; + quadIndexes[i++] = v+2; + if (i >= numIndexes) break; + quadIndexes[i++] = v; + if (i >= numIndexes) break; + quadIndexes[i++] = v+2; + if (i >= numIndexes) break; + quadIndexes[i++] = v+3; + if (i >= numIndexes) break; + v += 4; + } + Module.ctx.bufferData(Module.ctx.ELEMENT_ARRAY_BUFFER, quadIndexes, Module.ctx.STATIC_DRAW); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, null); }, // Renderers @@ -1394,7 +1531,14 @@ var LibraryGL = { if (!cacheItem[attribute.type]) cacheItem[attribute.type] = {}; cacheItem = cacheItem[attribute.type]; } - if (GL.currProgram) { + if (GLEmulation.fogEnabled) { + var fogParam = GLEmulation.fogMode; + } else { + var fogParam = 0; // all valid modes are non-zero + } + if (!cacheItem[fogParam]) cacheItem[fogParam] = {}; + cacheItem = cacheItem[fogParam]; + if (GL.currProgram) { // Note the order here; this one is last, and optional if (!cacheItem[GL.currProgram]) cacheItem[GL.currProgram] = {}; cacheItem = cacheItem[GL.currProgram]; } @@ -1446,20 +1590,43 @@ var LibraryGL = { } this.program = GL.programs[GL.currProgram]; } else { + // IMPORTANT NOTE: If you parameterize the shader source based on any runtime values + // in order to create the least expensive shader possible based on the features being + // used, you should also update the code in the beginning of getRenderer to make sure + // that you cache the renderer based on the said parameters. this.vertexShader = Module.ctx.createShader(Module.ctx.VERTEX_SHADER); var zero = positionSize == 2 ? '0, ' : ''; + if (GLEmulation.fogEnabled) { + switch (GLEmulation.fogMode) { + case 0x0801: // GL_EXP2 + // fog = exp(-(gl_Fog.density * gl_FogFragCoord)^2) + var fogFormula = ' float fog = exp(-u_fogDensity * u_fogDensity * ecDistance * ecDistance); \n'; + break; + case 0x2601: // GL_LINEAR + // fog = (gl_Fog.end - gl_FogFragCoord) * gl_fog.scale + var fogFormula = ' float fog = (u_fogEnd - ecDistance) * u_fogScale; \n'; + break; + default: // default to GL_EXP + // fog = exp(-gl_Fog.density * gl_FogFragCoord) + var fogFormula = ' float fog = exp(-u_fogDensity * ecDistance); \n'; + break; + } + } Module.ctx.shaderSource(this.vertexShader, 'attribute vec' + positionSize + ' a_position; \n' + 'attribute vec2 a_texCoord0; \n' + (hasTextures ? 'varying vec2 v_texCoord; \n' : '') + 'varying vec4 v_color; \n' + (colorSize ? 'attribute vec4 a_color; \n': 'uniform vec4 u_color; \n') + + (GLEmulation.fogEnabled ? 'varying float v_fogFragCoord; \n' : '') + 'uniform mat4 u_modelView; \n' + 'uniform mat4 u_projection; \n' + 'void main() \n' + '{ \n' + - ' gl_Position = u_projection * (u_modelView * vec4(a_position, ' + zero + '1.0)); \n' + + ' vec4 ecPosition = (u_modelView * vec4(a_position, ' + zero + '1.0)); \n' + // eye-coordinate position + ' gl_Position = u_projection * ecPosition; \n' + (hasTextures ? 'v_texCoord = a_texCoord0; \n' : '') + (colorSize ? 'v_color = a_color; \n' : 'v_color = u_color; \n') + + (GLEmulation.fogEnabled ? 'v_fogFragCoord = abs(ecPosition.z);\n' : '') + '} \n'); Module.ctx.compileShader(this.vertexShader); @@ -1468,16 +1635,30 @@ var LibraryGL = { 'varying vec2 v_texCoord; \n' + 'uniform sampler2D u_texture; \n' + 'varying vec4 v_color; \n' + + (GLEmulation.fogEnabled ? ( + 'varying float v_fogFragCoord; \n' + + 'uniform vec4 u_fogColor; \n' + + 'uniform float u_fogEnd; \n' + + 'uniform float u_fogScale; \n' + + 'uniform float u_fogDensity; \n' + + 'float ffog(in float ecDistance) { \n' + + fogFormula + + ' fog = clamp(fog, 0.0, 1.0); \n' + + ' return fog; \n' + + '} \n' + ) : '') + 'void main() \n' + '{ \n' + (hasTextures ? 'gl_FragColor = v_color * texture2D( u_texture, v_texCoord );\n' : 'gl_FragColor = v_color;\n') + + (GLEmulation.fogEnabled ? 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a); \n' : '') + '} \n'); Module.ctx.compileShader(this.fragmentShader); this.program = Module.ctx.createProgram(); Module.ctx.attachShader(this.program, this.vertexShader); Module.ctx.attachShader(this.program, this.fragmentShader); + Module.ctx.bindAttribLocation(this.program, 0, 'a_position'); Module.ctx.linkProgram(this.program); } @@ -1507,9 +1688,55 @@ var LibraryGL = { this.hasNormal = normalSize > 0 && this.normalLocation >= 0; this.floatType = Module.ctx.FLOAT; // minor optimization + + this.fogColorLocation = Module.ctx.getUniformLocation(this.program, 'u_fogColor'); + this.fogEndLocation = Module.ctx.getUniformLocation(this.program, 'u_fogEnd'); + this.fogScaleLocation = Module.ctx.getUniformLocation(this.program, 'u_fogScale'); + this.fogDensityLocation = Module.ctx.getUniformLocation(this.program, 'u_fogDensity'); + this.hasFog = !!(this.fogColorLocation || this.fogEndLocation || + this.fogScaleLocation || this.fogDensityLocation); }, prepare: function() { + // Calculate the array buffer + var arrayBuffer; + if (!GL.currArrayBuffer) { + var start = GL.immediate.firstVertex*GL.immediate.stride; + var end = GL.immediate.lastVertex*GL.immediate.stride; + assert(end <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); + arrayBuffer = GL.immediate.tempVertexBuffers[GL.immediate.tempBufferIndexLookup[end]]; + // TODO: consider using the last buffer we bound, if it was larger. downside is larger buffer, but we might avoid rebinding and preparing + } else { + arrayBuffer = GL.currArrayBuffer; + } + + // If the array buffer is unchanged and the renderer as well, then we can avoid all the work here + // XXX We use some heuristics here, and this may not work in all cases. Try disabling this if you + // have odd glitches (by setting canSkip always to 0, or even cleaning up the renderer right + // after rendering) + var lastRenderer = GL.immediate.lastRenderer; + var canSkip = this == lastRenderer && + arrayBuffer == GL.immediate.lastArrayBuffer && + (GL.currProgram || this.program) == GL.immediate.lastProgram && + !GL.immediate.matricesModified; + if (!canSkip && lastRenderer) lastRenderer.cleanup(); + if (!GL.currArrayBuffer) { + // Bind the array buffer and upload data after cleaning up the previous renderer + if (arrayBuffer != GL.immediate.lastArrayBuffer) { + Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, arrayBuffer); + } + Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, GL.immediate.vertexData.subarray(start >> 2, end >> 2)); + } + if (canSkip) return; + GL.immediate.lastRenderer = this; + GL.immediate.lastArrayBuffer = arrayBuffer; + GL.immediate.lastProgram = GL.currProgram || this.program; + GL.immediate.matricesModified = false; + + if (!GL.currProgram) { + Module.ctx.useProgram(this.program); + } + if (this.modelViewLocation) Module.ctx.uniformMatrix4fv(this.modelViewLocation, false, GL.immediate.matrix['m']); if (this.projectionLocation) Module.ctx.uniformMatrix4fv(this.projectionLocation, false, GL.immediate.matrix['p']); @@ -1550,6 +1777,12 @@ var LibraryGL = { Module.ctx.bindTexture(Module.ctx.TEXTURE_2D, texture); Module.ctx.uniform1i(this.textureLocation, 0); } + if (this.hasFog) { + if (this.fogColorLocation) Module.ctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor); + if (this.fogEndLocation) Module.ctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd); + if (this.fogScaleLocation) Module.ctx.uniform1f(this.fogScaleLocation, 1/(GLEmulation.fogEnd - GLEmulation.fogStart)); + if (this.fogDensityLocation) Module.ctx.uniform1f(this.fogDensityLocation, GLEmulation.fogDensity); + } }, cleanup: function() { @@ -1567,6 +1800,17 @@ var LibraryGL = { if (this.hasNormal) { Module.ctx.disableVertexAttribArray(this.normalLocation); } + if (!GL.currProgram) { + Module.ctx.useProgram(null); + } + if (!GL.currArrayBuffer) { + Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); + } + + GL.immediate.lastRenderer = null; + GL.immediate.lastArrayBuffer = null; + GL.immediate.lastProgram = null; + GL.immediate.matricesModified = true; } }; ret.init(); @@ -1601,7 +1845,9 @@ var LibraryGL = { // Initialize matrix library GL.immediate.matrix['m'] = GL.immediate.matrix.lib.mat4.create(); + GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['m']); GL.immediate.matrix['p'] = GL.immediate.matrix.lib.mat4.create(); + GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix['p']); for (var i = 0; i < GL.immediate.MAX_TEXTURES; i++) { GL.immediate.matrix['t' + i] = GL.immediate.matrix.lib.mat4.create(); } @@ -1748,43 +1994,19 @@ var LibraryGL = { emulatedElementArrayBuffer = true; } } else if (GL.immediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes - if (GL.immediate.mode == 7) { // GL_QUADS - var numQuads = numVertexes / 4; - assert(numQuads % 1 == 0); - for (var i = 0; i < numQuads; i++) { - var start = i*4; - GL.immediate.indexData[numIndexes++] = start; - GL.immediate.indexData[numIndexes++] = start+1; - GL.immediate.indexData[numIndexes++] = start+2; - GL.immediate.indexData[numIndexes++] = start; - GL.immediate.indexData[numIndexes++] = start+2; - GL.immediate.indexData[numIndexes++] = start+3; - } - } else { - throw 'unsupported immediate mode ' + GL.immediate.mode; - } - assert(numIndexes << 1 <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); - var indexBuffer = GL.immediate.tempIndexBuffers[GL.immediate.tempBufferIndexLookup[numIndexes << 1]]; - Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, indexBuffer); - Module.ctx.bufferSubData(Module.ctx.ELEMENT_ARRAY_BUFFER, 0, this.indexData.subarray(0, numIndexes)); + if (GL.immediate.mode != 7) throw 'unsupported immediate mode ' + GL.immediate.mode; // GL_QUADS + // GL.immediate.firstVertex is the first vertex we want. Quad indexes are in the pattern + // 0 1 2, 0 2 3, 4 5 6, 4 6 7, so we need to look at index firstVertex * 1.5 to see it. + // Then since indexes are 2 bytes each, that means 3 + assert(GL.immediate.firstVertex % 4 == 0); + ptr = GL.immediate.firstVertex*3; + var numQuads = numVertexes / 4; + numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern + assert(ptr + (numIndexes << 1) <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); + Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, this.tempQuadIndexBuffer); emulatedElementArrayBuffer = true; } - if (!GL.currArrayBuffer) { - Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, this.vertexObject); - var start = GL.immediate.firstVertex*GL.immediate.stride; - var end = GL.immediate.lastVertex*GL.immediate.stride; - assert(end <= GL.immediate.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); - var vertexBuffer = GL.immediate.tempVertexBuffers[GL.immediate.tempBufferIndexLookup[end]]; - Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, vertexBuffer); - Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, start, this.vertexData.subarray(start >> 2, end >> 2)); - } - - // Render - if (!GL.currProgram) { - Module.ctx.useProgram(renderer.program); - } - renderer.prepare(); if (numIndexes) { @@ -1793,17 +2015,9 @@ var LibraryGL = { Module.ctx.drawArrays(GL.immediate.mode, startIndex, numVertexes); } - renderer.cleanup(); - - if (!GL.currArrayBuffer) { - Module.ctx.bindBuffer(Module.ctx.ARRAY_BUFFER, null); - } if (emulatedElementArrayBuffer) { Module.ctx.bindBuffer(Module.ctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null); } - if (!GL.currProgram) { - Module.ctx.useProgram(null); - } } }, @@ -1886,7 +2100,7 @@ var LibraryGL = { }, glColor4us__deps: ['glColor4f'], glColor4us: function(r, g, b, a) { - _glColor4f((r&65525)/65535, (g&65525)/65535, (b&65525)/65535, (a&65525)/65535); + _glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535); }, glColor4ui__deps: ['glColor4f'], glColor4ui: function(r, g, b, a) { @@ -1931,10 +2145,58 @@ var LibraryGL = { _glColor4f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}, {{{ makeGetValue('p', '12', 'float') }}}); }, - glFogf: function(){}, // TODO - glFogi: function(){}, // TODO - glFogx: function(){}, // TODO - glFogfv: function(){}, // TODO + glFogf: function(pname, param) { // partial support, TODO + switch(pname) { + case 0x0B63: // GL_FOG_START + GLEmulation.fogStart = param; break; + case 0x0B64: // GL_FOG_END + GLEmulation.fogEnd = param; break; + case 0x0B62: // GL_FOG_DENSITY + GLEmulation.fogDensity = param; break; + case 0x0B65: // GL_FOG_MODE + switch (param) { + case 0x0801: // GL_EXP2 + case 0x2601: // GL_LINEAR + GLEmulation.fogMode = param; break; + default: // default to GL_EXP + GLEmulation.fogMode = 0x0800 /* GL_EXP */; break; + } + break; + } + }, + glFogi__deps: ['glFogf'], + glFogi: function(pname, param) { + return _glFogf(pname, param); + }, + glFogfv__deps: ['glFogf'], + glFogfv: function(pname, param) { // partial support, TODO + switch(pname) { + case 0x0B66: // GL_FOG_COLOR + GLEmulation.fogColor[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.fogColor[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.fogColor[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.fogColor[3] = {{{ makeGetValue('param', '12', 'float') }}}; + break; + case 0x0B63: // GL_FOG_START + case 0x0B64: // GL_FOG_END + _glFogf(pname, {{{ makeGetValue('param', '0', 'float') }}}); break; + } + }, + glFogiv__deps: ['glFogf'], + glFogiv: function(pname, param) { + switch(pname) { + case 0x0B66: // GL_FOG_COLOR + GLEmulation.fogColor[0] = ({{{ makeGetValue('param', '0', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[1] = ({{{ makeGetValue('param', '4', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[2] = ({{{ makeGetValue('param', '8', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[3] = ({{{ makeGetValue('param', '12', 'i32') }}}/2147483647)/2.0+0.5; + break; + default: + _glFogf(pname, {{{ makeGetValue('param', '0', 'i32') }}}); break; + } + }, + glFogx: 'glFogi', + glFogxv: 'glFogiv', glPolygonMode: function(){}, // TODO @@ -2011,20 +2273,24 @@ var LibraryGL = { }, glPushMatrix: function() { + GL.immediate.matricesModified = true; GL.immediate.matrixStack[GL.immediate.currentMatrix].push( Array.prototype.slice.call(GL.immediate.matrix[GL.immediate.currentMatrix])); }, glPopMatrix: function() { + GL.immediate.matricesModified = true; GL.immediate.matrix[GL.immediate.currentMatrix] = GL.immediate.matrixStack[GL.immediate.currentMatrix].pop(); }, glLoadIdentity__deps: ['$GL', '$GLImmediateSetup'], glLoadIdentity: function() { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.identity(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadMatrixd: function(matrix) { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+16*8') }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); }, @@ -2032,30 +2298,36 @@ var LibraryGL = { #if GL_DEBUG if (GL.debug) Module.printErr('glLoadMatrixf receiving: ' + Array.prototype.slice.call(HEAPF32.subarray(matrix >> 2, (matrix >> 2) + 16))); #endif + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+16*4') }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadTransposeMatrixd: function(matrix) { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+16*8') }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glLoadTransposeMatrixf: function(matrix) { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+16*4') }}}, GL.immediate.matrix[GL.immediate.currentMatrix]); GL.immediate.matrix.lib.mat4.transpose(GL.immediate.matrix[GL.immediate.currentMatrix]); }, glMultMatrixd: function(matrix) { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], {{{ makeHEAPView('F64', 'matrix', 'matrix+16*8') }}}); }, glMultMatrixf: function(matrix) { + GL.immediate.matricesModified = true; GL.immediate.matrix.lib.mat4.multiply(GL.immediate.matrix[GL.immediate.currentMatrix], {{{ makeHEAPView('F32', 'matrix', 'matrix+16*4') }}}); }, glMultTransposeMatrixd: function(matrix) { + GL.immediate.matricesModified = true; var colMajor = GL.immediate.matrix.lib.mat4.create(); GL.immediate.matrix.lib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+16*8') }}}, colMajor); GL.immediate.matrix.lib.mat4.transpose(colMajor); @@ -2063,6 +2335,7 @@ var LibraryGL = { }, glMultTransposeMatrixf: function(matrix) { + GL.immediate.matricesMod |