Easy GPGPU on the web, powered by WebGL 2.0.
blink.js (not to be confused with Blink, the Chromium render engine) is a small, easy to use GPGPU library for the web, exploiting the power of WebGL 2.0.
Please note: blink.js uses its own WebGL 2.0 context. Which means it's not pluggable with other WebGL frameworks. Though, theoretically, you could use blink.js' context as your main WebGL context.
Download the blink.min.js
file from the dist
folder. Then reference it in the HTML using the <script>
tag.
<script src="blink.min.js"></script>
Or use the unpkg CDN:
<script src="https://unpkg.com/blink.js/dist/blink.min.js"></script>
blink.js works with two types of objects: Buffers and Kernels. A Buffer is an (large) array of values that can be read from and/or written to. A Kernel contains the shader code that will be executed on the device.
In the following example we will initialize a Buffer, allocating space for 1,048,576 integers (4 MB). The Kernel will set all values to their corresponding location in the buffer.
let buffer = new blink.Buffer({
alloc: 1024 ** 2,
type: blink.UINT32,
vector: 1
}) // 4 MB
let kernel = new blink.Kernel({
output: { buffer }
}, `void main() {
buffer = bl_Id();
}`)
kernel.exec()
for (let a = 0; a < 10; a++) {
console.log(`Value at ${a} is ${buffer.data[a]}.`)
}
In this example we will use blink.js to convert the image data of a canvas context to black and white.
const ctx = canvas.getContext('2d')
/* Perform any drawing in the context here. */
let imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
let buffer = new blink.Buffer({
data: imageData.data,
vector: 4
})
let kernel = new blink.Kernel({
input: { in_rgba: buffer },
output: { out_rgba: buffer }
}, `void main() {
lowp uvec4 color = texture(in_rgba, bl_UV);
mediump uint grey = (color.r + color.g + color.b) / 3u;
out_rgba = uvec4(uvec3(grey), color.a);
}`)
kernel.exec()
ctx.putImageData(imageData, 0, 0)
let buffer = new blink.Buffer({
alloc: 1024 ** 2,
type: blink.UINT8,
vector: 1,
wrap: blink.CLAMP
})
buffer.data[0] = 1
The Buffer class represents an array of values that can be read from and written to on the device. A Buffer's data is copied to the device the moment it's required. After a Kernel is done executing all its steps, the data on the device is copied back to the host, the data on the device is destroyed immediately.
-
new Buffer({ alloc|data, type, vector, wrap })
Initialize a new buffer using the given Object containing the following parameters:
-
alloc
: Initialize an (0 filled) ArrayBuffer with this size. Note: The given number represents the number of elements oftype
. Not the size in bytes. -
data
: Opposed to having blink.js initialize the data, you can parse a TypedArray. The Buffer will hold a reference to this TypedArray. Note: If bothalloc
anddata
are present in the Object,alloc
is chosen. -
type
: The type of primitives of the Buffer. See Types. Default is FLOAT. -
vector
: Number of elements in the vector. Can be 1, 2 or 4. Default is 1. -
wrap
: Texture wrap mode. Can either be an array for S and T or a single constant. See Wrap modes. Default is CLAMP.
-
-
Buffer.prototype.data
Reference to the TypedArray.
-
Buffer.prototype.copy()
Returns a copy of the Buffer. The new instance will also hold a copy of the data allocated on the host.
const size = 512 ** 2 * 4
let d_buffer = new blink.DeviceBuffer({
alloc: size * 4,
type: blink.UINT32,
})
d_buffer.toDevice(new Uint32Array(size).fill(1))
const array = d_buffer.toHost()
d_buffer.delete()
Unlike a Buffer, a DeviceBuffer keeps its memory allocated only on the device. This greatly increases performance when memory is not required to be copied back to the host after a Kernel is done executing.
Memory is allocated (or copied) the moment the DeviceBuffer is initialized. Memory is retained on the device until the DeviceBuffer's delete
method is called. (Or until the browser garbage collects the DeviceBuffer. But it is strongly advised to manually maintain the memory on the device.)
Data can be downloaded to the host and uploaded to the device using the toHost
and toDevice
methods respectively.
-
new DeviceBuffer({ alloc|data, type, vector })
See
new Buffer()
. Only major difference is that no data is allocated nor referenced on the host. -
DeviceBuffer.prototype.copy()
Returns a copy of the DeviceBuffer. The data on the device is also copied.
-
DeviceBuffer.prototype.delete()
Delete the data on the device, and, essentially, turn the DeviceBuffer's instance unusable.
-
DeviceBuffer.prototype.toDevice(data)
data
: A TypedArray (of the same type and size the DeviceBuffer was initialized with) whose data will be uploaded to the device.
-
DeviceBuffer.prototype.toHost([data])
Download the data on the device back to the host.
data
: (Optional) If given, it should be of the same type and size the DeviceBuffer was initialized with. If not given, blink.js will initialize and return the correct TypedArray.
-
DeviceBuffer.prototype.toHostAsync([data])
Download the data on the device back to the host, asynchronously. Unlike
toHost()
, this method returns a Promise.data
: (Optional) See DeviceBuffer.prototype.toHost. If not given, blink.js will initialize the correct TypedArray, and pass it through the returned Promise's thenable.
NOTE: Only available if the WEBGL_get_buffer_sub_data_async
extension is supported.
let buffer = new blink.Buffer(/* ... */)
let input = { in_buffer: buffer }
let output = { out_buffer: buffer }
let kernel = new blink.Kernel({ input, output }, `
uniform float multiplier;
void main() {
float val = texture(in_buffer, bl_UV).r;
out_buffer = val * multiplier;
}`)
kernel.exec({ multiplier: 2 })
kernel.delete()
-
new Kernel({ input, output }, shaderSource)
Initialize a new Kernel with the given inputs, outputs and (fragment) shader source.
-
input
: (Optional) A key-value Object, where keys are the names of the inputs and the values a reference to either a Buffer or DeviceBuffer. The input names become available in the shader to read from their respective buffer. -
output
: Same as input, except for writing to buffers. -
shaderSource
: Source of the shader as a String.
-
-
Kernel.prototype.exec([uniforms])
Execute the Kernel.
uniforms
: (Optional) If given, it should be a key-value Object with the keys being the uniforms' names and values their value.
-
Kernel.prototype.delete()
Delete all associated shaders and programs on the device. Essentially rendering the Kernel's instance as unusable.
blink.FLOAT
(Float32Array
)blink.UINT8
(Uint8Array
) (Uint8ClampedArray
will be casted toUint8Array
)blink.UINT16
(Uint16Array
)blink.UINT32
(Uint32Array
)blink.INT8
(Int8Array
)blink.INT16
(Int16Array
)blink.INT32
(Int32Array
)
blink.CLAMP
blink.REPEAT
blink.MIRROR
blink.device
contains information gathered from the WebGL context.
glslVersion
: version of GLSL.maxColorAttachments
: Maximum number of outputs a Kernel can render to in a single step.maxTextureSize
: Maximum dimension of an input/output buffer.maxTextureUnits
: Maximum number of input buffers.renderer
: Renderer name.vendor
: Vendor name.unmaskedRenderer
: Unmasked renderer name.[1]unmaskedVendor
: Unmasked vendor name.[1]
[1] Only available if the WEBGL_debug_renderer_info
extension is supported.
WebGL 2.0 supports GLSL ES 3.00, which includes (but not limited to) the following significant new features compared to GLSL ES 1.30 in WebGL 1.0:
- Unsigned integer types.
- Bitwise operators.
for
andwhile
loops with variable lengths.- Non-square matrix types.
- A butt-load of matrix functions.
highp vec2 bl_UV; // Normalized coordinate of the current fragment.
highp ivec2 bl_Size; // Dimensions of the output buffer(s).
highp uint bl_Id(); // Returns the id of the current fragment.
Different combinations of operating systems, browsers and hardware may not support the entire list of type and vector size combinations. See COMPATIBILITY.md for a personally tested list of compatibility. Or open test/report.html in your browser to test yourself.
Both Firefox 59 and Chrome 65 (on desktop) support WebGL 2.0 now. As of writing this readme, the status of WebGL 2.0 for Safari is declared as Supported in preview (Safari Technology Preview). However, shaders are unable to successfully compile, thus rendering WebGL 2.0 support as pretty much unusable. There is no concrete answer whether Safari will fully support WebGL 2.0 in the future.
blink.js uses ES6 syntax/features that are not transpiled or polyfilled for ES5. Babel is only used to minify the final code, using the babili preset.
headless-gl - create WebGL contexts in Node.js, powered by ANGLE.
turbo.js - a similar GPGPU library for the browser, using WebGL 1.0.
WebGP.js - another similar GPGPU library for the browser.
gpu.js - transpile and run Javascript code on the GPU.
NOOOCL - OpenCL bindings for Node.js.
The Book of Shaders - Great introduction to GLSL by Patricio Gonzalez Vivo.
WebGL 2.0 Reference card - PDF containing all GLSL ES 3.00 functions and variables.