WebGLUniformsGroups.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. function WebGLUniformsGroups( gl, info, capabilities, state ) {
  2. let buffers = {};
  3. let updateList = {};
  4. let allocatedBindingPoints = [];
  5. const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program
  6. function bind( uniformsGroup, program ) {
  7. const webglProgram = program.program;
  8. state.uniformBlockBinding( uniformsGroup, webglProgram );
  9. }
  10. function update( uniformsGroup, program ) {
  11. let buffer = buffers[ uniformsGroup.id ];
  12. if ( buffer === undefined ) {
  13. prepareUniformsGroup( uniformsGroup );
  14. buffer = createBuffer( uniformsGroup );
  15. buffers[ uniformsGroup.id ] = buffer;
  16. uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose );
  17. }
  18. // ensure to update the binding points/block indices mapping for this program
  19. const webglProgram = program.program;
  20. state.updateUBOMapping( uniformsGroup, webglProgram );
  21. // update UBO once per frame
  22. const frame = info.render.frame;
  23. if ( updateList[ uniformsGroup.id ] !== frame ) {
  24. updateBufferData( uniformsGroup );
  25. updateList[ uniformsGroup.id ] = frame;
  26. }
  27. }
  28. function createBuffer( uniformsGroup ) {
  29. // the setup of an UBO is independent of a particular shader program but global
  30. const bindingPointIndex = allocateBindingPointIndex();
  31. uniformsGroup.__bindingPointIndex = bindingPointIndex;
  32. const buffer = gl.createBuffer();
  33. const size = uniformsGroup.__size;
  34. const usage = uniformsGroup.usage;
  35. gl.bindBuffer( gl.UNIFORM_BUFFER, buffer );
  36. gl.bufferData( gl.UNIFORM_BUFFER, size, usage );
  37. gl.bindBuffer( gl.UNIFORM_BUFFER, null );
  38. gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer );
  39. return buffer;
  40. }
  41. function allocateBindingPointIndex() {
  42. for ( let i = 0; i < maxBindingPoints; i ++ ) {
  43. if ( allocatedBindingPoints.indexOf( i ) === - 1 ) {
  44. allocatedBindingPoints.push( i );
  45. return i;
  46. }
  47. }
  48. console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' );
  49. return 0;
  50. }
  51. function updateBufferData( uniformsGroup ) {
  52. const buffer = buffers[ uniformsGroup.id ];
  53. const uniforms = uniformsGroup.uniforms;
  54. const cache = uniformsGroup.__cache;
  55. gl.bindBuffer( gl.UNIFORM_BUFFER, buffer );
  56. for ( let i = 0, il = uniforms.length; i < il; i ++ ) {
  57. const uniform = uniforms[ i ];
  58. // partly update the buffer if necessary
  59. if ( hasUniformChanged( uniform, i, cache ) === true ) {
  60. const offset = uniform.__offset;
  61. const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ];
  62. let arrayOffset = 0;
  63. for ( let i = 0; i < values.length; i ++ ) {
  64. const value = values[ i ];
  65. const info = getUniformSize( value );
  66. if ( typeof value === 'number' || typeof value === 'boolean' ) {
  67. uniform.__data[ 0 ] = value;
  68. gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data );
  69. } else if ( value.isMatrix3 ) {
  70. // manually converting 3x3 to 3x4
  71. uniform.__data[ 0 ] = value.elements[ 0 ];
  72. uniform.__data[ 1 ] = value.elements[ 1 ];
  73. uniform.__data[ 2 ] = value.elements[ 2 ];
  74. uniform.__data[ 3 ] = 0;
  75. uniform.__data[ 4 ] = value.elements[ 3 ];
  76. uniform.__data[ 5 ] = value.elements[ 4 ];
  77. uniform.__data[ 6 ] = value.elements[ 5 ];
  78. uniform.__data[ 7 ] = 0;
  79. uniform.__data[ 8 ] = value.elements[ 6 ];
  80. uniform.__data[ 9 ] = value.elements[ 7 ];
  81. uniform.__data[ 10 ] = value.elements[ 8 ];
  82. uniform.__data[ 11 ] = 0;
  83. } else {
  84. value.toArray( uniform.__data, arrayOffset );
  85. arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT;
  86. }
  87. }
  88. gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data );
  89. }
  90. }
  91. gl.bindBuffer( gl.UNIFORM_BUFFER, null );
  92. }
  93. function hasUniformChanged( uniform, index, cache ) {
  94. const value = uniform.value;
  95. if ( cache[ index ] === undefined ) {
  96. // cache entry does not exist so far
  97. if ( typeof value === 'number' || typeof value === 'boolean' ) {
  98. cache[ index ] = value;
  99. } else {
  100. const values = Array.isArray( value ) ? value : [ value ];
  101. const tempValues = [];
  102. for ( let i = 0; i < values.length; i ++ ) {
  103. tempValues.push( values[ i ].clone() );
  104. }
  105. cache[ index ] = tempValues;
  106. }
  107. return true;
  108. } else {
  109. // compare current value with cached entry
  110. if ( typeof value === 'number' || typeof value === 'boolean' ) {
  111. if ( cache[ index ] !== value ) {
  112. cache[ index ] = value;
  113. return true;
  114. }
  115. } else {
  116. const cachedObjects = Array.isArray( cache[ index ] ) ? cache[ index ] : [ cache[ index ] ];
  117. const values = Array.isArray( value ) ? value : [ value ];
  118. for ( let i = 0; i < cachedObjects.length; i ++ ) {
  119. const cachedObject = cachedObjects[ i ];
  120. if ( typeof cachedObject === 'number' || typeof cachedObject === 'boolean' ) {
  121. if ( cachedObject !== values[ i ] ) {
  122. cachedObjects[ i ] = values[ i ];
  123. return true;
  124. }
  125. } else if ( cachedObject.equals( values[ i ] ) === false ) {
  126. cachedObject.copy( values[ i ] );
  127. return true;
  128. }
  129. }
  130. }
  131. }
  132. return false;
  133. }
  134. function prepareUniformsGroup( uniformsGroup ) {
  135. // determine total buffer size according to the STD140 layout
  136. // Hint: STD140 is the only supported layout in WebGL 2
  137. const uniforms = uniformsGroup.uniforms;
  138. let offset = 0; // global buffer offset in bytes
  139. const chunkSize = 16; // size of a chunk in bytes
  140. let chunkOffset = 0; // offset within a single chunk in bytes
  141. for ( let i = 0, l = uniforms.length; i < l; i ++ ) {
  142. const uniform = uniforms[ i ];
  143. const infos = {
  144. boundary: 0, // bytes
  145. storage: 0 // bytes
  146. };
  147. const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ];
  148. for ( let j = 0, jl = values.length; j < jl; j ++ ) {
  149. const value = values[ j ];
  150. const info = getUniformSize( value );
  151. infos.boundary += info.boundary;
  152. infos.storage += info.storage;
  153. }
  154. // the following two properties will be used for partial buffer updates
  155. uniform.__data = new Float32Array( infos.storage / Float32Array.BYTES_PER_ELEMENT );
  156. uniform.__offset = offset;
  157. //
  158. if ( i > 0 ) {
  159. chunkOffset = offset % chunkSize;
  160. const remainingSizeInChunk = chunkSize - chunkOffset;
  161. // check for chunk overflow
  162. if ( chunkOffset !== 0 && ( remainingSizeInChunk - infos.boundary ) < 0 ) {
  163. // add padding and adjust offset
  164. offset += ( chunkSize - chunkOffset );
  165. uniform.__offset = offset;
  166. }
  167. }
  168. offset += infos.storage;
  169. }
  170. // ensure correct final padding
  171. chunkOffset = offset % chunkSize;
  172. if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset );
  173. //
  174. uniformsGroup.__size = offset;
  175. uniformsGroup.__cache = {};
  176. return this;
  177. }
  178. function getUniformSize( value ) {
  179. const info = {
  180. boundary: 0, // bytes
  181. storage: 0 // bytes
  182. };
  183. // determine sizes according to STD140
  184. if ( typeof value === 'number' || typeof value === 'boolean' ) {
  185. // float/int/bool
  186. info.boundary = 4;
  187. info.storage = 4;
  188. } else if ( value.isVector2 ) {
  189. // vec2
  190. info.boundary = 8;
  191. info.storage = 8;
  192. } else if ( value.isVector3 || value.isColor ) {
  193. // vec3
  194. info.boundary = 16;
  195. info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes
  196. } else if ( value.isVector4 ) {
  197. // vec4
  198. info.boundary = 16;
  199. info.storage = 16;
  200. } else if ( value.isMatrix3 ) {
  201. // mat3 (in STD140 a 3x3 matrix is represented as 3x4)
  202. info.boundary = 48;
  203. info.storage = 48;
  204. } else if ( value.isMatrix4 ) {
  205. // mat4
  206. info.boundary = 64;
  207. info.storage = 64;
  208. } else if ( value.isTexture ) {
  209. console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' );
  210. } else {
  211. console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value );
  212. }
  213. return info;
  214. }
  215. function onUniformsGroupsDispose( event ) {
  216. const uniformsGroup = event.target;
  217. uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose );
  218. const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex );
  219. allocatedBindingPoints.splice( index, 1 );
  220. gl.deleteBuffer( buffers[ uniformsGroup.id ] );
  221. delete buffers[ uniformsGroup.id ];
  222. delete updateList[ uniformsGroup.id ];
  223. }
  224. function dispose() {
  225. for ( const id in buffers ) {
  226. gl.deleteBuffer( buffers[ id ] );
  227. }
  228. allocatedBindingPoints = [];
  229. buffers = {};
  230. updateList = {};
  231. }
  232. return {
  233. bind: bind,
  234. update: update,
  235. dispose: dispose
  236. };
  237. }
  238. export { WebGLUniformsGroups };