npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mini-gpu

v0.3.1

Published

A mini library for working with WebGPU

Downloads

9

Readme

Mini GPU

A 🤏 helping hand for working with WebGPU

⚠️ Disclaimer! ⚠️

As WebGPU is a brand new API, and this library is very much a work in progress (as well as my first library), you can expect breaking changes. Only a fool would think of using this thing in a production setting for the time being, but it's always fun to play around with new toys!

It has only been tested in Chrome Canary, and you need to enable the WebGPU flag.

(I'm also not finished with writing these docs, so beware...)

Motivation

This mini library is a side project which has come out of my own experiments with WebGPU at Google Arts & Culture Lab, and a desire to come up with a good structure for writing WebGPU code. I'm basically attempting to simplify a few common use cases, and to abstract some of the boilerplate code you need to write to get something to happen.

It certainly doesn't (and doesn't aim to) do it all, and if you're after something highly configurable, you'll probably want to write it yourself, but if you're just looking for somewhere to get started, then come on in.

Usage

Install the library by running

npm i mini-gpu

Import the bits you need

import {
  Renderer,
  ...
} from "mini-gpu";

How it works

Mini GPU supports both render pipelines and compute pipelines, and they follow a similar structure.

  1. Get access to your machines GPU device.
  2. Create a Renderer or Computer to run your programs.
  3. Create one or multiple program inputs. There are currently two supported input types: UniformsInput (which creates a set of... wait for it... uniforms) and PingPongBufferInput (which creates a swappable pingpong buffer for doing GPGPU).
  4. For Render pipelines, create a Geometry by passing in indices, positions, normals and texcoords (I use the awesome twgl.js for this).
  5. Create a RenderProgram or a ComputeProgram with your WGSL shader code and your program inputs.
  6. Run the programs with the Renderer or Computer.
  7. Be amazed!!! (Or maybe slightly underwhelmed).

Extras

WGSL Header

Because mini GPU sets up your bind groups and their order for you, you might need to get a hint as to what to put in the header of your WGSL code. You can do this by calling

myComputeOrRenderProgram.getWgslChunk();

Bare in mind that this is still WIP, so the output might need some tweaking (the types might not be right all the time, but the locations should be.)

Camera

Sets up matrices required for a simple perspective camera, which can be passed into a program as uniforms:

const camera = new Camera({
  fov: DEG_TO_RAD * 50,
  aspectRatio: renderer.width / renderer.height,
  near: 0.01,
  far: 999,
  position: [0, 0, -5],
});

const uniforms = new UniformsInput(device as GPUDevice, {
  u_view_projection_matrix: camera.viewProjectionMatrix,
});

You can then update properties such as aspectRatio, fov, near, far, position, target and up and the matrices will be updated. You will then need to update the uniforms: uniforms.member.u_view_projection_matrix = camera.viewProjectionMatrix;

Camera Orbit Controls

Creates orbit controls for a passed camera:

const controls = new OrbitControls(camera, canvas);

You need to update the controls on each frame, and then update the uniforms with the new viewProjecttionMatrix of the camera:

controls.update();
uniforms.member.u_view_projection_matrix = camera.viewProjectionMatrix;

Texture Loader

Allows you to load and create a WebGPU texture from a number of different sources.

// create the loader
const loader = new TextureLoader(device);

// create a texture from a canvas (or ImageBitmap)
const texture = loader.createTextureFromImageBitmapOrCanvas(myCanvasOrOffscreenCanvasOrImageBitmap);

// create a texture from a URL
const texture = async loader.loadTextureFromImageSrc('my/image.png');

You can also pass in an array of six elements to each method to create a cube texture.

Structured Array

This class helps to created padded, structured array data which can be used as data for a BufferInput or PingPongBufferInput, and is also used internally when creating UniformsInput.

It takes a Javascript object (or a function which returns an object) and the number of array elements you require (it defaults to a single element), and creates a magically padded Float32Array. It also allows you to get and set struct members by their name and array index.

For example:

const particleCount = 1000;

// Pass an object in the constructor
const data = new StructuredFloat32Array(
  {
    position: [0, 0, 0],
    velocity: [0, 0, 0],
    randomSeed: () => Math.random(), // you can also use a function which returns an array or float
  },
  particleCount
);

// OR

// Pass a function which returns an object in the constructor
const data = new StructuredFloat32Array(
  (index) => ({
    position: [index, index, index],
    velocity: [0, 0, 0],
    randomSeed: () => Math.random(), // you can also use a function which returns an array or float
  }),
  particleCount
);

const simulation = new PingPongBufferInput(device, data);

You can then access and update members by name and array index, for example:

// gets the position of particle at index 10
const particlePosition = data.getValueAt('position', 10);

// sets the position of particle at index 20
data.setValueAt('position', [1,2,3], 20)

Class reference

The Class reference can be found here

Examples

Render something

render.wgsl

struct Uniforms {
  u_elapsed_time : f32,
}
@group(0) @binding(0) var<uniform> uniforms : Uniforms;

struct VertexInput {
  @location(0) position : vec4<f32>,
}

struct VertexOutput {
  @builtin(position) position : vec4<f32>,
}

@vertex
fn vertex_main(vert : VertexInput) -> VertexOutput {
  var output : VertexOutput;
  output.position = vert.position;
  return output;
}

@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
  let r = cos(uniforms.u_elapsed_time) / 2 + 0.5;
  let g = sin(uniforms.u_elapsed_time * 0.5) / 2 + 0.5;
  let b = cos(uniforms.u_elapsed_time * 0.25) / 2 + 0.5;
  return vec4<f32>(r,g,b,1);
}

main.ts

import {
  Renderer,
  RenderProgram,
  Geometry,
  UniformsInput,
  Helpers,
} from "../../src";

// I'm using twgl to create my geometries...
import { primitives } from "twgl.js";

import renderShader from "./render.wgsl?raw";
import { TGeometryArgs } from "../../src/core/Geometry";

const canvas = document.querySelector("canvas") as HTMLCanvasElement;

let renderer: Renderer;
let uniforms: UniformsInput;

const animate = () => {
  uniforms.member.u_elapsed_time = uniforms.member.u_elapsed_time + 0.01;
  renderer.renderAll();
  requestAnimationFrame(animate);
};

const init = async () => {
  const device = (await Helpers.requestWebGPU()) as GPUDevice;
  renderer = new Renderer(device as GPUDevice, canvas);

  // Using twgl.js here to create my geometry attributes
  const geometry = new Geometry(
    renderer,
    primitives.createSphereVertices(1, 64, 32) as TGeometryArgs
  );

  uniforms = new UniformsInput(device as GPUDevice, {
    u_elapsed_time: 0,
  });

  const renderProgram = new RenderProgram(
    renderer,
    renderShader,
    geometry,
    {
      uniforms,
    },
    true // wireframe
  );
  renderer.add(renderProgram);

  requestAnimationFrame(animate);
  window.addEventListener("resize", () => renderer.resize());
};

init();

Compute something

compute.wgsl

struct Item {
  value: f32,
}

@group(0) @binding(0) var<storage, read> input : array<Item>;
@group(0) @binding(1) var<storage, read_write> output : array<Item>;


@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
  let count = arrayLength(&output);
  let index = global_id.x * (global_id.y + 1) * (global_id.z + 1);

  if (index >= count) {
    return;
  }

  var current_state = input[index];
  let next_state: ptr<storage, Item, read_write> = &output[index];
  (*next_state) = current_state;

  (*next_state).value = (*next_state).value + 0.0001;
}

main.ts

import {
  Computer,
  ComputeProgram,
  UniformsInput,
  PingPongBufferInput,
  Helpers,
} from "mini-gpu";

import computeShader from "./compute.wgsl?raw";

let computer: Computer;
let uniforms: UniformsInput, pingPong: PingPongBufferInput;

const readPingPong = async () => {
  const data = await pingPong.read();
  if (!data) return;
  console.log(data);
};

const animate = () => {
  computer.runAll();
  pingPong.step();
  uniforms.member.u_elapsed_time = uniforms.member.u_elapsed_time + 0.01;
  readPingPong();
  requestAnimationFrame(animate);
};

const init = async () => {
  const device = (await Helpers.requestWebGPU()) as GPUDevice;
  computer = new Computer(device as GPUDevice);

  uniforms = new UniformsInput(device as GPUDevice, {
    u_elapsed_time: 0,
  });
  pingPong = new PingPongBufferInput(
    device as GPUDevice,
    new Float32Array([0, 1, 2, 3, 4])
  );

  const computeProgram = new ComputeProgram(
    device,
    computeShader,
    {
      values: pingPong,
    },
    pingPong.length,
    64
  );
  computer.add(computeProgram);

  requestAnimationFrame(animate);
};

init();

Compute something then render something

compute.wgsl

struct Uniforms {
  u_elapsed_time : f32,
}
@group(0) @binding(0) var<uniform> uniforms : Uniforms;

struct Color {
  r: f32,
  g: f32,
  b: f32,
}

@group(1) @binding(0) var<storage, read> input : array<Color>;
@group(1) @binding(1) var<storage, read_write> output : array<Color>;


@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
  let count = arrayLength(&output);
  let index = global_id.x * (global_id.y + 1) * (global_id.z + 1);

  if (index >= count) {
    return;
  }

  var current_state = input[index];
  let next_state: ptr<storage, Color, read_write> = &output[index];
  (*next_state) = current_state;

  let r = cos(uniforms.u_elapsed_time) / 2 + 0.5;
  let g = sin(uniforms.u_elapsed_time * 0.5) / 2 + 0.5;
  let b = cos(uniforms.u_elapsed_time * 0.25) / 2 + 0.5;

  (*next_state).r = r;
  (*next_state).g = g;
  (*next_state).b = b;
}

render.wgsl

struct Color {
  r: f32,
  g: f32,
  b: f32,
}
@group(0) @binding(0) var<storage, read> input : array<Color>;

struct VertexInput {
  @location(0) position : vec4<f32>,
}

struct VertexOutput {
  @builtin(position) position : vec4<f32>,
}

@vertex
fn vertex_main(vert : VertexInput) -> VertexOutput {
  var output : VertexOutput;
  output.position = vert.position;
  return output;
}

@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
  let r = input[0].r;
  let g = input[0].g;
  let b = input[0].b;
  return vec4<f32>(r,g,b,1);
}

main.ts

import {
  Renderer,
  RenderProgram,
  Geometry,
  UniformsInput,
  Helpers,
  Computer,
  PingPongBufferInput,
  ComputeProgram,
} from "mini-gpu";
import { TGeometryArgs } from "../../src/core/Geometry";

// I'm using twgl to create my geometries...
import { primitives } from "twgl.js";

import renderShader from "./render.wgsl?raw";
import computeShader from "./compute.wgsl?raw";

const canvas = document.querySelector("canvas") as HTMLCanvasElement;

let renderer: Renderer, computer: Computer;
let uniforms: UniformsInput, pingPong: PingPongBufferInput;

const animate = () => {
  uniforms.member.u_elapsed_time = uniforms.member.u_elapsed_time + 0.01;
  computer.runAll();
  renderer.renderAll();
  pingPong.step();
  requestAnimationFrame(animate);
};

const init = async () => {
  const device = (await Helpers.requestWebGPU()) as GPUDevice;
  renderer = new Renderer(device as GPUDevice, canvas);
  computer = new Computer(device as GPUDevice);

  pingPong = new PingPongBufferInput(device, new Float32Array([0, 0, 0]));
  uniforms = new UniformsInput(device as GPUDevice, {
    u_elapsed_time: 0,
  });

  const computeProgram = new ComputeProgram(
    device,
    computeShader,
    {
      uniforms,
      values: pingPong,
    },
    pingPong.length,
    64
  );
  computer.add(computeProgram);

  // Using twgl.js here to create my geometry attributes
  const geometry = new Geometry(
    renderer,
    primitives.createSphereVertices(1, 64, 32) as TGeometryArgs
  );

  const renderProgram = new RenderProgram(
    renderer,
    renderShader,
    geometry,
    {
      computed: pingPong,
    },
    true // wireframe
  );
  renderer.add(renderProgram);

  requestAnimationFrame(animate);
  window.addEventListener("resize", () => renderer.resize());
};

init();

Camera with Controls

render.wgsl

struct Uniforms {
  u_elapsed_time : f32,
  u_view_projection_matrix : mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> uniforms : Uniforms;

struct VertexInput {
  @location(0) position : vec4<f32>,
}

struct VertexOutput {
  @builtin(position) position : vec4<f32>,
}

@vertex
fn vertex_main(vert : VertexInput) -> VertexOutput {
  var output : VertexOutput;
  output.position = uniforms.u_view_projection_matrix * vert.position;
  return output;
}

@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
  let r = cos(uniforms.u_elapsed_time) / 2 + 0.5;
  let g = sin(uniforms.u_elapsed_time * 0.5) / 2 + 0.5;
  let b = cos(uniforms.u_elapsed_time * 0.25) / 2 + 0.5;
  return vec4<f32>(r,g,b,1);
}

main.ts

import {
  Renderer,
  RenderProgram,
  Geometry,
  UniformsInput,
  Helpers,
  Camera,
  OrbitControls,
} from "mini-gpu";

// I'm using twgl to create my geometries...
import { primitives } from "twgl.js";

import renderShader from "./render.wgsl?raw";
import { TGeometryArgs } from "../../src/core/Geometry";

const DEG_TO_RAD = 0.0174533;

const canvas = document.querySelector("canvas") as HTMLCanvasElement;

let renderer: Renderer;
let uniforms: UniformsInput;
let camera: Camera, controls: OrbitControls;

const animate = () => {
  controls?.update();
  uniforms.member.u_view_projection_matrix = camera.viewProjectionMatrix;
  uniforms.member.u_elapsed_time = uniforms.member.u_elapsed_time + 0.01;
  renderer.renderAll();
  requestAnimationFrame(animate);
};

const init = async () => {
  const device = (await Helpers.requestWebGPU()) as GPUDevice;
  renderer = new Renderer(device as GPUDevice, canvas);
  camera = new Camera({
    fov: DEG_TO_RAD * 50,
    aspectRatio: renderer.width / renderer.height,
    near: 0.01,
    far: 999,
    position: [0, 0, -5],
  });
  controls = new OrbitControls(camera, canvas);

  // Using twgl.js here to create my geometry attributes
  const geometry = new Geometry(
    renderer,
    primitives.createSphereVertices(1, 64, 32) as TGeometryArgs
  );

  uniforms = new UniformsInput(device as GPUDevice, {
    u_elapsed_time: 0,
    u_view_projection_matrix: camera.viewProjectionMatrix,
  });

  const renderProgram = new RenderProgram(
    renderer,
    renderShader,
    geometry,
    {
      uniforms,
    },
    true // wireframe
  );
  renderer.add(renderProgram);

  requestAnimationFrame(animate);
  window.addEventListener("resize", () => {
    renderer.resize();
    camera.aspectRatio = renderer.width / renderer.height;
  });
};

init();