diff options
Diffstat (limited to 'src/library_gl.js')
-rw-r--r-- | src/library_gl.js | 273 |
1 files changed, 186 insertions, 87 deletions
diff --git a/src/library_gl.js b/src/library_gl.js index a4da4220..3055309b 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -57,6 +57,7 @@ var LibraryGL = { unpackAlignment: 4, // default alignment is 4 bytes init: function() { + GL.createLog2ceilLookup(GL.MAX_TEMP_BUFFER_SIZE); Browser.moduleContextCreatedCallbacks.push(GL.initExtensions); }, @@ -81,36 +82,58 @@ var LibraryGL = { miniTempBuffer: null, miniTempBufferViews: [0], // index i has the view of size i+1 - // Large temporary buffers + // When user GL code wants to render from client-side memory, we need to upload the vertex data to a temp VBO + // for rendering. Maintain a set of temp VBOs that are created-on-demand to appropriate sizes, and never destroyed. + // Also, for best performance the VBOs are double-buffered, i.e. every second frame we switch the set of VBOs we + // upload to, so that rendering from the previous frame is not disturbed by uploading from new data to it, which + // could cause a GPU-CPU pipeline stall. + // Note that index buffers are not double-buffered (at the moment) in this manner. MAX_TEMP_BUFFER_SIZE: {{{ GL_MAX_TEMP_BUFFER_SIZE }}}, - tempBufferIndexLookup: null, - tempVertexBuffers: null, - tempIndexBuffers: null, + tempVertexBuffers1: [], + tempVertexBufferCounters1: [], + tempVertexBuffers2: [], + tempVertexBufferCounters2: [], + // Maximum number of temp VBOs of one size to maintain, after that we start reusing old ones, which is safe but can give + // a performance impact. If CPU-GPU stalls are a problem, increasing this might help. + numTempVertexBuffersPerSize: 64, // (const) + tempIndexBuffers: [], tempQuadIndexBuffer: null, - generateTempBuffers: function(quads) { - GL.tempBufferIndexLookup = new Uint8Array(GL.MAX_TEMP_BUFFER_SIZE+1); - GL.tempVertexBuffers = []; - GL.tempIndexBuffers = []; - var last = -1, curr = -1; - var size = 1; - for (var i = 0; i <= GL.MAX_TEMP_BUFFER_SIZE; i++) { - if (i > size) { - size <<= 1; + // Precompute a lookup table for the function ceil(log2(x)), i.e. how many bits are needed to represent x, or, + // if x was rounded up to next pow2, which index is the single '1' bit at? + // Then log2ceilLookup[x] returns ceil(log2(x)). + log2ceilLookup: null, + createLog2ceilLookup: function(maxValue) { + GL.log2ceilLookup = new Uint8Array(maxValue+1); + var log2 = 0; + var pow2 = 1; + GL.log2ceilLookup[0] = 0; + for(var i = 1; i <= maxValue; ++i) { + if (i > pow2) { + pow2 <<= 1; + ++log2; } - if (size != last) { - curr++; - GL.tempVertexBuffers[curr] = GLctx.createBuffer(); - GLctx.bindBuffer(GLctx.ARRAY_BUFFER, GL.tempVertexBuffers[curr]); - GLctx.bufferData(GLctx.ARRAY_BUFFER, size, GLctx.DYNAMIC_DRAW); - GLctx.bindBuffer(GLctx.ARRAY_BUFFER, null); - GL.tempIndexBuffers[curr] = GLctx.createBuffer(); - GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.tempIndexBuffers[curr]); - GLctx.bufferData(GLctx.ELEMENT_ARRAY_BUFFER, size, GLctx.DYNAMIC_DRAW); - GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, null); - last = size; + GL.log2ceilLookup[i] = log2; + } + }, + + generateTempBuffers: function(quads) { + var largestIndex = GL.log2ceilLookup[GL.MAX_TEMP_BUFFER_SIZE]; + GL.tempVertexBufferCounters1.length = GL.tempVertexBufferCounters2.length = largestIndex+1; + GL.tempVertexBuffers1.length = GL.tempVertexBuffers2.length = largestIndex+1; + GL.tempIndexBuffers.length = largestIndex+1; + for(var i = 0; i <= largestIndex; ++i) { + GL.tempIndexBuffers[i] = null; // Created on-demand + GL.tempVertexBufferCounters1[i] = GL.tempVertexBufferCounters2[i] = 0; + var ringbufferLength = GL.numTempVertexBuffersPerSize; + GL.tempVertexBuffers1[i] = []; + GL.tempVertexBuffers2[i] = []; + var ringbuffer1 = GL.tempVertexBuffers1[i]; + var ringbuffer2 = GL.tempVertexBuffers2[i]; + ringbuffer1.length = ringbuffer2.length = ringbufferLength; + for(var j = 0; j < ringbufferLength; ++j) { + ringbuffer1[j] = ringbuffer2[j] = null; // Created on-demand } - GL.tempBufferIndexLookup[i] = curr; } if (quads) { @@ -140,6 +163,53 @@ var LibraryGL = { } }, + getTempVertexBuffer: function getTempVertexBuffer(sizeBytes) { + var idx = GL.log2ceilLookup[sizeBytes]; + var ringbuffer = GL.tempVertexBuffers1[idx]; + var nextFreeBufferIndex = GL.tempVertexBufferCounters1[idx]; + GL.tempVertexBufferCounters1[idx] = (GL.tempVertexBufferCounters1[idx]+1) & (GL.numTempVertexBuffersPerSize-1); + var vbo = ringbuffer[nextFreeBufferIndex]; + if (vbo) { + return vbo; + } + var prevVBO = GLctx.getParameter(GLctx.ARRAY_BUFFER_BINDING); + ringbuffer[nextFreeBufferIndex] = GLctx.createBuffer(); + GLctx.bindBuffer(GLctx.ARRAY_BUFFER, ringbuffer[nextFreeBufferIndex]); + GLctx.bufferData(GLctx.ARRAY_BUFFER, 1 << idx, GLctx.DYNAMIC_DRAW); + GLctx.bindBuffer(GLctx.ARRAY_BUFFER, prevVBO); + return ringbuffer[nextFreeBufferIndex]; + }, + + getTempIndexBuffer: function getTempIndexBuffer(sizeBytes) { + var idx = GL.log2ceilLookup[sizeBytes]; + var ibo = GL.tempIndexBuffers[idx]; + if (ibo) { + return ibo; + } + var prevIBO = GLctx.getParameter(GLctx.ELEMENT_ARRAY_BUFFER_BINDING); + GL.tempIndexBuffers[idx] = GLctx.createBuffer(); + GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.tempIndexBuffers[idx]); + GLctx.bufferData(GLctx.ELEMENT_ARRAY_BUFFER, 1 << idx, GLctx.DYNAMIC_DRAW); + GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, prevIBO); + return GL.tempIndexBuffers[idx]; + }, + + // Called at start of each new WebGL rendering frame. This swaps the doublebuffered temp VB memory pointers, + // so that every second frame utilizes different set of temp buffers. The aim is to keep the set of buffers + // being rendered, and the set of buffers being updated disjoint. + newRenderingFrameStarted: function newRenderingFrameStarted() { + var vb = GL.tempVertexBuffers1; + GL.tempVertexBuffers1 = GL.tempVertexBuffers2; + GL.tempVertexBuffers2 = vb; + vb = GL.tempVertexBufferCounters1; + GL.tempVertexBufferCounters1 = GL.tempVertexBufferCounters2; + GL.tempVertexBufferCounters2 = vb; + var largestIndex = GL.log2ceilLookup[GL.MAX_TEMP_BUFFER_SIZE]; + for(var i = 0; i <= largestIndex; ++i) { + GL.tempVertexBufferCounters1[i] = 0; + } + }, + // Find a token in a shader source string findToken: function(source, token) { function isIdentChar(ch) { @@ -446,9 +516,6 @@ var LibraryGL = { preDrawHandleClientVertexAttribBindings: function preDrawHandleClientVertexAttribBindings(count) { GL.resetBufferBinding = false; - var used = GL.usedTempBuffers; - used.length = 0; - // TODO: initial pass to detect ranges we need to upload, might not need an upload per attrib for (var i = 0; i < GL.maxVertexAttribs; ++i) { var cb = GL.clientBuffers[i]; @@ -457,15 +524,7 @@ var LibraryGL = { GL.resetBufferBinding = true; var size = GL.calcBufLength(cb.size, cb.type, cb.stride, count); - var index = GL.tempBufferIndexLookup[size]; - var buf; - do { -#if ASSERTIONS - assert(index < GL.tempVertexBuffers.length); -#endif - buf = GL.tempVertexBuffers[index++]; - } while (used.indexOf(buf) >= 0); - used.push(buf); + var buf = GL.getTempVertexBuffer(size); GLctx.bindBuffer(GLctx.ARRAY_BUFFER, buf); GLctx.bufferSubData(GLctx.ARRAY_BUFFER, 0, @@ -2321,7 +2380,7 @@ var LibraryGL = { // GL Immediate mode // See comment in GLEmulation.init() -#if FULL_ES2 == 0 +#if !FULL_ES2 $GLImmediate__postset: 'GLImmediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(function() { GLImmediate.init() });', #endif $GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'], @@ -2742,14 +2801,6 @@ var LibraryGL = { 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. } - this.traverseState = function(keyView) { - if (this.key0 == -1) { - this.recomputeKey(); - } - keyView.next(this.key0); - keyView.next(this.key1); - keyView.next(this.key2); - }; } function CTexUnit() { @@ -2758,26 +2809,55 @@ var LibraryGL = { 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) { - var texUnitType = this.getTexType(); - keyView.next(texUnitType); - if (!texUnitType) return; - this.env.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.getTexType() != 0; + return this.texTypesEnabled; } CTexUnit.prototype.genPassLines = function CTexUnit_genPassLines(passOutputVar, passInputVar, texUnitID) { if (!this.enabled()) { return ["vec4 " + passOutputVar + " = " + passInputVar + ";"]; } - - return this.env.genPassLines(passOutputVar, passInputVar, texUnitID); + 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() { @@ -2898,13 +2978,18 @@ var LibraryGL = { 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(", - colorVar + " * " + valToFloatLiteral(this.colorScale), + scaledColor, ", ", - alphaVar + " * " + valToFloatLiteral(this.alphaScale), + scaledAlpha, ")", ";", ].join(""); @@ -3084,12 +3169,7 @@ var LibraryGL = { traverseState: function(keyView) { for (var i = 0; i < s_texUnits.length; i++) { - var texUnit = s_texUnits[i]; - var enabled = texUnit.enabled(); - keyView.next(enabled); - if (enabled) { - texUnit.traverseState(keyView); - } + s_texUnits[i].traverseState(keyView); } }, @@ -3113,24 +3193,28 @@ var LibraryGL = { 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; } @@ -3143,24 +3227,28 @@ var LibraryGL = { 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; } @@ -3514,6 +3602,11 @@ var LibraryGL = { 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]++; } @@ -3535,7 +3628,6 @@ var LibraryGL = { // we maintain a cache of renderers, optimized to not generate garbage var attributes = GLImmediate.liveClientAttributes; var cacheMap = GLImmediate.rendererCache; - var temp; var keyView = cacheMap.getStaticKeyView().reset(); // By attrib state: @@ -3543,7 +3635,6 @@ var LibraryGL = { for (var i = 0; i < attributes.length; i++) { enabledAttributesKey |= 1 << attributes[i].name; } - keyView.next(enabledAttributesKey); // By fog state: var fogParam = 0; @@ -3560,13 +3651,17 @@ var LibraryGL = { break; } } - keyView.next(fogParam); + keyView.next((enabledAttributesKey << 2) | fogParam); +#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(); @@ -3772,7 +3867,7 @@ var LibraryGL = { 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); @@ -3784,6 +3879,9 @@ var LibraryGL = { 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); } @@ -3791,7 +3889,6 @@ var LibraryGL = { for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { this.textureMatrixLocations[i] = GLctx.getUniformLocation(this.program, 'u_textureMatrix' + i); } - this.colorLocation = GLctx.getAttribLocation(this.program, 'a_color'); this.normalLocation = GLctx.getAttribLocation(this.program, 'a_normal'); this.modelViewLocation = GLctx.getUniformLocation(this.program, 'u_modelView'); @@ -3822,7 +3919,7 @@ var LibraryGL = { #if ASSERTIONS assert(end <= GL.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); #endif - arrayBuffer = GL.tempVertexBuffers[GL.tempBufferIndexLookup[end]]; + 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 = GL.currArrayBuffer; @@ -3884,11 +3981,9 @@ var LibraryGL = { #if GL_FFP_ONLY if (!GL.currArrayBuffer) { GLctx.vertexAttribPointer(GLImmediate.VERTEX, posAttr.size, posAttr.type, false, GLImmediate.stride, posAttr.offset); - GL.enableVertexAttribArray(GLImmediate.VERTEX); if (this.hasNormal) { var normalAttr = clientAttributes[GLImmediate.NORMAL]; GLctx.vertexAttribPointer(GLImmediate.NORMAL, normalAttr.size, normalAttr.type, true, GLImmediate.stride, normalAttr.offset); - GL.enableVertexAttribArray(GLImmediate.NORMAL); } } #else @@ -3911,11 +4006,9 @@ var LibraryGL = { var texAttr = clientAttributes[attribLoc]; if (texAttr.size) { GLctx.vertexAttribPointer(attribLoc, texAttr.size, texAttr.type, false, GLImmediate.stride, texAttr.offset); - GL.enableVertexAttribArray(attribLoc); } else { // These two might be dangerous, but let's try them. GLctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1); - GL.disableVertexAttribArray(attribLoc); } } #else @@ -3950,21 +4043,18 @@ var LibraryGL = { #if GL_FFP_ONLY if (!GL.currArrayBuffer) { GLctx.vertexAttribPointer(GLImmediate.COLOR, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); - GL.enableVertexAttribArray(GLImmediate.COLOR); } #else GLctx.vertexAttribPointer(this.colorLocation, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); GLctx.enableVertexAttribArray(this.colorLocation); #endif - } else if (this.hasColor) { -#if GL_FFP_ONLY - GL.disableVertexAttribArray(GLImmediate.COLOR); - GLctx.vertexAttrib4fv(GLImmediate.COLOR, GLImmediate.clientColor); -#else + } +#if !GL_FFP_ONLY + else if (this.hasColor) { GLctx.disableVertexAttribArray(this.colorLocation); GLctx.vertexAttrib4fv(this.colorLocation, GLImmediate.clientColor); -#endif } +#endif if (this.hasFog) { if (this.fogColorLocation) GLctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor); if (this.fogEndLocation) GLctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd); @@ -3991,6 +4081,7 @@ var LibraryGL = { } if (!GL.currProgram) { GLctx.useProgram(null); + GLImmediate.fixedFunctionProgram = 0; } if (!GL.currArrayBuffer) { GLctx.bindBuffer(GLctx.ARRAY_BUFFER, null); @@ -4138,11 +4229,12 @@ var LibraryGL = { if (!Module.useWebGL) return; // a 2D canvas may be currently used TODO: make sure we are actually called in that case - GLImmediate.TexEnvJIT.init(GLctx); - // 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. GLImmediate.MAX_TEXTURES = Module['GL_MAX_TEXTURE_IMAGE_UNITS'] || GLctx.getParameter(GLctx.MAX_TEXTURE_IMAGE_UNITS); + + GLImmediate.TexEnvJIT.init(GLctx, GLImmediate.MAX_TEXTURES); + GLImmediate.NUM_ATTRIBUTES = 3 /*pos+normal+color attributes*/ + GLImmediate.MAX_TEXTURES; GLImmediate.clientAttributes = []; GLEmulation.enabledClientAttribIndices = []; @@ -4331,7 +4423,7 @@ var LibraryGL = { #if ASSERTIONS assert(numProvidedIndexes << 1 <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); #endif - var indexBuffer = GL.tempIndexBuffers[GL.tempBufferIndexLookup[numProvidedIndexes << 1]]; + 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; @@ -4367,7 +4459,7 @@ var LibraryGL = { GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GL.currElementArrayBuffer] || null); } -#if GL_UNSAFE_OPTS == 0 +#if !GL_UNSAFE_OPTS #if !GL_FFP_ONLY renderer.cleanup(); #endif @@ -4480,6 +4572,9 @@ var LibraryGL = { 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', @@ -4627,6 +4722,10 @@ var LibraryGL = { 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; } @@ -4643,6 +4742,10 @@ var LibraryGL = { 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; } @@ -4654,7 +4757,6 @@ var LibraryGL = { #if GL_FFP_ONLY if (GL.currArrayBuffer) { GLctx.vertexAttribPointer(GLImmediate.VERTEX, size, type, false, stride, pointer); - GL.enableVertexAttribArray(GLImmediate.VERTEX); } #endif }, @@ -4664,7 +4766,6 @@ var LibraryGL = { if (GL.currArrayBuffer) { var loc = GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture; GLctx.vertexAttribPointer(loc, size, type, false, stride, pointer); - GL.enableVertexAttribArray(loc); } #endif }, @@ -4673,7 +4774,6 @@ var LibraryGL = { #if GL_FFP_ONLY if (GL.currArrayBuffer) { GLctx.vertexAttribPointer(GLImmediate.NORMAL, size, type, true, stride, pointer); - GL.enableVertexAttribArray(GLImmediate.NORMAL); } #endif }, @@ -4682,7 +4782,6 @@ var LibraryGL = { #if GL_FFP_ONLY if (GL.currArrayBuffer) { GLctx.vertexAttribPointer(GLImmediate.COLOR, size, type, true, stride, pointer); - GL.enableVertexAttribArray(GLImmediate.COLOR); } #endif }, @@ -5099,7 +5198,7 @@ var LibraryGL = { var buf; if (!GL.currElementArrayBuffer) { var size = GL.calcBufLength(1, type, 0, count); - buf = GL.tempIndexBuffers[GL.tempBufferIndexLookup[size]]; + buf = GL.getTempIndexBuffer(size); GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, buf); GLctx.bufferSubData(GLctx.ELEMENT_ARRAY_BUFFER, 0, |