/* * Copyright 2021, GFXFundamentals. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of GFXFundamentals. nor the names of his * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* global define */ (function (root, factory) { // eslint-disable-line if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define([], function () { return factory.call(root); }); } else { // Browser globals root.webglUtils = factory.call(root); } })(this, function () { "use strict"; const topWindow = this; /** @module webgl-utils */ function isInIFrame(w) { w = w || topWindow; return w !== w.top; } if (!isInIFrame()) { // console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line // console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line } /** * Wrapped logging function. * @param {string} msg The message to log. */ function error(msg) { if (topWindow.console) { if (topWindow.console.error) { topWindow.console.error(msg); } else if (topWindow.console.log) { topWindow.console.log(msg); } } } const errorRE = /ERROR:\s*\d+:(\d+)/gi; function addLineNumbersWithError(src, log = "") { // Note: Error message formats are not defined by any spec so this may or may not work. const matches = [...log.matchAll(errorRE)]; const lineNoToErrorMap = new Map( matches.map((m, ndx) => { const lineNo = parseInt(m[1]); const next = matches[ndx + 1]; const end = next ? next.index : log.length; const msg = log.substring(m.index, end); return [lineNo - 1, msg]; }) ); return src .split("\n") .map((line, lineNo) => { const err = lineNoToErrorMap.get(lineNo); return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ""}`; }) .join("\n"); } /** * Error Callback * @callback ErrorCallback * @param {string} msg error message. * @memberOf module:webgl-utils */ /** * Loads a shader. * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. * @param {string} shaderSource The shader source. * @param {number} shaderType The type of shader. * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. * @return {WebGLShader} The created shader. */ function loadShader(gl, shaderSource, shaderType, opt_errorCallback) { const errFn = opt_errorCallback || error; // Create the shader object const shader = gl.createShader(shaderType); // Load the shader source gl.shaderSource(shader, shaderSource); // Compile the shader gl.compileShader(shader); // Check the compile status const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!compiled) { // Something went wrong during compilation; get the error const lastError = gl.getShaderInfoLog(shader); errFn( `Error compiling shader: ${lastError}\n${addLineNumbersWithError( shaderSource, lastError )}` ); gl.deleteShader(shader); return null; } return shader; } /** * Creates a program, attaches shaders, binds attrib locations, links the * program and calls useProgram. * @param {WebGLShader[]} shaders The shaders to attach * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @memberOf module:webgl-utils */ function createProgram( gl, shaders, opt_attribs, opt_locations, opt_errorCallback ) { const errFn = opt_errorCallback || error; const program = gl.createProgram(); shaders.forEach(function (shader) { gl.attachShader(program, shader); }); if (opt_attribs) { opt_attribs.forEach(function (attrib, ndx) { gl.bindAttribLocation( program, opt_locations ? opt_locations[ndx] : ndx, attrib ); }); } gl.linkProgram(program); // Check the link status const linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { // something went wrong with the link const lastError = gl.getProgramInfoLog(program); errFn( `Error in program linking: ${lastError}\n${shaders .map((shader) => { const src = addLineNumbersWithError(gl.getShaderSource(shader)); const type = gl.getShaderParameter(shader, gl.SHADER_TYPE); return `${glEnumToString(gl, type)}:\n${src}`; }) .join("\n")}` ); gl.deleteProgram(program); return null; } return program; } /** * Loads a shader from a script tag. * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. * @param {string} scriptId The id of the script tag. * @param {number} opt_shaderType The type of shader. If not passed in it will * be derived from the type of the script tag. * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. * @return {WebGLShader} The created shader. */ function createShaderFromScript( gl, scriptId, opt_shaderType, opt_errorCallback ) { let shaderSource = ""; let shaderType; const shaderScript = document.getElementById(scriptId); if (!shaderScript) { throw "*** Error: unknown script element" + scriptId; } shaderSource = shaderScript.text; if (!opt_shaderType) { if (shaderScript.type === "x-shader/x-vertex") { shaderType = gl.VERTEX_SHADER; } else if (shaderScript.type === "x-shader/x-fragment") { shaderType = gl.FRAGMENT_SHADER; } else if ( shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER ) { throw "*** Error: unknown shader type"; } } return loadShader( gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType, opt_errorCallback ); } const defaultShaderType = ["VERTEX_SHADER", "FRAGMENT_SHADER"]; /** * Creates a program from 2 script tags. * * @param {WebGLRenderingContext} gl The WebGLRenderingContext * to use. * @param {string[]} shaderScriptIds Array of ids of the script * tags for the shaders. The first is assumed to be the * vertex shader, the second the fragment shader. * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {WebGLProgram} The created program. * @memberOf module:webgl-utils */ function createProgramFromScripts( gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback ) { const shaders = []; for (let ii = 0; ii < shaderScriptIds.length; ++ii) { shaders.push( createShaderFromScript( gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback ) ); } return createProgram( gl, shaders, opt_attribs, opt_locations, opt_errorCallback ); } /** * Creates a program from 2 sources. * * @param {WebGLRenderingContext} gl The WebGLRenderingContext * to use. * @param {string[]} shaderSourcess Array of sources for the * shaders. The first is assumed to be the vertex shader, * the second the fragment shader. * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console * on error. If you want something else pass an callback. It's passed an error message. * @return {WebGLProgram} The created program. * @memberOf module:webgl-utils */ function createProgramFromSources( gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback ) { const shaders = []; for (let ii = 0; ii < shaderSources.length; ++ii) { shaders.push( loadShader( gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback ) ); } return createProgram( gl, shaders, opt_attribs, opt_locations, opt_errorCallback ); } /** * Resize a canvas to match the size its displayed. * @param {HTMLCanvasElement} canvas The canvas to resize. * @param {number} [multiplier] amount to multiply by. * Pass in window.devicePixelRatio for native pixels. * @return {boolean} true if the canvas was resized. * @memberOf module:webgl-utils */ function resizeCanvasToDisplaySize(canvas, multiplier) { multiplier = multiplier || 1; const width = (canvas.clientWidth * multiplier) | 0; const height = (canvas.clientHeight * multiplier) | 0; if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; return true; } return false; } return { createProgram: createProgram, createProgramFromScripts: createProgramFromScripts, createProgramFromSources: createProgramFromSources, resizeCanvasToDisplaySize: resizeCanvasToDisplaySize, }; });