├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── screenshot.gif
├── dist
└── mini-webgl.js
├── examples
├── complex.html
├── cube.html
├── materials.html
└── simple.html
├── package.json
├── src
├── buffer-attribute.js
├── camera.js
├── geometries
│ ├── cube.js
│ ├── geometry.js
│ └── triangle.js
├── index.js
├── materials
│ ├── basic-material.js
│ └── material.js
├── math.js
├── model.js
├── node.js
├── renderer.js
├── scene.js
├── shaders
│ ├── base.fs
│ ├── base.vs
│ ├── basic.fs
│ └── basic.vs
└── webgl
│ ├── attribute.js
│ ├── gl-buffers.js
│ ├── index.js
│ ├── program.js
│ ├── programs.js
│ ├── register.js
│ └── uniform.js
├── vendor
└── webgl-debug.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "es2016",
5 | "es2017",
6 | ],
7 | "plugins": ["transform-object-rest-spread"]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.sw*
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jordan Santell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-webgl
2 |
3 | Mini toy WebGL library
4 |
5 | For educational purposes. **Do not use this library.**
6 | This is a barebones, unoptimized abstraction around WebGL. API ideas inspired by THREE.js.
7 |
8 | 
9 |
10 | ## What and How it Works
11 |
12 | * Basic rendering of a [scene](src/scene.js) graph with [perspective camera](src/camera.js).
13 | * [Models](src/model.js) are represented by a geometry and material, updating position/rotation/scale when local coordinates change.
14 | * Barebones [Math](src/math.js) library for managing Vector and Matrix types and track if dirty to minimize rebuilding matrices. Mostly a wrapper around [glMatrix](http://glmatrix.net/).
15 | * [Geometries](src/geometries) have a vertex buffer, and optionally an indices buffer.
16 | * Implemented: [Triangle](src/geometries/triangle.js), [Cube](src/geometries/cube.js)
17 | * [Materials](src/materials) take a vertex and fragment shader and define `uniforms` and `attributes` properties that can be updated and pushed to the GPU. Uniforms/attribs are initialized by readingInstances have their own uniform/attribute values, but reuse the same program if it can (based on vertex/fragment shader source), so for example, all instances of `BasicMaterial` use the same underlying WebGLProgram.
18 | * Implemented: [BasicMaterial](src/materials/basic-material.js)
19 | * The scene's [renderer](src/renderer.js) queues up the scene graph's nodes and passes it to the [GLWrapper](src/webgl/index.js) which manages all the WebGL calls, so all the API abstractions of models and materials are in [src/](src/), and translating that abstraction to WebGL lives in [src/webgl/](src/webgl/).
20 |
21 | ## Example
22 |
23 | * [triangle animations](https://jsantell.github.io/mini-webgl/examples/complex.html)
24 |
25 | ### Example Code
26 |
27 | ```js
28 | var scene = new MiniWebGL.Scene(canvas);
29 | var camera = new MiniWebGL.Camera();
30 | var cube = new MiniWebGL.Model(
31 | new MiniWebGL.Cube(),
32 | new MiniWebGL.BasicMaterial()
33 | );
34 |
35 | scene.useCamera(camera);
36 | cube.position.set(0, 0.5, -3);
37 | cube.scale.set(0.5, 0.5, 0.5);
38 | scene.add(cube);
39 |
40 | var t, x, y, z = 0;
41 | function tick () {
42 | t = performance.now();
43 | x = Math.cos(t * 0.001) + Math.PI;
44 | y = Math.sin(t * 0.001) + Math.PI;
45 | cube.rotation.set(x, y, z);
46 | cube.material.uniforms.color.setX(Math.abs(Math.sin(t * 0.001)));
47 | scene.render();
48 | requestAnimationFrame(tick);
49 | }
50 | tick();
51 | ```
52 |
53 | ## License
54 |
55 | MIT License, Copyright (c) 2017 Jordan Santell
56 |
--------------------------------------------------------------------------------
/assets/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsantell/mini-webgl/916099cd5a49f5016ddb80caf76e55f5874fc1d6/assets/screenshot.gif
--------------------------------------------------------------------------------
/examples/complex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/examples/cube.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/examples/materials.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jsantell/mini-webgl",
3 | "version": "1.0.4",
4 | "description": "Mini toy WebGL library",
5 | "main": "dist/mini-webgl.js",
6 | "scripts": {
7 | "build": "webpack"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/jsantell/mini-webgl.git"
12 | },
13 | "author": "Jordan Santell ",
14 | "license": "MIT",
15 | "devDependencies": {
16 | "babel-core": "^6.18.2",
17 | "babel-loader": "^6.2.8",
18 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
19 | "babel-preset-es2015": "^6.18.0",
20 | "babel-preset-es2016": "^6.16.0",
21 | "babel-preset-es2017": "^6.16.0",
22 | "glslify-loader": "^1.0.2",
23 | "raw-loader": "^0.5.1",
24 | "webpack": "^2.5.0"
25 | },
26 | "dependencies": {
27 | "gl-matrix": "^2.3.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/buffer-attribute.js:
--------------------------------------------------------------------------------
1 | const ARRAY_BUFFER = 0x8892;
2 | const ELEMENT_ARRAY_BUFFER = 0x8893;
3 |
4 | export default class BufferAttribute {
5 | constructor(array, size, isElementArray) {
6 | this.array = array;
7 | this.size = size;
8 | this.count = array.length / size;
9 | this.isElementArray = isElementArray;
10 | }
11 |
12 | getArray() {
13 | return this.array;
14 | }
15 |
16 | getCount() {
17 | return this.count;
18 | }
19 |
20 | getSize() {
21 | return this.size;
22 | }
23 |
24 | getType() {
25 | return this.isElementArray ? ELEMENT_ARRAY_BUFFER : ARRAY_BUFFER;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/camera.js:
--------------------------------------------------------------------------------
1 | import Node from './node';
2 | import { Matrix4 } from './math';
3 | const PMATRIX = Symbol('pmatrix');
4 | const IMATRIX = Symbol('imatrix');
5 |
6 | export default class Camera extends Node {
7 | constructor(fov=45, aspect=1, near=0.01, far=1000) {
8 | super();
9 | this[PMATRIX] = new Matrix4();
10 | this[IMATRIX] = new Matrix4();
11 | this.fov = fov;
12 | this.aspect = aspect;
13 | this.near = near;
14 | this.far = far;
15 |
16 | this.updateProjectionMatrix();
17 | }
18 |
19 | getProjectionMatrix() {
20 | return this[PMATRIX];
21 | }
22 |
23 | getInverseWorldMatrix() {
24 | return Matrix4.invert(this[IMATRIX].identity(), this.getWorldMatrix());
25 | }
26 |
27 | updateProjectionMatrix() {
28 | return this[PMATRIX].perspective(this.fov, this.aspect, this.near, this.far);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/geometries/cube.js:
--------------------------------------------------------------------------------
1 | import Geometry from './geometry';
2 | import BufferAttribute from '../buffer-attribute';
3 |
4 | export default class Cube extends Geometry {
5 | constructor() {
6 | const vertices = new BufferAttribute(new Float32Array([
7 | // Front face
8 | -1.0, -1.0, 1.0,
9 | 1.0, -1.0, 1.0,
10 | 1.0, 1.0, 1.0,
11 | -1.0, 1.0, 1.0,
12 |
13 | // Back face
14 | -1.0, -1.0, -1.0,
15 | -1.0, 1.0, -1.0,
16 | 1.0, 1.0, -1.0,
17 | 1.0, -1.0, -1.0,
18 |
19 | // Top face
20 | -1.0, 1.0, -1.0,
21 | -1.0, 1.0, 1.0,
22 | 1.0, 1.0, 1.0,
23 | 1.0, 1.0, -1.0,
24 |
25 | // Bottom face
26 | -1.0, -1.0, -1.0,
27 | 1.0, -1.0, -1.0,
28 | 1.0, -1.0, 1.0,
29 | -1.0, -1.0, 1.0,
30 |
31 | // Right face
32 | 1.0, -1.0, -1.0,
33 | 1.0, 1.0, -1.0,
34 | 1.0, 1.0, 1.0,
35 | 1.0, -1.0, 1.0,
36 |
37 | // Left face
38 | -1.0, -1.0, -1.0,
39 | -1.0, -1.0, 1.0,
40 | -1.0, 1.0, 1.0,
41 | -1.0, 1.0, -1.0
42 | ]), 3);
43 |
44 | const indices = new BufferAttribute(new Uint16Array([
45 | 0, 1, 2, 0, 2, 3, // Front face
46 | 4, 5, 6, 4, 6, 7, // Back face
47 | 8, 9, 10, 8, 10, 11, // Top face
48 | 12, 13, 14, 12, 14, 15, // Bottom face
49 | 16, 17, 18, 16, 18, 19, // Right face
50 | 20, 21, 22, 20, 22, 23 // Left face
51 | ]), 1, true);
52 | super(vertices, indices);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/geometries/geometry.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 |
3 | export default class Geometry {
4 | constructor(vertices, indices) {
5 | this.vertices = vertices;
6 | this.indices = indices;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/geometries/triangle.js:
--------------------------------------------------------------------------------
1 | import Geometry from './geometry';
2 | import BufferAttribute from '../buffer-attribute';
3 |
4 | export default class Triangle extends Geometry {
5 | constructor() {
6 | const vertices = new BufferAttribute(new Float32Array([
7 | 0, 1, 0,
8 | -1, -1, 0,
9 | 1, -1, 0
10 | ]), 3);
11 |
12 | super(vertices);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Renderer from './renderer';
3 | import Camera from './camera';
4 | import Scene from './scene';
5 | import * as Math from './math';
6 | import Model from './model';
7 | import BasicMaterial from './materials/basic-material';
8 | import Triangle from './geometries/triangle';
9 | import Node from './node';
10 | import Cube from './geometries/cube';
11 |
12 | export {
13 | assert,
14 | Math,
15 | Renderer,
16 | Camera,
17 | Cube,
18 | Scene,
19 | Model,
20 | Node,
21 | Triangle,
22 | BasicMaterial,
23 | };
24 |
--------------------------------------------------------------------------------
/src/materials/basic-material.js:
--------------------------------------------------------------------------------
1 | import { Vector4 } from '../math';
2 | import Material from './material';
3 | import vs from '../shaders/basic.vs';
4 | import fs from '../shaders/basic.fs';
5 |
6 | const createDefaultAttributes = () => {
7 | return {};
8 | };
9 |
10 | // Create a new instance of the underlying Vector4 data
11 | // so materials can have unique uniforms, or shared if
12 | // supplying its own uniform Vector.
13 | const createDefaultUniforms = () => {
14 | return {
15 | color: new Vector4(1, 1, 1, 1),
16 | }
17 | };
18 |
19 | export default class BasicMaterial extends Material {
20 | constructor(uniforms={}, attributes={}) {
21 | super(vs,
22 | fs,
23 | // TODO lazily create defaults if needed
24 | Object.assign({}, createDefaultUniforms(), uniforms),
25 | Object.assign({}, createDefaultAttributes(), attributes)
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/materials/material.js:
--------------------------------------------------------------------------------
1 | import vertShaderBase from '../shaders/base.vs';
2 | import fragShaderBase from '../shaders/base.fs';
3 |
4 | export const standardUniforms = Object.freeze({
5 | viewMatrix: { type: '4fv' },
6 |
7 | modelMatrix: { type: '4fv' },
8 | modelViewMatrix: { type: '4fv' },
9 | projectionMatrix: { type: '4fv' },
10 | });
11 |
12 | export const standardAttribs = Object.freeze({
13 | position: { type: '3f' },
14 | });
15 |
16 | export default class Material {
17 | constructor(vertSrc, fragSrc, uniforms={}, attributes={}) {
18 | this.uniforms = Object.assign({}, uniforms);
19 | this.attributes = Object.assign({}, attributes);
20 | this.vertSrc = vertSrc;
21 | this.fragSrc = fragSrc;
22 | this.fullVertSrc = `${vertShaderBase}\n${this.vertSrc}`
23 | this.fullFragSrc = `${fragShaderBase}\n${this.fragSrc}`
24 | }
25 |
26 | getFragmentSource() {
27 | return this.fullFragSrc;
28 | }
29 |
30 | getVertexSource() {
31 | return this.fullVertSrc;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/math.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import glMatrix from 'gl-matrix';
3 | const {
4 | vec2,
5 | vec3,
6 | vec4,
7 | mat2,
8 | mat3,
9 | mat4,
10 | } = glMatrix;
11 |
12 | const X_SCALAR = [1, 0, 0];
13 | const Y_SCALAR = [0, 1, 0];
14 | const Z_SCALAR = [0, 0, 1];
15 |
16 | class Vector {
17 | constructor(size, x, y, w, z) {
18 | this.size = size;
19 | switch (this.size) {
20 | case 2:
21 | this.mathBase = vec2;
22 | break;
23 | case 3:
24 | this.mathBase = vec3;
25 | break;
26 | case 4:
27 | this.mathBase = vec4;
28 | break;
29 | default:
30 | throw new Error('unknown size');
31 | }
32 | this.data = this.mathBase.create();
33 | this.set(x, y, w, z);
34 | this.dirty = true;
35 | this.isVector = true;
36 | }
37 |
38 | getArray() {
39 | return this.data;
40 | }
41 |
42 | identity() {
43 | this.mathBase.identity(this.data);
44 | return this;
45 | }
46 |
47 | set(x, y, z, w) {
48 | if (x !== undefined) this._setIndex(0, x);
49 | if (y !== undefined) this._setIndex(1, y);
50 | if (z !== undefined) this._setIndex(2, z);
51 | if (w !== undefined) this._setIndex(3, w);
52 | }
53 |
54 | getX() { return this._getIndex(0); }
55 | getY() { return this._getIndex(1); }
56 | getZ() { return this._getIndex(2); }
57 | getW() { return this._getIndex(3); }
58 |
59 | setX(v) { return this._setIndex(0, v); }
60 | setY(v) { return this._setIndex(1, v); }
61 | setZ(v) { return this._setIndex(2, v); }
62 | setW(v) { return this._setIndex(3, v); }
63 |
64 | _getIndex(i) {
65 | assert(this.size > i);
66 | return this.data[i];
67 | }
68 |
69 | _setIndex(i, value) {
70 | assert(this.size > i);
71 | this.data[i] = value;
72 | this.dirty = true;
73 | }
74 | }
75 |
76 | export class Vector2 extends Vector {
77 | constructor(x, y) {
78 | super(2, x, y);
79 | }
80 | }
81 |
82 | export class Vector3 extends Vector {
83 | constructor(x, y, z) {
84 | super(3, x, y, z);
85 | }
86 | }
87 |
88 | export class Vector4 extends Vector {
89 | constructor(x, y, z, w) {
90 | super(4, x, y, z, w);
91 | }
92 | }
93 |
94 | export class Matrix {
95 | constructor(size) {
96 | this.size = size;
97 | switch (this.size) {
98 | case 2:
99 | this.mathBase = mat2;
100 | break;
101 | case 3:
102 | this.mathBase = mat3;
103 | break;
104 | case 4:
105 | this.mathBase = mat4
106 | break;
107 | default:
108 | throw new Error('unknown size');
109 | }
110 |
111 | this.data = this.mathBase.create();
112 | this.isMatrix = true;
113 | }
114 |
115 | getArray() {
116 | return this.data;
117 | }
118 |
119 | identity() {
120 | this.mathBase.identity(this.data);
121 | return this;
122 | }
123 |
124 | translate(vec3) {
125 | this.mathBase.translate(this.data, this.data, vec3.getArray());
126 | }
127 |
128 | rotate(vec3) {
129 | const rArray = vec3.getArray();
130 | this.mathBase.rotate(this.data, this.data, rArray[0], X_SCALAR);
131 | this.mathBase.rotate(this.data, this.data, rArray[1], Y_SCALAR);
132 | this.mathBase.rotate(this.data, this.data, rArray[2], Z_SCALAR);
133 | }
134 |
135 | scale(vec3) {
136 | this.mathBase.scale(this.data, this.data, vec3.getArray());
137 | }
138 | }
139 |
140 | export class Matrix2 extends Matrix {
141 | constructor() {
142 | super(2);
143 | }
144 | }
145 |
146 | export class Matrix3 extends Matrix {
147 | constructor() {
148 | super(3);
149 | }
150 | }
151 |
152 | export class Matrix4 extends Matrix {
153 | constructor() {
154 | super(4);
155 | }
156 |
157 | static multiply(out, a, b) {
158 | mat4.multiply(out.getArray(), a.getArray(), b.getArray());
159 | return out;
160 | }
161 |
162 | static invert(out, a) {
163 | mat4.invert(out.getArray(), a.getArray());
164 | return out;
165 | }
166 |
167 | perspective(fov, aspect, near, far) {
168 | mat4.perspective(this.data, fov, aspect, near, far);
169 | return this;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/model.js:
--------------------------------------------------------------------------------
1 | import Node from './node';
2 |
3 | export default class Model extends Node {
4 | constructor(geometry, material) {
5 | super();
6 | this.geometry = geometry;
7 | this.material = material;
8 | this.isModel = true;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/node.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import { Matrix4, Vector3 } from './math';
3 | const CHILDREN = Symbol('children');
4 | const LMATRIX = Symbol('lmatrix');
5 | const WMATRIX = Symbol('wmatrix');
6 | const PARENT = Symbol('parent');
7 | let id = 0;
8 |
9 | /**
10 | * Node is a base class object that can contain children,
11 | * and have `position`, `rotation`, and `scale` properties.
12 | */
13 | export default class Node {
14 | constructor() {
15 | this.id = ++id;
16 | this[CHILDREN] = new Set();
17 |
18 | this.position = new Vector3(0, 0, 0);
19 | this.rotation = new Vector3(0, 0, 0);
20 | this.scale = new Vector3(1, 1, 1);
21 | this[LMATRIX] = new Matrix4();
22 | this[WMATRIX] = new Matrix4();
23 | this[PARENT] = null;
24 | }
25 |
26 | /**
27 | * Get the model matrix of this node in world coordinates.
28 | * If this node has no parent in the scene graph, then the
29 | * world matrix is the same as the local matrix.
30 | */
31 | getWorldMatrix() {
32 | if (this[PARENT] === null) {
33 | return this.getLocalMatrix();
34 | }
35 |
36 | Matrix4.multiply(this[WMATRIX].identity(),
37 | this[PARENT].getWorldMatrix(),
38 | this.getLocalMatrix()
39 | );
40 |
41 | return this[WMATRIX];
42 | }
43 |
44 | getLocalMatrix() {
45 | if (this.position.dirty ||
46 | this.rotation.dirty ||
47 | this.scale.dirty) {
48 | this._updateLocalMatrix();
49 | }
50 | return this[LMATRIX];
51 | }
52 |
53 | _updateLocalMatrix() {
54 | const matrix = this[LMATRIX];
55 | matrix.identity();
56 | matrix.translate(this.position);
57 | matrix.rotate(this.rotation);
58 | matrix.scale(this.scale);
59 | this.position.dirty =
60 | this.rotation.dirty =
61 | this.scale.dirty = false;
62 | return this;
63 | }
64 |
65 | add(object) {
66 | assert(object instanceof Node, 'Only Nodes may be added to scene graph');
67 | object[PARENT] = this;
68 | this[CHILDREN].add(object);
69 | }
70 |
71 | remove(object) {
72 | assert(object instanceof Node, 'Only Nodes may be removed from the scene graph');
73 | object[PARENT] = null;
74 | this[CHILDREN].delete(object);
75 | }
76 |
77 | getChildren() {
78 | return this[CHILDREN].values();
79 | }
80 |
81 | hasChildren() {
82 | return this[CHILDREN].size !== 0;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | import { Matrix4 } from './math';
2 | import GLWrapper from './webgl';
3 |
4 | export default class Renderer {
5 | constructor(canvas) {
6 | let ctx = this.ctx = canvas.getContext('webgl');
7 |
8 | // Use debugging utils if included
9 | // https://www.khronos.org/webgl/wiki/Debugging
10 | if (false && window.WebGLDebugUtils) {
11 | ctx = WebGLDebugUtils.makeDebugContext(
12 | ctx,
13 | undefined,
14 | (funcName, args) => {
15 | // Log on any undefined args
16 | let valid = true;
17 | for (let i = 0; i < args.length; i++) {
18 | if (args[i] === undefined) {
19 | valid = false;
20 | }
21 | }
22 | if (!valid) {
23 | console.error(`Undefined argument in ${funcName}: ${args}`);
24 | }
25 | }
26 | );
27 | }
28 |
29 | this.gl = new GLWrapper(ctx);
30 | this.gl.setSize(canvas.width, canvas.height);
31 |
32 | // Temp matrices during render
33 | this._mvMatrix = new Matrix4();
34 | this._mMatrix = new Matrix4();
35 | }
36 |
37 | getContext() {
38 | return this.ctx;
39 | }
40 |
41 | setSize(width, height) {
42 | this.gl.setSize(width, height);
43 | }
44 |
45 | render(scene, camera) {
46 | const { gl } = this;
47 | const pMatrix = camera.getProjectionMatrix();
48 | const vMatrix = camera.getInverseWorldMatrix();
49 |
50 | // Clear out the canvas
51 | gl.clear();
52 |
53 | const queue = [scene];
54 |
55 | while (queue.length) {
56 | const node = queue.shift();
57 |
58 | // Get and construct our model and model view matrices
59 | const mMatrix = node.getWorldMatrix();
60 | let mvMatrix = this._mvMatrix;
61 | Matrix4.multiply(mvMatrix.identity(), vMatrix, mMatrix);
62 |
63 | // If we have a model, pass it to the GLWrapper
64 | // to draw
65 | if (node.isModel) {
66 | this.gl.draw(node.geometry, node.material, mMatrix, mvMatrix, pMatrix, vMatrix);
67 | }
68 |
69 | // If this node has children, push them into the queue
70 | if (node.hasChildren()) {
71 | const children = node.getChildren();
72 | for (let child of children) {
73 | queue.push(child);
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/scene.js:
--------------------------------------------------------------------------------
1 | import Node from './node';
2 | import Camera from './camera';
3 | import Renderer from './renderer';
4 |
5 | export default class Scene extends Node {
6 | constructor(canvas) {
7 | super();
8 | this.camera = null;
9 | this.renderer = new Renderer(canvas);
10 | }
11 |
12 | useCamera(camera) {
13 | this.camera = camera;
14 | }
15 |
16 | render() {
17 | this.renderer.render(this, this.camera);
18 | }
19 |
20 | setSize(width, height) {
21 | this.renderer.setSize(width, height);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/shaders/base.fs:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | // uniform mat4 viewMatrix;
4 | // uniform vec3 cameraPosition;
5 |
--------------------------------------------------------------------------------
/src/shaders/base.vs:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform mat4 modelMatrix;
4 | uniform mat4 modelViewMatrix;
5 | uniform mat4 projectionMatrix;
6 | uniform mat4 viewMatrix;
7 | // uniform mat3 normalMatrix;
8 | // uniform vec3 cameraPosition;
9 |
10 | attribute vec3 position;
11 | // attribute vec3 normal;
12 | // attribute vec2 uv;
13 |
--------------------------------------------------------------------------------
/src/shaders/basic.fs:
--------------------------------------------------------------------------------
1 | uniform vec4 color;
2 |
3 | void main(void) {
4 | gl_FragColor = color;
5 | }
6 |
--------------------------------------------------------------------------------
/src/shaders/basic.vs:
--------------------------------------------------------------------------------
1 | void main(void) {
2 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
3 | }
4 |
--------------------------------------------------------------------------------
/src/webgl/attribute.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Register from './register';
3 |
4 | export default class Attribute extends Register {
5 | constructor(gl, program, info) {
6 | super(gl, program, info);
7 |
8 | this.gl.enableVertexAttribArray(this.loc);
9 | }
10 |
11 | _getLocation() {
12 | return this.gl.getAttribLocation(this.program, this.name);
13 | }
14 |
15 | set(value, size) {
16 | const { gl, loc, type } = this;
17 |
18 | gl.bindBuffer(gl.ARRAY_BUFFER, value);
19 | gl.vertexAttribPointer(this.loc, size, gl.FLOAT, false, 0, 0);
20 | this.value = value;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/webgl/gl-buffers.js:
--------------------------------------------------------------------------------
1 | const BUFFERS = Symbol('buffers');
2 |
3 | export default class GLBuffers {
4 | constructor(gl) {
5 | this.gl = gl;
6 | this[BUFFERS] = new Map();
7 | }
8 |
9 | _initBuffer(array, type) {
10 | const gl = this.gl;
11 | const buffer = gl.createBuffer();
12 | gl.bindBuffer(type, buffer);
13 | gl.bufferData(type, array, gl.STATIC_DRAW);
14 | this[BUFFERS].set(array, buffer);
15 | return buffer;
16 | }
17 |
18 | /**
19 | * @param {BufferAttribute} bufferAttr
20 | */
21 | getBuffer(bufferAttr) {
22 | const array = bufferAttr.getArray();
23 | let buffer = this[BUFFERS].get(array);
24 | if (buffer === undefined) {
25 | buffer = this._initBuffer(array, bufferAttr.getType());
26 | }
27 | return buffer;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/webgl/index.js:
--------------------------------------------------------------------------------
1 | import Programs from './programs';
2 | import GLBuffers from './gl-buffers';
3 |
4 | export default class GLWrapper {
5 | constructor(gl, width, height) {
6 | this.gl = gl;
7 | this.buffers = new GLBuffers(gl);
8 | this.programs = new Programs(gl);
9 | this.width = width;
10 | this.height = height;
11 | }
12 |
13 | setSize(width, height) {
14 | this.width = width;
15 | this.height = height;
16 | }
17 |
18 | clear() {
19 | const { gl } = this;
20 |
21 | gl.clearColor(0.2, 0.2, 0.2, 1);
22 | gl.enable(gl.DEPTH_TEST);
23 | gl.viewport(0, 0, this.width, this.height);
24 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
25 | }
26 |
27 | /**
28 | * @param {Geometry} geometry
29 | * @param {Material} material
30 | * @param {Matrix4} mMatrix
31 | * @param {Matrix4} mvMatrix
32 | * @param {Matrix4} pMatrix
33 | * @param {Matrix4} vMatrix
34 | */
35 | draw(geometry, material, mMatrix, mvMatrix, pMatrix, vMatrix) {
36 | const gl = this.gl;
37 | const geoVertexBuffer = this.buffers.getBuffer(geometry.vertices);
38 | let geoIndicesBuffer;
39 | if (geometry.indices) {
40 | geoIndicesBuffer = this.buffers.getBuffer(geometry.indices);
41 | }
42 |
43 | // Get the Program abstraction associated with this material,
44 | // or create one, which handles the compiling/linking of
45 | // shaders and setting up of uniforms. Reuses programs even if Material
46 | // instances are not identical based off of frag/vert source.
47 | let program = this.programs.getProgram(material);
48 |
49 | // Start using this program for all future calls
50 | program.use();
51 |
52 | for (let uniformName of Object.keys(program.uniforms)) {
53 | const uniform = program.uniforms[uniformName];
54 |
55 | // If this uniform is one of the standard uniforms,
56 | // set it; otherwise, it's probably user-driven
57 | switch (uniform.name) {
58 | case 'modelMatrix':
59 | uniform.set(mMatrix);
60 | break;
61 | case 'modelViewMatrix':
62 | uniform.set(mvMatrix);
63 | break;
64 | case 'projectionMatrix':
65 | uniform.set(pMatrix);
66 | break;
67 | case 'viewMatrix':
68 | uniform.set(vMatrix);
69 | break;
70 | default:
71 | const value = material.uniforms[uniform.name];
72 | uniform.set(value);
73 | break;
74 | }
75 | }
76 |
77 | for (let attribName of Object.keys(program.attributes)) {
78 | const attribute = program.attributes[attribName];
79 |
80 | // If this attribute is one of the standard attributes,
81 | // set it; otherwise, it's probably user-driven
82 | switch (attribute.name) {
83 | case 'position':
84 | attribute.set(geoVertexBuffer, geometry.vertices.getSize());
85 | break;
86 | default:
87 | const bufferAttr = material.attributes[attribute.name];
88 | const buffer = this.buffers.getBuffer(bufferAttr);
89 | attribute.set(buffer, bufferAttr.getSize());
90 | break;
91 | }
92 | }
93 |
94 | if (geoIndicesBuffer) {
95 | this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, geoIndicesBuffer);
96 | this.gl.drawElements(this.gl.TRIANGLES, geometry.indices.getCount(), gl.UNSIGNED_SHORT, 0);
97 | } else {
98 | this.gl.drawArrays(this.gl.TRIANGLES, 0, geometry.vertices.getCount());
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/webgl/program.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Uniform from './uniform';
3 | import Attribute from './attribute';
4 |
5 | const PROGRAM = Symbol('program');
6 | const VS = Symbol('vs');
7 | const FS = Symbol('fs');
8 |
9 | export default class Program {
10 | /**
11 | * @param {WebGLRenderingContext} gl
12 | * @param {String} vertSrc
13 | * @param {String} fragSrc
14 | */
15 | constructor(gl, vertSrc, fragSrc) {
16 | this.gl = gl;
17 |
18 | this[PROGRAM] = gl.createProgram();
19 |
20 | this.vertSrc = vertSrc;
21 | this.fragSrc = fragSrc;
22 |
23 | this[VS] = this._createShader(vertSrc, gl.VERTEX_SHADER);
24 | this[FS] = this._createShader(fragSrc, gl.FRAGMENT_SHADER);
25 |
26 | this._attachAndLink();
27 |
28 | this.attributes = {};
29 | this.uniforms = {};
30 |
31 | this._initializeAttributes();
32 | this._initializeUniforms();
33 | }
34 |
35 | use() {
36 | this.gl.useProgram(this[PROGRAM]);
37 | }
38 |
39 | getVertexSource() {
40 | return this.vertSrc;
41 | }
42 |
43 | getFragmentSource() {
44 | return this.fragSrc;
45 | }
46 |
47 | setUniform(name, value) {
48 | const uniform = this.uniforms[name];
49 | uniform.set(value);
50 | }
51 |
52 | setAttribute(name, value) {
53 | const attribute = this.attributes[name];
54 | attribute.set(value);
55 | }
56 |
57 | _initializeAttributes() {
58 | const attrCount = this._getParam(this.gl.ACTIVE_ATTRIBUTES);
59 |
60 | for (let i = 0; i < attrCount; i++) {
61 | const info = this.gl.getActiveAttrib(this[PROGRAM], i);
62 | this.attributes[info.name] = new Attribute(this.gl, this[PROGRAM], info);
63 | }
64 |
65 | // console.log(`Initialized attributes: ${Object.keys(this.attributes)}`);
66 | }
67 |
68 | _initializeUniforms() {
69 | const uniformCount = this._getParam(this.gl.ACTIVE_UNIFORMS);
70 |
71 | for (let i = 0; i < uniformCount; i++) {
72 | const info = this.gl.getActiveUniform(this[PROGRAM], i);
73 | this.uniforms[info.name] = new Uniform(this.gl, this[PROGRAM], info);
74 | }
75 |
76 | // console.log(`Initialized uniforms: ${Object.keys(this.uniforms)}`);
77 | }
78 |
79 | _createShader(src, glType) {
80 | const shader = this.gl.createShader(glType);
81 | this.gl.shaderSource(shader, src);
82 |
83 | // TODO compile lazily
84 | this.gl.compileShader(shader);
85 |
86 | if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
87 | console.error(this.gl.getShaderInfoLog(shader));
88 | }
89 |
90 | return shader;
91 | }
92 |
93 | _attachAndLink() {
94 | this.gl.attachShader(this[PROGRAM], this[VS]);
95 | this.gl.attachShader(this[PROGRAM], this[FS]);
96 | this.gl.linkProgram(this[PROGRAM]);
97 | if (!this._getParam(this.gl.LINK_STATUS)) {
98 | const error = this.gl.getProgramInfoLog(this[PROGRAM]);
99 | console.error(`Error linking program: ${error}`);
100 | }
101 | }
102 |
103 | /**
104 | * @param {GLenum}
105 | */
106 | _getParam(parameter) {
107 | assert(parameter, 'must provide a parameter');
108 | return this.gl.getProgramParameter(this[PROGRAM], parameter);
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/webgl/programs.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Program from './program';
3 |
4 | const PROGRAMS = Symbol('programs');
5 |
6 | export default class Programs {
7 | /**
8 | * @param {WebGLRenderingContext} gl
9 | */
10 | constructor(gl) {
11 | this.gl = gl;
12 | this[PROGRAMS] = new Map();
13 | }
14 |
15 | /**
16 | * @param {Material} material
17 | * @return {Program}
18 | */
19 | getProgram(material) {
20 |
21 | // Get the program from the cache if we've seen this
22 | // material before
23 | if (this[PROGRAMS].has(material)) {
24 | return this[PROGRAMS].get(material);
25 | }
26 |
27 | const vertSrc = material.getVertexSource();
28 | const fragSrc = material.getFragmentSource();
29 |
30 | // Otherwise, check to see if the frag and vert shaders
31 | // are the same as another program already in the cache;
32 | // this can occur when we use default materials,
33 | // creating new instances so each instance has its own
34 | // uniforms, but still should use the same underlying program/shaders.
35 | for (let program of this[PROGRAMS].values()) {
36 | if (program.getVertexSource() === vertSrc &&
37 | program.getFragmentSource() === fragSrc) {
38 | this[PROGRAMS].set(material, program);
39 | return program;
40 | }
41 | }
42 |
43 | const program = new Program(this.gl, vertSrc, fragSrc);
44 | this[PROGRAMS].set(material, program);
45 | return program;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/webgl/register.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | const TYPES = {
3 | 0x8B50: 'FLOAT_VEC2',
4 | 0x8B51: 'FLOAT_VEC3',
5 | 0x8B52: 'FLOAT_VEC4',
6 | 0x8B53: 'INT_VEC2',
7 | 0x8B54: 'INT_VEC3',
8 | 0x8B55: 'INT_VEC4',
9 | 0x8B56: 'BOOL',
10 | 0x8B57: 'BOOL_VEC2',
11 | 0x8B58: 'BOOL_VEC3',
12 | 0x8B59: 'BOOL_VEC4',
13 | 0x8B5A: 'FLOAT_MAT2',
14 | 0x8B5B: 'FLOAT_MAT3',
15 | 0x8B5C: 'FLOAT_MAT4',
16 | 0x8B5E: 'SAMPLER_2D',
17 | 0x8B60: 'SAMPLER_CUBE',
18 | 0x1400: 'BYTE',
19 | 0x1401: 'UNSIGNED_BYTE',
20 | 0x1402: 'SHORT',
21 | 0x1403: 'UNSIGNED_SHORT',
22 | 0x1404: 'INT',
23 | 0x1405: 'UNSIGNED_INT',
24 | 0x1406: 'FLOAT'
25 | };
26 |
27 | export default class Register {
28 | constructor(gl, program, info) {
29 | assert(typeof info.name === 'string', 'name must be a string');
30 | assert(typeof info.size === 'number', 'size must be a number');
31 | assert(typeof info.type === 'number', 'type must be a number');
32 | this.gl = gl;
33 | this.program = program;
34 | this.name = info.name;
35 | this.size = info.size;
36 | this.type = info.type;
37 | this.value = null;
38 | this.loc = this._getLocation();
39 | }
40 |
41 | _getLocation() {
42 | throw new Error('must be implemented by inheriting class');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/webgl/uniform.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import Register from './register';
3 |
4 | export default class Uniform extends Register {
5 | constructor(gl, program, info) {
6 | super(gl, program, info);
7 | }
8 |
9 | _getLocation() {
10 | return this.gl.getUniformLocation(this.program, this.name);
11 | }
12 |
13 | set(value) {
14 | const { gl, loc, type } = this;
15 |
16 | // Check if this is a Math type of ours to extract
17 | // the typed array data
18 | if (value && (value.isVector || value.isMatrix)) {
19 | // Continue setting values with the underlying
20 | // TypedArray
21 | value = value.getArray();
22 | }
23 |
24 | switch (type) {
25 | case gl.INT:
26 | gl.uniform1i(loc, value);
27 | break;
28 | case gl.INT_VEC2:
29 | gl.uniform2iv(loc, value);
30 | break;
31 | case gl.INT_VEC3:
32 | gl.uniform3iv(loc, value);
33 | break;
34 | case gl.INT_VEC4:
35 | gl.uniform4iv(loc, value);
36 | break;
37 | case gl.FLOAT:
38 | gl.uniform1f(loc, value);
39 | break;
40 | case gl.FLOAT_VEC2:
41 | gl.uniform2fv(loc, value);
42 | break;
43 | case gl.FLOAT_VEC3:
44 | gl.uniform3fv(loc, value);
45 | break;
46 | case gl.FLOAT_VEC4:
47 | gl.uniform4fv(loc, value);
48 | break;
49 | case gl.FLOAT_MAT3:
50 | gl.uniformMatrix3fv(loc, false, value);
51 | break;
52 | case gl.FLOAT_MAT4:
53 | gl.uniformMatrix4fv(loc, false, value);
54 | break;
55 | default:
56 | throw new Error('Unexpected uniform type');
57 | }
58 |
59 | this.value = value;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/vendor/webgl-debug.js:
--------------------------------------------------------------------------------
1 | /*
2 | ** Copyright (c) 2012 The Khronos Group Inc.
3 | **
4 | ** Permission is hereby granted, free of charge, to any person obtaining a
5 | ** copy of this software and/or associated documentation files (the
6 | ** "Materials"), to deal in the Materials without restriction, including
7 | ** without limitation the rights to use, copy, modify, merge, publish,
8 | ** distribute, sublicense, and/or sell copies of the Materials, and to
9 | ** permit persons to whom the Materials are furnished to do so, subject to
10 | ** the following conditions:
11 | **
12 | ** The above copyright notice and this permission notice shall be included
13 | ** in all copies or substantial portions of the Materials.
14 | **
15 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
22 | */
23 |
24 | // Various functions for helping debug WebGL apps.
25 |
26 | WebGLDebugUtils = function() {
27 |
28 | /**
29 | * Wrapped logging function.
30 | * @param {string} msg Message to log.
31 | */
32 | var log = function(msg) {
33 | if (window.console && window.console.log) {
34 | window.console.log(msg);
35 | }
36 | };
37 |
38 | /**
39 | * Wrapped error logging function.
40 | * @param {string} msg Message to log.
41 | */
42 | var error = function(msg) {
43 | if (window.console && window.console.error) {
44 | window.console.error(msg);
45 | } else {
46 | log(msg);
47 | }
48 | };
49 |
50 |
51 | /**
52 | * Which arguments are enums based on the number of arguments to the function.
53 | * So
54 | * 'texImage2D': {
55 | * 9: { 0:true, 2:true, 6:true, 7:true },
56 | * 6: { 0:true, 2:true, 3:true, 4:true },
57 | * },
58 | *
59 | * means if there are 9 arguments then 6 and 7 are enums, if there are 6
60 | * arguments 3 and 4 are enums
61 | *
62 | * @type {!Object.}
63 | */
64 | var glValidEnumContexts = {
65 | // Generic setters and getters
66 |
67 | 'enable': {1: { 0:true }},
68 | 'disable': {1: { 0:true }},
69 | 'getParameter': {1: { 0:true }},
70 |
71 | // Rendering
72 |
73 | 'drawArrays': {3:{ 0:true }},
74 | 'drawElements': {4:{ 0:true, 2:true }},
75 |
76 | // Shaders
77 |
78 | 'createShader': {1: { 0:true }},
79 | 'getShaderParameter': {2: { 1:true }},
80 | 'getProgramParameter': {2: { 1:true }},
81 | 'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
82 |
83 | // Vertex attributes
84 |
85 | 'getVertexAttrib': {2: { 1:true }},
86 | 'vertexAttribPointer': {6: { 2:true }},
87 |
88 | // Textures
89 |
90 | 'bindTexture': {2: { 0:true }},
91 | 'activeTexture': {1: { 0:true }},
92 | 'getTexParameter': {2: { 0:true, 1:true }},
93 | 'texParameterf': {3: { 0:true, 1:true }},
94 | 'texParameteri': {3: { 0:true, 1:true, 2:true }},
95 | // texImage2D and texSubImage2D are defined below with WebGL 2 entrypoints
96 | 'copyTexImage2D': {8: { 0:true, 2:true }},
97 | 'copyTexSubImage2D': {8: { 0:true }},
98 | 'generateMipmap': {1: { 0:true }},
99 | // compressedTexImage2D and compressedTexSubImage2D are defined below with WebGL 2 entrypoints
100 |
101 | // Buffer objects
102 |
103 | 'bindBuffer': {2: { 0:true }},
104 | // bufferData and bufferSubData are defined below with WebGL 2 entrypoints
105 | 'getBufferParameter': {2: { 0:true, 1:true }},
106 |
107 | // Renderbuffers and framebuffers
108 |
109 | 'pixelStorei': {2: { 0:true, 1:true }},
110 | // readPixels is defined below with WebGL 2 entrypoints
111 | 'bindRenderbuffer': {2: { 0:true }},
112 | 'bindFramebuffer': {2: { 0:true }},
113 | 'checkFramebufferStatus': {1: { 0:true }},
114 | 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
115 | 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
116 | 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
117 | 'getRenderbufferParameter': {2: { 0:true, 1:true }},
118 | 'renderbufferStorage': {4: { 0:true, 1:true }},
119 |
120 | // Frame buffer operations (clear, blend, depth test, stencil)
121 |
122 | 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}},
123 | 'depthFunc': {1: { 0:true }},
124 | 'blendFunc': {2: { 0:true, 1:true }},
125 | 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
126 | 'blendEquation': {1: { 0:true }},
127 | 'blendEquationSeparate': {2: { 0:true, 1:true }},
128 | 'stencilFunc': {3: { 0:true }},
129 | 'stencilFuncSeparate': {4: { 0:true, 1:true }},
130 | 'stencilMaskSeparate': {2: { 0:true }},
131 | 'stencilOp': {3: { 0:true, 1:true, 2:true }},
132 | 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
133 |
134 | // Culling
135 |
136 | 'cullFace': {1: { 0:true }},
137 | 'frontFace': {1: { 0:true }},
138 |
139 | // ANGLE_instanced_arrays extension
140 |
141 | 'drawArraysInstancedANGLE': {4: { 0:true }},
142 | 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
143 |
144 | // EXT_blend_minmax extension
145 |
146 | 'blendEquationEXT': {1: { 0:true }},
147 |
148 | // WebGL 2 Buffer objects
149 |
150 | 'bufferData': {
151 | 3: { 0:true, 2:true }, // WebGL 1
152 | 4: { 0:true, 2:true }, // WebGL 2
153 | 5: { 0:true, 2:true } // WebGL 2
154 | },
155 | 'bufferSubData': {
156 | 3: { 0:true }, // WebGL 1
157 | 4: { 0:true }, // WebGL 2
158 | 5: { 0:true } // WebGL 2
159 | },
160 | 'copyBufferSubData': {5: { 0:true, 1:true }},
161 | 'getBufferSubData': {3: { 0:true }, 4: { 0:true }, 5: { 0:true }},
162 |
163 | // WebGL 2 Framebuffer objects
164 |
165 | 'blitFramebuffer': {10: { 8: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }, 9:true }},
166 | 'framebufferTextureLayer': {5: { 0:true, 1:true }},
167 | 'invalidateFramebuffer': {2: { 0:true }},
168 | 'invalidateSubFramebuffer': {6: { 0:true }},
169 | 'readBuffer': {1: { 0:true }},
170 |
171 | // WebGL 2 Renderbuffer objects
172 |
173 | 'getInternalformatParameter': {3: { 0:true, 1:true, 2:true }},
174 | 'renderbufferStorageMultisample': {5: { 0:true, 2:true }},
175 |
176 | // WebGL 2 Texture objects
177 |
178 | 'texStorage2D': {5: { 0:true, 2:true }},
179 | 'texStorage3D': {6: { 0:true, 2:true }},
180 | 'texImage2D': {
181 | 9: { 0:true, 2:true, 6:true, 7:true }, // WebGL 1 & 2
182 | 6: { 0:true, 2:true, 3:true, 4:true }, // WebGL 1
183 | 10: { 0:true, 2:true, 6:true, 7:true } // WebGL 2
184 | },
185 | 'texImage3D': {
186 | 10: { 0:true, 2:true, 7:true, 8:true },
187 | 11: { 0:true, 2:true, 7:true, 8:true }
188 | },
189 | 'texSubImage2D': {
190 | 9: { 0:true, 6:true, 7:true }, // WebGL 1 & 2
191 | 7: { 0:true, 4:true, 5:true }, // WebGL 1
192 | 10: { 0:true, 6:true, 7:true } // WebGL 2
193 | },
194 | 'texSubImage3D': {
195 | 11: { 0:true, 8:true, 9:true },
196 | 12: { 0:true, 8:true, 9:true }
197 | },
198 | 'copyTexSubImage3D': {9: { 0:true }},
199 | 'compressedTexImage2D': {
200 | 7: { 0: true, 2:true }, // WebGL 1 & 2
201 | 8: { 0: true, 2:true }, // WebGL 2
202 | 9: { 0: true, 2:true } // WebGL 2
203 | },
204 | 'compressedTexImage3D': {
205 | 8: { 0: true, 2:true },
206 | 9: { 0: true, 2:true },
207 | 10: { 0: true, 2:true }
208 | },
209 | 'compressedTexSubImage2D': {
210 | 8: { 0: true, 6:true }, // WebGL 1 & 2
211 | 9: { 0: true, 6:true }, // WebGL 2
212 | 10: { 0: true, 6:true } // WebGL 2
213 | },
214 | 'compressedTexSubImage3D': {
215 | 10: { 0: true, 8:true },
216 | 11: { 0: true, 8:true },
217 | 12: { 0: true, 8:true }
218 | },
219 |
220 | // WebGL 2 Vertex attribs
221 |
222 | 'vertexAttribIPointer': {5: { 2:true }},
223 |
224 | // WebGL 2 Writing to the drawing buffer
225 |
226 | 'drawArraysInstanced': {4: { 0:true }},
227 | 'drawElementsInstanced': {5: { 0:true, 2:true }},
228 | 'drawRangeElements': {6: { 0:true, 4:true }},
229 |
230 | // WebGL 2 Reading back pixels
231 |
232 | 'readPixels': {
233 | 7: { 4:true, 5:true }, // WebGL 1 & 2
234 | 8: { 4:true, 5:true } // WebGL 2
235 | },
236 |
237 | // WebGL 2 Multiple Render Targets
238 |
239 | 'clearBufferfv': {3: { 0:true }, 4: { 0:true }},
240 | 'clearBufferiv': {3: { 0:true }, 4: { 0:true }},
241 | 'clearBufferuiv': {3: { 0:true }, 4: { 0:true }},
242 | 'clearBufferfi': {4: { 0:true }},
243 |
244 | // WebGL 2 Query objects
245 |
246 | 'beginQuery': {2: { 0:true }},
247 | 'endQuery': {1: { 0:true }},
248 | 'getQuery': {2: { 0:true, 1:true }},
249 | 'getQueryParameter': {2: { 1:true }},
250 |
251 | // WebGL 2 Sampler objects
252 |
253 | 'samplerParameteri': {3: { 1:true, 2:true }},
254 | 'samplerParameterf': {3: { 1:true }},
255 | 'getSamplerParameter': {2: { 1:true }},
256 |
257 | // WebGL 2 Sync objects
258 |
259 | 'fenceSync': {2: { 0:true, 1: { 'enumBitwiseOr': [] } }},
260 | 'clientWaitSync': {3: { 1: { 'enumBitwiseOr': ['SYNC_FLUSH_COMMANDS_BIT'] } }},
261 | 'waitSync': {3: { 1: { 'enumBitwiseOr': [] } }},
262 | 'getSyncParameter': {2: { 1:true }},
263 |
264 | // WebGL 2 Transform Feedback
265 |
266 | 'bindTransformFeedback': {2: { 0:true }},
267 | 'beginTransformFeedback': {1: { 0:true }},
268 | 'transformFeedbackVaryings': {3: { 2:true }},
269 |
270 | // WebGL2 Uniform Buffer Objects and Transform Feedback Buffers
271 |
272 | 'bindBufferBase': {3: { 0:true }},
273 | 'bindBufferRange': {5: { 0:true }},
274 | 'getIndexedParameter': {2: { 0:true }},
275 | 'getActiveUniforms': {3: { 2:true }},
276 | 'getActiveUniformBlockParameter': {3: { 2:true }}
277 | };
278 |
279 | /**
280 | * Map of numbers to names.
281 | * @type {Object}
282 | */
283 | var glEnums = null;
284 |
285 | /**
286 | * Map of names to numbers.
287 | * @type {Object}
288 | */
289 | var enumStringToValue = null;
290 |
291 | /**
292 | * Initializes this module. Safe to call more than once.
293 | * @param {!WebGLRenderingContext} ctx A WebGL context. If
294 | * you have more than one context it doesn't matter which one
295 | * you pass in, it is only used to pull out constants.
296 | */
297 | function init(ctx) {
298 | if (glEnums == null) {
299 | glEnums = { };
300 | enumStringToValue = { };
301 | for (var propertyName in ctx) {
302 | if (typeof ctx[propertyName] == 'number') {
303 | glEnums[ctx[propertyName]] = propertyName;
304 | enumStringToValue[propertyName] = ctx[propertyName];
305 | }
306 | }
307 | }
308 | }
309 |
310 | /**
311 | * Checks the utils have been initialized.
312 | */
313 | function checkInit() {
314 | if (glEnums == null) {
315 | throw 'WebGLDebugUtils.init(ctx) not called';
316 | }
317 | }
318 |
319 | /**
320 | * Returns true or false if value matches any WebGL enum
321 | * @param {*} value Value to check if it might be an enum.
322 | * @return {boolean} True if value matches one of the WebGL defined enums
323 | */
324 | function mightBeEnum(value) {
325 | checkInit();
326 | return (glEnums[value] !== undefined);
327 | }
328 |
329 | /**
330 | * Gets an string version of an WebGL enum.
331 | *
332 | * Example:
333 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
334 | *
335 | * @param {number} value Value to return an enum for
336 | * @return {string} The string version of the enum.
337 | */
338 | function glEnumToString(value) {
339 | checkInit();
340 | var name = glEnums[value];
341 | return (name !== undefined) ? ("gl." + name) :
342 | ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + "");
343 | }
344 |
345 | /**
346 | * Returns the string version of a WebGL argument.
347 | * Attempts to convert enum arguments to strings.
348 | * @param {string} functionName the name of the WebGL function.
349 | * @param {number} numArgs the number of arguments passed to the function.
350 | * @param {number} argumentIndx the index of the argument.
351 | * @param {*} value The value of the argument.
352 | * @return {string} The value as a string.
353 | */
354 | function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
355 | var funcInfo = glValidEnumContexts[functionName];
356 | if (funcInfo !== undefined) {
357 | var funcInfo = funcInfo[numArgs];
358 | if (funcInfo !== undefined) {
359 | if (funcInfo[argumentIndex]) {
360 | if (typeof funcInfo[argumentIndex] === 'object' &&
361 | funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) {
362 | var enums = funcInfo[argumentIndex]['enumBitwiseOr'];
363 | var orResult = 0;
364 | var orEnums = [];
365 | for (var i = 0; i < enums.length; ++i) {
366 | var enumValue = enumStringToValue[enums[i]];
367 | if ((value & enumValue) !== 0) {
368 | orResult |= enumValue;
369 | orEnums.push(glEnumToString(enumValue));
370 | }
371 | }
372 | if (orResult === value) {
373 | return orEnums.join(' | ');
374 | } else {
375 | return glEnumToString(value);
376 | }
377 | } else {
378 | return glEnumToString(value);
379 | }
380 | }
381 | }
382 | }
383 | if (value === null) {
384 | return "null";
385 | } else if (value === undefined) {
386 | return "undefined";
387 | } else {
388 | return value.toString();
389 | }
390 | }
391 |
392 | /**
393 | * Converts the arguments of a WebGL function to a string.
394 | * Attempts to convert enum arguments to strings.
395 | *
396 | * @param {string} functionName the name of the WebGL function.
397 | * @param {number} args The arguments.
398 | * @return {string} The arguments as a string.
399 | */
400 | function glFunctionArgsToString(functionName, args) {
401 | // apparently we can't do args.join(",");
402 | var argStr = "";
403 | var numArgs = args.length;
404 | for (var ii = 0; ii < numArgs; ++ii) {
405 | argStr += ((ii == 0) ? '' : ', ') +
406 | glFunctionArgToString(functionName, numArgs, ii, args[ii]);
407 | }
408 | return argStr;
409 | };
410 |
411 |
412 | function makePropertyWrapper(wrapper, original, propertyName) {
413 | //log("wrap prop: " + propertyName);
414 | wrapper.__defineGetter__(propertyName, function() {
415 | return original[propertyName];
416 | });
417 | // TODO(gmane): this needs to handle properties that take more than
418 | // one value?
419 | wrapper.__defineSetter__(propertyName, function(value) {
420 | //log("set: " + propertyName);
421 | original[propertyName] = value;
422 | });
423 | }
424 |
425 | // Makes a function that calls a function on another object.
426 | function makeFunctionWrapper(original, functionName) {
427 | //log("wrap fn: " + functionName);
428 | var f = original[functionName];
429 | return function() {
430 | //log("call: " + functionName);
431 | var result = f.apply(original, arguments);
432 | return result;
433 | };
434 | }
435 |
436 | /**
437 | * Given a WebGL context returns a wrapped context that calls
438 | * gl.getError after every command and calls a function if the
439 | * result is not gl.NO_ERROR.
440 | *
441 | * @param {!WebGLRenderingContext} ctx The webgl context to
442 | * wrap.
443 | * @param {!function(err, funcName, args): void} opt_onErrorFunc
444 | * The function to call when gl.getError returns an
445 | * error. If not specified the default function calls
446 | * console.log with a message.
447 | * @param {!function(funcName, args): void} opt_onFunc The
448 | * function to call when each webgl function is called.
449 | * You can use this to log all calls for example.
450 | * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
451 | * to call getError on if different than ctx.
452 | */
453 | function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) {
454 | opt_err_ctx = opt_err_ctx || ctx;
455 | init(ctx);
456 | opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
457 | // apparently we can't do args.join(",");
458 | var argStr = "";
459 | var numArgs = args.length;
460 | for (var ii = 0; ii < numArgs; ++ii) {
461 | argStr += ((ii == 0) ? '' : ', ') +
462 | glFunctionArgToString(functionName, numArgs, ii, args[ii]);
463 | }
464 | error("WebGL error "+ glEnumToString(err) + " in "+ functionName +
465 | "(" + argStr + ")");
466 | };
467 |
468 | // Holds booleans for each GL error so after we get the error ourselves
469 | // we can still return it to the client app.
470 | var glErrorShadow = { };
471 |
472 | // Makes a function that calls a WebGL function and then calls getError.
473 | function makeErrorWrapper(ctx, functionName) {
474 | return function() {
475 | if (opt_onFunc) {
476 | opt_onFunc(functionName, arguments);
477 | }
478 | var result = ctx[functionName].apply(ctx, arguments);
479 | var err = opt_err_ctx.getError();
480 | if (err != 0) {
481 | glErrorShadow[err] = true;
482 | opt_onErrorFunc(err, functionName, arguments);
483 | }
484 | return result;
485 | };
486 | }
487 |
488 | // Make a an object that has a copy of every property of the WebGL context
489 | // but wraps all functions.
490 | var wrapper = {};
491 | for (var propertyName in ctx) {
492 | if (typeof ctx[propertyName] == 'function') {
493 | if (propertyName != 'getExtension') {
494 | wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
495 | } else {
496 | var wrapped = makeErrorWrapper(ctx, propertyName);
497 | wrapper[propertyName] = function () {
498 | var result = wrapped.apply(ctx, arguments);
499 | if (!result) {
500 | return null;
501 | }
502 | return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx);
503 | };
504 | }
505 | } else {
506 | makePropertyWrapper(wrapper, ctx, propertyName);
507 | }
508 | }
509 |
510 | // Override the getError function with one that returns our saved results.
511 | wrapper.getError = function() {
512 | for (var err in glErrorShadow) {
513 | if (glErrorShadow.hasOwnProperty(err)) {
514 | if (glErrorShadow[err]) {
515 | glErrorShadow[err] = false;
516 | return err;
517 | }
518 | }
519 | }
520 | return ctx.NO_ERROR;
521 | };
522 |
523 | return wrapper;
524 | }
525 |
526 | function resetToInitialState(ctx) {
527 | var isWebGL2RenderingContext = !!ctx.createTransformFeedback;
528 |
529 | if (isWebGL2RenderingContext) {
530 | ctx.bindVertexArray(null);
531 | }
532 |
533 | var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
534 | var tmp = ctx.createBuffer();
535 | ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
536 | for (var ii = 0; ii < numAttribs; ++ii) {
537 | ctx.disableVertexAttribArray(ii);
538 | ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
539 | ctx.vertexAttrib1f(ii, 0);
540 | if (isWebGL2RenderingContext) {
541 | ctx.vertexAttribDivisor(ii, 0);
542 | }
543 | }
544 | ctx.deleteBuffer(tmp);
545 |
546 | var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
547 | for (var ii = 0; ii < numTextureUnits; ++ii) {
548 | ctx.activeTexture(ctx.TEXTURE0 + ii);
549 | ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
550 | ctx.bindTexture(ctx.TEXTURE_2D, null);
551 | if (isWebGL2RenderingContext) {
552 | ctx.bindTexture(ctx.TEXTURE_2D_ARRAY, null);
553 | ctx.bindTexture(ctx.TEXTURE_3D, null);
554 | ctx.bindSampler(ii, null);
555 | }
556 | }
557 |
558 | ctx.activeTexture(ctx.TEXTURE0);
559 | ctx.useProgram(null);
560 | ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
561 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
562 | ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
563 | ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
564 | ctx.disable(ctx.BLEND);
565 | ctx.disable(ctx.CULL_FACE);
566 | ctx.disable(ctx.DEPTH_TEST);
567 | ctx.disable(ctx.DITHER);
568 | ctx.disable(ctx.SCISSOR_TEST);
569 | ctx.blendColor(0, 0, 0, 0);
570 | ctx.blendEquation(ctx.FUNC_ADD);
571 | ctx.blendFunc(ctx.ONE, ctx.ZERO);
572 | ctx.clearColor(0, 0, 0, 0);
573 | ctx.clearDepth(1);
574 | ctx.clearStencil(-1);
575 | ctx.colorMask(true, true, true, true);
576 | ctx.cullFace(ctx.BACK);
577 | ctx.depthFunc(ctx.LESS);
578 | ctx.depthMask(true);
579 | ctx.depthRange(0, 1);
580 | ctx.frontFace(ctx.CCW);
581 | ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
582 | ctx.lineWidth(1);
583 | ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
584 | ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
585 | ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
586 | ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
587 | // TODO: Delete this IF.
588 | if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
589 | ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
590 | }
591 | ctx.polygonOffset(0, 0);
592 | ctx.sampleCoverage(1, false);
593 | ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
594 | ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
595 | ctx.stencilMask(0xFFFFFFFF);
596 | ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
597 | ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
598 | ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
599 |
600 | if (isWebGL2RenderingContext) {
601 | ctx.drawBuffers([ctx.BACK]);
602 | ctx.readBuffer(ctx.BACK);
603 | ctx.bindBuffer(ctx.COPY_READ_BUFFER, null);
604 | ctx.bindBuffer(ctx.COPY_WRITE_BUFFER, null);
605 | ctx.bindBuffer(ctx.PIXEL_PACK_BUFFER, null);
606 | ctx.bindBuffer(ctx.PIXEL_UNPACK_BUFFER, null);
607 | var numTransformFeedbacks = ctx.getParameter(ctx.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
608 | for (var ii = 0; ii < numTransformFeedbacks; ++ii) {
609 | ctx.bindBufferBase(ctx.TRANSFORM_FEEDBACK_BUFFER, ii, null);
610 | }
611 | var numUBOs = ctx.getParameter(ctx.MAX_UNIFORM_BUFFER_BINDINGS);
612 | for (var ii = 0; ii < numUBOs; ++ii) {
613 | ctx.bindBufferBase(ctx.UNIFORM_BUFFER, ii, null);
614 | }
615 | ctx.disable(ctx.RASTERIZER_DISCARD);
616 | ctx.pixelStorei(ctx.UNPACK_IMAGE_HEIGHT, 0);
617 | ctx.pixelStorei(ctx.UNPACK_SKIP_IMAGES, 0);
618 | ctx.pixelStorei(ctx.UNPACK_ROW_LENGTH, 0);
619 | ctx.pixelStorei(ctx.UNPACK_SKIP_ROWS, 0);
620 | ctx.pixelStorei(ctx.UNPACK_SKIP_PIXELS, 0);
621 | ctx.pixelStorei(ctx.PACK_ROW_LENGTH, 0);
622 | ctx.pixelStorei(ctx.PACK_SKIP_ROWS, 0);
623 | ctx.pixelStorei(ctx.PACK_SKIP_PIXELS, 0);
624 | ctx.hint(ctx.FRAGMENT_SHADER_DERIVATIVE_HINT, ctx.DONT_CARE);
625 | }
626 |
627 | // TODO: This should NOT be needed but Firefox fails with 'hint'
628 | while(ctx.getError());
629 | }
630 |
631 | function makeLostContextSimulatingCanvas(canvas) {
632 | var unwrappedContext_;
633 | var wrappedContext_;
634 | var onLost_ = [];
635 | var onRestored_ = [];
636 | var wrappedContext_ = {};
637 | var contextId_ = 1;
638 | var contextLost_ = false;
639 | var resourceId_ = 0;
640 | var resourceDb_ = [];
641 | var numCallsToLoseContext_ = 0;
642 | var numCalls_ = 0;
643 | var canRestore_ = false;
644 | var restoreTimeout_ = 0;
645 | var isWebGL2RenderingContext;
646 |
647 | // Holds booleans for each GL error so can simulate errors.
648 | var glErrorShadow_ = { };
649 |
650 | canvas.getContext = function(f) {
651 | return function() {
652 | var ctx = f.apply(canvas, arguments);
653 | // Did we get a context and is it a WebGL context?
654 | if ((ctx instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext))) {
655 | if (ctx != unwrappedContext_) {
656 | if (unwrappedContext_) {
657 | throw "got different context"
658 | }
659 | isWebGL2RenderingContext = window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext);
660 | unwrappedContext_ = ctx;
661 | wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);
662 | }
663 | return wrappedContext_;
664 | }
665 | return ctx;
666 | }
667 | }(canvas.getContext);
668 |
669 | function wrapEvent(listener) {
670 | if (typeof(listener) == "function") {
671 | return listener;
672 | } else {
673 | return function(info) {
674 | listener.handleEvent(info);
675 | }
676 | }
677 | }
678 |
679 | var addOnContextLostListener = function(listener) {
680 | onLost_.push(wrapEvent(listener));
681 | };
682 |
683 | var addOnContextRestoredListener = function(listener) {
684 | onRestored_.push(wrapEvent(listener));
685 | };
686 |
687 |
688 | function wrapAddEventListener(canvas) {
689 | var f = canvas.addEventListener;
690 | canvas.addEventListener = function(type, listener, bubble) {
691 | switch (type) {
692 | case 'webglcontextlost':
693 | addOnContextLostListener(listener);
694 | break;
695 | case 'webglcontextrestored':
696 | addOnContextRestoredListener(listener);
697 | break;
698 | default:
699 | f.apply(canvas, arguments);
700 | }
701 | };
702 | }
703 |
704 | wrapAddEventListener(canvas);
705 |
706 | canvas.loseContext = function() {
707 | if (!contextLost_) {
708 | contextLost_ = true;
709 | numCallsToLoseContext_ = 0;
710 | ++contextId_;
711 | while (unwrappedContext_.getError());
712 | clearErrors();
713 | glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;
714 | var event = makeWebGLContextEvent("context lost");
715 | var callbacks = onLost_.slice();
716 | setTimeout(function() {
717 | //log("numCallbacks:" + callbacks.length);
718 | for (var ii = 0; ii < callbacks.length; ++ii) {
719 | //log("calling callback:" + ii);
720 | callbacks[ii](event);
721 | }
722 | if (restoreTimeout_ >= 0) {
723 | setTimeout(function() {
724 | canvas.restoreContext();
725 | }, restoreTimeout_);
726 | }
727 | }, 0);
728 | }
729 | };
730 |
731 | canvas.restoreContext = function() {
732 | if (contextLost_) {
733 | if (onRestored_.length) {
734 | setTimeout(function() {
735 | if (!canRestore_) {
736 | throw "can not restore. webglcontestlost listener did not call event.preventDefault";
737 | }
738 | freeResources();
739 | resetToInitialState(unwrappedContext_);
740 | contextLost_ = false;
741 | numCalls_ = 0;
742 | canRestore_ = false;
743 | var callbacks = onRestored_.slice();
744 | var event = makeWebGLContextEvent("context restored");
745 | for (var ii = 0; ii < callbacks.length; ++ii) {
746 | callbacks[ii](event);
747 | }
748 | }, 0);
749 | }
750 | }
751 | };
752 |
753 | canvas.loseContextInNCalls = function(numCalls) {
754 | if (contextLost_) {
755 | throw "You can not ask a lost contet to be lost";
756 | }
757 | numCallsToLoseContext_ = numCalls_ + numCalls;
758 | };
759 |
760 | canvas.getNumCalls = function() {
761 | return numCalls_;
762 | };
763 |
764 | canvas.setRestoreTimeout = function(timeout) {
765 | restoreTimeout_ = timeout;
766 | };
767 |
768 | function isWebGLObject(obj) {
769 | //return false;
770 | return (obj instanceof WebGLBuffer ||
771 | obj instanceof WebGLFramebuffer ||
772 | obj instanceof WebGLProgram ||
773 | obj instanceof WebGLRenderbuffer ||
774 | obj instanceof WebGLShader ||
775 | obj instanceof WebGLTexture);
776 | }
777 |
778 | function checkResources(args) {
779 | for (var ii = 0; ii < args.length; ++ii) {
780 | var arg = args[ii];
781 | if (isWebGLObject(arg)) {
782 | return arg.__webglDebugContextLostId__ == contextId_;
783 | }
784 | }
785 | return true;
786 | }
787 |
788 | function clearErrors() {
789 | var k = Object.keys(glErrorShadow_);
790 | for (var ii = 0; ii < k.length; ++ii) {
791 | delete glErrorShadow_[k[ii]];
792 | }
793 | }
794 |
795 | function loseContextIfTime() {
796 | ++numCalls_;
797 | if (!contextLost_) {
798 | if (numCallsToLoseContext_ == numCalls_) {
799 | canvas.loseContext();
800 | }
801 | }
802 | }
803 |
804 | // Makes a function that simulates WebGL when out of context.
805 | function makeLostContextFunctionWrapper(ctx, functionName) {
806 | var f = ctx[functionName];
807 | return function() {
808 | // log("calling:" + functionName);
809 | // Only call the functions if the context is not lost.
810 | loseContextIfTime();
811 | if (!contextLost_) {
812 | //if (!checkResources(arguments)) {
813 | // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;
814 | // return;
815 | //}
816 | var result = f.apply(ctx, arguments);
817 | return result;
818 | }
819 | };
820 | }
821 |
822 | function freeResources() {
823 | for (var ii = 0; ii < resourceDb_.length; ++ii) {
824 | var resource = resourceDb_[ii];
825 | if (resource instanceof WebGLBuffer) {
826 | unwrappedContext_.deleteBuffer(resource);
827 | } else if (resource instanceof WebGLFramebuffer) {
828 | unwrappedContext_.deleteFramebuffer(resource);
829 | } else if (resource instanceof WebGLProgram) {
830 | unwrappedContext_.deleteProgram(resource);
831 | } else if (resource instanceof WebGLRenderbuffer) {
832 | unwrappedContext_.deleteRenderbuffer(resource);
833 | } else if (resource instanceof WebGLShader) {
834 | unwrappedContext_.deleteShader(resource);
835 | } else if (resource instanceof WebGLTexture) {
836 | unwrappedContext_.deleteTexture(resource);
837 | }
838 | else if (isWebGL2RenderingContext) {
839 | if (resource instanceof WebGLQuery) {
840 | unwrappedContext_.deleteQuery(resource);
841 | } else if (resource instanceof WebGLSampler) {
842 | unwrappedContext_.deleteSampler(resource);
843 | } else if (resource instanceof WebGLSync) {
844 | unwrappedContext_.deleteSync(resource);
845 | } else if (resource instanceof WebGLTransformFeedback) {
846 | unwrappedContext_.deleteTransformFeedback(resource);
847 | } else if (resource instanceof WebGLVertexArrayObject) {
848 | unwrappedContext_.deleteVertexArray(resource);
849 | }
850 | }
851 | }
852 | }
853 |
854 | function makeWebGLContextEvent(statusMessage) {
855 | return {
856 | statusMessage: statusMessage,
857 | preventDefault: function() {
858 | canRestore_ = true;
859 | }
860 | };
861 | }
862 |
863 | return canvas;
864 |
865 | function makeLostContextSimulatingContext(ctx) {
866 | // copy all functions and properties to wrapper
867 | for (var propertyName in ctx) {
868 | if (typeof ctx[propertyName] == 'function') {
869 | wrappedContext_[propertyName] = makeLostContextFunctionWrapper(
870 | ctx, propertyName);
871 | } else {
872 | makePropertyWrapper(wrappedContext_, ctx, propertyName);
873 | }
874 | }
875 |
876 | // Wrap a few functions specially.
877 | wrappedContext_.getError = function() {
878 | loseContextIfTime();
879 | if (!contextLost_) {
880 | var err;
881 | while (err = unwrappedContext_.getError()) {
882 | glErrorShadow_[err] = true;
883 | }
884 | }
885 | for (var err in glErrorShadow_) {
886 | if (glErrorShadow_[err]) {
887 | delete glErrorShadow_[err];
888 | return err;
889 | }
890 | }
891 | return wrappedContext_.NO_ERROR;
892 | };
893 |
894 | var creationFunctions = [
895 | "createBuffer",
896 | "createFramebuffer",
897 | "createProgram",
898 | "createRenderbuffer",
899 | "createShader",
900 | "createTexture"
901 | ];
902 | if (isWebGL2RenderingContext) {
903 | creationFunctions.push(
904 | "createQuery",
905 | "createSampler",
906 | "fenceSync",
907 | "createTransformFeedback",
908 | "createVertexArray"
909 | );
910 | }
911 | for (var ii = 0; ii < creationFunctions.length; ++ii) {
912 | var functionName = creationFunctions[ii];
913 | wrappedContext_[functionName] = function(f) {
914 | return function() {
915 | loseContextIfTime();
916 | if (contextLost_) {
917 | return null;
918 | }
919 | var obj = f.apply(ctx, arguments);
920 | obj.__webglDebugContextLostId__ = contextId_;
921 | resourceDb_.push(obj);
922 | return obj;
923 | };
924 | }(ctx[functionName]);
925 | }
926 |
927 | var functionsThatShouldReturnNull = [
928 | "getActiveAttrib",
929 | "getActiveUniform",
930 | "getBufferParameter",
931 | "getContextAttributes",
932 | "getAttachedShaders",
933 | "getFramebufferAttachmentParameter",
934 | "getParameter",
935 | "getProgramParameter",
936 | "getProgramInfoLog",
937 | "getRenderbufferParameter",
938 | "getShaderParameter",
939 | "getShaderInfoLog",
940 | "getShaderSource",
941 | "getTexParameter",
942 | "getUniform",
943 | "getUniformLocation",
944 | "getVertexAttrib"
945 | ];
946 | if (isWebGL2RenderingContext) {
947 | functionsThatShouldReturnNull.push(
948 | "getInternalformatParameter",
949 | "getQuery",
950 | "getQueryParameter",
951 | "getSamplerParameter",
952 | "getSyncParameter",
953 | "getTransformFeedbackVarying",
954 | "getIndexedParameter",
955 | "getUniformIndices",
956 | "getActiveUniforms",
957 | "getActiveUniformBlockParameter",
958 | "getActiveUniformBlockName"
959 | );
960 | }
961 | for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
962 | var functionName = functionsThatShouldReturnNull[ii];
963 | wrappedContext_[functionName] = function(f) {
964 | return function() {
965 | loseContextIfTime();
966 | if (contextLost_) {
967 | return null;
968 | }
969 | return f.apply(ctx, arguments);
970 | }
971 | }(wrappedContext_[functionName]);
972 | }
973 |
974 | var isFunctions = [
975 | "isBuffer",
976 | "isEnabled",
977 | "isFramebuffer",
978 | "isProgram",
979 | "isRenderbuffer",
980 | "isShader",
981 | "isTexture"
982 | ];
983 | if (isWebGL2RenderingContext) {
984 | isFunctions.push(
985 | "isQuery",
986 | "isSampler",
987 | "isSync",
988 | "isTransformFeedback",
989 | "isVertexArray"
990 | );
991 | }
992 | for (var ii = 0; ii < isFunctions.length; ++ii) {
993 | var functionName = isFunctions[ii];
994 | wrappedContext_[functionName] = function(f) {
995 | return function() {
996 | loseContextIfTime();
997 | if (contextLost_) {
998 | return false;
999 | }
1000 | return f.apply(ctx, arguments);
1001 | }
1002 | }(wrappedContext_[functionName]);
1003 | }
1004 |
1005 | wrappedContext_.checkFramebufferStatus = function(f) {
1006 | return function() {
1007 | loseContextIfTime();
1008 | if (contextLost_) {
1009 | return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;
1010 | }
1011 | return f.apply(ctx, arguments);
1012 | };
1013 | }(wrappedContext_.checkFramebufferStatus);
1014 |
1015 | wrappedContext_.getAttribLocation = function(f) {
1016 | return function() {
1017 | loseContextIfTime();
1018 | if (contextLost_) {
1019 | return -1;
1020 | }
1021 | return f.apply(ctx, arguments);
1022 | };
1023 | }(wrappedContext_.getAttribLocation);
1024 |
1025 | wrappedContext_.getVertexAttribOffset = function(f) {
1026 | return function() {
1027 | loseContextIfTime();
1028 | if (contextLost_) {
1029 | return 0;
1030 | }
1031 | return f.apply(ctx, arguments);
1032 | };
1033 | }(wrappedContext_.getVertexAttribOffset);
1034 |
1035 | wrappedContext_.isContextLost = function() {
1036 | return contextLost_;
1037 | };
1038 |
1039 | if (isWebGL2RenderingContext) {
1040 | wrappedContext_.getFragDataLocation = function(f) {
1041 | return function() {
1042 | loseContextIfTime();
1043 | if (contextLost_) {
1044 | return -1;
1045 | }
1046 | return f.apply(ctx, arguments);
1047 | };
1048 | }(wrappedContext_.getFragDataLocation);
1049 |
1050 | wrappedContext_.clientWaitSync = function(f) {
1051 | return function() {
1052 | loseContextIfTime();
1053 | if (contextLost_) {
1054 | return wrappedContext_.WAIT_FAILED;
1055 | }
1056 | return f.apply(ctx, arguments);
1057 | };
1058 | }(wrappedContext_.clientWaitSync);
1059 |
1060 | wrappedContext_.getUniformBlockIndex = function(f) {
1061 | return function() {
1062 | loseContextIfTime();
1063 | if (contextLost_) {
1064 | return wrappedContext_.INVALID_INDEX;
1065 | }
1066 | return f.apply(ctx, arguments);
1067 | };
1068 | }(wrappedContext_.getUniformBlockIndex);
1069 | }
1070 |
1071 | return wrappedContext_;
1072 | }
1073 | }
1074 |
1075 | return {
1076 | /**
1077 | * Initializes this module. Safe to call more than once.
1078 | * @param {!WebGLRenderingContext} ctx A WebGL context. If
1079 | * you have more than one context it doesn't matter which one
1080 | * you pass in, it is only used to pull out constants.
1081 | */
1082 | 'init': init,
1083 |
1084 | /**
1085 | * Returns true or false if value matches any WebGL enum
1086 | * @param {*} value Value to check if it might be an enum.
1087 | * @return {boolean} True if value matches one of the WebGL defined enums
1088 | */
1089 | 'mightBeEnum': mightBeEnum,
1090 |
1091 | /**
1092 | * Gets an string version of an WebGL enum.
1093 | *
1094 | * Example:
1095 | * WebGLDebugUtil.init(ctx);
1096 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
1097 | *
1098 | * @param {number} value Value to return an enum for
1099 | * @return {string} The string version of the enum.
1100 | */
1101 | 'glEnumToString': glEnumToString,
1102 |
1103 | /**
1104 | * Converts the argument of a WebGL function to a string.
1105 | * Attempts to convert enum arguments to strings.
1106 | *
1107 | * Example:
1108 | * WebGLDebugUtil.init(ctx);
1109 | * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D);
1110 | *
1111 | * would return 'TEXTURE_2D'
1112 | *
1113 | * @param {string} functionName the name of the WebGL function.
1114 | * @param {number} numArgs The number of arguments
1115 | * @param {number} argumentIndx the index of the argument.
1116 | * @param {*} value The value of the argument.
1117 | * @return {string} The value as a string.
1118 | */
1119 | 'glFunctionArgToString': glFunctionArgToString,
1120 |
1121 | /**
1122 | * Converts the arguments of a WebGL function to a string.
1123 | * Attempts to convert enum arguments to strings.
1124 | *
1125 | * @param {string} functionName the name of the WebGL function.
1126 | * @param {number} args The arguments.
1127 | * @return {string} The arguments as a string.
1128 | */
1129 | 'glFunctionArgsToString': glFunctionArgsToString,
1130 |
1131 | /**
1132 | * Given a WebGL context returns a wrapped context that calls
1133 | * gl.getError after every command and calls a function if the
1134 | * result is not NO_ERROR.
1135 | *
1136 | * You can supply your own function if you want. For example, if you'd like
1137 | * an exception thrown on any GL error you could do this
1138 | *
1139 | * function throwOnGLError(err, funcName, args) {
1140 | * throw WebGLDebugUtils.glEnumToString(err) +
1141 | * " was caused by call to " + funcName;
1142 | * };
1143 | *
1144 | * ctx = WebGLDebugUtils.makeDebugContext(
1145 | * canvas.getContext("webgl"), throwOnGLError);
1146 | *
1147 | * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
1148 | * @param {!function(err, funcName, args): void} opt_onErrorFunc The function
1149 | * to call when gl.getError returns an error. If not specified the default
1150 | * function calls console.log with a message.
1151 | * @param {!function(funcName, args): void} opt_onFunc The
1152 | * function to call when each webgl function is called. You
1153 | * can use this to log all calls for example.
1154 | */
1155 | 'makeDebugContext': makeDebugContext,
1156 |
1157 | /**
1158 | * Given a canvas element returns a wrapped canvas element that will
1159 | * simulate lost context. The canvas returned adds the following functions.
1160 | *
1161 | * loseContext:
1162 | * simulates a lost context event.
1163 | *
1164 | * restoreContext:
1165 | * simulates the context being restored.
1166 | *
1167 | * lostContextInNCalls:
1168 | * loses the context after N gl calls.
1169 | *
1170 | * getNumCalls:
1171 | * tells you how many gl calls there have been so far.
1172 | *
1173 | * setRestoreTimeout:
1174 | * sets the number of milliseconds until the context is restored
1175 | * after it has been lost. Defaults to 0. Pass -1 to prevent
1176 | * automatic restoring.
1177 | *
1178 | * @param {!Canvas} canvas The canvas element to wrap.
1179 | */
1180 | 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,
1181 |
1182 | /**
1183 | * Resets a context to the initial state.
1184 | * @param {!WebGLRenderingContext} ctx The webgl context to
1185 | * reset.
1186 | */
1187 | 'resetToInitialState': resetToInitialState
1188 | };
1189 |
1190 | }();
1191 |
1192 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 |
4 | module.exports = {
5 | entry: './src/index.js',
6 | output: {
7 | path: path.join(__dirname, 'dist'),
8 | filename: 'mini-webgl.js',
9 | libraryTarget: 'umd',
10 | library: 'MiniWebGL',
11 | },
12 | module: {
13 | rules: [
14 | { test: /\.js/, exclude: /node_modules/, use: ['babel-loader'] },
15 | { test: /\.(vs|fs)$/, exclude: /node_modules/, use: ['raw-loader', 'glslify-loader'] },
16 | ]
17 | },
18 | resolve: {
19 | extensions: ['.js']
20 | },
21 | watch: true,
22 | };
23 |
--------------------------------------------------------------------------------