Skip to main content

Getting Started

taichi.js is a Javascript framework which translates JS code into WebGPU compute and/or render pipelines. With taichi.js, the programmer can write WebGPU programs without having to deal with WebGPU's API or shading language. It offers a way to rapidly prototype WebGPU programs, for both parallel computation and rendering purposes. It is inspired by and named after the taichi Python library, from which it also inherits many API concepts and design principles.

Prerequisites

taichi.js depends on WebGPU, which is avaialable since Chrome v113.

Trying taichi.js

The quickest way to play with taichi.js is to access the Playground page. You can edit, build, and run taichi.js programs directly in this playground. You can also click the "Edit on Stackblitz" button, which will take you to a StackBlitz project that can be downloaded, so you can work on it locally.

Npm

You can also add taichi.js to your node project by running

npm install taichi.js --save

Example

The following code shows a WebGPU "Game of Life" program written in taichi.js. This example contains 3 WebGPU compute pipelines and 1 WebGPU render pipeline, all of which are implemented in taichi.js with basic typescript syntax. This demo is explained in details in this article.

import * as ti from "taichi.js"

let main = async () => {
await ti.init();

let N = 128;

let liveness = ti.field(ti.i32, [N, N])
let numNeighbors = ti.field(ti.i32, [N, N])

ti.addToKernelScope({ N, liveness, numNeighbors });

let init = ti.kernel(() => {
for (let I of ti.ndrange(N, N)) {
liveness[I] = 0
let f = ti.random()
if (f < 0.2) {
liveness[I] = 1
}
}
})
await init()

let countNeighbors = ti.kernel(() => {
for (let I of ti.ndrange(N, N)) {
let neighbors = 0
for (let delta of ti.ndrange(3, 3)) {
let J = (I + delta - 1) % N
if ((J.x != I.x || J.y != I.y) && liveness[J] == 1) {
neighbors = neighbors + 1;
}
}
numNeighbors[I] = neighbors
}
});
let updateLiveness = ti.kernel(() => {
for (let I of ti.ndrange(N, N)) {
let neighbors = numNeighbors[I]
if (liveness[I] == 1) {
if (neighbors < 2 || neighbors > 3) {
liveness[I] = 0;
}
}
else {
if (neighbors == 3) {
liveness[I] = 1;
}
}
}
})

let htmlCanvas = document.getElementById('result_canvas');
htmlCanvas.width = 512;
htmlCanvas.height = 512;
let renderTarget = ti.canvasTexture(htmlCanvas);

let vertices = ti.field(ti.types.vector(ti.f32, 2), [6]);
await vertices.fromArray([
[-1, -1],
[1, -1],
[-1, 1],
[1, -1],
[1, 1],
[-1, 1],
]);

ti.addToKernelScope({ vertices, renderTarget });

let render = ti.kernel(() => {
ti.clearColor(renderTarget, [0.0, 0.0, 0.0, 1.0]);
for (let v of ti.inputVertices(vertices)) {
ti.outputPosition([v.x, v.y, 0.0, 1.0]);
ti.outputVertex(v);
}
for (let f of ti.inputFragments()) {
let coord = (f + 1) / 2.0;
let cellIndex = ti.i32(coord * (liveness.dimensions - 1));
let live = ti.f32(liveness[cellIndex]);
ti.outputColor(renderTarget, [live, live, live, 1.0]);
}
});

async function frame() {
countNeighbors()
updateLiveness()
await render();
requestAnimationFrame(frame);
}
await frame();
};

main()