site/www/js/smc/progbuilder.js
2026-02-02 03:43:54 +10:00

134 lines
3.1 KiB
JavaScript

import { SmcErr } from './errors.js';
export { SmcProgramBuilder };
class SmcProgramBuilder {
#gl;
#program;
#isBuilt = false;
#hasVertexShader = false;
#hasFragmentShader = false;
#defaultVertexShader = `
attribute vec4 aVertex;
void main() {
gl_Position = aVertex;
}
`;
// TODO: reset the sample fragment shader back to the rainbow
#sampleFragmentShader = `
precision mediump float;
// uniform float uTime;
uniform vec2 uResolution;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
// vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4));
// gl_FragColor = vec4(col, 1.0);
// gl_FragColor = vec4(216., 43., 72., 255.) / 255.;
// float maxfc = max(gl_FragCoord.x, gl_FragCoord.y);
// gl_FragColor = vec4(gl_FragCoord.xy, maxfc, maxfc) / maxfc;
float maxuv = max(uv.x, uv.y);
gl_FragColor = vec4(216. * maxuv, 43., 72., 255.) / 255.;
// vec3 col = 0.5 + 0.5*cos(uv.xyx+vec3(0,2,4));
// gl_FragColor = vec4(col, 1.);
}
`;
constructor(gl, raiseError) {
this.#gl = gl;
this.#program = this.#gl.createProgram();
this.raiseError = raiseError;
}
addVertexShader(source) {
this.#gl.attachShader(
this.#program,
this.#newShader(
this.#gl.VERTEX_SHADER,
source
)
)
this.#hasVertexShader = true;
return this;
}
addFragmentShader(source) {
this.#gl.attachShader(
this.#program,
this.#newShader(
this.#gl.FRAGMENT_SHADER,
source
)
)
this.#hasFragmentShader = true;
return this;
}
fetchVertexShader(uri) {
this.#fetchShader(uri, (source) => this.addVertexShader(source));
return this;
}
fetchFragmentShader(uri) {
this.#fetchShader(uri, (source) => this.addFragmentShader(source));
return this;
}
build() {
// avoid user accidental calls to build()
if (!this.#isBuilt) {
if (!this.#hasVertexShader)
this.addVertexShader(this.#defaultVertexShader)
if (!this.#hasFragmentShader)
this.addFragmentShader(this.#sampleFragmentShader);
this.#gl.linkProgram(this.#program);
this.#gl.useProgram(this.#program);
}
return this.#program;
}
// Creates a shader of the given type, uploads the source and compiles
#newShader(type, source) {
const shader = this.#gl.createShader(type);
this.#gl.shaderSource(shader, source);
this.#gl.compileShader(shader);
if (!this.#gl.getShaderParameter(shader, this.#gl.COMPILE_STATUS)) {
this.#gl.deleteShader(shader);
const infoLog = this.#gl.getShaderInfoLog(shader);
this.raiseError(
SmcErr.SHADER_COMPILATION,
new Error(`An error occurred while compiling the shader: ${infoLog}`)
);
}
return shader;
}
#fetchShader(uri, delegate) {
return fetch(uri)
.then(res => {
if (res.ok)
delegate(res.text());
else {
this.raiseError(
SmcErr.FETCH_SHADER,
`Failed to load shader source ${url}: ${res.status} ${res.json()}`);
}
});
}
}