• Stars
    star
    1,204
  • Rank 38,902 (Top 0.8 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 4 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A GPU-accelerated computing library for running physics simulations and other GPGPU computations in a web browser.

gpu-io

gpu-io main image

NPM Package Build Size NPM Downloads License

A GPU-accelerated computing library for running physics simulations on the web

gpu-io is a WebGL library that helps you easily compose GPU-accelerated computing workflows. This library can be used for a variety of applications including real-time physics simulations, particle/agent-based simulations, cellular automata, image processing, and general purpose GPU computations. gpu-io supports rendering directly to the WebGL canvas and has some built-in features that make interactivity easy. See Examples for more details.

Designed for WebGL 2.0 (if available), with fallbacks to support WebGL 1.0 - so it should run on practically any mobile or older browsers. WebGPU support is planned in the future.

Motivation

The main motivation behind gpu-io is to make it easier to compose GPU-accelerated applications without worrying too much about low-level WebGL details. This library manages WebGL state, implements shader and program caching, and deals with issues of available WebGL versions or spec inconsistencies across different browsers/hardware. It should significantly cut down on the amount of boilerplate code and state management you need to do in your applications. At the same time, gpu-io gives you enough low-level control to write extremely efficient programs for computationally demanding applications.

As of Feb 2022, WebGL2 has now been rolled out to all major platforms (including mobile Safari and Microsoft Edge) - but even among WebGL2 implementations, there are differences in behavior across browsers (especially mobile). Additionally, you may still come across non-WebGL2 enabled browsers in the wild for some time. gpu-io rigorously checks for these gotchas and uses software polyfills to patch any issues so you don't have to worry about it. gpu-io will also attempt to automatically convert your GLSL3 shader code into GLSL1 so that it can run in WebGL1 in a pinch. See tests/README.md for more information on browser support.

Installation

Install via npm

npm install gpu-io

And import into your project:

import { GPUComposer, GPULayer, GPUProgram } from 'gpu-io';

Import into HTML

OR you can add gpu-io.js or gpu-io.min.js to your html directly:

<html>
  <head>
    <script src="gpu-io.js"></script>
  </head>
  <body>
  </body>
</html>

GPUIO will be accessible globally:

const { GPUComposer, GPULayer, GPUProgram } = GPUIO;

Use

If you have questions about how to use gpu-io: feel free to start a new discussion thread.

A simple example of how to use gpu-io to simulate 2D diffusion:

import {
  GPUComposer,
  GPULayer,
  GPUProgram,
  renderAmplitudeProgram,
  FLOAT,
  INT,
  REPEAT,
  NEAREST,
} from 'gpu-io';

// Init a canvas element.
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);

// Init a composer.
const composer = new GPUComposer({ canvas });

// Init a layer of float data filled with noise.
const noise = new Float32Array(canvas.width * canvas.height);
noise.forEach((el, i) => noise[i] = Math.random());
const state = new GPULayer(composer, {
  name: 'state',
  dimensions: [canvas.width, canvas.height],
  numComponents: 1, // Scalar state has one component.
  type: FLOAT,
  filter: NEAREST,
  // Use 2 buffers so we can toggle read/write
  // from one to the other.
  numBuffers: 2,
  wrapX: REPEAT,
  wrapY: REPEAT,
  array: noise,
});

// Init a program to diffuse state.
const diffuseProgram = new GPUProgram(composer, {
  name: 'render',
  fragmentShader: `
    in vec2 v_uv;

    uniform sampler2D u_state;
    uniform vec2 u_pxSize;

    out float out_result;

    void main() {
      // Compute the discrete Laplacian.
      // https://en.wikipedia.org/wiki/Discrete_Laplace_operator
      float center = texture(u_state, v_uv).x;
      float n = texture(u_state, v_uv + vec2(0, u_pxSize.y)).x;
      float s = texture(u_state, v_uv - vec2(0, u_pxSize.y)).x;
      float e = texture(u_state, v_uv + vec2(u_pxSize.x, 0)).x;
      float w = texture(u_state, v_uv - vec2(u_pxSize.x, 0)).x;
      const float diffusionRate = 0.1;
      out_result =
        center + diffusionRate * (n + s + e + w - 4.0 * center);
    }
  `,
  uniforms: [
    { // Index of sampler2D uniform to assign to value "u_state".
      name: 'u_state',
      value: 0,
      type: INT,
    },
    { // Calculate the size of a 1 px step in UV coordinates.
      name: 'u_pxSize',
      value: [1 / canvas.width, 1 / canvas.height],
      type: FLOAT,
    },
  ],
});

// Init a program to render state to canvas.
// See https://github.com/amandaghassaei/gpu-io/tree/main/docs#gpuprogram-helper-functions
// for more built-in GPUPrograms to use.
const renderProgram = renderAmplitudeProgram(composer, {
  name: 'render',
  type: state.type,
  components: 'x',
});

// Simulation/render loop.
function loop() {
  window.requestAnimationFrame(loop);

  // Diffuse state and write result to state.
  composer.step({
    program: diffuseProgram,
    input: state,
    output: state,
  });

  // If no "output", will draw to canvas.
  composer.step({
    program: renderProgram,
    input: state,
  });
}
loop(); // Start animation loop.

Demo this code - You should see the noise slowly blur, refresh the page to start it over.

Examples

Check out the Examples page to really understand how gpu-io works and how to easily create touch interactions in your application. Source code for all examples can be found in the examples/ folder.

Please let me know if you made something with gpu-io! Feel free to also post a link in the Show and Tell discussions thread. I'll periodically add some of these to the Examples page as well.

API

Full API documentation can be found in the docs/ folder.

More information about writing GLSL shaders for gpu-io can be found at docs/GLSL.

Compatibility with Threejs

gpu-io can share a webgl context with Threejs so that both libraries will be able to access shared memory on the gpu:

import THREE from 'three';
import {
  GPUComposer,
  GPULayer,
  FLOAT,
  CLAMP_TO_EDGE,
  LINEAR,
} from 'gpu-io';

const renderer = new THREE.WebGLRenderer();
// Use renderer.autoClear = false if you want to overlay threejs stuff
// on top of things rendered to the screen from gpu-io.
// renderer.autoClear = false;

const composer = GPUComposer.initWithThreeRenderer(renderer);

Data is passed between gpu-io and Threejs via WebGLTextures. To bind a GPULayer to a Threejs Texture:

const layer1 = new GPULayer(composer, {
  name: 'layer1',
  dimensions: [100, 100],
  type: FLOAT,
  numComponents: 1,
});

const texture = new THREE.Texture();
// Link webgl texture to threejs object.
layer1.attachToThreeTexture(texture);

// Use texture in threejs scene.
const mesh = new THREE.Mesh(
  new PlaneBufferGeometry(1, 1),
  new MeshBasicMaterial({
    map: texture,
  }),
);

loop() {
  // Undo any changes threejs has made to global WebGL state.
  composer.undoThreeState();

  // Compute things with gpu-io.
  composer.step({
    program: myProgram,
    output: layer1,
  });

  ....

  // Reset global WebGL state back to what threejs is expecting
  // (otherwise we get WebGL errors).
  composer.resetThreeState();
  // Render threejs scene.
  // Updates to layer1 will propagate to texture without any
  // additional needsUpdate flags.
  renderer.render(scene, camera);
}

More info about using gpu-io with Threejs can be found in the Threejs Example.

Limitations/Notes

Limitations

  • gpu-io currently only supports GPULayers with 1D or 2D arrays of dense data. 3D textures are not officially supported by the library (e.g. there are not currently 3D GPULayers, though I am looking at adding this). If you want to compute 3D simulations on triangle meshes in gpu-io, you will just need to pass in your 3D position data as a 1D list to a GPULayer and then access it in the fragment shader using .xyz. TODO: make example for this.
  • gpu-io does not currently allow you to pass in your own vertex shaders. Currently all computation is happening in user-specified fragment shaders; vertex shaders are managed internally.
  • In order for the WRAP/FILTER polyfilling to work correctly, any calls to texture() must contain a direct reference to the sampler2D that it should operate on. For example:
varying vec2 v_uv;

uniform sampler2D u_sampler1;
uniform sampler2D u_sampler2;

out vec4 out_result;

vec4 lookupSampler2(vec2 uv) {
  // This is good, it passes u_sampler2 directly to texture().
  return texture(u_sampler2, uv);
}

vec4 lookupSampler(sampler2D sampler, vec2 uv) {
  // At compile time it is hard to say which sampler
  // is passed to texture().
  // This will not be polyfilled, it will throw a warning.
  return texture(sampler, uv);
}

void main() {
  // This is good, it passes u_sampler1 directly to texture().
  vec2 position = texture(u_sampler1, v_uv).xy;

  ....
}

GLSL Version

gpu-io defaults to using WebGL2 (if available) with GLSL version 300 (GLSL3) but you can set it to use WebGL1 or GLSL version 100 (GLSL1) by passing contextID or glslVersion parameters to GPUComposer:

import {
  GPUComposer,
  GLSL1,
  WEBGL1,
} from 'gpu-io';

// Init with WebGL2 (if available) with GLSL1.
const composer1 = new GPUComposer({
  canvas: document.createElement('canvas'),
  glslVersion: GLSL1,
});

// Init with WebGL1 with GLSL1 (GLSL3 is not supported in WebGL1).
const composer2 = new GPUComposer({
  canvas: document.createElement('canvas'),
  contextID: WEBGL1,
});

See docs>GPUComposer>constructor for more information.

gpu-io will automatically convert any GLSL3 shaders to GLSL1 when targeting WebGL1. If supporting WebGL1/GLSL1 is important to you, see the GLSL1 Support doc for more info about what functions/types/operators are available in gpu-io's flavor of GLSL1.

Transform Feedback

You might notice that gpu-io does not use any transform feedback to handle computations on GPULayers. Transform feedback is great for things like particle simulations and other types of physics that is computed on the vertex level as opposed to the pixel level. It is still absolutely possible to perform these types of simulations using gpu-io (see Examples), but currently all the computation happens in a fragment shader. There are a few reasons for this:

  • The main use case for gpu-io is to operate on 2D spatially-distributed state (i.e. fields) stored in textures using fragment shaders. There is additional support for 1D arrays and lines/particles, but that is secondary functionality.
  • Transform feedback is only supported in WebGL2. At the time I first started writing this in 2020, WebGL2 was not supported by mobile Safari. Though that has changed recently, for now I'd like to support all functionality in gpu-io in WebGL1/GLSL1 as well.
  • The API is simpler if we constrain computations to the fragment shader only.

My current plan is to wait for WebGPU to officially launch by default in some browsers, and then re-evaluate some of the design decisions made in gpu-io. WebGL puts artificial constraints on the current API by forcing general-purpose computing to happen in a vertex and fragment shader rendering pipeline rather than a compute pipeline, so I'd like to get away from WebGL in the long term – and using transform feedback feels like a step backwards at this point.

Precision

By default all shaders in gpu-io are inited with highp precision floats and ints, but they will fall back to mediump if highp is not available (this is the same convention used by Threejs). More info in src/glsl/common/precision.ts.

You can override these defaults by specifying intPrecision and floatPrecision in GPUComposer's constructor:

import {
  GPUComposer,
  PRECISION_LOW_P,
  PRECISION_MEDIUM_P,
  PRECISION_HIGH_P,
} from 'gpu-io';

const composer = new GPUComposer({
  canvas: document.getElementById('webgl-canvas'),
  intPrecision: PRECISION_MEDIUM_P,
  floatPrecision: PRECISION_MEDIUM_P,
});

Of course, you can also always manually specify the precision of a particular variable in your shader code:

in vec2 v_uv;

// u_state is a BYTE array, so we can set its precision to lowp.
uniform lowp isampler2D u_state;

out vec4 out_result;

void main() {
  lowp int state = texture(u_state, v_uv).r;
  ....
}

Note: even if highp is specified in your shader code, gpu-io will convert to mediump if the current browser does not support highp (the alternative would be to throw an error).

I've also included the following helper functions to test the precision of mediump on your device and determine whether highp is supported:

import {
  isHighpSupportedInVertexShader,
  isHighpSupportedInFragmentShader,
  getVertexShaderMediumpPrecision,
  getFragmentShaderMediumpPrecision,
} from 'gpu-io';

// Prints 'highp' or 'mediump' depending on returned precision of
// mediump (16+bit or 32+bit).
// On many devices (esp desktop) mediump defaults to 32bit.
// See https://webglfundamentals.org/webgl/lessons/webgl-precision-issues.html
// for more info.
console.log(getVertexShaderMediumpPrecision());
console.log(getFragmentShaderMediumpPrecision());

// Print true or false depending on highp support of browser/device.
console.log(isHighpSupportedInVertexShader());
console.log(isHighpSupportedInFragmentShader());

Acknowledgements

I used a few codebases as reference when writing this, thanks to their authors for making these repos available:

Other resources:

License

This work is distributed under an MIT license. Note that gpu-io depends on a few npm packages:

Development

Update 10/2022: I'm switching gears a bit to focus on some new projects, but I'll be continuing to use gpu-io as the foundation for almost everything I'm working on. I expect that some new features will be added to this over the next six months or so, but I can't guarantee I'll have time to help debug issues you may run into. Feel free to log issues or ask questions, but don't expect a super prompt response! See the Examples for more info about how to use this library.

Pull requests welcome! I hope this library is useful to others, but I also realize that I have some very specific needs that have influenced the direction of this code – so we'll see what happens. Please let me know if you end up using this, I'd love to see what you're making!

Compiling with Webpack

Compiled with webpack. To build ts files from src to js in dist run:

npm install
npm run build

Automated Testing

I'm using mocha + karma + chai + headless Chrome to test the components of gpu-io, following the setup described in Automated testing with Headless Chrome. Those tests are located in tests/mocha/. To run the automated tests, use:

npm run test

The automated tests do not get full code coverage yet, but I'm planning to add to them when I go back to implement WebGPU features in this library.

Browser/Device Testing

I've also included a webpage for testing various functions of this library in a browser/hardware combo of your choice. This page is current hosted at apps.amandaghassaei.com/gpu-io/tests/.

Note: The detected OS and browser version may not always be 100% accurate.

See tests/README#browser-support for results of various browser/hardware combos.

More Repositories

1

OrigamiSimulator

Realtime WebGL origami simulator
JavaScript
1,083
star
2

FluidSimulation

WebGL shader for mixed grid-particle fluid simulation
TypeScript
273
star
3

canvas-capture

Record the canvas as an image, mp4 video, or gif from the browser
JavaScript
205
star
4

VortexShedding

A realtime fluid flow simulation on the GPU using WebGL
JavaScript
142
star
5

Fusion360-Scripts

A collection of Fusion360 scripts, mostly for generating animations
Python
103
star
6

LaserCutRecord

generate vector cutting paths from digital audio to make a working record
Processing
55
star
7

SoapFlow

Jupyter Notebook
42
star
8

MassSpringShader

WebGL Shader that implements a mass-spring-damper physical simulation
HTML
41
star
9

3DPrintedRecord

3d printable record stl generator using Processing and Python
Processing
37
star
10

tellurion-orrery

CAD files and info for building an orrery from scratch
JavaScript
34
star
11

ShellFormFinding

Web-based simulation tool that allows you to design 3D forms in pure compression
JavaScript
28
star
12

ConwayShader

WebGL Shader for Conway's Game of Life
TypeScript
22
star
13

Lithograph3DPrint

converts an image to a 3d printable heightmap or lithophane (http://en.wikipedia.org/wiki/Lithophane)
Processing
20
star
14

MeshWriter

Live three.js coding with STL and OBJ export
JavaScript
19
star
15

botanigram

Create "growing" animations of plants from a single photograph
TypeScript
18
star
16

Sugarcube-Arduino-Library

A grid-based midi instrument, with accelerometer and gyroscope for playful interactions
Max
18
star
17

Genetic-Images

a Processing sketch that generates images from translucent polygons using a genetic algorithm
Processing
18
star
18

marbling-experiment

An early experiment toward building a marbling simulation
TypeScript
16
star
19

msh-parser

Finite element .msh format parser, written in TypeScript
TypeScript
15
star
20

MotionMagnification

Eulerian, Lagrangian, and Phase-Based Motion Magnification in MATLAB
MATLAB
13
star
21

stl-parser

Standalone module for parsing binary and ASCII STL files, written in TypeScript
TypeScript
12
star
22

salesforce-tower

3D visualization for artwork on Salesforce Tower
JavaScript
11
star
23

TrussOptimization2D

Web-based design and optimization tool that uses real-time simulation feedback to inform the design process.
JavaScript
11
star
24

ProjectionMappingAlignment

WebGL app that breaks out many of the threejs camera parameters to fine tune projection mapping alignment
JavaScript
11
star
25

ScratchSerialExtension

A Serial Communication Extension for Scratch
JavaScript
11
star
26

ffmpeg-scripts

A collection of ffmpeg scripts I'm using to generate gifs and mp4
Shell
10
star
27

vector-math

A minimal vector math library to handle 2D/3D translations and rotations.
TypeScript
10
star
28

ReactionDiffusionShader

WebGL Shader for a Gray-Scott reaction diffusion system
JavaScript
10
star
29

MichellStructures

Web-based simulation tool that visualizes geometric properties and static internal forces in a loaded Michell cantilever
JavaScript
9
star
30

CurvedCreases

JavaScript
6
star
31

AMOEBA

JavaScript
5
star
32

LinkageOptimization

JavaScript
4
star
33

Linkages

A planar linkage optimization tool
nesC
3
star
34

LenticularProcessor

a Processing sketch for preparing a stack of images for a lenticlar lens
Processing
2
star
35

AMOEBA-2.5D

JavaScript
2
star
36

NodeSerialPortBoilerplate

Simple demo to get serial ports up and running with node and client-side javascript
JavaScript
2
star
37

event-dispatcher

Parent class to support custom event listeners, written in TypeScript.
TypeScript
2
star
38

StepperRotaryStage

A geared, rotary stage made from 3D printed and laser cut parts (and some off the shelf components). Modeled parametrically in Fusion360.
1
star
39

ActiveBendingHybridStructures

JavaScript
1
star
40

ms-thesis

TeX
1
star
41

Processing-Linkage-Generator

leg linkages designed in Processing, exported to STL for 3D Printing via ModelBuilder Library
Processing
1
star
42

ImageMagick

a collection of ImageMagick scripts
Shell
1
star
43

type-checks

A collection of JavaScript type checks.
TypeScript
1
star
44

RecordProcessor

A web app for designing custom records, outputs to laser cutter, CNC mill...
JavaScript
1
star