Skip to content

Commit 3e9fbb8

Browse files
committed
Packing optimization; don't wait for mapAsync
1 parent 4750831 commit 3e9fbb8

2 files changed

Lines changed: 94 additions & 59 deletions

File tree

src/webgl/p5.Shader.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @requires core
77
*/
88

9+
const TypedArray = Object.getPrototypeOf(Uint8Array);
910
class Shader {
1011
constructor(renderer, vertSrc, fragSrc, options = {}) {
1112
this._renderer = renderer;
@@ -1003,18 +1004,14 @@ class Shader {
10031004
* plane(100, 100);
10041005
* }
10051006
*/
1006-
setUniform(uniformName, rawData) {
1007+
setUniform(uniformName, data) {
10071008
this.init();
10081009

10091010
const uniform = this.uniforms[uniformName];
10101011
if (!uniform) {
10111012
return;
10121013
}
10131014

1014-
const data = this._renderer._mapUniformData
1015-
? this._renderer._mapUniformData(uniform, rawData)
1016-
: rawData;
1017-
10181015
if (uniform.isArray) {
10191016
if (
10201017
uniform._cachedData &&
@@ -1027,7 +1024,7 @@ class Shader {
10271024
} else if (uniform._cachedData && uniform._cachedData === data) {
10281025
return;
10291026
} else {
1030-
if (Array.isArray(data)) {
1027+
if (Array.isArray(data) || data instanceof TypedArray) {
10311028
if (uniform._cachedData && this._renderer._arraysEqual(uniform._cachedData, data)) {
10321029
return;
10331030
}

src/webgpu/p5.RendererWebGPU.js

Lines changed: 91 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ function rendererWebGPU(p5, fn) {
4040
// Used to group draws into one big render pass
4141
this.activeRenderPass = null;
4242
this.activeRenderPassEncoder = null;
43+
this.activeShaderOptions = null;
44+
this.activeShader = null;
4345

4446
this.samplers = new Map();
4547

@@ -70,9 +72,6 @@ function rendererWebGPU(p5, fn) {
7072
// Registry to track geometries with buffer pools
7173
this._geometriesWithPools = [];
7274

73-
// Reusable Map for uniform buffer bindings to avoid GC
74-
this._uniformBuffersForBinding = new Map();
75-
7675
// Flag to track if any draws have happened that need queue submission
7776
this._hasPendingDraws = false;
7877
this._pendingCommandEncoders = [];
@@ -278,6 +277,8 @@ function rendererWebGPU(p5, fn) {
278277
this._pendingCommandEncoders.push(commandEncoder.finish());
279278
this.activeRenderPassEncoder = null;
280279
this.activeRenderPass = null;
280+
this.activeShader = null;
281+
this.activeShaderOptions = null;
281282
}
282283

283284
clear(...args) {
@@ -520,6 +521,14 @@ function rendererWebGPU(p5, fn) {
520521
}
521522
}
522523

524+
_shaderOptionsDifferent(newOptions) {
525+
if (!this.activeShaderOptions) return true;
526+
for (const key in this.activeShaderOptions) {
527+
if (this.activeShaderOptions[key] !== newOptions[key]) return true;
528+
}
529+
return false;
530+
}
531+
523532
_initShader(shader) {
524533
const device = this.device;
525534

@@ -1296,13 +1305,14 @@ function rendererWebGPU(p5, fn) {
12961305
// });
12971306
buf.offset = 0;
12981307
buf.lastOffset = 0;
1299-
this.resettingUniformBuffers.push(
1308+
// this.resettingUniformBuffers.push(
13001309
buf.buffer.mapAsync(GPUMapMode.WRITE).then(() => {
13011310
buf.data = new Float32Array(buf.buffer.getMappedRange());
13021311
buf.dataView = new DataView(buf.data.buffer);
1312+
this.uniformBufferPool.push(buf);
13031313
return buf;
13041314
})
1305-
)
1315+
// )
13061316
}
13071317
this.activeUniformBuffers = [];
13081318
this.currentUniformBuffer = undefined;
@@ -1380,7 +1390,7 @@ function rendererWebGPU(p5, fn) {
13801390
this._markGeometryBuffersForReturn(geometry);
13811391
}
13821392

1383-
this.uniformBufferPool.push(...(await Promise.all(this.resettingUniformBuffers)));
1393+
// this.uniformBufferPool.push(...(await Promise.all(this.resettingUniformBuffers)));
13841394
this.resettingUniformBuffers = [];
13851395

13861396
// Return all vertex buffers to their pools
@@ -1425,7 +1435,12 @@ function rendererWebGPU(p5, fn) {
14251435
this._beginActiveRenderPass();
14261436
const passEncoder = this.activeRenderPass;
14271437
const currentShader = this._curShader;
1428-
passEncoder.setPipeline(currentShader.getPipeline(this._shaderOptions({ mode })));
1438+
const shaderOptions = this._shaderOptions({ mode });
1439+
if (this.activeShader !== currentShader || this._shaderOptionsDifferent(shaderOptions)) {
1440+
passEncoder.setPipeline(currentShader.getPipeline(shaderOptions));
1441+
}
1442+
this.activeShader = currentShader;
1443+
this.activeShaderOptions = shaderOptions;
14291444

14301445
// Set stencil reference value for clipping
14311446
const drawTarget = this.drawTarget();
@@ -1445,8 +1460,8 @@ function rendererWebGPU(p5, fn) {
14451460
passEncoder.setVertexBuffer(location, gpuBuffer, 0);
14461461
}
14471462

1448-
// Clear and reuse the map to avoid GC
1449-
this._uniformBuffersForBinding.clear();
1463+
const uniformBuffersForBinding = {};
1464+
const uniformBufferOffsets = {};
14501465

14511466
for (const bufferGroup of currentShader._uniformBufferGroups) {
14521467
if (bufferGroup.dynamic) {
@@ -1462,7 +1477,8 @@ function rendererWebGPU(p5, fn) {
14621477
uniformBufferInfo.offset += Math.ceil(bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment;
14631478

14641479
// Make a shallow copy so that we keep track of the last offset for this uniform
1465-
this._uniformBuffersForBinding.set(bufferGroup.cacheKey, { ...uniformBufferInfo });
1480+
uniformBuffersForBinding[bufferGroup.cacheKey] = uniformBufferInfo;
1481+
uniformBufferOffsets[bufferGroup.cacheKey] = uniformBufferInfo.lastOffset;
14661482
} else {
14671483
// Bind uniforms to a binding-specific buffer, which may be cached for performance
14681484
let bufferInfo;
@@ -1492,56 +1508,60 @@ function rendererWebGPU(p5, fn) {
14921508
bufferGroup.currentBuffer = bufferInfo;
14931509
}
14941510

1495-
this._uniformBuffersForBinding.set(bufferGroup.cacheKey, bufferInfo);
1511+
uniformBuffersForBinding[bufferGroup.cacheKey] = bufferInfo;
1512+
}
1513+
}
1514+
for (const sampler of currentShader.samplers) {
1515+
const key = sampler.group + ',' + sampler.binding;
1516+
if (currentShader.buffersDirty[key]) {
1517+
currentShader._cachedBindGroup[sampler.group] = undefined;
1518+
currentShader.buffersDirty[key] = false;
14961519
}
14971520
}
14981521

14991522
// Bind sampler/texture uniforms and uniform buffers
15001523
for (const [group, entries] of currentShader._groupEntries) {
1501-
const bgEntries = entries.map(entry => {
1524+
const dynamicEntryOffsets = [];
1525+
const bgEntries = [];
1526+
let bindGroup = currentShader._cachedBindGroup[group];
1527+
for (const entry of entries) {
15021528
// Check if this is a uniform buffer binding
15031529
const uniformBufferInfo = entry.bufferGroup &&
1504-
this._uniformBuffersForBinding.get(entry.bufferGroup.cacheKey);
1530+
uniformBuffersForBinding[entry.bufferGroup.cacheKey];
15051531
if (uniformBufferInfo && entry.bufferGroup) {
1506-
return {
1532+
if (entry.bufferGroup.dynamic) {
1533+
dynamicEntryOffsets.push(uniformBufferOffsets[entry.bufferGroup.cacheKey]);
1534+
}
1535+
if (!bindGroup) {
1536+
bgEntries.push({
1537+
binding: entry.binding,
1538+
resource: entry.bufferGroup.dynamic
1539+
? {
1540+
buffer: uniformBufferInfo.uniformBuffer,
1541+
offset: 0,
1542+
size: Math.ceil(entry.bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment,
1543+
}
1544+
: { buffer: uniformBufferInfo.buffer },
1545+
});
1546+
}
1547+
} else if (!bindGroup) {
1548+
bgEntries.push({
15071549
binding: entry.binding,
1508-
resource: entry.bufferGroup.dynamic
1509-
? {
1510-
buffer: uniformBufferInfo.uniformBuffer,
1511-
offset: 0,
1512-
size: Math.ceil(entry.bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment,
1513-
}
1514-
: { buffer: uniformBufferInfo.buffer },
1515-
};
1550+
resource: entry.uniform.type === 'sampler'
1551+
? (entry.uniform.textureSource.texture || this._getEmptyTexture()).getSampler()
1552+
: (entry.uniform.texture || this._getEmptyTexture()).textureHandle.view,
1553+
});
15161554
}
1517-
1518-
const key = entry.uniform.group + ',' + entry.uniform.binding;
1519-
if (currentShader.buffersDirty[key]) {
1520-
currentShader._cachedBindGroup[group] = undefined;
1521-
currentShader.buffersDirty[key] = false;
1522-
}
1523-
1524-
return {
1525-
binding: entry.binding,
1526-
resource: entry.uniform.type === 'sampler'
1527-
? (entry.uniform.textureSource.texture || this._getEmptyTexture()).getSampler()
1528-
: (entry.uniform.texture || this._getEmptyTexture()).textureHandle.view,
1529-
};
1530-
});
1555+
}
15311556

15321557
const layout = currentShader._bindGroupLayouts[group];
1533-
let bindGroup = currentShader._cachedBindGroup[group];
15341558
if (!bindGroup) {
15351559
bindGroup = this.device.createBindGroup({
15361560
layout,
15371561
entries: bgEntries,
15381562
});
15391563
}
15401564
currentShader._cachedBindGroup[group] = bindGroup;
1541-
const dynamicEntryOffsets = entries
1542-
.map(e => e.bufferGroup && this._uniformBuffersForBinding.get(e.bufferGroup.cacheKey))
1543-
.filter(b => b?.dynamic)
1544-
.map(b => b.lastOffset);
15451565
passEncoder.setBindGroup(
15461566
group,
15471567
bindGroup,
@@ -1588,29 +1608,45 @@ function rendererWebGPU(p5, fn) {
15881608
for (const uniform of groupUniforms) {
15891609
const fullUniform = shader.uniforms[uniform.name];
15901610
if (!fullUniform || fullUniform.isSampler) continue;
1611+
const uniformData = fullUniform._mappedData;
15911612

15921613
if (fullUniform.baseType === 'u32') {
15931614
if (fullUniform.size === 4) {
1594-
dataView.setUint32(offset + fullUniform.offset, fullUniform._cachedData, true);
1615+
dataView.setUint32(offset + fullUniform.offset, uniformData, true);
15951616
} else {
1596-
const uniformData = fullUniform._cachedData;
1617+
const uniformData = uniformData;
15971618
for (let i = 0; i < uniformData.length; i++) {
15981619
dataView.setUint32(offset + fullUniform.offset + i * 4, uniformData[i], true);
15991620
}
16001621
}
16011622
} else if (fullUniform.baseType === 'i32') {
16021623
if (fullUniform.size === 4) {
1603-
dataView.setInt32(offset + fullUniform.offset, fullUniform._cachedData, true);
1624+
dataView.setInt32(offset + fullUniform.offset, uniformData, true);
16041625
} else {
1605-
const uniformData = fullUniform._cachedData;
1626+
const uniformData = uniformData;
16061627
for (let i = 0; i < uniformData.length; i++) {
16071628
dataView.setInt32(offset + fullUniform.offset + i * 4, uniformData[i], true);
16081629
}
16091630
}
1631+
} else if (fullUniform.packInPlace) {
1632+
// In-place packing for mat3: write directly to buffer with padding
1633+
const baseOffset = (offset + fullUniform.offset) / 4;
1634+
// Column 0
1635+
data[baseOffset + 0] = uniformData[0];
1636+
data[baseOffset + 1] = uniformData[1];
1637+
data[baseOffset + 2] = uniformData[2];
1638+
// Column 1
1639+
data[baseOffset + 4] = uniformData[3];
1640+
data[baseOffset + 5] = uniformData[4];
1641+
data[baseOffset + 6] = uniformData[5];
1642+
// Column 2
1643+
data[baseOffset + 8] = uniformData[6];
1644+
data[baseOffset + 9] = uniformData[7];
1645+
data[baseOffset + 10] = uniformData[8];
16101646
} else if (fullUniform.size === 4) {
1611-
data.set([fullUniform._cachedData], (offset + fullUniform.offset) / 4);
1612-
} else if (fullUniform._cachedData !== undefined) {
1613-
data.set(fullUniform._cachedData, (offset + fullUniform.offset) / 4);
1647+
data.set([uniformData], (offset + fullUniform.offset) / 4);
1648+
} else if (uniformData !== undefined) {
1649+
data.set(uniformData, (offset + fullUniform.offset) / 4);
16141650
}
16151651
}
16161652
}
@@ -1664,17 +1700,16 @@ function rendererWebGPU(p5, fn) {
16641700
const align = dim === 2 ? 8 : 16;
16651701
// Each column must be aligned
16661702
const size = Math.ceil(dim * 4 / align) * align * dim;
1703+
// For mat3, use in-place packing to avoid array allocation
16671704
const pack = dim === 3
16681705
? (data) => [
16691706
...data.slice(0, 3),
1670-
0,
16711707
...data.slice(3, 6),
1672-
0,
16731708
...data.slice(6, 9),
1674-
0
16751709
]
16761710
: undefined;
1677-
return { align, size, pack, items: dim * dim, baseType: 'f32' };
1711+
const packInPlace = dim === 3;
1712+
return { align, size, pack, packInPlace, items: dim * dim, baseType: 'f32' };
16781713
}
16791714
if (/^array<.+>$/.test(type)) {
16801715
const [, subtype, rawLength] = type.match(/^array<(.+),\s*(\d+)>/);
@@ -1711,7 +1746,7 @@ function rendererWebGPU(p5, fn) {
17111746

17121747
while ((match = elementRegex.exec(structBody)) !== null) {
17131748
const [_, location, name, type] = match;
1714-
const { size, align, pack, baseType } = baseAlignAndSize(type);
1749+
const { size, align, pack, packInPlace, baseType } = baseAlignAndSize(type);
17151750
offset = Math.ceil(offset / align) * align;
17161751
const offsetEnd = offset + size;
17171752
elements[name] = {
@@ -1723,6 +1758,7 @@ function rendererWebGPU(p5, fn) {
17231758
offset,
17241759
offsetEnd,
17251760
pack,
1761+
packInPlace,
17261762
baseType
17271763
};
17281764
index++;
@@ -1873,6 +1909,8 @@ function rendererWebGPU(p5, fn) {
18731909
if (uniform.isSampler) {
18741910
uniform.texture =
18751911
data instanceof Texture ? data : this.getTexture(data);
1912+
} else {
1913+
uniform._mappedData = this._mapUniformData(uniform, uniform._cachedData);
18761914
}
18771915
shader.buffersDirty = shader.buffersDirty || {};
18781916
shader.buffersDirty[uniform.group + ',' + uniform.binding] = true;

0 commit comments

Comments
 (0)