Public
Edited
May 12, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
glcomp = {
let t1 = performance.now()
const renderer = new WebGLCompute();
const result = renderer.compute({
instances: 8, // => threads
divisor:1,
iters:4, // => feedback slots
//needsUpdate:true,
inputs: {
source: {
data:new Float32Array(1),
size:1,
},
// b:{data:new Float32Array(1),size:1}, use all params

},
compute: `#version 300 es
in float source;
out float result;

void main() {

for (int j = 0; j < ${4}; j += 1) {
result += source + 1.0;

result += (float(gl_InstanceID)+1.0);// - (float(gl_VertexID));
}
// result = 1.0;//(source*0.0) + (float(gl_InstanceID) + 0.0);// + (float(gl_VertexID) * 0.0);// + (b*0.0);
}
`,
});

invalidation.then(()=>renderer.dispose())
return {
t:parseInt(performance.now()-t1).toString().padStart(3,0),
...result, renderer
}
}
Insert cell
// adapted from:
// https://gist.github.com/CodyJasonBennett/34c36b91719171c45ec50e850dc38a34

WebGLCompute = {
/* Matches against GLSL shader outputs.
*/
const VARYING_REGEX = /[^\w](?:varying|out)\s+\w+\s+(\w+)\s*;/g;

/**
* Adds line numbers to a string with an optional starting offset.
*/
const lineNumbers = (source, offset = 0) =>
source.replace(/^/gm, () => `${offset++}:`);

/**
* Gets the appropriate WebGL data type for a data view.
*/
const getDataType = (data) => {
switch (data.constructor) {
case Float32Array:
return 5126; // FLOAT
case Int8Array:
return 5120; // BYTE
case Int16Array:
return 5122; // SHORT
case Int32Array:
return 5124; // INT
case Uint8Array:
case Uint8ClampedArray:
return 5121; // UNSIGNED_BYTE
case Uint16Array:
return 5123; // UNSIGNED_SHORT
case Uint32Array:
return 5125; // UNSIGNED_INT
default:
return null;
}
};

/**
* Constructs a WebGL compute program via transform feedback. Can be used to compute and serialize data from the GPU.
*/
class WebGLCompute {
constructor(gl = new OffscreenCanvas(1,1).getContext("webgl2")) {
this.gl = gl;
this._compiled = new Map();
this._fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(
this._fragmentShader,
"#version 300 es\nvoid main(){}"
);
this.gl.compileShader(this._fragmentShader);
}

/**
* Compiles a transform feedback program from compute options.
*/
compile(options) {
let compiled = this._compiled.get(options);
if (compiled) {
this.gl.bindVertexArray(compiled.VAO);
for (const [name, buffer] of compiled.buffers) {
const input = options.inputs[name];
if (!input.needsUpdate) continue;

this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
input.data,
this.gl.DYNAMIC_READ
);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);

input.needsUpdate = false;
}
this.gl.bindVertexArray(null);

return compiled;
}

// Parse outputs from shader source
const outputs = Array.from(options.compute.matchAll(VARYING_REGEX)).map(
([, varying]) => varying
);

// Compile shaders, configure output varyings
const program = this.gl.createProgram();

const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(vertexShader, options.compute);
this.gl.compileShader(vertexShader);

this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, this._fragmentShader);

this.gl.transformFeedbackVaryings(
program,
outputs,
this.gl.SEPARATE_ATTRIBS
);
this.gl.linkProgram(program);

const shaderError = this.gl.getShaderInfoLog(vertexShader);
if (shaderError)
throw `Error compiling shader: ${shaderError}\n${lineNumbers(
options.compute
)}`;

const programError = this.gl.getProgramInfoLog(program);
if (programError)
throw `Error compiling program: ${this.gl.getProgramInfoLog(program)}`;

this.gl.detachShader(program, vertexShader);
this.gl.detachShader(program, this._fragmentShader);
this.gl.deleteShader(vertexShader);

// Init VAO state (input)
const VAO = this.gl.createVertexArray();
this.gl.bindVertexArray(VAO);

let length = 0;

const buffers = new Map();
for (const name in options.inputs) {
const input = options.inputs[name];
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
input.data,
this.gl.DYNAMIC_READ //this.gl.STATIC_READ
);

const location = this.gl.getAttribLocation(program, name);

const slots = Math.min(4, Math.max(1, Math.floor(input.size / 3)));
for (let i = 0; i < slots; i++) {
this.gl.enableVertexAttribArray(location + i);
if (input.data instanceof Float32Array) {
this.gl.vertexAttribPointer(
location,
input.size,
this.gl.FLOAT,
false,
0,
0
);
} else {
const dataType = getDataType(input.data);
this.gl.vertexAttribIPointer(location, input.size, dataType, 0, 0);
}

if (input.divisor)
this.gl.vertexAttribDivisor(location + i, input.divisor);
}

buffers.set(name, buffer);
length = Math.max(length, input.data.length / input.size);
input.needsUpdate = false;
}
this.gl.bindVertexArray(null);

// Init feedback state (output)
const transformFeedback = this.gl.createTransformFeedback();
this.gl.bindTransformFeedback(
this.gl.TRANSFORM_FEEDBACK,
transformFeedback
);

const containers = new Map();
for (let i = 0; i < outputs.length; i++) {
const output = outputs[i];
const data = new Float32Array(length * options.instances);
containers.set(output, data);

const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.gl.STATIC_COPY);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
buffers.set(output, buffer);

this.gl.bindBufferBase(this.gl.TRANSFORM_FEEDBACK_BUFFER, i, buffer);
}
this.gl.bindTransformFeedback(this.gl.TRANSFORM_FEEDBACK, null);

compiled = {
program,
VAO,
transformFeedback,
buffers,
containers,
length,
};
this._compiled.set(options, compiled);

return compiled;
}

/**
* Runs and reads from the compute program.
*/
compute(options) {
return compute.call(this,options)
}

/**
* Disposes the compute pipeline from GPU memory.
*/
dispose() {
this.gl.deleteShader(this._fragmentShader);
for (const [, compiled] of this._compiled) {
this.gl.deleteProgram(compiled.program);
this.gl.deleteVertexArray(compiled.VAO);
this.gl.deleteTransformFeedback(compiled.transformFeedback);
compiled.buffers.forEach((buffer) => this.gl.deleteBuffer(buffer));
}
this._compiled.clear();
}
}

return WebGLCompute;
}

Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more