///////////////////////////////////////////////////////////////////////////////////
// The MIT License (MIT)
//
// Copyright (c) 2017 Tarek Sherif
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////
import { GL, WEBGL_INFO, DUMMY_OBJECT } from "./constants.js";
import { Cubemap } from "./cubemap.js";
import { DrawCall } from "./draw-call.js";
import { Framebuffer } from "./framebuffer.js";
import { Renderbuffer } from "./renderbuffer.js";
import { Program } from "./program.js";
import { Shader } from "./shader.js";
import { Texture } from "./texture.js";
import { Timer } from "./timer.js";
import { TransformFeedback } from "./transform-feedback.js";
import { UniformBuffer } from "./uniform-buffer.js";
import { VertexArray } from "./vertex-array.js";
import { VertexBuffer } from "./vertex-buffer.js";
import { Query } from "./query.js";
/**
Primary entry point to PicoGL. An app will store all parts of the WebGL
state.
@class App
@param {WebGLRenderingContext} gl
@prop {HTMLElement} canvas The canvas on which this app drawing.
@prop {WebGLRenderingContext} gl The WebGL context.
@prop {number} width The width of the drawing surface.
@prop {number} height The height of the drawing surface.
@prop {Object} state Tracked GL state.
@prop {Object} state.drawFramebufferBinding=GL.DRAW_FRAMEBUFFER Binding point to bind framebuffers to for draw. Should be set before any binding occurs. Should only have values GL.DRAW_FRAMEBUFFER or GL.FRAMEBUFFER (the latter with state.readFramebufferBinding set to the same).
@prop {Object} state.readFramebufferBinding=GL.READ_FRAMEBUFFER Binding point to bind framebuffers to for read. Should be set before any binding occurs. Should only have values GL.READ_FRAMEBUFFER or GL.FRAMEBUFFER (the latter with state.drawFramebufferBinding set to the same).
@prop {GLenum} clearBits Current clear mask to use with clear().
*/
export class App {
constructor(gl) {
this.gl = gl;
this.canvas = gl.canvas;
this.width = this.gl.drawingBufferWidth;
this.height = this.gl.drawingBufferHeight;
this.viewportX = 0;
this.viewportY = 0;
this.viewportWidth = 0;
this.viewportHeight = 0;
this.currentDrawCalls = null;
this.emptyFragmentShader = null;
this.state = {
program: null,
vertexArray: null,
transformFeedback: null,
activeTexture: -1,
textures: new Array(WEBGL_INFO.MAX_TEXTURE_UNITS),
uniformBuffers: new Array(WEBGL_INFO.MAX_UNIFORM_BUFFERS),
freeUniformBufferBases: [],
framebuffers: {},
drawFramebufferBinding: GL.DRAW_FRAMEBUFFER,
readFramebufferBinding: GL.READ_FRAMEBUFFER,
extensions: {}
};
this.clearBits = this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT| this.gl.STENCIL_BUFFER_BIT;
this.cpuTime = 0;
this.gpuTime = 0;
this.viewport(0, 0, this.width, this.height);
this.contextLostExt = null;
this.contextLostListener = null;
this.contextRestoredListener = null;
this.contextRestoredHandler = null;
this.initExtensions();
}
/**
Simulate context loss.
@method
@return {App} The App object.
*/
loseContext() {
if (this.contextLostExt) {
this.contextLostExt.loseContext();
}
return this;
}
/**
Simulate context restoration.
@method
@return {App} The App object.
*/
restoreContext() {
if (this.contextLostExt) {
this.contextLostExt.restoreContext();
}
return this;
}
/**
Set function to handle context restoration after loss.
@method
@param {function} fn Context restored handler.
@return {App} The App object.
*/
onContextRestored(fn) {
this.contextRestoredHandler = fn;
this.initContextListeners();
return this;
}
/**
Enable WebGL capability (e.g. depth testing, blending, etc.).
@method
@param {GLenum} cap Capability to enable.
@return {App} The App object.
*/
enable(cap) {
this.gl.enable(cap);
return this;
}
/**
Disable WebGL capability (e.g. depth testing, blending, etc.).
@method
@param {GLenum} cap Capability to disable.
@return {App} The App object.
*/
disable(cap) {
this.gl.disable(cap);
return this;
}
/**
Set the color mask to selectively enable or disable particular
color channels while rendering.
@method
@param {boolean} r Red channel.
@param {boolean} g Green channel.
@param {boolean} b Blue channel.
@param {boolean} a Alpha channel.
@return {App} The App object.
*/
colorMask(r, g, b, a) {
this.gl.colorMask(r, g, b, a);
return this;
}
/**
Set the clear color.
@method
@param {number} r Red channel.
@param {number} g Green channel.
@param {number} b Blue channel.
@param {number} a Alpha channel.
@return {App} The App object.
*/
clearColor(r, g, b, a) {
this.gl.clearColor(r, g, b, a);
return this;
}
/**
Set the clear mask bits to use when calling clear().
E.g. app.clearMask(PicoGL.COLOR_BUFFER_BIT).
@method
@param {GLenum} mask Bit mask of buffers to clear.
@return {App} The App object.
*/
clearMask(mask) {
this.clearBits = mask;
return this;
}
/**
Clear the canvas
@method
@return {App} The App object.
*/
clear() {
this.gl.clear(this.clearBits);
return this;
}
/**
Bind a draw framebuffer to the WebGL context.
@method
@param {Framebuffer} framebuffer The Framebuffer object to bind.
@see Framebuffer
@return {App} The App object.
*/
drawFramebuffer(framebuffer) {
framebuffer.bindForDraw();
return this;
}
/**
Bind a read framebuffer to the WebGL context.
@method
@param {Framebuffer} framebuffer The Framebuffer object to bind.
@see Framebuffer
@return {App} The App object.
*/
readFramebuffer(framebuffer) {
framebuffer.bindForRead();
return this;
}
/**
Switch back to the default framebuffer for drawing (i.e. draw to the screen).
Note that this method resets the viewport to match the default framebuffer.
@method
@return {App} The App object.
*/
defaultDrawFramebuffer() {
let binding = this.state.drawFramebufferBinding;
if (this.state.framebuffers[binding] !== null) {
this.gl.bindFramebuffer(binding, null);
this.state.framebuffers[binding] = null;
}
return this;
}
/**
Switch back to the default framebuffer for reading (i.e. read from the screen).
@method
@return {App} The App object.
*/
defaultReadFramebuffer() {
let binding = this.state.readFramebufferBinding;
if (this.state.framebuffers[binding] !== null) {
this.gl.bindFramebuffer(binding, null);
this.state.framebuffers[binding] = null;
}
return this;
}
/**
Copy data from framebuffer attached to READ_FRAMEBUFFER to framebuffer attached to DRAW_FRAMEBUFFER.
@method
@param {GLenum} mask Write mask (e.g. PicoGL.COLOR_BUFFER_BIT).
@param {Object} [options] Blit options.
@param {number} [options.srcStartX=0] Source start x coordinate.
@param {number} [options.srcStartY=0] Source start y coordinate.
@param {number} [options.srcEndX=Width of the read framebuffer] Source end x coordinate.
@param {number} [options.srcEndY=Height of the read framebuffer] Source end y coordinate.
@param {number} [options.dstStartX=0] Destination start x coordinate.
@param {number} [options.dstStartY=0] Destination start y coordinate.
@param {number} [options.dstEndX=Width of the draw framebuffer] Destination end x coordinate.
@param {number} [options.dstEndY=Height of the draw framebuffer] Destination end y coordinate.
@param {number} [options.filter=NEAREST] Sampling filter.
@return {App} The App object.
*/
blitFramebuffer(mask, options = DUMMY_OBJECT) {
let readBinding = this.state.readFramebufferBinding;
let drawBinding = this.state.drawFramebufferBinding;
let readFramebuffer = this.state.framebuffers[readBinding];
let drawFramebuffer = this.state.framebuffers[drawBinding];
let defaultReadWidth = readFramebuffer ? readFramebuffer.width : this.width;
let defaultReadHeight = readFramebuffer ? readFramebuffer.height : this.height;
let defaultDrawWidth = drawFramebuffer ? drawFramebuffer.width : this.width;
let defaultDrawHeight = drawFramebuffer ? drawFramebuffer.height : this.height;
let {
srcStartX = 0,
srcStartY = 0,
srcEndX = defaultReadWidth,
srcEndY = defaultReadHeight,
dstStartX = 0,
dstStartY = 0,
dstEndX = defaultDrawWidth,
dstEndY = defaultDrawHeight,
filter = GL.NEAREST
} = options;
this.gl.blitFramebuffer(srcStartX, srcStartY, srcEndX, srcEndY, dstStartX, dstStartY, dstEndX, dstEndY, mask, filter);
return this;
}
/**
Set the depth range.
@method
@param {number} near Minimum depth value.
@param {number} far Maximum depth value.
@return {App} The App object.
*/
depthRange(near, far) {
this.gl.depthRange(near, far);
return this;
}
/**
Enable or disable writing to the depth buffer.
@method
@param {Boolean} mask The depth mask.
@return {App} The App object.
*/
depthMask(mask) {
this.gl.depthMask(mask);
return this;
}
/**
Set the depth test function. E.g. app.depthFunc(PicoGL.LEQUAL).
@method
@param {GLenum} func The depth testing function to use.
@return {App} The App object.
*/
depthFunc(func) {
this.gl.depthFunc(func);
return this;
}
/**
Set the blend function. E.g. app.blendFunc(PicoGL.ONE, PicoGL.ONE_MINUS_SRC_ALPHA).
@method
@param {GLenum} src The source blending weight.
@param {GLenum} dest The destination blending weight.
@return {App} The App object.
*/
blendFunc(src, dest) {
this.gl.blendFunc(src, dest);
return this;
}
/**
Set the blend function, with separate weighting for color and alpha channels.
E.g. app.blendFuncSeparate(PicoGL.ONE, PicoGL.ONE_MINUS_SRC_ALPHA, PicoGL.ONE, PicoGL.ONE).
@method
@param {GLenum} csrc The source blending weight for the RGB channels.
@param {GLenum} cdest The destination blending weight for the RGB channels.
@param {GLenum} asrc The source blending weight for the alpha channel.
@param {GLenum} adest The destination blending weight for the alpha channel.
@return {App} The App object.
*/
blendFuncSeparate(csrc, cdest, asrc, adest) {
this.gl.blendFuncSeparate(csrc, cdest, asrc, adest);
return this;
}
/**
Set the blend equation. E.g. app.blendEquation(PicoGL.MIN).
@method
@param {GLenum} mode The operation to use in combining source and destination channels.
@return {App} The App object.
*/
blendEquation(mode) {
this.gl.blendEquation(mode);
return this;
}
/**
Set the bitmask to use for tested stencil values.
E.g. app.stencilMask(0xFF).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {number} mask The mask value.
@return {App} The App object.
*/
stencilMask(mask) {
this.gl.stencilMask(mask);
return this;
}
/**
Set the bitmask to use for tested stencil values for a particular face orientation.
E.g. app.stencilMaskSeparate(PicoGL.FRONT, 0xFF).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {GLenum} face The face orientation to apply the mask to.
@param {number} mask The mask value.
@return {App} The App object.
*/
stencilMaskSeparate(face, mask) {
this.gl.stencilMaskSeparate(face, mask);
return this;
}
/**
Set the stencil function and reference value.
E.g. app.stencilFunc(PicoGL.EQUAL, 1, 0xFF).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {GLenum} func The testing function.
@param {number} ref The reference value.
@param {GLenum} mask The bitmask to use against tested values before applying
the stencil function.
@return {App} The App object.
*/
stencilFunc(func, ref, mask) {
this.gl.stencilFunc(func, ref, mask);
return this;
}
/**
Set the stencil function and reference value for a particular face orientation.
E.g. app.stencilFuncSeparate(PicoGL.FRONT, PicoGL.EQUAL, 1, 0xFF).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {GLenum} face The face orientation to apply the function to.
@param {GLenum} func The testing function.
@param {number} ref The reference value.
@param {GLenum} mask The bitmask to use against tested values before applying
the stencil function.
@return {App} The App object.
*/
stencilFuncSeparate(face, func, ref, mask) {
this.gl.stencilFuncSeparate(face, func, ref, mask);
return this;
}
/**
Set the operations for updating stencil buffer values.
E.g. app.stencilOp(PicoGL.KEEP, PicoGL.KEEP, PicoGL.REPLACE).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {GLenum} stencilFail Operation to apply if the stencil test fails.
@param {GLenum} depthFail Operation to apply if the depth test fails.
@param {GLenum} pass Operation to apply if the both the depth and stencil tests pass.
@return {App} The App object.
*/
stencilOp(stencilFail, depthFail, pass) {
this.gl.stencilOp(stencilFail, depthFail, pass);
return this;
}
/**
Set the operations for updating stencil buffer values for a particular face orientation.
E.g. app.stencilOpSeparate(PicoGL.FRONT, PicoGL.KEEP, PicoGL.KEEP, PicoGL.REPLACE).
NOTE: Only works if { stencil: true } passed as a
context attribute when creating the App!
@method
@param {GLenum} face The face orientation to apply the operations to.
@param {GLenum} stencilFail Operation to apply if the stencil test fails.
@param {GLenum} depthFail Operation to apply if the depth test fails.
@param {GLenum} pass Operation to apply if the both the depth and stencil tests pass.
@return {App} The App object.
*/
stencilOpSeparate(face, stencilFail, depthFail, pass) {
this.gl.stencilOpSeparate(face, stencilFail, depthFail, pass);
return this;
}
/**
Define the scissor box.
@method
@param {Number} x Horizontal position of the scissor box.
@param {Number} y Vertical position of the scissor box.
@param {Number} width Width of the scissor box.
@param {Number} height Height of the scissor box.
@return {App} The App object.
*/
scissor(x, y, width, height) {
this.gl.scissor(x, y, width, height);
return this;
}
/**
Set the scale and units used.
@method
@param {Number} factor Scale factor used to create a variable depth offset for each polygon.
@param {Number} units Constant depth offset.
@return {App} The App object.
*/
polygonOffset(factor, units) {
this.gl.polygonOffset(factor, units);
return this;
}
/**
Read a pixel's color value from the currently-bound framebuffer.
@method
@param {number} x The x coordinate of the pixel.
@param {number} y The y coordinate of the pixel.
@param {ArrayBufferView} outColor Typed array to store the pixel's color.
@param {object} [options] Options.
@param {GLenum} [options.type=UNSIGNED_BYTE] Type of data stored in the read framebuffer.
@param {GLenum} [options.format=RGBA] Read framebuffer data format.
@return {App} The App object.
*/
readPixel(x, y, outColor, options = DUMMY_OBJECT) {
let {
format = GL.RGBA,
type = GL.UNSIGNED_BYTE
} = options;
this.gl.readPixels(x, y, 1, 1, format, type, outColor);
return this;
}
/**
Set the viewport.
@method
@param {number} x Left bound of the viewport rectangle.
@param {number} y Lower bound of the viewport rectangle.
@param {number} width Width of the viewport rectangle.
@param {number} height Height of the viewport rectangle.
@return {App} The App object.
*/
viewport(x, y, width, height) {
if (this.viewportWidth !== width || this.viewportHeight !== height ||
this.viewportX !== x || this.viewportY !== y) {
this.viewportX = x;
this.viewportY = y;
this.viewportWidth = width;
this.viewportHeight = height;
this.gl.viewport(x, y, this.viewportWidth, this.viewportHeight);
}
return this;
}
/**
Set the viewport to the full canvas.
@method
@return {App} The App object.
*/
defaultViewport() {
this.viewport(0, 0, this.width, this.height);
return this;
}
/**
Resize the drawing surface.
@method
@param {number} width The new canvas width.
@param {number} height The new canvas height.
@return {App} The App object.
*/
resize(width, height) {
this.canvas.width = width;
this.canvas.height = height;
this.width = this.gl.drawingBufferWidth;
this.height = this.gl.drawingBufferHeight;
this.viewport(0, 0, this.width, this.height);
return this;
}
/**
Create a program synchronously. It is highly recommended to use <b>createPrograms</b> instead as
that method will compile shaders in parallel where possible.
@method
@param {Shader|string} vertexShader Vertex shader object or source code.
@param {Shader|string} fragmentShader Fragment shader object or source code.
@param {Object} [options] Texture options.
@param {Object} [options.attributeLocations] Map of attribute names to locations (useful when using GLSL 1).
@param {Array} [options.transformFeedbackVaryings] Array of varying names used for transform feedback output.
@param {GLenum} [options.transformFeedbackMode] Capture mode of the transform feedback. (Default: PicoGL.SEPARATE_ATTRIBS).
@return {Program} New Program object.
*/
createProgram(vsSource, fsSource, opts = {}) {
let {transformFeedbackVaryings, attributeLocations, transformFeedbackMode} = opts;
return new Program(this.gl, this.state, vsSource, fsSource, transformFeedbackVaryings, attributeLocations, transformFeedbackMode)
.link()
.checkLinkage();
}
/**
Create several programs. Preferred method for program creation as it will compile shaders
in parallel where possible.
@method
@param {...Array} sources Variable number of 2 or 3 element arrays, each containing:
<ul>
<li> (Shader|string) Vertex shader object or source code.
<li> (Shader|string) Fragment shader object or source code.
<li> (Object - optional) Optional program parameters.
<ul>
<li>(Object - optional) <strong><code>attributeLocations</code></strong> Map of attribute names to locations (useful when using GLSL 1).
<li>(Array - optional) <strong><code>transformFeedbackVaryings</code></strong> Array of varying names used for transform feedback output.
<li>(GLenum - optional) <strong><code>transformFeedbackMode</code></strong> Capture mode of the transform feedback. (Default: PicoGL.SEPARATE_ATTRIBS).
</ul>
</ul>
</ul>
@return {Promise<Program[]>} Promise that will resolve to an array of Programs when compilation and
linking are complete for all programs.
*/
createPrograms(...sources) {
return new Promise((resolve, reject) => {
let numPrograms = sources.length;
let programs = new Array(numPrograms);
let pendingPrograms = new Array(numPrograms);
let numPending = numPrograms;
for (let i = 0; i < numPrograms; ++i) {
let source = sources[i];
let vsSource = source[0];
let fsSource = source[1];
let opts = source[2] || {};
let {transformFeedbackVaryings, attributeLocations, transformFeedbackMode} = opts;
programs[i] = new Program(this.gl, this.state, vsSource, fsSource, transformFeedbackVaryings, attributeLocations, transformFeedbackMode);
pendingPrograms[i] = programs[i];
}
for (let i = 0; i < numPrograms; ++i) {
programs[i].link();
}
let poll = () => {
let linked = 0;
for (let i = 0; i < numPending; ++i) {
if (pendingPrograms[i].checkCompletion()) {
pendingPrograms[i].checkLinkage();
if (pendingPrograms[i].linked) {
++linked;
} else {
reject(new Error("Program linkage failed"));
return;
}
} else {
pendingPrograms[i - linked] = pendingPrograms[i];
}
}
numPending -= linked;
if (numPending === 0) {
resolve(programs);
} else {
requestAnimationFrame(poll);
}
};
poll();
});
}
/**
Restore several programs after a context loss. Will do so in parallel where available.
@method
@param {...Program} sources Variable number of programs to restore.
@return {Promise<void>} Promise that will resolve once all programs have been restored.
*/
restorePrograms(...programs) {
return new Promise((resolve, reject) => {
let numPrograms = programs.length;
let pendingPrograms = programs.slice();
let numPending = numPrograms;
for (let i = 0; i < numPrograms; ++i) {
programs[i].initialize();
}
for (let i = 0; i < numPrograms; ++i) {
programs[i].link();
}
for (let i = 0; i < numPrograms; ++i) {
programs[i].checkCompletion();
}
let poll = () => {
let linked = 0;
for (let i = 0; i < numPending; ++i) {
if (pendingPrograms[i].checkCompletion()) {
pendingPrograms[i].checkLinkage();
if (pendingPrograms[i].linked) {
++linked;
} else {
reject(new Error("Program linkage failed"));
return;
}
} else {
pendingPrograms[i - linked] = pendingPrograms[i];
}
}
numPending -= linked;
if (numPending === 0) {
resolve();
} else {
requestAnimationFrame(poll);
}
};
poll();
});
}
/**
Create a shader. Creating a shader separately from a program allows for
shader reuse.
@method
@param {GLenum} type Shader type.
@param {string} source Shader source.
@return {Shader} New Shader object.
*/
createShader(type, source) {
return new Shader(this.gl, this.state, type, source);
}
/**
Create a vertex array.
@method
@return {VertexArray} New VertexArray object.
*/
createVertexArray() {
return new VertexArray(this.gl, this.state);
}
/**
Create a transform feedback object.
@method
@return {TransformFeedback} New TransformFeedback object.
*/
createTransformFeedback() {
return new TransformFeedback(this.gl, this.state);
}
/**
Create a vertex buffer.
@method
@param {GLenum} type The data type stored in the vertex buffer.
@param {number} itemSize Number of elements per vertex.
@param {ArrayBufferView|number} data Buffer data itself or the total
number of elements to be allocated.
@param {GLenum} [usage=STATIC_DRAW] Buffer usage.
@return {VertexBuffer} New VertexBuffer object.
*/
createVertexBuffer(type, itemSize, data, usage) {
return new VertexBuffer(this.gl, this.state, type, itemSize, data, usage);
}
/**
Create a per-vertex matrix buffer. Matrix buffers ensure that columns
are correctly split across attribute locations.
@method
@param {GLenum} type The data type stored in the matrix buffer. Valid types
are FLOAT_MAT4, FLOAT_MAT4x2, FLOAT_MAT4x3, FLOAT_MAT3, FLOAT_MAT3x2,
FLOAT_MAT3x4, FLOAT_MAT2, FLOAT_MAT2x3, FLOAT_MAT2x4.
@param {ArrayBufferView} data Matrix buffer data.
@param {GLenum} [usage=STATIC_DRAW] Buffer usage.
@return {VertexBuffer} New VertexBuffer object.
*/
createMatrixBuffer(type, data, usage) {
return new VertexBuffer(this.gl, this.state, type, 0, data, usage);
}
/**
Create an buffer without any structure information. Structure
must be fully specified when binding to a VertexArray.
@method
@param {number} bytesPerVertex Number of bytes per vertex.
@param {ArrayBufferView|number} data Buffer data itself or the total
number of bytes to be allocated.
@param {GLenum} [usage=STATIC_DRAW] Buffer usage.
@return {VertexBuffer} New VertexBuffer object.
*/
createInterleavedBuffer(bytesPerVertex, data, usage) {
return new VertexBuffer(this.gl, this.state, null, bytesPerVertex, data, usage);
}
/**
Create an index buffer. If the `itemSize` is not specified, it defaults to 3
@method
@variation 1
@param {GLenum} type The data type stored in the index buffer.
@param {ArrayBufferView} data Index buffer data.
@param {GLenum} [usage=STATIC_DRAW] Buffer usage.
@return {VertexBuffer} New VertexBuffer object.
*//**
Create an index buffer.
@method
@variation 2
@param {GLenum} type The data type stored in the index buffer.
@param {number} itemSize Number of elements per primitive.
@param {ArrayBufferView} data Index buffer data.
@param {GLenum} [usage=STATIC_DRAW] Buffer usage.
@return {VertexBuffer} New VertexBuffer object.
*/
createIndexBuffer(type, itemSize, data, usage) {
if (ArrayBuffer.isView(itemSize)) {
usage = data;
data = itemSize;
itemSize = 3;
}
return new VertexBuffer(this.gl, this.state, type, itemSize, data, usage, true);
}
/**
Create a uniform buffer in std140 layout. NOTE: FLOAT_MAT2, FLOAT_MAT3x2, FLOAT_MAT4x2,
FLOAT_MAT3, FLOAT_MAT2x3, FLOAT_MAT4x3 are supported, but must be manually padded to
4-float column alignment by the application!
@method
@param {Array} layout Array indicating the order and types of items to
be stored in the buffer.
@param {GLenum} [usage=DYNAMIC_DRAW] Buffer usage.
@return {UniformBuffer} New UniformBuffer object.
*/
createUniformBuffer(layout, usage) {
return new UniformBuffer(this.gl, this.state, layout, usage);
}
/**
Create empty 2D texture.
@method
@variation 1
@param {number} width - Texture width. Required for array or empty data.
@param {number} height - Texture height. Required for array or empty data.
@param {Object} [options] Texture options.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>internalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the texture.
@param {boolean} [options.premultiplyAlpha=false] Whether the alpha channel should be pre-multiplied when unpacking the texture.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Texture} New Texture object.
*//**
Create a 2D texture from a DOM image element.
@method
@variation 2
@param {HTMLImageElement|HTMLImageElement[]} image - Image data. An array can be passed to manually set all levels
of the mipmap chain. If a single level is passed and mipmap filtering is being used,
generateMipmap() will be called to produce the remaining levels.
@param {Object} [options] Texture options.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>intrnalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the texture.
@param {boolean} [options.premultiplyAlpha=false] Whether the alpha channel should be pre-multiplied when unpacking the texture.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Texture} New Texture object.
*//**
Create 2D texture from a typed array.
@method
@variation 3
@param {ArrayBufferView|ArrayBufferView[]} image - Image data. An array can be passed to manually set all levels
of the mipmap chain. If a single level is passed and mipmap filtering is being used,
generateMipmap() will be called to produce the remaining levels.
@param {number} width - Texture width. Required for array or empty data.
@param {number} height - Texture height. Required for array or empty data.
@param {Object} [options] Texture options.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>internalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the texture.
@param {boolean} [options.premultiplyAlpha=false] Whether the alpha channel should be pre-multiplied when unpacking the texture.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Texture} New Texture object.
*/
createTexture2D(image, width, height, options) {
if (typeof image === "number") {
// Create empty texture just give width/height.
options = height;
height = width;
width = image;
image = null;
} else if (height === undefined) {
// Passing in a DOM element. Height/width not required.
options = width;
width = image.width;
height = image.height;
}
return new Texture(this.gl, this.state, this.gl.TEXTURE_2D, image, width, height, undefined, false, options);
}
/**
Create a 2D texture array.
@method
@param {ArrayBufferView|Array} image Pixel data. An array can be passed to manually set all levels
of the mipmap chain. If a single level is passed and mipmap filtering is being used,
generateMipmap() will be called to produce the remaining levels.
@param {number} width Texture width.
@param {number} height Texture height.
@param {number} size Number of images in the array.
@param {Object} [options] Texture options.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>internalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the texture.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.wrapR=REPEAT] Depth wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Texture} New Texture object.
*/
createTextureArray(image, width, height, depth, options) {
if (typeof image === "number") {
// Create empty texture just give width/height/depth.
options = depth;
depth = height;
height = width;
width = image;
image = null;
}
return new Texture(this.gl, this.state, this.gl.TEXTURE_2D_ARRAY, image, width, height, depth, true, options);
}
/**
Create a 3D texture.
@method
@param {ArrayBufferView|Array} image Pixel data. An array can be passed to manually set all levels
of the mipmap chain. If a single level is passed and mipmap filtering is being used,
generateMipmap() will be called to produce the remaining levels.
@param {number} width Texture width.
@param {number} height Texture height.
@param {number} depth Texture depth.
@param {Object} [options] Texture options.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>internalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the texture.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.wrapR=REPEAT] Depth wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Texture} New Texture object.
*/
createTexture3D(image, width, height, depth, options) {
if (typeof image === "number") {
// Create empty texture just give width/height/depth.
options = depth;
depth = height;
height = width;
width = image;
image = null;
}
return new Texture(this.gl, this.state, this.gl.TEXTURE_3D, image, width, height, depth, true, options);
}
/**
Create a cubemap.
@method
@param {Object} options Texture options.
@param {HTMLElement|ArrayBufferView} [options.negX] The image data for the negative X direction.
Can be any format that would be accepted by texImage2D.
@param {HTMLElement|ArrayBufferView} [options.posX] The image data for the positive X direction.
Can be any format that would be accepted by texImage2D.
@param {HTMLElement|ArrayBufferView} [options.negY] The image data for the negative Y direction.
Can be any format that would be accepted by texImage2D.
@param {HTMLElement|ArrayBufferView} [options.posY] The image data for the positive Y direction.
Can be any format that would be accepted by texImage2D.
@param {HTMLElement|ArrayBufferView} [options.negZ] The image data for the negative Z direction.
Can be any format that would be accepted by texImage2D.
@param {HTMLElement|ArrayBufferView} [options.posZ] The image data for the positive Z direction.
Can be any format that would be accepted by texImage2D.
@param {number} [options.width] Cubemap side width. Defaults to the width of negX if negX is an image.
@param {number} [options.height] Cubemap side height. Defaults to the height of negX if negX is an image.
@param {GLenum} [options.internalFormat=RGBA8] Texture data internal format. Must be a sized format.
@param {GLenum} [options.type] Type of data stored in the texture. Default based on
<b>internalFormat</b>.
@param {boolean} [options.flipY=false] Whether the y-axis should be flipped when unpacking the image.
@param {boolean} [options.premultiplyAlpha=false] Whether the alpha channel should be pre-multiplied when unpacking the image.
@param {GLenum} [options.minFilter] Minification filter. Defaults to
LINEAR_MIPMAP_NEAREST if image data is provided, NEAREST otherwise.
@param {GLenum} [options.magFilter] Magnification filter. Defaults to LINEAR
if image data is provided, NEAREST otherwise.
@param {GLenum} [options.wrapS=REPEAT] Horizontal wrap mode.
@param {GLenum} [options.wrapT=REPEAT] Vertical wrap mode.
@param {GLenum} [options.compareMode=NONE] Comparison mode.
@param {GLenum} [options.compareFunc=LEQUAL] Comparison function.
@param {GLenum} [options.baseLevel] Base mipmap level.
@param {GLenum} [options.maxLevel] Maximum mipmap level.
@param {GLenum} [options.minLOD] Mimimum level of detail.
@param {GLenum} [options.maxLOD] Maximum level of detail.
@param {GLenum} [options.maxAnisotropy] Maximum anisotropy in filtering.
@return {Cubemap} New Cubemap object.
*/
createCubemap(options) {
return new Cubemap(this.gl, this.state, options);
}
/**
Create a renderbuffer.
@method
@param {number} width Renderbuffer width.
@param {number} height Renderbuffer height.
@param {GLenum} internalFormat Internal arrangement of the renderbuffer data.
@param {number} [samples=0] Number of MSAA samples.
@return {Renderbuffer} New Renderbuffer object.
*/
createRenderbuffer(width, height, internalFormat, samples = 0) {
return new Renderbuffer(this.gl, width, height, internalFormat, samples);
}
/**
Create a framebuffer.
@method
@return {Framebuffer} New Framebuffer object.
*/
createFramebuffer() {
return new Framebuffer(this.gl, this.state);
}
/**
Create a query.
@method
@param {GLenum} target Information to query.
@return {Query} New Query object.
*/
createQuery(target) {
return new Query(this.gl, target);
}
/**
Create a timer.
@method
@return {Timer} New Timer object.
*/
createTimer() {
return new Timer(this.gl);
}
/**
Create a DrawCall. A DrawCall manages the state associated with
a WebGL draw call including a program and associated vertex data, textures,
uniforms and uniform blocks.
@method
@param {Program} program The program to use for this DrawCall.
@param {VertexArray} [vertexArray=null] Vertex data to use for drawing.
@return {DrawCall} New DrawCall object.
*/
createDrawCall(program, vertexArray, primitive) {
return new DrawCall(this.gl, this.state, program, vertexArray, primitive);
}
// Enable extensions
initExtensions() {
this.gl.getExtension("EXT_color_buffer_float");
this.gl.getExtension("OES_texture_float_linear");
this.gl.getExtension("WEBGL_compressed_texture_s3tc");
this.gl.getExtension("WEBGL_compressed_texture_s3tc_srgb");
this.gl.getExtension("WEBGL_compressed_texture_etc");
this.gl.getExtension("WEBGL_compressed_texture_astc");
this.gl.getExtension("WEBGL_compressed_texture_pvrtc");
this.gl.getExtension("EXT_disjoint_timer_query_webgl2");
this.gl.getExtension("EXT_disjoint_timer_query");
this.gl.getExtension("EXT_texture_filter_anisotropic");
this.state.extensions.debugShaders = this.gl.getExtension("WEBGL_debug_shaders");
this.contextLostExt = this.gl.getExtension("WEBGL_lose_context");
// Draft extensions
this.gl.getExtension("KHR_parallel_shader_compile");
this.state.extensions.multiDrawInstanced = this.gl.getExtension("WEBGL_multi_draw_instanced");
}
initContextListeners() {
if (this.contextRestoredHandler) {
this.contextLostListener = (e) => {
e.preventDefault();
};
this.contextRestoredListener = () => {
this.initExtensions();
this.contextRestoredHandler();
};
this.canvas.addEventListener("webglcontextlost", this.contextLostListener);
this.canvas.addEventListener("webglcontextrestored", this.contextRestoredListener);
} else {
this.canvas.removeEventListener("webglcontextlost", this.contextLostListener);
this.canvas.removeEventListener("webglcontextrestored", this.contextRestoredListener);
this.contextLostListener = null;
this.contextRestoredListener = null;
}
}
// DEPRECATED
depthTest() {
console.warn("App.depthTest is deprecated. Use App.enable(PicoGL.DEPTH_TEST) instead.");
this.enable(GL.DEPTH_TEST);
return this;
}
noDepthTest() {
console.warn("App.noDepthTest is deprecated. Use App.disable(PicoGL.DEPTH_TEST) instead.");
this.disable(GL.DEPTH_TEST);
return this;
}
blend() {
console.warn("App.blend is deprecated. Use App.enable(PicoGL.BLEND) instead.");
this.enable(GL.BLEND);
return this;
}
noBlend() {
console.warn("App.noBlend is deprecated. Use App.disable(PicoGL.BLEND) instead.");
this.disable(GL.BLEND);
return this;
}
stencilTest() {
console.warn("App.stencilTest is deprecated. Use App.enable(PicoGL.STENCIL_TEST) instead.");
this.enable(GL.STENCIL_TEST);
return this;
}
noStencilTest() {
console.warn("App.noStencilTest is deprecated. Use App.disable(PicoGL.STENCIL_TEST) instead.");
this.disable(GL.STENCIL_TEST);
return this;
}
scissorTest() {
console.warn("App.scissorTest is deprecated. Use App.enable(PicoGL.SCISSOR_TEST) instead.");
this.enable(GL.SCISSOR_TEST);
return this;
}
noScissorTest() {
console.warn("App.noScissorTest is deprecated. Use App.disable(PicoGL.SCISSOR_TEST) instead.");
this.disable(GL.SCISSOR_TEST);
return this;
}
rasterize() {
console.warn("App.noRasterize is deprecated. Use App.disable(PicoGL.RASTERIZER_DISCARD) instead.");
this.disable(GL.RASTERIZER_DISCARD);
return this;
}
noRasterize() {
console.warn("App.rasterize is deprecated. Use App.enable(PicoGL.RASTERIZER_DISCARD) instead.");
this.enable(GL.RASTERIZER_DISCARD);
return this;
}
cullBackfaces() {
console.warn("App.cullBackfaces is deprecated. Use App.enable(PicoGL.CULL_FACE) instead.");
this.enable(GL.CULL_FACE);
return this;
}
drawBackfaces() {
console.warn("App.drawBackfaces is deprecated. Use App.disable(PicoGL.CULL_FACE) instead.");
this.disable(GL.CULL_FACE);
return this;
}
}