├── src ├── webgl-lint.js ├── wrap.js ├── parse-stack.js ├── check-framebuffer-feedback.js ├── vertex-array-manager.js ├── check-attributes-buffer-overflow.js ├── samplers.js └── utils.js ├── .gitignore ├── .eslintignore ├── .npmignore ├── webgl-lint.png ├── test ├── mocha-support.js ├── tests │ ├── wrong-number-of-arguments-tests.js │ ├── extension-enum-tests.js │ ├── drawing-test.js │ ├── data-view-tests.js │ ├── uniform-mismatch-tests.js │ ├── uniform-buffer-tests.js │ ├── uniform-type-tests.js │ ├── enum-tests.js │ ├── draw-reports-program-and-vao-tests.js │ ├── ignore-uniforms-tests.js │ ├── framebuffer-feedback-tests.js │ ├── report-vao-tests.js │ ├── untagged-objects-tests.js │ ├── disable-tests.js │ ├── program-delete-tests.js │ ├── uniformXXv-tests.js │ ├── bad-data-tests.js │ ├── naming-tests.js │ ├── arrays-with-offsets-tests.js │ ├── buffer-tests.js │ ├── zero-matrix-tests.js │ ├── shader-fail-tests.js │ ├── recommended-extensions.js │ ├── unset-uniform-tests.js │ ├── undefined-uniform-tests.js │ ├── buffer-overflow-tests.js │ ├── redundant-state-tests.js │ └── unrenderable-texture-tests.js ├── index.js ├── webgl.js ├── index.html ├── assert.js └── mocha.css ├── rollup.config.js ├── .vscode └── settings.json ├── package.json ├── webgl-lint-check-redundant-state-setting.js ├── LICENSE.md ├── .eslintrc.js └── README.md /src/webgl-lint.js: -------------------------------------------------------------------------------- 1 | import './wrap.js'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/js 2 | test/mocha.js 3 | /webgl-lint.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | rollup.config.js 4 | .* 5 | *.png 6 | -------------------------------------------------------------------------------- /webgl-lint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greggman/webgl-lint/HEAD/webgl-lint.png -------------------------------------------------------------------------------- /test/mocha-support.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | 3 | export const describe = window.describe; 4 | export const it = window.it; 5 | export const before = window.before; 6 | export const after = window.after; 7 | export const beforeEach = window.beforeEach; 8 | export const afterEach = window.afterEach; 9 | 10 | -------------------------------------------------------------------------------- /test/tests/wrong-number-of-arguments-tests.js: -------------------------------------------------------------------------------- 1 | import {createContext} from '../webgl.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | 5 | describe('wrong number of arguments test', () => { 6 | 7 | it('test wrong number of args', () => { 8 | const {gl} = createContext(); 9 | assertThrowsWith(() => { 10 | gl.enable(gl.DEPTH_TEST, 0); // wrong number of args 11 | }, [/'enable'.*?2 arguments/]); 12 | }); 13 | 14 | }); -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import fs from 'fs'; 3 | 4 | const pkg = JSON.parse(fs.readFileSync('package.json', {encoding: 'utf8'})); 5 | const banner = `/* webgl-lint@${pkg.version}, license MIT */`; 6 | 7 | export default [ 8 | { 9 | input: 'src/webgl-lint.js', 10 | plugins: [ 11 | resolve({ 12 | modulesOnly: true, 13 | }), 14 | ], 15 | output: [ 16 | { 17 | format: 'umd', 18 | file: 'webgl-lint.js', 19 | indent: ' ', 20 | banner, 21 | }, 22 | ], 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Bufferfi", 4 | "Bufferfv", 5 | "Bufferiv", 6 | "Bufferuiv", 7 | "bvec", 8 | "camelcase", 9 | "eqeqeq", 10 | "framebuffer", 11 | "gman", 12 | "highp", 13 | "isampler", 14 | "mediump", 15 | "minmax", 16 | "Multisample", 17 | "nonwords", 18 | "phong", 19 | "proto", 20 | "Renderability", 21 | "Renderbuffers", 22 | "SNORM", 23 | "SRGB", 24 | "Unrenderability", 25 | "unrenderable", 26 | "untagging", 27 | "usampler", 28 | "uvec", 29 | "webgl" 30 | ] 31 | } -------------------------------------------------------------------------------- /test/tests/extension-enum-tests.js: -------------------------------------------------------------------------------- 1 | import {createContext} from '../webgl.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | 5 | describe('extension enum test', () => { 6 | 7 | it('test extension enums', () => { 8 | const {gl} = createContext(); 9 | const ext = gl.getExtension('OES_standard_derivatives'); 10 | if (!ext) { 11 | throw new Error('FRAGMENT_SHADER_DERIVATIVE_HINT_OES'); 12 | } 13 | assertThrowsWith(() => { 14 | gl.enable(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES); 15 | }, [/FRAGMENT_SHADER_DERIVATIVE_HINT_OES/]); 16 | }); 17 | 18 | }); -------------------------------------------------------------------------------- /test/tests/drawing-test.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext} from '../webgl.js'; 4 | 5 | describe('test drawing', () => { 6 | it('draws', () => { 7 | const {gl, tagObject} = createContext(); 8 | const prg = twgl.createProgram(gl, [ 9 | ` 10 | void main() { 11 | gl_Position = vec4(0, 0, 0, 1); 12 | gl_PointSize = 128.0; 13 | } 14 | `, 15 | ` 16 | precision mediump float; 17 | uniform vec4 pointColor; 18 | void main() { 19 | gl_FragColor = pointColor; 20 | } 21 | `, 22 | ]); 23 | tagObject(prg, 'point program'); 24 | gl.useProgram(prg); 25 | const loc = gl.getUniformLocation(prg, 'pointColor'); 26 | gl.uniform4fv(loc, [1, 0.7, .5, 1]); 27 | gl.drawArrays(gl.POINTS, 0, 1); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/tests/data-view-tests.js: -------------------------------------------------------------------------------- 1 | import {createContext} from '../webgl.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | 5 | describe('DataView tests', () => { 6 | 7 | it('test good data with DataView', () => { 8 | const {gl, tagObject} = createContext(); 9 | const buf = gl.createBuffer(); 10 | tagObject(buf, 'positions-buffer'); 11 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 12 | gl.bufferData(gl.ARRAY_BUFFER, new DataView(new ArrayBuffer(13)), gl.STATIC_DRAW); 13 | }); 14 | 15 | it('test bad enum with DataView', () => { 16 | const {gl, tagObject} = createContext(); 17 | const buf = gl.createBuffer(); 18 | tagObject(buf, 'positions-buffer'); 19 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 20 | assertThrowsWith(() => { 21 | gl.bufferData(gl.ARRAY_BUFFER, new DataView(new ArrayBuffer(13)), gl.BLEND); 22 | }, [/positions-buffer/, /INVALID_ENUM/, /DataView/]); 23 | }); 24 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-lint", 3 | "version": "1.11.4", 4 | "description": "A semi strict WebGL checker", 5 | "main": "webgl-lint.js", 6 | "scripts": { 7 | "build": "rollup -c", 8 | "eslint": "eslint \"**/*.js\"", 9 | "pre-push": "npm run eslint", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/greggman/webgl-lint.git" 15 | }, 16 | "keywords": [ 17 | "webgl" 18 | ], 19 | "author": "Gregg Tavares", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/greggman/webgl-lint/issues" 23 | }, 24 | "homepage": "https://github.com/greggman/webgl-lint#readme", 25 | "devDependencies": { 26 | "eslint": "^7.5.0", 27 | "eslint-plugin-html": "^6.0.2", 28 | "eslint-plugin-optional-comma-spacing": "0.0.4", 29 | "eslint-plugin-require-trailing-comma": "0.0.1", 30 | "rollup": "^2.23.0", 31 | "rollup-plugin-node-resolve": "^5.2.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/tests/uniform-mismatch-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import assert from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('uniform mismatch tests', () => { 7 | it('test uniform mis-match', () => { 8 | const {gl, tagObject} = createContext(); 9 | const prg = twgl.createProgram(gl, [ 10 | ` 11 | void main() { 12 | gl_Position = vec4(0, 0, 0, 1); 13 | } 14 | `, 15 | ` 16 | precision mediump float; 17 | uniform vec4 pointColor; 18 | void main() { 19 | gl_FragColor = pointColor; 20 | } 21 | `, 22 | ]); 23 | tagObject(prg, 'point program'); 24 | gl.useProgram(prg); 25 | const loc = gl.getUniformLocation(prg, 'pointColor'); 26 | assert.throwsWith(() => { 27 | gl.uniform3fv(loc, [1, 0.7, .5]); // error, needs to be 4fv 28 | }, [/vec4 which is wrong for uniform3fv/]); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /test/tests/uniform-buffer-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertEqual} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext2} from '../webgl.js'; 5 | 6 | describe('uniform buffer tests', () => { 7 | 8 | it('test it does not warn about uniforms in UBOs when linking', () => { 9 | const {gl, ext, tagObject} = createContext2(); 10 | ext.setConfiguration({failUndefinedUniforms: true}); 11 | const prg = twgl.createProgram(gl, [ 12 | `#version 300 es 13 | uniform Foo { 14 | vec4 bar; 15 | vec4 moo; 16 | }; 17 | 18 | void main() { 19 | gl_Position = bar + moo; 20 | } 21 | `, 22 | `#version 300 es 23 | precision mediump float; 24 | uniform vec4 pointColor; 25 | out vec4 fragColor; 26 | void main() { 27 | fragColor = pointColor; 28 | } 29 | `, 30 | ]); 31 | tagObject(prg, 'ubo program'); 32 | gl.useProgram(prg); 33 | assertEqual(gl.getError(), gl.NO_ERROR); 34 | }); 35 | 36 | 37 | }); -------------------------------------------------------------------------------- /test/tests/uniform-type-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('uniform type tests', () => { 7 | 8 | it('test uniform fails if array of arrays passed to v func', () => { 9 | const {gl, tagObject} = createContext(); 10 | const prg = twgl.createProgram(gl, [ 11 | ` 12 | void main() { 13 | gl_Position = vec4(0, 0, 0, 1); 14 | } 15 | `, 16 | ` 17 | precision mediump float; 18 | uniform vec3 pointColor[4]; 19 | void main() { 20 | gl_FragColor = vec4(pointColor[3], 1); 21 | } 22 | `, 23 | ]); 24 | tagObject(prg, 'point program'); 25 | gl.useProgram(prg); 26 | const loc = gl.getUniformLocation(prg, 'pointColor'); 27 | const value = [ 28 | [1, 2, 3], 29 | [5, 6, 7], 30 | [9, 10, 11], 31 | [13, 14, 15], 32 | ]; 33 | assertThrowsWith(() => { 34 | gl.uniform3fv(loc, value); 35 | }, [/point program/, /pointColor/, /flat arrays/]); 36 | }); 37 | 38 | }); -------------------------------------------------------------------------------- /test/tests/enum-tests.js: -------------------------------------------------------------------------------- 1 | import {createContext} from '../webgl.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | 5 | describe('enum tests', () => { 6 | 7 | it('test bad enum 1', () => { 8 | const {gl} = createContext(); 9 | const buf = gl.createBuffer(); 10 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 11 | assertThrowsWith(() => { 12 | gl.vertexAttribPointer(0, 1, gl.BYE, false, 0, 0); // error 13 | }, [/argument.*?is undefined/]); 14 | }); 15 | 16 | it('test bad enum 2', () => { 17 | const {gl} = createContext(); 18 | gl.enable(gl.BLEND); 19 | assertThrowsWith(() => { 20 | gl.enable(gl.CULL_FADE); // error 21 | }, [/argument.*?is undefined/]); 22 | gl.enable(gl.DEPTH_TEST); 23 | }); 24 | 25 | it('test bad enum 3', () => { 26 | const {gl, tagObject} = createContext(); 27 | const tex = gl.createTexture(); 28 | tagObject(tex, 'depth-tex'); 29 | gl.bindTexture(gl.TEXTURE_2D, tex); 30 | assertThrowsWith(() => { 31 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT16, 512, 256, 0, gl.DEPTH_COMPONENT16, gl.INT, null); // error 32 | }, [/depth-tex.*?INVALID_VALUE/]); 33 | }); 34 | 35 | }); -------------------------------------------------------------------------------- /test/tests/draw-reports-program-and-vao-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext2} from '../webgl.js'; 5 | 6 | describe('draw reports program and vao tests', () => { 7 | 8 | it('test drawArrays with bad enum reports program and vao', () => { 9 | const {gl, tagObject} = createContext2(); 10 | if (!gl) { 11 | throw new Error('drawArraysBE vaoBE'); 12 | } 13 | 14 | const vs = ` 15 | attribute vec4 position; 16 | 17 | void main() { 18 | gl_Position = position; 19 | } 20 | `; 21 | 22 | const fs = ` 23 | precision mediump float; 24 | void main() { 25 | gl_FragColor = vec4(0); 26 | } 27 | `; 28 | 29 | const vao = gl.createVertexArray(); 30 | // tagObject(vao, 'vaoBE'); 31 | gl.bindVertexArray(vao); 32 | const prg = twgl.createProgram(gl, [vs, fs]); 33 | tagObject(prg, 'drawArraysBE'); 34 | 35 | gl.useProgram(prg); 36 | assertThrowsWith(() => { 37 | gl.drawArrays(gl.TRAINGLES, 0, 1); // error, TRIANGLES misspelled. 38 | }, [/drawArraysBE/, /WebGLVertexArrayObject\("\*UNTAGGED:VertexArray1\*"\)/]); 39 | }); 40 | 41 | }); -------------------------------------------------------------------------------- /test/tests/ignore-uniforms-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext} from '../webgl.js'; 4 | 5 | describe('ignore uniforms test', () => { 6 | 7 | it('test ignore uniforms', () => { 8 | const {gl, ext, tagObject} = createContext(); 9 | if (ext) { 10 | ext.setConfiguration({ignoreUniforms: ['perspective']}); 11 | } 12 | const prg = twgl.createProgram(gl, [ 13 | ` 14 | attribute vec4 position; 15 | uniform mat4 perspective; 16 | uniform mat4 view; 17 | uniform mat4 model; 18 | void main() { 19 | gl_Position = perspective * view * model * position; 20 | } 21 | `, 22 | ` 23 | precision mediump float; 24 | void main() { 25 | gl_FragColor = vec4(0); 26 | } 27 | `, 28 | ]); 29 | tagObject(prg, 'uniforms-with-matrices'); 30 | gl.useProgram(prg); 31 | gl.uniformMatrix4fv(gl.getUniformLocation(prg, 'view'), false, new Float32Array([ 32 | 0, 0, 0, 0, 33 | 0, 0, 0, 0.00000001, 34 | 0, 0, 0, 0, 35 | 0, 0, 0, 0, 36 | ])); 37 | gl.uniformMatrix4fv(gl.getUniformLocation(prg, 'perspective'), false, new Float32Array([ 38 | 0, 0, 0, 0, 39 | 0, 0, 0, 0, 40 | 0, 0, 0, 0, 41 | 0, 0, 0, 0, 42 | ])); 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /test/tests/framebuffer-feedback-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('framebuffer feedback tests', () => { 7 | 8 | it('test feedback check', () => { 9 | const {gl, tagObject} = createContext(); 10 | const vs = ` 11 | void main() { 12 | gl_Position = vec4(0, 0, 0, 1); 13 | gl_PointSize = 100.0; 14 | } 15 | `; 16 | 17 | const fs = ` 18 | precision mediump float; 19 | uniform sampler2D u_diffuse; 20 | void main() { 21 | gl_FragColor = texture2D(u_diffuse, vec2(0)); 22 | } 23 | `; 24 | 25 | const prg = twgl.createProgram(gl, [vs, fs]); 26 | tagObject(prg, 'fbPrg'); 27 | 28 | const tex = gl.createTexture(); 29 | tagObject(tex, 'fbTex'); 30 | gl.bindTexture(gl.TEXTURE_2D, tex); 31 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 32 | 33 | const fb = gl.createFramebuffer(); 34 | tagObject(fb, 'fbTest'); 35 | gl.bindFramebuffer(gl.FRAMEBUFFER, fb); 36 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); 37 | 38 | gl.useProgram(prg); 39 | assertThrowsWith(() => { 40 | gl.drawArrays(gl.POINTS, 0, 1); // feedback 41 | }, [/fbPrg/, /fbTex/, /fbTest/, /u_diffuse/, /COLOR_ATTACHMENT0/]); 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global mocha */ 2 | /* global URLSearchParams */ 3 | /* global window */ 4 | 5 | import './tests/arrays-with-offsets-tests.js'; 6 | import './tests/bad-data-tests.js'; 7 | import './tests/buffer-tests.js'; 8 | import './tests/buffer-overflow-tests.js'; 9 | import './tests/data-view-tests.js'; 10 | import './tests/disable-tests.js'; 11 | import './tests/draw-reports-program-and-vao-tests.js'; 12 | import './tests/drawing-test.js'; 13 | import './tests/framebuffer-feedback-tests.js'; 14 | import './tests/enum-tests.js'; 15 | import './tests/extension-enum-tests.js'; 16 | import './tests/ignore-uniforms-tests.js'; 17 | import './tests/naming-tests.js'; 18 | import './tests/program-delete-tests.js'; 19 | import './tests/redundant-state-tests.js'; 20 | import './tests/report-vao-tests.js'; 21 | import './tests/shader-fail-tests.js'; 22 | import './tests/undefined-uniform-tests.js'; 23 | import './tests/uniform-buffer-tests.js'; 24 | import './tests/uniform-mismatch-tests.js'; 25 | import './tests/uniform-type-tests.js'; 26 | import './tests/uniformXXv-tests.js'; 27 | import './tests/untagged-objects-tests.js'; 28 | import './tests/unrenderable-texture-tests.js'; 29 | import './tests/unset-uniform-tests.js'; 30 | import './tests/wrong-number-of-arguments-tests.js'; 31 | import './tests/zero-matrix-tests.js'; 32 | 33 | const settings = Object.fromEntries(new URLSearchParams(window.location.search).entries()); 34 | if (settings.reporter) { 35 | mocha.reporter(settings.reporter); 36 | } 37 | mocha.run((failures) => { 38 | window.testsPromiseInfo.resolve(failures); 39 | }); 40 | -------------------------------------------------------------------------------- /test/tests/report-vao-tests.js: -------------------------------------------------------------------------------- 1 | import {assertThrowsWith} from '../assert.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext} from '../webgl.js'; 4 | 5 | describe('report vao tests', () => { 6 | 7 | it('test vertex func reports vao', () => { 8 | const {gl, tagObject} = createContext(); 9 | const ext = gl.getExtension('OES_vertex_array_object'); 10 | if (!ext) { 11 | throw new Error('sphere-data'); // something to satisfy test. 12 | } 13 | const vao = ext.createVertexArrayOES(); 14 | tagObject(vao, 'sphere-data'); 15 | const buf = gl.createBuffer(); 16 | tagObject(buf, 'normals'); 17 | ext.bindVertexArrayOES(vao); 18 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 19 | assertThrowsWith(() => { 20 | gl.vertexAttribPointer(3, 5, gl.FLOAT, false, 0, 0); // error, size too large, INVALID_VALUE 21 | }, [/sphere-data/]); 22 | }); 23 | 24 | it('test vertex func reports vao 2', () => { 25 | const {gl, tagObject} = createContext(); 26 | const ext = gl.getExtension('OES_vertex_array_object'); 27 | if (!ext) { 28 | throw new Error('sphere-data'); // something to satisfy test. 29 | } 30 | const vao = ext.createVertexArrayOES(); 31 | tagObject(vao, 'sphere-data'); 32 | const buf = gl.createBuffer(); 33 | tagObject(buf, 'normals'); 34 | ext.bindVertexArrayOES(vao); 35 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 36 | assertThrowsWith(() => { 37 | gl.vertexAttribPointer(3, 4, gl.FLOATs, false, 0, 0); // error, undefined 38 | }, [/sphere-data/]); 39 | }); 40 | 41 | }); -------------------------------------------------------------------------------- /webgl-lint-check-redundant-state-setting.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | /* global WeakRef */ 3 | import './webgl-lint.js'; 4 | 5 | const glContextRefs = []; 6 | HTMLCanvasElement.prototype.getContext = (function(origFn) { 7 | return function(contextType, ...args) { 8 | const ctx = origFn.call(this, contextType, ...args); 9 | if (ctx && (contextType === 'webgl' || contextType === 'webgl2')) { 10 | if (glContextRefs.findIndex(ref => ref.deref() === ctx) < 0) { 11 | const ext = ctx.getExtension('GMAN_debug_helper'); 12 | ext.setConfiguration({maxDrawCalls: 0}); 13 | glContextRefs.push({ref: new WeakRef(ctx), ext}); 14 | } 15 | } 16 | return ctx; 17 | }; 18 | })(HTMLCanvasElement.prototype.getContext); 19 | 20 | window.requestAnimationFrame = (function(origFn) { 21 | return function(fn) { 22 | return origFn.call(this, (time) => { 23 | // add up all calls across contexts since we have no idea which contexts will render 24 | // Note: You could call getAndResetRedundantCallInfo yourself for your own context. 25 | const sums = {}; 26 | 27 | const refs = glContextRefs.filter(({ref}) => ref.deref()); 28 | glContextRefs.length = 0; 29 | glContextRefs.push(...refs); 30 | 31 | for (const {ext} of glContextRefs) { 32 | const info = ext.getAndResetRedundantCallInfo(); 33 | for (const [key, value] of Object.entries(info)) { 34 | sums[key] = (sums[key] || 0) + value; 35 | } 36 | } 37 | console.log('rc:', JSON.stringify(sums)); 38 | fn(time); 39 | }); 40 | }; 41 | })(window.requestAnimationFrame); -------------------------------------------------------------------------------- /test/tests/untagged-objects-tests.js: -------------------------------------------------------------------------------- 1 | import {assertThrowsWith} from '../assert.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext, createContext2} from '../webgl.js'; 4 | 5 | describe('untagged objects test', () => { 6 | 7 | it('test untagged buffer is called UNTAGGED:BufferX', () => { 8 | const {gl} = createContext(); 9 | const buf1 = gl.createBuffer(); 10 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1); 11 | assertThrowsWith(() => { 12 | gl.bufferData(gl.ARRAY_BUFFER, 3, gl.BLEND); 13 | }, [/\*UNTAGGED:Buffer1\*/]); 14 | const buf2 = gl.createBuffer(); 15 | gl.bindBuffer(gl.ARRAY_BUFFER, buf2); 16 | assertThrowsWith(() => { 17 | gl.bufferData(gl.ARRAY_BUFFER, 3, gl.BLEND); 18 | }, [/\*UNTAGGED:Buffer2\*/]); 19 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1); 20 | assertThrowsWith(() => { 21 | gl.bufferData(gl.ARRAY_BUFFER, 3, gl.BLEND); 22 | }, [/\*UNTAGGED:Buffer1\*/]); 23 | }); 24 | 25 | it('test untagged buffer is uses *unnamed* if default names are off', () => { 26 | const {gl, ext} = createContext(); 27 | ext.setConfiguration({makeDefaultTags: false}); 28 | const buf1 = gl.createBuffer(); 29 | gl.bindBuffer(gl.ARRAY_BUFFER, buf1); 30 | assertThrowsWith(() => { 31 | gl.bufferData(gl.ARRAY_BUFFER, 3, gl.BLEND); 32 | }, [/\*unnamed\*/]); 33 | }); 34 | 35 | it('test untagged sync', () => { 36 | const {gl} = createContext2(); 37 | if (!gl) { 38 | return; 39 | } 40 | const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); 41 | assertThrowsWith(() => { 42 | gl.clientWaitSync(sync, 0xFF, 0); 43 | }, [/\*UNTAGGED:Sync1\*/]); 44 | }); 45 | 46 | }); -------------------------------------------------------------------------------- /test/tests/disable-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertEqual, assertNotEqual} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('disable tests', () => { 7 | 8 | it('test disable', () => { 9 | const {gl, ext, vaoExt} = createContext(); 10 | if (!ext) { 11 | return; 12 | } 13 | const before = gl.bufferData.toString(); 14 | const beforeVAO = vaoExt ? vaoExt.createVertexArrayOES.toString() : 'b4'; 15 | ext.disable(); 16 | const after = gl.bufferData.toString(); 17 | const afterVAO = vaoExt ? vaoExt.createVertexArrayOES.toString() : 'af'; 18 | assertNotEqual(before, after); 19 | assertNotEqual(beforeVAO, afterVAO); 20 | }); 21 | 22 | it('test disable on maxDrawCalls', () => { 23 | const {gl, ext} = createContext(); 24 | if (!ext) { 25 | return; 26 | } 27 | ext.setConfiguration({ 28 | maxDrawCalls: 2, 29 | }); 30 | 31 | const prg = twgl.createProgram(gl, [ 32 | ` 33 | void main() { 34 | gl_Position = vec4(0, 0, 0, 1); 35 | gl_PointSize = 128.0; 36 | } 37 | `, 38 | ` 39 | precision mediump float; 40 | void main() { 41 | gl_FragColor = vec4(0); 42 | } 43 | `, 44 | ]); 45 | gl.useProgram(prg); 46 | const before = gl.bufferData.toString(); 47 | gl.drawArrays(gl.POINTS, 0, 1); 48 | const between = gl.bufferData.toString(); 49 | assertEqual(before, between); 50 | gl.drawArrays(gl.POINTS, 0, 1); 51 | const after = gl.bufferData.toString(); 52 | assertNotEqual(before, after); 53 | }); 54 | 55 | }); -------------------------------------------------------------------------------- /test/tests/program-delete-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext} from '../webgl.js'; 4 | 5 | describe('program re-link/delete tests', () => { 6 | 7 | it('test program re-link/delete', () => { 8 | const {gl, tagObject} = createContext(); 9 | const vs = ` 10 | attribute vec4 position; 11 | attribute vec2 texcoord; 12 | attribute vec4 color; 13 | 14 | varying vec4 v_color; 15 | varying vec2 v_texcoord; 16 | 17 | void main() { 18 | gl_Position = position; 19 | v_color = color; 20 | v_texcoord = texcoord; 21 | } 22 | `; 23 | 24 | const fs = ` 25 | precision mediump float; 26 | 27 | varying vec4 v_color; 28 | varying vec2 v_texcoord; 29 | uniform float u_mult; 30 | 31 | uniform sampler2D u_diffuse; 32 | 33 | void main() { 34 | gl_FragColor = texture2D(u_diffuse, v_texcoord) * v_color * u_mult; 35 | } 36 | `; 37 | 38 | const prg = twgl.createProgram(gl, [vs, fs]); 39 | tagObject(prg, 'prgToDelete'); 40 | gl.linkProgram(prg); 41 | gl.deleteProgram(prg); 42 | 43 | const prg2 = twgl.createProgram(gl, [vs, fs]); 44 | tagObject(prg2, 'prgToDelete2'); 45 | gl.getUniformLocation(prg2, 'u_mult'); 46 | gl.getUniformLocation(prg2, 'u_mult'); // double lookup is intentional 47 | gl.getUniformLocation(prg2, 'u_diffuse'); 48 | gl.getUniformLocation(prg2, 'u_notExist'); 49 | gl.linkProgram(prg2); 50 | gl.getUniformLocation(prg2, 'u_mult'); 51 | gl.getUniformLocation(prg2, 'u_mult'); // double lookup is intentional 52 | gl.getUniformLocation(prg2, 'u_diffuse'); 53 | gl.getUniformLocation(prg2, 'u_notExist'); 54 | gl.deleteProgram(prg2); 55 | }); 56 | 57 | }); -------------------------------------------------------------------------------- /test/tests/uniformXXv-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('uniformXXv tests', () => { 7 | 8 | it('test arrays when uniformXXv has offset', () => { 9 | const {gl, tagObject} = createContext(); 10 | const prg = twgl.createProgram(gl, [ 11 | ` 12 | void main() { 13 | gl_Position = vec4(0, 0, 0, 1); 14 | } 15 | `, 16 | ` 17 | precision mediump float; 18 | uniform vec4 pointColor; 19 | void main() { 20 | gl_FragColor = pointColor; 21 | } 22 | `, 23 | ]); 24 | tagObject(prg, 'point program'); 25 | gl.useProgram(prg); 26 | const loc = gl.getUniformLocation(prg, 'pointColor'); 27 | const array = new Array(16).fill(1); 28 | array[13] = undefined; 29 | assertThrowsWith(() => { 30 | gl.uniform4fv(loc, array, 12); 31 | }, [/element 13 of argument 1 is undefined/]); 32 | }); 33 | 34 | it('test arrays when uniformXXv has offset and length', () => { 35 | const {gl, tagObject} = createContext(); 36 | const prg = twgl.createProgram(gl, [ 37 | ` 38 | void main() { 39 | gl_Position = vec4(0, 0, 0, 1); 40 | } 41 | `, 42 | ` 43 | precision mediump float; 44 | uniform vec4 pointColor; 45 | void main() { 46 | gl_FragColor = pointColor; 47 | } 48 | `, 49 | ]); 50 | tagObject(prg, 'point program'); 51 | gl.useProgram(prg); 52 | const loc = gl.getUniformLocation(prg, 'pointColor'); 53 | const array = new Array(16).fill(1); 54 | array[9] = undefined; 55 | assertThrowsWith(() => { 56 | gl.uniform4fv(loc, array, 8, 4); 57 | }, [/element 9 of argument 1 is undefined/]); 58 | }); 59 | 60 | }); -------------------------------------------------------------------------------- /test/tests/bad-data-tests.js: -------------------------------------------------------------------------------- 1 | import {assertThrowsWith} from '../assert.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext, createContext2} from '../webgl.js'; 4 | 5 | describe('bad data tests', () => { 6 | 7 | it('test bad vertex data', () => { 8 | const {gl, tagObject} = createContext(); 9 | const buf = gl.createBuffer(); 10 | tagObject(buf, 'positions-buffer'); 11 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 12 | gl.bufferData(gl.ARRAY_BUFFER, 12, gl.STATIC_DRAW); 13 | gl.bufferData(gl.ARRAY_BUFFER, new ArrayBuffer(13), gl.STATIC_DRAW); 14 | const data = new Float32Array(40000); 15 | data[34567] = 3 / 'foo'; 16 | assertThrowsWith(() => { 17 | gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); // error 18 | }, [/positions-buffer.*?NaN/, /Float32Array/]); 19 | }); 20 | 21 | it('test bad texture data', () => { 22 | const {gl, tagObject} = createContext(); 23 | const ext = gl.getExtension('OES_texture_float'); 24 | if (!ext) { 25 | return; 26 | } 27 | const tex = gl.createTexture(); 28 | tagObject(tex, 'float-texture'); 29 | gl.bindTexture(gl.TEXTURE_2D, tex); 30 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.FLOAT, null); 31 | assertThrowsWith(() => { 32 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.FLOAT, new Float32Array([1, 2, 3 / 'foo', 4])); // error 33 | }, [/texImage2D.*?float-texture.*?NaN/]); 34 | }); 35 | 36 | it('test bad argument', () => { 37 | const {gl} = createContext2(); 38 | if (!gl) { 39 | return; 40 | } 41 | gl.clearBufferfv(gl.COLOR, 0, [0, 0, 0, 0]); 42 | gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 0]), 0); 43 | assertThrowsWith(() => { 44 | gl.clearBufferfv(gl.COLOR, 'foo', [0, 0, 0, 0]); 45 | }, [/not a number/]); 46 | assertThrowsWith(() => { 47 | gl.clearBufferfv(gl.COLOR, 0, 0); 48 | }, [/not an array or typedarray/]); 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /test/tests/naming-tests.js: -------------------------------------------------------------------------------- 1 | import * as twgl from '../js/twgl-full.module.js'; 2 | import {assertThrowsWith} from '../assert.js'; 3 | import {describe, it} from '../mocha-support.js'; 4 | import {createContext} from '../webgl.js'; 5 | 6 | describe('namings test', () => { 7 | 8 | it('test naming objects', () => { 9 | const {gl, tagObject} = createContext(); 10 | // console.assert(gl.getExtension('OES_vertex_array_objects') === gl.getExtension('OES_vertex_array_objects')); 11 | // console.assert(gl.getSupportedExtensions().includes('GMAN_debug_helper')); 12 | const p = gl.createProgram(); 13 | tagObject(p, 'my-test-prg'); 14 | assertThrowsWith(() => { 15 | gl.useProgram(p); 16 | }, [/my-test-prg/]); 17 | }); 18 | 19 | it('test getting names of uniforms', () => { 20 | const {gl, tagObject} = createContext(); 21 | const prg = twgl.createProgram(gl, [ 22 | ` 23 | void main() { 24 | gl_Position = vec4(0); 25 | } 26 | `, 27 | ` 28 | precision mediump float; 29 | uniform vec4 diffuseColor; 30 | void main() { 31 | gl_FragColor = diffuseColor; 32 | } 33 | `, 34 | ]); 35 | tagObject(prg, 'simple program'); 36 | gl.useProgram(prg); 37 | const loc = gl.getUniformLocation(prg, 'diffuseColor'); 38 | gl.uniform4fv(loc, [1, 2, 3, 4]); 39 | assertThrowsWith(() => { 40 | gl.uniform4fv(loc, [1, 2, 3 / 'foo', 4]); 41 | }, [/diffuseColor.*?NaN/]); 42 | }); 43 | 44 | it('test large uniform', () => { 45 | const {gl, tagObject} = createContext(); 46 | const prg = twgl.createProgram(gl, [ 47 | ` 48 | void main() { 49 | gl_Position = vec4(0); 50 | } 51 | `, 52 | ` 53 | precision mediump float; 54 | uniform vec4 diffuseColors[9]; 55 | void main() { 56 | gl_FragColor = diffuseColors[8]; 57 | } 58 | `, 59 | ]); 60 | tagObject(prg, 'simple program'); 61 | gl.useProgram(prg); 62 | const loc = gl.getUniformLocation(prg, 'diffuseColors'); 63 | const value = new Array(36).fill(0); 64 | value[33] = 3 / 'foo'; 65 | assertThrowsWith(() => { 66 | gl.uniform4fv(loc, value); 67 | }, [/diffuseColors.*?NaN/]); 68 | }); 69 | 70 | }); -------------------------------------------------------------------------------- /test/tests/arrays-with-offsets-tests.js: -------------------------------------------------------------------------------- 1 | import {assertThrowsWith} from '../assert.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext2} from '../webgl.js'; 4 | 5 | describe('arrays with offsets', () => { 6 | it('test bufferData with offset and length', () => { 7 | const {gl, tagObject} = createContext2(); 8 | if (!gl) { 9 | return; 10 | } 11 | const buf = gl.createBuffer(); 12 | tagObject(buf, 'test-buf'); 13 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 14 | gl.bufferData(gl.ARRAY_BUFFER, 5, gl.STATIC_DRAW); 15 | gl.bufferData(gl.ARRAY_BUFFER, new ArrayBuffer(5), gl.STATIC_DRAW); 16 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 2, 3 / 'foo', 4, 5]), gl.STATIC_DRAW, 3); 17 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 2, 3 / 'foo', 4, 5]), gl.STATIC_DRAW, 0, 2); 18 | assertThrowsWith(() => { 19 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 2, 3 / 'foo', 4, 5]), gl.STATIC_DRAW, 2); 20 | }, 21 | [/element 2 of argument 1 is NaN/], 22 | ); 23 | assertThrowsWith(() => { 24 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 2, 3 / 'foo', 4, 5]), gl.STATIC_DRAW, 2, 2); 25 | }, 26 | [/element 2 of argument 1 is NaN/], 27 | ); 28 | }); 29 | 30 | it('test bufferSubData with offset and length', () => { 31 | const {gl, tagObject} = createContext2(); 32 | if (!gl) { 33 | return; 34 | } 35 | const buf = gl.createBuffer(); 36 | tagObject(buf, 'test-buf'); 37 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 38 | gl.bufferData(gl.ARRAY_BUFFER, 50, gl.STATIC_DRAW); 39 | gl.bufferSubData(gl.ARRAY_BUFFER, 1, new Float32Array(5)); 40 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float32Array([1, 2, 3 / 'foo', 4, 5]), 3); 41 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float32Array([1, 2, 3 / 'foo', 4, 5]), 0, 2); 42 | assertThrowsWith(() => { 43 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float32Array([1, 2, 3 / 'foo', 4, 5]), 2); 44 | }, 45 | [/element 2 of argument 2 is NaN/], 46 | ); 47 | assertThrowsWith(() => { 48 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float32Array([1, 2, 3 / 'foo', 4, 5]), 2, 2); 49 | }, 50 | [/element 2 of argument 2 is NaN/], 51 | ); 52 | }); 53 | 54 | }); -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Gregg Tavares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- 23 | 24 | Copyright (c) 2012 The Khronos Group Inc. 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a 27 | copy of this software and/or associated documentation files (the 28 | "Materials"), to deal in the Materials without restriction, including 29 | without limitation the rights to use, copy, modify, merge, publish, 30 | distribute, sublicense, and/or sell copies of the Materials, and to 31 | permit persons to whom the Materials are furnished to do so, subject to 32 | the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included 35 | in all copies or substantial portions of the Materials. 36 | 37 | THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 38 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 39 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 40 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 41 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 42 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 43 | MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -------------------------------------------------------------------------------- /test/webgl.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | 3 | export function createContext() { 4 | const gl = document.createElement('canvas').getContext('webgl'); 5 | const ext = gl.getExtension('GMAN_debug_helper'); 6 | const vaoExt = gl.getExtension('OES_vertex_array_object'); 7 | const tagObject = ext ? ext.tagObject.bind(ext) : () => {}; 8 | return { gl, ext, vaoExt, tagObject }; 9 | } 10 | 11 | export function createContext2() { 12 | const gl = document.createElement('canvas').getContext('webgl2'); 13 | const ext = gl ? gl.getExtension('GMAN_debug_helper') : null; 14 | const tagObject = ext ? ext.tagObject.bind(ext) : () => {}; 15 | return { gl, ext, tagObject }; 16 | } 17 | 18 | export function createContexts() { 19 | const {gl, ext, vaoExt, tagObject} = createContext(); 20 | const {gl: gl2, ext: ext2, tagObject: tagObject2} = createContext2(); 21 | return { gl, gl2, ext, ext2, vaoExt, tagObject, tagObject2 }; 22 | } 23 | 24 | function resetContext(gl) { 25 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 26 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 27 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 28 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 29 | gl.activeTexture(gl.TEXTURE0); 30 | gl.bindTexture(gl.TEXTURE_2D, null); 31 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); 32 | gl.useProgram(null); 33 | } 34 | 35 | export function resetContexts(context) { 36 | const { gl, gl2, vaoExt } = context; 37 | if (vaoExt) { 38 | vaoExt.bindVertexArrayOES(null); 39 | } 40 | resetContext(gl); 41 | 42 | if (gl2) { 43 | gl2.bindVertexArray(null); 44 | resetContext(gl2); 45 | } 46 | } 47 | 48 | export function escapeRE(str) { 49 | return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); 50 | } 51 | 52 | export function not(str) { 53 | return new RegExp(`^((?!${escapeRE(str)}).)*$`); 54 | } 55 | 56 | export function checkDest(gl, color) { 57 | const {width, height} = gl.canvas; 58 | const pixels = new Uint8Array(width * height * 4); 59 | gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); 60 | for (let i = 0; i < pixels.length; i += 4) { 61 | if (pixels[i + 0] !== color[0] || 62 | pixels[i + 1] !== color[1] || 63 | pixels[i + 2] !== color[2] || 64 | pixels[i + 3] !== color[3]) { 65 | const x = (i / 4) % width; 66 | const y = (i / 4) / width | 0; 67 | throw new Error(`pixel at ${x},${y} expected: ${color}, was ${pixels.slice(i, i + 4)}`); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/tests/buffer-tests.js: -------------------------------------------------------------------------------- 1 | import {assertEqual} from '../assert.js'; 2 | import {describe, it} from '../mocha-support.js'; 3 | import {createContext} from '../webgl.js'; 4 | 5 | describe('buffer tests', () => { 6 | 7 | it('test bindBuffer with null and undefined', () => { 8 | const {gl} = createContext(); 9 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 10 | gl.bindBuffer(gl.ARRAY_BUFFER, undefined); 11 | }); 12 | 13 | it('test bufferData with different BufferSource', () => { 14 | const {gl} = createContext(); 15 | 16 | const buf = gl.createBuffer(); 17 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 18 | 19 | gl.bufferData(gl.ARRAY_BUFFER, 10, gl.STATIC_DRAW); 20 | gl.bufferData(gl.ARRAY_BUFFER, new Int8Array(10), gl.STATIC_DRAW); 21 | gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(10), gl.STATIC_DRAW); 22 | gl.bufferData(gl.ARRAY_BUFFER, new Int16Array(10), gl.STATIC_DRAW); 23 | gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(10), gl.STATIC_DRAW); 24 | gl.bufferData(gl.ARRAY_BUFFER, new Int32Array(10), gl.STATIC_DRAW); 25 | gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array(10), gl.STATIC_DRAW); 26 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(10), gl.STATIC_DRAW); 27 | gl.bufferData(gl.ARRAY_BUFFER, new Float64Array(10), gl.STATIC_DRAW); 28 | gl.bufferData(gl.ARRAY_BUFFER, new ArrayBuffer(10), gl.STATIC_DRAW); 29 | gl.bufferData(gl.ARRAY_BUFFER, new DataView(new ArrayBuffer(200)), gl.STATIC_DRAW); 30 | 31 | assertEqual(gl.getError(), gl.NO_ERROR, 'no errors'); 32 | }); 33 | 34 | it('test bufferSubData with different BufferSource', () => { 35 | const {gl} = createContext(); 36 | 37 | const buf = gl.createBuffer(); 38 | gl.bindBuffer(gl.ARRAY_BUFFER, buf); 39 | 40 | gl.bufferData(gl.ARRAY_BUFFER, 200, gl.STATIC_DRAW); 41 | 42 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Int8Array(10)); 43 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Uint8Array(10)); 44 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Int16Array(10)); 45 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Uint16Array(10)); 46 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Int32Array(10)); 47 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Uint32Array(10)); 48 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float32Array(10)); 49 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new Float64Array(10)); 50 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new ArrayBuffer(10)); 51 | gl.bufferSubData(gl.ARRAY_BUFFER, 4, new DataView(new ArrayBuffer(10))); 52 | 53 | assertEqual(gl.getError(), gl.NO_ERROR, 'no errors'); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
4 |
5 | WebGL lint is a script you can throw into your WebGL project
6 | to check for common WebGL errors.
7 |
8 | * Calls `getError` after every function and throws if there was an error.
9 |
10 | * Checks that no arguments to any functions are `undefined`.
11 |
12 | Pass `0` or `false` or `null` where you mean `0` or `false` or `null`.
13 |
14 | * Checks that no numbers or values in arrays of numbers are `NaN`.
15 |
16 | * Checks that all non-sampler uniforms are set. (see configuration below)
17 |
18 | * Checks that uniform matrices are not all zero.
19 |
20 | * Warns if you try to access an undefined uniform.
21 |
22 | * Checks for out of range access issues and will tell you which attribute/s are out of range
23 |
24 | * Checks that shaders compile. On failure prints the shader and tries to highlight errors.
25 |
26 | * Checks that programs link. On failure prints the attached shaders.
27 |
28 | * If there is a WebGL error it tries to provide more info about why
29 |
30 | * for framebuffer feedback it will tell you which textures assigned to which uniforms and which attachments
31 |
32 | * for other errors it will try print extra info where possible.
33 |
34 | * it lets you name webgl objects so those names can be shown in the error messages.
35 |
36 | ## Example
37 |
38 | **Open the JavaScript console**. You'll see the first example prints fewer
39 | errors and less info where as the second prints much more info.
40 |
41 | * [without script](https://greggman.github.io/webgl-lint/test/?lint=false)
42 | * [with script](https://greggman.github.io/webgl-lint/test/)
43 |
44 | # Usage
45 |
46 | ```
47 |
48 | ```
49 |
50 | or
51 |
52 | ```
53 | import 'https://greggman.github.io/webgl-lint/webgl-lint.js';
54 | ```
55 |
56 | WebGL Lint `throw`s a JavaScript exception when there is an issue so if you are
57 | using `try`/`catch` to catch errors you might need to print the exceptions
58 | inside your catch block. You can also turn on "pause on exception" on your
59 | JavaScript debugger.
60 |
61 | Throwing seemed more a appropriate than just printing an error because if you
62 | get an error you should fix it! I tried the script out with all the
63 | [three.js examples](https://threejs.org/examples). It found 1 real bug and several half bugs.
64 | By half bugs I mean there were several examples that functioned but were actually
65 | passing `NaN` or `null` in the wrong places for a few frames or they were not setting
66 | uniforms that probably should have been set. Arguably it's
67 | better to fix those so that you can continue to use the helper to find real
68 | errors. In any case most of examples ran without error so you can do it
69 | too! 😉
70 |
71 | ### `GMAN_debug_helper` extension
72 |
73 | WebGL Lint adds a special extension `GMAN_debug_helper` with these functions
74 |
75 | * `tagObject(obj: WebGLObject, name: string): void` - see naming below
76 | * `untagObject(obj: WebGLObject): void` - see naming below
77 | * `getTagForObject(obj: WebGLObject): string` - see naming below
78 | * `setConfiguration(settings): void` - see configuration below
79 | * `disable(): void` - turns off the checking
80 | * `getAndResetRedundantCallInfo(): RedundantCallInfo` - see below
81 |
82 | ### Configuration
83 |
84 | You don't need to configure anything to use in general but there are some settings
85 | for special needs.
86 |
87 | * `maxDrawCalls` (default: 1000)
88 |
89 | Turns off the checking after this many draw calls. Set to 0 to check forever.
90 |
91 | * `failUnsetUniforms`: (default: true)
92 |
93 | Checks that you set uniforms except for samplers and fails if you didn't.
94 | It's a common error to forget to set a uniform or to mis-spell the name of
95 | a uniform and therefore not set the real one. The common exception is
96 | samplers because uniforms default to 0 so not setting a sampler means use
97 | texture unit 0 so samplers are not checked.
98 |
99 | Of course maybe you're not initializing some uniforms on purpose
100 | so you can turn off this check. I'd recommend setting them so you get the
101 | benefit of this check finding errors.
102 |
103 | Note: uniform blocks are not checked directly. They are checked by WebGL itself
104 | in the sense that if you fail to provide uniform buffers for your uniform blocks
105 | you'll get an error but there is no easy way to check that you set them.
106 |
107 | * `failUnsetSamplerUniforms`: (default: false)
108 |
109 | See above why sampler uniforms are not checked by default. You can force them
110 | to be checked by this setting.
111 |
112 | * `failZeroMatrixUniforms`: (default: true)
113 |
114 | Checks that a uniform matrix is not all zeros. It's a common source of errors to
115 | forget to set a matrix to the identity and it seems uncommon to have an all
116 | zero matrix. If you have a reason a matrix needs to be all zeros you may want
117 | to turn this off.
118 |
119 | * `failUnrenderableTextures`: (default: true)
120 |
121 | Unrenderable textures are not an error in WebGL, they just don't render.
122 | WebGL itself usually print's a warning but it's usually fairly cryptic
123 | just telling you an unrenderable texture exists but not much else.
124 |
125 | Examples of unrenderable textures are non-power of 2 textures in WebGL1
126 | with filtering set to need mips and wrap not set to `CLAMP_TO_EDGE` or
127 | in both WebGL and WebGL2 would be mips of different internal formats
128 | or the wrong size.
129 |
130 | * `failUndefinedUniforms`: (default: false)
131 |
132 | WebGL by default returns `null` when you call `gl.getUniformLocation` for
133 | a uniform that does not exist. It then silently ignores calling `gl.uniformXXX`
134 | if the location is `null`. This is great when you're editing a shader in that
135 | if you remove a uniform from the shader your code that is still setting
136 | the old uniform will keep working.
137 |
138 | For example if you are debugging and you go to the bottom of your fragment
139 | shader and add `gl_FragColor = vec4(1, 0, 0, 1);` all the uniforms in your
140 | fragment shader will be optimized out. If WebGL suddenly issues errors trying
141 | to set those it would be much more frustrating to debug. Conversely though, if
142 | you have a typo, for example you want to look up the location of `'u_color'` and
143 | you type `gl.getUniformLocation(prg, 'uColor')` you'll get no error and it
144 | will likely take you a while to find your typo.
145 |
146 | So, by default webgl-lint only prints a warning for undefined uniforms.
147 | You can make throw by setting `failUndefinedUniforms` to `true`.
148 |
149 | * `failBadShadersAndPrograms`: (default: true)
150 |
151 | Most WebGL programs expect all shaders to compile and all programs
152 | to link but often programmers don't check for errors. While it's likely
153 | they'd get an error about a bad program further in their code, at that point
154 | it's likely too late to tell them it's because the program didn't compile or
155 | link. Instead the message will just be something like "no valid program in use".
156 |
157 | If you're working on a project that expects shaders to fail to compile
158 | and/or programs to link you can set this to `false`.
159 |
160 | * `warnUndefinedUniforms`: (default: true)
161 |
162 | See `failUndefinedUniforms`. Setting this to false turns off warnings
163 | about undefined uniforms.
164 |
165 | * `ignoreUniforms`: (default: [])
166 |
167 | Lets you configure certain uniforms not to be checked. This way you can turn
168 | off checking for certain uniforms if they don't obey the rules above and still
169 | keep the rules on for other uniforms. This configuration is additive. In other words
170 |
171 | ```js
172 | ext.setConfiguration({ignoreUniforms: ['foo', 'bar']});
173 | ext.setConfiguration({ignoreUniforms: ['baz']});
174 | ```
175 |
176 | Ignores uniforms called 'foo', 'bar', and 'baz'.
177 |
178 | * `throwOnError`: (default: true)
179 |
180 | The default is to throw an exception on error. This has several benefits.
181 |
182 | 1. It encourages you to fix the bug.
183 |
184 | 2. You'll get a stack trace which you can drill down to find the bug.
185 |
186 | 3. If you use "pause on exception" in your browser's dev tools you'll
187 | get a live stack trace where you can explore all the local variables
188 | and state of your program.
189 |
190 | But, there might be times when you can't avoid the error, say you're
191 | running a 3rd party library that gets errors. You should go politely
192 | ask them to fix the bug or better, fix it yourself and send them a pull request.
193 | In any case, if you just want it to print an error instead of throw then
194 | you can set `throwOnError` to false.
195 |
196 | * `makeDefaultTags`: (default: true)
197 |
198 | If true, all objects get a default tag, Example `*UNTAGGED:Buffer1`,
199 | `*UNTAGGED:Buffer2` etc. This is a minor convenience to have something
200 | to distinguish one object from another though it's highly recommended
201 | you tag your objects. (See naming).
202 |
203 | The only reason to turn this off is if you're creating and deleting
204 | lots of objects and you want to make sure tags are not leaking memory
205 | since tags are never deleted automatically. (See "naming).
206 |
207 | There 2 ways to configure
208 |
209 | 1. Via the extension and JavaScript.
210 |
211 | Example:
212 |
213 | ```js
214 | const gl = someCanvas.getContext('webgl');
215 | const ext = gl.getExtension('GMAN_debug_helper');
216 | if (ext) {
217 | ext.setConfiguration({
218 | maxDrawCalls: 2000,
219 | failUnsetSamplerUniforms: true,
220 | });
221 | }
222 | ```
223 |
224 | 2. Via an HTML dataset attribute
225 |
226 | Example:
227 |
228 | ```html
229 |
238 | ```
239 |
240 | Note: (1) the setting string must be valid JSON. (2) any tag will do, `