Introduction
GLML (OpenGL Meta Language) is a functional DSL that compiles to GLSL fragment shaders. It brings ML-style programming to GPU shaders with HM-style type inference, first-class functions, algebraic data types, and pattern matching.
Try GLML Playground: www.glml-lang.com/playground

What GLML Gives You
- Type inference: No need to annotate every variable.
- First-class functions: Pass functions as values, store them in records and variants, build higher-order helpers that compose.
- Algebraic data types: Define
type shape = | Circle of float | Rect of float * floatandmatchon it. - Purity: Every GLML program is a pure function from
vec2(screen coordinate) tovec3(RGB color). No side effects, no mutation. - Recursion: Write recursive shaders; the compiler compiles them to bounded while loops (capped at 1000 iterations, since GLSL doesn't support recursion).
Example: Mandelbrot Shader in GLML
#extern vec2 u_resolution
#extern float u_time
// Normalizes coordinates to [-1, 1] and handles aspect ratio
let get_uv coord =
let top = 2.0 * coord - u_resolution in
let bot = #min(u_resolution.0, u_resolution.1) in
top / bot
// Recursive Mandelbrot function
// zx, zy: Current state
// cx, cy: Constant location
let rec mandel zx zy cx cy i =
if #length([zx, zy]) > 2. || i > 150. then
i
else
let next_zx = zx * zx - zy * zy + cx in
let next_zy = 2.0 * zx * zy + cy in
mandel next_zx next_zy cx cy (i + 1.)
let main (coord : vec2) =
let uv = get_uv coord in
// Coordinates for the Seahorse Valley, zooming in and out
let zoom = #exp(#sin(u_time * 0.4) * 4.5 + 3.5) in
let cx = -0.7453 + uv.0 / zoom in
let cy = 0.1127 + uv.1 / zoom in
// Mandelbrot starting at <0., 0.>
let iter = mandel 0.0 0.0 cx cy 0.0 in
if iter > 149.0 then
[ 0.0, 0.0, 0.0 ]
else
let n = iter / 150.0 in
#sin(n * [10.0, 20.0, 30.0] + u_time) * 0.5 + 0.5
Program Structure
Every GLML program must define a main function with the signature main : vec2 -> vec3, a pure function from pixel coordinates on the fragment to the RGB color for that pixel.
External uniforms from the host application are declared with #extern:
#extern vec2 u_resolution
#extern float u_time
Getting Started
Web Playground
The fastest way to try GLML is the online playground at glml-lang.com/playground. Paste any GLML program or select one of the examples and see the compiled GLSL output immediately. Programs can be run with ctrl-enter.
Building from Source
With Nix (recommended)
git clone https://github.com/glml-lang/glml
cd glml
nix develop # enters a shell with all dependencies
With opam
Requires OCaml 5.3.0+:
git clone https://github.com/glml-lang/glml
cd glml
opam switch create . 5.3.0
opam install . --deps-only
eval $(opam env)
Compiling a Shader
Compile a .glml file to GLSL and print to stdout:
dune exec GLML -- build examples/rainbow.glml
Write the output to a file:
dune exec GLML -- build examples/rainbow.glml -o shader.glsl
Inspecting Compiler Passes
GLML has a multi-pass pipeline. You can dump the AST at any pass for debugging:
# Dump all passes to stdout
dune exec GLML -- build examples/rainbow.glml -p all
# Dump a specific pass
dune exec GLML -- build examples/rainbow.glml -p typecheck
# Dump all passes to files in a directory
dune exec GLML -- build examples/rainbow.glml -p all -d /tmp/passes
# List available pass names
dune exec GLML -- list-passes
Running Tests
make test # run all expect tests
dune promote # accept new output after intentional changes
Information Dump
This page contains a dump of current TODO items and unorganized thoughts and tasks. This will likely stay a mess.
Tasks / Bugs
- Allow binary operations and pipes to be used as curried functions
- Nested destructing, removing mat pattern case
- User defined constraints for broadcasting/others, eg:
let f (x : a) (y : b) : c where (a b +> c) = x + y - Type annotations for arbitrary terms
- Type aliases with parameters
- Curried builtin functions for partial application
- Auto lift
f : float -> floatto work over vecs? - Add builtin GLSL function callers or GLSL extern libraries
uintBitsToFloatinstead of fat structs to represent variants, instead storing as uvec4 in raw bits- Reuse fields with same type for structs / defunctionalization
- Tuples and static arrays
whenclause for match statements- Add types to new passes like
specialize_params - Potentially some kind of recursive types like
type list['a] = Nil | Cons of 'a * list['a] - Passing constraints to mono is a bit weird, they should concretize that in
typecheckstep - Merging specialize and mono passes since they interplay like they're supposed to be the same pass
- Mutual recursion
- Add a guide or overview to playground
- Refactor Makefile if needed to shared playground build files, since we rebuild playground on serve
- Add common GLSL builtins:
#radians/#degrees,#refract,#faceforward,#dFdx,#dFdy,#fwidth,#matrixCompMult,#transpose,#inverse,#determinant
Example Ideas
- Raymarched volumetric clouds
- Buffer passing uses (e.g. Game of Life)
- Lava lamp-like example
- Example storing functions and hotswapping functions during executions to justify DFns in variants
- Pathtracing
- Make logo with GLML (finish beaver)
Long Term Tasks
- Update GLML screenshot
- Implicit error field added to every function to propagate error color back
- Add support for LSP hover or something, at least a simple [inspect] for the playground
- Size dependent types
- Swizzle syntax or some kind of rank polymorphism
- Function
inlining/specialize(but likely everything is specialized) - Dead code elimination
- Constant folding/propagation (Sparse conditional constant propagation)
- Doc strings or emission of helpful comments
- Better benchmarking tests
- Add sliders in playground to change values
- Update blog posts
- Set up Neovim/Emacs/Treesitter plugins
- Set up Github organization
- Add language reference and examples to mdbook
Potentially Interesting Thoughts
wasm_of_ocamlbuild?Coreseems to causeError: Base_am_testing not implemented- Write Nix derivation for Javascript and OCaml bindings
- Emit on compilation what data needs to be passed from host
- Indexing vectors by arbitrary terms?
- Differentiate int and float division explicitly
- Have
int <: floatbe a true subtype (currently can't assignlet (x : option[float]) = Some 5) - WebGPU backend for computer shaders and SSBOs?
- Export to shadertoy?