Skip to content

Commit 0120c42

Browse files
committed
Try using dynamic mapping
1 parent bbfc6d3 commit 0120c42

2 files changed

Lines changed: 134 additions & 79 deletions

File tree

preview/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242

4343
instance = p.buildGeometry(() => p.sphere(5));
4444

45-
redFilter = p.baseFilterShader().modify(() => {
45+
/*redFilter = p.baseFilterShader().modify(() => {
4646
p.getColor((inputs, canvasContent) => {
4747
let col = p.getTexture(canvasContent, inputs.texCoord);
4848
col.g = col.r;
4949
col.b = col.r;
5050
return col;
5151
})
52-
}, { p })
52+
}, { p })*/
5353

5454
tex = p.createImage(100, 100);
5555
tex.loadPixels();

src/webgpu/p5.RendererWebGPU.js

Lines changed: 132 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ function rendererWebGPU(p5, fn) {
4242

4343
this.samplers = new Map();
4444

45+
this.uniformBufferAlignment = 256;
46+
this.uniformBufferPool = [];
47+
this.activeUniformBuffers = [];
48+
this.currentUniformBuffer = undefined;
49+
4550
// Cache for current frame's canvas texture view
4651
this.currentCanvasColorTexture = null;
4752
this.currentCanvasColorTextureView = null;
@@ -600,6 +605,7 @@ function rendererWebGPU(p5, fn) {
600605
// data: firstData,
601606
// dataView: firstDataView
602607
// }],
608+
dynamic: groupUniforms.some(u => u.name.startsWith('uModel')),
603609
buffersInUse: new Set(),
604610
currentBuffer: null, // For caching
605611
cachedData: null // For caching comparison
@@ -616,9 +622,10 @@ function rendererWebGPU(p5, fn) {
616622
const group0Entries = [];
617623
for (const bufferGroup of shader._uniformBufferGroups) {
618624
group0Entries.push({
625+
bufferGroup,
619626
binding: bufferGroup.binding,
620627
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
621-
buffer: { type: 'uniform' },
628+
buffer: { type: 'uniform', hasDynamicOffset: bufferGroup.dynamic },
622629
});
623630
}
624631
group0Entries.sort((a, b) => a.binding - b.binding);
@@ -1155,7 +1162,6 @@ function rendererWebGPU(p5, fn) {
11551162
// Uniform buffer pool management
11561163
//////////////////////////////////////////////
11571164

1158-
// TODO(dave): delete?
11591165
_getUniformBufferFromPool(bufferGroup) {
11601166
// Try to get a buffer from the pool
11611167
if (bufferGroup.bufferPool.length > 0) {
@@ -1171,7 +1177,6 @@ function rendererWebGPU(p5, fn) {
11711177
});
11721178
const newData = new Float32Array(bufferGroup.size / 4);
11731179
const newDataView = new DataView(newData.buffer);
1174-
11751180
const bufferInfo = {
11761181
buffer: newBuffer,
11771182
data: newData,
@@ -1182,6 +1187,39 @@ function rendererWebGPU(p5, fn) {
11821187
return bufferInfo;
11831188
}
11841189

1190+
_getDynamicUniformBufferFromPool(bufferGroup) {
1191+
let buffer;
1192+
if (this.currentUniformBuffer && this.currentUniformBuffer.offset + bufferGroup.size < this.currentUniformBuffer.size) {
1193+
buffer = this.currentUniformBuffer;
1194+
} else if (this.uniformBufferPool.length > 0) {
1195+
buffer = this.uniformBufferPool.pop();
1196+
this.activeUniformBuffers.push(buffer);
1197+
} else {
1198+
const size = 256 * 10 * 4;
1199+
buffer = {
1200+
dynamic: true,
1201+
lastOffset: 0,
1202+
offset: 0,
1203+
size,
1204+
buffer: this.device.createBuffer({
1205+
size: size,
1206+
usage: GPUBufferUsage.UNIFORM,
1207+
mappedAtCreation: true,
1208+
}),
1209+
}
1210+
this.activeUniformBuffers.push(buffer);
1211+
}
1212+
1213+
if (!buffer.data) {
1214+
buffer.data = new Float32Array(buffer.buffer.getMappedRange());
1215+
buffer.dataView = new DataView(buffer.data.buffer);
1216+
}
1217+
1218+
this.currentUniformBuffer = buffer;
1219+
1220+
return buffer;
1221+
}
1222+
11851223
_returnUniformBuffersToPool() {
11861224
// Return all used buffers back to their pools for all registered shaders
11871225
for (const shader of this._shadersWithPools) {
@@ -1213,9 +1251,16 @@ function rendererWebGPU(p5, fn) {
12131251
this._pendingCommandEncoders = [];
12141252
this._hasPendingDraws = false;
12151253

1254+
for (const bufferInfo of this.activeUniformBuffers) {
1255+
bufferInfo.buffer.unmap();
1256+
}
1257+
12161258
// Submit the commands
12171259
this.queue.submit(commandsToSubmit);
12181260

1261+
this.activeUniformBuffers = [];
1262+
this.currentUniformBuffer = undefined;
1263+
12191264
// Execute post-submit callbacks after GPU work completes
12201265
if (this._postSubmitCallbacks.length > 0) {
12211266
const callbacks = this._postSubmitCallbacks;
@@ -1355,83 +1400,83 @@ function rendererWebGPU(p5, fn) {
13551400
this._uniformBuffersForBinding.clear();
13561401

13571402
for (const bufferGroup of currentShader._uniformBufferGroups) {
1358-
let bufferInfo;
1359-
const dataChanged = this._hasGroupDataChanged(currentShader, bufferGroup);
1360-
1361-
if (!dataChanged && bufferGroup.currentBuffer) {
1362-
// Reuse the cached buffer - no need to pack or write
1363-
bufferInfo = bufferGroup.currentBuffer;
1364-
// Still need to track it in buffersInUse for proper cleanup
1365-
bufferGroup.buffersInUse.add(bufferInfo);
1403+
if (bufferGroup.dynamic) {
1404+
// Bind uniforms - get a buffer from the pool
1405+
const uniformBufferInfo = this._getDynamicUniformBufferFromPool(bufferGroup);
1406+
this._packUniformGroup(currentShader, bufferGroup.uniforms, uniformBufferInfo);
1407+
// this._packUniforms(currentShader, uniformBufferInfo);
1408+
uniformBufferInfo.lastOffset = uniformBufferInfo.offset;
1409+
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+
);*/
1417+
// Make a shallow copy so that we keep track of the last offset for this uniform
1418+
this._uniformBuffersForBinding.set(bufferGroup.binding, { ...uniformBufferInfo });
13661419
} else {
1367-
// Data changed - get a buffer from the pool
1368-
bufferInfo = this._getUniformBufferFromPool(bufferGroup);
1420+
let bufferInfo;
1421+
const dataChanged = this._hasGroupDataChanged(currentShader, bufferGroup);
1422+
1423+
if (!dataChanged && bufferGroup.currentBuffer) {
1424+
// Reuse the cached buffer - no need to pack or write
1425+
bufferInfo = bufferGroup.currentBuffer;
1426+
// Still need to track it in buffersInUse for proper cleanup
1427+
bufferGroup.buffersInUse.add(bufferInfo);
1428+
} else {
1429+
// Data changed - get a buffer from the pool
1430+
bufferInfo = this._getUniformBufferFromPool(bufferGroup);
13691431

1370-
// Pack and write the data
1371-
this._packUniformGroup(currentShader, bufferGroup.uniforms, bufferInfo);
1372-
this.device.queue.writeBuffer(
1373-
bufferInfo.buffer,
1374-
0,
1375-
bufferInfo.data.buffer,
1376-
bufferInfo.data.byteOffset,
1377-
bufferInfo.data.byteLength
1378-
);
1432+
// Pack and write the data
1433+
this._packUniformGroup(currentShader, bufferGroup.uniforms, bufferInfo);
1434+
this.device.queue.writeBuffer(
1435+
bufferInfo.buffer,
1436+
0,
1437+
bufferInfo.data.buffer,
1438+
bufferInfo.data.byteOffset,
1439+
bufferInfo.data.byteLength
1440+
);
13791441

1380-
currentShader.buffersDirty = currentShader.buffersDirty || {};
1381-
currentShader.buffersDirty[bufferGroup.group + ',' + bufferGroup.binding] = false;
1382-
/*for (const uniform of bufferGroup.uniforms) {
1383-
const fullUniform = currentShader.uniforms[uniform.name];
1384-
if (fullUniform) {
1385-
fullUniform.dirty = false;
1386-
}
1387-
}*/
1442+
currentShader.buffersDirty = currentShader.buffersDirty || {};
1443+
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+
}*/
13881450

1389-
// Cache this buffer and data for next frame
1390-
bufferGroup.currentBuffer = bufferInfo;
1391-
}
1451+
// Cache this buffer and data for next frame
1452+
bufferGroup.currentBuffer = bufferInfo;
1453+
}
13921454

1393-
this._uniformBuffersForBinding.set(bufferGroup.binding, bufferInfo.buffer);
1455+
this._uniformBuffersForBinding.set(bufferGroup.binding, bufferInfo);
1456+
}
13941457
}
13951458

13961459
// Bind sampler/texture uniforms and uniform buffers
13971460
for (const [group, entries] of currentShader._groupEntries) {
13981461
const bgEntries = entries.map(entry => {
13991462
// Check if this is a uniform buffer binding
1400-
const uniformBuffer = this._uniformBuffersForBinding.get(entry.binding);
1401-
if (uniformBuffer) {
1463+
const uniformBufferInfo = this._uniformBuffersForBinding.get(entry.binding);
1464+
if (uniformBufferInfo) {
14021465
return {
14031466
binding: entry.binding,
1404-
resource: { buffer: uniformBuffer },
1467+
resource: entry.bufferGroup.dynamic
1468+
? {
1469+
buffer: uniformBufferInfo.buffer,
1470+
offset: 0,
1471+
size: Math.ceil(entry.bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment,
1472+
}
1473+
: { buffer: uniformBufferInfo.buffer },
14051474
};
14061475
}
14071476

1408-
// This must be a texture/sampler entry
1409-
if (!entry.uniform) {
1410-
console.error('Entry missing uniform field:', entry, 'uniformBuffersForBinding:', this._uniformBuffersForBinding);
1411-
throw new Error(
1412-
`Bind group entry at binding ${entry.binding} has no uniform field and is not a uniform buffer!`
1413-
);
1414-
}
1415-
1416-
if (!entry.uniform.isSampler) {
1417-
console.error('Non-sampler uniform not handled:', entry.uniform);
1418-
throw new Error(
1419-
'All non-texture/sampler uniforms should be in the uniform struct!'
1420-
);
1421-
}
1422-
1423-
const texture = entry.uniform.type === 'sampler'
1424-
? entry.uniform.textureSource?.texture
1425-
: entry.uniform.texture;
1426-
1427-
if (!texture && entry.uniform.type !== 'sampler') {
1428-
console.warn(`Texture uniform ${entry.uniform.name} at binding ${entry.binding} has no texture! Using empty texture.`, {
1429-
uniform: entry.uniform,
1430-
type: entry.uniform.type,
1431-
hasTexture: !!entry.uniform.texture,
1432-
texture: entry.uniform.texture
1433-
});
1434-
}
1477+
// const texture = entry.uniform.type === 'sampler'
1478+
// ? entry.uniform.textureSource?.texture
1479+
// : entry.uniform.texture;
14351480

14361481
return {
14371482
binding: entry.binding,
@@ -1446,7 +1491,13 @@ function rendererWebGPU(p5, fn) {
14461491
layout,
14471492
entries: bgEntries,
14481493
});
1449-
passEncoder.setBindGroup(group, bindGroup);
1494+
passEncoder.setBindGroup(
1495+
group,
1496+
bindGroup,
1497+
entries.map(e => this._uniformBuffersForBinding.get(e.binding))
1498+
.filter(b => b?.dynamic)
1499+
.map(b => b.lastOffset)
1500+
);
14501501
}
14511502

14521503
if (currentShader.shaderType === "fill") {
@@ -1488,32 +1539,33 @@ function rendererWebGPU(p5, fn) {
14881539
const data = bufferInfo.data;
14891540
const dataView = bufferInfo.dataView;
14901541

1542+
const offset = bufferInfo.offset || 0;
14911543
for (const uniform of groupUniforms) {
14921544
const fullUniform = shader.uniforms[uniform.name];
14931545
if (!fullUniform || fullUniform.isSampler) continue;
14941546

14951547
if (fullUniform.baseType === 'u32') {
14961548
if (fullUniform.size === 4) {
1497-
dataView.setUint32(fullUniform.offset, fullUniform._cachedData, true);
1549+
dataView.setUint32(offset + fullUniform.offset, fullUniform._cachedData, true);
14981550
} else {
14991551
const uniformData = fullUniform._cachedData;
15001552
for (let i = 0; i < uniformData.length; i++) {
1501-
dataView.setUint32(fullUniform.offset + i * 4, uniformData[i], true);
1553+
dataView.setUint32(offset + fullUniform.offset + i * 4, uniformData[i], true);
15021554
}
15031555
}
15041556
} else if (fullUniform.baseType === 'i32') {
15051557
if (fullUniform.size === 4) {
1506-
dataView.setInt32(fullUniform.offset, fullUniform._cachedData, true);
1558+
dataView.setInt32(offset + fullUniform.offset, fullUniform._cachedData, true);
15071559
} else {
15081560
const uniformData = fullUniform._cachedData;
15091561
for (let i = 0; i < uniformData.length; i++) {
1510-
dataView.setInt32(fullUniform.offset + i * 4, uniformData[i], true);
1562+
dataView.setInt32(offset + fullUniform.offset + i * 4, uniformData[i], true);
15111563
}
15121564
}
15131565
} else if (fullUniform.size === 4) {
1514-
data.set([fullUniform._cachedData], fullUniform.offset / 4);
1566+
data.set([fullUniform._cachedData], (offset + fullUniform.offset) / 4);
15151567
} else if (fullUniform._cachedData !== undefined) {
1516-
data.set(fullUniform._cachedData, fullUniform.offset / 4);
1568+
data.set(fullUniform._cachedData, (offset + fullUniform.offset) / 4);
15171569
}
15181570
}
15191571
}
@@ -1571,42 +1623,44 @@ function rendererWebGPU(p5, fn) {
15711623
return false; // No changes detected
15721624
}
15731625

1626+
// TODO: delete
15741627
_packUniforms(shader, bufferInfo) {
15751628
const data = bufferInfo.data;
15761629
const dataView = bufferInfo.dataView;
15771630

1631+
const offset = bufferInfo.offset;
15781632
for (const name in shader.uniforms) {
15791633
const uniform = shader.uniforms[name];
15801634
if (uniform.isSampler) continue;
15811635

15821636
if (uniform.baseType === 'u32') {
15831637
if (uniform.size === 4) {
15841638
// Single u32
1585-
dataView.setUint32(uniform.offset, uniform._cachedData, true);
1639+
dataView.setUint32(offset + uniform.offset, uniform._cachedData, true);
15861640
} else {
15871641
// Vector of u32s
15881642
const uniformData = uniform._cachedData;
15891643
for (let i = 0; i < uniformData.length; i++) {
1590-
dataView.setUint32(uniform.offset + i * 4, uniformData[i], true);
1644+
dataView.setUint32(offset + uniform.offset + i * 4, uniformData[i], true);
15911645
}
15921646
}
15931647
} else if (uniform.baseType === 'i32') {
15941648
if (uniform.size === 4) {
15951649
// Single i32
1596-
dataView.setInt32(uniform.offset, uniform._cachedData, true);
1650+
dataView.setInt32(offset + uniform.offset, uniform._cachedData, true);
15971651
} else {
15981652
// Vector of i32s
15991653
const uniformData = uniform._cachedData;
16001654
for (let i = 0; i < uniformData.length; i++) {
1601-
dataView.setInt32(uniform.offset + i * 4, uniformData[i], true);
1655+
dataView.setInt32(offset + uniform.offset + i * 4, uniformData[i], true);
16021656
}
16031657
}
16041658
} else if (uniform.size === 4) {
16051659
// Single float value
1606-
data.set([uniform._cachedData], uniform.offset / 4);
1660+
data.set([uniform._cachedData], (offset + uniform.offset) / 4);
16071661
} else if (uniform._cachedData !== undefined) {
16081662
// Float array (including vec2<f32>, vec3<f32>, vec4<f32>, mat4x4<f32>)
1609-
data.set(uniform._cachedData, uniform.offset / 4);
1663+
data.set(uniform._cachedData, (offset + uniform.offset) / 4);
16101664
}
16111665
}
16121666
}
@@ -3116,6 +3170,7 @@ ${hookUniformFields}}
31163170
* Copy framebuffer content directly to WebGPU texture mip level
31173171
*/
31183172
_accumulateMipLevel(framebuffer, mipmapData, mipLevel, width, height) {
3173+
this.flushDraw();
31193174
// Copy from framebuffer texture to the mip level
31203175
const commandEncoder = this.device.createCommandEncoder();
31213176

0 commit comments

Comments
 (0)