| 973 | } |
| 974 | |
| 975 | _finalizeShader(shader) { |
| 976 | // Per-group buffer pools. We will pull from these when we draw multiple |
| 977 | // times using the shader in a render pass. These are per group instead of |
| 978 | // global so that we can reuse the last used buffer when uniform values |
| 979 | // don't change. |
| 980 | shader._uniformBufferGroups = []; |
| 981 | shader.buffersDirty = new Set(); |
| 982 | |
| 983 | for (const group of shader._uniformGroups) { |
| 984 | // Calculate the size needed for this group's uniforms |
| 985 | const groupUniforms = Object.values(group.uniforms); |
| 986 | const rawSize = Math.max( |
| 987 | 0, |
| 988 | ...groupUniforms.map(u => u.offsetEnd) |
| 989 | ); |
| 990 | const alignedSize = Math.ceil(rawSize / 16) * 16; |
| 991 | |
| 992 | shader._uniformBufferGroups.push({ |
| 993 | group: group.group, |
| 994 | binding: group.binding, |
| 995 | cacheKey: group.group * 1000 + group.binding, |
| 996 | varName: group.varName, |
| 997 | structType: group.structType, |
| 998 | uniforms: groupUniforms, |
| 999 | size: alignedSize, |
| 1000 | |
| 1001 | bufferPool: [], |
| 1002 | nextBufferPool: [], |
| 1003 | |
| 1004 | dynamic: groupUniforms.some(u => u.name.startsWith('uModel')), |
| 1005 | buffersInUse: new Set(), |
| 1006 | currentBuffer: null, // For caching |
| 1007 | }); |
| 1008 | } |
| 1009 | |
| 1010 | // Register this shader in our registry for pool cleanup |
| 1011 | this._shadersWithPools.push(shader); |
| 1012 | |
| 1013 | const bindGroupLayouts = new Map(); // group index -> bindGroupLayout |
| 1014 | const groupEntries = new Map(); // group index -> array of entries |
| 1015 | |
| 1016 | // Add all uniform group bindings to group 0 |
| 1017 | const structEntries = new Map(); |
| 1018 | for (const bufferGroup of shader._uniformBufferGroups) { |
| 1019 | const entries = structEntries.get(bufferGroup.group) || []; |
| 1020 | entries.push({ |
| 1021 | bufferGroup, |
| 1022 | binding: bufferGroup.binding, |
| 1023 | visibility: shader.shaderType === 'compute' |
| 1024 | ? GPUShaderStage.COMPUTE |
| 1025 | : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, |
| 1026 | buffer: { type: 'uniform', hasDynamicOffset: bufferGroup.dynamic }, |
| 1027 | }); |
| 1028 | structEntries.set(bufferGroup.group, entries); |
| 1029 | } |
| 1030 | for (const [group, entries] of structEntries.entries()) { |
| 1031 | entries.sort((a, b) => a.binding - b.binding); |
| 1032 | groupEntries.set(group, entries); |