From 2e8677acaf3295ffae5ca8a706747f257c4475df Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Thu, 1 Apr 2021 13:07:35 +0200 Subject: [PATCH 1/3] WebGL2: Added support for Uniform Buffer Objects. --- examples/files.json | 1 + examples/screenshots/webgl2_ubo.jpg | Bin 0 -> 38268 bytes examples/webgl2_ubo.html | 301 +++++++++++++++++ src/Three.js | 1 + src/core/UniformsGroup.js | 93 ++++++ src/materials/ShaderMaterial.js | 4 +- src/renderers/WebGLRenderer.js | 30 +- src/renderers/shaders/UniformsUtils.js | 14 + src/renderers/webgl/WebGLState.js | 47 +++ src/renderers/webgl/WebGLUniformsGroups.js | 372 +++++++++++++++++++++ utils/build/rollup.config.js | 2 + 11 files changed, 863 insertions(+), 2 deletions(-) create mode 100644 examples/screenshots/webgl2_ubo.jpg create mode 100644 examples/webgl2_ubo.html create mode 100644 src/core/UniformsGroup.js create mode 100644 src/renderers/webgl/WebGLUniformsGroups.js diff --git a/examples/files.json b/examples/files.json index 4fca40924db47e..0d596e0f69ba43 100644 --- a/examples/files.json +++ b/examples/files.json @@ -311,6 +311,7 @@ "webgl2_materials_texture3d_partialupdate", "webgl2_multisampled_renderbuffers", "webgl2_rendertarget_texture2darray", + "webgl2_ubo", "webgl2_volume_cloud", "webgl2_volume_instancing", "webgl2_volume_perlin" diff --git a/examples/screenshots/webgl2_ubo.jpg b/examples/screenshots/webgl2_ubo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..98c7101d4fc4655cadf067014e2ab98d9a083d40 GIT binary patch literal 38268 zcmbTd1yq~ewk{k>3k3=kr)XQCI20>RYIt$iv{0ZxDel4D3lwOvlu}$$+zIY(!CiwT zXn+td-?#TZXP9^T(KKEZuFd;)wty!%A=2?+nXZofSsB7E@I zkAHN#a}O8y9w9y+{@<_vubP_{0NH)OCC*n|95TQiG8|kooSP1uU$-*e`&+bILH}{# z+`+wj4-fxV1fqwxV&mf83VZKX(%XOf+`a?cCA&xdMBo)3g~kW`r;e0@0r6S)Szed5 zQfZDNS%u7;0tpDIX=oqOu{~qw;N%h(c_At$F7f8Atem`pqLP-jj;@}*fuZ?F3rnj{ z);7*Au5Rugo?b!0A)#U65s?Xr-;$D3zW+%5mHj&>H!r`Su)LzOs=B7OuD-3kqqD2K zr?+oxd}4A6K0PzLvbwguvAMOqgFqb}pPZhZqc1LT0J#59=eGTC`rImxbLX!y$Nx(o zoICEfH!j)Tdrt)L$X{vTe{iIDDj0B|@^ySxSt|jHkS3DK%xRR6npJp(4fU5we{1yr zPocp7Cyo9?q5sh5W)|=O7w2}uaLE8b0M>6C@biv8Az&Aak1<1$w%h>1sheMnd{d#t zW0Y%CJ9SUqKVSWD88q#03!w-LeU=#9Z2E4A*i_%E@Y7W+TpP(A3ns-6mtjDc4y%tI zW-A&gD)+3_kADO{7qAzX(*#at3d(3oVg|AdzwL`5>QAgj<#M>n&RMH=CftJ7xFL_` zWS5oc6Gi%&*)=k02h1cL$F*0!D@XbmZtktGGx9Y=!XjQ?zowc_zw2_4zpq%e;f_(me8v{^dP@ zkLML32%QfGHrFCA_2;BFwnKOI>!K^&;-9XV&*I?xF=#f!A84dQ9!Hkud@9&W4!@Kl zwn_R_G^Zj>E)$4^wF8NFoQKy$QG=8Fmj;#a)JD<4m>{0sZ!8-#8u}SB^X|c#} z77}VdO4EF1A^fKZ8yi7gF`_8jDDlOq1N=z+>EigF90LW!eW@T*hHMVSE~P=ng51(5 zEHUa!!!k3?j;RE)X{Bc3#7>TAUuOBig>&cQczQ-;9|<>C`Jz^%Ur&4S5O+Rjd?3Z7W z@%xkE*FLuETlXY86~QMmIch1okWC}haEIDGbW!^`jh9^G)a(!cv^4Gwj`PRh-bzu) z_|RpAEmo`eh!Bad$@vAUW6<36Av)ffP&nvqbFBo;L%0 z)S}WtDYVI8*-}JD>C#31EBvR!Jpd&@9p`ql{2w1U^S;ANk4_H*nID#5w0_T<5eQIW zNQ4+hDSBM7PcARtFT-_7DR_9lSk!2>uJR;=dZ$PTp_9vOT`&;&YiZJaFii>gEjnrS zYKa^zsWT=V!GGx9QYR3q`SaFgg-zG824uRE4cTs0iD0_CG$*)|@&_@O@8QW}(+?d-6jZ(RzL>~Q%W0+7 zsHjT$fIhF?m&|lrhR(FF;YaeV)Q@7gH-9M+`Q>z`<87gf>dcXqg?!cwei|J^e4{c& zXwNH2uzpbc?n7k09^Wvh_q06gS}x`Ux5`I_M6~x$r6=!;Ybuz~a0U)iQ%Wx*~u|XIsIoC_F;id_Dt36X58%RJX*8 z+f!|e63D6f7U?4o?hYp0DWf+is-Lmy4cRVsy<8vt`@DnV%n|p%Zv6P5dqy*px_&{& z@+C^e-14bx3q-4joy^4guW~;50w+uMdB2ua{aUPxq~aNP{e(@M+R_ZLUM8*LIo`H{ zP^^1pa>1wpH6$EO{Bv;%316be80BmmCg;f%<d5@WX)=I$Xoo372 zrydbC?B@_=)nJZS94De+^W_`BLv&huimqWsQ&?0b(Y9Ov6C!(oHy8t8gV-*HeX%@G z^Lc`;;?pgB;qghKWl#)=_!ruGO}w({g~9LfO*dQWIzl!=QjROdyQPj_6{LM$UKaEE z*()9T<_)ZV@+M3+auFxo5pTN%4IH>TanpxqC8HSntifeqM16m|^`}gWkojP;4cz0m z&O`FZI44O$l=FiJg=(`}Mh9``I5E*rdNuaTKxeVnjR)AruSA1=X}8G?cd|LJi~IoI zKWMH)K8IuTcuxx3ENn7zbav;m#L@?cP`Q-{;5DgZ;{iAgc|}9D$^3dELd9!`wK$Hm zdSQCj>{c4UPlC!T`WV$qMu|aDWk;@Vy2~;#suDsG0g*K&!lOh*uO^~A0`1gbcQ0ne zZC|<(yUHk^J3fMo9wp2A<3j;Pj~_6syYGTVX{8!))d(I4|NQ+cVUoQD>N7mb`5|8} zGIupqX*N3&y}ts&-)15ay>t)b*)O$dh~-$X34SKV<6%q^X}jhz$J6H^E?=1=CyK}+G%d`y*U4KjsgE`up##F{0Tso5Y}+p?VTuRaJ+}PQ*y)yH>5duDbBb=>Dl(Tet#QbpCq0VJT{a00m z-Ko19D83(_3SR!7jWNC-^37QgO1{-cJESa1e>ATJHp*`tz5!Sle7?!9sih(xK2c@~-RfSvAYd?K4t;NDb?*gopELNn87t zUHI<(JR;_7oZVf_rQYD`Q8!BSDz2(+6KDxt^r0!jxFPw2!NmFK@+m+Sr;29?(WO2jvD|y2tC(9!^_sWw-LH_P66bK zdwI{tl};mD<+U{Fw-Q~QLhALWP`YOyH7`fm6hA;oQyL%I7zPq zs>(^h%)>x&PIf+}G#8(^pOKDnBGQ+oTi!^>Dgu45bVa0DoAPe*2|at{s^068dCCX( zF@BemAvQ9-=W>+@t|FD;WWO(<2vZ@nEh>|G1X9jov{{Rar?L7tt4@Fgmq~4r-h+kstJgzW zaBLXk3p#C$y<_sT_4m8h}iaZU8j9<}lup*P*=HMdg@bo6$z^e zzo)mbccy_kzTH8?PU+^RU5!Tj-oUyUEdGo}_wp{70+e|5qaQmekqnXelxl)G_}a@; zK4fNGzE7Ba19)w3nsl`I#LT*n)<)1v^chxH`PeEgQ#CqADMNR{$ZpP=zdatUrCesvyC zimOa)bb(w@@nZ6q>xB6=87>dOV}m-G$73_UJ$@1K)*-arLB5T8kkk!V8>k(z;ey}o zQil9ZLxwau+j(i4rZ=QQ-foY#K!l&}vqcjs^@{nklse67U0 zAStxYKNr+dgME%72`RLdQASYflA3!vepc^(0iTb~ak$^k>^NTKU1zM#!u@%eLMSmY z$~%+Bv~1&6d>jNbj-L&T34=f8dK;KIKFi9nal~Bu4CLsF_Vns9y^%SzD%0 zr~iXfnhxRip)Bb+%5i}fMg#n$7U@UN-*{`6Jp9wNf!miBsh+wcv90Q^-9_X+yYFPk zLmzoZnO?u!H7hb>Zamo^S+)JW_4i1V+FgJ1`@kRNSFIN)<+#cmBvqS$#zR zEw#c*0~x1WKLvh$7PvAt^Vlk#B=`i>%?=EJf>_#>sUjV}j4IY#@bC6)c3SC2Cr1`2 z>&}kuU11s3-j1!xFipTJ`1zMPg)G^Q{lPbYde5!d=Ew~Epbm~9rN#Q|If!!LYOR&H z3Yd?Y{A@fpA*OQEX7+YZOmz#aLZdFVX#{0gcG)wIgLv9)a1r=gmwF_kz^q3iuSI

C3PL2D6O`+H*9s1ykeaeAj(SV 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 literal 0 HcmV?d00001 diff --git a/examples/webgl2_ubo.html b/examples/webgl2_ubo.html new file mode 100644 index 00000000000000..7c15ae435b1fe7 --- /dev/null +++ b/examples/webgl2_ubo.html @@ -0,0 +1,301 @@ + + + + three.js WebGL 2 - Uniform Buffer Objects + + + + + + + +

+ three.js - Uniform Buffer Objects +
+
+ + + + + + + + + + + + + + diff --git a/src/Three.js b/src/Three.js index 7e3bb3e8b29baa..6a8e629c8f9b9d 100644 --- a/src/Three.js +++ b/src/Three.js @@ -87,6 +87,7 @@ export { AnimationObjectGroup } from './animation/AnimationObjectGroup.js'; export { AnimationMixer } from './animation/AnimationMixer.js'; export { AnimationClip } from './animation/AnimationClip.js'; export { Uniform } from './core/Uniform.js'; +export { UniformsGroup } from './core/UniformsGroup.js'; export { InstancedBufferGeometry } from './core/InstancedBufferGeometry.js'; export { BufferGeometry } from './core/BufferGeometry.js'; export { InterleavedBufferAttribute } from './core/InterleavedBufferAttribute.js'; diff --git a/src/core/UniformsGroup.js b/src/core/UniformsGroup.js new file mode 100644 index 00000000000000..f2d51284941e53 --- /dev/null +++ b/src/core/UniformsGroup.js @@ -0,0 +1,93 @@ +import { EventDispatcher } from './EventDispatcher.js'; +import { StaticDrawUsage } from '../constants.js'; + +let id = 0; + +class UniformsGroup extends EventDispatcher { + + constructor() { + + super(); + + Object.defineProperty( this, 'id', { value: id ++ } ); + + this.name = ''; + + this.usage = StaticDrawUsage; + this.uniforms = []; + + } + + add( uniform ) { + + this.uniforms.push( uniform ); + + return this; + + } + + remove( uniform ) { + + const index = this.uniforms.indexOf( uniform ); + + if ( index !== - 1 ) this.uniforms.splice( index, 1 ); + + return this; + + } + + setName( name ) { + + this.name = name; + + return this; + + } + + setUsage( value ) { + + this.usage = value; + + return this; + + } + + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + return this; + + } + + copy( source ) { + + this.name = source.name; + this.usage = source.usage; + + const uniformsSource = source.uniforms; + + this.uniforms.length = 0; + + for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { + + this.uniforms.push( uniformsSource[ i ].clone() ); + + } + + return this; + + } + + clone() { + + return new this.constructor().copy( this ); + + } + +} + +UniformsGroup.prototype.isUniformsGroup = true; + + +export { UniformsGroup }; diff --git a/src/materials/ShaderMaterial.js b/src/materials/ShaderMaterial.js index 76b11359a120cf..40dbbc8b7205e6 100644 --- a/src/materials/ShaderMaterial.js +++ b/src/materials/ShaderMaterial.js @@ -1,5 +1,5 @@ import { Material } from './Material.js'; -import { cloneUniforms } from '../renderers/shaders/UniformsUtils.js'; +import { cloneUniforms, cloneUniformsGroups } from '../renderers/shaders/UniformsUtils.js'; import default_vertex from '../renderers/shaders/ShaderChunk/default_vertex.glsl.js'; import default_fragment from '../renderers/shaders/ShaderChunk/default_fragment.glsl.js'; @@ -31,6 +31,7 @@ function ShaderMaterial( parameters ) { this.defines = {}; this.uniforms = {}; + this.uniformsGroups = []; this.vertexShader = default_vertex; this.fragmentShader = default_fragment; @@ -95,6 +96,7 @@ ShaderMaterial.prototype.copy = function ( source ) { this.vertexShader = source.vertexShader; this.uniforms = cloneUniforms( source.uniforms ); + this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); this.defines = Object.assign( {}, source.defines ); diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 8444fa67b1df6c..d95a281f7d643f 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -39,6 +39,7 @@ import { WebGLUniforms } from './webgl/WebGLUniforms.js'; import { WebGLUtils } from './webgl/WebGLUtils.js'; import { WebXRManager } from './webxr/WebXRManager.js'; import { WebGLMaterials } from './webgl/WebGLMaterials.js'; +import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js'; function createCanvasElement() { @@ -261,7 +262,7 @@ function WebGLRenderer( parameters ) { let background, morphtargets, bufferRenderer, indexedBufferRenderer; - let utils, bindingStates; + let utils, bindingStates, uniformsGroups; function initGLContext() { @@ -291,6 +292,7 @@ function WebGLRenderer( parameters ) { renderStates = new WebGLRenderStates( extensions, capabilities ); background = new WebGLBackground( _this, cubemaps, state, objects, _premultipliedAlpha ); shadowMap = new WebGLShadowMap( _this, objects, capabilities ); + uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); @@ -586,6 +588,7 @@ function WebGLRenderer( parameters ) { cubemaps.dispose(); objects.dispose(); bindingStates.dispose(); + uniformsGroups.dispose(); xr.dispose(); @@ -1754,6 +1757,31 @@ function WebGLRenderer( parameters ) { p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); + // UBOs + + if ( material.isShaderMaterial || material.isRawShaderMaterial ) { + + const groups = material.uniformsGroups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + if ( capabilities.isWebGL2 ) { + + const group = groups[ i ]; + + uniformsGroups.update( group, program ); + uniformsGroups.bind( group, program ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); + + } + + } + + } + return program; } diff --git a/src/renderers/shaders/UniformsUtils.js b/src/renderers/shaders/UniformsUtils.js index f1af43b8aeb086..855e534c02e3a6 100644 --- a/src/renderers/shaders/UniformsUtils.js +++ b/src/renderers/shaders/UniformsUtils.js @@ -59,6 +59,20 @@ export function mergeUniforms( uniforms ) { } +export function cloneUniformsGroups( src ) { + + const dst = []; + + for ( let u = 0; u < src.length; u ++ ) { + + dst.push( src[ u ].clone() ); + + } + + return dst; + +} + // Legacy const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js index e3c5c8bbfc0c98..5e2246808d5db0 100644 --- a/src/renderers/webgl/WebGLState.js +++ b/src/renderers/webgl/WebGLState.js @@ -314,6 +314,9 @@ function WebGLState( gl, extensions, capabilities ) { const depthBuffer = new DepthBuffer(); const stencilBuffer = new StencilBuffer(); + const uboBindings = new WeakMap(); + const uboProgamMap = new WeakMap(); + let enabledCapabilities = {}; let xrFramebuffer = null; @@ -906,6 +909,47 @@ function WebGLState( gl, extensions, capabilities ) { } + function updateUBOMapping( uniformsGroup, program ) { + + let mapping = uboProgamMap.get( program ); + + if ( mapping === undefined ) { + + mapping = new WeakMap(); + + uboProgamMap.set( program, mapping ); + + } + + let blockIndex = mapping.get( uniformsGroup ); + + if ( blockIndex === undefined ) { + + blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); + + mapping.set( uniformsGroup, blockIndex ); + + } + + } + + function uniformBlockBinding( uniformsGroup, program ) { + + const mapping = uboProgamMap.get( program ); + const blockIndex = mapping.get( uniformsGroup ); + + if ( uboBindings.get( uniformsGroup ) !== blockIndex ) { + + // bind shader specific block index to global block point + + gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); + + uboBindings.set( uniformsGroup, blockIndex ); + + } + + } + // function reset() { @@ -1032,6 +1076,9 @@ function WebGLState( gl, extensions, capabilities ) { texImage2D: texImage2D, texImage3D: texImage3D, + updateUBOMapping: updateUBOMapping, + uniformBlockBinding: uniformBlockBinding, + scissor: scissor, viewport: viewport, diff --git a/src/renderers/webgl/WebGLUniformsGroups.js b/src/renderers/webgl/WebGLUniformsGroups.js new file mode 100644 index 00000000000000..06b8ca65c12cfb --- /dev/null +++ b/src/renderers/webgl/WebGLUniformsGroups.js @@ -0,0 +1,372 @@ +function WebGLUniformsGroups( gl, info, capabilities, state ) { + + let buffers = {}; + let updateList = {}; + let allocatedBindingPoints = []; + + const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program + + function bind( uniformsGroup, program ) { + + const webglProgram = program.program; + state.uniformBlockBinding( uniformsGroup, webglProgram ); + + } + + function update( uniformsGroup, program ) { + + let buffer = buffers[ uniformsGroup.id ]; + + if ( buffer === undefined ) { + + prepareUniformsGroup( uniformsGroup ); + + buffer = createBuffer( uniformsGroup ); + buffers[ uniformsGroup.id ] = buffer; + + uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); + + } + + // ensure to update the binding points/block indices mapping for this program + + const webglProgram = program.program; + state.updateUBOMapping( uniformsGroup, webglProgram ); + + // update UBO once per frame + + const frame = info.render.frame; + + if ( updateList[ uniformsGroup.id ] !== frame ) { + + updateBufferData( uniformsGroup ); + + updateList[ uniformsGroup.id ] = frame; + + } + + } + + function createBuffer( uniformsGroup ) { + + // the setup of an UBO is independent of a particular shader program but global + + const bindingPointIndex = allocateBindingPointIndex(); + uniformsGroup.__bindingPointIndex = bindingPointIndex; + + const buffer = gl.createBuffer(); + const size = uniformsGroup.__size; + const usage = uniformsGroup.usage; + + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); + + return buffer; + + } + + function allocateBindingPointIndex() { + + for ( let i = 0; i < maxBindingPoints; i ++ ) { + + if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { + + allocatedBindingPoints.push( i ); + return i; + + } + + } + + console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); + + return 0; + + } + + function updateBufferData( uniformsGroup ) { + + const buffer = buffers[ uniformsGroup.id ]; + const uniforms = uniformsGroup.uniforms; + const cache = uniformsGroup.__cache; + + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + + for ( let i = 0, il = uniforms.length; i < il; i ++ ) { + + const uniform = uniforms[ i ]; + + // partly update the buffer if necessary + + if ( hasUniformChanged( uniform, i, cache ) === true ) { + + const value = uniform.value; + const offset = uniform.__offset; + + if ( typeof value === 'number' ) { + + uniform.__data[ 0 ] = value; + gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); + + } else { + + if ( uniform.value.isMatrix3 ) { + + // manually converting 3x3 to 3x4 + + uniform.__data[ 0 ] = uniform.value.elements[ 0 ]; + uniform.__data[ 1 ] = uniform.value.elements[ 1 ]; + uniform.__data[ 2 ] = uniform.value.elements[ 2 ]; + uniform.__data[ 3 ] = uniform.value.elements[ 0 ]; + uniform.__data[ 4 ] = uniform.value.elements[ 3 ]; + uniform.__data[ 5 ] = uniform.value.elements[ 4 ]; + uniform.__data[ 6 ] = uniform.value.elements[ 5 ]; + uniform.__data[ 7 ] = uniform.value.elements[ 0 ]; + uniform.__data[ 8 ] = uniform.value.elements[ 6 ]; + uniform.__data[ 9 ] = uniform.value.elements[ 7 ]; + uniform.__data[ 10 ] = uniform.value.elements[ 8 ]; + uniform.__data[ 11 ] = uniform.value.elements[ 0 ]; + + } else { + + value.toArray( uniform.__data ); + + } + + gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); + + } + + } + + } + + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + + } + + function hasUniformChanged( uniform, index, cache ) { + + const value = uniform.value; + + if ( cache[ index ] === undefined ) { + + // cache entry does not exist so far + + if ( typeof value === 'number' ) { + + cache[ index ] = value; + + } else { + + cache[ index ] = value.clone(); + + } + + return true; + + } else { + + // compare current value with cached entry + + if ( typeof value === 'number' ) { + + if ( cache[ index ] !== value ) { + + cache[ index ] = value; + return true; + + } + + } else { + + const cachedObject = cache[ index ]; + + if ( cachedObject.equals( value ) === false ) { + + cachedObject.copy( value ); + return true; + + } + + } + + } + + return false; + + } + + function prepareUniformsGroup( uniformsGroup ) { + + // determine total buffer size according to the STD140 layout + // Hint: STD140 is the only supported layout in WebGL 2 + + const uniforms = uniformsGroup.uniforms; + + let offset = 0; // global buffer offset in bytes + const chunkSize = 16; // size of a chunk in bytes + let chunkOffset = 0; // offset within a single chunk in bytes + + for ( let i = 0, l = uniforms.length; i < l; i ++ ) { + + const uniform = uniforms[ i ]; + const info = getUniformSize( uniform ); + + // the following two properties will be used for partial buffer updates + + uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); + uniform.__offset = offset; + + // + + if ( i > 0 ) { + + chunkOffset = offset % chunkSize; + + const remainingSizeInChunk = chunkSize - chunkOffset; + + // check for chunk overflow + + if ( chunkOffset !== 0 && ( remainingSizeInChunk - info.boundary ) < 0 ) { + + // add padding and adjust offset + + offset += ( chunkSize - chunkOffset ); + uniform.__offset = offset; + + } + + } + + offset += info.storage; + + } + + // ensure correct final padding + + chunkOffset = offset % chunkSize; + + if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); + + // + + uniformsGroup.__size = offset; + uniformsGroup.__cache = {}; + + return this; + + } + + function getUniformSize( uniform ) { + + const value = uniform.value; + + const info = { + boundary: 0, // bytes + storage: 0 // bytes + }; + + // determine sizes according to STD140 + + if ( typeof value === 'number' ) { + + // float/int + + info.boundary = 4; + info.storage = 4; + + } else if ( value.isVector2 ) { + + // vec2 + + info.boundary = 8; + info.storage = 8; + + } else if ( value.isVector3 || value.isColor ) { + + // vec3 + + info.boundary = 16; + info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes + + } else if ( value.isVector4 ) { + + // vec4 + + info.boundary = 16; + info.storage = 16; + + } else if ( value.isMatrix3 ) { + + // mat3 (in STD140 a 3x3 matrix is represented as 3x4) + + info.boundary = 48; + info.storage = 48; + + } else if ( value.isMatrix4 ) { + + // mat4 + + info.boundary = 64; + info.storage = 64; + + } else if ( value.isTexture ) { + + console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); + + } + + return info; + + } + + function onUniformsGroupsDispose( event ) { + + const uniformsGroup = event.target; + + uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); + + const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); + allocatedBindingPoints.splice( index, 1 ); + + gl.deleteBuffer( buffers[ uniformsGroup.id ] ); + + delete buffers[ uniformsGroup.id ]; + delete updateList[ uniformsGroup.id ]; + + } + + function dispose() { + + for ( const id in buffers ) { + + gl.deleteBuffer( buffers[ id ] ); + + } + + allocatedBindingPoints = []; + buffers = {}; + updateList = {}; + + } + + return { + + bind: bind, + update: update, + + dispose: dispose + + }; + +} + + +export { WebGLUniformsGroups }; diff --git a/utils/build/rollup.config.js b/utils/build/rollup.config.js index 9fd20d91c9fdfd..6c6f4d6bc1f6e0 100644 --- a/utils/build/rollup.config.js +++ b/utils/build/rollup.config.js @@ -115,6 +115,7 @@ function glconstants() { MAX_VERTEX_ATTRIBS: 34921, MAX_TEXTURE_IMAGE_UNITS: 34930, ARRAY_BUFFER: 34962, + UNIFORM_BUFFER: 35345, ELEMENT_ARRAY_BUFFER: 34963, STATIC_DRAW: 35044, DYNAMIC_DRAW: 35048, @@ -122,6 +123,7 @@ function glconstants() { FRAGMENT_SHADER: 35632, MAX_VERTEX_TEXTURE_IMAGE_UNITS: 35660, MAX_COMBINED_TEXTURE_IMAGE_UNITS: 35661, + MAX_UNIFORM_BUFFER_BINDINGS: 35375, COMPILE_STATUS: 35713, LINK_STATUS: 35714, VALIDATE_STATUS: 35715, From 8dcaf6965e6c13026847c00eccb818ced9068383 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 7 Dec 2021 17:04:43 +0100 Subject: [PATCH 2/3] Update WebGLState.js Clean up. --- src/renderers/webgl/WebGLState.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js index c34dc6b202b26b..c594e9cebee9eb 100644 --- a/src/renderers/webgl/WebGLState.js +++ b/src/renderers/webgl/WebGLState.js @@ -1153,8 +1153,8 @@ function WebGLState( gl, extensions, capabilities ) { compressedTexImage2D: compressedTexImage2D, texImage2D: texImage2D, texImage3D: texImage3D, - - updateUBOMapping: updateUBOMapping, + + updateUBOMapping: updateUBOMapping, uniformBlockBinding: uniformBlockBinding, texStorage2D: texStorage2D, From c5b8f1b33da00dd557cad586b4c907b4608529f2 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Sun, 20 Mar 2022 10:30:29 +0100 Subject: [PATCH 3/3] Fix example. --- examples/webgl2_ubo.html | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/examples/webgl2_ubo.html b/examples/webgl2_ubo.html index 7c15ae435b1fe7..0cfd54f092ba2f 100644 --- a/examples/webgl2_ubo.html +++ b/examples/webgl2_ubo.html @@ -128,12 +128,25 @@ + + + + + + +