getContext('webgl2') returns a context or null; 'webgl' returns the older WebGL 1 context. A canvas keeps the first kind of context it hands out, so decide once, then query what you got:
const canvas = document.querySelector('canvas');
let gl = canvas.getContext('webgl2', { antialias: true });
const isWebGL2 = gl !== null;
if (!gl) gl = canvas.getContext('webgl'); // GLSL ES 1.00 shaders only
if (!gl) throw new Error('WebGL is unavailable: show a static image instead');
console.log('WebGL 2:', isWebGL2, '|', gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
const info = gl.getExtension('WEBGL_debug_renderer_info'); // may be masked or absent
if (info) console.log(gl.getParameter(info.UNMASKED_RENDERER_WEBGL));
console.log('MAX_TEXTURE_SIZE', gl.getParameter(gl.MAX_TEXTURE_SIZE));Output
WebGL 2: true | WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium) ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 (0x00001F82) Direct3D11 vs_5_0 ps_5_0, D3D11) MAX_TEXTURE_SIZE 16384
With WebGL 2 at 96.44% of global usage (caniuse.com 3,038 ), a WebGL 1 branch with GLSL ES 1.00 shaders rarely pays; Three.js 30,815 dropped WebGL 1 in release 163. Still handle null and context loss (Context and WebGL 2).
<!doctype html>
<style>
body { margin: 0; font: 13px system-ui, sans-serif; background: #f7f4ee; color: #333; }
.row { display: flex; gap: 12px; max-width: 600px; align-items: center; }
canvas { width: 240px; height: 200px; background: #fff; border: 1px solid #ddd; flex: none; }
table { border-collapse: collapse; flex: 1; }
td { padding: 4px 6px; border-bottom: 1px solid #e3ddd0; vertical-align: top; word-break: break-word; }
td:first-child { color: #777; font-size: 11px; white-space: nowrap; }
</style>
<div class="row">
<canvas id="view" width="480" height="400"></canvas>
<table id="report"></table>
</div>
<script>
const canvas = document.getElementById('view');
let gl = canvas.getContext('webgl2', { antialias: true });
const isWebGL2 = gl !== null;
if (!gl) gl = canvas.getContext('webgl'); // GLSL ES 1.00 shaders only
if (!gl) throw new Error('WebGL is unavailable: show a static image instead');
// Pick shader sources that match the context we actually received.
const vs = isWebGL2
? `#version 300 es
in vec2 aPos; in vec3 aColor; out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos, 0.0, 1.0); }`
: `attribute vec2 aPos; attribute vec3 aColor; varying vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos, 0.0, 1.0); }`;
const fs = isWebGL2
? `#version 300 es
precision mediump float; in vec3 vColor; out vec4 outColor;
void main() { outColor = vec4(vColor, 1.0); }`
: `precision mediump float; varying vec3 vColor;
void main() { gl_FragColor = vec4(vColor, 1.0); }`;
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
const shader = gl.createShader(type);
gl.shaderSource(shader, src.trim()); // #version must be the very first line
gl.compileShader(shader);
gl.attachShader(program, shader);
}
gl.linkProgram(program);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.8, 0.12, 0.37, 0.55,
-0.8, -0.7, 0.87, 0.47, 0.13,
0.8, -0.7, 0.25, 0.55, 0.30]), gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(program, 'aPos');
const aColor = gl.getAttribLocation(program, 'aColor');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 20, 0);
gl.enableVertexAttribArray(aColor);
gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, 20, 8);
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Report what we got.
const info = gl.getExtension('WEBGL_debug_renderer_info'); // may be masked or absent
const rows = [
['Context', isWebGL2 ? 'WebGL 2 (OpenGL ES 3.0)' : 'WebGL 1 fallback (OpenGL ES 2.0)'],
['VERSION', gl.getParameter(gl.VERSION)],
['SHADING_LANGUAGE_VERSION', gl.getParameter(gl.SHADING_LANGUAGE_VERSION)],
['Renderer', info ? gl.getParameter(info.UNMASKED_RENDERER_WEBGL) : '(masked)'],
['MAX_TEXTURE_SIZE', gl.getParameter(gl.MAX_TEXTURE_SIZE)],
['antialias', gl.getContextAttributes().antialias],
];
document.getElementById('report').innerHTML =
rows.map(([k, v]) => `<tr><td>${k}</td><td>${v}</td></tr>`).join('');
</script>