Easy GPGPU on the web, powered by WebGL 2.0.
npm install blink.jsblink.js
========
> Easy GPGPU on the web, powered by WebGL 2.0.
[![Latest NPM release][npm-badge]][npm-badge-url]
[![License][license-badge]][license-badge-url]
[![Dependencies][dependencies-badge]][dependencies-badge-url]
[![Dev Dependencies][devdependencies-badge]][devdependencies-badge-url]
__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.
blink.min.js file from the dist folder. Then reference it in the HTML using the tag.
html
`
Or use the unpkg CDN:
`html
`
Quickstart
__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.
$3
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.
`javascript
let buffer = new blink.Buffer({
alloc: 1024 ** 2,
type: blink.UINT32,
vector: 1
}) // 4 MB
let kernel = new blink.Kernel({
output: { buffer }
}, void main() {
)
kernel.exec()
for (let a = 0; a < 10; a++) {
console.log(Value at ${a} is ${buffer.data[a]}.)
}
`
$3
In this example we will use __blink.js__ to convert the image data of a canvas context to black and white.
`javascript
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() {
)
kernel.exec()
ctx.putImageData(imageData, 0, 0)
`
Usage
$3
#### Buffer
`javascript
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 of type. 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 both alloc and data 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.
#### DeviceBuffer
`javascript
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.
#### Kernel
`javascript
let buffer = new blink.Buffer(/ ... /)
let input = { in_buffer: buffer }
let output = { out_buffer: buffer }
let kernel = new blink.Kernel({ input, output },
)
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.
$3
* blink.FLOAT (Float32Array)
* blink.UINT8 (Uint8Array) (Uint8ClampedArray will be casted to Uint8Array)
* blink.UINT16 (Uint16Array)
* blink.UINT32 (Uint32Array)
* blink.INT8 (Int8Array)
* blink.INT16 (Int16Array)
* blink.INT32 (Int32Array)
$3
* blink.CLAMP
* blink.REPEAT
* blink.MIRROR
$3
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.
GLSL
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 and while loops with variable lengths.
* Non-square matrix types.
* A butt-load of matrix functions.
$3
`glsl
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.
``