twgl.js

twgl.js for Less Boilerplate, Still Raw WebGL

twgl.js 3,005 (MIT, 7.0.0, github.com/greggman/twgl.js (https://github.com/greggman/twgl.js 3,005 ), about 3,000 stars), by the author of WebGL2Fundamentals, is what webgl-helper.js became by accident: helpers that link programs, collect locations, build buffers from arrays, load textures and set uniforms by name, while you still call useProgram() and draw. Its math module appeared in Math Library Comparison; here it draws the six real covers:

Six textured covers with twgl.js's program, buffer, texture and uniform helpersJavaScript
<canvas id="c" width="1200" height="300" style="width:600px;height:150px"></canvas>
<script src="https://cdn.jsdelivr.net/npm/twgl.js@7.0.0/dist/7.x/twgl-full.min.js"></script>
<script>
const gl = document.getElementById('c').getContext('webgl2');
const programInfo = twgl.createProgramInfo(gl, [`#version 300 es
  in vec2 position; in vec2 texcoord; uniform vec2 uOffset; out vec2 vUV;
  void main() { vUV = texcoord;
                gl_Position = vec4(position * vec2(0.12, 0.72) + uOffset, 0, 1); }`,
`#version 300 es
  precision mediump float; uniform sampler2D uCover; in vec2 vUV; out vec4 c;
  void main() { c = texture(uCover, vUV); }`]);                  // compiled, linked, reflected
const quad = twgl.primitives.createXYQuadBufferInfo(gl);         // position, normal, texcoord
const sources = [1, 2, 3, 4, 5, 6].map((n) => ({ src: `booknest-cover-${n}.png`, flipY: 1 }));
const covers = twgl.createTextures(gl, { ...sources }, () => {    // runs once all have loaded
  gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, quad);
  Object.values(covers).forEach((texture, i) => {
    twgl.setUniforms(programInfo, { uCover: texture, uOffset: [-0.8 + i * 0.32, 0] });
    twgl.drawBufferInfo(gl, quad);
  });
  window.__done = true;
});
</script>
Browser output of Listing 3.158
Browser output of 158

There is no getAttribLocation(), vertexAttribPointer() or texParameteri() call: twgl matched the buffer names position and texcoord to the shader's inputs. It also wraps VAOs, framebuffers and transform feedback.

Six textured covers drawn with twgl.js's program, buffer, texture and uniform helpers, with no attribute or texParameter callsHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  canvas { display: block; width: 100%; max-width: 600px; }
  p { max-width: 600px; margin: 4px 6px; font: 11px monospace; }
</style>
<canvas id="c" width="1200" height="300"></canvas>
<p id="out"></p>
<script src="https://cdn.jsdelivr.net/npm/twgl.js@7.0.0/dist/7.x/twgl-full.min.js"></script>
<script>
// Covers painted on 2D canvases stand in for the book's PNG files.
const books = [['The Quiet Harbor', '#1e5f8c'], ['Small Steps to Big Summits', '#5b3f99'], ["The Clockmaker's Paradox", '#e09a10'],
  ['Salt and Saffron', '#3f7d3a'], ['The Quiet Harbor', '#b5452e'], ['Salt and Saffron', '#2a9d8f']];
function paintCover([title, color]) {
  const c = document.createElement('canvas'); c.width = 128; c.height = 192;
  const g = c.getContext('2d');
  g.fillStyle = color; g.fillRect(0, 0, 128, 192);
  g.fillStyle = 'rgba(0,0,0,0.25)'; g.fillRect(0, 0, 10, 192);
  g.fillStyle = '#f7f4ee'; g.fillRect(18, 120, 100, 2);
  g.font = 'bold 15px Georgia'; g.textAlign = 'center';
  title.split(' ').reduce((lines, word) => {                   // naive word wrap
    const last = lines[lines.length - 1];
    if ((last + ' ' + word).length > 11 && last) lines.push(word); else lines[lines.length - 1] = (last + ' ' + word).trim();
    return lines;
  }, ['']).forEach((line, i) => g.fillText(line, 68, 40 + i * 20));
  g.font = '10px system-ui'; g.fillText('BookNest', 68, 176);
  return c;
}
const gl = document.getElementById('c').getContext('webgl2');
const programInfo = twgl.createProgramInfo(gl, [`#version 300 es
  in vec2 position; in vec2 texcoord; uniform vec2 uOffset; out vec2 vUV;
  void main() { vUV = texcoord; gl_Position = vec4(position * vec2(0.12, 0.72) + uOffset, 0, 1); }`,
`#version 300 es
  precision mediump float; uniform sampler2D uCover; in vec2 vUV; out vec4 c;
  void main() { c = texture(uCover, vUV); }`]);                   // compiled, linked, reflected
const quad = twgl.primitives.createXYQuadBufferInfo(gl);          // position, normal, texcoord
const sources = books.map((book) => ({ src: paintCover(book), flipY: 1, min: gl.LINEAR }));
const covers = twgl.createTextures(gl, { ...sources });           // canvases upload immediately
gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quad);              // names matched to shader inputs
Object.values(covers).forEach((texture, i) => {
  twgl.setUniforms(programInfo, { uCover: texture, uOffset: [-0.8 + i * 0.32, 0] });
  twgl.drawBufferInfo(gl, quad);
});
document.getElementById('out').textContent =
  `attributes found by name: ${Object.keys(programInfo.attribSetters).join(', ')} | uniforms: ${Object.keys(programInfo.uniformSetters).join(', ')}`;
</script>