import { Context } from "./Context";
/**
* @typedef {Object} WebGLContext
*/
/**
* @typedef {Object} WebGLProgram
*/
/**
* @typedef {Object} RendererConfig
* @property {Array<string>} locations
* @property {WebGLContext} context
*/
/**
* @typedef {Object} ContextConfig
* @property {HTMLCanvasElement} canvas
* @property {function} initCallback
* @property {Object} contextAttributes
*/
/**
* Location types
* @const {Object}
* @property {string} a
* @property {string} u
* @ignore
*/
const _locationTypes = {
a: "Attrib",
u: "Uniform",
};
/**
* Shade creator
* @param {WebGLContext} gl WebGL context
* @param {number} shaderType Shader type
* @param {string} shaderSource Shader source
* @returns {Object} Shader
* @ignore
*/
const _createShader = (gl, shaderSource, shaderType) => {
const shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
return shader;
};
/**
* Common utilities
* @typedef {Object} Utils
* @property {number} THETA Useful number for conversion between rad and deg
* @property {Object} GLSL Common glsl scripts
* @property {Object} INFO Information about WebGL
* @property {function(ContextConfig)} initContextConfig Create new context config
* @property {function(RendererConfig)} initRendererConfig Create new renderer config
* @property {function(function)} initApplication Call the callback function if the document.readyState interactive or complete
* @property {function(WebGLContext, string, string):WebGLProgram} createProgram Create a WebGL program
* @property {function(WebGLContext, WebGLProgram, Object):Object} getLocationsFor
*/
const _THETA = Math.PI / 180;
export const Utils = {
/**
* @property {number}
*/
ALPHA: 90 * _THETA,
/**
* @property {number}
*/
THETA: _THETA,
/**
* @property {Object}
*/
// prettier-ignore
GLSL: {
VERSION: "#version 300 es\n",
DEFINE: {
RADIANS_360: "#define RADIANS_360 radians(360.)\n",
HEIGHT: "#define HEIGHT 255.\n",
Z: "#define Z vec3(0,1,-1)\n",
PI: `#define PI ${Math.PI}\n`,
},
RANDOM:
"float rand(vec2 p,float s){" +
"p=mod(p,vec2(1e4));" +
"return fract(" +
"sin(" +
"dot(" +
"p," +
"vec2(" +
"sin(p.x+p.y)," +
"cos(p.y-p.x)" +
")" +
")" +
")*s" +
");" +
"}" +
"float rand(vec2 p){" +
"return rand(p,1.);" +
"}"
},
/**
* @property {Object}
*/
INFO: {
isWebGl2Supported: false,
},
/**
* Create new context config
* @param {ContextConfig} config Context config
* @returns {ContextConfig}
*/
initContextConfig: (config = {}) => ({
canvas: document.createElement("canvas"),
...config,
contextAttributes: {
...{
powerPreference: "high-performance",
preserveDrawingBuffer: false,
premultipliedAlpha: false,
},
...(config.contextAttributes || {}),
},
}),
/**
* Create new renderer config
* @param {RendererConfig} config Renderer config
* @returns {RendererConfig}
*/
initRendererConfig: (config = {}) => ({
locations: config.locations || [],
context: config.context || new Context(),
}),
/**
* Call the callback function if the document.readyState interactive or complete
* @param {function} callback Callback function
*/
initApplication: (callback) => {
const loadedCallback = () => callback(Utils.INFO.isWebGl2Supported);
["interactive", "complete"].includes(document.readyState)
? loadedCallback()
: document.addEventListener("DOMContentLoaded", loadedCallback, {
once: true,
});
},
/**
* Create a WebGL program
* @param {WebGLContext} gl WebGL context
* @param {string} vertexShaderSource Vertex shader source
* @param {string} fragmentShaderSource Fragment shader source
* @returns {WebGLProgram} WebGL program
*/
createProgram: (gl, vertexShaderSource, fragmentShaderSource) => {
const vertexShader = _createShader(
gl,
vertexShaderSource,
Const.VERTEX_SHADER
),
fragmentShader = _createShader(
gl,
fragmentShaderSource,
Const.FRAGMENT_SHADER
),
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, Const.LINK_STATUS)) {
const vertexShaderInfo = gl.getShaderInfoLog(vertexShader);
const fragmentShaderInfo = gl.getShaderInfoLog(fragmentShader);
console.error(
[
"Program info: " + gl.getProgramInfoLog(program),
"Validate status: " +
gl.getProgramParameter(program, Const.VALIDATE_STATUS),
...(vertexShaderInfo
? [
"",
"Vertex shader info: " + vertexShaderInfo,
"Vertex shader: " + vertexShaderSource,
]
: []),
...(fragmentShaderInfo
? [
"",
"Fragment shader info: " + fragmentShaderInfo,
"Fragment shader: " + fragmentShaderSource,
]
: []),
].join("\n")
);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.deleteProgram(program);
throw "WebGL application stoped";
}
return program;
},
/**
* @param {WebGLContext} gl WebGL context
* @param {WebGLProgram} program WebGL program
* @param {Array<string>} locationsDescriptor List of attributes and uniforms locations
* @returns {Object}
*/
getLocationsFor: (gl, program, locationsDescriptor) => {
const locations = {};
locationsDescriptor.forEach(
(name) =>
(locations[name] = gl[`get${_locationTypes[name[0]]}Location`](
program,
name
))
);
return locations;
},
};
/**
* Contains all constant values for WebGL
* @type {Object}
*/
export const Const = {};
const _gl = document.createElement("canvas").getContext("webgl2");
if (_gl) {
for (let key in _gl) {
const value = _gl[key];
if (typeof value === "number" && key === key.toUpperCase())
Const[key] = value;
}
Utils.INFO.isWebGl2Supported = true;
Utils.INFO.maxTextureImageUnits = _gl.getParameter(
Const.MAX_TEXTURE_IMAGE_UNITS
);
}