(compute-pipeline …)
Creates a compute pipeline for general-purpose GPU computation.
Syntax
Section titled “Syntax”(compute-pipeline :name pipelineName :layout auto (compute :module shaderName :entry main))A compute pipeline states its stage the way a
(render-pipeline …) states its
vertex and fragment stages: one positional (compute …) child, the same
GPUProgrammableStage shape, naming the module and its @compute entry point.
Keys & Sub-forms
Section titled “Keys & Sub-forms”| Key / Sub-form | Type | Required | Default | Description |
|---|---|---|---|---|
:name |
symbol | Yes | - | Pipeline name |
:layout |
auto or reference |
Yes | - | auto, or a declared (pipeline-layout …) |
(compute …) |
sub-form | Yes | - | Compute stage (exactly one) |
:layout
Section titled “:layout”:layout is one required key, mirroring GPUPipelineDescriptorBase.layout,
which is one required member. auto derives the bind-group layouts from the
WGSL; the alternative is the name of an explicit
(pipeline-layout …). A value that is
neither is a union_no_branch_matched validation error, and no form may be
named auto, which is a builtin spelling.
(compute …)
Section titled “(compute …)”| Key / Sub-form | Type | Required | Description |
|---|---|---|---|
:module |
reference | Yes | The (shader-module …) holding the @compute entry |
:entry |
symbol | No | Entry function name; defaults to the module’s sole @compute fn |
(constant …) |
sub-form | No | WGSL override values for this stage |
:module is resolved as a cross-reference. :entry is optional: a module with
exactly one @compute function is used without naming it, while zero or several
is a located compile error (shader module 'code' declares 2 compute entry points, so pngine cannot infer which one this stage uses).
A (constant …) child specialises a WGSL override with a compile-time value,
taking a required :name (the override identifier) and :value (a literal, an
expression over (define …) constants, or a bare define name). Specialising
@workgroup_size(WG) this way is worth doing: with WG resolved to a number,
the dispatch-grid and device-limit checks run instead of being skipped.
(shader-module :name computeShader :code """ @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) {}""")
(compute-pipeline :name simPipeline :layout auto (compute :module computeShader :entry main))Examples
Section titled “Examples”Basic Compute Pipeline
Section titled “Basic Compute Pipeline”(shader-module :name computeShader :code """ @group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) { data[id.x] = data[id.x] * 2.0; }""")
(buffer :name data :size 4096 :usage [storage copy-src])
(compute-pipeline :name doublePipeline :layout auto (compute :module computeShader :entry main))
(bind-group :name dataGroup :layout doublePipeline :group 0 (entry :binding 0 :buffer data))
(compute-pass :name double :pipeline doublePipeline :bind-groups [dataGroup] (dispatch :workgroups [16 1 1]))Particle Simulation
Section titled “Particle Simulation”(shader-module :name particleSim :code """ struct Particle { pos: vec2f, vel: vec2f, }
@group(0) @binding(0) var<storage, read> particlesA: array<Particle>; @group(0) @binding(1) var<storage, read_write> particlesB: array<Particle>; @group(0) @binding(2) var<uniform> params: vec4f;
@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) { let i = id.x; let dt = params.x;
var p = particlesA[i]; p.pos = p.pos + p.vel * dt;
// Wrap around p.pos = fract(p.pos);
particlesB[i] = p; }""")
(buffer :name particlesA :size 65536 :usage [storage])(buffer :name particlesB :size 65536 :usage [storage])(buffer :name params :size 16 :usage [uniform copy-dst])
(compute-pipeline :name simPipeline :layout auto (compute :module particleSim :entry main))
(bind-group :name simGroup :layout simPipeline :group 0 (entry :binding 0 :buffer particlesA) (entry :binding 1 :buffer particlesB) (entry :binding 2 :buffer params))Every buffer binding the WGSL declares must be bound by a
(bind-group …); one that is not is a
located compile error naming the @group/@binding pair.
Image Processing
Section titled “Image Processing”(shader-module :name blur :code """ @group(0) @binding(0) var inputTex: texture_2d<f32>; @group(0) @binding(1) var outputTex: texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) id: vec3u) { let dims = textureDimensions(inputTex); if (id.x >= dims.x || id.y >= dims.y) { return; }
var sum = vec4f(0); for (var dy: i32 = -1; dy <= 1; dy++) { for (var dx: i32 = -1; dx <= 1; dx++) { let coord = vec2i(id.xy) + vec2i(dx, dy); sum += textureLoad(inputTex, clamp(coord, vec2i(0), vec2i(dims) - 1), 0); } } textureStore(outputTex, id.xy, sum / 9.0); }""")
(compute-pipeline :name blurPipeline :layout auto (compute :module blur :entry main))With Explicit Layout
Section titled “With Explicit Layout”Name a (pipeline-layout …) as
:layout to give the pipeline an explicit layout composed of
(bind-group-layout …) forms,
rather than auto-deriving it from the shader. Bind groups then take their
per-group layout from the pipeline (:layout simPipeline :group N) or from a
bind-group layout directly (:layout bgl).
(bind-group-layout :name bgl (entry :binding 0 :visibility [compute] (buffer :type read-only-storage)) (entry :binding 1 :visibility [compute] (buffer :type storage)))
(pipeline-layout :name pl :bind-group-layouts [bgl])
(compute-pipeline :name simPipeline :layout pl (compute :module particleSim :entry main))Workgroup Sizes
Section titled “Workgroup Sizes”The shader defines workgroup size with @workgroup_size(x, y, z):
@compute @workgroup_size(64) // 1D: 64 threads@compute @workgroup_size(8, 8) // 2D: 8x8 = 64 threads@compute @workgroup_size(4, 4, 4) // 3D: 4x4x4 = 64 threadsHow many workgroups run is a pass-level command:
(compute-pass …) takes a
(dispatch :workgroups [x y z]) child, or a
(dispatch-indirect :buffer b :offset o) one when the counts live in a buffer.
A compute pass with no command at all is a positional_missing error.
Validation Rules
Section titled “Validation Rules”| Rule | Error |
|---|---|
:layout is required |
missing_required_key |
:layout must be auto or a declared (pipeline-layout …) |
union_no_branch_matched |
Exactly one (compute …) sub-form |
positional_missing / positional_too_many |
(compute …) requires :module |
missing_required_key |
| Referenced module must exist | not_cross_ref |
| Two pipelines may not share a name | duplicate_cross_ref_target |
:module and :entry are keys of the (compute …) child, not of the pipeline:
writing them on the form itself is unknown_key. A name is one namespace across
every form kind, so a pipeline reusing a buffer’s or texture’s name is a
compiler diagnostic with no code: duplicate name 'x': already declared as a (buffer …) at line N. An :entry naming a function the module does not declare
is a compiler diagnostic too (entry point 'nope' not found in shader module 'code').
WebGPU Mapping
Section titled “WebGPU Mapping”Maps to GPUComputePipeline via:
device.createComputePipeline({ layout: 'auto', compute: { module: shaderModule, entryPoint: 'main' }});Related
Section titled “Related”(shader-module …)- Shader modules(compute-pass …)- Execute compute pipelines(buffer …)- Storage buffers(bind-group …)- Resource bindings(pipeline-layout …)- Explicit layout named by:layout