Skip to content

Commit eda2725

Browse files
committed
Clean up + comment more
1 parent d1051eb commit eda2725

2 files changed

Lines changed: 57 additions & 160 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: 55 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = /@group\((\d+)\)\s+@binding\((\d+)\)\s+var<uniform>\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 = /@group\((\d+)\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*(texture_2d<f32>|sampler);/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

Comments
 (0)