import { assertArrayEqual, assertTruthy, assertFalsy, assertEqual, } from '../assert.js'; import {beforeEach, afterEach, describe, it} from '../mocha-support.js'; import { assertNoWebGLError, itWebGL2, createContext, createContext2, checkColor, itWebGL, fnWithCallbackToPromise, } from '../webgl.js'; class MsgCapturer { constructor() { this.errors = []; this.cb = (msg) => this.errors.push(msg); } hasMsgs() { return this.errors.length > 0; } } describe('program tests', () => { describe('createProgramInfo', () => { itWebGL('succeeds with good shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfo = twgl.createProgramInfo(gl, [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertTruthy(programInfo); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('assigns attribute locations via 3rd argument', () => { const {gl} = createContext(); const programInfo = twgl.createProgramInfo(gl, [ ` attribute vec4 foo; attribute vec4 bar; attribute vec4 moo; void main() { gl_Position = foo + bar + moo; } `, 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], ['bar', 'moo', 'foo']); assertTruthy(programInfo); assertEqual(gl.getAttribLocation(programInfo.program, 'bar'), 0); assertEqual(gl.getAttribLocation(programInfo.program, 'moo'), 1); assertEqual(gl.getAttribLocation(programInfo.program, 'foo'), 2); }); itWebGL('assigns attribute locations via programOptions', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfo = twgl.createProgramInfo(gl, [ ` attribute vec4 foo; attribute vec4 bar; attribute vec4 moo; void main() { gl_Position = foo + bar + moo; } `, 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], { errorCallback: msgCapturer.cb, attribLocations: ['bar', 'moo', 'foo'], }); assertTruthy(programInfo); assertFalsy(msgCapturer.hasMsgs()); assertEqual(gl.getAttribLocation(programInfo.program, 'bar'), 0); assertEqual(gl.getAttribLocation(programInfo.program, 'moo'), 1); assertEqual(gl.getAttribLocation(programInfo.program, 'foo'), 2); }); itWebGL('fails with bad shader', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfo = twgl.createProgramInfo(gl, [ 'void main() { gl_Position = vec4(0); }', 'bad code', ], { errorCallback: msgCapturer.cb, }); assertFalsy(programInfo); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('fails with too few shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfo = twgl.createProgramInfo(gl, [ 'void main() { gl_Position = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertFalsy(programInfo); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('works async with callback', async function() { const {gl} = createContext(); const programInfo = await fnWithCallbackToPromise(twgl.createProgramInfo)(gl, [ `void main() {gl_Position = vec4(0); }`, `precision mediump float; uniform vec4 u_foo; void main() { gl_FragColor = u_foo; }`, ], { callback(err, programInfo) { assertFalsy(err); assertTruthy(programInfo); assertTruthy(programInfo.program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(programInfo.program, gl.LINK_STATUS)); gl.useProgram(programInfo.program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertNoWebGLError(gl); }, }); assertFalsy(programInfo); }); itWebGL('works async with callback with bad shader', async function() { const {gl} = createContext(); const msgs = []; const programInfo = await fnWithCallbackToPromise(twgl.createProgramInfo)(gl, [ `void main() {gl_Position = vec4(0); }`, `precision mediump float; uniform vec4 u_foo; void main() { gl_Frag Color = u_foo; }`, ], { errorCallback(msg) { msgs.push(msg); }, callback(err, programInfo) { assertTruthy(err); assertFalsy(programInfo); assertTruthy(msgs.length > 0); }, }); assertFalsy(programInfo); }); itWebGL('works async with callback missing shader', async function() { const {gl} = createContext(); const msgs = []; const programInfo = await fnWithCallbackToPromise(twgl.createProgramInfo)(gl, [ `void main() {gl_Position = vec4(0); }`, ], { errorCallback(msg) { msgs.push(msg); }, callback(err, programInfo) { assertTruthy(err); assertFalsy(programInfo); assertTruthy(msgs.length > 0); }, }); assertFalsy(programInfo); }); }); describe('createProgramInfos', () => { itWebGL('createProgramInfos', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfos = twgl.createProgramInfos(gl, { foo: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], bar: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, { errorCallback: msgCapturer.cb, }); assertTruthy(programInfos.foo.program instanceof WebGLProgram); assertTruthy(programInfos.bar.program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('createProgramInfos sets attrib locations', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfos = twgl.createProgramInfos(gl, { foo: { shaders: [ 'attribute vec4 foo; attribute vec4 bar; void main() { gl_Position = foo + bar; }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, bar: { shaders: [ 'attribute vec4 moo; attribute vec4 bar; void main() { gl_Position = moo + bar; }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], attribLocations: ['bar', 'moo'], }, moo: { shaders: [ 'attribute vec4 moo; attribute vec4 bar; void main() { gl_Position = moo + bar; }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, }, { attribLocations: ['moo', 'foo', 'bar'], errorCallback: msgCapturer.cb, }); assertTruthy(programInfos.foo.program instanceof WebGLProgram); assertTruthy(programInfos.bar.program instanceof WebGLProgram); assertTruthy(programInfos.moo.program instanceof WebGLProgram); assertEqual(gl.getAttribLocation(programInfos.foo.program, 'foo'), 1); assertEqual(gl.getAttribLocation(programInfos.foo.program, 'bar'), 2); assertEqual(gl.getAttribLocation(programInfos.bar.program, 'moo'), 1); assertEqual(gl.getAttribLocation(programInfos.bar.program, 'bar'), 0); assertEqual(gl.getAttribLocation(programInfos.moo.program, 'moo'), 0); assertEqual(gl.getAttribLocation(programInfos.moo.program, 'bar'), 2); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('createProgramInfos calls the correct errorCallback', () => { const {gl} = createContext(); const mainMsgCapturer = new MsgCapturer(); const subMsgCapturer = new MsgCapturer(); const programInfos = twgl.createProgramInfos(gl, { foo: { shaders: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, bar: { shaders: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], attribLocations: ['bar', 'moo'], errorCallback: subMsgCapturer.cb, }, moo: { shaders: [ 'bad', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, }, { attribLocations: ['moo', 'foo', 'bar'], errorCallback: mainMsgCapturer.cb, }); assertFalsy(programInfos); assertFalsy(subMsgCapturer.hasMsgs()); assertTruthy(mainMsgCapturer.hasMsgs()); }); itWebGL('createProgramInfos calls the correct errorCallback(2)', () => { const {gl} = createContext(); const mainMsgCapturer = new MsgCapturer(); const subMsgCapturer = new MsgCapturer(); const programInfos = twgl.createProgramInfos(gl, { foo: { shaders: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, bar: { shaders: [ 'bad', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], attribLocations: ['bar', 'moo'], errorCallback: subMsgCapturer.cb, }, moo: { shaders: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, }, { attribLocations: ['moo', 'foo', 'bar'], errorCallback: mainMsgCapturer.cb, }); assertFalsy(programInfos); assertTruthy(subMsgCapturer.hasMsgs()); assertFalsy(mainMsgCapturer.hasMsgs()); }); }); describe('createProgram', () => { itWebGL('succeeds with good shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgram(gl, [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertTruthy(program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('fails with bad shader', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgram(gl, [ 'void main() { gl_Position = vec4(0); }', 'bad code', ], { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('fails with too few shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgram(gl, [ 'void main() { gl_Position = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('succeeds with good existing shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const vs = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vs, `void main() { gl_Position = vec4(0); }`); gl.compileShader(vs); const fs = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fs, `precision mediump float; void main() { gl_FragColor = vec4(0); }`); gl.compileShader(fs); const program = twgl.createProgram(gl, [vs, fs], { errorCallback: msgCapturer.cb, }); assertTruthy(program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('does not delete existing shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const vs = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vs, `void main() { gl_Position = vec4(0); }`); gl.compileShader(vs); const fsBad = `precision mediump float; void main() { gl_FragColorS = vec4(0); }`; const programBad = twgl.createProgram(gl, [vs, fsBad], { errorCallback: () => {}, }); assertFalsy(programBad); const fsGood = `precision mediump float; void main() { gl_FragColor = vec4(0); }`; const programGood = twgl.createProgram(gl, [vs, fsGood], { errorCallback: msgCapturer.cb, }); assertTruthy(programGood instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('compiles program async with callback', async function() { const {gl} = createContext(); const program = await fnWithCallbackToPromise(twgl.createProgram)(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_FragColor = vec4(0); }`, ], { callback(err, program) { assertFalsy(err); assertTruthy(program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(program, gl.LINK_STATUS)); gl.useProgram(program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertEqual(gl.getError(), gl.NONE); }, }); assertFalsy(program); // nothing is returned if callback }); itWebGL('compiles program async with callback with error', async function() { const {gl} = createContext(); const msgs = []; const program = await fnWithCallbackToPromise(twgl.createProgram)(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_Frag Color = vec4(0); }`, ], { errorCallback(msg) { msgs.push(msg); }, callback(err, program) { assertTruthy(err); assertFalsy(program); assertTruthy(msgs.length > 0); }, }); assertFalsy(program); // nothing is returned if callback }); }); describe('createProgramAsync', () => { itWebGL('compiles program async', async() => { const {gl} = createContext(); const program = await twgl.createProgramAsync(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_FragColor = vec4(0); }`, ]); assertTruthy(program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(program, gl.LINK_STATUS)); gl.useProgram(program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertEqual(gl.getError(), gl.NONE); }); itWebGL('throws error with bad shaders', async() => { const {gl} = createContext(); const msgs = []; let err; try { await twgl.createProgramAsync(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_Frag Color = vec4(0); }`, ], { errorCallback(msg) { msgs.push(msg); }, }); } catch (e) { err = e; } assertTruthy(err); assertTruthy(msgs.length > 0); }); }); describe('createProgramInfoAsync', () => { itWebGL('succeeds with good shaders', async() => { const {gl} = createContext(); const programInfo = await twgl.createProgramInfoAsync(gl, [ `void main() {gl_Position = vec4(0); }`, `precision mediump float; uniform vec4 u_foo; void main() { gl_FragColor = u_foo; }`, ]); assertTruthy(programInfo); assertTruthy(programInfo.program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(programInfo.program, gl.LINK_STATUS)); gl.useProgram(programInfo.program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertEqual(gl.getError(), gl.NONE); }); itWebGL('throws with bad shaders', async() => { const {gl} = createContext(); const msgs = []; let err; let programInfo; try { programInfo = await twgl.createProgramInfoAsync(gl, [ `void main() {gl_Position = vec4(0); }`, `precision mediump float; uniform vec4 u_foo; void main() { gl_Frag Color = u_foo; }`, ], { errorCallback(msg) { msgs.push(msg); }, }); } catch (e) { err = e; } assertTruthy(err); assertFalsy(programInfo); assertTruthy(msgs.length > 0); }); itWebGL('throws with bad id', async() => { const {gl} = createContext(); const msgs = []; let err; let programInfo; try { programInfo = await twgl.createProgramInfoAsync(gl, [ `idThatDoesNotExist`, 'anotherIdThatDoesNotExist', ], { errorCallback(msg) { msgs.push(msg); }, }); } catch (e) { err = e; } assertTruthy(err); assertFalsy(programInfo); assertTruthy(msgs.length > 0); }); }); describe('uniform blocks', () => { itWebGL2('test uniform block', async() => { const {gl} = createContext2(); const vs = `#version 300 es void main() { gl_Position = vec4(0, 0, 0, 1); gl_PointSize = 1.0; } `; const fs = `#version 300 es precision mediump float; uniform Foo { vec2 v2a[3]; vec3 v3a[3]; mat2 m2a[3]; mat2x3 m23a[3]; mat3x2 m32a[3]; float f; float f1; vec2 v21; vec2 v22; vec4 v4; float f2; float fa[2]; float f3; }; out vec4 outColor; void main() { outColor = v4; } `; const programInfo = twgl.createProgramInfo(gl, [vs, fs]); assertTruthy(programInfo); const uboInfo = twgl.createUniformBlockInfo(gl, programInfo, "Foo"); twgl.setBlockUniforms(uboInfo, { v2a: [ 1, 2, 3, 4, 5, 6, ], v3a: [ 11, 12, 13, 14, 15, 16, 17, 18, 19, ], m2a: [ 21, 22, 23, 24, 25, 26, 27, 28, 29, 210, 211, 212, ], m23a: [ 31, 32, 33, 34, 35, 36, 37, 38, 39, 310, 311, 312, 313, 314, 315, 316, 317, 318, ], m32a: [ 41, 42, 43, 44, 45, 46, 47, 48, 49, 410, 411, 412, 412, 412, 415, 416, 417, 418, ], f: [51], f1: [61], v21: [71, 72], v22: [81, 82], v4: [91, 92, 93, 94], f2: 101, // test I can set scalar with value, not array fa: [111, 112], f3: [121], }); twgl.setUniformBlock(gl, programInfo, uboInfo); const data = new Float32Array( 4 * 3 + // vec2 v2a[3]; // 0: 0 4 * 3 + // vec3 v3a[3]; // 12: 48 2 * 4 * 3 + // mat2 m2a[3]; // 24: 96 2 * 4 * 3 + // mat2x3 m23a[3]; // 48: 192 3 * 4 * 3 + // mat3x2 m32a[3]; // 72: 288 1 + // float f; // 108: 432 1 + // float f1; // 109: 436 2 + // vec2 v21; // 110: 440 2 + // vec2 v22; // 112: 448 2 + // padding // 4 + // vec4 v4; // 116: 464 1 + // float f2 // 120: 480 3 + // padding // 2 * 4 + // float fa[2]; // 124: 496 1 + // float f3; // 132: 528 0 ); gl.getBufferSubData(gl.UNIFORM_BUFFER, 0, data); const expected = new Float32Array([ // v2a: [ 1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 0, 0, // ], // v3a: [ 11, 12, 13, 0, 14, 15, 16, 0, 17, 18, 19, 0, // ], // m2a: [ 21, 22, 0, 0, 23, 24, 0, 0, 25, 26, 0, 0, 27, 28, 0, 0, 29, 210, 0, 0, 211, 212, 0, 0, // ], // m23a: [ 31, 32, 33, 0, 34, 35, 36, 0, 37, 38, 39, 0, 310, 311, 312, 0, 313, 314, 315, 0, 316, 317, 318, 0, // ], // m32a: [ 41, 42, 0, 0, 43, 44, 0, 0, 45, 46, 0, 0, 47, 48, 0, 0, 49, 410, 0, 0, 411, 412, 0, 0, 412, 412, 0, 0, 415, 416, 0, 0, 417, 418, 0, 0, // ], //f: [ 51, //], // f1: [ 61, //], // v21: [ 71, 72, //], // v22: [ 81, 82, //], // padding 0, 0, //v4: [ 91, 92, 93, 94, //], //f1: [ 101, // ], // padding 0, 0, 0, //fa: [ 111, 0, 0, 0, 112, 0, 0, 0, //], //f2: [ 121, //], ]); assertArrayEqual(data, expected); }); itWebGL2('test built in uniform', ['WEBGL_multi_draw'], () => { const {gl} = createContext2(); const ext = gl.getExtension('WEBGL_multi_draw'); assertTruthy(ext); const vs = `#version 300 es #extension GL_ANGLE_multi_draw : require uniform float foo; uniform float moo; uniform MyBlock { float bar; }; void main() { gl_Position = vec4(gl_DrawID, foo, moo, bar); } `; const fs = `#version 300 es precision highp float; out vec4 outColor; void main() { outColor = vec4(0, 1, 0, 1); }`; const programInfo = twgl.createProgramInfo(gl, [vs, fs]); // this isn't much of a test. Mostly twgl used to have issues here assertTruthy(programInfo.uniformSetters.foo); assertTruthy(programInfo.uniformSetters.moo); assertFalsy(programInfo.uniformSetters.gl_DrawID); assertTruthy(programInfo.uniformBlockSpec.uniformData.length >= 3); // foo, bar, moo assertEqual(gl.getError(), gl.NONE); }); it('test uniform struct/array', () => { const {gl} = createContext(); gl.canvas.width = 1; gl.canvas.height = 1; gl.viewport(0, 0, 1, 1); const vs = ` struct Extra { float f; vec4 v4; vec3 v3; }; struct Light { float intensity; vec4 color; float nearFar[2]; Extra extra[2]; }; uniform Light lights[2]; varying vec4 v_out; vec4 getLight(Light l) { return vec4(l.intensity) + vec4(l.color) + vec4(l.nearFar[0], l.nearFar[1], 0, 0) + vec4(l.extra[0].f) + vec4(l.extra[0].v4) + vec4(l.extra[0].v3, 0) + vec4(l.extra[1].f) + vec4(l.extra[1].v4) + vec4(l.extra[1].v3, 0); } void main() { gl_Position = vec4(0, 0, 0, 1); gl_PointSize = 1.0; v_out = getLight(lights[0]) + getLight(lights[1]); } `; const fs = ` precision mediump float; varying vec4 v_out; void main() { gl_FragColor = v_out / 255.0; } `; const programInfo = twgl.createProgramInfo(gl, [vs, fs]); gl.useProgram(programInfo.program); twgl.setUniforms(programInfo, { lights: [ { extra: [ { v4: [11, 22, 33, 44] }, ], }, ], }); gl.drawArrays(gl.POINTS, 0, 1); checkColor(gl, [11, 22, 33, 44]); twgl.setUniforms(programInfo, { lights: [ { extra: [ { v4: [11, 22, 33, 44] }, { v3: [10, 20, 30] }, ], }, ], }); gl.drawArrays(gl.POINTS, 0, 1); checkColor(gl, [21, 42, 63, 44]); twgl.setUniforms(programInfo, { 'lights[0].extra': [ { v4: [1, 2, 3, 4], }, ], 'lights[1].intensity': 100, }); gl.drawArrays(gl.POINTS, 0, 1); checkColor(gl, [111, 122, 133, 104]); /* TWGL does not currently support setting random elements of leaf arrays twgl.setUniforms(programInfo, { 'lights[0].nearFar[1]': 1000, }); checkColor(gl, [111, 1022, 133, 104]); */ }); itWebGL2('test uniformblock struct/array', () => { const {gl} = createContext2(); const vs = `#version 300 es struct Extra { float f; vec4 v4; vec3 v3; }; struct Light { float intensity; vec4 color; float nearFar[2]; Extra extra[2]; }; uniform Lights { Light lights[2]; }; out vec4 v_out; vec4 getLight(Light l) { return vec4(l.intensity) + vec4(l.color) + vec4(l.nearFar[0], l.nearFar[1], 0, 0) + vec4(l.extra[0].f) + vec4(l.extra[0].v4) + vec4(l.extra[0].v3, 0) + vec4(l.extra[1].f) + vec4(l.extra[1].v4) + vec4(l.extra[1].v3, 0); } void main() { gl_Position = vec4(0, 0, 0, 1); gl_PointSize = 1.0; v_out = getLight(lights[0]) + getLight(lights[1]); } `; const fs = `#version 300 es precision mediump float; in vec4 v_out; out vec4 outColor; void main() { outColor = v_out / 255.0; } `; const programInfo = twgl.createProgramInfo(gl, [vs, fs]); const uboInfo = twgl.createUniformBlockInfo(gl, programInfo, "Lights"); twgl.setBlockUniforms(uboInfo, { lights: [ { intensity: 11, color: [12, 13, 14, 15], nearFar: [16, 17], extra: [ { f: 18, v4: [110, 111, 112, 113], v3: [114, 115, 116]}, { f: 19, v4: [117, 118, 119, 120], v3: [121, 122, 123]}, ], }, { intensity: 21, color: [22, 23, 24, 25], nearFar: [26, 27], extra: [ { f: 28, v4: [210, 211, 212, 213], v3: [214, 215, 216]}, { f: 29, v4: [217, 218, 219, 220], v3: [221, 222, 223]}, ], }, ], }); const expected = new Float32Array([ // lights[0] 11, // intensity 0, 0, 0, // padding 12, 13, 14, 15, // color 16, 0, 0, 0, // nearFar[0] + padding 17, 0, 0, 0, // nearFar[1] + padding // extra[0] 18, // f 0, 0, 0, // padding 110, 111, 112, 113, // v4 114, 115, 116, // v3 0, // padding // extra[1] 19, // f 0, 0, 0, // padding 117, 118, 119, 120, // v4 121, 122, 123, // v3 0, // padding // lights[1] 21, // intensity 0, 0, 0, // padding 22, 23, 24, 25, // color 26, 0, 0, 0, // nearFar[0] + padding 27, 0, 0, 0, // nearFar[1] + padding // extra[0] 28, // f 0, 0, 0, // padding 210, 211, 212, 213, // v4 214, 215, 216, // v3 0, // padding // extra[1] 29, // f 0, 0, 0, // padding 217, 218, 219, 220, // v4 221, 222, 223, // v3 0, // padding ]); assertArrayEqual(uboInfo.asFloat, expected); twgl.setBlockUniforms(uboInfo, { 'lights[1].extra[1]': { v4: [301, 302, 303, 304] }, }); expected.set([301, 302, 303, 304], expected.indexOf(217)); assertArrayEqual(uboInfo.asFloat, expected); }); }); describe('createProgramFromScripts', () => { beforeEach(() => { document.querySelectorAll('script[type=foo]').forEach(elem => elem.remove()); }); afterEach(() => { document.querySelectorAll('script[type=foo]').forEach(elem => elem.remove()); }); let nextId = 0; function addShaderScripts(shaders) { return shaders.map(shader => { const id = `foobar${nextId++}`; const elem = document.createElement('script'); elem.type = 'foo'; elem.text = shader; elem.id = id; document.documentElement.appendChild(elem); return id; }); } itWebGL('succeeds with good shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromScripts(gl, addShaderScripts([ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ]), { errorCallback: msgCapturer.cb, }); assertTruthy(program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('fails with bad shader', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromScripts(gl, addShaderScripts([ 'void main() { gl_Position = vec4(0); }', 'bad code', ]), { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('fails with too few shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromScripts(gl, addShaderScripts([ 'void main() { gl_Position = vec4(0); }', ]), { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('compiles program async with callback', async function() { const {gl} = createContext(); const program = await fnWithCallbackToPromise(twgl.createProgramFromScripts)(gl, addShaderScripts([ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_FragColor = vec4(0); }`, ]), { callback(err, program) { assertFalsy(err); assertTruthy(program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(program, gl.LINK_STATUS)); gl.useProgram(program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertEqual(gl.getError(), gl.NONE); }, }); assertFalsy(program); // nothing is returned if callback }); itWebGL('compiles program async with callback with error', async function() { const {gl} = createContext(); const msgs = []; const program = await fnWithCallbackToPromise(twgl.createProgramFromScripts)(gl, addShaderScripts([ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_Frag Color = vec4(0); }`, ]), { errorCallback(msg) { msgs.push(msg); }, callback(err, program) { assertTruthy(err); assertFalsy(program); assertTruthy(msgs.length > 0); }, }); assertFalsy(program); // nothing is returned if callback }); itWebGL('compiles program async with callback with bad ids', async function() { const {gl} = createContext(); const msgs = []; const program = await fnWithCallbackToPromise(twgl.createProgramFromScripts)(gl, addShaderScripts([ `idThatDoesNotExist`, ]), { errorCallback(msg) { msgs.push(msg); }, callback(err, program) { assertTruthy(err); assertFalsy(program); assertTruthy(msgs.length > 0); }, }); assertFalsy(program); // nothing is returned if callback }); }); describe('createProgramFromSources', () => { itWebGL('succeeds with good shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromSources(gl, [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertTruthy(program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('fails with bad shader', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromSources(gl, [ 'void main() { gl_Position = vec4(0); }', 'bad code', ], { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('fails with too few shaders', () => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const program = twgl.createProgramFromSources(gl, [ 'void main() { gl_Position = vec4(0); }', ], { errorCallback: msgCapturer.cb, }); assertFalsy(program); assertTruthy(msgCapturer.hasMsgs()); }); itWebGL('compiles program async with callback', async function() { const {gl} = createContext(); const program = await fnWithCallbackToPromise(twgl.createProgramFromSources)(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_FragColor = vec4(0); }`, ], { callback(err, program) { assertFalsy(err); assertTruthy(program instanceof WebGLProgram); assertTruthy(gl.getProgramParameter(program, gl.LINK_STATUS)); gl.useProgram(program); gl.drawArrays(gl.TRIANGLES, 0, 3); assertEqual(gl.getError(), gl.NONE); }, }); assertFalsy(program); // nothing is returned if callback }); itWebGL('compiles program async with callback with error', async function() { const {gl} = createContext(); const msgs = []; const program = await fnWithCallbackToPromise(twgl.createProgramFromSources)(gl, [ `void main() { gl_Position = vec4(0); }`, `precision mediump float; void main() { gl_Frag Color = vec4(0); }`, ], { errorCallback(msg) { msgs.push(msg); }, callback(err, program) { assertTruthy(err); assertFalsy(program); assertTruthy(msgs.length > 0); }, }); assertFalsy(program); // nothing is returned if callback }); }); describe('createProgramInfosAsync', () => { itWebGL('createProgramInfosAsync', async() => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); const programInfos = await twgl.createProgramInfosAsync(gl, { foo: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], bar: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], }, { errorCallback: msgCapturer.cb, }); assertTruthy(programInfos.foo.program instanceof WebGLProgram); assertTruthy(programInfos.bar.program instanceof WebGLProgram); assertFalsy(msgCapturer.hasMsgs()); }); itWebGL('createProgramInfosAsync throws with bad shaders', async() => { const {gl} = createContext(); const msgCapturer = new MsgCapturer(); let err; try { await twgl.createProgramInfosAsync(gl, { foo: [ 'void main() { gl_Position = vec4(0); }', 'precision mediump float; void main() { gl_FragColor = vec4(0); }', ], bar: [ 'void main() { gl_Position = vec4(0); }', 'bad', ], }, { errorCallback: msgCapturer.cb, }); } catch (e) { err = e; } assertTruthy(err); assertTruthy(msgCapturer.hasMsgs()); }); }); describe('setUniforms', () => { function testBindingNullToTexture(gl) { const programInfo = twgl.createProgramInfo(gl, [ `void main() { gl_Position = vec4(0); } `, ` precision mediump float; uniform sampler2D tex; uniform sampler2D texArr[1]; void main() { texArr[0]; gl_FragColor = texture2D(tex, vec2(0)); } `, ]); gl.useProgram(programInfo.program); twgl.setUniforms(programInfo, { tex: null, texArr: [null], }); assertNoWebGLError(gl); } itWebGL(`WebGL: lets you bind a null texture`, () => { const {gl} = createContext(); testBindingNullToTexture(gl); }); itWebGL2(`WebGL2: lets you bind a null texture`, () => { const {gl} = createContext2(); testBindingNullToTexture(gl); }); }); });