352 lines
12 KiB
JavaScript
352 lines
12 KiB
JavaScript
/*
|
|
* 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,
|
|
};
|
|
});
|