🎨 WebGL GLSL Linter
Real WebGL2 shader compilation for Visual Studio Code, with readable diagnostics, accurate source-line mapping, shader-stage detection, and optional Three.js ShaderMaterial support.
Unlike a text-only GLSL checker, WebGL GLSL Linter keeps a real WebGL2 context alive and uses the browser shader compiler to validate your code.
❓ What does this do?
It compiles your shader as you work, maps WebGL's compiler errors back to the correct source line, and explains them in language you can act on.
For example, this fragment shader tries to add vectors with different numbers of components:
#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
vec2 uv = vec2(0.5);
vec3 offset = vec3(0.1);
vec3 position = uv + offset;
// ^^^^^^^^^^^ Error: vec2 + vec3
fragColor = vec4(position, 1.0);
}
Instead of leaving you with the driver's long, difficult-to-read message, the extension highlights uv + offset and shows:
✘ Cannot add vec2 and vec3
WHY THIS HAPPENS
The left value has 2 components, but the right value has 3. GLSL arithmetic requires compatible vector sizes.
HOW TO FIX
Make both sides the same size—for example, extend uv to a vec3, or select matching components such as offset.xy.
ORIGINAL WEBGL ERROR
'+' : wrong operand types - no operation '+' exists that takes a left-hand operand of type 'vec2' and a right operand of type 'vec3'
The readable explanation appears in the editor hover and Problems panel, while the untouched compiler output remains available for debugging.
✨ Features
⚡ Real WebGL2 compilation
- Compiles shaders with a persistent WebGL2 context.
- Uses the shader compiler's real
COMPILE_STATUS and getShaderInfoLog() output.
- Lints when a file changes or when it is saved.
- Ignores outdated results when the document changes during compilation.
- Recreates the compiler safely if its background tab is closed.
The small WebGL GLSL Linter compiler tab hosts the WebGL2 context. You can ignore it and continue editing your shader; leave it open so the context remains available.
🧼 Friendly shader errors
- Converts common driver messages into plain-English explanations.
- Provides a clear Why this happens section.
- Suggests concrete ways to fix each problem.
- Groups multiple errors occurring on the same source line.
- Suppresses secondary assignment and type errors caused by an undeclared identifier.
- Keeps the untouched WebGL2 error at the bottom for debugging.
- Shows diagnostics in the editor and Problems panel.
The extension understands common errors such as:
- Undeclared identifiers
- Incompatible scalar, vector, and matrix types
- Invalid operand combinations
- Missing precision declarations
- GLSL syntax errors
- Duplicate Three.js globals
- Legacy GLSL keywords in GLSL3
- Vertex-only built-ins used in fragment shaders and vice versa
🎯 Shader-stage detection
Shader stages are selected in this order:
- File extension
- Explicit stage pragma
- Stage-specific GLSL symbols
- Configured fallback stage
Supported extensions:
| Stage |
Extensions |
| Generic |
.glsl |
| Vertex |
.vert, .vs, .vsh |
| Fragment |
.frag, .fs, .fsh |
For an explicit stage, add:
#pragma shader_stage vertex
or:
#pragma shader_stage fragment
The linter can also infer a stage from built-ins such as gl_Position, gl_FragColor, gl_FragCoord, and discard. When it reports a stage mismatch, it explains exactly why the file was classified as vertex or fragment.
🧩 Utility shader files
GLSL utility files do not have a main() entry point and cannot be compiled independently. Mark them explicitly:
#pragma shader_stage utility
vec3 saturateColor(vec3 color) {
return clamp(color, 0.0, 1.0);
}
Utility mode skips standalone compilation and clears stale diagnostics.
🟢 How does it work with Three.js?
Three.js ShaderMaterial does not send your shader source to WebGL exactly as you wrote it. Before compilation, Three.js adds precision declarations, built-in uniforms, vertex attributes, and compatibility definitions. Ordinary standalone shader validators do not know about that generated environment, so valid Three.js shaders can appear to be broken because values such as position, projectionMatrix, and modelViewMatrix seem to be missing.
For example, this is valid as a Three.js ShaderMaterial vertex shader:
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
| Standalone GLSL validation |
WebGL GLSL Linter with Three.js |
projectionMatrix is not defined |
Recognized as a Three.js-injected uniform |
modelViewMatrix is not defined |
Recognized as a Three.js-injected uniform |
position is not defined |
Recognized as a Three.js-injected vertex attribute |
| Shader is rejected because its environment is incomplete |
Shader is compiled with the environment Three.js normally provides |
Add one pragma to tell the linter that the file belongs to a Three.js ShaderMaterial:
#pragma engine threejs
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
The pragma is used only for lint compilation; Three.js does not need it at runtime. To model material.glslVersion = THREE.GLSL3, add the glsl3 mode:
#pragma engine threejs glsl3
The linter injects the relevant environment before test compilation:
- Float and integer precision defaults
- Standard matrix and camera uniforms
- Vertex attributes such as
position, normal, uv, tangent, and color
- Default-mode compatibility aliases for
attribute, varying, texture2D, and gl_FragColor
#version 300 es and native GLSL3 syntax in glsl3 mode
Hover over the engine pragma to see the exact declarations for the current shader stage.
The linter also detects declarations that conflict with injected Three.js globals and suggests using the built-in value or renaming the custom declaration.
RawShaderMaterial is different: Three.js does not prepend its built-in definitions, so everything is manual. Do not add the engine pragma when linting a raw shader; declare its version, precision, uniforms, attributes, and outputs yourself.
Fragment output variables are not injected in GLSL3. Declare your own output, such as out vec4 fragColor;.
Conditional Three.js features such as shader chunks, skinning, morph targets, and instancing are not supported yet.
🚀 Installation
From a VSIX file
- Build or download
webgl-glsl-linter.vsix.
- Open the Extensions view in VS Code.
- Open the … menu.
- Select Install from VSIX….
- Choose the VSIX and reload VS Code.
Local development installation
npm install
npm run install:local
This packages and installs the extension into your current VS Code installation.
📖 Usage
Open a supported shader file and start editing. The status bar displays GLSL WebGL2 when the compiler is ready.
Force compilation from the Command Palette:
GLSL: Lint Active Shader Now
Open the complete compiler log:
GLSL: Show Full Compiler Output
The untouched log is also printed to the Extension Host console when compilation fails.
GLSL3 Three.js fragment example
#pragma engine threejs glsl3
out vec4 fragColor;
void main() {
vec3 viewDirection = normalize(cameraPosition);
fragColor = vec4(viewDirection * 0.5 + 0.5, 1.0);
}
The linter supplies the Three.js precision and camera declarations. Your shader remains responsible for its fragment output.
⚙️ Configuration
Example configuration:
{
"offscreenGlslLinter.run": "onChange",
"offscreenGlslLinter.onChangeDebounce": 150,
"offscreenGlslLinter.lintOnOpen": true,
"offscreenGlslLinter.defaultStage": "fragment",
"offscreenGlslLinter.defaultEngine": "none",
"offscreenGlslLinter.suppressCascadingErrors": true,
"offscreenGlslLinter.friendlyErrors": true
}
| Setting |
Default |
Description |
run |
onChange |
Compile after edits or only when saving. |
onChangeDebounce |
150 |
Milliseconds to wait after an edit before compiling. |
lintOnOpen |
true |
Compile a shader when it is first opened. |
defaultStage |
fragment |
Stage used when a generic shader remains ambiguous. |
defaultEngine |
none |
Use no default engine or apply threejs compatibility automatically. |
suppressCascadingErrors |
true |
Hide follow-on errors caused by an undeclared identifier. |
friendlyErrors |
true |
Show explanations and solutions instead of only raw compiler text. |
To lint only when saving:
"offscreenGlslLinter.run": "onSave"
To apply the default Three.js environment without adding a pragma to every shader:
"offscreenGlslLinter.defaultEngine": "threejs"
🛠 Development
Install dependencies and run the test suite:
npm install
npm test
Package a VSIX:
npm run package
Open this directory in VS Code and press F5 to launch an Extension Development Host for debugging.
🙏 Acknowledgments
- GLSL and WebGL specifications by the Khronos Group
- Three.js and its shader-program conventions
- The shader-development community
Built for faster, clearer shader iteration.