@webgpu-kit/core
v1.1.1
Published
### A light wrapper around the webGPU API that aims to reduce boilerplate for render and compute operations.
Downloads
6
Readme
@webgpu-kit/core
A light wrapper around the webGPU API that aims to reduce boilerplate for render and compute operations.
Table of Contents
Installation
Run the following to add the package to your project:
npm i @webgpu-kit/core
# or
yarn add @webgpu-kit/core
# or
pnpm i @webgpu-kit/core
Overview
The package exposes an API that allows for easy construction and execution of webGPU pipelines. An understanding of the webGPU spec/API is recommended but not stricly needed.
Read up on the webGPU spec here.
In @webgpu-kit/core, pipelines are intended to be shader-driven. They are generally intended also to be executed in groups of pipelines that share bind-groups (GPU resources).
The following simple example showcases how one would render a quad to a canvas:
// myShader.wgsl
struct VertexInput {
@location(0) pos: vec2<f32>,
}
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
}
fn vertexMain(inputs: VertexInput) -> VertexOutput {
return vec4(inputs.pos, 0,0, 1.0);
}
fn fragmentMain(inputs: VertexOutput) -> location(0) vec4<f32> {
return vec4(inputs.pos, 0.0, 1.0);
}
// myQuad.ts
import {
Attribute,
VertexAttributeObject,
RenderPipeline,
PipelineGroup,
Executor,
} from "@webgpu-kit/core";
import myShader from "./myShader.wgsl";
const canvas = document.querySelector(".myCanvas");
const vertices = [
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8,
-0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
];
const posAttribute = new Attribute({
label: "Position",
format: "float32x2",
shaderLocation: 0,
arrayBuffer: vertices,
itemCount: vertices.length / 2,
itemSize: 2,
});
const vao = new VertexAttributeObject({
label: "myQuad vao",
vertexCount: vertices.length / 2,
});
await vao.addAttributes(posAttribute);
const pipeline = new RenderPipeline({
label: "myQuad Render pipeline",
shader: myShader,
});
const pipelineGroup = new PipelineGroup({
label: "myQuad pipeline group",
pipelines: [pipeline],
vertexAttributeObject: vao,
canvas,
});
const executor = new Executor({
label: "myQuad Executor",
});
await executor.addPipelineGroups(pipelineGroup);
await executor.run();
Note that the shader declared the location of the position attribute, and we used that location declaration in the instatiation of our Attribute object. It is mandatory that these values match. This will also be the case for any other gpu-bound objects such as Uniforms, Textures, Samplers and Storage objects which will be covered later.
Pipelines
A pipeline is essentially a context in which either a render or a compute operation is defined. These pipelines are executed in batches (or singularly, if desired) via Pipeline Groups.
Pipelines can contain various types of state that will compose the operation to be executed:
- Shader Code
- Bind Groups composed of
To create a render pipeline you can do the following:
import { RenderPipeline } from "@webgpu-kit/core";
import myShader from "./myShader.wgsl";
const pipeline = new RenderPipeline({
label: "My Render Pipeline",
shader: myShader,
});
And similarly for a compute pipeline:
import { ComputePipeline } from "@webgpu-kit/core";
import myShader from "./myShader.wgsl";
const pipeline = new ComputePipeline({
label: "My Compute Pipeline",
shader: myShader,
});
reference the source for the full list of constructor options available
Pipeline Groups
A pipeline group is a collection of pipelines that share bind groups. This allows for an ergonmic way to execute multiple render and/or compute passes within a shared context.
To create a pipeline group you can do the following:
import { PipelineGroup, Pipeline } from "@webgpu-kit/core";
import myShader1 from "./myShader1.wgsl";
import myShader2 from "./myShader2.wgsl";
const pipeline1 = new Pipeline({
label: "My Pipeline 1",
shader: myShader1,
});
const pipeline2 = new Pipeline({
label: "My Pipeline 2",
shader: myShader2,
});
const pipelineGroup = new PipelineGroup({
label: "My Pipeline Group",
pipelines: [pipeline1, pipeline2],
});
reference the source for the full list of constructor options available
Bind Groups
Bind groups are collections of objects that are bound to the GPU. You can think of them as a way to collect all of the resources that a shader needs to execute (except for those defined in the vertex attribute object). This is one of the primary ways to pass and update data from the CPU to the GPU.
Bind groups contain the following types of objects:
Bind groups are created and used like so:
// myShader.wgsl
@group(0) @binding(0) var<uniform> vec3<f32> uColor;
@group(0) @binding(1) var<storage> vertexColors: array<vec3<f32>>;
@group(0) @binding(2) var mySampler: sampler;
@group(0) @binding(3) var myTexture: texture_2d<f32>;
struct VertexInput {
@location(0) pos: vec2<f32>,
@location(1) texCoords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
texCoords: vec2<f32>,
}
@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
let output = VertexOutput(
vec4<f32>(input.pos, 0.0, 1.0),
input.texCoords,
);
}
@fragment
fn fragmentMain(input: VertexOutput) {
return textureSample(myTexture, mySampler, input.texCoords);
}
import {
Uniform,
Storage,
Sampler,
Texture,
BindGroup,
} from "@webgpu-kit/core";
// assume we have a pipeline group already defined
import myPipelineGroup from "./myPipelineGroup";
import myShader from "./myShader.wgsl";
// note that the value of the binding property must match
// the binding location defined in the shader
const myColorUniform = new Uniform({ label: "color uniform", binding: 0 });
const myColorStorage = new Storage({ label: "color storage", binding: 1 });
const mySampler = new Sampler({ label: "my texture sampler", binding: 2 });
const myTexture = new Texture({ label: "my texture", binding: 3 });
const bindGroup = new BindGroup({
label: "my bind group",
});
await bindGroup.addUniforms(myColorUniform);
await bindGroup.addStorages(myColorStorage);
await bindGroup.addSamplers(mySampler);
await bindGroup.addTextures(myTexture);
// optionally, update the bind group on the gpu
// if you elect to omit this here then it will
// be updated when the pipeline group is executed
await bindGroup.updateBindGroup();
// add the bind group to the pipeline group
await myPipelineGroup.setBindGroups(bindGroup);
reference the source for the full list of constructor options available
Uniform Buffers
Uniforms are created and used like so:
Pay close attention to how the binding location in the shader is matched by that defined in the Uniform instantiation. This is mandatory.
import { Uniform } from "@webgpu-kit/core";
const myShader = wgsl`
@group(0) @binding(0) var<uniform> vec3<f32> uColor;
@fragment
fn fragmentMain() {
return vec4<f32>(uColor, 1.0);
}
`;
const myColorUniform = new Uniform({ label: "color uniform", binding: 0 });
// add the uniform to a bind group in order to pass it to the shader
// note that the bind group must be added to a pipeline group in order
// to be used
await myBindGroup.addUniforms(myColorUniform);
reference the source for the full list of constructor options available
Storage Buffers
Storages are created and used in the exact same fashion as Uniforms:
Pay close attention to how the binding location in the shader is matched by that defined in the Storage instantiation. This is mandatory.
import { Storage } from "@webgpu-kit/core";
const myShader = wgsl`
@group(0) @binding(0) var<storage> vertexColors: array<vec3<f32>>;
@fragment
fn fragmentMain(@builtin(vertex_index) vIndex) {
return vec4<f32>(vertexColors[vIndex], 1.0);
}
`;
const myColorStorage = new Storage({ label: "color storage", binding: 0 });
// add the storage to a bind group in order to pass it to the shader
// note that the bind group must be added to a pipeline group in order
// to be used
await myBindGroup.addStorages(myColorStorage);
reference the source for the full list of constructor options available
Samplers and Textures
Create and use samplers and textures like so:
Pay close attention to how the binding location in the shader is matched by that defined in the respective instantiations. This is mandatory.
// myShader.wgsl
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;
struct VertexOutput {
texCoords: vec2<f32>,
}
@fragment
fn fragmentMain(input: VertexOutput) {
return textureSample(myTexture, mySampler, input.texCoords);
}
import { Sampler, Texture, Pipeline } from "@webgpu-kit/core";
import myShader from "./myShader.wgsl";
import myImage from "./myImage.png";
const sampler = new Sampler({
label: "my texture sampler",
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
});
const texture = new Texture({
label: "my texture",
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
});
await texture.setFromImage(myImage);
await texture.generateMipmaps();
// add to a bind group in order to pass it to the shader
await myBindGroup.addSamplers(sampler);
await myBindGroup.addTextures(texture);
reference the Sampler source and the Texture source for the full list of constructor options available
Vertex Attribute Objects
Vertex attribute objects, or VAO's, contain per-vertex (or per-instance) data commonly referred to as attributes. The most common attribute is the position of each vertex. Each pipeline group will contain at least one VAO. The following example showcases how to use a VertexAttributeObject:
import {
Attribute,
VertexAttributeObject,
PipelineGroup,
} from "@webgpu-kit/core";
const vertices = [
// triangle 1
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8,
// triangle 2
-0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
];
const positionAttribute = new Attribute({
label: "positions",
format: "float32x2", // vec2<f32> datatype
shaderLocation: 0, // the location defined in the shader
arrayBuffer: new Float32Array(vertices), // the data
itemCount: vertices.length / 2, // 12 / 2 == 6 two-dimensional vertices
itemSize: 2, // how many elements of the input array each item is defined by
});
const vao = new VertexAttributeObject({
label: "my vao",
vertexCount: vertices.length / 2,
});
await vao.addAttributes(positionAttribute);
const pipelineGroup = new PipelineGroup({
label: "my pipeline",
});
pipelineGroup.addVertesAttributeObjects(vao);
reference the Attribute source and the VertexAttributeObject source for the full list of constructor options available