A C-style preprocessor rewrites the source before compilation; each directive starts its own line. #version 300 es must come first. #define and #undef manage macros; #if, #ifdef, #ifndef, #elif, #else and #endif include or skip code; #error stops with a message; #extension name : enable turns on an extension before any code; #pragma and #line pass hints and renumber lines. The predefined macros are __LINE__, __VERSION__, GL_ES and GL_FRAGMENT_PRECISION_HIGH. There is no #include:
show('result = vec4(__VERSION__, __LINE__, GL_ES, GL_FRAGMENT_PRECISION_HIGH);');
show('\n#if __VERSION__ >= 300\n result = vec4(3.0);\n#else\n result = vec4(1.0);\n#endif\n');
show('\n#include "lighting.glsl"\nresult = vec4(0.0);');300, 3, 1, 1 3, 3, 3, 3 ERROR: 0:4: 'include' : invalid directive name
The snippets begin with \n because GLH.evaluate() places the body right after void main() {, and a directive must start a line. Spellings from older tutorials, #undefine and #version 101, do not exist, and ANGLE 2,592 rejects both.
<!doctype html>
<style>
body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.names { display: flex; max-width: 600px; text-align: center; font-family: monospace; font-size: 11px; }
.names div { flex: 1; padding: 4px 2px; }
pre { max-width: 600px; margin: 4px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd; font-size: 11px; white-space: pre-wrap; }
</style>
<canvas id="view" width="600" height="200"></canvas>
<div class="names"><div>(no defines)</div><div>#define SPINE</div><div>#define SPINE + #define GLOSS 0.6</div></div>
<pre id="out"></pre>
<script>
const gl = document.getElementById('view').getContext('webgl2');
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); }`;
// The shared body; #version must stay the very first line, so defines go after it.
const coverBody = `
precision mediump float;
uniform vec2 uOrigin;
out vec4 outColor;
void main() {
vec2 uv = (gl_FragCoord.xy - uOrigin) / vec2(200.0);
vec3 color = vec3(0.71, 0.27, 0.18); // Salt and Saffron's cover
#ifdef SPINE
color *= mix(0.55, 1.0, smoothstep(0.24, 0.27, uv.x));
#endif
#if defined(GLOSS) && __VERSION__ >= 300
color += GLOSS * pow(max(0.0, 1.0 - abs(uv.x + uv.y - 1.1) * 4.0), 3.0);
#endif
outColor = vec4(color, 1.0);
}`;
function compile(type, src) {
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));
return s;
}
function variant(defines) {
const p = gl.createProgram();
gl.attachShader(p, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(p, compile(gl.FRAGMENT_SHADER, '#version 300 es\n' + defines + coverBody));
gl.linkProgram(p);
return p;
}
gl.bindVertexArray(gl.createVertexArray());
gl.enable(gl.SCISSOR_TEST);
['', '#define SPINE\n', '#define SPINE\n#define GLOSS 0.6\n'].forEach((defines, i) => {
const p = variant(defines);
gl.useProgram(p);
gl.uniform2f(gl.getUniformLocation(p, 'uOrigin'), i * 200, 0);
gl.scissor(i * 200 + 30, 10, 140, 180); // one cover per variant
gl.drawArrays(gl.TRIANGLES, 0, 3);
});
// Read the predefined macros back: render them into a 1x1 float target and readPixels.
gl.disable(gl.SCISSOR_TEST);
gl.getExtension('EXT_color_buffer_float');
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, 1, 1);
gl.bindFramebuffer(gl.FRAMEBUFFER, gl.createFramebuffer());
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
gl.viewport(0, 0, 1, 1);
function evaluate(body) {
try {
const p = gl.createProgram();
gl.attachShader(p, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(p, compile(gl.FRAGMENT_SHADER,
`#version 300 es\nprecision highp float; out vec4 result;\nvoid main() {\n${body}\n}`));
gl.linkProgram(p); gl.useProgram(p);
gl.drawArrays(gl.TRIANGLES, 0, 3);
const v = new Float32Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, v);
return v.join(', ');
} catch (e) { return e.message.split('\n')[0]; }
}
document.getElementById('out').textContent = [
'vec4(__VERSION__, __LINE__, GL_ES, GL_FRAGMENT_PRECISION_HIGH) -> ' +
evaluate('result = vec4(__VERSION__, __LINE__, GL_ES, GL_FRAGMENT_PRECISION_HIGH);'),
'#if __VERSION__ >= 300 ... #else ... #endif -> ' +
evaluate('#if __VERSION__ >= 300\n result = vec4(3.0);\n#else\n result = vec4(1.0);\n#endif'),
'#include "lighting.glsl" -> ' + evaluate('#include "lighting.glsl"\nresult = vec4(0.0);'),
].join('\n');
</script>