cd /news/developer-tools/modern-glsl-shader-development-in-ze… · home topics developer-tools article
[ARTICLE · art-134810] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Modern GLSL Shader Development in Zed & VS Code: Zero Setup, Real-Time Diagnostics

A developer built an open-source, Rust-based GLSL language server that provides zero-setup shader development in both Zed and VS Code, using AI assistance during development. The tool validates shaders against the glslang compiler backend in real time, offering instant diagnostics, autocompletion, signature help, go-to-definition, and background #include resolution for shared GLSL headers. It supports per-file targeting of Desktop OpenGL and Vulkan SPIR-V via a // @target: vulkan directive.

by read4 min views1 publishedSep 19, 2026

Writing GLSL shaders often feels like stepping ten years back in time compared to modern web or backend development. Whether you are targeting modern Desktop OpenGL or Vulkan, finding an editor setup that just works out of the box is surprisingly difficult.

Recently, while trying to get a decent shader workflow running across Zed and VS Code, I kept hitting the same roadblocks:

#version 330 to 460 core or Vulkan SPIR-V, they start throwing false, confusing errors.#include resolution: Instead of constantly fighting with fragmented tools, I decided to build a unified solution with the help of AI: a lightweight Rust-based language server supporting both Zed and VS Code with zero manual setup.

The project is completely open source. If you want to jump straight into the code or install it for your editor, you can find the repository here:

.xyzw, .rgba), functions, and structs. glslang) in the background. To see how this works, let's create a basic vertex shader inside a glsl/ directory named standart.vert:

#version 460 core

layout(location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos, 1.0);
}

Now, let's say we accidentally omit the semicolon on the gl_Position assignment:

void main()
{
    gl_Position = vec4(aPos, 1.0) // Missing semicolon
}

The moment you stop typing, the language server validates the shader against the compiler backend and immediately highlights the syntax error with precise line and column information:

No need to recompile your engine or run command-line tools—you catch typos the second you make them.

In real-world projects, shaders share math helpers, camera matrices, and lighting calculations. To keep things modular, let's create a shared header named common.glsl:

mat4 calculateMVP(mat4 modelMatrix, mat4 viewMatrix, mat4 projectionMatrix) {
    return projectionMatrix * viewMatrix * modelMatrix;
}

vec3 TransformToWorldSpace(mat4 modelMatrix, vec3 position) {
    return vec3(modelMatrix * vec4(position, 1.0));
}

vec4 TransformToClipSpace(mat4 modelMatrix, mat4 viewMatrix, mat4 projectionMatrix, vec4 position) {
    return projectionMatrix * viewMatrix * modelMatrix * position;
}

vec3 TransformNormalToWorldSpace(mat3 normalMatrix, vec3 normal) {
    return normalize(normalMatrix * normal);
}

vec3 TransformNormalToWorldSpace(mat4 normalMatrix, vec3 normal) {
    return normalize(mat3(normalMatrix) * normal);
}

(We assume normal matrix inversion/transposition is pre-calculated on the CPU side).

Standard GLSL does not support #include without compiler extensions. By enabling GL_ARB_shading_language_include (or GL_GOOGLE_include_directive), we can cleanly include our shared header inside standart.vert:

#version 460 core

#extension GL_ARB_shading_language_include : enable
#include "common.glsl"

layout(location = 0) in vec3 aPos; out vec3 FragPos;
layout(location = 0) in vec3 aNormal; out vec3 Normal;
layout(location = 0) in vec2 aTexCoords; out vec2 TexCoords;

uniform mat4 model, normal;
uniform mat4 view, projection;

void main()
{
    TexCoords = aTexCoords;
    FragPos = TransformToWorldSpace(model, aPos);
    Normal = TransformNormalToWorldSpace(normal, aNormal);
    gl_Position = TransformToClipSpace(model, view, projection, vec4(aPos, 1.0));
}

The language server immediately parses common.glsl in the background. You get instant autocompletion for all helper functions:

Along with full signature help and parameter hints while typing function arguments:

You can also press F12 (Go to Definition) on functions like TransformToClipSpace to jump straight to their definition in common.glsl.

If your engine targets Vulkan, your shader requirements are different: you use descriptor sets (set = X, binding = Y), push constants, and compile directly to SPIR-V.

You can tell the language server to validate specifically for Vulkan on a per-file basis using the // @target: vulkan directive at the top of your shader:

// @target: vulkan
#version 460

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;

layout(set = 0, binding = 0) uniform CameraBuffer {
    mat4 view;
    mat4 projection;
} camera;

layout(push_constant) uniform PushConstants {
    mat4 model;
} pc;

layout(location = 0) out vec3 outFragPos;
layout(location = 1) out vec3 outNormal;
layout(location = 2) out vec2 outTexCoord;

void main() {
    outFragPos = vec3(pc.model * vec4(inPosition, 1.0));
    outNormal = mat3(pc.model) * inNormal;
    outTexCoord = inTexCoord;

    gl_Position = camera.projection * camera.view * vec4(outFragPos, 1.0);
}

With // @target: vulkan, the language server automatically switches the compiler backend to strict SPIR-V validation mode. It properly checks descriptor sets, push constant alignment, and Vulkan-specific qualifiers without complaining about missing OpenGL uniforms:

You do not even need to write per-file directives. You can configure the target API globally or per-workspace in your settings.json:

{
  "lsp": {
    "glsl_validator": {
      "initialization_options": {
        "target_api": "vulkan"
      }
    }
  }
}

(You can always override this per-file using // @target: vulkan or // @target: opengl at the top of any shader).

Getting a fast, zero-configuration GLSL environment working across Zed and VS Code solved a huge daily pain point in my graphics workflow.

Next up, I built the exact same zero-bloat workflow for HLSL and Unity ShaderLab, powered by Microsoft's DirectX Shader Compiler (dxc). I will be sharing a breakdown of that setup in a follow-up post.

If you want to try it out, you can find the project, installation guides, and full documentation on GitHub:

👉 glsl-extended on GitHub

Feel free to open an issue if you encounter edge cases or have feature requests.

What does your current shader development workflow look like? Which editor do you rely on most for writing shaders? Let me know in the comments!

── more in #developer-tools 4 stories · sorted by recency
── more on @zed 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/modern-glsl-shader-d…] indexed:0 read:4min 2026-09-19 ·