Skip to content

Give compute shaders access to workgroup info#8820

Draft
davepagurek wants to merge 2 commits into
dev-2.0from
compute-shader-workgroups
Draft

Give compute shaders access to workgroup info#8820
davepagurek wants to merge 2 commits into
dev-2.0from
compute-shader-workgroups

Conversation

@davepagurek
Copy link
Copy Markdown
Contributor

When I first implemented compute shaders, the API for the hook was very simple: just pass in a vec3 index.

We've been talking about what it would take to implement something like WCAG flash detection via a shader. A compute shader could possibly do this, but would be done most efficiently with some reduction of sums in workgroup scope before writing out to a final value. This is an initial draft at starting to give compute shaders some of that access in shader hooks/strands without changing the existing API -- you can still refer to index as before, but this exposes a few more properties with more info.

@p5-bot
Copy link
Copy Markdown

p5-bot Bot commented May 21, 2026

Continuous Release

CDN link

Published Packages

Commit hash: 87ca934

Previous deployments

742b259


This is an automated message.

@davepagurek
Copy link
Copy Markdown
Contributor Author

So I was testing this shader:

  countPixelsChanged = buildComputeShader({
    declarations: `
      @group(0) @binding(1)
      var prevFrame: texture_2d<f32>;
      @group(0) @binding(2)
      var prevFrame_sampler: sampler;

      @group(0) @binding(3)
      var nextFrame: texture_2d<f32>;
      @group(0) @binding(4)
      var nextFrame_sampler: sampler;

      @group(0) @binding(5)
      var<storage, read_write> pixelsChanged: array<f32>;

      var<workgroup> localCount: atomic<u32>;

      fn luminance(c: vec3<f32>) -> f32 {
        return dot(c, vec3<f32>(0.2126, 0.7152, 0.0722));
      }
    `,

    'void computeIteration': `(inputs: ComputeInputs) {
      if (inputs.localIndex == 0) {
        atomicStore(&localCount, 0u);
      }

      workgroupBarrier();

      let coord = vec2<i32>(inputs.index.xy);

      let prevColor = textureLoad(prevFrame, coord, 0).rgb;
      let nextColor = textureLoad(nextFrame, coord, 0).rgb;

      let prevLum = luminance(prevColor);
      let nextLum = luminance(nextColor);

      let delta = abs(nextLum - prevLum);

      let flashed = delta > 0.01; // TODO

      if (flashed) {
        atomicAdd(&localCount, 1u);
      }

      workgroupBarrier();

      // One thread writes one float
      if (inputs.localIndex == 0) {
        let workgroupsX =
          (uniforms.uPhysicalCount.x + 7) / 8;

        let groupIndex =
          inputs.workgroupID.x +
          inputs.workgroupID.y * workgroupsX;

        pixelsChanged[groupIndex] =
          f32(atomicLoad(&localCount));
      }
    }`
  });

This works in Firefox, but in Chrome gives:

Error while parsing WGSL: :44:7 error: 'workgroupBarrier' must only be called from uniform control flow
    workgroupBarrier();
    ^^^^^^^^^^^^^^^^

:105:3 note: called by 'HOOK_computeIteration' from 'main'
HOOK_computeIteration(inputs, globalId);
^^^^^^^^^^^^^^^^^^^^^

:87:3 note: control flow depends on possibly non-uniform value
if (physicalId >= totalIterations) {
^^

:85:81 note: builtin 'globalId' of 'main' may be non-uniform
let physicalId = globalId.x + globalId.y * (u32(uniforms.uPhysicalCount.x)) + globalId.z * (u32(uniforms.uPhysicalCount.x) * u32(uniforms.uPhysicalCount.y));
                                                                              ^^^^^^^^

...because currently before running the compute shader hook, we have a possible early return. This is because the number of threads you have will not always be EXACTLY the number of iterations of the shader you want to run, and it makes for a very simple compute shader API if we early return those few extra ones so that you don't have to worry about it in your own functions. But having that early return seems to be fundamentally incompatible with the use of barriers.

So maybe that means we need a separate hook or shader for these advanced use cases?

@davepagurek
Copy link
Copy Markdown
Contributor Author

example of the above: this works in Firefox but not Chrome https://editor.p5js.org/davepagurek/sketches/xRmwoEIcK

example of a working compute shader in both browsers due to it not using barriers: https://editor.p5js.org/davepagurek/sketches/IqdsZPqze

That second sketch doesn't need barriers due to its use of atomicAdd, but because atomics are only supported for u32 and not floats right now, the data being written to is incorrectly interpreted as a float when read back to javascript, so there's some code in that sketch manually reinterpreting it back as an integer. Need to figure out how to deal with that better, since there's not really an API currently to say what data type to use for numbers in storage buffers.

@aashu2006
Copy link
Copy Markdown

I looked at the diff, the early return guard in compute.js fires before the hook even runs, so any workgroupBarrier you put in the hook is already in non-uniform control flow as far as chrome is concerned..

maybe a separate entry point or a flag on buildComputeShader to skip the guard could work? anyone doing workgroup reductions can handle the bounds check themselves anyway, and ComputeInputs already has everything they would need for that (localIndex, workgroupID etc..)

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