philomena/assets/js/vision/webgl.ts
2024-09-03 20:05:02 -04:00

125 lines
3.6 KiB
TypeScript

export function compileShader(gl: WebGL2RenderingContext, shaderSource: string, shaderType: GLenum) {
const shader = gl.createShader(shaderType);
if (!shader) {
throw new Error(`failed to create shader of type ${shaderType}`);
}
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(`Shader compilation failed: ${gl.getShaderInfoLog(shader)}`);
}
return shader;
}
export function createProgramFromSources(
gl: WebGL2RenderingContext,
vertexShaderSource: string,
fragmentShaderSource: string,
) {
const vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
if (!program) {
throw new Error('failed to create vertex + fragment program');
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(`Program link failed: ${gl.getProgramInfoLog(program)}`);
}
return program;
}
export function createOffscreenCanvasRenderingContext(): WebGL2RenderingContext {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
throw new Error('failed to create WebGL2 context');
}
if (!gl.getExtension('EXT_color_buffer_float')) {
throw new Error('failed to enable EXT_color_buffer_float extension');
}
return gl;
}
export interface TextureParameters {
width: number;
height: number;
internalFormat: GLenum;
format: GLenum;
type: GLenum;
pixels?: ImageBitmap;
}
export interface Texture extends TextureParameters {
object: WebGLTexture;
}
export interface Framebuffer {
object: WebGLFramebuffer;
texture: Texture;
}
export function createNonMippedLinearTexture(gl: WebGL2RenderingContext, params: TextureParameters): Texture {
const texture = gl.createTexture();
if (!texture) {
throw new Error('failed to create texture');
}
const level = 0;
const internalFormat = params.internalFormat;
const border = 0;
const format = params.format;
const type = params.type;
const data = params.pixels;
gl.bindTexture(gl.TEXTURE_2D, texture);
if (data) {
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, format, type, data);
} else {
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, params.width, params.height, border, format, type, null);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
return { object: texture, ...params };
}
export function createNonMippedLinearTextureFbo(gl: WebGL2RenderingContext, params: TextureParameters): Framebuffer {
const texture = createNonMippedLinearTexture(gl, params);
const fbo = gl.createFramebuffer();
if (!fbo) {
throw new Error('failed to create framebuffer object');
}
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture.object, 0);
return { object: fbo, texture };
}
export function getUniformLocation(
gl: WebGL2RenderingContext,
program: WebGLProgram,
name: string,
): WebGLUniformLocation {
const location = gl.getUniformLocation(program, name);
if (!location) {
throw new Error('failed to get uniform location');
}
return location;
}