pex-gpu
v0.0.1
Published
Modern WebGPU wrapper for PEX: allocate GPU resources (textures, buffers), reflect WGSL shaders, cache pipelines, and optionally describe render and compute work as plain command objects.
Maintainers
Readme
pex-gpu
Modern WebGPU wrapper for PEX: describe render and compute work as plain command objects, backed by automatic WGSL reflection, pipeline caching, and GPU resource allocation.
[!NOTE] pex-gpu is WebGPU only and written in TypeScript. Shaders are written in WGSL and all enum-like values are raw WebGPU strings (
"rgba8unorm","triangle-list", ...). For the WebGL 1/2 version, see pex-context.
Installation
npm install pex-gpuUsage
Everything is a free function behind one import — import * as gpu from "pex-gpu" — typed, tree-shakeable, and discoverable by autocompleting gpu..
Describe draws, compute dispatches and render bundles as plain objects and gpu.submit() them inside a gpu.frame() loop. Vertex layouts, bind groups, pipelines and uniform buffers are derived from your WGSL and cached automatically: you write a shader and a command, pex-gpu does the WebGPU.
When something is slow or leaking, always-on gpu.debugStats(ctx) counters and opt-in gpu.debug(ctx) logging show resource pressure, cache behavior and the submit stream at any time.
Advanced users who need to drive GPUCommandEncoders directly can reach for the core helpers underneath — context/canvas setup, buffers, textures, samplers, readback, timestamp queries, render bundles — which every command is itself built on. Options follow WebGPU naming and derive from WebGPU descriptor types, so raw-WebGPU knowledge transfers 1:1, and hand-built pipelines/bind groups/encoders plug into declarative passes. See the API docs below. The machinery itself — WGSL reflection, uniform struct packing, the pipeline cache, allocators, byte math — is importable from "pex-gpu/internals" for the same purpose.
import * as gpu from "pex-gpu";
import { mat4 } from "pex-math";
import { cube } from "primitive-geometry";
const ctx = await gpu.createContext();
const geometry = cube();
const shader = /* wgsl */ `
struct Uniforms {
projection: mat4x4f,
view: mat4x4f,
model: mat4x4f,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexIn {
@location(0) position: vec3f,
@location(1) normal: vec3f,
}
struct VertexOut {
@builtin(position) position: vec4f,
@location(0) color: vec4f,
}
@vertex
fn vertexMain(input: VertexIn) -> VertexOut {
var output: VertexOut;
output.color = vec4f(input.normal * 0.5 + 0.5, 1.0);
output.position =
uniforms.projection * uniforms.view * uniforms.model * vec4f(input.position, 1.0);
return output;
}
@fragment
fn fragmentMain(input: VertexOut) -> @location(0) vec4f {
return input.color;
}
`;
// defineCommand is a typed identity helper: the command stays a plain object
const drawCmd = gpu.defineCommand({
// depthClearValue implies a context-managed, canvas-sized depth texture
pass: { clearValue: [0.2, 0.2, 0.2, 1], depthClearValue: 1 },
pipeline: { vertex: shader, fragment: shader, depthWriteEnabled: true, cullMode: "back" },
attributes: {
position: gpu.createBuffer(ctx, { usage: "vertex", data: geometry.positions }),
normal: gpu.createBuffer(ctx, { usage: "vertex", data: geometry.normals }),
},
indices: gpu.createBuffer(ctx, { usage: "index", data: geometry.cells }),
uniforms: {
// WebGPU clip-space depth is [0, 1]: use the ZO projection variants
projection: mat4.perspectiveZO(mat4.create(), Math.PI / 4, ctx.width / ctx.height, 0.1, 100),
view: mat4.lookAt(mat4.create(), [2, 2, 3], [0, 0, 0], [0, 1, 0]),
model: mat4.create(),
},
});
gpu.frame(ctx, () => {
gpu.submit(ctx, drawCmd);
});Every WebGPU feature has a dedicated example: MRT, MSAA resolve, compute + storage buffers, indirect draws, async picking, timestamp queries and render bundles — including hand-built pipelines and bind groups replayed inside a declarative pass (examples/render-bundles.js).
API
License
MIT. See license file.
