Skip to content

Conversation

@davepagurek
Copy link
Contributor

@davepagurek davepagurek commented Oct 21, 2025

This refactors RendererGL into two classes, Renderer3D, and a subclass RendererGL. It also introduces a new RendererWebGPU that extends Renderer3D as well.

No WebGPU code is bound by default. Currently it can be tested just by importing its addon module and setting it up, e.g. this test sketch using textures and framebuffers:

WebGPU test sketch source code
import p5 from '../src/app.js';
import rendererWebGPU from '../src/webgpu/p5.RendererWebGPU.js';

p5.registerAddon(rendererWebGPU);

const sketch = function (p) {
  let fbo;
  let sh;
  let tex;

  p.setup = async function () {
    await p.createCanvas(400, 400, p.WEBGPU);
    fbo = p.createFramebuffer();

    tex = p.createImage(100, 100);
    tex.loadPixels();
    for (let x = 0; x < tex.width; x++) {
      for (let y = 0; y < tex.height; y++) {
        const off = (x + y * tex.width) * 4;
        tex.pixels[off] = p.round((x / tex.width) * 255);
        tex.pixels[off + 1] = p.round((y / tex.height) * 255);
        tex.pixels[off + 2] = 0;
        tex.pixels[off + 3] = 255;
      }
    }
    tex.updatePixels();
    fbo.draw(() => {
      p.imageMode(p.CENTER);
      p.image(tex, 0, 0, p.width, p.height);
    });

    sh = p.baseMaterialShader().modify({
      uniforms: {
        'f32 time': () => p.millis(),
      },
      'Vertex getWorldInputs': `(inputs: Vertex) {
        var result = inputs;
        result.position.y += 40.0 * sin(uniforms.time * 0.005);
        return result;
      }`,
    })
  };

  p.draw = function () {
    p.orbitControl();
    const t = p.millis() * 0.002;
    p.background(200);
    p.shader(sh);
    p.ambientLight(150);
    p.directionalLight(100, 100, 100, 0, 1, -1);
    p.pointLight(155, 155, 155, 0, -200, 500);
    p.specularMaterial(255);
    p.shininess(300);
    p.noStroke();
    for (const [i, c] of ['red', 'lime', 'blue'].entries()) {
      p.push();
      p.fill(c);
      p.translate(
        p.width/3 * p.sin(t + i * Math.E),
        0,
        p.width/3 * p.sin(t * 1.2 + i * Math.E + 0.3),
      )
      p.texture(fbo)
      p.sphere(30);
      p.pop();
    }
  };
};

new p5(sketch);

Notes

  • Tests do not currently run on CI, only locally! In a separate PR (Run WebGPU tests on self-hosted runner davepagurek/p5.js#2) I've separated out the tests that need a GPU to run and have them running on a self-hosted runner. That PR can also be adapted to run those tests on a runner on e.g. Azure if we need too. For now though, they only will be run manually on your own computer.
  • In WEBGPU mode, you need to await createCanvas(w, h, WEBGPU)
  • In WebGPU mode, you have to await loadPixels() and await get()
  • The WebGPU implementation is actually pretty usable but is not fully complete. Remaining features include:
    • imageLight()
    • filter shaders
    • font rendering
    • p5.strands (currently shader hooks work, but only written in WGSL, not in js)
    • clipping
  • The current goal is just feature parity. We will definitely want to then optimize performance more.

@davepagurek
Copy link
Contributor Author

Actually I'll have to give this a tad more thought, since it needs more than just the webgpu renderer -- it also needs basically everything else included in the webgl export other than its renderer, e.g. primitives3D:

p5.js/src/webgl/index.js

Lines 19 to 37 in aa73cea

export default function(p5){
rendererGL(p5, p5.prototype);
primitives3D(p5, p5.prototype);
interaction(p5, p5.prototype);
light(p5, p5.prototype);
loading(p5, p5.prototype);
material(p5, p5.prototype);
text(p5, p5.prototype);
renderBuffer(p5, p5.prototype);
quat(p5, p5.prototype);
matrix(p5, p5.prototype);
geometry(p5, p5.prototype);
camera(p5, p5.prototype);
framebuffer(p5, p5.prototype);
dataArray(p5, p5.prototype);
shader(p5, p5.prototype);
texture(p5, p5.prototype);
strands(p5, p5.prototype);
}

If I make a similar function for WebGPU that sets up all of those, it'd probably work but would double-add those addons if both WebGPU and WebGL are added I think? Do you think that's OK or should we add some way to detect when an addon function has already been called? It'd have to pay attention to what's passed in because p5.Graphics would possibly also need to call the same function but with different parameters.

@limzykenneth
Copy link
Member

limzykenneth commented Oct 22, 2025

It would be nice if the call to p5.registerAddon could be idempotent (I don't know for sure if it already is or not, nor if it is a reasonable expectation?) so repeated call to it with the same function (as in same reference) would be the same as only calling it once.

The main thing I'm currently thinking may be a problem is the decoration feature which if the same addon is registered multiple times then it will also create duplicate decoration. The other thing is that since the addon function will be called multiple times, would it affect some of the internal initialization of the addon if that is the case?

Calling p5.registerAddon with multiple different copies (instead of same reference) of the same function might be even harder to make idempotent I think.

@davepagurek
Copy link
Contributor Author

@limzykenneth I made an update to make p5.registerAddon stop early if it's been passed in the same function instance. It's not bulletproof but is maybe good enough for now. We can look into this more as we explore modular builds -- this same-instance deduplication is probably good enough if we're creating a new unified build with different modules, but not sufficient if we're creating a bunch of independent single-module files that users then add script tags for.

@davepagurek davepagurek marked this pull request as ready for review November 16, 2025 22:05
@davepagurek
Copy link
Contributor Author

Update: added a WGSL backend for p5.strands!

This silly little demo is running this shader:

baseMaterialShader().modify(() => {
  const time = uniformFloat(() => millis())
  p.getWorldInputs((inputs) => {
    inputs.position.y += 40 * sin(time * 0.005);
    return inputs;
  });
})
Screen.Recording.2025-11-22.at.7.29.16.PM.mov

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.

6 participants