|
@@ -0,0 +1,564 @@
|
|
|
+/**
|
|
|
+ * @author Mugen87 / https://github.com/Mugen87
|
|
|
+ * @author Takahiro / https://github.com/takahirox
|
|
|
+ */
|
|
|
+
|
|
|
+function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
|
|
|
+
|
|
|
+ const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );
|
|
|
+
|
|
|
+ const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' );
|
|
|
+ const vaoAvailable = capabilities.isWebGL2 || extension !== null;
|
|
|
+
|
|
|
+ const bindingStates = {};
|
|
|
+
|
|
|
+ const defaultState = createBindingState( null );
|
|
|
+ let currentState = defaultState;
|
|
|
+
|
|
|
+ function setup( object, material, program, geometry, index ) {
|
|
|
+
|
|
|
+ let updateBuffers = false;
|
|
|
+
|
|
|
+ if ( vaoAvailable ) {
|
|
|
+
|
|
|
+ const state = getBindingState( geometry, program, material );
|
|
|
+
|
|
|
+ if ( currentState !== state ) {
|
|
|
+
|
|
|
+ currentState = state;
|
|
|
+ bindVertexArrayObject( currentState.object );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ updateBuffers = needsUpdate( geometry );
|
|
|
+
|
|
|
+ if ( updateBuffers ) saveCache( geometry );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ const wireframe = ( material.wireframe === true );
|
|
|
+
|
|
|
+ if ( currentState.geometry !== geometry.id ||
|
|
|
+ currentState.program !== program.id ||
|
|
|
+ currentState.wireframe !== wireframe ) {
|
|
|
+
|
|
|
+ currentState.geometry = geometry.id;
|
|
|
+ currentState.program = program.id;
|
|
|
+ currentState.wireframe = wireframe;
|
|
|
+
|
|
|
+ updateBuffers = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( object.isInstancedMesh === true ) {
|
|
|
+
|
|
|
+ updateBuffers = true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( index !== null ) {
|
|
|
+
|
|
|
+ attributes.update( index, gl.ELEMENT_ARRAY_BUFFER );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( updateBuffers ) {
|
|
|
+
|
|
|
+ setupVertexAttributes( object, material, program, geometry );
|
|
|
+
|
|
|
+ if ( index !== null ) {
|
|
|
+
|
|
|
+ gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function createVertexArrayObject() {
|
|
|
+
|
|
|
+ if ( capabilities.isWebGL2 ) return gl.createVertexArray();
|
|
|
+
|
|
|
+ return extension.createVertexArrayOES();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function bindVertexArrayObject( vao ) {
|
|
|
+
|
|
|
+ if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao );
|
|
|
+
|
|
|
+ return extension.bindVertexArrayOES( vao );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function deleteVertexArrayObject( vao ) {
|
|
|
+
|
|
|
+ if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao );
|
|
|
+
|
|
|
+ return extension.deleteVertexArrayOES( vao );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function getBindingState( geometry, program, material ) {
|
|
|
+
|
|
|
+ const wireframe = ( material.wireframe === true );
|
|
|
+
|
|
|
+ let programMap = bindingStates[ geometry.id ];
|
|
|
+
|
|
|
+ if ( programMap === undefined ) {
|
|
|
+
|
|
|
+ programMap = {};
|
|
|
+ bindingStates[ geometry.id ] = programMap;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ let stateMap = programMap[ program.id ];
|
|
|
+
|
|
|
+ if ( stateMap === undefined ) {
|
|
|
+
|
|
|
+ stateMap = {};
|
|
|
+ programMap[ program.id ] = stateMap;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ let state = stateMap[ wireframe ];
|
|
|
+
|
|
|
+ if ( state === undefined ) {
|
|
|
+
|
|
|
+ state = createBindingState( createVertexArrayObject() );
|
|
|
+ stateMap[ wireframe ] = state;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return state;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function createBindingState( vao ) {
|
|
|
+
|
|
|
+ const newAttributes = [];
|
|
|
+ const enabledAttributes = [];
|
|
|
+ const attributeDivisors = [];
|
|
|
+
|
|
|
+ for ( let i = 0; i < maxVertexAttributes; i ++ ) {
|
|
|
+
|
|
|
+ newAttributes[ i ] = 0;
|
|
|
+ enabledAttributes[ i ] = 0;
|
|
|
+ attributeDivisors[ i ] = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+
|
|
|
+ // for backward compatibility on non-VAO support browser
|
|
|
+ geometry: null,
|
|
|
+ program: null,
|
|
|
+ wireframe: false,
|
|
|
+
|
|
|
+ newAttributes: newAttributes,
|
|
|
+ enabledAttributes: enabledAttributes,
|
|
|
+ attributeDivisors: attributeDivisors,
|
|
|
+ object: vao,
|
|
|
+ attributes: {}
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we sacrifice some BufferGeometry/Attribute API flexibility
|
|
|
+ // needsUpdate() and saveCache() can be much simpler. See #16287
|
|
|
+
|
|
|
+ function needsUpdate( geometry ) {
|
|
|
+
|
|
|
+ const cachedAttributes = currentState.attributes;
|
|
|
+ const geometryAttributes = geometry.attributes;
|
|
|
+
|
|
|
+ if ( Object.keys( cachedAttributes ).length !== Object.keys( geometryAttributes ).length ) return true;
|
|
|
+
|
|
|
+ for ( const key in geometryAttributes ) {
|
|
|
+
|
|
|
+ const cachedAttribute = cachedAttributes[ key ];
|
|
|
+ const geometryAttribute = geometryAttributes[ key ];
|
|
|
+
|
|
|
+ if ( cachedAttribute.attribute !== geometryAttribute ) return true;
|
|
|
+
|
|
|
+ if ( cachedAttribute.data !== geometryAttribute.data ) return true;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function saveCache( geometry ) {
|
|
|
+
|
|
|
+ const cache = {};
|
|
|
+ const attributes = geometry.attributes;
|
|
|
+
|
|
|
+ for ( const key in attributes ) {
|
|
|
+
|
|
|
+ const attribute = attributes[ key ];
|
|
|
+
|
|
|
+ const data = {};
|
|
|
+ data.attribute = attribute;
|
|
|
+
|
|
|
+ if ( attribute.data ) {
|
|
|
+
|
|
|
+ data.data = attribute.data;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ cache[ key ] = data;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ currentState.attributes = cache;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function initAttributes() {
|
|
|
+
|
|
|
+ const newAttributes = currentState.newAttributes;
|
|
|
+
|
|
|
+ for ( let i = 0, il = newAttributes.length; i < il; i ++ ) {
|
|
|
+
|
|
|
+ newAttributes[ i ] = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function enableAttribute( attribute ) {
|
|
|
+
|
|
|
+ enableAttributeAndDivisor( attribute, 0 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function enableAttributeAndDivisor( attribute, meshPerAttribute ) {
|
|
|
+
|
|
|
+ const newAttributes = currentState.newAttributes;
|
|
|
+ const enabledAttributes = currentState.enabledAttributes;
|
|
|
+ const attributeDivisors = currentState.attributeDivisors;
|
|
|
+
|
|
|
+ newAttributes[ attribute ] = 1;
|
|
|
+
|
|
|
+ if ( enabledAttributes[ attribute ] === 0 ) {
|
|
|
+
|
|
|
+ gl.enableVertexAttribArray( attribute );
|
|
|
+ enabledAttributes[ attribute ] = 1;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( attributeDivisors[ attribute ] !== meshPerAttribute ) {
|
|
|
+
|
|
|
+ const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' );
|
|
|
+
|
|
|
+ extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute );
|
|
|
+ attributeDivisors[ attribute ] = meshPerAttribute;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function disableUnusedAttributes() {
|
|
|
+
|
|
|
+ const newAttributes = currentState.newAttributes;
|
|
|
+ const enabledAttributes = currentState.enabledAttributes;
|
|
|
+
|
|
|
+ for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) {
|
|
|
+
|
|
|
+ if ( enabledAttributes[ i ] !== newAttributes[ i ] ) {
|
|
|
+
|
|
|
+ gl.disableVertexAttribArray( i );
|
|
|
+ enabledAttributes[ i ] = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function vertexAttribPointer( index, size, type, normalized, stride, offset ) {
|
|
|
+
|
|
|
+ if ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT ) ) {
|
|
|
+
|
|
|
+ gl.vertexAttribIPointer( index, size, type, normalized, stride, offset );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ gl.vertexAttribPointer( index, size, type, normalized, stride, offset );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function setupVertexAttributes( object, material, program, geometry ) {
|
|
|
+
|
|
|
+ if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) {
|
|
|
+
|
|
|
+ if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ initAttributes();
|
|
|
+
|
|
|
+ const geometryAttributes = geometry.attributes;
|
|
|
+
|
|
|
+ const programAttributes = program.getAttributes();
|
|
|
+
|
|
|
+ const materialDefaultAttributeValues = material.defaultAttributeValues;
|
|
|
+
|
|
|
+ for ( const name in programAttributes ) {
|
|
|
+
|
|
|
+ const programAttribute = programAttributes[ name ];
|
|
|
+
|
|
|
+ if ( programAttribute >= 0 ) {
|
|
|
+
|
|
|
+ const geometryAttribute = geometryAttributes[ name ];
|
|
|
+
|
|
|
+ if ( geometryAttribute !== undefined ) {
|
|
|
+
|
|
|
+ const normalized = geometryAttribute.normalized;
|
|
|
+ const size = geometryAttribute.itemSize;
|
|
|
+
|
|
|
+ const attribute = attributes.get( geometryAttribute );
|
|
|
+
|
|
|
+ // TODO Attribute may not be available on context restore
|
|
|
+
|
|
|
+ if ( attribute === undefined ) continue;
|
|
|
+
|
|
|
+ const buffer = attribute.buffer;
|
|
|
+ const type = attribute.type;
|
|
|
+ const bytesPerElement = attribute.bytesPerElement;
|
|
|
+
|
|
|
+ if ( geometryAttribute.isInterleavedBufferAttribute ) {
|
|
|
+
|
|
|
+ const data = geometryAttribute.data;
|
|
|
+ const stride = data.stride;
|
|
|
+ const offset = geometryAttribute.offset;
|
|
|
+
|
|
|
+ if ( data && data.isInstancedInterleavedBuffer ) {
|
|
|
+
|
|
|
+ enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );
|
|
|
+
|
|
|
+ if ( geometry._maxInstanceCount === undefined ) {
|
|
|
+
|
|
|
+ geometry._maxInstanceCount = data.meshPerAttribute * data.count;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ enableAttribute( programAttribute );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
|
|
|
+ vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, offset * bytesPerElement );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ if ( geometryAttribute.isInstancedBufferAttribute ) {
|
|
|
+
|
|
|
+ enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );
|
|
|
+
|
|
|
+ if ( geometry._maxInstanceCount === undefined ) {
|
|
|
+
|
|
|
+ geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ enableAttribute( programAttribute );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
|
|
|
+ vertexAttribPointer( programAttribute, size, type, normalized, 0, 0 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if ( name === 'instanceMatrix' ) {
|
|
|
+
|
|
|
+ const attribute = attributes.get( object.instanceMatrix );
|
|
|
+
|
|
|
+ // TODO Attribute may not be available on context restore
|
|
|
+
|
|
|
+ if ( attribute === undefined ) continue;
|
|
|
+
|
|
|
+ const buffer = attribute.buffer;
|
|
|
+ const type = attribute.type;
|
|
|
+
|
|
|
+ enableAttributeAndDivisor( programAttribute + 0, 1 );
|
|
|
+ enableAttributeAndDivisor( programAttribute + 1, 1 );
|
|
|
+ enableAttributeAndDivisor( programAttribute + 2, 1 );
|
|
|
+ enableAttributeAndDivisor( programAttribute + 3, 1 );
|
|
|
+
|
|
|
+ gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
|
|
|
+
|
|
|
+ gl.vertexAttribPointer( programAttribute + 0, 4, type, false, 64, 0 );
|
|
|
+ gl.vertexAttribPointer( programAttribute + 1, 4, type, false, 64, 16 );
|
|
|
+ gl.vertexAttribPointer( programAttribute + 2, 4, type, false, 64, 32 );
|
|
|
+ gl.vertexAttribPointer( programAttribute + 3, 4, type, false, 64, 48 );
|
|
|
+
|
|
|
+ } else if ( materialDefaultAttributeValues !== undefined ) {
|
|
|
+
|
|
|
+ const value = materialDefaultAttributeValues[ name ];
|
|
|
+
|
|
|
+ if ( value !== undefined ) {
|
|
|
+
|
|
|
+ switch ( value.length ) {
|
|
|
+
|
|
|
+ case 2:
|
|
|
+ gl.vertexAttrib2fv( programAttribute, value );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 3:
|
|
|
+ gl.vertexAttrib3fv( programAttribute, value );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 4:
|
|
|
+ gl.vertexAttrib4fv( programAttribute, value );
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ gl.vertexAttrib1fv( programAttribute, value );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ disableUnusedAttributes();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function dispose() {
|
|
|
+
|
|
|
+ reset();
|
|
|
+
|
|
|
+ for ( const geometryId in bindingStates ) {
|
|
|
+
|
|
|
+ const programMap = bindingStates[ geometryId ];
|
|
|
+
|
|
|
+ for ( const programId in programMap ) {
|
|
|
+
|
|
|
+ const stateMap = programMap[ programId ];
|
|
|
+
|
|
|
+ for ( const wireframe in stateMap ) {
|
|
|
+
|
|
|
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
|
|
|
+
|
|
|
+ delete stateMap[ wireframe ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ delete programMap[ programId ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ delete bindingStates[ geometryId ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function releaseStatesOfGeometry( geometry ) {
|
|
|
+
|
|
|
+ if ( bindingStates[ geometry.id ] === undefined ) return;
|
|
|
+
|
|
|
+ const programMap = bindingStates[ geometry.id ];
|
|
|
+
|
|
|
+ for ( const programId in programMap ) {
|
|
|
+
|
|
|
+ const stateMap = programMap[ programId ];
|
|
|
+
|
|
|
+ for ( const wireframe in stateMap ) {
|
|
|
+
|
|
|
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
|
|
|
+
|
|
|
+ delete stateMap[ wireframe ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ delete programMap[ programId ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ delete bindingStates[ geometry.id ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function releaseStatesOfProgram( program ) {
|
|
|
+
|
|
|
+ for ( const geometryId in bindingStates ) {
|
|
|
+
|
|
|
+ const programMap = bindingStates[ geometryId ];
|
|
|
+
|
|
|
+ if ( programMap[ program.id ] === undefined ) continue;
|
|
|
+
|
|
|
+ const stateMap = programMap[ program.id ];
|
|
|
+
|
|
|
+ for ( const wireframe in stateMap ) {
|
|
|
+
|
|
|
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
|
|
|
+
|
|
|
+ delete stateMap[ wireframe ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ delete programMap[ program.id ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function reset() {
|
|
|
+
|
|
|
+ resetDefaultState();
|
|
|
+
|
|
|
+ if ( currentState === defaultState ) return;
|
|
|
+
|
|
|
+ currentState = defaultState;
|
|
|
+ bindVertexArrayObject( currentState.object );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // for backward-compatilibity
|
|
|
+
|
|
|
+ function resetDefaultState() {
|
|
|
+
|
|
|
+ defaultState.geometry = null;
|
|
|
+ defaultState.program = null;
|
|
|
+ defaultState.wireframe = false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+
|
|
|
+ setup: setup,
|
|
|
+ reset: reset,
|
|
|
+ resetDefaultState: resetDefaultState,
|
|
|
+ dispose: dispose,
|
|
|
+ releaseStatesOfGeometry: releaseStatesOfGeometry,
|
|
|
+ releaseStatesOfProgram: releaseStatesOfProgram,
|
|
|
+
|
|
|
+ initAttributes: initAttributes,
|
|
|
+ enableAttribute: enableAttribute,
|
|
|
+ disableUnusedAttributes: disableUnusedAttributes
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+export { WebGLBindingStates };
|