@@ -37,13 +37,18 @@ function rendererWebGPU(p5, fn) {
3737 constructor ( pInst , w , h , isMainCanvas , elt ) {
3838 super ( pInst , w , h , isMainCanvas , elt )
3939
40+ // Used to group draws into one big render pass
4041 this . activeRenderPass = null ;
4142 this . activeRenderPassEncoder = null ;
4243
4344 this . samplers = new Map ( ) ;
4445
46+ // Some uniforms update every frame, like model matrices and sometimes colors.
47+ // The fastest way to handle these is to use mapped memory. We'll batch those
48+ // into bigger buffers with dynamic offsets, separate from the usual system
49+ // where bind groups have their own little buffers that get cached when they
50+ // are unchanged
4551 this . uniformBufferAlignment = 256 ;
46- this . uniformBufferPool = [ ] ;
4752 this . activeUniformBuffers = [ ] ;
4853 this . currentUniformBuffer = undefined ;
4954
@@ -567,7 +572,10 @@ function rendererWebGPU(p5, fn) {
567572 }
568573
569574 _finalizeShader ( shader ) {
570- // Create per-group buffer pools instead of a single pool
575+ // Per-group buffer pools. We will pull from these when we draw multiple
576+ // times using the shader in a render pass. These are per group instead of
577+ // global so that we can reuse the last used buffer when uniform values
578+ // don't change.
571579 shader . _uniformBufferGroups = [ ] ;
572580
573581 for ( const group of shader . _uniformGroups ) {
@@ -579,36 +587,20 @@ function rendererWebGPU(p5, fn) {
579587 ) ;
580588 const alignedSize = Math . ceil ( rawSize / 16 ) * 16 ;
581589
582- // Create staging data arrays for this group
583- // const groupData = new Float32Array(alignedSize / 4);
584- // const groupDataView = new DataView(groupData.buffer);
585-
586- // // Create GPU buffer pool for this group
587- // const firstGPUBuffer = this.device.createBuffer({
588- // size: alignedSize,
589- // usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
590- // });
591- // const firstData = new Float32Array(alignedSize / 4);
592- // const firstDataView = new DataView(firstData.buffer);
593-
594590 shader . _uniformBufferGroups . push ( {
595591 group : group . group ,
596592 binding : group . binding ,
597593 varName : group . varName ,
598594 structType : group . structType ,
599595 uniforms : groupUniforms ,
600596 size : alignedSize ,
597+
601598 bufferPool : [ ] ,
602599 nextBufferPool : [ ] ,
603- // bufferPool: [{
604- // buffer: firstGPUBuffer,
605- // data: firstData,
606- // dataView: firstDataView
607- // }],
600+
608601 dynamic : groupUniforms . some ( u => u . name . startsWith ( 'uModel' ) ) ,
609602 buffersInUse : new Set ( ) ,
610- currentBuffer : null , // For caching
611- cachedData : null // For caching comparison
603+ currentBuffer : null , // For caching
612604 } ) ;
613605 }
614606
@@ -1188,14 +1180,22 @@ function rendererWebGPU(p5, fn) {
11881180 }
11891181
11901182 _getDynamicUniformBufferFromPool ( bufferGroup ) {
1183+ //
11911184 let buffer ;
1192- if ( this . currentUniformBuffer && this . currentUniformBuffer . offset + bufferGroup . size < this . currentUniformBuffer . size ) {
1185+ if (
1186+ this . currentUniformBuffer &&
1187+ this . currentUniformBuffer . offset + bufferGroup . size < this . currentUniformBuffer . size
1188+ ) {
1189+ // We can fit this next block of uniforms into the current active memory chunk
11931190 buffer = this . currentUniformBuffer ;
1194- } else if ( this . uniformBufferPool . length > 0 ) {
1195- buffer = this . uniformBufferPool . pop ( ) ;
1196- this . activeUniformBuffers . push ( buffer ) ;
11971191 } else {
1198- const size = 256 * 10 * 4 ;
1192+ // Kinda arbitrary. Each dynamic offset has to be in groups of 256, but then
1193+ // we can choose how many things we want to be able to fit into a block.
1194+ // There's some overhead to each block so if we're drawing a lot of stuff,
1195+ // bigger is better. But it's also a lot of wasted memory if we AREN'T drawing
1196+ // a lot of stuff. So.... right now it's 40. Feel free to update this if
1197+ // a better balance can be achieved.
1198+ const size = 256 * 40 ;
11991199 buffer = {
12001200 dynamic : true ,
12011201 lastOffset : 0 ,
@@ -1207,12 +1207,11 @@ function rendererWebGPU(p5, fn) {
12071207 mappedAtCreation : true ,
12081208 } ) ,
12091209 }
1210- this . activeUniformBuffers . push ( buffer ) ;
1211- }
12121210
1213- if ( ! buffer . data ) {
12141211 buffer . data = new Float32Array ( buffer . buffer . getMappedRange ( ) ) ;
12151212 buffer . dataView = new DataView ( buffer . data . buffer ) ;
1213+
1214+ this . activeUniformBuffers . push ( buffer ) ;
12161215 }
12171216
12181217 this . currentUniformBuffer = buffer ;
@@ -1236,7 +1235,7 @@ function rendererWebGPU(p5, fn) {
12361235 for ( const bufferInfo of bufferGroup . buffersInUse . keys ( ) ) {
12371236 bufferGroup . nextBufferPool . push ( bufferInfo ) ;
12381237 }
1239- // bufferGroup.currentBuffer = null;
1238+ bufferGroup . currentBuffer = null ;
12401239 bufferGroup . buffersInUse . clear ( ) ;
12411240 }
12421241 }
@@ -1401,35 +1400,27 @@ function rendererWebGPU(p5, fn) {
14011400
14021401 for ( const bufferGroup of currentShader . _uniformBufferGroups ) {
14031402 if ( bufferGroup . dynamic ) {
1404- // Bind uniforms - get a buffer from the pool
1403+ // Bind uniforms into a part of a big dynamic memory block because
1404+ // the group changes often
14051405 const uniformBufferInfo = this . _getDynamicUniformBufferFromPool ( bufferGroup ) ;
14061406 this . _packUniformGroup ( currentShader , bufferGroup . uniforms , uniformBufferInfo ) ;
1407- // this._packUniforms(currentShader, uniformBufferInfo);
14081407 uniformBufferInfo . lastOffset = uniformBufferInfo . offset ;
14091408 uniformBufferInfo . offset += Math . ceil ( bufferGroup . size / this . uniformBufferAlignment ) * this . uniformBufferAlignment ;
1410- /*this.device.queue.writeBuffer(
1411- uniformBufferInfo.buffer,
1412- 0,
1413- uniformBufferInfo.data.buffer,
1414- uniformBufferInfo.data.byteOffset,
1415- uniformBufferInfo.data.byteLength
1416- );*/
1409+
14171410 // Make a shallow copy so that we keep track of the last offset for this uniform
14181411 this . _uniformBuffersForBinding . set ( bufferGroup . binding , { ...uniformBufferInfo } ) ;
14191412 } else {
1413+ // Bind uniforms to a binding-specific buffer, which may be cached for performance
14201414 let bufferInfo ;
14211415 const dataChanged = this . _hasGroupDataChanged ( currentShader , bufferGroup ) ;
14221416
14231417 if ( ! dataChanged && bufferGroup . currentBuffer ) {
14241418 // Reuse the cached buffer - no need to pack or write
14251419 bufferInfo = bufferGroup . currentBuffer ;
1426- // Still need to track it in buffersInUse for proper cleanup
14271420 bufferGroup . buffersInUse . add ( bufferInfo ) ;
14281421 } else {
1429- // Data changed - get a buffer from the pool
1422+ // Data changed - get a new buffer and write to it
14301423 bufferInfo = this . _getUniformBufferFromPool ( bufferGroup ) ;
1431-
1432- // Pack and write the data
14331424 this . _packUniformGroup ( currentShader , bufferGroup . uniforms , bufferInfo ) ;
14341425 this . device . queue . writeBuffer (
14351426 bufferInfo . buffer ,
@@ -1441,12 +1432,6 @@ function rendererWebGPU(p5, fn) {
14411432
14421433 currentShader . buffersDirty = currentShader . buffersDirty || { } ;
14431434 currentShader . buffersDirty [ bufferGroup . group + ',' + bufferGroup . binding ] = false ;
1444- /*for (const uniform of bufferGroup.uniforms) {
1445- const fullUniform = currentShader.uniforms[uniform.name];
1446- if (fullUniform) {
1447- fullUniform.dirty = false;
1448- }
1449- }*/
14501435
14511436 // Cache this buffer and data for next frame
14521437 bufferGroup . currentBuffer = bufferInfo ;
@@ -1474,10 +1459,6 @@ function rendererWebGPU(p5, fn) {
14741459 } ;
14751460 }
14761461
1477- // const texture = entry.uniform.type === 'sampler'
1478- // ? entry.uniform.textureSource?.texture
1479- // : entry.uniform.texture;
1480-
14811462 return {
14821463 binding : entry . binding ,
14831464 resource : entry . uniform . type === 'sampler'
@@ -1524,10 +1505,6 @@ function rendererWebGPU(p5, fn) {
15241505
15251506 // Mark that we have pending draws that need submission
15261507 this . _hasPendingDraws = true ;
1527-
1528- /*if (this._pendingCommandEncoders.length > 50) {
1529- this.flushDraw();
1530- }*/
15311508 }
15321509
15331510 //////////////////////////////////////////////
@@ -1574,95 +1551,6 @@ function rendererWebGPU(p5, fn) {
15741551 // First time
15751552 if ( ! bufferGroup . currentBuffer ) return true ;
15761553 return shader . buffersDirty ?. [ bufferGroup . group + ',' + bufferGroup . binding ] ;
1577- const cachedData = bufferGroup . currentBuffer . data ;
1578- const cachedDataView = bufferGroup . currentBuffer . dataView ;
1579-
1580- for ( const uniform of bufferGroup . uniforms ) {
1581- const fullUniform = shader . uniforms [ uniform . name ] ;
1582- if ( ! fullUniform || fullUniform . isSampler ) continue ;
1583- if ( fullUniform . dirty ) return true ;
1584- // continue;
1585-
1586- // Compare typed arrays bytewise
1587- const currentData = fullUniform . _cachedData ;
1588- const cachedOffset = fullUniform . offset ;
1589-
1590- // Note: intentionally using == instead of === below
1591- if ( fullUniform . baseType === 'u32' || fullUniform . baseType === 'i32' ) {
1592- if ( fullUniform . size === 4 ) {
1593- // Single value
1594- if ( cachedDataView . getUint32 ( cachedOffset , true ) != currentData ) {
1595- return true ;
1596- }
1597- } else {
1598- // Array
1599- for ( let i = 0 ; i < currentData . length ; i ++ ) {
1600- if ( cachedDataView . getUint32 ( cachedOffset + i * 4 , true ) != currentData [ i ] ) {
1601- return true ;
1602- }
1603- }
1604- }
1605- } else {
1606- if ( fullUniform . size === 4 ) {
1607- // Single float
1608- if ( cachedData [ cachedOffset / 4 ] != currentData ) {
1609- return true ;
1610- }
1611- } else if ( currentData !== undefined ) {
1612- // Float array
1613- const floatOffset = cachedOffset / 4 ;
1614- for ( let i = 0 ; i < currentData . length ; i ++ ) {
1615- if ( cachedData [ floatOffset + i ] != currentData [ i ] ) {
1616- return true ;
1617- }
1618- }
1619- }
1620- }
1621- }
1622-
1623- return false ; // No changes detected
1624- }
1625-
1626- // TODO: delete
1627- _packUniforms ( shader , bufferInfo ) {
1628- const data = bufferInfo . data ;
1629- const dataView = bufferInfo . dataView ;
1630-
1631- const offset = bufferInfo . offset ;
1632- for ( const name in shader . uniforms ) {
1633- const uniform = shader . uniforms [ name ] ;
1634- if ( uniform . isSampler ) continue ;
1635-
1636- if ( uniform . baseType === 'u32' ) {
1637- if ( uniform . size === 4 ) {
1638- // Single u32
1639- dataView . setUint32 ( offset + uniform . offset , uniform . _cachedData , true ) ;
1640- } else {
1641- // Vector of u32s
1642- const uniformData = uniform . _cachedData ;
1643- for ( let i = 0 ; i < uniformData . length ; i ++ ) {
1644- dataView . setUint32 ( offset + uniform . offset + i * 4 , uniformData [ i ] , true ) ;
1645- }
1646- }
1647- } else if ( uniform . baseType === 'i32' ) {
1648- if ( uniform . size === 4 ) {
1649- // Single i32
1650- dataView . setInt32 ( offset + uniform . offset , uniform . _cachedData , true ) ;
1651- } else {
1652- // Vector of i32s
1653- const uniformData = uniform . _cachedData ;
1654- for ( let i = 0 ; i < uniformData . length ; i ++ ) {
1655- dataView . setInt32 ( offset + uniform . offset + i * 4 , uniformData [ i ] , true ) ;
1656- }
1657- }
1658- } else if ( uniform . size === 4 ) {
1659- // Single float value
1660- data . set ( [ uniform . _cachedData ] , ( offset + uniform . offset ) / 4 ) ;
1661- } else if ( uniform . _cachedData !== undefined ) {
1662- // Float array (including vec2<f32>, vec3<f32>, vec4<f32>, mat4x4<f32>)
1663- data . set ( uniform . _cachedData , ( offset + uniform . offset ) / 4 ) ;
1664- }
1665- }
16661554 }
16671555
16681556 _parseStruct ( shaderSource , structName ) {
@@ -1792,8 +1680,12 @@ function rendererWebGPU(p5, fn) {
17921680 }
17931681
17941682 getUniformMetadata ( shader ) {
1795- // Parse all uniform struct bindings in group 0
1796- // Each binding represents a logical group of uniforms
1683+ // Parse all uniform struct bindings in group 0.
1684+ // TODO: support non-sampler uniforms being in other groups
1685+
1686+ // Each binding represents a logical group of uniforms, since they get
1687+ // updated or cached all at once.
1688+
17971689 const uniformGroups = [ ] ;
17981690 const uniformVarRegex = / @ g r o u p \( ( \d + ) \) \s + @ b i n d i n g \( ( \d + ) \) \s + v a r < u n i f o r m > \s + ( \w + ) \s * : \s * ( \w + ) ; / g;
17991691
@@ -1816,20 +1708,21 @@ function rendererWebGPU(p5, fn) {
18161708 throw new Error ( 'Expected at least one uniform struct bound to @group(0)' ) ;
18171709 }
18181710
1819- // Flatten all uniforms for backward compatibility, but keep track of their groups
1711+ // While we're also keeping track of the groups, the API we expose
1712+ // to users of p5 is just a flat list of uniforms (which can be the
1713+ // individual struct items in the group.)
18201714 const allUniforms = { } ;
18211715 for ( const group of uniformGroups ) {
18221716 for ( const [ uniformName , uniformData ] of Object . entries ( group . uniforms ) ) {
18231717 allUniforms [ uniformName ] = {
18241718 ...uniformData ,
1825- group : 0 ,
1719+ group : group . group ,
18261720 binding : group . binding ,
18271721 varName : group . varName
18281722 } ;
18291723 }
18301724 }
18311725
1832- const uniforms = allUniforms ;
18331726 // Store uniform groups for buffer pooling
18341727 shader . _uniformGroups = uniformGroups ;
18351728
@@ -1838,8 +1731,12 @@ function rendererWebGPU(p5, fn) {
18381731 // TODO: support other texture types
18391732 const samplerRegex = / @ g r o u p \( ( \d + ) \) \s * @ b i n d i n g \( ( \d + ) \) \s * v a r \s + ( \w + ) \s * : \s * ( t e x t u r e _ 2 d < f 3 2 > | s a m p l e r ) ; / g;
18401733
1841- // Track which bindings in group 0 are taken by uniforms
1842- const group0UniformBindings = new Set ( uniformGroups . map ( g => g . binding ) ) ;
1734+ // Track which bindings are taken by the struct properties we've parsed
1735+ // (the rest should be textures/samplers)
1736+ const structUniformBindings = { } ;
1737+ for ( const g of uniformGroups ) {
1738+ structUniformBindings [ g . group + ',' + g . binding ] = true ;
1739+ }
18431740
18441741 for ( const [ src , visibility ] of [
18451742 [ shader . vertSrc ( ) , GPUShaderStage . VERTEX ] ,
@@ -1850,8 +1747,8 @@ function rendererWebGPU(p5, fn) {
18501747 const [ _ , group , binding , name , type ] = match ;
18511748 const groupIndex = parseInt ( group ) ;
18521749 const bindingIndex = parseInt ( binding ) ;
1853- // Skip uniform bindings in group 0 which we've already parsed
1854- if ( groupIndex === 0 && group0UniformBindings . has ( bindingIndex ) ) continue ;
1750+ // Skip struct uniform bindings which we've already parsed
1751+ if ( structUniformBindings [ groupIndex + ',' + bindingIndex ] ) continue ;
18551752
18561753 const key = `${ groupIndex } ,${ bindingIndex } ` ;
18571754 samplers [ key ] = {
@@ -1880,7 +1777,7 @@ function rendererWebGPU(p5, fn) {
18801777 }
18811778 }
18821779 }
1883- return [ ...Object . values ( uniforms ) . sort ( ( a , b ) => a . index - b . index ) , ...Object . values ( samplers ) ] ;
1780+ return [ ...Object . values ( allUniforms ) . sort ( ( a , b ) => a . index - b . index ) , ...Object . values ( samplers ) ] ;
18841781 }
18851782
18861783 getNextBindingIndex ( { vert, frag } , group = 0 ) {
@@ -2241,8 +2138,8 @@ function rendererWebGPU(p5, fn) {
22412138
22422139 if ( hookUniformFields ) {
22432140 // Find the next available binding in group 0
2244- // Use the source we're currently building (preMain) so we can see texture bindings
2245- // added by strands, and use the original source for the other shader type
2141+ // Use the source we're currently building (preMain) which has texture bindings. We can't call `fragSrc()`
2142+ // or `vertSrc()` because we may be in one of those calls already, and might infinite loop
22462143 const nextBinding = this . getNextBindingIndex ( {
22472144 vert : shaderType === 'vertex' ? preMain + ( shader . hooks . vertex ?. declarations ?? '' ) + shader . hooks . declarations : shader . _vertSrc ,
22482145 frag : shaderType === 'fragment' ? preMain + ( shader . hooks . fragment ?. declarations ?? '' ) + shader . hooks . declarations : shader . _fragSrc ,
0 commit comments