I started designing Drift's syntax after trying to write shaders for Unity. What bothered me was how much shader development is shaped by concepts and conventions specific to graphics APIs and their surrounding ecosystems — such as explicit shader stages, GPU-specific types and semantics, resource bindings, varying variables, and API-specific boilerplate.
At the same time, shader languages often retain constraints and conventions inherited from older generations of graphics APIs and shading languages. Even seemingly basic language features can feel surprisingly low-level or restrictive compared to modern general-purpose languages. For example, GLSL requires functions to be declared before they are called, unless an explicit forward declaration is provided.
This led to a natural question:
Is it possible to design a language with modern syntax that abstracts away most of the graphics-specific machinery, allowing developers to write shaders as if they were writing ordinary CPU code, while still providing escape hatches for experienced graphics developers?
Drift still has some graphics-specific terminology, but its goal is to reduce it as much as possible.
This extends beyond syntax. Drift aims to provide a more modern development experience around shaders as a whole, including static diagnostics, useful compiler errors, editor tooling, and eventually a more practical debugging workflow.
Drift explores the boundaries of abstraction in shading languages and investigates where these abstractions begin to break down.
The goal is to determine how much of the underlying graphics programming model can be hidden from the developer while still providing sufficient control and predictable compilation to target graphics APIs.
-
Here is an example of a fragment shader written in Drift:
import { dot } from "@drift/math"; shader Grayscale { props { texture: sampler2d; } fragment (uv: vec2) -> color { const weights: vec3 = vec3(0.299, 0.587, 0.114); let sample = texture.sample(uv); let gray = dot(sample.rgb, weights); return color(gray, gray, gray, sample.a); } }
-
The current WebGL 1 backend compiles it to:
precision mediump float; uniform sampler2D u_texture; vec4 d_fragment_29(vec2 d_uv_30); vec4 d_fragment_29(vec2 d_uv_30) { vec3 d_weights_31 = vec3(0.299, 0.587, 0.114); vec4 d_sample_32 = texture2D(u_texture, d_uv_30); float d_gray_33 = dot(d_sample_32.rgb, d_weights_31); return vec4(d_gray_33, d_gray_33, d_gray_33, d_sample_32.a); } varying vec2 v_uv; void main() { gl_FragColor = d_fragment_29(v_uv); }
During compilation, Drift automatically lowers method calls into flat function calls, resolves overloaded function calls, performs static type checking, and maps fragment inputs to the generated shader interface.
floatvec2vec3vec4color(alias forvec4)sampler2d
- Lexer
- Parser
- Module (dependency) resolver
- Name resolver
- Type checker
- WebGL 1 code generation
Drift Source ←—————┐
↓ |
Lexer |
↓ |
Parser |
↓ |
Abstract Syntax Tree |
↓ |
Module Resolution ————┘
↓
Resolved Module
↓
Name Resolution
↓
Type Checking
↓
IR (Typed Program)
↓
WebGL 1 Backend
↓
GLSL ES 1.0
Drift
↓
Semantic Model
↓
WebGL 1
↓
GLSL ES
The Drift Playground provides an interactive browser-based environment for experimenting with Drift. The compiler runs through WebAssembly, allowing shaders to be compiled without a local installation.
This project is licensed under the MIT license. See the LICENSE file for details.