Files
front/assets/workers/ShaderWorker.ts
T
2024-08-01 17:31:09 +02:00

244 lines
7.1 KiB
TypeScript

import noise from "~/assets/shaders/noise.frag?raw";
import resolveLygia from "~/assets/workers/resolve-lygia.esm";
let interval: number;
let timeout: number;
const shaderOptions = { quality: 1, framerate: 1000 / 60 };
const screen = { width: 0, height: 0, devicePixelRatio: 0 };
const _colorStart = [0, 0, 0];
const _colorEnd = [0, 0, 0];
let fragmentShader = resolveLygia(noise);
let canvas: OffscreenCanvas;
let render: (time: number) => void;
let must_resize = false;
async function main(canvas: OffscreenCanvas) {
const gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
const program = gl.createProgram();
if (!program) {
return;
}
const fragment = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragment) {
return;
}
gl.shaderSource(fragment, fragmentShader);
gl.compileShader(fragment);
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
const lastError = gl.getShaderInfoLog(fragment);
console.error(lastError);
gl.deleteShader(fragment);
return;
}
const vertex = gl.createShader(gl.VERTEX_SHADER);
if (!vertex) {
return;
}
gl.shaderSource(
vertex,
`attribute vec4 a_position;void main() {gl_Position = a_position;}`
);
gl.compileShader(vertex);
if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
const lastError = gl.getShaderInfoLog(vertex);
console.error(lastError);
gl.deleteShader(vertex);
return;
}
gl.attachShader(program, fragment);
gl.attachShader(program, vertex);
gl.linkProgram(program);
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
return;
}
// const program = webglUtils.createProgramFromSources(gl, [vs, fragmentShader2]);
// look up where the vertex data needs to go.
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
const offset = gl.getUniformLocation(program, "u_offset");
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
const colorStart = gl.getUniformLocation(program, "u_color_start");
const quality = gl.getUniformLocation(program, "u_quality");
const colorEnd = gl.getUniformLocation(program, "u_color_end");
const timeLocation = gl.getUniformLocation(program, "u_time");
// Create a buffer to put three 2d clip space points in
const positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// fill it with a 2 triangles that cover clipspace
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1,
-1, // first triangle
1,
-1,
-1,
1,
-1,
1, // second triangle
1,
-1,
1,
1,
]),
gl.STATIC_DRAW
);
const gl2 = gl;
function render(time: number) {
if (must_resize) {
canvas.width = (screen.width * shaderOptions.quality) / 10;
canvas.height = (screen.height * shaderOptions.quality) / 10;
must_resize = false;
}
time *= 0.001; // convert to seconds
// Tell WebGL how to convert from clip space to pixels
gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height);
// Tell it to use our program (pair of shaders)
gl2.useProgram(program);
// Turn on the attribute
gl2.enableVertexAttribArray(positionAttributeLocation);
// Bind the position buffer.
gl2.bindBuffer(gl2.ARRAY_BUFFER, positionBuffer);
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
gl2.vertexAttribPointer(
positionAttributeLocation,
2, // 2 components per iteration
gl2.FLOAT, // the data is 32bit floats
false, // don't normalize the data
0, // 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
const secondRatio = screen.width / screen.height;
if (secondRatio < 1) {
const half = canvas.height / 2;
gl2.uniform2f(
resolutionLocation,
canvas.width,
canvas.height * secondRatio
);
gl2.uniform2f(offset, 0, -half + half / (1 / secondRatio));
} else {
const half = canvas.width / 2;
gl2.uniform2f(
resolutionLocation,
canvas.width / secondRatio,
canvas.height
);
gl2.uniform2f(offset, -half + half / secondRatio, 0);
}
gl2.uniform1i(quality, shaderOptions.quality);
gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]);
gl2.uniform3f(colorEnd, _colorEnd[0], _colorEnd[1], _colorEnd[2]);
gl2.uniform1f(timeLocation, time);
gl2.drawArrays(
gl2.TRIANGLES,
0, // offset
6 // num vertices to process
);
if (timeout) {
self.clearTimeout(timeout);
timeout = self.setTimeout(() => {
requestAnimationFrame(render);
}, 1000 / shaderOptions.framerate);
}
}
requestAnimationFrame(render);
// self.clearInterval(interval)
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
self.clearTimeout(timeout);
if (shaderOptions.framerate == 0) {
return render;
}
timeout = self.setTimeout(() => {
requestAnimationFrame(render);
}, 1000 / shaderOptions.framerate);
return render;
}
self.addEventListener("message", (e) => {
const data = e.data;
if (data.command == "initShader") {
canvas = data.canvas;
shaderOptions.quality = data.shaderOptions.quality;
shaderOptions.framerate = data.shaderOptions.framerate;
if (shaderOptions.framerate == 65) {
shaderOptions.framerate == Infinity;
}
screen.devicePixelRatio = data.screen.devicePixelRatio;
screen.width = data.screen.width;
screen.height = data.screen.height;
loadShader();
} else if (data.command == "killShader") {
self.clearInterval(interval);
self.clearTimeout(timeout);
} else if (data.command == "resumeShader") {
self.clearInterval(interval);
self.clearTimeout(timeout);
shaderOptions.framerate = data.shaderOptions.framerate;
if (shaderOptions.framerate == 65) {
shaderOptions.framerate == Infinity;
}
timeout = self.setTimeout(() => {
requestAnimationFrame(render);
}, 1000 / shaderOptions.framerate);
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
} else if (data.command == "recolorShader") {
_colorStart[0] = data.colorStart[0];
_colorStart[1] = data.colorStart[1];
_colorStart[2] = data.colorStart[2];
_colorEnd[0] = data.colorEnd[0];
_colorEnd[1] = data.colorEnd[1];
_colorEnd[2] = data.colorEnd[2];
} else if (data.command == "resizeShader") {
shaderOptions.quality = data.shaderOptions.quality;
screen.devicePixelRatio = data.screen.devicePixelRatio;
screen.width = data.screen.width;
screen.height = data.screen.height;
must_resize = true;
}
});
async function loadShader() {
if (!canvas) {
return;
}
// let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality);
const temp = await main(canvas);
if (temp) {
render = temp;
}
postMessage("ready");
}