Skip to content

Infer storage buffer element type from the typed array passed in - #9083

Open
aashu2006 wants to merge 4 commits into
processing:mainfrom
aashu2006:storage-element-types
Open

Infer storage buffer element type from the typed array passed in#9083
aashu2006 wants to merge 4 commits into
processing:mainfrom
aashu2006:storage-element-types

Conversation

@aashu2006

Copy link
Copy Markdown
Member

Changes:

First piece of the compute shader work in #8820, which I'm picking up from @davepagurek.

Storage buffers currently assume every element is an f32. WGSL only supports atomics on u32 and i32, so a shader declaring array<atomic<u32>> writes ints that come back to JS as meaningless floats. The atomic sketch in #8820
has to reinterpret the bytes by hand because of this.

StorageBuffer now carries an _arrayType, inferred from the typed array passed to createStorage, and read() / update() use it instead of hardcoding Float32Array. Plain JS arrays and createStorage(count) still give Float32Array, so existing sketches are unaffected.

The storage regex also needed widening to match array<atomic<u32>>, and it now keeps the element type it captures. That gets used for a friendly error when the JS typed array doesn't match what the shader declares, so a Float32Array bound to an array<atomic<u32>> says so instead of silently reading back garbage. Checked from setUniform and from the bind group path, warning once per buffer so it doesn't spam a draw loop.

Reference docs for read(), update() and createStorage() were updated, including a second example showing the atomic u32 case. Note that @param types are validated at runtime against globals, so these list Float32Array|Uint32Array|Int32Array rather than an abstract TypedArray.

Testing:

Verified in Chrome on WebGPU:

  • array<atomic<u32>> seeded with a Uint32Array reads back as integers with no reinterpreting
  • f32 buffers from both Float32Array and plain arrays unchanged, including update()
  • struct buffers still forced to Float32Array, no spurious warning
  • mismatch case with setUniform inside draw() warned exactly once across 300 frames

Unit tests for the mismatch check are in test/unit/webgpu-storage-element-type.js, kept outside test/unit/webgpu/
so the unit-tests project picks them up. Happy to move them if there's a better home.

Next from #8820 is controlFlow: 'automatic' | 'manual' plus the workgroup builtins, which can be opened separately once this lands.

PR Checklist

@p5-bot

p5-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is looking good! Just added a bit of feedback around performance and docs.

Comment thread src/webgpu/p5.RendererWebGPU.js Outdated
} else if (shader._storageBuffers) {
// The shader has been parsed, so we know what element type it
// declares for this buffer and can check it early
const parsedStorage = shader._storageBuffers.find(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we can turn into an object instead of an array so that we can look it up without looping over all the buffers? probably not a huge deal if you don't have too many of them, but it feels like something we should make a habit of optimizing since there may be many of these per frame

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I've added shader._storageBuffersByName alongside the array, built once at parse time, so setUniform does a keyed lookup instead of scanning. Kept the array too since a couple of other spots iterate it every frame.

Comment thread src/webgpu/p5.RendererWebGPU.js Outdated
if (storageBuffer._schema !== null) return;

// atomic<u32> and friends store their underlying type
const elementType = parsedStorage.elementType.replace(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we'd also be calling this every time right? Is this something we can cache?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it was. Cached in two places now: the WGSL -> typed array resolution happens once at parse time (expectedArrayType), and the buffer remembers which type it was last checked against, so repeat calls exit on a comparison. The warning still only fires once.

* }
* ```
*
* ```js example

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fairly advanced example so we should probably add some text above it explaining why you'd use it this way. The pitch is probably something like, while we're developing p5.strands, you might want to reach for features in wgsl that we haven't added yet, and here's how you might use atomics in compute shaders?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added an intro along those lines - while p5.strands is still growing you may want WGSL features it doesn't cover yet, with atomics as the example since WGSL only allows them on u32/i32.

Comment thread src/webgpu/p5.RendererWebGPU.js Outdated
* await createCanvas(100, 100, WEBGPU);
*
* data = createStorage(new Uint32Array([10, 20, 30, 40]));
* computeShader = baseComputeShader().modify({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can still pass an object into buildComputeShader(...) same as baseComputeShader().modify(...), so we can simplify a little bit

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, simplified to buildComputeShader({...})

// Copy before unmapping because mapped memory becomes invalid after unmap
const rawCopy = new Float32Array(mappedRange.byteLength / 4);
rawCopy.set(new Float32Array(mappedRange));
const ArrayType = this._arrayType;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@aashu2006
aashu2006 force-pushed the storage-element-types branch from 867ed7e to e0a49c3 Compare August 20, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants