GPUs cap how many attributes, uniform vectors (one vec4 slot each; a mat4 takes four), interpolated outputs and texture units a shader may use. GLSL exposes the caps as built-in const int values, and JavaScript reads the same numbers with gl.getParameter(), such as gl.MAX_VERTEX_ATTRIBS:
show(`result = vec4(gl_MaxVertexAttribs, gl_MaxVertexUniformVectors,
gl_MaxFragmentUniformVectors, gl_MaxVertexOutputVectors);`);
show(`result = vec4(gl_MaxVertexTextureImageUnits, gl_MaxTextureImageUnits,
gl_MaxCombinedTextureImageUnits, gl_MaxDrawBuffers);`);16, 4095, 1024, 30 16, 16, 32, 8
The GLSL ES 3.00 specification guarantees only 16 attributes, 256 vertex and 224 fragment uniform vectors, 16 texture units per stage and 4 draw buffers; GLSL ES 1.00 guaranteed 8 attributes, 16 fragment uniform vectors and zero vertex texture units. SwiftShader 2,546 reports 4096 uniform vectors in both stages and 32 texture units. Design for the minimums, or query the limit and cap arrays such as lights to what the device reports.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
.chart { display: flex; max-width: 600px; gap: 8px; }
.labels { width: 190px; flex: none; }
.labels div { height: 32px; display: flex; align-items: center; justify-content: flex-end; text-align: right; }
canvas { flex: 1; min-width: 0; height: 256px; }
.key { max-width: 600px; margin-top: 4px; }
.key span { display: inline-block; width: 10px; height: 10px; margin: 0 4px 0 12px; vertical-align: -1px; }
</style>
<div class="chart">
<div class="labels" id="labels"></div>
<canvas id="view" width="400" height="256"></canvas>
</div>
<div class="key"><span style="background:#1f5f8b"></span>this device (log scale)<span style="background:#e09a10"></span>GLSL ES 3.00 guaranteed minimum</div>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const names = ['gl_MaxVertexAttribs', 'gl_MaxVertexUniformVectors', 'gl_MaxFragmentUniformVectors',
'gl_MaxVertexOutputVectors', 'gl_MaxVertexTextureImageUnits', 'gl_MaxTextureImageUnits',
'gl_MaxCombinedTextureImageUnits', 'gl_MaxDrawBuffers'];
const minimums = [16, 256, 224, 16, 16, 16, 32, 4];
// The same numbers from JavaScript, for the labels.
const params = ['MAX_VERTEX_ATTRIBS', 'MAX_VERTEX_UNIFORM_VECTORS', 'MAX_FRAGMENT_UNIFORM_VECTORS',
'MAX_VERTEX_OUTPUT_COMPONENTS', 'MAX_VERTEX_TEXTURE_IMAGE_UNITS', 'MAX_TEXTURE_IMAGE_UNITS',
'MAX_COMBINED_TEXTURE_IMAGE_UNITS', 'MAX_DRAW_BUFFERS'];
document.getElementById('labels').innerHTML = names.map((n, i) => {
let v = gl.getParameter(gl[params[i]]);
if (i === 3) v /= 4; // JavaScript reports components, GLSL vec4 slots
return `<div>${n}<br>${v} (min ${minimums[i]})</div>`;
}).join('');
// The fragment shader reads the limits itself: they are compile-time const ints.
const vs = `#version 300 es
void main() { gl_Position = vec4(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0, 0.0, 1.0); }`;
const fs = `#version 300 es
precision highp float;
uniform float uGrow;
out vec4 outColor;
const int limits[8] = int[](gl_MaxVertexAttribs, gl_MaxVertexUniformVectors,
gl_MaxFragmentUniformVectors, gl_MaxVertexOutputVectors, gl_MaxVertexTextureImageUnits,
gl_MaxTextureImageUnits, gl_MaxCombinedTextureImageUnits, gl_MaxDrawBuffers);
const int minimums[8] = int[](16, 256, 224, 16, 16, 16, 32, 4);
float bar(int value) { return log2(float(value)) / 13.0 * 400.0; } // 1..8192 on a log axis
void main() {
int row = 7 - int(gl_FragCoord.y / 32.0);
float y = mod(gl_FragCoord.y, 32.0), x = gl_FragCoord.x;
vec3 color = vec3(0.97, 0.96, 0.93);
if (y > 8.0 && y < 26.0 && x < bar(limits[row]) * uGrow) color = vec3(0.12, 0.37, 0.55);
if (y > 4.0 && y < 30.0 && abs(x - bar(minimums[row])) < 1.5) color = vec3(0.88, 0.6, 0.06);
if (mod(x, 400.0 / 13.0) < 1.0) color *= 0.92; // a faint line at every power of two
outColor = vec4(color, 1.0);
}`;
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
gl.bindVertexArray(gl.createVertexArray());
const uGrow = gl.getUniformLocation(program, 'uGrow');
function frame(ms) {
const grow = Math.min(1, ms / 800);
gl.uniform1f(uGrow, grow);
gl.drawArrays(gl.TRIANGLES, 0, 3);
if (grow < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>